@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.
Files changed (72) hide show
  1. package/dist/LICENSE.txt +905 -0
  2. package/dist/metadata.js +8 -0
  3. package/dist/popover-back.d.ts +9 -0
  4. package/dist/popover-back.d.ts.map +1 -0
  5. package/dist/popover-button.d.ts +37 -0
  6. package/dist/popover-button.d.ts.map +1 -0
  7. package/dist/popover-content.d.ts +93 -0
  8. package/dist/popover-content.d.ts.map +1 -0
  9. package/dist/popover-flow.d.ts +65 -0
  10. package/dist/popover-flow.d.ts.map +1 -0
  11. package/dist/popover-footer.d.ts +16 -0
  12. package/dist/popover-footer.d.ts.map +1 -0
  13. package/dist/popover-header.d.ts +7 -0
  14. package/dist/popover-header.d.ts.map +1 -0
  15. package/dist/popover-internal-context.d.ts +15 -0
  16. package/dist/popover-internal-context.d.ts.map +1 -0
  17. package/dist/popover-next.d.ts +9 -0
  18. package/dist/popover-next.d.ts.map +1 -0
  19. package/dist/popover-standalone.d.ts +12 -0
  20. package/dist/popover-standalone.d.ts.map +1 -0
  21. package/dist/popover-steps.d.ts +6 -0
  22. package/dist/popover-steps.d.ts.map +1 -0
  23. package/dist/popover-trigger.d.ts +27 -0
  24. package/dist/popover-trigger.d.ts.map +1 -0
  25. package/dist/popover-walkthrough.d.ts +13 -0
  26. package/dist/popover-walkthrough.d.ts.map +1 -0
  27. package/dist/popover.cjs.js +42 -0
  28. package/dist/popover.cjs.js.map +1 -0
  29. package/dist/popover.d.ts +36 -0
  30. package/dist/popover.d.ts.map +1 -0
  31. package/dist/popover.es.js +3849 -0
  32. package/dist/popover.es.js.map +1 -0
  33. package/dist/styles.css +1 -0
  34. package/dist/use-screen-size.hook.d.ts +7 -0
  35. package/dist/use-screen-size.hook.d.ts.map +1 -0
  36. package/dist/use-smooth-scroll.d.ts +5 -0
  37. package/dist/use-smooth-scroll.d.ts.map +1 -0
  38. package/dist/usePopoverTrigger.d.ts +5 -0
  39. package/dist/usePopoverTrigger.d.ts.map +1 -0
  40. package/dist/usePopoverWalkthrough.d.ts +7 -0
  41. package/dist/usePopoverWalkthrough.d.ts.map +1 -0
  42. package/eslint.config.mjs +2 -0
  43. package/package.json +82 -0
  44. package/src/global.d.ts +4 -0
  45. package/src/popover-back.test.tsx +63 -0
  46. package/src/popover-back.tsx +40 -0
  47. package/src/popover-button.test.tsx +51 -0
  48. package/src/popover-button.tsx +84 -0
  49. package/src/popover-content.test.tsx +1122 -0
  50. package/src/popover-content.tsx +277 -0
  51. package/src/popover-flow.tsx +170 -0
  52. package/src/popover-footer.test.tsx +21 -0
  53. package/src/popover-footer.tsx +32 -0
  54. package/src/popover-header.test.tsx +22 -0
  55. package/src/popover-header.tsx +32 -0
  56. package/src/popover-internal-context.tsx +28 -0
  57. package/src/popover-next.test.tsx +61 -0
  58. package/src/popover-next.tsx +40 -0
  59. package/src/popover-standalone.tsx +48 -0
  60. package/src/popover-steps.tsx +32 -0
  61. package/src/popover-trigger.tsx +71 -0
  62. package/src/popover-walkthrough.test.tsx +346 -0
  63. package/src/popover-walkthrough.tsx +45 -0
  64. package/src/popover.module.scss +315 -0
  65. package/src/popover.stories.tsx +1157 -0
  66. package/src/popover.test.tsx +642 -0
  67. package/src/popover.tsx +76 -0
  68. package/src/use-screen-size.hook.ts +39 -0
  69. package/src/use-smooth-scroll.ts +62 -0
  70. package/src/usePopoverTrigger.ts +59 -0
  71. package/src/usePopoverWalkthrough.ts +85 -0
  72. 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
+ });