@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,70 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/react-vite";
2
-
3
- import { Label } from "@/components/label";
4
-
5
- import { Switch } from "./switch";
6
-
7
- const meta = {
8
- title: "Forms/Switch",
9
- component: Switch,
10
- args: { disabled: false },
11
- argTypes: { disabled: { control: "boolean" } },
12
- } satisfies Meta<typeof Switch>;
13
-
14
- export default meta;
15
- type Story = StoryObj<typeof meta>;
16
-
17
- export const Default: Story = {
18
- render: (args) => (
19
- <div className="flex items-center gap-2">
20
- <Switch id="airplane" {...args} />
21
- <Label htmlFor="airplane">Airplane mode</Label>
22
- </div>
23
- ),
24
- };
25
-
26
- export const States: Story = {
27
- parameters: { controls: { disable: true } },
28
- render: () => (
29
- <div className="grid gap-3">
30
- {[
31
- { id: "off", label: "Off", checked: false },
32
- { id: "on", label: "On", checked: true },
33
- ].map((state) => (
34
- <div key={state.id} className="flex items-center gap-2">
35
- <Switch id={state.id} checked={state.checked} />
36
- <Label htmlFor={state.id}>{state.label}</Label>
37
- </div>
38
- ))}
39
- <div className="flex items-center gap-2">
40
- <Switch id="disabled-off" disabled />
41
- <Label htmlFor="disabled-off">Disabled</Label>
42
- </div>
43
- <div className="flex items-center gap-2">
44
- <Switch id="disabled-on" disabled checked />
45
- <Label htmlFor="disabled-on">Disabled and on</Label>
46
- </div>
47
- </div>
48
- ),
49
- };
50
-
51
- /** A switch applies immediately — if it needs a Save button, use a Checkbox. */
52
- export const SettingsRow: Story = {
53
- parameters: { controls: { disable: true } },
54
- render: () => (
55
- <div className="w-80 divide-y divide-border rounded-lg border border-border">
56
- {[
57
- { id: "row-1", label: "Automatic deploys", hint: "Deploy every push to main." },
58
- { id: "row-2", label: "Preview comments", hint: "Comment on pull requests." },
59
- ].map((row) => (
60
- <div key={row.id} className="flex items-start justify-between gap-4 p-4">
61
- <div className="grid gap-1">
62
- <Label htmlFor={row.id}>{row.label}</Label>
63
- <p className="text-xs text-muted-foreground">{row.hint}</p>
64
- </div>
65
- <Switch id={row.id} defaultChecked={row.id === "row-1"} />
66
- </div>
67
- ))}
68
- </div>
69
- ),
70
- };
@@ -1,101 +0,0 @@
1
- import { render, screen } 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 { Label } from "../label";
8
- import { Switch } from "./switch";
9
-
10
- describe("Switch", () => {
11
- it("is exposed as a switch, not a checkbox", () => {
12
- render(<Switch aria-label="Airplane mode" />);
13
- expect(screen.getByRole("switch", { name: "Airplane mode" })).toBeInTheDocument();
14
- });
15
-
16
- it("starts off", () => {
17
- render(<Switch aria-label="Airplane mode" />);
18
- expect(screen.getByRole("switch")).not.toBeChecked();
19
- });
20
-
21
- it("toggles on click", async () => {
22
- const user = userEvent.setup();
23
- render(<Switch aria-label="Airplane mode" />);
24
-
25
- const control = screen.getByRole("switch");
26
- await user.click(control);
27
- expect(control).toBeChecked();
28
- });
29
-
30
- it("toggles from the keyboard", async () => {
31
- const user = userEvent.setup();
32
- render(<Switch aria-label="Airplane mode" />);
33
-
34
- await user.tab();
35
- expect(screen.getByRole("switch")).toHaveFocus();
36
- await user.keyboard(" ");
37
- expect(screen.getByRole("switch")).toBeChecked();
38
- });
39
-
40
- it("reports changes", async () => {
41
- const onCheckedChange = vi.fn();
42
- const user = userEvent.setup();
43
- render(<Switch aria-label="Airplane mode" onCheckedChange={onCheckedChange} />);
44
-
45
- await user.click(screen.getByRole("switch"));
46
- expect(onCheckedChange).toHaveBeenCalledWith(true);
47
- });
48
-
49
- it("works controlled", async () => {
50
- const user = userEvent.setup();
51
-
52
- function Controlled() {
53
- const [on, setOn] = useState(false);
54
- return <Switch aria-label="Airplane mode" checked={on} onCheckedChange={setOn} />;
55
- }
56
-
57
- render(<Controlled />);
58
- await user.click(screen.getByRole("switch"));
59
- expect(screen.getByRole("switch")).toBeChecked();
60
- });
61
-
62
- it("does not toggle while disabled", async () => {
63
- const onCheckedChange = vi.fn();
64
- const user = userEvent.setup();
65
- render(<Switch aria-label="Airplane mode" disabled onCheckedChange={onCheckedChange} />);
66
-
67
- await user.click(screen.getByRole("switch"));
68
- expect(onCheckedChange).not.toHaveBeenCalled();
69
- });
70
-
71
- it("moves its thumb to reflect state, not colour alone", async () => {
72
- const user = userEvent.setup();
73
- const { container } = render(<Switch aria-label="Airplane mode" />);
74
-
75
- const thumb = container.querySelector("[data-slot='switch-thumb']");
76
- expect(thumb).toHaveAttribute("data-state", "unchecked");
77
-
78
- await user.click(screen.getByRole("switch"));
79
- expect(thumb).toHaveAttribute("data-state", "checked");
80
- });
81
-
82
- it("takes its name from an associated label", () => {
83
- render(
84
- <>
85
- <Switch id="wifi" />
86
- <Label htmlFor="wifi">Wi-Fi</Label>
87
- </>,
88
- );
89
- expect(screen.getByRole("switch", { name: "Wi-Fi" })).toBeInTheDocument();
90
- });
91
-
92
- it("has no accessibility violations", async () => {
93
- const { container } = render(
94
- <div className="flex items-center gap-2">
95
- <Switch id="a11y-wifi" />
96
- <Label htmlFor="a11y-wifi">Wi-Fi</Label>
97
- </div>,
98
- );
99
- await expectNoA11yViolations(container);
100
- });
101
- });
@@ -1,131 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/react-vite";
2
-
3
- import { Badge } from "@/components/badge";
4
-
5
- import {
6
- Table,
7
- TableBody,
8
- TableCaption,
9
- TableCell,
10
- TableFooter,
11
- TableHead,
12
- TableHeader,
13
- TableRow,
14
- } from "./table";
15
-
16
- const INVOICES = [
17
- { id: "INV-001", status: "Paid", method: "Credit card", amount: "$250.00" },
18
- { id: "INV-002", status: "Pending", method: "PayPal", amount: "$150.00" },
19
- { id: "INV-003", status: "Failed", method: "Bank transfer", amount: "$350.00" },
20
- { id: "INV-004", status: "Paid", method: "Credit card", amount: "$450.00" },
21
- ];
22
-
23
- const meta = {
24
- title: "Data/Table",
25
- component: Table,
26
- parameters: { controls: { disable: true } },
27
- } satisfies Meta<typeof Table>;
28
-
29
- export default meta;
30
- type Story = StoryObj<typeof meta>;
31
-
32
- export const Default: Story = {
33
- render: () => (
34
- <Table className="w-[36rem]">
35
- <TableCaption>Invoices from the last month.</TableCaption>
36
- <TableHeader>
37
- <TableRow>
38
- <TableHead>Invoice</TableHead>
39
- <TableHead>Status</TableHead>
40
- <TableHead>Method</TableHead>
41
- <TableHead className="text-right">Amount</TableHead>
42
- </TableRow>
43
- </TableHeader>
44
- <TableBody>
45
- {INVOICES.map((invoice) => (
46
- <TableRow key={invoice.id}>
47
- {/* A row header, so a screen reader can say which row a cell is in. */}
48
- <TableHead scope="row" className="font-medium text-foreground">
49
- {invoice.id}
50
- </TableHead>
51
- <TableCell>
52
- <Badge
53
- variant={
54
- invoice.status === "Paid"
55
- ? "success"
56
- : invoice.status === "Failed"
57
- ? "destructive"
58
- : "secondary"
59
- }
60
- size="sm"
61
- >
62
- {invoice.status}
63
- </Badge>
64
- </TableCell>
65
- <TableCell>{invoice.method}</TableCell>
66
- <TableCell className="text-right tabular-nums">{invoice.amount}</TableCell>
67
- </TableRow>
68
- ))}
69
- </TableBody>
70
- <TableFooter>
71
- <TableRow>
72
- <TableCell colSpan={3}>Total</TableCell>
73
- <TableCell className="text-right tabular-nums">$1,200.00</TableCell>
74
- </TableRow>
75
- </TableFooter>
76
- </Table>
77
- ),
78
- };
79
-
80
- export const Minimal: Story = {
81
- render: () => (
82
- <Table className="w-96" aria-label="Team members">
83
- <TableHeader>
84
- <TableRow>
85
- <TableHead>Name</TableHead>
86
- <TableHead>Role</TableHead>
87
- </TableRow>
88
- </TableHeader>
89
- <TableBody>
90
- <TableRow>
91
- <TableCell>Ada Lovelace</TableCell>
92
- <TableCell>Engineering</TableCell>
93
- </TableRow>
94
- <TableRow>
95
- <TableCell>Grace Hopper</TableCell>
96
- <TableCell>Engineering</TableCell>
97
- </TableRow>
98
- </TableBody>
99
- </Table>
100
- ),
101
- };
102
-
103
- /** Overflowing horizontally: the wrapper is focusable so it can be scrolled by keyboard. */
104
- export const Overflowing: Story = {
105
- render: () => (
106
- <div className="w-80">
107
- <Table aria-label="Wide data">
108
- <TableHeader>
109
- <TableRow>
110
- {Array.from({ length: 8 }, (_, index) => (
111
- <TableHead key={index} className="whitespace-nowrap">
112
- Column {index + 1}
113
- </TableHead>
114
- ))}
115
- </TableRow>
116
- </TableHeader>
117
- <TableBody>
118
- {[0, 1, 2].map((row) => (
119
- <TableRow key={row}>
120
- {Array.from({ length: 8 }, (_, index) => (
121
- <TableCell key={index} className="whitespace-nowrap">
122
- Value {row + 1}.{index + 1}
123
- </TableCell>
124
- ))}
125
- </TableRow>
126
- ))}
127
- </TableBody>
128
- </Table>
129
- </div>
130
- ),
131
- };
@@ -1,153 +0,0 @@
1
- import { render, screen, within } from "@testing-library/react";
2
- import userEvent from "@testing-library/user-event";
3
- import { describe, expect, it } from "vitest";
4
-
5
- import { expectNoA11yViolations } from "../../../test/a11y";
6
- import {
7
- Table,
8
- TableBody,
9
- TableCaption,
10
- TableCell,
11
- TableFooter,
12
- TableHead,
13
- TableHeader,
14
- TableRow,
15
- } from "./table";
16
-
17
- function Example() {
18
- return (
19
- <Table>
20
- <TableCaption>Recent invoices</TableCaption>
21
- <TableHeader>
22
- <TableRow>
23
- <TableHead>Invoice</TableHead>
24
- <TableHead>Status</TableHead>
25
- <TableHead>Amount</TableHead>
26
- </TableRow>
27
- </TableHeader>
28
- <TableBody>
29
- <TableRow>
30
- <TableHead scope="row">INV-001</TableHead>
31
- <TableCell>Paid</TableCell>
32
- <TableCell>$250.00</TableCell>
33
- </TableRow>
34
- <TableRow data-state="selected">
35
- <TableHead scope="row">INV-002</TableHead>
36
- <TableCell>Pending</TableCell>
37
- <TableCell>$150.00</TableCell>
38
- </TableRow>
39
- </TableBody>
40
- <TableFooter>
41
- <TableRow>
42
- <TableCell colSpan={2}>Total</TableCell>
43
- <TableCell>$400.00</TableCell>
44
- </TableRow>
45
- </TableFooter>
46
- </Table>
47
- );
48
- }
49
-
50
- describe("Table", () => {
51
- it("renders a real table, not a grid of divs", () => {
52
- render(<Example />);
53
- expect(screen.getByRole("table")).toBeInTheDocument();
54
- });
55
-
56
- it("is named by its caption", () => {
57
- render(<Example />);
58
- expect(screen.getByRole("table", { name: "Recent invoices" })).toBeInTheDocument();
59
- });
60
-
61
- it("exposes column headers", () => {
62
- render(<Example />);
63
- const headers = screen.getAllByRole("columnheader");
64
- expect(headers.map((header) => header.textContent)).toEqual([
65
- "Invoice",
66
- "Status",
67
- "Amount",
68
- ]);
69
- });
70
-
71
- it("exposes row headers when scope is set", () => {
72
- render(<Example />);
73
- expect(screen.getAllByRole("rowheader")).toHaveLength(2);
74
- });
75
-
76
- it("groups rows into header, body and footer", () => {
77
- render(<Example />);
78
- expect(screen.getAllByRole("rowgroup")).toHaveLength(3);
79
- });
80
-
81
- it("marks a selected row for styling", () => {
82
- render(<Example />);
83
- const rows = screen.getAllByRole("row");
84
- const selected = rows.find((row) => row.getAttribute("data-state") === "selected");
85
- expect(selected).toBeDefined();
86
- expect(within(selected as HTMLElement).getByText("Pending")).toBeInTheDocument();
87
- });
88
-
89
- describe("the scrolling wrapper", () => {
90
- it("is focusable, so an overflowing table can be scrolled by keyboard", async () => {
91
- const user = userEvent.setup();
92
- const { container } = render(<Example />);
93
-
94
- const wrapper = container.querySelector("[data-slot='table-container']");
95
- expect(wrapper).toHaveAttribute("tabindex", "0");
96
-
97
- await user.tab();
98
- expect(wrapper).toHaveFocus();
99
- });
100
-
101
- it("is a named region, so it is not an unlabelled focus stop", () => {
102
- render(<Example />);
103
- expect(screen.getByRole("region", { name: "Table" })).toBeInTheDocument();
104
- });
105
-
106
- it("takes its name from the table's aria-label", () => {
107
- render(
108
- <Table aria-label="Invoices">
109
- <TableBody>
110
- <TableRow>
111
- <TableCell>Cell</TableCell>
112
- </TableRow>
113
- </TableBody>
114
- </Table>,
115
- );
116
- expect(screen.getByRole("region", { name: "Invoices" })).toBeInTheDocument();
117
- });
118
-
119
- it("accepts props of its own", () => {
120
- const { container } = render(
121
- <Table containerProps={{ className: "max-h-64", "data-testid": "wrapper" }}>
122
- <TableBody>
123
- <TableRow>
124
- <TableCell>Cell</TableCell>
125
- </TableRow>
126
- </TableBody>
127
- </Table>,
128
- );
129
- expect(container.querySelector("[data-testid='wrapper']")).toHaveClass("max-h-64");
130
- });
131
- });
132
-
133
- it("lets a consumer className override a conflicting utility", () => {
134
- render(
135
- <Table className="text-base" aria-label="T">
136
- <TableBody>
137
- <TableRow>
138
- <TableCell>Cell</TableCell>
139
- </TableRow>
140
- </TableBody>
141
- </Table>,
142
- );
143
-
144
- const table = screen.getByRole("table");
145
- expect(table).toHaveClass("text-base");
146
- expect(table).not.toHaveClass("text-sm");
147
- });
148
-
149
- it("has no accessibility violations", async () => {
150
- const { container } = render(<Example />);
151
- await expectNoA11yViolations(container);
152
- });
153
- });
@@ -1,139 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/react-vite";
2
-
3
- import { Button } from "@/components/button";
4
- import {
5
- Card,
6
- CardContent,
7
- CardDescription,
8
- CardFooter,
9
- CardHeader,
10
- CardTitle,
11
- } from "@/components/card";
12
- import { Input } from "@/components/input";
13
- import { Label } from "@/components/label";
14
-
15
- import { Tabs, TabsContent, TabsList, TabsTrigger } from "./tabs";
16
-
17
- /**
18
- * Annotated rather than inferred with `satisfies`. These components are direct
19
- * re-exports of Radix primitives, and inferring the meta type makes the emitted
20
- * declaration reference Radix-internal prop types that are not nameable from
21
- * this path (TS2883).
22
- */
23
- const meta: Meta<typeof Tabs> = {
24
- title: "Navigation/Tabs",
25
- component: Tabs,
26
- parameters: { controls: { disable: true } },
27
- };
28
-
29
- export default meta;
30
- type Story = StoryObj<typeof Tabs>;
31
-
32
- export const Default: Story = {
33
- render: () => (
34
- <Tabs defaultValue="account" className="w-96">
35
- <TabsList>
36
- <TabsTrigger value="account">Account</TabsTrigger>
37
- <TabsTrigger value="password">Password</TabsTrigger>
38
- </TabsList>
39
- <TabsContent value="account">
40
- <Card>
41
- <CardHeader>
42
- <CardTitle>Account</CardTitle>
43
- <CardDescription>Update your account details.</CardDescription>
44
- </CardHeader>
45
- <CardContent className="grid gap-2">
46
- <Label htmlFor="tabs-name">Name</Label>
47
- <Input id="tabs-name" defaultValue="Ada Lovelace" />
48
- </CardContent>
49
- <CardFooter className="justify-end">
50
- <Button>Save</Button>
51
- </CardFooter>
52
- </Card>
53
- </TabsContent>
54
- <TabsContent value="password">
55
- <Card>
56
- <CardHeader>
57
- <CardTitle>Password</CardTitle>
58
- <CardDescription>Change your password here.</CardDescription>
59
- </CardHeader>
60
- <CardContent className="grid gap-2">
61
- <Label htmlFor="tabs-password">New password</Label>
62
- <Input id="tabs-password" type="password" />
63
- </CardContent>
64
- <CardFooter className="justify-end">
65
- <Button>Update password</Button>
66
- </CardFooter>
67
- </Card>
68
- </TabsContent>
69
- </Tabs>
70
- ),
71
- };
72
-
73
- export const Underline: Story = {
74
- render: () => (
75
- <Tabs defaultValue="overview" className="w-96">
76
- <TabsList variant="underline">
77
- <TabsTrigger value="overview" variant="underline">
78
- Overview
79
- </TabsTrigger>
80
- <TabsTrigger value="deployments" variant="underline">
81
- Deployments
82
- </TabsTrigger>
83
- <TabsTrigger value="settings" variant="underline">
84
- Settings
85
- </TabsTrigger>
86
- </TabsList>
87
- <TabsContent value="overview" className="text-sm text-muted-foreground">
88
- Traffic, errors and latency for the last 24 hours.
89
- </TabsContent>
90
- <TabsContent value="deployments" className="text-sm text-muted-foreground">
91
- 42 deployments this week.
92
- </TabsContent>
93
- <TabsContent value="settings" className="text-sm text-muted-foreground">
94
- Environment variables and build settings.
95
- </TabsContent>
96
- </Tabs>
97
- ),
98
- };
99
-
100
- export const WithDisabledTab: Story = {
101
- render: () => (
102
- <Tabs defaultValue="general" className="w-96">
103
- <TabsList>
104
- <TabsTrigger value="general">General</TabsTrigger>
105
- <TabsTrigger value="team">Team</TabsTrigger>
106
- <TabsTrigger value="billing" disabled>
107
- Billing
108
- </TabsTrigger>
109
- </TabsList>
110
- <TabsContent value="general" className="text-sm text-muted-foreground">
111
- General settings.
112
- </TabsContent>
113
- <TabsContent value="team" className="text-sm text-muted-foreground">
114
- Invite and manage teammates.
115
- </TabsContent>
116
- <TabsContent value="billing" className="text-sm text-muted-foreground">
117
- Upgrade to manage billing.
118
- </TabsContent>
119
- </Tabs>
120
- ),
121
- };
122
-
123
- /** Manual activation stops arrow keys from loading each panel in passing. */
124
- export const ManualActivation: Story = {
125
- render: () => (
126
- <Tabs defaultValue="one" activationMode="manual" className="w-96">
127
- <TabsList>
128
- <TabsTrigger value="one">Expensive one</TabsTrigger>
129
- <TabsTrigger value="two">Expensive two</TabsTrigger>
130
- </TabsList>
131
- <TabsContent value="one" className="text-sm text-muted-foreground">
132
- Arrow to the next tab, then press Enter or Space to load it.
133
- </TabsContent>
134
- <TabsContent value="two" className="text-sm text-muted-foreground">
135
- Loaded on purpose, not in passing.
136
- </TabsContent>
137
- </Tabs>
138
- ),
139
- };