@dowel-ui/react 0.1.0 → 0.1.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 (120) hide show
  1. package/dist/components/ai-agent-status/ai-agent-status.d.ts +1 -1
  2. package/dist/components/ai-tool/ai-tool.d.ts +1 -1
  3. package/dist/components/alert/alert.d.ts +1 -1
  4. package/dist/components/avatar/avatar.d.ts +1 -1
  5. package/dist/components/badge/badge.d.ts +1 -1
  6. package/dist/components/button/button.d.ts +1 -1
  7. package/dist/components/progress/progress.d.ts +1 -1
  8. package/dist/components/spinner/spinner.d.ts +1 -1
  9. package/dist/components/toast/toast.d.ts +1 -1
  10. package/package.json +7 -4
  11. package/src/blocks/admin-users/admin-users.stories.tsx +0 -83
  12. package/src/blocks/admin-users/admin-users.test.tsx +0 -159
  13. package/src/blocks/ai-chat/ai-chat.stories.tsx +0 -149
  14. package/src/blocks/ai-chat/ai-chat.test.tsx +0 -166
  15. package/src/blocks/dashboard/dashboard.stories.tsx +0 -106
  16. package/src/blocks/dashboard/dashboard.test.tsx +0 -94
  17. package/src/blocks/forgot-password/forgot-password.stories.tsx +0 -41
  18. package/src/blocks/forgot-password/forgot-password.test.tsx +0 -62
  19. package/src/blocks/login/login.stories.tsx +0 -46
  20. package/src/blocks/login/login.test.tsx +0 -100
  21. package/src/blocks/pricing/pricing.stories.tsx +0 -62
  22. package/src/blocks/pricing/pricing.test.tsx +0 -101
  23. package/src/blocks/settings/settings.stories.tsx +0 -94
  24. package/src/blocks/settings/settings.test.tsx +0 -167
  25. package/src/blocks/signup/signup.stories.tsx +0 -27
  26. package/src/blocks/signup/signup.test.tsx +0 -97
  27. package/src/components/accordion/accordion.stories.tsx +0 -90
  28. package/src/components/accordion/accordion.test.tsx +0 -156
  29. package/src/components/activity-feed/activity-feed.stories.tsx +0 -163
  30. package/src/components/activity-feed/activity-feed.test.tsx +0 -125
  31. package/src/components/ai-agent-status/ai-agent-status.stories.tsx +0 -86
  32. package/src/components/ai-agent-status/ai-agent-status.test.tsx +0 -66
  33. package/src/components/ai-conversation/ai-conversation.stories.tsx +0 -126
  34. package/src/components/ai-conversation/ai-conversation.test.tsx +0 -144
  35. package/src/components/ai-message/ai-message.stories.tsx +0 -106
  36. package/src/components/ai-message/ai-message.test.tsx +0 -160
  37. package/src/components/ai-model-selector/ai-model-selector.stories.tsx +0 -66
  38. package/src/components/ai-model-selector/ai-model-selector.test.tsx +0 -120
  39. package/src/components/ai-prompt-input/ai-prompt-input.stories.tsx +0 -145
  40. package/src/components/ai-prompt-input/ai-prompt-input.test.tsx +0 -273
  41. package/src/components/ai-reasoning/ai-reasoning.stories.tsx +0 -60
  42. package/src/components/ai-reasoning/ai-reasoning.test.tsx +0 -95
  43. package/src/components/ai-response/ai-response.stories.tsx +0 -75
  44. package/src/components/ai-response/ai-response.test.tsx +0 -96
  45. package/src/components/ai-sources/ai-sources.stories.tsx +0 -93
  46. package/src/components/ai-sources/ai-sources.test.tsx +0 -102
  47. package/src/components/ai-token-usage/ai-token-usage.stories.tsx +0 -59
  48. package/src/components/ai-token-usage/ai-token-usage.test.tsx +0 -83
  49. package/src/components/ai-tool/ai-tool.stories.tsx +0 -96
  50. package/src/components/ai-tool/ai-tool.test.tsx +0 -137
  51. package/src/components/alert/alert.stories.tsx +0 -101
  52. package/src/components/alert/alert.test.tsx +0 -94
  53. package/src/components/avatar/avatar.stories.tsx +0 -58
  54. package/src/components/avatar/avatar.test.tsx +0 -64
  55. package/src/components/badge/badge.stories.tsx +0 -68
  56. package/src/components/badge/badge.test.tsx +0 -61
  57. package/src/components/button/button.stories.tsx +0 -128
  58. package/src/components/button/button.test.tsx +0 -224
  59. package/src/components/calendar/calendar.stories.tsx +0 -105
  60. package/src/components/calendar/calendar.test.tsx +0 -255
  61. package/src/components/card/card.stories.tsx +0 -61
  62. package/src/components/card/card.test.tsx +0 -66
  63. package/src/components/checkbox/checkbox.stories.tsx +0 -124
  64. package/src/components/checkbox/checkbox.test.tsx +0 -121
  65. package/src/components/code-block/code-block.stories.tsx +0 -62
  66. package/src/components/code-block/code-block.test.tsx +0 -139
  67. package/src/components/combobox/combobox.stories.tsx +0 -171
  68. package/src/components/combobox/combobox.test.tsx +0 -429
  69. package/src/components/command/command.stories.tsx +0 -152
  70. package/src/components/command/command.test.tsx +0 -380
  71. package/src/components/data-table/data-table.stories.tsx +0 -151
  72. package/src/components/data-table/data-table.test.tsx +0 -288
  73. package/src/components/date-picker/date-picker.stories.tsx +0 -103
  74. package/src/components/date-picker/date-picker.test.tsx +0 -205
  75. package/src/components/dialog/dialog.stories.tsx +0 -140
  76. package/src/components/dialog/dialog.test.tsx +0 -186
  77. package/src/components/drawer/drawer.stories.tsx +0 -111
  78. package/src/components/drawer/drawer.test.tsx +0 -187
  79. package/src/components/dropdown-menu/dropdown-menu.stories.tsx +0 -128
  80. package/src/components/dropdown-menu/dropdown-menu.test.tsx +0 -321
  81. package/src/components/empty-state/empty-state.stories.tsx +0 -70
  82. package/src/components/empty-state/empty-state.test.tsx +0 -97
  83. package/src/components/form/form.stories.tsx +0 -144
  84. package/src/components/form/form.test.tsx +0 -203
  85. package/src/components/input/input.stories.tsx +0 -84
  86. package/src/components/input/input.test.tsx +0 -108
  87. package/src/components/label/label.stories.tsx +0 -51
  88. package/src/components/label/label.test.tsx +0 -55
  89. package/src/components/pagination/pagination.stories.tsx +0 -129
  90. package/src/components/pagination/pagination.test.tsx +0 -119
  91. package/src/components/popover/popover.stories.tsx +0 -84
  92. package/src/components/popover/popover.test.tsx +0 -163
  93. package/src/components/progress/progress.stories.tsx +0 -90
  94. package/src/components/progress/progress.test.tsx +0 -78
  95. package/src/components/radio-group/radio-group.stories.tsx +0 -102
  96. package/src/components/radio-group/radio-group.test.tsx +0 -152
  97. package/src/components/select/select.stories.tsx +0 -137
  98. package/src/components/select/select.test.tsx +0 -209
  99. package/src/components/separator/separator.stories.tsx +0 -42
  100. package/src/components/separator/separator.test.tsx +0 -46
  101. package/src/components/sheet/sheet.stories.tsx +0 -107
  102. package/src/components/sheet/sheet.test.tsx +0 -152
  103. package/src/components/skeleton/skeleton.stories.tsx +0 -46
  104. package/src/components/skeleton/skeleton.test.tsx +0 -57
  105. package/src/components/slider/slider.stories.tsx +0 -87
  106. package/src/components/slider/slider.test.tsx +0 -148
  107. package/src/components/spinner/spinner.stories.tsx +0 -47
  108. package/src/components/spinner/spinner.test.tsx +0 -41
  109. package/src/components/switch/switch.stories.tsx +0 -70
  110. package/src/components/switch/switch.test.tsx +0 -101
  111. package/src/components/table/table.stories.tsx +0 -131
  112. package/src/components/table/table.test.tsx +0 -153
  113. package/src/components/tabs/tabs.stories.tsx +0 -139
  114. package/src/components/tabs/tabs.test.tsx +0 -148
  115. package/src/components/toast/toast.stories.tsx +0 -167
  116. package/src/components/toast/toast.test.tsx +0 -306
  117. package/src/components/tooltip/tooltip.stories.tsx +0 -89
  118. package/src/components/tooltip/tooltip.test.tsx +0 -120
  119. package/src/lib/utils.test.ts +0 -32
  120. package/src/registry/meta.test.ts +0 -234
