@purpurds/popover 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/LICENSE.txt +905 -0
- package/dist/metadata.js +8 -0
- package/dist/popover-back.d.ts +9 -0
- package/dist/popover-back.d.ts.map +1 -0
- package/dist/popover-button.d.ts +37 -0
- package/dist/popover-button.d.ts.map +1 -0
- package/dist/popover-content.d.ts +93 -0
- package/dist/popover-content.d.ts.map +1 -0
- package/dist/popover-flow.d.ts +65 -0
- package/dist/popover-flow.d.ts.map +1 -0
- package/dist/popover-footer.d.ts +16 -0
- package/dist/popover-footer.d.ts.map +1 -0
- package/dist/popover-header.d.ts +7 -0
- package/dist/popover-header.d.ts.map +1 -0
- package/dist/popover-internal-context.d.ts +15 -0
- package/dist/popover-internal-context.d.ts.map +1 -0
- package/dist/popover-next.d.ts +9 -0
- package/dist/popover-next.d.ts.map +1 -0
- package/dist/popover-standalone.d.ts +12 -0
- package/dist/popover-standalone.d.ts.map +1 -0
- package/dist/popover-steps.d.ts +6 -0
- package/dist/popover-steps.d.ts.map +1 -0
- package/dist/popover-trigger.d.ts +27 -0
- package/dist/popover-trigger.d.ts.map +1 -0
- package/dist/popover-walkthrough.d.ts +13 -0
- package/dist/popover-walkthrough.d.ts.map +1 -0
- package/dist/popover.cjs.js +42 -0
- package/dist/popover.cjs.js.map +1 -0
- package/dist/popover.d.ts +36 -0
- package/dist/popover.d.ts.map +1 -0
- package/dist/popover.es.js +3849 -0
- package/dist/popover.es.js.map +1 -0
- package/dist/styles.css +1 -0
- package/dist/use-screen-size.hook.d.ts +7 -0
- package/dist/use-screen-size.hook.d.ts.map +1 -0
- package/dist/use-smooth-scroll.d.ts +5 -0
- package/dist/use-smooth-scroll.d.ts.map +1 -0
- package/dist/usePopoverTrigger.d.ts +5 -0
- package/dist/usePopoverTrigger.d.ts.map +1 -0
- package/dist/usePopoverWalkthrough.d.ts +7 -0
- package/dist/usePopoverWalkthrough.d.ts.map +1 -0
- package/eslint.config.mjs +2 -0
- package/package.json +82 -0
- package/src/global.d.ts +4 -0
- package/src/popover-back.test.tsx +63 -0
- package/src/popover-back.tsx +40 -0
- package/src/popover-button.test.tsx +51 -0
- package/src/popover-button.tsx +84 -0
- package/src/popover-content.test.tsx +1122 -0
- package/src/popover-content.tsx +277 -0
- package/src/popover-flow.tsx +170 -0
- package/src/popover-footer.test.tsx +21 -0
- package/src/popover-footer.tsx +32 -0
- package/src/popover-header.test.tsx +22 -0
- package/src/popover-header.tsx +32 -0
- package/src/popover-internal-context.tsx +28 -0
- package/src/popover-next.test.tsx +61 -0
- package/src/popover-next.tsx +40 -0
- package/src/popover-standalone.tsx +48 -0
- package/src/popover-steps.tsx +32 -0
- package/src/popover-trigger.tsx +71 -0
- package/src/popover-walkthrough.test.tsx +346 -0
- package/src/popover-walkthrough.tsx +45 -0
- package/src/popover.module.scss +315 -0
- package/src/popover.stories.tsx +1157 -0
- package/src/popover.test.tsx +642 -0
- package/src/popover.tsx +76 -0
- package/src/use-screen-size.hook.ts +39 -0
- package/src/use-smooth-scroll.ts +62 -0
- package/src/usePopoverTrigger.ts +59 -0
- package/src/usePopoverWalkthrough.ts +85 -0
- package/vitest.setup.ts +30 -0
|
@@ -0,0 +1,642 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Button } from "@purpurds/button";
|
|
3
|
+
import { render, screen, waitFor } from "@testing-library/react";
|
|
4
|
+
import userEvent from "@testing-library/user-event";
|
|
5
|
+
import { describe, expect, it, vi } from "vitest";
|
|
6
|
+
import { axe } from "vitest-axe";
|
|
7
|
+
|
|
8
|
+
import { Popover } from "./popover";
|
|
9
|
+
|
|
10
|
+
describe("Popover - Standalone Variant", () => {
|
|
11
|
+
it("should render trigger button", () => {
|
|
12
|
+
render(
|
|
13
|
+
<Popover>
|
|
14
|
+
<Popover.Trigger>
|
|
15
|
+
<Button variant="primary" type="button">
|
|
16
|
+
Click me
|
|
17
|
+
</Button>
|
|
18
|
+
</Popover.Trigger>
|
|
19
|
+
<Popover.Content closeIconAriaLabel="Close" title="Test Title" body="Test body text">
|
|
20
|
+
<Popover.Footer>
|
|
21
|
+
<Popover.Button>Got it</Popover.Button>
|
|
22
|
+
</Popover.Footer>
|
|
23
|
+
</Popover.Content>
|
|
24
|
+
</Popover>
|
|
25
|
+
);
|
|
26
|
+
expect(screen.getByRole("button", { name: "Click me" })).toBeInTheDocument();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("should open popover when trigger is clicked", async () => {
|
|
30
|
+
const user = userEvent.setup();
|
|
31
|
+
render(
|
|
32
|
+
<Popover>
|
|
33
|
+
<Popover.Trigger>
|
|
34
|
+
<Button variant="primary" type="button">
|
|
35
|
+
Click me
|
|
36
|
+
</Button>
|
|
37
|
+
</Popover.Trigger>
|
|
38
|
+
<Popover.Content closeIconAriaLabel="Close" title="Test Title" body="Test body text">
|
|
39
|
+
<Popover.Footer>
|
|
40
|
+
<Popover.Button>Got it</Popover.Button>
|
|
41
|
+
</Popover.Footer>
|
|
42
|
+
</Popover.Content>
|
|
43
|
+
</Popover>
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
await user.click(screen.getByRole("button", { name: "Click me" }));
|
|
47
|
+
|
|
48
|
+
await waitFor(() => {
|
|
49
|
+
expect(screen.getByText("Test Title")).toBeInTheDocument();
|
|
50
|
+
});
|
|
51
|
+
expect(screen.getByText("Test body text")).toBeInTheDocument();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("should close popover when action button is clicked", async () => {
|
|
55
|
+
const user = userEvent.setup();
|
|
56
|
+
render(
|
|
57
|
+
<Popover>
|
|
58
|
+
<Popover.Trigger>
|
|
59
|
+
<Button variant="primary" type="button">
|
|
60
|
+
Click me
|
|
61
|
+
</Button>
|
|
62
|
+
</Popover.Trigger>
|
|
63
|
+
<Popover.Content closeIconAriaLabel="Close" title="Test Title" body="Test body text">
|
|
64
|
+
<Popover.Footer>
|
|
65
|
+
<Popover.Button>Got it</Popover.Button>
|
|
66
|
+
</Popover.Footer>
|
|
67
|
+
</Popover.Content>
|
|
68
|
+
</Popover>
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
// Open the popover
|
|
72
|
+
await user.click(screen.getByRole("button", { name: "Click me" }));
|
|
73
|
+
await waitFor(() => {
|
|
74
|
+
expect(screen.getByText("Test Title")).toBeInTheDocument();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Click the action button
|
|
78
|
+
await user.click(screen.getByRole("button", { name: "Got it" }));
|
|
79
|
+
|
|
80
|
+
await waitFor(() => {
|
|
81
|
+
expect(screen.queryByText("Test Title")).not.toBeInTheDocument();
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("should close popover when close icon is clicked", async () => {
|
|
86
|
+
const user = userEvent.setup();
|
|
87
|
+
render(
|
|
88
|
+
<Popover>
|
|
89
|
+
<Popover.Trigger>
|
|
90
|
+
<Button variant="primary" type="button">
|
|
91
|
+
Click me
|
|
92
|
+
</Button>
|
|
93
|
+
</Popover.Trigger>
|
|
94
|
+
<Popover.Content closeIconAriaLabel="Close" title="Test Title" body="Test body text">
|
|
95
|
+
<Popover.Footer>
|
|
96
|
+
<Popover.Button>Got it</Popover.Button>
|
|
97
|
+
</Popover.Footer>
|
|
98
|
+
</Popover.Content>
|
|
99
|
+
</Popover>
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// Open the popover
|
|
103
|
+
await user.click(screen.getByRole("button", { name: "Click me" }));
|
|
104
|
+
await waitFor(() => {
|
|
105
|
+
expect(screen.getByText("Test Title")).toBeInTheDocument();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Click the close icon
|
|
109
|
+
await user.click(screen.getByRole("button", { name: "Close" }));
|
|
110
|
+
|
|
111
|
+
await waitFor(() => {
|
|
112
|
+
expect(screen.queryByText("Test Title")).not.toBeInTheDocument();
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("should close popover when ESC is pressed", async () => {
|
|
117
|
+
const user = userEvent.setup();
|
|
118
|
+
render(
|
|
119
|
+
<Popover>
|
|
120
|
+
<Popover.Trigger>
|
|
121
|
+
<Button variant="primary" type="button">
|
|
122
|
+
Click me
|
|
123
|
+
</Button>
|
|
124
|
+
</Popover.Trigger>
|
|
125
|
+
<Popover.Content closeIconAriaLabel="Close" title="Test Title" body="Test body text">
|
|
126
|
+
<Popover.Footer>
|
|
127
|
+
<Popover.Button>Got it</Popover.Button>
|
|
128
|
+
</Popover.Footer>
|
|
129
|
+
</Popover.Content>
|
|
130
|
+
</Popover>
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
// Open the popover
|
|
134
|
+
await user.click(screen.getByRole("button", { name: "Click me" }));
|
|
135
|
+
await waitFor(() => {
|
|
136
|
+
expect(screen.getByText("Test Title")).toBeInTheDocument();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Press ESC
|
|
140
|
+
await user.keyboard("{Escape}");
|
|
141
|
+
|
|
142
|
+
await waitFor(() => {
|
|
143
|
+
expect(screen.queryByText("Test Title")).not.toBeInTheDocument();
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("should work in controlled mode", async () => {
|
|
148
|
+
const user = userEvent.setup();
|
|
149
|
+
const onOpenChange = vi.fn();
|
|
150
|
+
const TestComponent = () => {
|
|
151
|
+
const [open, setOpen] = React.useState(false);
|
|
152
|
+
return (
|
|
153
|
+
<>
|
|
154
|
+
<button onClick={() => setOpen(true)}>External Open</button>
|
|
155
|
+
<Popover
|
|
156
|
+
open={open}
|
|
157
|
+
onOpenChange={(isOpen) => {
|
|
158
|
+
setOpen(isOpen);
|
|
159
|
+
onOpenChange(isOpen);
|
|
160
|
+
}}
|
|
161
|
+
>
|
|
162
|
+
<Popover.Trigger>
|
|
163
|
+
<Button variant="primary" type="button">
|
|
164
|
+
Click me
|
|
165
|
+
</Button>
|
|
166
|
+
</Popover.Trigger>
|
|
167
|
+
<Popover.Content closeIconAriaLabel="Close" title="Test Title" body="Test body text">
|
|
168
|
+
<Popover.Footer>
|
|
169
|
+
<Popover.Button>Got it</Popover.Button>
|
|
170
|
+
</Popover.Footer>
|
|
171
|
+
</Popover.Content>
|
|
172
|
+
</Popover>
|
|
173
|
+
</>
|
|
174
|
+
);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
render(<TestComponent />);
|
|
178
|
+
|
|
179
|
+
// Open via external button
|
|
180
|
+
await user.click(screen.getByRole("button", { name: "External Open" }));
|
|
181
|
+
await waitFor(() => {
|
|
182
|
+
expect(screen.getByText("Test Title")).toBeInTheDocument();
|
|
183
|
+
});
|
|
184
|
+
// Note: Radix Popover doesn't call onOpenChange when open prop is set externally,
|
|
185
|
+
// only when internal interactions (trigger click, ESC) change the state
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("should be accessible", async () => {
|
|
189
|
+
const user = userEvent.setup();
|
|
190
|
+
render(
|
|
191
|
+
<Popover>
|
|
192
|
+
<Popover.Trigger>
|
|
193
|
+
<Button variant="primary" type="button">
|
|
194
|
+
Click me
|
|
195
|
+
</Button>
|
|
196
|
+
</Popover.Trigger>
|
|
197
|
+
<Popover.Content closeIconAriaLabel="Close" title="Test Title" body="Test body text">
|
|
198
|
+
<Popover.Footer>
|
|
199
|
+
<Popover.Button>Got it</Popover.Button>
|
|
200
|
+
</Popover.Footer>
|
|
201
|
+
</Popover.Content>
|
|
202
|
+
</Popover>
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// Open the popover first
|
|
206
|
+
await user.click(screen.getByRole("button", { name: "Click me" }));
|
|
207
|
+
await waitFor(() => {
|
|
208
|
+
expect(screen.getByText("Test Title")).toBeInTheDocument();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Test only the content dialog, not the trigger wrapper
|
|
212
|
+
const dialog = screen.getByRole("dialog");
|
|
213
|
+
const results = await axe(dialog);
|
|
214
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
215
|
+
// @ts-ignore
|
|
216
|
+
expect(results).toHaveNoViolations();
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
describe("Popover - Walkthrough Variant", () => {
|
|
221
|
+
it("should render step indicator with default separator", async () => {
|
|
222
|
+
render(
|
|
223
|
+
<Popover.Flow
|
|
224
|
+
separatorText="of"
|
|
225
|
+
stepText="Step"
|
|
226
|
+
backLabel="Back"
|
|
227
|
+
nextLabel="Next"
|
|
228
|
+
finishLabel="Finish"
|
|
229
|
+
>
|
|
230
|
+
<Popover multistep step={1}>
|
|
231
|
+
<Popover.Trigger>
|
|
232
|
+
<Button variant="primary" type="button">
|
|
233
|
+
Step 1
|
|
234
|
+
</Button>
|
|
235
|
+
</Popover.Trigger>
|
|
236
|
+
<Popover.Content closeIconAriaLabel="Close" title="Step 1" body="Step 1 content" />
|
|
237
|
+
</Popover>
|
|
238
|
+
<Popover multistep step={2}>
|
|
239
|
+
<Popover.Trigger>
|
|
240
|
+
<Button variant="primary" type="button">
|
|
241
|
+
Step 2
|
|
242
|
+
</Button>
|
|
243
|
+
</Popover.Trigger>
|
|
244
|
+
<Popover.Content closeIconAriaLabel="Close" title="Step 2" body="Step 2 content" />
|
|
245
|
+
</Popover>
|
|
246
|
+
</Popover.Flow>
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
// First wait for step 1 content to appear (steps need to register, then 300ms delay to open)
|
|
250
|
+
await waitFor(
|
|
251
|
+
() => {
|
|
252
|
+
expect(screen.getByText("Step 1 content")).toBeInTheDocument();
|
|
253
|
+
},
|
|
254
|
+
{ timeout: 5000, interval: 50 }
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
// Then check for step indicator (appears twice: screen reader + visible)
|
|
258
|
+
const stepIndicators = screen.getAllByText("Step 1 of 2");
|
|
259
|
+
expect(stepIndicators.length).toBeGreaterThan(0);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("should render step indicator with custom separator", async () => {
|
|
263
|
+
render(
|
|
264
|
+
<Popover.Flow
|
|
265
|
+
separatorText="av"
|
|
266
|
+
stepText="Steg"
|
|
267
|
+
backLabel="Tillbaka"
|
|
268
|
+
nextLabel="Nästa"
|
|
269
|
+
finishLabel="Klar"
|
|
270
|
+
>
|
|
271
|
+
<Popover multistep step={1}>
|
|
272
|
+
<Popover.Trigger>
|
|
273
|
+
<Button variant="primary" type="button">
|
|
274
|
+
Step 1
|
|
275
|
+
</Button>
|
|
276
|
+
</Popover.Trigger>
|
|
277
|
+
<Popover.Content closeIconAriaLabel="Stäng" title="Steg 1" body="Steg 1 innehåll" />
|
|
278
|
+
</Popover>
|
|
279
|
+
<Popover multistep step={2}>
|
|
280
|
+
<Popover.Trigger>
|
|
281
|
+
<Button variant="primary" type="button">
|
|
282
|
+
Step 2
|
|
283
|
+
</Button>
|
|
284
|
+
</Popover.Trigger>
|
|
285
|
+
<Popover.Content closeIconAriaLabel="Stäng" title="Steg 2" body="Steg 2 innehåll" />
|
|
286
|
+
</Popover>
|
|
287
|
+
</Popover.Flow>
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
// First wait for step 1 content to appear (steps need to register, then 300ms delay to open)
|
|
291
|
+
await waitFor(
|
|
292
|
+
() => {
|
|
293
|
+
expect(screen.getByText("Steg 1 innehåll")).toBeInTheDocument();
|
|
294
|
+
},
|
|
295
|
+
{ timeout: 5000, interval: 50 }
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
// Then check for step indicator (appears twice: screen reader + visible)
|
|
299
|
+
const stepIndicators = screen.getAllByText("Steg 1 av 2");
|
|
300
|
+
expect(stepIndicators.length).toBeGreaterThan(0);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it("should show first step on mount", async () => {
|
|
304
|
+
render(
|
|
305
|
+
<Popover.Flow
|
|
306
|
+
separatorText="of"
|
|
307
|
+
stepText="Step"
|
|
308
|
+
backLabel="Back"
|
|
309
|
+
nextLabel="Next"
|
|
310
|
+
finishLabel="Finish"
|
|
311
|
+
>
|
|
312
|
+
<Popover multistep step={1}>
|
|
313
|
+
<Popover.Trigger>
|
|
314
|
+
<Button variant="primary" type="button">
|
|
315
|
+
Step 1 Target
|
|
316
|
+
</Button>
|
|
317
|
+
</Popover.Trigger>
|
|
318
|
+
<Popover.Content closeIconAriaLabel="Close" title="Step 1 Title" body="Step 1 content" />
|
|
319
|
+
</Popover>
|
|
320
|
+
<Popover multistep step={2}>
|
|
321
|
+
<Popover.Trigger>
|
|
322
|
+
<Button variant="primary" type="button">
|
|
323
|
+
Step 2 Target
|
|
324
|
+
</Button>
|
|
325
|
+
</Popover.Trigger>
|
|
326
|
+
<Popover.Content closeIconAriaLabel="Close" title="Step 2 Title" body="Step 2 content" />
|
|
327
|
+
</Popover>
|
|
328
|
+
</Popover.Flow>
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
// Wait for first step to appear
|
|
332
|
+
await waitFor(() => {
|
|
333
|
+
expect(screen.getByText("Step 1 Title")).toBeInTheDocument();
|
|
334
|
+
expect(screen.getByText("Step 1 content")).toBeInTheDocument();
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it("should advance to next step when Next button is clicked", async () => {
|
|
339
|
+
const user = userEvent.setup();
|
|
340
|
+
const onAction = vi.fn();
|
|
341
|
+
|
|
342
|
+
render(
|
|
343
|
+
<Popover.Flow
|
|
344
|
+
separatorText="of"
|
|
345
|
+
stepText="Step"
|
|
346
|
+
backLabel="Back"
|
|
347
|
+
nextLabel="Next"
|
|
348
|
+
finishLabel="Finish"
|
|
349
|
+
>
|
|
350
|
+
<Popover multistep step={1}>
|
|
351
|
+
<Popover.Trigger>
|
|
352
|
+
<Button variant="primary" type="button">
|
|
353
|
+
Step 1 Target
|
|
354
|
+
</Button>
|
|
355
|
+
</Popover.Trigger>
|
|
356
|
+
<Popover.Content
|
|
357
|
+
closeIconAriaLabel="Close"
|
|
358
|
+
title="Step 1"
|
|
359
|
+
body="Step 1 content"
|
|
360
|
+
onAction={onAction}
|
|
361
|
+
/>
|
|
362
|
+
</Popover>
|
|
363
|
+
<Popover multistep step={2}>
|
|
364
|
+
<Popover.Trigger>
|
|
365
|
+
<Button variant="primary" type="button">
|
|
366
|
+
Step 2 Target
|
|
367
|
+
</Button>
|
|
368
|
+
</Popover.Trigger>
|
|
369
|
+
<Popover.Content
|
|
370
|
+
closeIconAriaLabel="Close"
|
|
371
|
+
title="Step 2"
|
|
372
|
+
body="Step 2 content"
|
|
373
|
+
onAction={onAction}
|
|
374
|
+
/>
|
|
375
|
+
</Popover>
|
|
376
|
+
</Popover.Flow>
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
// Wait for step 1 to appear
|
|
380
|
+
await waitFor(() => {
|
|
381
|
+
expect(screen.getByText("Step 1")).toBeInTheDocument();
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
// Click Next button
|
|
385
|
+
await user.click(screen.getByRole("button", { name: "Next" }));
|
|
386
|
+
|
|
387
|
+
// Verify onAction was called with correct params
|
|
388
|
+
expect(onAction).toHaveBeenCalledWith({ type: "next", step: 1 });
|
|
389
|
+
|
|
390
|
+
// Wait for step 2 to appear
|
|
391
|
+
await waitFor(() => {
|
|
392
|
+
expect(screen.getByText("Step 2")).toBeInTheDocument();
|
|
393
|
+
expect(screen.getByText("Step 2 content")).toBeInTheDocument();
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
it("should go back to previous step when Back button is clicked", async () => {
|
|
398
|
+
const user = userEvent.setup();
|
|
399
|
+
const onAction = vi.fn();
|
|
400
|
+
|
|
401
|
+
render(
|
|
402
|
+
<Popover.Flow
|
|
403
|
+
separatorText="of"
|
|
404
|
+
stepText="Step"
|
|
405
|
+
backLabel="Back"
|
|
406
|
+
nextLabel="Next"
|
|
407
|
+
finishLabel="Finish"
|
|
408
|
+
>
|
|
409
|
+
<Popover multistep step={1}>
|
|
410
|
+
<Popover.Trigger>
|
|
411
|
+
<Button variant="primary" type="button">
|
|
412
|
+
Step 1 Target
|
|
413
|
+
</Button>
|
|
414
|
+
</Popover.Trigger>
|
|
415
|
+
<Popover.Content
|
|
416
|
+
closeIconAriaLabel="Close"
|
|
417
|
+
title="Step 1"
|
|
418
|
+
body="Step 1 content"
|
|
419
|
+
onAction={onAction}
|
|
420
|
+
/>
|
|
421
|
+
</Popover>
|
|
422
|
+
<Popover multistep step={2}>
|
|
423
|
+
<Popover.Trigger>
|
|
424
|
+
<Button variant="primary" type="button">
|
|
425
|
+
Step 2 Target
|
|
426
|
+
</Button>
|
|
427
|
+
</Popover.Trigger>
|
|
428
|
+
<Popover.Content
|
|
429
|
+
closeIconAriaLabel="Close"
|
|
430
|
+
title="Step 2"
|
|
431
|
+
body="Step 2 content"
|
|
432
|
+
onAction={onAction}
|
|
433
|
+
/>
|
|
434
|
+
</Popover>
|
|
435
|
+
</Popover.Flow>
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
// Wait for step 1 and navigate to step 2
|
|
439
|
+
await waitFor(() => {
|
|
440
|
+
expect(screen.getByText("Step 1")).toBeInTheDocument();
|
|
441
|
+
});
|
|
442
|
+
await user.click(screen.getByRole("button", { name: "Next" }));
|
|
443
|
+
await waitFor(() => {
|
|
444
|
+
expect(screen.getByText("Step 2")).toBeInTheDocument();
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
// Click Back button
|
|
448
|
+
await user.click(screen.getByRole("button", { name: "Back" }));
|
|
449
|
+
expect(onAction).toHaveBeenCalledWith({ type: "back", step: 2 });
|
|
450
|
+
|
|
451
|
+
// Verify we're back at step 1
|
|
452
|
+
await waitFor(() => {
|
|
453
|
+
expect(screen.getByText("Step 1")).toBeInTheDocument();
|
|
454
|
+
expect(screen.getByText("Step 1 content")).toBeInTheDocument();
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
it("should show Finish button on last step and dismiss flow when clicked", async () => {
|
|
459
|
+
const user = userEvent.setup();
|
|
460
|
+
const onAction = vi.fn();
|
|
461
|
+
|
|
462
|
+
render(
|
|
463
|
+
<Popover.Flow
|
|
464
|
+
separatorText="of"
|
|
465
|
+
stepText="Step"
|
|
466
|
+
backLabel="Back"
|
|
467
|
+
nextLabel="Next"
|
|
468
|
+
finishLabel="Finish"
|
|
469
|
+
>
|
|
470
|
+
<Popover multistep step={1}>
|
|
471
|
+
<Popover.Trigger>
|
|
472
|
+
<Button variant="primary" type="button">
|
|
473
|
+
Step 1 Target
|
|
474
|
+
</Button>
|
|
475
|
+
</Popover.Trigger>
|
|
476
|
+
<Popover.Content
|
|
477
|
+
closeIconAriaLabel="Close"
|
|
478
|
+
title="Step 1"
|
|
479
|
+
body="Step 1 content"
|
|
480
|
+
onAction={onAction}
|
|
481
|
+
/>
|
|
482
|
+
</Popover>
|
|
483
|
+
<Popover multistep step={2}>
|
|
484
|
+
<Popover.Trigger>
|
|
485
|
+
<Button variant="primary" type="button">
|
|
486
|
+
Step 2 Target
|
|
487
|
+
</Button>
|
|
488
|
+
</Popover.Trigger>
|
|
489
|
+
<Popover.Content
|
|
490
|
+
closeIconAriaLabel="Close"
|
|
491
|
+
title="Step 2"
|
|
492
|
+
body="Step 2 content"
|
|
493
|
+
onAction={onAction}
|
|
494
|
+
/>
|
|
495
|
+
</Popover>
|
|
496
|
+
</Popover.Flow>
|
|
497
|
+
);
|
|
498
|
+
|
|
499
|
+
// Navigate to last step
|
|
500
|
+
await waitFor(() => {
|
|
501
|
+
expect(screen.getByText("Step 1")).toBeInTheDocument();
|
|
502
|
+
});
|
|
503
|
+
await user.click(screen.getByRole("button", { name: "Next" }));
|
|
504
|
+
await waitFor(() => {
|
|
505
|
+
expect(screen.getByText("Step 2")).toBeInTheDocument();
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
// Verify Finish button is shown
|
|
509
|
+
expect(screen.getByRole("button", { name: "Finish" })).toBeInTheDocument();
|
|
510
|
+
expect(screen.queryByRole("button", { name: "Next" })).not.toBeInTheDocument();
|
|
511
|
+
|
|
512
|
+
// Click Finish button
|
|
513
|
+
await user.click(screen.getByRole("button", { name: "Finish" }));
|
|
514
|
+
expect(onAction).toHaveBeenCalledWith({ type: "finish", step: 2 });
|
|
515
|
+
|
|
516
|
+
// Verify flow is dismissed
|
|
517
|
+
await waitFor(() => {
|
|
518
|
+
expect(screen.queryByText("Step 2")).not.toBeInTheDocument();
|
|
519
|
+
});
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
it("should dismiss flow when close icon is clicked", async () => {
|
|
523
|
+
const user = userEvent.setup();
|
|
524
|
+
const onAction = vi.fn();
|
|
525
|
+
|
|
526
|
+
render(
|
|
527
|
+
<Popover.Flow
|
|
528
|
+
separatorText="of"
|
|
529
|
+
stepText="Step"
|
|
530
|
+
backLabel="Back"
|
|
531
|
+
nextLabel="Next"
|
|
532
|
+
finishLabel="Finish"
|
|
533
|
+
>
|
|
534
|
+
<Popover multistep step={1}>
|
|
535
|
+
<Popover.Trigger>
|
|
536
|
+
<Button variant="primary" type="button">
|
|
537
|
+
Step 1 Target
|
|
538
|
+
</Button>
|
|
539
|
+
</Popover.Trigger>
|
|
540
|
+
<Popover.Content
|
|
541
|
+
closeIconAriaLabel="Close"
|
|
542
|
+
title="Step 1"
|
|
543
|
+
body="Step 1 content"
|
|
544
|
+
onAction={onAction}
|
|
545
|
+
/>
|
|
546
|
+
</Popover>
|
|
547
|
+
</Popover.Flow>
|
|
548
|
+
);
|
|
549
|
+
|
|
550
|
+
// Wait for step 1 to appear
|
|
551
|
+
await waitFor(() => {
|
|
552
|
+
expect(screen.getByText("Step 1")).toBeInTheDocument();
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
// Click close icon
|
|
556
|
+
await user.click(screen.getByRole("button", { name: "Close" }));
|
|
557
|
+
expect(onAction).toHaveBeenCalledWith({ type: "dismiss", step: 1 });
|
|
558
|
+
|
|
559
|
+
// Verify flow is dismissed
|
|
560
|
+
await waitFor(() => {
|
|
561
|
+
expect(screen.queryByText("Step 1")).not.toBeInTheDocument();
|
|
562
|
+
});
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
it("should dismiss flow when ESC is pressed", async () => {
|
|
566
|
+
const user = userEvent.setup();
|
|
567
|
+
const onAction = vi.fn();
|
|
568
|
+
|
|
569
|
+
render(
|
|
570
|
+
<Popover.Flow
|
|
571
|
+
separatorText="of"
|
|
572
|
+
stepText="Step"
|
|
573
|
+
backLabel="Back"
|
|
574
|
+
nextLabel="Next"
|
|
575
|
+
finishLabel="Finish"
|
|
576
|
+
>
|
|
577
|
+
<Popover multistep step={1}>
|
|
578
|
+
<Popover.Trigger>
|
|
579
|
+
<Button variant="primary" type="button">
|
|
580
|
+
Step 1 Target
|
|
581
|
+
</Button>
|
|
582
|
+
</Popover.Trigger>
|
|
583
|
+
<Popover.Content
|
|
584
|
+
closeIconAriaLabel="Close"
|
|
585
|
+
title="Step 1"
|
|
586
|
+
body="Step 1 content"
|
|
587
|
+
onAction={onAction}
|
|
588
|
+
/>
|
|
589
|
+
</Popover>
|
|
590
|
+
</Popover.Flow>
|
|
591
|
+
);
|
|
592
|
+
|
|
593
|
+
// Wait for step 1 to appear
|
|
594
|
+
await waitFor(() => {
|
|
595
|
+
expect(screen.getByText("Step 1")).toBeInTheDocument();
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
// Press ESC
|
|
599
|
+
await user.keyboard("{Escape}");
|
|
600
|
+
|
|
601
|
+
// Verify flow is dismissed
|
|
602
|
+
await waitFor(() => {
|
|
603
|
+
expect(screen.queryByText("Step 1")).not.toBeInTheDocument();
|
|
604
|
+
});
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
it("should not show Back button on first step", async () => {
|
|
608
|
+
render(
|
|
609
|
+
<Popover.Flow
|
|
610
|
+
separatorText="of"
|
|
611
|
+
stepText="Step"
|
|
612
|
+
backLabel="Back"
|
|
613
|
+
nextLabel="Next"
|
|
614
|
+
finishLabel="Finish"
|
|
615
|
+
>
|
|
616
|
+
<Popover multistep step={1}>
|
|
617
|
+
<Popover.Trigger>
|
|
618
|
+
<Button variant="primary" type="button">
|
|
619
|
+
Step 1 Target
|
|
620
|
+
</Button>
|
|
621
|
+
</Popover.Trigger>
|
|
622
|
+
<Popover.Content closeIconAriaLabel="Close" title="Step 1" body="Step 1 content" />
|
|
623
|
+
</Popover>
|
|
624
|
+
<Popover multistep step={2}>
|
|
625
|
+
<Popover.Trigger>
|
|
626
|
+
<Button variant="primary" type="button">
|
|
627
|
+
Step 2 Target
|
|
628
|
+
</Button>
|
|
629
|
+
</Popover.Trigger>
|
|
630
|
+
<Popover.Content closeIconAriaLabel="Close" title="Step 2" body="Step 2 content" />
|
|
631
|
+
</Popover>
|
|
632
|
+
</Popover.Flow>
|
|
633
|
+
);
|
|
634
|
+
|
|
635
|
+
await waitFor(() => {
|
|
636
|
+
expect(screen.getByText("Step 1")).toBeInTheDocument();
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
expect(screen.queryByRole("button", { name: "Back" })).not.toBeInTheDocument();
|
|
640
|
+
expect(screen.getByRole("button", { name: "Next" })).toBeInTheDocument();
|
|
641
|
+
});
|
|
642
|
+
});
|