@dowel-ui/react 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/ai-agent-status/ai-agent-status.d.ts +1 -1
- package/dist/components/ai-tool/ai-tool.d.ts +1 -1
- package/dist/components/alert/alert.d.ts +1 -1
- package/dist/components/avatar/avatar.d.ts +1 -1
- package/dist/components/badge/badge.d.ts +1 -1
- package/dist/components/button/button.d.ts +1 -1
- package/dist/components/progress/progress.d.ts +1 -1
- package/dist/components/spinner/spinner.d.ts +1 -1
- package/dist/components/toast/toast.d.ts +1 -1
- package/package.json +7 -4
- package/src/blocks/admin-users/admin-users.stories.tsx +0 -83
- package/src/blocks/admin-users/admin-users.test.tsx +0 -159
- package/src/blocks/ai-chat/ai-chat.stories.tsx +0 -149
- package/src/blocks/ai-chat/ai-chat.test.tsx +0 -166
- package/src/blocks/dashboard/dashboard.stories.tsx +0 -106
- package/src/blocks/dashboard/dashboard.test.tsx +0 -94
- package/src/blocks/forgot-password/forgot-password.stories.tsx +0 -41
- package/src/blocks/forgot-password/forgot-password.test.tsx +0 -62
- package/src/blocks/login/login.stories.tsx +0 -46
- package/src/blocks/login/login.test.tsx +0 -100
- package/src/blocks/pricing/pricing.stories.tsx +0 -62
- package/src/blocks/pricing/pricing.test.tsx +0 -101
- package/src/blocks/settings/settings.stories.tsx +0 -94
- package/src/blocks/settings/settings.test.tsx +0 -167
- package/src/blocks/signup/signup.stories.tsx +0 -27
- package/src/blocks/signup/signup.test.tsx +0 -97
- package/src/components/accordion/accordion.stories.tsx +0 -90
- package/src/components/accordion/accordion.test.tsx +0 -156
- package/src/components/activity-feed/activity-feed.stories.tsx +0 -163
- package/src/components/activity-feed/activity-feed.test.tsx +0 -125
- package/src/components/ai-agent-status/ai-agent-status.stories.tsx +0 -86
- package/src/components/ai-agent-status/ai-agent-status.test.tsx +0 -66
- package/src/components/ai-conversation/ai-conversation.stories.tsx +0 -126
- package/src/components/ai-conversation/ai-conversation.test.tsx +0 -144
- package/src/components/ai-message/ai-message.stories.tsx +0 -106
- package/src/components/ai-message/ai-message.test.tsx +0 -160
- package/src/components/ai-model-selector/ai-model-selector.stories.tsx +0 -66
- package/src/components/ai-model-selector/ai-model-selector.test.tsx +0 -120
- package/src/components/ai-prompt-input/ai-prompt-input.stories.tsx +0 -145
- package/src/components/ai-prompt-input/ai-prompt-input.test.tsx +0 -273
- package/src/components/ai-reasoning/ai-reasoning.stories.tsx +0 -60
- package/src/components/ai-reasoning/ai-reasoning.test.tsx +0 -95
- package/src/components/ai-response/ai-response.stories.tsx +0 -75
- package/src/components/ai-response/ai-response.test.tsx +0 -96
- package/src/components/ai-sources/ai-sources.stories.tsx +0 -93
- package/src/components/ai-sources/ai-sources.test.tsx +0 -102
- package/src/components/ai-token-usage/ai-token-usage.stories.tsx +0 -59
- package/src/components/ai-token-usage/ai-token-usage.test.tsx +0 -83
- package/src/components/ai-tool/ai-tool.stories.tsx +0 -96
- package/src/components/ai-tool/ai-tool.test.tsx +0 -137
- package/src/components/alert/alert.stories.tsx +0 -101
- package/src/components/alert/alert.test.tsx +0 -94
- package/src/components/avatar/avatar.stories.tsx +0 -58
- package/src/components/avatar/avatar.test.tsx +0 -64
- package/src/components/badge/badge.stories.tsx +0 -68
- package/src/components/badge/badge.test.tsx +0 -61
- package/src/components/button/button.stories.tsx +0 -128
- package/src/components/button/button.test.tsx +0 -224
- package/src/components/calendar/calendar.stories.tsx +0 -105
- package/src/components/calendar/calendar.test.tsx +0 -255
- package/src/components/card/card.stories.tsx +0 -61
- package/src/components/card/card.test.tsx +0 -66
- package/src/components/checkbox/checkbox.stories.tsx +0 -124
- package/src/components/checkbox/checkbox.test.tsx +0 -121
- package/src/components/code-block/code-block.stories.tsx +0 -62
- package/src/components/code-block/code-block.test.tsx +0 -139
- package/src/components/combobox/combobox.stories.tsx +0 -171
- package/src/components/combobox/combobox.test.tsx +0 -429
- package/src/components/command/command.stories.tsx +0 -152
- package/src/components/command/command.test.tsx +0 -380
- package/src/components/data-table/data-table.stories.tsx +0 -151
- package/src/components/data-table/data-table.test.tsx +0 -288
- package/src/components/date-picker/date-picker.stories.tsx +0 -103
- package/src/components/date-picker/date-picker.test.tsx +0 -205
- package/src/components/dialog/dialog.stories.tsx +0 -140
- package/src/components/dialog/dialog.test.tsx +0 -186
- package/src/components/drawer/drawer.stories.tsx +0 -111
- package/src/components/drawer/drawer.test.tsx +0 -187
- package/src/components/dropdown-menu/dropdown-menu.stories.tsx +0 -128
- package/src/components/dropdown-menu/dropdown-menu.test.tsx +0 -321
- package/src/components/empty-state/empty-state.stories.tsx +0 -70
- package/src/components/empty-state/empty-state.test.tsx +0 -97
- package/src/components/form/form.stories.tsx +0 -144
- package/src/components/form/form.test.tsx +0 -203
- package/src/components/input/input.stories.tsx +0 -84
- package/src/components/input/input.test.tsx +0 -108
- package/src/components/label/label.stories.tsx +0 -51
- package/src/components/label/label.test.tsx +0 -55
- package/src/components/pagination/pagination.stories.tsx +0 -129
- package/src/components/pagination/pagination.test.tsx +0 -119
- package/src/components/popover/popover.stories.tsx +0 -84
- package/src/components/popover/popover.test.tsx +0 -163
- package/src/components/progress/progress.stories.tsx +0 -90
- package/src/components/progress/progress.test.tsx +0 -78
- package/src/components/radio-group/radio-group.stories.tsx +0 -102
- package/src/components/radio-group/radio-group.test.tsx +0 -152
- package/src/components/select/select.stories.tsx +0 -137
- package/src/components/select/select.test.tsx +0 -209
- package/src/components/separator/separator.stories.tsx +0 -42
- package/src/components/separator/separator.test.tsx +0 -46
- package/src/components/sheet/sheet.stories.tsx +0 -107
- package/src/components/sheet/sheet.test.tsx +0 -152
- package/src/components/skeleton/skeleton.stories.tsx +0 -46
- package/src/components/skeleton/skeleton.test.tsx +0 -57
- package/src/components/slider/slider.stories.tsx +0 -87
- package/src/components/slider/slider.test.tsx +0 -148
- package/src/components/spinner/spinner.stories.tsx +0 -47
- package/src/components/spinner/spinner.test.tsx +0 -41
- package/src/components/switch/switch.stories.tsx +0 -70
- package/src/components/switch/switch.test.tsx +0 -101
- package/src/components/table/table.stories.tsx +0 -131
- package/src/components/table/table.test.tsx +0 -153
- package/src/components/tabs/tabs.stories.tsx +0 -139
- package/src/components/tabs/tabs.test.tsx +0 -148
- package/src/components/toast/toast.stories.tsx +0 -167
- package/src/components/toast/toast.test.tsx +0 -306
- package/src/components/tooltip/tooltip.stories.tsx +0 -89
- package/src/components/tooltip/tooltip.test.tsx +0 -120
- package/src/lib/utils.test.ts +0 -32
- package/src/registry/meta.test.ts +0 -234
|
@@ -1,380 +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 {
|
|
8
|
-
Command,
|
|
9
|
-
CommandDialog,
|
|
10
|
-
CommandEmpty,
|
|
11
|
-
CommandGroup,
|
|
12
|
-
CommandInput,
|
|
13
|
-
CommandItem,
|
|
14
|
-
CommandList,
|
|
15
|
-
CommandSeparator,
|
|
16
|
-
CommandShortcut,
|
|
17
|
-
defaultCommandFilter,
|
|
18
|
-
} from "./command";
|
|
19
|
-
|
|
20
|
-
function Example({ onSelect }: { onSelect?: (value: string) => void } = {}) {
|
|
21
|
-
return (
|
|
22
|
-
<Command>
|
|
23
|
-
<CommandInput placeholder="Type a command…" aria-label="Search commands" />
|
|
24
|
-
<CommandList>
|
|
25
|
-
<CommandEmpty>No results found.</CommandEmpty>
|
|
26
|
-
<CommandGroup heading="Suggestions">
|
|
27
|
-
<CommandItem value="Calendar" keywords={["date", "schedule"]} onSelect={onSelect} />
|
|
28
|
-
<CommandItem value="Search Emoji" onSelect={onSelect} />
|
|
29
|
-
<CommandItem value="Calculator" disabled onSelect={onSelect} />
|
|
30
|
-
</CommandGroup>
|
|
31
|
-
<CommandSeparator />
|
|
32
|
-
<CommandGroup heading="Settings">
|
|
33
|
-
<CommandItem value="Profile" onSelect={onSelect}>
|
|
34
|
-
Profile
|
|
35
|
-
<CommandShortcut>⌘P</CommandShortcut>
|
|
36
|
-
</CommandItem>
|
|
37
|
-
<CommandItem value="Billing" onSelect={onSelect} />
|
|
38
|
-
</CommandGroup>
|
|
39
|
-
</CommandList>
|
|
40
|
-
</Command>
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
describe("defaultCommandFilter", () => {
|
|
45
|
-
it("matches case-insensitively across every entry", () => {
|
|
46
|
-
expect(defaultCommandFilter("cal", ["Calendar", "date"])).toBe(true);
|
|
47
|
-
expect(defaultCommandFilter("DATE", ["Calendar", "date"])).toBe(true);
|
|
48
|
-
expect(defaultCommandFilter("zzz", ["Calendar", "date"])).toBe(false);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it("matches everything on an empty search", () => {
|
|
52
|
-
expect(defaultCommandFilter(" ", ["anything"])).toBe(true);
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
describe("Command", () => {
|
|
57
|
-
it("renders a combobox input and a listbox", () => {
|
|
58
|
-
render(<Example />);
|
|
59
|
-
|
|
60
|
-
const input = screen.getByRole("combobox", { name: "Search commands" });
|
|
61
|
-
expect(input).toHaveAttribute("aria-controls", screen.getByRole("listbox").id);
|
|
62
|
-
expect(input).toHaveAttribute("aria-autocomplete", "list");
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it("takes focus on mount, so typing works immediately", async () => {
|
|
66
|
-
render(<Example />);
|
|
67
|
-
await waitFor(() => {
|
|
68
|
-
expect(screen.getByRole("combobox")).toHaveFocus();
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it("renders every item to begin with", () => {
|
|
73
|
-
render(<Example />);
|
|
74
|
-
expect(screen.getAllByRole("option")).toHaveLength(5);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it("labels each group by its heading", () => {
|
|
78
|
-
render(<Example />);
|
|
79
|
-
expect(screen.getByRole("group", { name: "Suggestions" })).toBeInTheDocument();
|
|
80
|
-
expect(screen.getByRole("group", { name: "Settings" })).toBeInTheDocument();
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
describe("filtering", () => {
|
|
84
|
-
it("narrows as you type", async () => {
|
|
85
|
-
const user = userEvent.setup();
|
|
86
|
-
render(<Example />);
|
|
87
|
-
|
|
88
|
-
await user.keyboard("prof");
|
|
89
|
-
expect(screen.getAllByRole("option")).toHaveLength(1);
|
|
90
|
-
expect(screen.getByRole("option", { name: /Profile/ })).toBeInTheDocument();
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it("matches hidden keywords", async () => {
|
|
94
|
-
const user = userEvent.setup();
|
|
95
|
-
render(<Example />);
|
|
96
|
-
|
|
97
|
-
await user.keyboard("schedule");
|
|
98
|
-
expect(screen.getAllByRole("option")).toHaveLength(1);
|
|
99
|
-
expect(screen.getByRole("option", { name: "Calendar" })).toBeInTheDocument();
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it("hides a group whose items have all been filtered out", async () => {
|
|
103
|
-
const user = userEvent.setup();
|
|
104
|
-
render(<Example />);
|
|
105
|
-
|
|
106
|
-
await user.keyboard("prof");
|
|
107
|
-
expect(screen.queryByRole("group", { name: "Suggestions" })).not.toBeInTheDocument();
|
|
108
|
-
expect(screen.getByRole("group", { name: "Settings" })).toBeInTheDocument();
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it("brings a hidden group back when the search is cleared", async () => {
|
|
112
|
-
const user = userEvent.setup();
|
|
113
|
-
render(<Example />);
|
|
114
|
-
|
|
115
|
-
await user.keyboard("prof");
|
|
116
|
-
expect(screen.queryByRole("group", { name: "Suggestions" })).not.toBeInTheDocument();
|
|
117
|
-
|
|
118
|
-
await user.clear(screen.getByRole("combobox"));
|
|
119
|
-
await waitFor(() => {
|
|
120
|
-
expect(screen.getByRole("group", { name: "Suggestions" })).toBeInTheDocument();
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it("shows the empty state only when nothing matches", async () => {
|
|
125
|
-
const user = userEvent.setup();
|
|
126
|
-
render(<Example />);
|
|
127
|
-
expect(screen.queryByText("No results found.")).not.toBeInTheDocument();
|
|
128
|
-
|
|
129
|
-
await user.keyboard("zzzz");
|
|
130
|
-
expect(screen.getByText("No results found.")).toBeInTheDocument();
|
|
131
|
-
expect(screen.queryAllByRole("option")).toHaveLength(0);
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
it("accepts a custom filter", async () => {
|
|
135
|
-
const user = userEvent.setup();
|
|
136
|
-
render(
|
|
137
|
-
<Command filter={(search, haystack) => haystack.some((e) => e.startsWith(search))}>
|
|
138
|
-
<CommandInput aria-label="Search" />
|
|
139
|
-
<CommandList>
|
|
140
|
-
<CommandItem value="Alpha" />
|
|
141
|
-
<CommandItem value="Beta" />
|
|
142
|
-
</CommandList>
|
|
143
|
-
</Command>,
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
await user.keyboard("Al");
|
|
147
|
-
expect(screen.getAllByRole("option")).toHaveLength(1);
|
|
148
|
-
});
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
describe("keyboard", () => {
|
|
152
|
-
it("moves through items with the arrows, keeping focus in the input", async () => {
|
|
153
|
-
const user = userEvent.setup();
|
|
154
|
-
render(<Example />);
|
|
155
|
-
|
|
156
|
-
const input = screen.getByRole("combobox");
|
|
157
|
-
await user.keyboard("{ArrowDown}");
|
|
158
|
-
|
|
159
|
-
expect(input).toHaveAttribute(
|
|
160
|
-
"aria-activedescendant",
|
|
161
|
-
screen.getByRole("option", { name: "Calendar" }).id,
|
|
162
|
-
);
|
|
163
|
-
expect(input).toHaveFocus();
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it("skips disabled items", async () => {
|
|
167
|
-
const user = userEvent.setup();
|
|
168
|
-
render(<Example />);
|
|
169
|
-
|
|
170
|
-
const input = screen.getByRole("combobox");
|
|
171
|
-
await user.keyboard("{ArrowDown}{ArrowDown}{ArrowDown}");
|
|
172
|
-
|
|
173
|
-
expect(input).toHaveAttribute(
|
|
174
|
-
"aria-activedescendant",
|
|
175
|
-
screen.getByRole("option", { name: /Profile/ }).id,
|
|
176
|
-
);
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
it("wraps at the ends", async () => {
|
|
180
|
-
const user = userEvent.setup();
|
|
181
|
-
render(<Example />);
|
|
182
|
-
|
|
183
|
-
const input = screen.getByRole("combobox");
|
|
184
|
-
await user.keyboard("{ArrowUp}");
|
|
185
|
-
expect(input).toHaveAttribute(
|
|
186
|
-
"aria-activedescendant",
|
|
187
|
-
screen.getByRole("option", { name: "Billing" }).id,
|
|
188
|
-
);
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
it("jumps to the ends with Home and End", async () => {
|
|
192
|
-
const user = userEvent.setup();
|
|
193
|
-
render(<Example />);
|
|
194
|
-
|
|
195
|
-
const input = screen.getByRole("combobox");
|
|
196
|
-
await user.keyboard("{End}");
|
|
197
|
-
expect(input).toHaveAttribute(
|
|
198
|
-
"aria-activedescendant",
|
|
199
|
-
screen.getByRole("option", { name: "Billing" }).id,
|
|
200
|
-
);
|
|
201
|
-
|
|
202
|
-
await user.keyboard("{Home}");
|
|
203
|
-
expect(input).toHaveAttribute(
|
|
204
|
-
"aria-activedescendant",
|
|
205
|
-
screen.getByRole("option", { name: "Calendar" }).id,
|
|
206
|
-
);
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
it("runs the active item on Enter", async () => {
|
|
210
|
-
const onSelect = vi.fn();
|
|
211
|
-
const user = userEvent.setup();
|
|
212
|
-
render(<Example onSelect={onSelect} />);
|
|
213
|
-
|
|
214
|
-
await user.keyboard("{ArrowDown}{Enter}");
|
|
215
|
-
expect(onSelect).toHaveBeenCalledWith("Calendar");
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
it("does nothing on Enter with no active item", async () => {
|
|
219
|
-
const onSelect = vi.fn();
|
|
220
|
-
const user = userEvent.setup();
|
|
221
|
-
render(<Example onSelect={onSelect} />);
|
|
222
|
-
|
|
223
|
-
await user.keyboard("{Enter}");
|
|
224
|
-
expect(onSelect).not.toHaveBeenCalled();
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
it("resets the active item when the search changes", async () => {
|
|
228
|
-
const user = userEvent.setup();
|
|
229
|
-
render(<Example />);
|
|
230
|
-
|
|
231
|
-
const input = screen.getByRole("combobox");
|
|
232
|
-
await user.keyboard("{ArrowDown}");
|
|
233
|
-
expect(input).toHaveAttribute("aria-activedescendant");
|
|
234
|
-
|
|
235
|
-
await user.keyboard("c");
|
|
236
|
-
expect(input).not.toHaveAttribute("aria-activedescendant");
|
|
237
|
-
});
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
it("runs an item on click", async () => {
|
|
241
|
-
const onSelect = vi.fn();
|
|
242
|
-
const user = userEvent.setup();
|
|
243
|
-
render(<Example onSelect={onSelect} />);
|
|
244
|
-
|
|
245
|
-
await user.click(screen.getByRole("option", { name: "Billing" }));
|
|
246
|
-
expect(onSelect).toHaveBeenCalledWith("Billing");
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
it("ignores a disabled item", async () => {
|
|
250
|
-
const onSelect = vi.fn();
|
|
251
|
-
const user = userEvent.setup();
|
|
252
|
-
render(<Example onSelect={onSelect} />);
|
|
253
|
-
|
|
254
|
-
await user.click(screen.getByRole("option", { name: "Calculator" }));
|
|
255
|
-
expect(onSelect).not.toHaveBeenCalled();
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
it("hides the shortcut hint from assistive technology", () => {
|
|
259
|
-
const { container } = render(<Example />);
|
|
260
|
-
expect(container.querySelector("[data-slot='command-shortcut']")).toHaveAttribute(
|
|
261
|
-
"aria-hidden",
|
|
262
|
-
"true",
|
|
263
|
-
);
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
it("keeps items out of the tab order", () => {
|
|
267
|
-
render(<Example />);
|
|
268
|
-
for (const option of screen.getAllByRole("option")) {
|
|
269
|
-
expect(option).toHaveAttribute("tabindex", "-1");
|
|
270
|
-
}
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
it("can be controlled", async () => {
|
|
274
|
-
const user = userEvent.setup();
|
|
275
|
-
|
|
276
|
-
function Controlled() {
|
|
277
|
-
const [search, setSearch] = useState("");
|
|
278
|
-
return (
|
|
279
|
-
<>
|
|
280
|
-
<Command value={search} onValueChange={setSearch}>
|
|
281
|
-
<CommandInput aria-label="Search" />
|
|
282
|
-
<CommandList>
|
|
283
|
-
<CommandItem value="Alpha" />
|
|
284
|
-
<CommandItem value="Beta" />
|
|
285
|
-
</CommandList>
|
|
286
|
-
</Command>
|
|
287
|
-
<span data-testid="echo">{search}</span>
|
|
288
|
-
</>
|
|
289
|
-
);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
render(<Controlled />);
|
|
293
|
-
await user.keyboard("al");
|
|
294
|
-
expect(screen.getByTestId("echo")).toHaveTextContent("al");
|
|
295
|
-
expect(screen.getAllByRole("option")).toHaveLength(1);
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
it("throws a useful error if a part is used outside the root", () => {
|
|
299
|
-
const consoleError = vi.spyOn(console, "error").mockImplementation(() => undefined);
|
|
300
|
-
expect(() => render(<CommandList />)).toThrow(
|
|
301
|
-
/CommandList must be rendered inside <Command>/,
|
|
302
|
-
);
|
|
303
|
-
consoleError.mockRestore();
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
it("has no accessibility violations", async () => {
|
|
307
|
-
const { container } = render(<Example />);
|
|
308
|
-
await expectNoA11yViolations(container);
|
|
309
|
-
});
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
describe("CommandDialog", () => {
|
|
313
|
-
it("is always named, even though the title is not shown", async () => {
|
|
314
|
-
render(
|
|
315
|
-
<CommandDialog open>
|
|
316
|
-
<CommandInput aria-label="Search" />
|
|
317
|
-
<CommandList>
|
|
318
|
-
<CommandItem value="Alpha" />
|
|
319
|
-
</CommandList>
|
|
320
|
-
</CommandDialog>,
|
|
321
|
-
);
|
|
322
|
-
|
|
323
|
-
expect(await screen.findByRole("dialog", { name: "Command palette" })).toBeInTheDocument();
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
it("accepts a custom title and description", async () => {
|
|
327
|
-
render(
|
|
328
|
-
<CommandDialog open title="Jump to" description="Search pages and actions.">
|
|
329
|
-
<CommandInput aria-label="Search" />
|
|
330
|
-
<CommandList>
|
|
331
|
-
<CommandItem value="Alpha" />
|
|
332
|
-
</CommandList>
|
|
333
|
-
</CommandDialog>,
|
|
334
|
-
);
|
|
335
|
-
|
|
336
|
-
const dialog = await screen.findByRole("dialog", { name: "Jump to" });
|
|
337
|
-
expect(dialog).toHaveAccessibleDescription("Search pages and actions.");
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
it("closes on Escape", async () => {
|
|
341
|
-
const user = userEvent.setup();
|
|
342
|
-
|
|
343
|
-
function Controlled() {
|
|
344
|
-
const [open, setOpen] = useState(true);
|
|
345
|
-
return (
|
|
346
|
-
<CommandDialog open={open} onOpenChange={setOpen}>
|
|
347
|
-
<CommandInput aria-label="Search" />
|
|
348
|
-
<CommandList>
|
|
349
|
-
<CommandItem value="Alpha" />
|
|
350
|
-
</CommandList>
|
|
351
|
-
</CommandDialog>
|
|
352
|
-
);
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
render(<Controlled />);
|
|
356
|
-
await screen.findByRole("dialog");
|
|
357
|
-
|
|
358
|
-
await user.keyboard("{Escape}");
|
|
359
|
-
await waitFor(() => {
|
|
360
|
-
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
|
361
|
-
});
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
it("has no accessibility violations while open", async () => {
|
|
365
|
-
const { baseElement } = render(
|
|
366
|
-
<CommandDialog open>
|
|
367
|
-
<CommandInput aria-label="Search" />
|
|
368
|
-
<CommandList>
|
|
369
|
-
<CommandGroup heading="Pages">
|
|
370
|
-
<CommandItem value="Home" />
|
|
371
|
-
<CommandItem value="Settings" />
|
|
372
|
-
</CommandGroup>
|
|
373
|
-
</CommandList>
|
|
374
|
-
</CommandDialog>,
|
|
375
|
-
);
|
|
376
|
-
|
|
377
|
-
await screen.findByRole("dialog");
|
|
378
|
-
await expectNoA11yViolations(baseElement);
|
|
379
|
-
});
|
|
380
|
-
});
|
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
columnVisibilityFeature,
|
|
3
|
-
createPaginatedRowModel,
|
|
4
|
-
createSortedRowModel,
|
|
5
|
-
rowPaginationFeature,
|
|
6
|
-
rowSortingFeature,
|
|
7
|
-
tableFeatures,
|
|
8
|
-
useTable,
|
|
9
|
-
} from "@tanstack/react-table";
|
|
10
|
-
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
11
|
-
|
|
12
|
-
import { Badge } from "@/components/badge";
|
|
13
|
-
|
|
14
|
-
import {
|
|
15
|
-
DataTable,
|
|
16
|
-
DataTableColumnHeader,
|
|
17
|
-
DataTablePagination,
|
|
18
|
-
DataTableViewOptions,
|
|
19
|
-
} from "./data-table";
|
|
20
|
-
|
|
21
|
-
interface Deployment {
|
|
22
|
-
id: string;
|
|
23
|
-
branch: string;
|
|
24
|
-
status: "Ready" | "Building" | "Failed";
|
|
25
|
-
duration: number;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const DEPLOYMENTS: Deployment[] = [
|
|
29
|
-
{ id: "dpl_a1", branch: "main", status: "Ready", duration: 42 },
|
|
30
|
-
{ id: "dpl_b2", branch: "feat/shortcuts", status: "Building", duration: 12 },
|
|
31
|
-
{ id: "dpl_c3", branch: "fix/tokens", status: "Failed", duration: 8 },
|
|
32
|
-
{ id: "dpl_d4", branch: "main", status: "Ready", duration: 51 },
|
|
33
|
-
{ id: "dpl_e5", branch: "chore/deps", status: "Ready", duration: 37 },
|
|
34
|
-
{ id: "dpl_f6", branch: "feat/table", status: "Ready", duration: 64 },
|
|
35
|
-
{ id: "dpl_g7", branch: "main", status: "Failed", duration: 3 },
|
|
36
|
-
];
|
|
37
|
-
|
|
38
|
-
const features = tableFeatures({
|
|
39
|
-
rowSortingFeature,
|
|
40
|
-
rowPaginationFeature,
|
|
41
|
-
columnVisibilityFeature,
|
|
42
|
-
sortedRowModel: createSortedRowModel(),
|
|
43
|
-
paginatedRowModel: createPaginatedRowModel(),
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Annotated rather than inferred: DataTable is generic over the table's feature
|
|
48
|
-
* set, and inferring story args through the meta makes every story demand a
|
|
49
|
-
* fully-typed `table` arg even though they all render their own.
|
|
50
|
-
*/
|
|
51
|
-
const meta: Meta = {
|
|
52
|
-
title: "Data/Data Table",
|
|
53
|
-
component: DataTable,
|
|
54
|
-
parameters: { controls: { disable: true } },
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
export default meta;
|
|
58
|
-
type Story = StoryObj;
|
|
59
|
-
|
|
60
|
-
function useDeploymentsTable(data: Deployment[], pageSize = 5) {
|
|
61
|
-
return useTable({
|
|
62
|
-
features,
|
|
63
|
-
data,
|
|
64
|
-
initialState: { pagination: { pageIndex: 0, pageSize } },
|
|
65
|
-
columns: [
|
|
66
|
-
{
|
|
67
|
-
accessorKey: "id",
|
|
68
|
-
header: "Deployment",
|
|
69
|
-
enableSorting: false,
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
accessorKey: "branch",
|
|
73
|
-
header: ({ column }) => <DataTableColumnHeader column={column} title="Branch" />,
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
accessorKey: "status",
|
|
77
|
-
header: ({ column }) => <DataTableColumnHeader column={column} title="Status" />,
|
|
78
|
-
cell: ({ row }) => {
|
|
79
|
-
const status = row.original.status;
|
|
80
|
-
return (
|
|
81
|
-
<Badge
|
|
82
|
-
size="sm"
|
|
83
|
-
variant={
|
|
84
|
-
status === "Ready"
|
|
85
|
-
? "success"
|
|
86
|
-
: status === "Failed"
|
|
87
|
-
? "destructive"
|
|
88
|
-
: "secondary"
|
|
89
|
-
}
|
|
90
|
-
>
|
|
91
|
-
{status}
|
|
92
|
-
</Badge>
|
|
93
|
-
);
|
|
94
|
-
},
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
accessorKey: "duration",
|
|
98
|
-
header: ({ column }) => <DataTableColumnHeader column={column} title="Duration" />,
|
|
99
|
-
cell: ({ row }) => <span className="tabular-nums">{row.original.duration}s</span>,
|
|
100
|
-
},
|
|
101
|
-
],
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/** Sortable columns, hideable columns and pagination, all driven by the table instance. */
|
|
106
|
-
export const Default: Story = {
|
|
107
|
-
render: function Default() {
|
|
108
|
-
const table = useDeploymentsTable(DEPLOYMENTS);
|
|
109
|
-
|
|
110
|
-
return (
|
|
111
|
-
<div className="w-[42rem]">
|
|
112
|
-
<div className="flex items-center justify-end pb-3">
|
|
113
|
-
<DataTableViewOptions table={table} />
|
|
114
|
-
</div>
|
|
115
|
-
<DataTable table={table} aria-label="Deployments" />
|
|
116
|
-
<DataTablePagination table={table} pageSizes={[5, 10, 20]} />
|
|
117
|
-
</div>
|
|
118
|
-
);
|
|
119
|
-
},
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
export const Empty: Story = {
|
|
123
|
-
render: function Empty() {
|
|
124
|
-
const table = useDeploymentsTable([]);
|
|
125
|
-
|
|
126
|
-
return (
|
|
127
|
-
<div className="w-[42rem]">
|
|
128
|
-
<DataTable table={table} aria-label="Deployments" />
|
|
129
|
-
<DataTablePagination table={table} pageSizes={[5, 10, 20]} />
|
|
130
|
-
</div>
|
|
131
|
-
);
|
|
132
|
-
},
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
/** The status slot replaces the page position for things like a selection count. */
|
|
136
|
-
export const WithStatusSlot: Story = {
|
|
137
|
-
render: function WithStatusSlot() {
|
|
138
|
-
const table = useDeploymentsTable(DEPLOYMENTS);
|
|
139
|
-
|
|
140
|
-
return (
|
|
141
|
-
<div className="w-[42rem]">
|
|
142
|
-
<DataTable table={table} aria-label="Deployments" />
|
|
143
|
-
<DataTablePagination
|
|
144
|
-
table={table}
|
|
145
|
-
pageSizes={[5, 10, 20]}
|
|
146
|
-
status={`${DEPLOYMENTS.length} deployments`}
|
|
147
|
-
/>
|
|
148
|
-
</div>
|
|
149
|
-
);
|
|
150
|
-
},
|
|
151
|
-
};
|