@@ -1,128 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/react-vite";
2
- import { CreditCard, LogOut, Settings, User } from "lucide-react";
3
- import { useState } from "react";
4
-
5
- import { Button } from "@/components/button";
6
-
7
- import {
8
- DropdownMenu,
9
- DropdownMenuCheckboxItem,
10
- DropdownMenuContent,
11
- DropdownMenuGroup,
12
- DropdownMenuItem,
13
- DropdownMenuLabel,
14
- DropdownMenuRadioGroup,
15
- DropdownMenuRadioItem,
16
- DropdownMenuSeparator,
17
- DropdownMenuShortcut,
18
- DropdownMenuSub,
19
- DropdownMenuSubContent,
20
- DropdownMenuSubTrigger,
21
- DropdownMenuTrigger,
22
- } from "./dropdown-menu";
23
-
24
- /**
25
- * Annotated rather than inferred with `satisfies`. These components are direct
26
- * re-exports of Radix primitives, and inferring the meta type makes the emitted
27
- * declaration reference Radix-internal prop types that are not nameable from
28
- * this path (TS2883).
29
- */
30
- const meta: Meta<typeof DropdownMenu> = {
31
- title: "Overlays/Dropdown Menu",
32
- component: DropdownMenu,
33
- parameters: { controls: { disable: true } },
34
- };
35
-
36
- export default meta;
37
- type Story = StoryObj<typeof DropdownMenu>;
38
-
39
- export const Default: Story = {
40
- render: () => (
41
- <DropdownMenu>
42
- <DropdownMenuTrigger asChild>
43
- <Button variant="outline">Open menu</Button>
44
- </DropdownMenuTrigger>
45
- <DropdownMenuContent className="w-56" align="start">
46
- <DropdownMenuLabel>My account</DropdownMenuLabel>
47
- <DropdownMenuSeparator />
48
- <DropdownMenuGroup>
49
- <DropdownMenuItem>
50
- <User />
51
- Profile
52
- <DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
53
- </DropdownMenuItem>
54
- <DropdownMenuItem>
55
- <CreditCard />
56
- Billing
57
- </DropdownMenuItem>
58
- <DropdownMenuItem>
59
- <Settings />
60
- Settings
61
- </DropdownMenuItem>
62
- </DropdownMenuGroup>
63
- <DropdownMenuSeparator />
64
- <DropdownMenuItem variant="destructive">
65
- <LogOut />
66
- Sign out
67
- </DropdownMenuItem>
68
- </DropdownMenuContent>
69
- </DropdownMenu>
70
- ),
71
- };
72
-
73
- export const WithSubmenu: Story = {
74
- render: () => (
75
- <DropdownMenu>
76
- <DropdownMenuTrigger asChild>
77
- <Button variant="outline">Share</Button>
78
- </DropdownMenuTrigger>
79
- <DropdownMenuContent className="w-52" align="start">
80
- <DropdownMenuItem>Copy link</DropdownMenuItem>
81
- <DropdownMenuSub>
82
- <DropdownMenuSubTrigger>Invite people</DropdownMenuSubTrigger>
83
- <DropdownMenuSubContent>
84
- <DropdownMenuItem>By email</DropdownMenuItem>
85
- <DropdownMenuItem>By username</DropdownMenuItem>
86
- <DropdownMenuSeparator />
87
- <DropdownMenuItem>Create invite link</DropdownMenuItem>
88
- </DropdownMenuSubContent>
89
- </DropdownMenuSub>
90
- <DropdownMenuSeparator />
91
- <DropdownMenuItem disabled>Publish to web</DropdownMenuItem>
92
- </DropdownMenuContent>
93
- </DropdownMenu>
94
- ),
95
- };
96
-
97
- export const Selectable: Story = {
98
- render: function Selectable() {
99
- const [showStatusBar, setShowStatusBar] = useState(true);
100
- const [showPanel, setShowPanel] = useState(false);
101
- const [position, setPosition] = useState("bottom");
102
-
103
- return (
104
- <DropdownMenu>
105
- <DropdownMenuTrigger asChild>
106
- <Button variant="outline">View</Button>
107
- </DropdownMenuTrigger>
108
- <DropdownMenuContent className="w-56" align="start">
109
- <DropdownMenuLabel>Appearance</DropdownMenuLabel>
110
- <DropdownMenuSeparator />
111
- <DropdownMenuCheckboxItem checked={showStatusBar} onCheckedChange={setShowStatusBar}>
112
- Status bar
113
- </DropdownMenuCheckboxItem>
114
- <DropdownMenuCheckboxItem checked={showPanel} onCheckedChange={setShowPanel}>
115
- Activity panel
116
- </DropdownMenuCheckboxItem>
117
- <DropdownMenuSeparator />
118
- <DropdownMenuLabel>Panel position</DropdownMenuLabel>
119
- <DropdownMenuRadioGroup value={position} onValueChange={setPosition}>
120
- <DropdownMenuRadioItem value="top">Top</DropdownMenuRadioItem>
121
- <DropdownMenuRadioItem value="bottom">Bottom</DropdownMenuRadioItem>
122
- <DropdownMenuRadioItem value="right">Right</DropdownMenuRadioItem>
123
- </DropdownMenuRadioGroup>
124
- </DropdownMenuContent>
125
- </DropdownMenu>
126
- );
127
- },
128
- };
@@ -1,321 +0,0 @@
1
- import { render, screen, waitFor } from "@testing-library/react";
2
- import userEvent from "@testing-library/user-event";
3
- import { useState } from "react";
4
- import { describe, expect, it, vi } from "vitest";
5
-
6
- import { expectNoA11yViolations } from "../../../test/a11y";
7
- import { Button } from "../button";
8
- import {
9
- DropdownMenu,
10
- DropdownMenuCheckboxItem,
11
- DropdownMenuContent,
12
- DropdownMenuItem,
13
- DropdownMenuLabel,
14
- DropdownMenuRadioGroup,
15
- DropdownMenuRadioItem,
16
- DropdownMenuSeparator,
17
- DropdownMenuShortcut,
18
- DropdownMenuSub,
19
- DropdownMenuSubContent,
20
- DropdownMenuSubTrigger,
21
- DropdownMenuTrigger,
22
- } from "./dropdown-menu";
23
-
24
- function Example({ onSelect }: { onSelect?: () => void } = {}) {
25
- return (
26
- <DropdownMenu>
27
- <DropdownMenuTrigger asChild>
28
- <Button>Actions</Button>
29
- </DropdownMenuTrigger>
30
- <DropdownMenuContent>
31
- <DropdownMenuLabel>My account</DropdownMenuLabel>
32
- <DropdownMenuSeparator />
33
- <DropdownMenuItem onSelect={onSelect}>
34
- Profile
35
- <DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
36
- </DropdownMenuItem>
37
- <DropdownMenuItem disabled>Billing</DropdownMenuItem>
38
- <DropdownMenuItem variant="destructive">Delete account</DropdownMenuItem>
39
- </DropdownMenuContent>
40
- </DropdownMenu>
41
- );
42
- }
43
-
44
- describe("DropdownMenu", () => {
45
- it("is closed until the trigger is activated", async () => {
46
- const user = userEvent.setup();
47
- render(<Example />);
48
-
49
- expect(screen.queryByRole("menu")).not.toBeInTheDocument();
50
- await user.click(screen.getByRole("button", { name: "Actions" }));
51
- expect(await screen.findByRole("menu")).toBeInTheDocument();
52
- });
53
-
54
- it("reports its state on the trigger", async () => {
55
- const user = userEvent.setup();
56
- render(<Example />);
57
-
58
- const trigger = screen.getByRole("button", { name: "Actions" });
59
- expect(trigger).toHaveAttribute("aria-expanded", "false");
60
-
61
- await user.click(trigger);
62
- await waitFor(() => {
63
- expect(trigger).toHaveAttribute("aria-expanded", "true");
64
- });
65
- });
66
-
67
- it("opens from the keyboard and focuses the first item", async () => {
68
- const user = userEvent.setup();
69
- render(<Example />);
70
-
71
- await user.tab();
72
- await user.keyboard("{Enter}");
73
-
74
- await waitFor(() => {
75
- expect(screen.getByRole("menuitem", { name: /Profile/ })).toHaveFocus();
76
- });
77
- });
78
-
79
- it("moves between items with the arrow keys, skipping disabled ones", async () => {
80
- const user = userEvent.setup();
81
- render(<Example />);
82
-
83
- await user.click(screen.getByRole("button", { name: "Actions" }));
84
- await screen.findByRole("menu");
85
-
86
- await user.keyboard("{ArrowDown}");
87
- await waitFor(() => {
88
- expect(screen.getByRole("menuitem", { name: /Profile/ })).toHaveFocus();
89
- });
90
-
91
- await user.keyboard("{ArrowDown}");
92
- await waitFor(() => {
93
- expect(screen.getByRole("menuitem", { name: "Delete account" })).toHaveFocus();
94
- });
95
- });
96
-
97
- it("selects an item and closes", async () => {
98
- const onSelect = vi.fn();
99
- const user = userEvent.setup();
100
- render(<Example onSelect={onSelect} />);
101
-
102
- await user.click(screen.getByRole("button", { name: "Actions" }));
103
- await user.click(await screen.findByRole("menuitem", { name: /Profile/ }));
104
-
105
- expect(onSelect).toHaveBeenCalledTimes(1);
106
- await waitFor(() => {
107
- expect(screen.queryByRole("menu")).not.toBeInTheDocument();
108
- });
109
- });
110
-
111
- it("does not select a disabled item", async () => {
112
- const user = userEvent.setup();
113
- render(<Example />);
114
-
115
- await user.click(screen.getByRole("button", { name: "Actions" }));
116
- const billing = await screen.findByRole("menuitem", { name: "Billing" });
117
-
118
- expect(billing).toHaveAttribute("data-disabled");
119
- await user.click(billing);
120
- expect(screen.getByRole("menu")).toBeInTheDocument();
121
- });
122
-
123
- it("closes on Escape and restores focus to the trigger", async () => {
124
- const user = userEvent.setup();
125
- render(<Example />);
126
-
127
- const trigger = screen.getByRole("button", { name: "Actions" });
128
- await user.click(trigger);
129
- await screen.findByRole("menu");
130
-
131
- await user.keyboard("{Escape}");
132
- await waitFor(() => {
133
- expect(screen.queryByRole("menu")).not.toBeInTheDocument();
134
- });
135
- await waitFor(() => {
136
- expect(trigger).toHaveFocus();
137
- });
138
- });
139
-
140
- it("marks a destructive item for styling", async () => {
141
- const user = userEvent.setup();
142
- render(<Example />);
143
-
144
- await user.click(screen.getByRole("button", { name: "Actions" }));
145
- const item = await screen.findByRole("menuitem", { name: "Delete account" });
146
- expect(item).toHaveAttribute("data-variant", "destructive");
147
- expect(item).toHaveClass("text-destructive");
148
- });
149
-
150
- it("hides the shortcut hint from assistive technology", async () => {
151
- const user = userEvent.setup();
152
- const { baseElement } = render(<Example />);
153
-
154
- await user.click(screen.getByRole("button", { name: "Actions" }));
155
- await screen.findByRole("menu");
156
-
157
- const shortcut = baseElement.querySelector("[data-slot='dropdown-menu-shortcut']");
158
- expect(shortcut).toHaveAttribute("aria-hidden", "true");
159
- });
160
-
161
- it("toggles a checkbox item", async () => {
162
- const user = userEvent.setup();
163
-
164
- function WithCheckbox() {
165
- const [checked, setChecked] = useState(false);
166
- return (
167
- <DropdownMenu>
168
- <DropdownMenuTrigger>View</DropdownMenuTrigger>
169
- <DropdownMenuContent>
170
- <DropdownMenuCheckboxItem checked={checked} onCheckedChange={setChecked}>
171
- Status bar
172
- </DropdownMenuCheckboxItem>
173
- </DropdownMenuContent>
174
- </DropdownMenu>
175
- );
176
- }
177
-
178
- render(<WithCheckbox />);
179
- await user.click(screen.getByText("View"));
180
-
181
- const item = await screen.findByRole("menuitemcheckbox", { name: "Status bar" });
182
- expect(item).toHaveAttribute("aria-checked", "false");
183
-
184
- await user.click(item);
185
- await user.click(screen.getByText("View"));
186
- await waitFor(() => {
187
- expect(screen.getByRole("menuitemcheckbox", { name: "Status bar" })).toHaveAttribute(
188
- "aria-checked",
189
- "true",
190
- );
191
- });
192
- });
193
-
194
- it("selects a radio item", async () => {
195
- const user = userEvent.setup();
196
-
197
- function WithRadio() {
198
- const [value, setValue] = useState("light");
199
- return (
200
- <DropdownMenu>
201
- <DropdownMenuTrigger>Theme</DropdownMenuTrigger>
202
- <DropdownMenuContent>
203
- <DropdownMenuRadioGroup value={value} onValueChange={setValue}>
204
- <DropdownMenuRadioItem value="light">Light</DropdownMenuRadioItem>
205
- <DropdownMenuRadioItem value="dark">Dark</DropdownMenuRadioItem>
206
- </DropdownMenuRadioGroup>
207
- </DropdownMenuContent>
208
- </DropdownMenu>
209
- );
210
- }
211
-
212
- render(<WithRadio />);
213
- await user.click(screen.getByText("Theme"));
214
- expect(await screen.findByRole("menuitemradio", { name: "Light" })).toHaveAttribute(
215
- "aria-checked",
216
- "true",
217
- );
218
-
219
- await user.click(screen.getByRole("menuitemradio", { name: "Dark" }));
220
- await user.click(screen.getByText("Theme"));
221
- await waitFor(() => {
222
- expect(screen.getByRole("menuitemradio", { name: "Dark" })).toHaveAttribute(
223
- "aria-checked",
224
- "true",
225
- );
226
- });
227
- });
228
-
229
- describe("submenus", () => {
230
- function WithSubmenu() {
231
- return (
232
- <DropdownMenu>
233
- <DropdownMenuTrigger>Share</DropdownMenuTrigger>
234
- <DropdownMenuContent>
235
- <DropdownMenuItem>Copy link</DropdownMenuItem>
236
- <DropdownMenuSub>
237
- <DropdownMenuSubTrigger>Invite people</DropdownMenuSubTrigger>
238
- <DropdownMenuSubContent>
239
- <DropdownMenuItem>By email</DropdownMenuItem>
240
- </DropdownMenuSubContent>
241
- </DropdownMenuSub>
242
- </DropdownMenuContent>
243
- </DropdownMenu>
244
- );
245
- }
246
-
247
- it("opens a submenu with the right arrow key", async () => {
248
- const user = userEvent.setup();
249
- render(<WithSubmenu />);
250
-
251
- await user.click(screen.getByText("Share"));
252
- await screen.findByRole("menu");
253
-
254
- const subTrigger = screen.getByRole("menuitem", { name: "Invite people" });
255
- expect(subTrigger).toHaveAttribute("aria-expanded", "false");
256
-
257
- subTrigger.focus();
258
- await user.keyboard("{ArrowRight}");
259
-
260
- await waitFor(() => {
261
- expect(screen.getByRole("menuitem", { name: "By email" })).toBeInTheDocument();
262
- });
263
- expect(subTrigger).toHaveAttribute("aria-expanded", "true");
264
- });
265
-
266
- it("closes a submenu with the left arrow key", async () => {
267
- const user = userEvent.setup();
268
- render(<WithSubmenu />);
269
-
270
- await user.click(screen.getByText("Share"));
271
- const subTrigger = await screen.findByRole("menuitem", { name: "Invite people" });
272
-
273
- subTrigger.focus();
274
- await user.keyboard("{ArrowRight}");
275
- await screen.findByRole("menuitem", { name: "By email" });
276
-
277
- await user.keyboard("{ArrowLeft}");
278
- await waitFor(() => {
279
- expect(screen.queryByRole("menuitem", { name: "By email" })).not.toBeInTheDocument();
280
- });
281
- });
282
-
283
- it("has no accessibility violations with a submenu open", async () => {
284
- const user = userEvent.setup();
285
- const { baseElement } = render(<WithSubmenu />);
286
-
287
- await user.click(screen.getByText("Share"));
288
- const subTrigger = await screen.findByRole("menuitem", { name: "Invite people" });
289
- subTrigger.focus();
290
- await user.keyboard("{ArrowRight}");
291
- await screen.findByRole("menuitem", { name: "By email" });
292
-
293
- await expectNoA11yViolations(baseElement);
294
- });
295
- });
296
-
297
- it("merges a consumer className on the content", async () => {
298
- const user = userEvent.setup();
299
- render(
300
- <DropdownMenu>
301
- <DropdownMenuTrigger>Open</DropdownMenuTrigger>
302
- <DropdownMenuContent className="w-64">
303
- <DropdownMenuItem>Item</DropdownMenuItem>
304
- </DropdownMenuContent>
305
- </DropdownMenu>,
306
- );
307
-
308
- await user.click(screen.getByText("Open"));
309
- expect(await screen.findByRole("menu")).toHaveClass("w-64");
310
- });
311
-
312
- it("has no accessibility violations while open", async () => {
313
- const user = userEvent.setup();
314
- const { baseElement } = render(<Example />);
315
-
316
- await user.click(screen.getByRole("button", { name: "Actions" }));
317
- await screen.findByRole("menu");
318
-
319
- await expectNoA11yViolations(baseElement);
320
- });
321
- });
@@ -1,70 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/react-vite";
2
- import { FolderPlus, Search } from "lucide-react";
3
-
4
- import { Button } from "@/components/button";
5
-
6
- import {
7
- EmptyState,
8
- EmptyStateActions,
9
- EmptyStateDescription,
10
- EmptyStateIcon,
11
- EmptyStateTitle,
12
- } from "./empty-state";
13
-
14
- const meta = {
15
- title: "Feedback/Empty State",
16
- component: EmptyState,
17
- args: { size: "md", bordered: true },
18
- argTypes: {
19
- size: { control: "inline-radio", options: ["sm", "md", "lg"] },
20
- bordered: { control: "boolean" },
21
- },
22
- } satisfies Meta<typeof EmptyState>;
23
-
24
- export default meta;
25
- type Story = StoryObj<typeof meta>;
26
-
27
- /** Nothing created yet: this is where a call to action belongs. */
28
- export const NothingYet: Story = {
29
- render: (args) => (
30
- <EmptyState className="w-96" {...args}>
31
- <EmptyStateIcon>
32
- <FolderPlus />
33
- </EmptyStateIcon>
34
- <EmptyStateTitle>No projects yet</EmptyStateTitle>
35
- <EmptyStateDescription>
36
- Projects group your deployments and environments. Create one to get started.
37
- </EmptyStateDescription>
38
- <EmptyStateActions>
39
- <Button>New project</Button>
40
- <Button variant="outline">Import from Git</Button>
41
- </EmptyStateActions>
42
- </EmptyState>
43
- ),
44
- };
45
-
46
- /** Nothing matched a search: a different message, and a different next step. */
47
- export const NoResults: Story = {
48
- render: (args) => (
49
- <EmptyState className="w-96" {...args}>
50
- <EmptyStateIcon>
51
- <Search />
52
- </EmptyStateIcon>
53
- <EmptyStateTitle>No results for “analytics”</EmptyStateTitle>
54
- <EmptyStateDescription>
55
- Check the spelling, or try a broader search term.
56
- </EmptyStateDescription>
57
- <EmptyStateActions>
58
- <Button variant="outline">Clear filters</Button>
59
- </EmptyStateActions>
60
- </EmptyState>
61
- ),
62
- };
63
-
64
- export const Minimal: Story = {
65
- render: (args) => (
66
- <EmptyState className="w-96" {...args}>
67
- <EmptyStateTitle>Nothing here</EmptyStateTitle>
68
- </EmptyState>
69
- ),
70
- };
@@ -1,97 +0,0 @@
1
- import { render, screen } from "@testing-library/react";
2
- import { describe, expect, it } from "vitest";
3
-
4
- import { expectNoA11yViolations } from "../../../test/a11y";
5
- import { Button } from "../button";
6
- import {
7
- EmptyState,
8
- EmptyStateActions,
9
- EmptyStateDescription,
10
- EmptyStateIcon,
11
- EmptyStateTitle,
12
- } from "./empty-state";
13
-
14
- describe("EmptyState", () => {
15
- it("renders its parts", () => {
16
- render(
17
- <EmptyState>
18
- <EmptyStateIcon>
19
- <svg />
20
- </EmptyStateIcon>
21
- <EmptyStateTitle>No projects yet</EmptyStateTitle>
22
- <EmptyStateDescription>Create one to get started.</EmptyStateDescription>
23
- <EmptyStateActions>
24
- <Button>New project</Button>
25
- </EmptyStateActions>
26
- </EmptyState>,
27
- );
28
-
29
- expect(screen.getByText("No projects yet")).toBeInTheDocument();
30
- expect(screen.getByText("Create one to get started.")).toBeInTheDocument();
31
- expect(screen.getByRole("button", { name: "New project" })).toBeInTheDocument();
32
- });
33
-
34
- it("hides the icon from assistive technology", () => {
35
- const { container } = render(
36
- <EmptyState>
37
- <EmptyStateIcon>
38
- <svg />
39
- </EmptyStateIcon>
40
- <EmptyStateTitle>Nothing here</EmptyStateTitle>
41
- </EmptyState>,
42
- );
43
-
44
- expect(container.querySelector("[data-slot='empty-state-icon']")).toHaveAttribute(
45
- "aria-hidden",
46
- "true",
47
- );
48
- });
49
-
50
- it.each([
51
- ["sm", "py-8"],
52
- ["md", "py-12"],
53
- ["lg", "py-20"],
54
- ] as const)("applies the %s size", (size, expectedClass) => {
55
- const { container } = render(
56
- <EmptyState size={size}>
57
- <EmptyStateTitle>Nothing</EmptyStateTitle>
58
- </EmptyState>,
59
- );
60
- expect(container.firstElementChild).toHaveClass(expectedClass);
61
- });
62
-
63
- it("can render a dashed border", () => {
64
- const { container } = render(
65
- <EmptyState bordered>
66
- <EmptyStateTitle>Nothing</EmptyStateTitle>
67
- </EmptyState>,
68
- );
69
- expect(container.firstElementChild).toHaveClass("border-dashed");
70
- });
71
-
72
- it("lets a consumer className override a conflicting utility", () => {
73
- const { container } = render(
74
- <EmptyState className="py-2">
75
- <EmptyStateTitle>Nothing</EmptyStateTitle>
76
- </EmptyState>,
77
- );
78
- expect(container.firstElementChild).toHaveClass("py-2");
79
- expect(container.firstElementChild).not.toHaveClass("py-12");
80
- });
81
-
82
- it("has no accessibility violations", async () => {
83
- const { container } = render(
84
- <EmptyState bordered>
85
- <EmptyStateIcon>
86
- <svg />
87
- </EmptyStateIcon>
88
- <EmptyStateTitle>No results</EmptyStateTitle>
89
- <EmptyStateDescription>Try a different search.</EmptyStateDescription>
90
- <EmptyStateActions>
91
- <Button variant="outline">Clear filters</Button>
92
- </EmptyStateActions>
93
- </EmptyState>,
94
- );
95
- await expectNoA11yViolations(container);
96
- });
97
- });