@dowel-ui/react 0.1.0 → 0.2.0

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 (195) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +187 -0
  3. package/dist/components/ai-action-ledger/ai-action-ledger.d.ts +77 -0
  4. package/dist/components/ai-action-ledger/ai-action-ledger.d.ts.map +1 -0
  5. package/dist/components/ai-action-ledger/ai-action-ledger.js +224 -0
  6. package/dist/components/ai-action-ledger/ai-action-ledger.js.map +1 -0
  7. package/dist/components/ai-action-ledger/index.d.ts +2 -0
  8. package/dist/components/ai-action-ledger/index.js +2 -0
  9. package/dist/components/ai-action-ledger/meta.js +17 -0
  10. package/dist/components/ai-action-ledger/meta.js.map +1 -0
  11. package/dist/components/ai-agent-status/ai-agent-status.d.ts +1 -1
  12. package/dist/components/ai-inline-completion/ai-inline-completion.d.ts +45 -0
  13. package/dist/components/ai-inline-completion/ai-inline-completion.d.ts.map +1 -0
  14. package/dist/components/ai-inline-completion/ai-inline-completion.js +110 -0
  15. package/dist/components/ai-inline-completion/ai-inline-completion.js.map +1 -0
  16. package/dist/components/ai-inline-completion/index.d.ts +2 -0
  17. package/dist/components/ai-inline-completion/index.js +2 -0
  18. package/dist/components/ai-inline-completion/meta.js +17 -0
  19. package/dist/components/ai-inline-completion/meta.js.map +1 -0
  20. package/dist/components/ai-structured-output/ai-structured-output.d.ts +75 -0
  21. package/dist/components/ai-structured-output/ai-structured-output.d.ts.map +1 -0
  22. package/dist/components/ai-structured-output/ai-structured-output.js +126 -0
  23. package/dist/components/ai-structured-output/ai-structured-output.js.map +1 -0
  24. package/dist/components/ai-structured-output/index.d.ts +2 -0
  25. package/dist/components/ai-structured-output/index.js +2 -0
  26. package/dist/components/ai-structured-output/meta.js +17 -0
  27. package/dist/components/ai-structured-output/meta.js.map +1 -0
  28. package/dist/components/ai-tool/ai-tool.d.ts +1 -1
  29. package/dist/components/alert/alert.d.ts +1 -1
  30. package/dist/components/avatar/avatar.d.ts +1 -1
  31. package/dist/components/badge/badge.d.ts +1 -1
  32. package/dist/components/button/button.d.ts +1 -1
  33. package/dist/components/meter/index.d.ts +2 -0
  34. package/dist/components/meter/index.js +2 -0
  35. package/dist/components/meter/meta.js +17 -0
  36. package/dist/components/meter/meta.js.map +1 -0
  37. package/dist/components/meter/meter.d.ts +69 -0
  38. package/dist/components/meter/meter.d.ts.map +1 -0
  39. package/dist/components/meter/meter.js +144 -0
  40. package/dist/components/meter/meter.js.map +1 -0
  41. package/dist/components/metric-delta/index.d.ts +2 -0
  42. package/dist/components/metric-delta/index.js +2 -0
  43. package/dist/components/metric-delta/meta.js +17 -0
  44. package/dist/components/metric-delta/meta.js.map +1 -0
  45. package/dist/components/metric-delta/metric-delta.d.ts +59 -0
  46. package/dist/components/metric-delta/metric-delta.d.ts.map +1 -0
  47. package/dist/components/metric-delta/metric-delta.js +82 -0
  48. package/dist/components/metric-delta/metric-delta.js.map +1 -0
  49. package/dist/components/progress/progress.d.ts +1 -1
  50. package/dist/components/record-diff/index.d.ts +2 -0
  51. package/dist/components/record-diff/index.js +2 -0
  52. package/dist/components/record-diff/meta.js +17 -0
  53. package/dist/components/record-diff/meta.js.map +1 -0
  54. package/dist/components/record-diff/record-diff.d.ts +56 -0
  55. package/dist/components/record-diff/record-diff.d.ts.map +1 -0
  56. package/dist/components/record-diff/record-diff.js +199 -0
  57. package/dist/components/record-diff/record-diff.js.map +1 -0
  58. package/dist/components/spinner/spinner.d.ts +1 -1
  59. package/dist/components/toast/toast.d.ts +1 -1
  60. package/dist/index.d.ts +13 -1
  61. package/dist/index.js +7 -1
  62. package/dist/registry/components.d.ts.map +1 -1
  63. package/dist/registry/components.js +57 -45
  64. package/dist/registry/components.js.map +1 -1
  65. package/package.json +40 -5
  66. package/src/components/ai-action-ledger/ai-action-ledger.tsx +381 -0
  67. package/src/components/ai-action-ledger/index.ts +14 -0
  68. package/src/components/ai-action-ledger/meta.ts +19 -0
  69. package/src/components/ai-inline-completion/ai-inline-completion.tsx +214 -0
  70. package/src/components/ai-inline-completion/index.ts +1 -0
  71. package/src/components/ai-inline-completion/meta.ts +20 -0
  72. package/src/components/ai-structured-output/ai-structured-output.tsx +244 -0
  73. package/src/components/ai-structured-output/index.ts +10 -0
  74. package/src/components/ai-structured-output/meta.ts +18 -0
  75. package/src/components/meter/index.ts +8 -0
  76. package/src/components/meter/meta.ts +20 -0
  77. package/src/components/meter/meter.tsx +219 -0
  78. package/src/components/metric-delta/index.ts +6 -0
  79. package/src/components/metric-delta/meta.ts +18 -0
  80. package/src/components/metric-delta/metric-delta.tsx +180 -0
  81. package/src/components/record-diff/index.ts +10 -0
  82. package/src/components/record-diff/meta.ts +19 -0
  83. package/src/components/record-diff/record-diff.tsx +341 -0
  84. package/src/index.ts +6 -0
  85. package/src/registry/components.ts +12 -0
  86. package/src/blocks/admin-users/admin-users.stories.tsx +0 -83
  87. package/src/blocks/admin-users/admin-users.test.tsx +0 -159
  88. package/src/blocks/ai-chat/ai-chat.stories.tsx +0 -149
  89. package/src/blocks/ai-chat/ai-chat.test.tsx +0 -166
  90. package/src/blocks/dashboard/dashboard.stories.tsx +0 -106
  91. package/src/blocks/dashboard/dashboard.test.tsx +0 -94
  92. package/src/blocks/forgot-password/forgot-password.stories.tsx +0 -41
  93. package/src/blocks/forgot-password/forgot-password.test.tsx +0 -62
  94. package/src/blocks/login/login.stories.tsx +0 -46
  95. package/src/blocks/login/login.test.tsx +0 -100
  96. package/src/blocks/pricing/pricing.stories.tsx +0 -62
  97. package/src/blocks/pricing/pricing.test.tsx +0 -101
  98. package/src/blocks/settings/settings.stories.tsx +0 -94
  99. package/src/blocks/settings/settings.test.tsx +0 -167
  100. package/src/blocks/signup/signup.stories.tsx +0 -27
  101. package/src/blocks/signup/signup.test.tsx +0 -97
  102. package/src/components/accordion/accordion.stories.tsx +0 -90
  103. package/src/components/accordion/accordion.test.tsx +0 -156
  104. package/src/components/activity-feed/activity-feed.stories.tsx +0 -163
  105. package/src/components/activity-feed/activity-feed.test.tsx +0 -125
  106. package/src/components/ai-agent-status/ai-agent-status.stories.tsx +0 -86
  107. package/src/components/ai-agent-status/ai-agent-status.test.tsx +0 -66
  108. package/src/components/ai-conversation/ai-conversation.stories.tsx +0 -126
  109. package/src/components/ai-conversation/ai-conversation.test.tsx +0 -144
  110. package/src/components/ai-message/ai-message.stories.tsx +0 -106
  111. package/src/components/ai-message/ai-message.test.tsx +0 -160
  112. package/src/components/ai-model-selector/ai-model-selector.stories.tsx +0 -66
  113. package/src/components/ai-model-selector/ai-model-selector.test.tsx +0 -120
  114. package/src/components/ai-prompt-input/ai-prompt-input.stories.tsx +0 -145
  115. package/src/components/ai-prompt-input/ai-prompt-input.test.tsx +0 -273
  116. package/src/components/ai-reasoning/ai-reasoning.stories.tsx +0 -60
  117. package/src/components/ai-reasoning/ai-reasoning.test.tsx +0 -95
  118. package/src/components/ai-response/ai-response.stories.tsx +0 -75
  119. package/src/components/ai-response/ai-response.test.tsx +0 -96
  120. package/src/components/ai-sources/ai-sources.stories.tsx +0 -93
  121. package/src/components/ai-sources/ai-sources.test.tsx +0 -102
  122. package/src/components/ai-token-usage/ai-token-usage.stories.tsx +0 -59
  123. package/src/components/ai-token-usage/ai-token-usage.test.tsx +0 -83
  124. package/src/components/ai-tool/ai-tool.stories.tsx +0 -96
  125. package/src/components/ai-tool/ai-tool.test.tsx +0 -137
  126. package/src/components/alert/alert.stories.tsx +0 -101
  127. package/src/components/alert/alert.test.tsx +0 -94
  128. package/src/components/avatar/avatar.stories.tsx +0 -58
  129. package/src/components/avatar/avatar.test.tsx +0 -64
  130. package/src/components/badge/badge.stories.tsx +0 -68
  131. package/src/components/badge/badge.test.tsx +0 -61
  132. package/src/components/button/button.stories.tsx +0 -128
  133. package/src/components/button/button.test.tsx +0 -224
  134. package/src/components/calendar/calendar.stories.tsx +0 -105
  135. package/src/components/calendar/calendar.test.tsx +0 -255
  136. package/src/components/card/card.stories.tsx +0 -61
  137. package/src/components/card/card.test.tsx +0 -66
  138. package/src/components/checkbox/checkbox.stories.tsx +0 -124
  139. package/src/components/checkbox/checkbox.test.tsx +0 -121
  140. package/src/components/code-block/code-block.stories.tsx +0 -62
  141. package/src/components/code-block/code-block.test.tsx +0 -139
  142. package/src/components/combobox/combobox.stories.tsx +0 -171
  143. package/src/components/combobox/combobox.test.tsx +0 -429
  144. package/src/components/command/command.stories.tsx +0 -152
  145. package/src/components/command/command.test.tsx +0 -380
  146. package/src/components/data-table/data-table.stories.tsx +0 -151
  147. package/src/components/data-table/data-table.test.tsx +0 -288
  148. package/src/components/date-picker/date-picker.stories.tsx +0 -103
  149. package/src/components/date-picker/date-picker.test.tsx +0 -205
  150. package/src/components/dialog/dialog.stories.tsx +0 -140
  151. package/src/components/dialog/dialog.test.tsx +0 -186
  152. package/src/components/drawer/drawer.stories.tsx +0 -111
  153. package/src/components/drawer/drawer.test.tsx +0 -187
  154. package/src/components/dropdown-menu/dropdown-menu.stories.tsx +0 -128
  155. package/src/components/dropdown-menu/dropdown-menu.test.tsx +0 -321
  156. package/src/components/empty-state/empty-state.stories.tsx +0 -70
  157. package/src/components/empty-state/empty-state.test.tsx +0 -97
  158. package/src/components/form/form.stories.tsx +0 -144
  159. package/src/components/form/form.test.tsx +0 -203
  160. package/src/components/input/input.stories.tsx +0 -84
  161. package/src/components/input/input.test.tsx +0 -108
  162. package/src/components/label/label.stories.tsx +0 -51
  163. package/src/components/label/label.test.tsx +0 -55
  164. package/src/components/pagination/pagination.stories.tsx +0 -129
  165. package/src/components/pagination/pagination.test.tsx +0 -119
  166. package/src/components/popover/popover.stories.tsx +0 -84
  167. package/src/components/popover/popover.test.tsx +0 -163
  168. package/src/components/progress/progress.stories.tsx +0 -90
  169. package/src/components/progress/progress.test.tsx +0 -78
  170. package/src/components/radio-group/radio-group.stories.tsx +0 -102
  171. package/src/components/radio-group/radio-group.test.tsx +0 -152
  172. package/src/components/select/select.stories.tsx +0 -137
  173. package/src/components/select/select.test.tsx +0 -209
  174. package/src/components/separator/separator.stories.tsx +0 -42
  175. package/src/components/separator/separator.test.tsx +0 -46
  176. package/src/components/sheet/sheet.stories.tsx +0 -107
  177. package/src/components/sheet/sheet.test.tsx +0 -152
  178. package/src/components/skeleton/skeleton.stories.tsx +0 -46
  179. package/src/components/skeleton/skeleton.test.tsx +0 -57
  180. package/src/components/slider/slider.stories.tsx +0 -87
  181. package/src/components/slider/slider.test.tsx +0 -148
  182. package/src/components/spinner/spinner.stories.tsx +0 -47
  183. package/src/components/spinner/spinner.test.tsx +0 -41
  184. package/src/components/switch/switch.stories.tsx +0 -70
  185. package/src/components/switch/switch.test.tsx +0 -101
  186. package/src/components/table/table.stories.tsx +0 -131
  187. package/src/components/table/table.test.tsx +0 -153
  188. package/src/components/tabs/tabs.stories.tsx +0 -139
  189. package/src/components/tabs/tabs.test.tsx +0 -148
  190. package/src/components/toast/toast.stories.tsx +0 -167
  191. package/src/components/toast/toast.test.tsx +0 -306
  192. package/src/components/tooltip/tooltip.stories.tsx +0 -89
  193. package/src/components/tooltip/tooltip.test.tsx +0 -120
  194. package/src/lib/utils.test.ts +0 -32
  195. 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
- });