Skip to content

Commit e659564

Browse files
committed
popover visual testing
1 parent 093b866 commit e659564

File tree

5 files changed

+241
-0
lines changed

5 files changed

+241
-0
lines changed

packages/floating-ui-svelte/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,10 @@
4040
"@testing-library/jest-dom": "^6.6.3",
4141
"@testing-library/svelte": "^5.2.6",
4242
"@testing-library/user-event": "^14.5.2",
43+
"bits-ui": "1.0.0-next.78",
4344
"clsx": "^2.1.1",
4445
"csstype": "^3.1.3",
46+
"lucide-svelte": "^0.469.0",
4547
"resize-observer-polyfill": "^1.5.1",
4648
"svelte": "^5.17.3",
4749
"tailwindcss": "4.0.0-beta.9",
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<script lang="ts">
2+
import Button from "../button.svelte";
3+
import Popover from "./popover.svelte";
4+
import { Checkbox } from "bits-ui";
5+
import Check from "lucide-svelte/icons/check";
6+
7+
let modal = $state(true);
8+
</script>
9+
10+
<h1 class="text-5xl font-bold mb-8">Popover</h1>
11+
<div
12+
class="grid place-items-center border border-slate-400 rounded lg:w-[40rem] h-[20rem] mb-4">
13+
<Popover {modal} bubbles={true}>
14+
{#snippet children(ref, props)}
15+
<Button bind:ref={ref.current} {...props}>My button</Button>
16+
{/snippet}
17+
{#snippet content({ labelId, descriptionId, close })}
18+
<h2 id={labelId} class="text-2xl font-bold mb-2">Title</h2>
19+
<p id={descriptionId} class="mb-2">Description</p>
20+
<Popover {modal} bubbles={true}>
21+
{#snippet content({ labelId, descriptionId, close })}
22+
<h2 id={labelId} class="text-2xl font-bold mb-2">Title</h2>
23+
<p id={descriptionId} class="mb-2">Description</p>
24+
<Popover {modal} bubbles={false}>
25+
{#snippet children(ref, props)}
26+
<Button bind:ref={ref.current} {...props}
27+
>My button</Button>
28+
{/snippet}
29+
{#snippet content({ labelId, descriptionId, close })}
30+
<h2 id={labelId} class="text-2xl font-bold mb-2">
31+
Title
32+
</h2>
33+
<p id={descriptionId} class="mb-2">Description</p>
34+
<button onclick={close} class="font-bold">
35+
Close
36+
</button>
37+
{/snippet}
38+
</Popover>
39+
<button onclick={close} class="font-bold"> Close </button>
40+
{/snippet}
41+
{#snippet children(ref, props)}
42+
<Button bind:ref={ref.current} {...props}>My button</Button>
43+
{/snippet}
44+
</Popover>
45+
46+
<button onclick={close} class="font-bold"> Close </button>
47+
{/snippet}
48+
</Popover>
49+
</div>
50+
51+
<label class="flex items-center">
52+
<Checkbox.Root
53+
class="bg-slate-900 text-white rounded w-5 h-5 mr-2 grid place-items-center shadow"
54+
checked={modal}
55+
onCheckedChange={(value) => (modal = value)}>
56+
{#snippet children({ checked })}
57+
{#if checked}
58+
<Check class="size-5" />
59+
{/if}
60+
{/snippet}
61+
</Checkbox.Root>
62+
Modal focus management
63+
</label>
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<script lang="ts">
2+
import type { Placement } from "@floating-ui/utils";
3+
import type { Snippet } from "svelte";
4+
import {
5+
box,
6+
type WritableBox,
7+
} from "../../../../src/internal/box.svelte.js";
8+
import {
9+
useClick,
10+
useDismiss,
11+
useFloating,
12+
useFloatingNodeId,
13+
useId,
14+
useInteractions,
15+
useRole,
16+
} from "../../../../src/index.js";
17+
import { autoUpdate, flip, offset, shift } from "@floating-ui/dom";
18+
import FloatingNode from "../../../../src/components/floating-tree/floating-node.svelte";
19+
import FloatingPortal from "../../../../src/components/floating-portal/floating-portal.svelte";
20+
import FloatingFocusManager from "../../../../src/components/floating-focus-manager/floating-focus-manager.svelte";
21+
22+
let {
23+
content,
24+
placement,
25+
children,
26+
modal = true,
27+
bubbles = true,
28+
}: {
29+
content: Snippet<
30+
[
31+
{
32+
close: () => void;
33+
labelId: string;
34+
descriptionId: string;
35+
},
36+
]
37+
>;
38+
placement?: Placement;
39+
modal?: boolean;
40+
children?: Snippet<
41+
[ref: WritableBox<Element | null>, props: Record<string, unknown>]
42+
>;
43+
bubbles?: boolean;
44+
} = $props();
45+
46+
let open = $state(false);
47+
48+
const nodeId = useFloatingNodeId();
49+
const ref = box<Element | null>(null);
50+
51+
const f = useFloating({
52+
reference: () => ref.current,
53+
onReferenceChange: (v) => {
54+
ref.current = v;
55+
},
56+
open: () => open,
57+
onOpenChange: (v) => {
58+
open = v;
59+
},
60+
nodeId,
61+
placement: () => placement,
62+
middleware: [offset(10), flip(), shift()],
63+
whileElementsMounted: autoUpdate,
64+
});
65+
66+
const id = useId();
67+
const labelId = `${id}-label`;
68+
const descriptionId = `${id}-description`;
69+
70+
const ints = useInteractions([
71+
useClick(f.context),
72+
useRole(f.context),
73+
useDismiss(f.context, { bubbles: () => bubbles }),
74+
]);
75+
</script>
76+
77+
<FloatingNode id={nodeId}>
78+
{@render children?.(
79+
ref,
80+
ints.getReferenceProps({ "data-open": open ? "" : undefined })
81+
)}
82+
83+
{#if open}
84+
<FloatingPortal>
85+
<FloatingFocusManager context={f.context} {modal}>
86+
<div
87+
class="bg-white border border-slate-900/10 shadow-md rounded px-4 py-6 bg-clip-padding"
88+
bind:this={f.floating}
89+
style={f.floatingStyles}
90+
aria-labelledby={labelId}
91+
aria-describedby={descriptionId}
92+
{...ints.getFloatingProps()}>
93+
{@render content({
94+
labelId,
95+
descriptionId,
96+
close: () => (open = false),
97+
})}
98+
</div>
99+
</FloatingFocusManager>
100+
</FloatingPortal>
101+
{/if}
102+
</FloatingNode>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script lang="ts">
2+
import Main from "../../components/popover/main.svelte";
3+
</script>
4+
5+
<Main />

pnpm-lock.yaml

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

0 commit comments

Comments
 (0)