@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,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
|
-
});
|