Skip to content

Commit 1ddd9ba

Browse files
committed
feat(portal): support portal
1 parent 0ea3d93 commit 1ddd9ba

File tree

14 files changed

+240
-2
lines changed

14 files changed

+240
-2
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 carbon-components-svelte@0.89.2.
3+
> 167 components exported from carbon-components-svelte@0.89.2.
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)
@@ -1435,6 +1437,20 @@ None.
14351437
| click | forwarded | -- |
14361438
| keydown | forwarded | -- |
14371439

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

14401456
### Props
@@ -2835,6 +2851,22 @@ None.
28352851
| :------------ | :--------- | :------------------------------------ |
28362852
| click:outside | dispatched | <code>{ target: HTMLElement; }</code> |
28372853

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

28402872
### Props

docs/package-lock.json

Lines changed: 1 addition & 0 deletions
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",
@@ -5241,6 +5241,16 @@
52415241
"name": "div | button | svg"
52425242
}
52435243
},
5244+
{
5245+
"moduleName": "FloatingPortal",
5246+
"filePath": "src/Portal/FloatingPortal.svelte",
5247+
"props": [],
5248+
"moduleExports": [],
5249+
"slots": [],
5250+
"events": [],
5251+
"typedefs": [],
5252+
"generics": null
5253+
},
52445254
{
52455255
"moduleName": "FluidForm",
52465256
"filePath": "src/FluidForm/FluidForm.svelte",
@@ -10851,6 +10861,22 @@
1085110861
"name": "div"
1085210862
}
1085310863
},
10864+
{
10865+
"moduleName": "Portal",
10866+
"filePath": "src/Portal/Portal.svelte",
10867+
"props": [],
10868+
"moduleExports": [],
10869+
"slots": [
10870+
{
10871+
"name": "__default__",
10872+
"default": true,
10873+
"slot_props": "{}"
10874+
}
10875+
],
10876+
"events": [],
10877+
"typedefs": [],
10878+
"generics": null
10879+
},
1085410880
{
1085510881
"moduleName": "ProgressBar",
1085610882
"filePath": "src/ProgressBar/ProgressBar.svelte",

docs/src/pages/components/Portal.svx

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)