@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,140 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/react-vite";
2
-
3
- import { Button } from "@/components/button";
4
- import { Input } from "@/components/input";
5
- import { Label } from "@/components/label";
6
-
7
- import {
8
- Dialog,
9
- DialogClose,
10
- DialogContent,
11
- DialogDescription,
12
- DialogFooter,
13
- DialogHeader,
14
- DialogTitle,
15
- DialogTrigger,
16
- } from "./dialog";
17
-
18
- /**
19
- * Annotated rather than inferred with `satisfies`. These components are direct
20
- * re-exports of Radix primitives, and inferring the meta type makes the emitted
21
- * declaration reference Radix-internal prop types that are not nameable from
22
- * this path (TS2883).
23
- */
24
- const meta: Meta<typeof Dialog> = {
25
- title: "Overlays/Dialog",
26
- component: Dialog,
27
- parameters: { controls: { disable: true } },
28
- };
29
-
30
- export default meta;
31
- type Story = StoryObj<typeof Dialog>;
32
-
33
- export const Default: Story = {
34
- render: () => (
35
- <Dialog>
36
- <DialogTrigger asChild>
37
- <Button>Edit profile</Button>
38
- </DialogTrigger>
39
- <DialogContent>
40
- <DialogHeader>
41
- <DialogTitle>Edit profile</DialogTitle>
42
- <DialogDescription>
43
- Make changes to your profile. Save when you are done.
44
- </DialogDescription>
45
- </DialogHeader>
46
- <div className="grid gap-4">
47
- <div className="grid gap-2">
48
- <Label htmlFor="name">Name</Label>
49
- <Input id="name" defaultValue="Ada Lovelace" />
50
- </div>
51
- <div className="grid gap-2">
52
- <Label htmlFor="handle">Handle</Label>
53
- <Input id="handle" defaultValue="@ada" />
54
- </div>
55
- </div>
56
- <DialogFooter>
57
- <DialogClose asChild>
58
- <Button variant="outline">Cancel</Button>
59
- </DialogClose>
60
- <Button>Save changes</Button>
61
- </DialogFooter>
62
- </DialogContent>
63
- </Dialog>
64
- ),
65
- };
66
-
67
- /** A destructive confirmation: the description must state what is lost. */
68
- export const Confirmation: Story = {
69
- render: () => (
70
- <Dialog>
71
- <DialogTrigger asChild>
72
- <Button variant="destructive">Delete project</Button>
73
- </DialogTrigger>
74
- <DialogContent className="max-w-md">
75
- <DialogHeader>
76
- <DialogTitle>Delete acme-inc?</DialogTitle>
77
- <DialogDescription>
78
- This permanently deletes the project and all 42 of its deployments. This cannot be
79
- undone.
80
- </DialogDescription>
81
- </DialogHeader>
82
- <DialogFooter>
83
- <DialogClose asChild>
84
- <Button variant="outline">Cancel</Button>
85
- </DialogClose>
86
- <Button variant="destructive">Delete project</Button>
87
- </DialogFooter>
88
- </DialogContent>
89
- </Dialog>
90
- ),
91
- };
92
-
93
- /** Without the built-in close button, supply your own dismissal path. */
94
- export const WithoutCloseButton: Story = {
95
- render: () => (
96
- <Dialog>
97
- <DialogTrigger asChild>
98
- <Button variant="outline">Open</Button>
99
- </DialogTrigger>
100
- <DialogContent showCloseButton={false}>
101
- <DialogHeader>
102
- <DialogTitle>Terms of service</DialogTitle>
103
- <DialogDescription>Accept the terms to continue.</DialogDescription>
104
- </DialogHeader>
105
- <DialogFooter>
106
- <DialogClose asChild>
107
- <Button variant="outline">Decline</Button>
108
- </DialogClose>
109
- <DialogClose asChild>
110
- <Button>Accept</Button>
111
- </DialogClose>
112
- </DialogFooter>
113
- </DialogContent>
114
- </Dialog>
115
- ),
116
- };
117
-
118
- export const ScrollingContent: Story = {
119
- render: () => (
120
- <Dialog>
121
- <DialogTrigger asChild>
122
- <Button variant="outline">Read changelog</Button>
123
- </DialogTrigger>
124
- <DialogContent>
125
- <DialogHeader>
126
- <DialogTitle>Changelog</DialogTitle>
127
- <DialogDescription>Everything shipped this month.</DialogDescription>
128
- </DialogHeader>
129
- <div className="max-h-72 space-y-4 overflow-y-auto pr-2 text-sm text-muted-foreground">
130
- {Array.from({ length: 12 }, (_, index) => (
131
- <p key={index}>
132
- Release {String(index + 1)}: performance improvements, bug fixes and a handful of
133
- new keyboard shortcuts.
134
- </p>
135
- ))}
136
- </div>
137
- </DialogContent>
138
- </Dialog>
139
- ),
140
- };
@@ -1,186 +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
- Dialog,
10
- DialogClose,
11
- DialogContent,
12
- DialogDescription,
13
- DialogFooter,
14
- DialogHeader,
15
- DialogTitle,
16
- DialogTrigger,
17
- } from "./dialog";
18
-
19
- function Example({ showCloseButton = true }: { showCloseButton?: boolean }) {
20
- return (
21
- <Dialog>
22
- <DialogTrigger asChild>
23
- <Button>Open</Button>
24
- </DialogTrigger>
25
- <DialogContent showCloseButton={showCloseButton}>
26
- <DialogHeader>
27
- <DialogTitle>Delete project</DialogTitle>
28
- <DialogDescription>This cannot be undone.</DialogDescription>
29
- </DialogHeader>
30
- <DialogFooter>
31
- <DialogClose asChild>
32
- <Button variant="outline">Cancel</Button>
33
- </DialogClose>
34
- <Button variant="destructive">Delete</Button>
35
- </DialogFooter>
36
- </DialogContent>
37
- </Dialog>
38
- );
39
- }
40
-
41
- describe("Dialog", () => {
42
- it("is closed until the trigger is activated", async () => {
43
- const user = userEvent.setup();
44
- render(<Example />);
45
-
46
- expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
47
- await user.click(screen.getByRole("button", { name: "Open" }));
48
- expect(await screen.findByRole("dialog")).toBeInTheDocument();
49
- });
50
-
51
- it("is named and described by its title and description", async () => {
52
- const user = userEvent.setup();
53
- render(<Example />);
54
- await user.click(screen.getByRole("button", { name: "Open" }));
55
-
56
- const dialog = await screen.findByRole("dialog", { name: "Delete project" });
57
- expect(dialog).toHaveAccessibleDescription("This cannot be undone.");
58
- });
59
-
60
- it("opens from the keyboard", async () => {
61
- const user = userEvent.setup();
62
- render(<Example />);
63
-
64
- await user.tab();
65
- expect(screen.getByRole("button", { name: "Open" })).toHaveFocus();
66
- await user.keyboard("{Enter}");
67
- expect(await screen.findByRole("dialog")).toBeInTheDocument();
68
- });
69
-
70
- it("moves focus into the dialog when it opens", async () => {
71
- const user = userEvent.setup();
72
- render(<Example />);
73
- await user.click(screen.getByRole("button", { name: "Open" }));
74
-
75
- const dialog = await screen.findByRole("dialog");
76
- await waitFor(() => {
77
- expect(dialog).toContainElement(document.activeElement as HTMLElement);
78
- });
79
- });
80
-
81
- it("closes on Escape and restores focus to the trigger", async () => {
82
- const user = userEvent.setup();
83
- render(<Example />);
84
-
85
- const trigger = screen.getByRole("button", { name: "Open" });
86
- await user.click(trigger);
87
- await screen.findByRole("dialog");
88
-
89
- await user.keyboard("{Escape}");
90
- await waitFor(() => {
91
- expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
92
- });
93
- await waitFor(() => {
94
- expect(trigger).toHaveFocus();
95
- });
96
- });
97
-
98
- it("closes via the built-in close button", async () => {
99
- const user = userEvent.setup();
100
- render(<Example />);
101
- await user.click(screen.getByRole("button", { name: "Open" }));
102
- await screen.findByRole("dialog");
103
-
104
- await user.click(screen.getByRole("button", { name: "Close" }));
105
- await waitFor(() => {
106
- expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
107
- });
108
- });
109
-
110
- it("omits the close button when asked", async () => {
111
- const user = userEvent.setup();
112
- render(<Example showCloseButton={false} />);
113
- await user.click(screen.getByRole("button", { name: "Open" }));
114
- await screen.findByRole("dialog");
115
-
116
- expect(screen.queryByRole("button", { name: "Close" })).not.toBeInTheDocument();
117
- });
118
-
119
- it("closes via DialogClose", async () => {
120
- const user = userEvent.setup();
121
- render(<Example />);
122
- await user.click(screen.getByRole("button", { name: "Open" }));
123
- await screen.findByRole("dialog");
124
-
125
- await user.click(screen.getByRole("button", { name: "Cancel" }));
126
- await waitFor(() => {
127
- expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
128
- });
129
- });
130
-
131
- it("works as a controlled component", async () => {
132
- const onOpenChange = vi.fn();
133
- const user = userEvent.setup();
134
-
135
- function Controlled() {
136
- const [open, setOpen] = useState(false);
137
- return (
138
- <Dialog
139
- open={open}
140
- onOpenChange={(next) => {
141
- onOpenChange(next);
142
- setOpen(next);
143
- }}
144
- >
145
- <DialogTrigger asChild>
146
- <Button>Open</Button>
147
- </DialogTrigger>
148
- <DialogContent>
149
- <DialogTitle>Controlled</DialogTitle>
150
- </DialogContent>
151
- </Dialog>
152
- );
153
- }
154
-
155
- render(<Controlled />);
156
- await user.click(screen.getByRole("button", { name: "Open" }));
157
- expect(onOpenChange).toHaveBeenCalledWith(true);
158
- expect(await screen.findByRole("dialog")).toBeInTheDocument();
159
- });
160
-
161
- it("merges a consumer className on the content", async () => {
162
- const user = userEvent.setup();
163
- render(
164
- <Dialog>
165
- <DialogTrigger>Open</DialogTrigger>
166
- <DialogContent className="max-w-2xl">
167
- <DialogTitle>Wide</DialogTitle>
168
- </DialogContent>
169
- </Dialog>,
170
- );
171
-
172
- await user.click(screen.getByText("Open"));
173
- const dialog = await screen.findByRole("dialog");
174
- expect(dialog).toHaveClass("max-w-2xl");
175
- expect(dialog).not.toHaveClass("max-w-lg");
176
- });
177
-
178
- it("has no accessibility violations while open", async () => {
179
- const user = userEvent.setup();
180
- const { baseElement } = render(<Example />);
181
- await user.click(screen.getByRole("button", { name: "Open" }));
182
- await screen.findByRole("dialog");
183
-
184
- await expectNoA11yViolations(baseElement);
185
- });
186
- });
@@ -1,111 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/react-vite";
2
-
3
- import { Button } from "@/components/button";
4
-
5
- import {
6
- Drawer,
7
- DrawerBody,
8
- DrawerCancel,
9
- DrawerContent,
10
- DrawerDescription,
11
- DrawerFooter,
12
- DrawerHeader,
13
- DrawerTitle,
14
- DrawerTrigger,
15
- } from "./drawer";
16
-
17
- const meta = {
18
- title: "Overlays/Drawer",
19
- component: Drawer,
20
- parameters: { controls: { disable: true } },
21
- } satisfies Meta<typeof Drawer>;
22
-
23
- export default meta;
24
- type Story = StoryObj<typeof meta>;
25
-
26
- /** Drag the handle downward to dismiss, or use Escape or Cancel. */
27
- export const Default: Story = {
28
- render: () => (
29
- <Drawer>
30
- <DrawerTrigger asChild>
31
- <Button variant="outline">Open drawer</Button>
32
- </DrawerTrigger>
33
- <DrawerContent>
34
- <DrawerHeader>
35
- <DrawerTitle>Filters</DrawerTitle>
36
- <DrawerDescription>Narrow down the deployment list.</DrawerDescription>
37
- </DrawerHeader>
38
- <DrawerBody className="space-y-2 text-sm text-muted-foreground">
39
- <p>Status, branch, environment and author filters would live here.</p>
40
- </DrawerBody>
41
- <DrawerFooter>
42
- <Button>Apply filters</Button>
43
- <DrawerCancel>Cancel</DrawerCancel>
44
- </DrawerFooter>
45
- </DrawerContent>
46
- </Drawer>
47
- ),
48
- };
49
-
50
- export const ScrollingContent: Story = {
51
- render: () => (
52
- <Drawer>
53
- <DrawerTrigger asChild>
54
- <Button variant="outline">Pick a region</Button>
55
- </DrawerTrigger>
56
- <DrawerContent>
57
- <DrawerHeader>
58
- <DrawerTitle>Region</DrawerTitle>
59
- <DrawerDescription>Where your workloads run.</DrawerDescription>
60
- </DrawerHeader>
61
- <DrawerBody className="max-h-72">
62
- <ul className="grid gap-1 text-sm">
63
- {[
64
- "us-east-1",
65
- "us-west-2",
66
- "eu-west-1",
67
- "eu-central-1",
68
- "ap-south-1",
69
- "ap-southeast-2",
70
- "sa-east-1",
71
- "af-south-1",
72
- ].map((region) => (
73
- <li key={region}>
74
- <button
75
- type="button"
76
- className="w-full rounded-md px-3 py-2 text-left hover:bg-accent hover:text-accent-foreground"
77
- >
78
- {region}
79
- </button>
80
- </li>
81
- ))}
82
- </ul>
83
- </DrawerBody>
84
- <DrawerFooter>
85
- <DrawerCancel>Cancel</DrawerCancel>
86
- </DrawerFooter>
87
- </DrawerContent>
88
- </Drawer>
89
- ),
90
- };
91
-
92
- /** Without a handle the drawer cannot be dragged; keep an explicit way out. */
93
- export const WithoutHandle: Story = {
94
- render: () => (
95
- <Drawer>
96
- <DrawerTrigger asChild>
97
- <Button variant="outline">Open</Button>
98
- </DrawerTrigger>
99
- <DrawerContent showHandle={false}>
100
- <DrawerHeader>
101
- <DrawerTitle>Confirm</DrawerTitle>
102
- <DrawerDescription>This drawer cannot be dragged away.</DrawerDescription>
103
- </DrawerHeader>
104
- <DrawerFooter>
105
- <Button variant="destructive">Delete</Button>
106
- <DrawerCancel>Cancel</DrawerCancel>
107
- </DrawerFooter>
108
- </DrawerContent>
109
- </Drawer>
110
- ),
111
- };
@@ -1,187 +0,0 @@
1
- import { fireEvent, render, screen, waitFor } from "@testing-library/react";
2
- import userEvent from "@testing-library/user-event";
3
- import { describe, expect, it, vi } from "vitest";
4
-
5
- import { expectNoA11yViolations } from "../../../test/a11y";
6
- import { Button } from "../button";
7
- import {
8
- Drawer,
9
- DrawerBody,
10
- DrawerCancel,
11
- DrawerContent,
12
- DrawerDescription,
13
- DrawerFooter,
14
- DrawerHeader,
15
- DrawerTitle,
16
- DrawerTrigger,
17
- } from "./drawer";
18
-
19
- const DRAWER_HEIGHT = 400;
20
-
21
- function Example({ onOpenChange }: { onOpenChange?: (open: boolean) => void } = {}) {
22
- return (
23
- <Drawer onOpenChange={onOpenChange}>
24
- <DrawerTrigger asChild>
25
- <Button>Open</Button>
26
- </DrawerTrigger>
27
- <DrawerContent>
28
- <DrawerHeader>
29
- <DrawerTitle>Filters</DrawerTitle>
30
- <DrawerDescription>Narrow down the results.</DrawerDescription>
31
- </DrawerHeader>
32
- <DrawerBody>Body</DrawerBody>
33
- <DrawerFooter>
34
- <DrawerCancel>Cancel</DrawerCancel>
35
- </DrawerFooter>
36
- </DrawerContent>
37
- </Drawer>
38
- );
39
- }
40
-
41
- /** jsdom reports every box as zero-sized, so the drawer's height is stubbed to
42
- * exercise the distance-based dismiss rule. */
43
- function stubHeight(element: Element, height = DRAWER_HEIGHT) {
44
- vi.spyOn(element, "getBoundingClientRect").mockReturnValue({
45
- height,
46
- width: 320,
47
- top: 0,
48
- left: 0,
49
- right: 320,
50
- bottom: height,
51
- x: 0,
52
- y: 0,
53
- toJSON: () => ({}),
54
- });
55
- }
56
-
57
- async function open() {
58
- const user = userEvent.setup();
59
- render(<Example />);
60
- await user.click(screen.getByRole("button", { name: "Open" }));
61
- const dialog = await screen.findByRole("dialog");
62
- const handle = dialog.querySelector("[data-slot='drawer-handle']");
63
- if (!handle) throw new Error("drawer handle not rendered");
64
- stubHeight(dialog);
65
- return { user, dialog, handle };
66
- }
67
-
68
- function drag(handle: Element, distance: number) {
69
- fireEvent.pointerDown(handle, { button: 0, pointerId: 1, clientY: 0 });
70
- fireEvent.pointerMove(handle, { pointerId: 1, clientY: distance });
71
- fireEvent.pointerUp(handle, { pointerId: 1, clientY: distance });
72
- }
73
-
74
- describe("Drawer", () => {
75
- it("opens from its trigger", async () => {
76
- const user = userEvent.setup();
77
- render(<Example />);
78
-
79
- expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
80
- await user.click(screen.getByRole("button", { name: "Open" }));
81
- expect(await screen.findByRole("dialog", { name: "Filters" })).toBeInTheDocument();
82
- });
83
-
84
- it("hides the drag handle from assistive technology", async () => {
85
- const { handle } = await open();
86
- expect(handle).toHaveAttribute("aria-hidden", "true");
87
- });
88
-
89
- it("closes on Escape", async () => {
90
- const { user } = await open();
91
- await user.keyboard("{Escape}");
92
- await waitFor(() => {
93
- expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
94
- });
95
- });
96
-
97
- it("closes from DrawerCancel", async () => {
98
- const { user } = await open();
99
- await user.click(screen.getByRole("button", { name: "Cancel" }));
100
- await waitFor(() => {
101
- expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
102
- });
103
- });
104
-
105
- describe("drag to dismiss", () => {
106
- it("closes when dragged past a quarter of its height", async () => {
107
- const { handle } = await open();
108
-
109
- drag(handle, DRAWER_HEIGHT * 0.5);
110
- await waitFor(() => {
111
- expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
112
- });
113
- });
114
-
115
- it("stays open when dragged only a little", async () => {
116
- const { handle } = await open();
117
-
118
- // Below MIN_FLICK_DISTANCE, so neither the distance nor the velocity rule
119
- // fires. A tap on the handle must never dismiss the drawer.
120
- drag(handle, 8);
121
- expect(screen.getByRole("dialog")).toBeInTheDocument();
122
- });
123
-
124
- it("follows the pointer while dragging", async () => {
125
- const { dialog, handle } = await open();
126
-
127
- fireEvent.pointerDown(handle, { button: 0, pointerId: 1, clientY: 0 });
128
- fireEvent.pointerMove(handle, { pointerId: 1, clientY: 60 });
129
-
130
- expect(dialog).toHaveAttribute("data-dragging", "true");
131
- expect(dialog.style.transform).toBe("translate3d(0, 60px, 0)");
132
-
133
- fireEvent.pointerUp(handle, { pointerId: 1, clientY: 60 });
134
- });
135
-
136
- it("ignores upward drags so the panel stays anchored to the edge", async () => {
137
- const { dialog, handle } = await open();
138
-
139
- fireEvent.pointerDown(handle, { button: 0, pointerId: 1, clientY: 100 });
140
- fireEvent.pointerMove(handle, { pointerId: 1, clientY: 20 });
141
-
142
- expect(dialog.style.transform).toBe("");
143
-
144
- fireEvent.pointerUp(handle, { pointerId: 1, clientY: 20 });
145
- expect(screen.getByRole("dialog")).toBeInTheDocument();
146
- });
147
-
148
- it("ignores non-primary buttons", async () => {
149
- const { dialog, handle } = await open();
150
-
151
- fireEvent.pointerDown(handle, { button: 2, pointerId: 1, clientY: 0 });
152
- fireEvent.pointerMove(handle, { pointerId: 1, clientY: 300 });
153
-
154
- expect(dialog.style.transform).toBe("");
155
- });
156
- });
157
-
158
- it("reports open state changes", async () => {
159
- const onOpenChange = vi.fn();
160
- const user = userEvent.setup();
161
- render(<Example onOpenChange={onOpenChange} />);
162
-
163
- await user.click(screen.getByRole("button", { name: "Open" }));
164
- expect(onOpenChange).toHaveBeenCalledWith(true);
165
- });
166
-
167
- it("throws a useful error if content is rendered outside the root", () => {
168
- const consoleError = vi.spyOn(console, "error").mockImplementation(() => undefined);
169
- expect(() =>
170
- render(
171
- <DrawerContent>
172
- <DrawerTitle>Orphan</DrawerTitle>
173
- </DrawerContent>,
174
- ),
175
- ).toThrow(/must be rendered inside <Drawer>/);
176
- consoleError.mockRestore();
177
- });
178
-
179
- it("has no accessibility violations while open", async () => {
180
- const user = userEvent.setup();
181
- const { baseElement } = render(<Example />);
182
- await user.click(screen.getByRole("button", { name: "Open" }));
183
- await screen.findByRole("dialog");
184
-
185
- await expectNoA11yViolations(baseElement);
186
- });
187
- });