Skip to content

Commit f8e5348

Browse files
committed
feat(portal): support portal
1 parent f755422 commit f8e5348

File tree

14 files changed

+240
-3
lines changed

14 files changed

+240
-3
lines changed

COMPONENT_INDEX.md

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Component Index
22

3-
> 165 components exported from [email protected].
3+
> 167 components exported from [email protected].
44
55
## Components
66

@@ -45,6 +45,7 @@
4545
- [`FileUploaderItem`](#fileuploaderitem)
4646
- [`FileUploaderSkeleton`](#fileuploaderskeleton)
4747
- [`Filename`](#filename)
48+
- [`FloatingPortal`](#floatingportal)
4849
- [`FluidForm`](#fluidform)
4950
- [`Form`](#form)
5051
- [`FormGroup`](#formgroup)
@@ -95,6 +96,7 @@
9596
- [`PaginationSkeleton`](#paginationskeleton)
9697
- [`PasswordInput`](#passwordinput)
9798
- [`Popover`](#popover)
99+
- [`Portal`](#portal)
98100
- [`ProgressBar`](#progressbar)
99101
- [`ProgressIndicator`](#progressindicator)
100102
- [`ProgressIndicatorSkeleton`](#progressindicatorskeleton)
@@ -1434,6 +1436,20 @@ None.
14341436
| click | forwarded | -- |
14351437
| keydown | forwarded | -- |
14361438

1439+
## `FloatingPortal`
1440+
1441+
### Props
1442+
1443+
None.
1444+
1445+
### Slots
1446+
1447+
None.
1448+
1449+
### Events
1450+
1451+
None.
1452+
14371453
## `FluidForm`
14381454

14391455
### Props
@@ -2833,6 +2849,22 @@ None.
28332849
| :------------ | :--------- | :------------------------------------ |
28342850
| click:outside | dispatched | <code>{ target: HTMLElement; }</code> |
28352851

2852+
## `Portal`
2853+
2854+
### Props
2855+
2856+
None.
2857+
2858+
### Slots
2859+
2860+
| Slot name | Default | Props | Fallback |
2861+
| :-------- | :------ | :---- | :------- |
2862+
| -- | Yes | -- | -- |
2863+
2864+
### Events
2865+
2866+
None.
2867+
28362868
## `ProgressBar`
28372869

28382870
### Props

docs/package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/src/COMPONENT_API.json

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"total": 165,
2+
"total": 167,
33
"components": [
44
{
55
"moduleName": "Accordion",
@@ -5229,6 +5229,16 @@
52295229
"name": "div | button | svg"
52305230
}
52315231
},
5232+
{
5233+
"moduleName": "FloatingPortal",
5234+
"filePath": "src/Portal/FloatingPortal.svelte",
5235+
"props": [],
5236+
"moduleExports": [],
5237+
"slots": [],
5238+
"events": [],
5239+
"typedefs": [],
5240+
"generics": null
5241+
},
52325242
{
52335243
"moduleName": "FluidForm",
52345244
"filePath": "src/FluidForm/FluidForm.svelte",
@@ -10827,6 +10837,22 @@
1082710837
"name": "div"
1082810838
}
1082910839
},
10840+
{
10841+
"moduleName": "Portal",
10842+
"filePath": "src/Portal/Portal.svelte",
10843+
"props": [],
10844+
"moduleExports": [],
10845+
"slots": [
10846+
{
10847+
"name": "__default__",
10848+
"default": true,
10849+
"slot_props": "{}"
10850+
}
10851+
],
10852+
"events": [],
10853+
"typedefs": [],
10854+
"generics": null
10855+
},
1083010856
{
1083110857
"moduleName": "ProgressBar",
1083210858
"filePath": "src/ProgressBar/ProgressBar.svelte",
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<script>
2+
import { Portal } from "carbon-components-svelte";
3+
import Preview from "../../components/Preview.svelte";
4+
</script>
5+
6+
## Default
7+
8+
<FileSource src="/framed/Portal/BasicPortal" />
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<script>
2+
import { Portal } from "carbon-components-svelte";
3+
</script>
4+
5+
<Portal>Hello world</Portal>
6+
<Portal>Another portal</Portal>

package-lock.json

Lines changed: 26 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"release": "standard-version && npm run build:docs"
4242
},
4343
"dependencies": {
44+
"@floating-ui/dom": "^1.6.13",
4445
"@ibm/telemetry-js": "^1.5.0",
4546
"flatpickr": "4.6.9"
4647
},

src/Portal/FloatingPortal.svelte

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<script>
2+
import { onMount } from "svelte";
3+
import { computePosition, autoUpdate } from "@floating-ui/dom";
4+
import Portal from "./Portal.svelte";
5+
6+
/** @type {null | HTMLButtonElement} */
7+
let button = null;
8+
9+
/** @type {null | HTMLDivElement} */
10+
let tooltip = null;
11+
12+
let togglePortal = false;
13+
let padding = 10;
14+
15+
/** @type {null | ReturnType<typeof autoUpdate>} */
16+
let cleanup = null;
17+
18+
function updatePosition() {
19+
if (!button || !tooltip) return;
20+
computePosition(button, tooltip).then(({ x, y }) => {
21+
if (!tooltip) return;
22+
Object.assign(tooltip.style, {
23+
left: `${x}px`,
24+
top: `${y}px`,
25+
});
26+
});
27+
}
28+
29+
onMount(() => {
30+
updatePosition();
31+
32+
if (button && tooltip) {
33+
cleanup = autoUpdate(button, tooltip, updatePosition);
34+
}
35+
36+
return () => {
37+
return cleanup?.();
38+
};
39+
});
40+
</script>
41+
42+
<button
43+
type="button"
44+
style="padding: {padding}px"
45+
bind:this={button}
46+
on:click={() => (padding += 2)}
47+
>
48+
Click to increase padding
49+
</button>
50+
51+
<button
52+
on:click={() => {
53+
togglePortal = !togglePortal;
54+
}}
55+
>
56+
Toggle portal
57+
</button>
58+
{#if togglePortal}
59+
<Portal>
60+
<div bind:this={tooltip}>Floating menu</div>
61+
</Portal>
62+
{/if}

src/Portal/Portal.svelte

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<script context="module">
2+
/** @type {HTMLDivElement | null} */
3+
let portalContainer = null;
4+
5+
let instances = 0;
6+
7+
/**
8+
* Creates or returns the shared portal container
9+
* @returns {HTMLDivElement}
10+
*/
11+
function getPortalContainer() {
12+
if (!portalContainer && typeof document !== "undefined") {
13+
portalContainer = document.createElement("div");
14+
portalContainer.setAttribute("data-portal", "");
15+
document.body.appendChild(portalContainer);
16+
}
17+
18+
return portalContainer;
19+
}
20+
</script>
21+
22+
<script>
23+
import { onMount } from "svelte";
24+
25+
/** @type {null | HTMLDivElement} */
26+
let portal = null;
27+
28+
onMount(() => {
29+
instances++;
30+
const container = getPortalContainer();
31+
32+
if (portal && container) {
33+
container.appendChild(portal);
34+
}
35+
36+
return () => {
37+
instances--;
38+
39+
if (portal?.parentNode) {
40+
portal.parentNode.removeChild(portal);
41+
}
42+
43+
if (instances === 0 && portalContainer?.parentNode) {
44+
portalContainer.parentNode.removeChild(portalContainer);
45+
portalContainer = null;
46+
}
47+
};
48+
});
49+
</script>
50+
51+
<div bind:this={portal}>
52+
<slot />
53+
</div>

src/Portal/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { default as Portal } from "./Portal.svelte";
2+
export { default as FloatingPortal } from "./FloatingPortal.svelte";

0 commit comments

Comments
 (0)