Skip to content

Commit a84dd91

Browse files
authored
fix(stacks-svelte): shift popovers horizontally (#2288)
1 parent 838a33a commit a84dd91

4 files changed

Lines changed: 73 additions & 2 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@stackoverflow/stacks-svelte": patch
3+
---
4+
5+
Shift Popover content horizontally when it would overflow the viewport.

packages/stacks-svelte/src/components/Popover/Popover.stories.svelte

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,21 @@
122122
</div>
123123
</Story>
124124

125+
<Story name="Horizontal Shift" asChild>
126+
<div class="hmn3 d-flex jc-end ai-center pr8">
127+
<Popover id="horizontal-shift" placement="bottom" autoshow>
128+
<PopoverReference>
129+
<Button>Trigger</Button>
130+
</PopoverReference>
131+
<PopoverContent class="w-auto wmn0">
132+
<span style="display: block; width: 220px;">
133+
Shifted content
134+
</span>
135+
</PopoverContent>
136+
</Popover>
137+
</div>
138+
</Story>
139+
125140
<Story name="Strategies" asChild>
126141
<div class="d-grid grid__2 w100 ji-center py128">
127142
{#each PopoverStrategies as strategy (strategy)}

packages/stacks-svelte/src/components/Popover/Popover.svelte

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
<script lang="ts">
4040
import type { Strategy } from "@floating-ui/core";
4141
import { setContext } from "svelte";
42-
import { offset, inline, flip } from "@floating-ui/dom";
42+
import { offset, inline, flip, shift } from "@floating-ui/dom";
4343
import { createFloatingActions } from "svelte-floating-ui";
4444
4545
interface Props {
@@ -137,7 +137,12 @@
137137
const [floatingRef, floatingContent, update] = createFloatingActions({
138138
placement,
139139
strategy,
140-
middleware: [offset(10), flip(), inline()],
140+
middleware: [
141+
offset(10),
142+
flip(),
143+
shift({ crossAxis: true, padding: 8 }),
144+
inline(),
145+
],
141146
onComputed({ placement: computedPlacement }) {
142147
pstate.computedPlacement = computedPlacement;
143148
},

packages/stacks-svelte/src/components/Popover/Popover.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,52 @@ describe("Popover", () => {
590590
);
591591
});
592592

593+
it("should shift the popover content horizontally when it would overflow the viewport", async () => {
594+
await setViewport({ width: 260, height: 260 });
595+
596+
const waitForFloatingPosition = () =>
597+
new Promise<void>((resolve) => {
598+
requestAnimationFrame(() => {
599+
requestAnimationFrame(() => resolve());
600+
});
601+
});
602+
603+
render(Popover, {
604+
props: {
605+
...defaultProps,
606+
placement: "bottom",
607+
children: createSvelteComponentsSnippet([
608+
{
609+
component: PopoverReference,
610+
props: {
611+
children: createRawSnippet(() => ({
612+
render: () =>
613+
'<button style="position: fixed; top: 80px; left: 220px;">Trigger</button>',
614+
})),
615+
},
616+
},
617+
{
618+
component: PopoverContent,
619+
props: {
620+
class: "w-auto wmn0",
621+
children: createRawSnippet(() => ({
622+
render: () =>
623+
'<span style="box-sizing: border-box; display: block; width: 160px;">Popover Content</span>',
624+
})),
625+
},
626+
},
627+
]),
628+
},
629+
});
630+
631+
await userEvent.click(screen.getByRole("button"));
632+
await waitForFloatingPosition();
633+
634+
const rect = screen.getByRole("dialog").getBoundingClientRect();
635+
636+
expect(Math.ceil(rect.right)).to.be.at.most(window.innerWidth);
637+
});
638+
593639
it("should not throw any error if the component is unmounted while the popover is open", async () => {
594640
const { unmount } = render(Popover, {
595641
props: {

0 commit comments

Comments
 (0)