gladvn 0.2.35 → 0.2.37
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/README.md +1 -1
- package/package.json +2 -1
- package/scripts/strip-comments.ts +79 -0
- package/src/blocks/auth-form.tsx +34 -30
- package/src/blocks/auth-recovery.tsx +9 -13
- package/src/blocks/auth-split.tsx +46 -36
- package/src/blocks/settings.tsx +84 -117
- package/src/components/macro/alert-preset.tsx +3 -1
- package/src/components/macro/carousel-preset.tsx +0 -1
- package/src/components/macro/checkbox-preset.test.tsx +2 -2
- package/src/components/macro/checkbox-preset.tsx +1 -2
- package/src/components/macro/command-preset.tsx +1 -1
- package/src/components/macro/date-picker.tsx +5 -18
- package/src/components/macro/dialog-preset.tsx +5 -3
- package/src/components/macro/input-preset.tsx +0 -1
- package/src/components/macro/select-preset.tsx +1 -1
- package/src/components/macro/switch-preset.test.tsx +1 -1
- package/src/components/micro/accordion.test.tsx +6 -12
- package/src/components/micro/alert-dialog.tsx +0 -2
- package/src/components/micro/alert.test.tsx +5 -8
- package/src/components/micro/alert.tsx +0 -2
- package/src/components/micro/badge.test.tsx +5 -7
- package/src/components/micro/badge.tsx +1 -3
- package/src/components/micro/button.test.tsx +19 -26
- package/src/components/micro/button.tsx +3 -11
- package/src/components/micro/calendar.tsx +12 -13
- package/src/components/micro/card.test.tsx +3 -5
- package/src/components/micro/carousel.tsx +0 -1
- package/src/components/micro/chart.tsx +3 -2
- package/src/components/micro/checkbox.test.tsx +9 -18
- package/src/components/micro/combobox.tsx +1 -3
- package/src/components/micro/command.tsx +23 -11
- package/src/components/micro/context-menu.tsx +0 -2
- package/src/components/micro/dialog.test.tsx +6 -14
- package/src/components/micro/dialog.tsx +0 -2
- package/src/components/micro/drawer.tsx +0 -3
- package/src/components/micro/dropdown-menu.test.tsx +8 -14
- package/src/components/micro/dropdown-menu.tsx +0 -2
- package/src/components/micro/field.tsx +3 -3
- package/src/components/micro/hover-card.tsx +0 -2
- package/src/components/micro/input-group.tsx +9 -42
- package/src/components/micro/input.test.tsx +15 -24
- package/src/components/micro/navigation-menu.tsx +0 -2
- package/src/components/micro/popover.test.tsx +5 -9
- package/src/components/micro/progress.tsx +5 -1
- package/src/components/micro/select.test.tsx +5 -18
- package/src/components/micro/select.tsx +3 -5
- package/src/components/micro/separator.tsx +1 -1
- package/src/components/micro/sheet.test.tsx +6 -14
- package/src/components/micro/sheet.tsx +0 -2
- package/src/components/micro/sidebar.tsx +5 -6
- package/src/components/micro/sonner.tsx +0 -1
- package/src/components/micro/switch.test.tsx +9 -18
- package/src/components/micro/switch.tsx +1 -1
- package/src/components/micro/tabs.test.tsx +5 -11
- package/src/components/micro/theme-provider.tsx +0 -12
- package/src/components/micro/toggle-group.tsx +1 -12
- package/src/components/micro/tooltip.test.tsx +7 -12
- package/src/dev/App.tsx +6 -6
- package/src/dev/components/BlockViewer.tsx +15 -4
- package/src/dev/components/Container.tsx +20 -0
- package/src/dev/components/GladvnLogo.tsx +3 -9
- package/src/dev/components/code-highlighter.tsx +5 -3
- package/src/dev/components/showcase.tsx +116 -168
- package/src/dev/sections/buttons.tsx +4 -9
- package/src/dev/sections/display.tsx +68 -72
- package/src/dev/sections/feedback.tsx +304 -314
- package/src/dev/sections/forms.tsx +10 -14
- package/src/dev/sections/interactive.tsx +32 -32
- package/src/dev/sections/overview.tsx +2 -10
- package/src/dev/showcase/accordion.tsx +512 -554
- package/src/dev/showcase/alert-dialog.tsx +525 -565
- package/src/dev/showcase/alert.tsx +352 -402
- package/src/dev/showcase/aspect-ratio.tsx +167 -193
- package/src/dev/showcase/avatar.tsx +231 -269
- package/src/dev/showcase/badge.tsx +92 -140
- package/src/dev/showcase/breadcrumb.tsx +113 -135
- package/src/dev/showcase/button.tsx +140 -173
- package/src/dev/showcase/calendar.tsx +187 -237
- package/src/dev/showcase/card.tsx +229 -261
- package/src/dev/showcase/carousel.tsx +288 -315
- package/src/dev/showcase/chart.tsx +44 -50
- package/src/dev/showcase/checkbox.tsx +286 -330
- package/src/dev/showcase/collapsible.tsx +143 -160
- package/src/dev/showcase/combobox.tsx +327 -369
- package/src/dev/showcase/command.tsx +106 -120
- package/src/dev/showcase/context-menu.tsx +93 -107
- package/src/dev/showcase/dialog.tsx +585 -630
- package/src/dev/showcase/drawer.tsx +176 -196
- package/src/dev/showcase/dropdown-menu.tsx +183 -201
- package/src/dev/showcase/empty.tsx +232 -274
- package/src/dev/showcase/hover-card.tsx +153 -180
- package/src/dev/showcase/input-group.tsx +221 -253
- package/src/dev/showcase/input-otp.tsx +204 -250
- package/src/dev/showcase/input.tsx +246 -325
- package/src/dev/showcase/item.tsx +134 -152
- package/src/dev/showcase/kbd.tsx +149 -172
- package/src/dev/showcase/label.tsx +50 -68
- package/src/dev/showcase/menubar.tsx +194 -213
- package/src/dev/showcase/navigation-menu.tsx +119 -133
- package/src/dev/showcase/overview-components/ArchitectureSection.tsx +126 -0
- package/src/dev/showcase/overview-components/CodeBlock.tsx +50 -0
- package/src/dev/showcase/overview-components/ColorPaletteSection.tsx +44 -0
- package/src/dev/showcase/overview-components/DashboardPreview.tsx +159 -0
- package/src/dev/showcase/overview-components/FeatureGridSection.tsx +535 -0
- package/src/dev/showcase/overview-components/FooterCTA.tsx +137 -0
- package/src/dev/showcase/overview-components/HeroSection.tsx +65 -0
- package/src/dev/showcase/overview-components/PrinciplesSection.tsx +128 -0
- package/src/dev/showcase/overview.tsx +21 -1163
- package/src/dev/showcase/pagination.tsx +144 -190
- package/src/dev/showcase/popover.tsx +181 -196
- package/src/dev/showcase/progress.tsx +141 -189
- package/src/dev/showcase/radio-group.tsx +272 -332
- package/src/dev/showcase/resizable.tsx +119 -141
- package/src/dev/showcase/scroll-area.tsx +21 -36
- package/src/dev/showcase/select.tsx +262 -322
- package/src/dev/showcase/separator.tsx +75 -73
- package/src/dev/showcase/sheet.tsx +333 -361
- package/src/dev/showcase/sidebar.tsx +53 -59
- package/src/dev/showcase/skeleton.tsx +93 -112
- package/src/dev/showcase/slider.tsx +244 -296
- package/src/dev/showcase/sonner.tsx +251 -258
- package/src/dev/showcase/spinner.tsx +46 -67
- package/src/dev/showcase/switch.tsx +298 -362
- package/src/dev/showcase/table.tsx +76 -80
- package/src/dev/showcase/tabs.tsx +302 -355
- package/src/dev/showcase/textarea.tsx +240 -293
- package/src/dev/showcase/toast.tsx +48 -54
- package/src/dev/showcase/toggle-group.tsx +104 -129
- package/src/dev/showcase/toggle.tsx +82 -102
- package/src/dev/showcase/tooltip.tsx +173 -192
- package/src/dev/showcase/why.tsx +6 -12
- package/src/hooks/use-mobile.test.ts +3 -2
- package/src/lib/utils.test.ts +5 -4
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
|
|
16
16
|
describe("DropdownMenu", () => {
|
|
17
17
|
it("[3C.5-01] [P1] renders trigger correctly and content is hidden initially", () => {
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
render(
|
|
20
20
|
<DropdownMenu>
|
|
21
21
|
<DropdownMenuTrigger>Open Menu</DropdownMenuTrigger>
|
|
@@ -31,15 +31,14 @@ describe("DropdownMenu", () => {
|
|
|
31
31
|
);
|
|
32
32
|
|
|
33
33
|
const trigger = screen.getByRole("button", { name: "Open Menu" });
|
|
34
|
-
|
|
34
|
+
|
|
35
35
|
expect(trigger).toBeInTheDocument();
|
|
36
36
|
|
|
37
|
-
// Base UI Menu might not use role="dialog" or "menu" before open, but it's hidden
|
|
38
37
|
expect(screen.queryByRole("menu")).not.toBeInTheDocument();
|
|
39
38
|
});
|
|
40
39
|
|
|
41
40
|
it("[3C.5-02] [P1] opens the menu when trigger is clicked", async () => {
|
|
42
|
-
|
|
41
|
+
|
|
43
42
|
const user = userEvent.setup();
|
|
44
43
|
render(
|
|
45
44
|
<DropdownMenu>
|
|
@@ -54,10 +53,9 @@ describe("DropdownMenu", () => {
|
|
|
54
53
|
);
|
|
55
54
|
|
|
56
55
|
const trigger = screen.getByRole("button", { name: "Open Menu" });
|
|
57
|
-
|
|
56
|
+
|
|
58
57
|
await user.click(trigger);
|
|
59
58
|
|
|
60
|
-
// Then
|
|
61
59
|
await waitFor(() => {
|
|
62
60
|
const menu = screen.getByRole("menu");
|
|
63
61
|
expect(menu).toBeInTheDocument();
|
|
@@ -69,7 +67,7 @@ describe("DropdownMenu", () => {
|
|
|
69
67
|
});
|
|
70
68
|
|
|
71
69
|
it("[3C.5-03] [P1] closes the menu when an item is clicked", async () => {
|
|
72
|
-
|
|
70
|
+
|
|
73
71
|
const user = userEvent.setup();
|
|
74
72
|
render(
|
|
75
73
|
<DropdownMenu>
|
|
@@ -80,10 +78,8 @@ describe("DropdownMenu", () => {
|
|
|
80
78
|
</DropdownMenu>,
|
|
81
79
|
);
|
|
82
80
|
|
|
83
|
-
// When
|
|
84
81
|
await user.click(screen.getByRole("button", { name: "Open Menu" }));
|
|
85
82
|
|
|
86
|
-
// Then
|
|
87
83
|
await waitFor(() => {
|
|
88
84
|
expect(screen.getByRole("menu")).toBeInTheDocument();
|
|
89
85
|
});
|
|
@@ -97,7 +93,7 @@ describe("DropdownMenu", () => {
|
|
|
97
93
|
});
|
|
98
94
|
|
|
99
95
|
it("[3C.5-04] [P1] supports keyboard navigation (ArrowDown, Escape)", async () => {
|
|
100
|
-
|
|
96
|
+
|
|
101
97
|
const user = userEvent.setup();
|
|
102
98
|
render(
|
|
103
99
|
<DropdownMenu>
|
|
@@ -110,19 +106,17 @@ describe("DropdownMenu", () => {
|
|
|
110
106
|
);
|
|
111
107
|
|
|
112
108
|
const trigger = screen.getByRole("button", { name: "Open Menu" });
|
|
113
|
-
|
|
109
|
+
|
|
114
110
|
await user.tab();
|
|
115
|
-
|
|
111
|
+
|
|
116
112
|
expect(trigger).toHaveFocus();
|
|
117
113
|
|
|
118
|
-
// Open with ArrowDown or Enter
|
|
119
114
|
await user.keyboard("[Enter]");
|
|
120
115
|
|
|
121
116
|
await waitFor(() => {
|
|
122
117
|
expect(screen.getByRole("menu")).toBeInTheDocument();
|
|
123
118
|
});
|
|
124
119
|
|
|
125
|
-
// Close with Escape
|
|
126
120
|
await user.keyboard("[Escape]");
|
|
127
121
|
|
|
128
122
|
await waitFor(() => {
|
|
@@ -73,7 +73,7 @@ const fieldVariants = cva("group/field flex min-w-fit", {
|
|
|
73
73
|
orientation: {
|
|
74
74
|
vertical: "flex-col [&>.sr-only]:w-auto",
|
|
75
75
|
horizontal: "flex-row items-center [&>[data-slot=field-label]]:flex-auto",
|
|
76
|
-
|
|
76
|
+
|
|
77
77
|
responsive:
|
|
78
78
|
"flex-col @md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>[data-slot=field-label]]:flex-auto [&>.sr-only]:w-auto",
|
|
79
79
|
},
|
|
@@ -200,9 +200,9 @@ const FieldDescription = React.forwardRef<
|
|
|
200
200
|
ref={ref}
|
|
201
201
|
data-slot="field-description"
|
|
202
202
|
className={cn(
|
|
203
|
-
|
|
203
|
+
|
|
204
204
|
"text-left leading-normal font-normal text-muted-foreground group-data-[orientation=horizontal]/field:text-balance [[data-variant=legend]+&]:-mt-1.5",
|
|
205
|
-
|
|
205
|
+
|
|
206
206
|
"last:mt-0 nth-last-2:-mt-1",
|
|
207
207
|
"[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
|
|
208
208
|
"text-sm group-data-[size=sm]/field:text-xs",
|
|
@@ -13,28 +13,23 @@ import { type VariantProps, cva } from "class-variance-authority";
|
|
|
13
13
|
|
|
14
14
|
import { cn } from "../../lib/utils";
|
|
15
15
|
|
|
16
|
-
/* ── InputGroup ─────────────────────────────────────────────────
|
|
17
|
-
Wrapper that groups inputs, addons, and action buttons.
|
|
18
|
-
`size` is set once here and propagated to all children via
|
|
19
|
-
CSS group modifiers: group-data-[size=sm|md|lg]/input-group:*
|
|
20
|
-
─────────────────────────────────────────────────────────────── */
|
|
21
16
|
const inputGroupVariants = cva(
|
|
22
17
|
[
|
|
23
18
|
"group/input-group relative flex min-w-0 items-center overflow-hidden rounded-lg border border-input transition-colors outline-none",
|
|
24
|
-
|
|
19
|
+
|
|
25
20
|
"has-disabled:bg-input/50 has-disabled:opacity-50 has-disabled:cursor-not-allowed dark:has-disabled:bg-input/80",
|
|
26
|
-
|
|
21
|
+
|
|
27
22
|
"has-[[data-slot=input-group-control]:focus-visible]:border-ring",
|
|
28
23
|
"has-[[data-slot=input-group-control]:focus-visible]:ring-3",
|
|
29
24
|
"has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50",
|
|
30
25
|
"has-[[data-slot=input-group-control]:focus-visible]:ring-offset-1",
|
|
31
26
|
"has-[[data-slot=input-group-control]:focus-visible]:ring-offset-background",
|
|
32
|
-
|
|
27
|
+
|
|
33
28
|
"has-[[data-slot][aria-invalid=true]]:border-destructive",
|
|
34
29
|
"has-[[data-slot][aria-invalid=true]:focus-visible]:ring-3",
|
|
35
30
|
"has-[[data-slot][aria-invalid=true]:focus-visible]:ring-destructive/50",
|
|
36
31
|
"has-[[data-slot][aria-invalid=true]:focus-visible]:border-destructive",
|
|
37
|
-
|
|
32
|
+
|
|
38
33
|
"dark:bg-input/30",
|
|
39
34
|
],
|
|
40
35
|
{
|
|
@@ -66,11 +61,6 @@ const InputGroup = React.forwardRef<
|
|
|
66
61
|
});
|
|
67
62
|
InputGroup.displayName = "InputGroup";
|
|
68
63
|
|
|
69
|
-
/* ── InputGroupAddon ─────────────────────────────────────────────
|
|
70
|
-
Decorative slot for text/icon labels only (non-interactive).
|
|
71
|
-
Clicking the addon area focuses the nearest input/textarea.
|
|
72
|
-
Use `align="start"` (default) or `align="end"`.
|
|
73
|
-
─────────────────────────────────────────────────────────────── */
|
|
74
64
|
const inputGroupAddonVariants = cva(
|
|
75
65
|
[
|
|
76
66
|
"flex h-auto cursor-text items-center justify-center gap-2 font-medium text-muted-foreground select-none",
|
|
@@ -114,12 +104,6 @@ const InputGroupAddon = React.forwardRef<
|
|
|
114
104
|
});
|
|
115
105
|
InputGroupAddon.displayName = "InputGroupAddon";
|
|
116
106
|
|
|
117
|
-
/* ── InputGroupText ──────────────────────────────────────────────
|
|
118
|
-
Inline label text or icon+text combo inside an InputGroupAddon.
|
|
119
|
-
@note Decorative icons placed here should include `aria-hidden="true"`
|
|
120
|
-
to prevent screen readers from announcing them, e.g.:
|
|
121
|
-
<InputGroupText><SearchIcon aria-hidden="true" /></InputGroupText>
|
|
122
|
-
─────────────────────────────────────────────────────────────── */
|
|
123
107
|
const InputGroupText = React.forwardRef<
|
|
124
108
|
HTMLSpanElement,
|
|
125
109
|
React.ComponentPropsWithoutRef<"span">
|
|
@@ -139,26 +123,15 @@ const InputGroupText = React.forwardRef<
|
|
|
139
123
|
});
|
|
140
124
|
InputGroupText.displayName = "InputGroupText";
|
|
141
125
|
|
|
142
|
-
/* ── InputGroupButton ────────────────────────────────────────────
|
|
143
|
-
Self-contained native <button>. Place directly inside InputGroup
|
|
144
|
-
(not inside InputGroupAddon). Size is always inherited from the
|
|
145
|
-
parent InputGroup via CSS group modifiers — no `size` prop.
|
|
146
|
-
|
|
147
|
-
Separator borders (border-l / border-r) are applied automatically
|
|
148
|
-
based on DOM position within the flex container.
|
|
149
|
-
|
|
150
|
-
icon={true} → square icon-only button (width = row height)
|
|
151
|
-
icon={false} → text button with horizontal padding (default)
|
|
152
|
-
─────────────────────────────────────────────────────────────── */
|
|
153
126
|
const inputGroupButtonVariants = cva(
|
|
154
127
|
[
|
|
155
128
|
"inline-flex shrink-0 self-stretch cursor-pointer items-center justify-center gap-1.5 font-medium whitespace-nowrap transition-colors duration-150 select-none",
|
|
156
|
-
|
|
129
|
+
|
|
157
130
|
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-1 focus-visible:ring-offset-background",
|
|
158
131
|
"disabled:pointer-events-none disabled:opacity-50",
|
|
159
|
-
|
|
132
|
+
|
|
160
133
|
"[&:not(:first-child)]:border-l [&:not(:last-child)]:border-r border-border",
|
|
161
|
-
|
|
134
|
+
|
|
162
135
|
"[&>svg:not([class*='size-'])]:size-4",
|
|
163
136
|
"group-data-[size=sm]/input-group:[&>svg:not([class*='size-'])]:size-3.5",
|
|
164
137
|
],
|
|
@@ -174,13 +147,13 @@ const inputGroupButtonVariants = cva(
|
|
|
174
147
|
},
|
|
175
148
|
icon: {
|
|
176
149
|
true: [
|
|
177
|
-
|
|
150
|
+
|
|
178
151
|
"group-data-[size=sm]/input-group:w-7",
|
|
179
152
|
"group-data-[size=md]/input-group:w-8",
|
|
180
153
|
"group-data-[size=lg]/input-group:w-9",
|
|
181
154
|
],
|
|
182
155
|
false: [
|
|
183
|
-
|
|
156
|
+
|
|
184
157
|
"group-data-[size=sm]/input-group:px-2 group-data-[size=sm]/input-group:text-xs",
|
|
185
158
|
"group-data-[size=md]/input-group:px-2.5 group-data-[size=md]/input-group:text-sm",
|
|
186
159
|
"group-data-[size=lg]/input-group:px-3 group-data-[size=lg]/input-group:text-sm",
|
|
@@ -212,9 +185,6 @@ const InputGroupButton = React.forwardRef<
|
|
|
212
185
|
});
|
|
213
186
|
InputGroupButton.displayName = "InputGroupButton";
|
|
214
187
|
|
|
215
|
-
/* ── InputGroupInput ─────────────────────────────────────────────
|
|
216
|
-
Self-contained native <input>. Size inherited from InputGroup.
|
|
217
|
-
─────────────────────────────────────────────────────────────── */
|
|
218
188
|
const InputGroupInput = React.forwardRef<
|
|
219
189
|
HTMLInputElement,
|
|
220
190
|
React.ComponentPropsWithoutRef<"input">
|
|
@@ -236,9 +206,6 @@ const InputGroupInput = React.forwardRef<
|
|
|
236
206
|
});
|
|
237
207
|
InputGroupInput.displayName = "InputGroupInput";
|
|
238
208
|
|
|
239
|
-
/* ── InputGroupTextarea ──────────────────────────────────────────
|
|
240
|
-
Self-contained native <textarea>. Size inherited from InputGroup.
|
|
241
|
-
─────────────────────────────────────────────────────────────── */
|
|
242
209
|
const InputGroupTextarea = React.forwardRef<
|
|
243
210
|
HTMLTextAreaElement,
|
|
244
211
|
React.ComponentPropsWithoutRef<"textarea">
|
|
@@ -7,88 +7,79 @@ import { describe, expect, it, vi } from "vitest";
|
|
|
7
7
|
import { Input } from "./input";
|
|
8
8
|
|
|
9
9
|
describe("Input", () => {
|
|
10
|
-
|
|
10
|
+
|
|
11
11
|
it("[3C.2-01] [P1] renders correctly with default props", () => {
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
render(<Input placeholder="Enter email" />);
|
|
14
14
|
const input = screen.getByPlaceholderText("Enter email");
|
|
15
|
-
|
|
15
|
+
|
|
16
16
|
expect(input).toBeInTheDocument();
|
|
17
17
|
expect(input.tagName).toBe("INPUT");
|
|
18
18
|
expect(input).toHaveAttribute("data-slot", "input");
|
|
19
19
|
});
|
|
20
20
|
|
|
21
21
|
it("[3C.2-02] [P1] applies sizing correctly", () => {
|
|
22
|
-
|
|
22
|
+
|
|
23
23
|
render(<Input placeholder="Small input" size="sm" />);
|
|
24
24
|
const input = screen.getByPlaceholderText("Small input");
|
|
25
|
-
|
|
26
|
-
// but a basic check that it received the size classes is fine.
|
|
27
|
-
// The cva variant for sm contains "h-7"
|
|
28
|
-
// Then
|
|
25
|
+
|
|
29
26
|
expect(input.className).toContain("h-7");
|
|
30
27
|
});
|
|
31
28
|
|
|
32
29
|
it("[3C.2-03] [P1] merges custom classNames properly", () => {
|
|
33
|
-
|
|
30
|
+
|
|
34
31
|
render(<Input placeholder="Classy input" className="custom-test-class" />);
|
|
35
32
|
const input = screen.getByPlaceholderText("Classy input");
|
|
36
|
-
|
|
33
|
+
|
|
37
34
|
expect(input).toHaveClass("custom-test-class");
|
|
38
35
|
});
|
|
39
36
|
|
|
40
|
-
// 2. Behaviors (Interaction & Events)
|
|
41
37
|
it("[3C.2-04] [P1] handles user typing correctly", async () => {
|
|
42
|
-
|
|
38
|
+
|
|
43
39
|
const user = userEvent.setup();
|
|
44
40
|
const handleChange = vi.fn();
|
|
45
41
|
render(<Input placeholder="Type here" onChange={handleChange} />);
|
|
46
42
|
|
|
47
43
|
const input = screen.getByPlaceholderText("Type here");
|
|
48
|
-
|
|
44
|
+
|
|
49
45
|
await user.type(input, "Hello");
|
|
50
46
|
|
|
51
|
-
// Then
|
|
52
47
|
expect(input).toHaveValue("Hello");
|
|
53
48
|
expect(handleChange).toHaveBeenCalled();
|
|
54
49
|
});
|
|
55
50
|
|
|
56
51
|
it("[3C.2-05] [P1] can be focused and activated via keyboard", async () => {
|
|
57
|
-
|
|
52
|
+
|
|
58
53
|
const user = userEvent.setup();
|
|
59
54
|
render(<Input placeholder="Focus me" />);
|
|
60
55
|
|
|
61
56
|
const input = screen.getByPlaceholderText("Focus me");
|
|
62
57
|
|
|
63
|
-
// Test focus
|
|
64
|
-
// When
|
|
65
58
|
await user.tab();
|
|
66
|
-
|
|
59
|
+
|
|
67
60
|
expect(input).toHaveFocus();
|
|
68
61
|
});
|
|
69
62
|
|
|
70
63
|
it("[3C.2-06] [P1] handles disabled state correctly and blocks typing", async () => {
|
|
71
|
-
|
|
64
|
+
|
|
72
65
|
const user = userEvent.setup();
|
|
73
66
|
render(<Input placeholder="Disabled" disabled />);
|
|
74
67
|
|
|
75
68
|
const input = screen.getByPlaceholderText("Disabled");
|
|
76
|
-
|
|
69
|
+
|
|
77
70
|
expect(input).toBeDisabled();
|
|
78
71
|
|
|
79
|
-
// When
|
|
80
72
|
await user.type(input, "Hello");
|
|
81
73
|
expect(input).toHaveValue("");
|
|
82
74
|
});
|
|
83
75
|
|
|
84
|
-
// 3. Architecture & Integration
|
|
85
76
|
it("[3C.2-07] [P1] forwards the ref to the underlying DOM element", () => {
|
|
86
|
-
|
|
77
|
+
|
|
87
78
|
const ref = React.createRef<HTMLInputElement>();
|
|
88
79
|
render(
|
|
89
80
|
<Input ref={ref} placeholder="Ref input" defaultValue="ref value" />,
|
|
90
81
|
);
|
|
91
|
-
|
|
82
|
+
|
|
92
83
|
expect(ref.current).toBeInstanceOf(HTMLInputElement);
|
|
93
84
|
expect(ref.current?.value).toBe("ref value");
|
|
94
85
|
});
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
|
|
14
14
|
describe("Popover", () => {
|
|
15
15
|
it("[3C.14-01] [P1] renders trigger correctly and content is hidden initially", () => {
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
render(
|
|
18
18
|
<Popover>
|
|
19
19
|
<PopoverTrigger>Open Popover</PopoverTrigger>
|
|
@@ -25,15 +25,14 @@ describe("Popover", () => {
|
|
|
25
25
|
);
|
|
26
26
|
|
|
27
27
|
const trigger = screen.getByRole("button", { name: "Open Popover" });
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
expect(trigger).toBeInTheDocument();
|
|
30
30
|
|
|
31
|
-
// Content should not be in the document
|
|
32
31
|
expect(screen.queryByText("Popover Title")).not.toBeInTheDocument();
|
|
33
32
|
});
|
|
34
33
|
|
|
35
34
|
it("[3C.14-02] [P1] opens the popover when trigger is clicked", async () => {
|
|
36
|
-
|
|
35
|
+
|
|
37
36
|
const user = userEvent.setup();
|
|
38
37
|
render(
|
|
39
38
|
<Popover>
|
|
@@ -45,17 +44,16 @@ describe("Popover", () => {
|
|
|
45
44
|
);
|
|
46
45
|
|
|
47
46
|
const trigger = screen.getByRole("button", { name: "Open Popover" });
|
|
48
|
-
|
|
47
|
+
|
|
49
48
|
await user.click(trigger);
|
|
50
49
|
|
|
51
|
-
// Then
|
|
52
50
|
await waitFor(() => {
|
|
53
51
|
expect(screen.getByText("Popover Title")).toBeInTheDocument();
|
|
54
52
|
});
|
|
55
53
|
});
|
|
56
54
|
|
|
57
55
|
it("[3C.14-03] [P1] closes the popover when Escape is pressed", async () => {
|
|
58
|
-
|
|
56
|
+
|
|
59
57
|
const user = userEvent.setup();
|
|
60
58
|
render(
|
|
61
59
|
<Popover>
|
|
@@ -66,10 +64,8 @@ describe("Popover", () => {
|
|
|
66
64
|
</Popover>,
|
|
67
65
|
);
|
|
68
66
|
|
|
69
|
-
// When
|
|
70
67
|
await user.click(screen.getByRole("button", { name: "Open Popover" }));
|
|
71
68
|
|
|
72
|
-
// Then
|
|
73
69
|
await waitFor(() => {
|
|
74
70
|
expect(screen.getByText("Popover Title")).toBeInTheDocument();
|
|
75
71
|
});
|
|
@@ -62,7 +62,11 @@ function ProgressTrack({ className, ...props }: ProgressPrimitive.Track.Props) {
|
|
|
62
62
|
<ProgressPrimitive.Track
|
|
63
63
|
className={cn(
|
|
64
64
|
"relative flex w-full items-center overflow-x-hidden rounded-full bg-muted",
|
|
65
|
-
|
|
65
|
+
{
|
|
66
|
+
"h-1": size === "sm",
|
|
67
|
+
"h-1.5": size === "md",
|
|
68
|
+
"h-2": size === "lg",
|
|
69
|
+
},
|
|
66
70
|
className,
|
|
67
71
|
)}
|
|
68
72
|
data-slot="progress-track"
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
|
|
15
15
|
describe("Select", () => {
|
|
16
16
|
it("[3C.3-01] [P1] renders correctly with default closed state", () => {
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
render(
|
|
19
19
|
<Select>
|
|
20
20
|
<SelectTrigger aria-label="Food">
|
|
@@ -27,20 +27,17 @@ describe("Select", () => {
|
|
|
27
27
|
</Select>,
|
|
28
28
|
);
|
|
29
29
|
|
|
30
|
-
// The trigger should be visible
|
|
31
30
|
const trigger = screen.getByRole("combobox", { name: "Food" });
|
|
32
|
-
|
|
31
|
+
|
|
33
32
|
expect(trigger).toBeInTheDocument();
|
|
34
33
|
|
|
35
|
-
// Check placeholder
|
|
36
34
|
expect(trigger).toHaveTextContent("Select a fruit");
|
|
37
35
|
|
|
38
|
-
// The popup content should not be in the document initially
|
|
39
36
|
expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
|
|
40
37
|
});
|
|
41
38
|
|
|
42
39
|
it("[3C.3-02] [P1] opens the popup on click and allows selection", async () => {
|
|
43
|
-
|
|
40
|
+
|
|
44
41
|
const user = userEvent.setup();
|
|
45
42
|
|
|
46
43
|
const ControlledSelect = () => {
|
|
@@ -62,28 +59,22 @@ describe("Select", () => {
|
|
|
62
59
|
|
|
63
60
|
const trigger = screen.getByRole("combobox", { name: "Food" });
|
|
64
61
|
|
|
65
|
-
// Click trigger to open
|
|
66
|
-
// When
|
|
67
62
|
await user.click(trigger);
|
|
68
63
|
|
|
69
|
-
// Wait for the popup to appear in the document
|
|
70
|
-
// Then
|
|
71
64
|
await waitFor(() => {
|
|
72
65
|
expect(screen.getByRole("listbox")).toBeInTheDocument();
|
|
73
66
|
});
|
|
74
67
|
|
|
75
|
-
// Select Banana
|
|
76
68
|
const bananaItem = screen.getByRole("option", { name: "Banana" });
|
|
77
69
|
await user.click(bananaItem);
|
|
78
70
|
|
|
79
|
-
// Wait for the trigger to update
|
|
80
71
|
await waitFor(() => {
|
|
81
72
|
expect(trigger).toHaveTextContent(/banana/i);
|
|
82
73
|
});
|
|
83
74
|
});
|
|
84
75
|
|
|
85
76
|
it("[3C.3-03] [P1] can be navigated via keyboard", async () => {
|
|
86
|
-
|
|
77
|
+
|
|
87
78
|
const user = userEvent.setup();
|
|
88
79
|
render(
|
|
89
80
|
<Select defaultValue="apple">
|
|
@@ -99,20 +90,16 @@ describe("Select", () => {
|
|
|
99
90
|
|
|
100
91
|
const trigger = screen.getByRole("combobox", { name: "Food" });
|
|
101
92
|
|
|
102
|
-
// Focus trigger
|
|
103
|
-
// When
|
|
104
93
|
await user.tab();
|
|
105
|
-
|
|
94
|
+
|
|
106
95
|
expect(trigger).toHaveFocus();
|
|
107
96
|
|
|
108
|
-
// Open menu with Enter (or Space)
|
|
109
97
|
await user.keyboard("[Enter]");
|
|
110
98
|
|
|
111
99
|
await waitFor(() => {
|
|
112
100
|
expect(screen.getByRole("listbox")).toBeInTheDocument();
|
|
113
101
|
});
|
|
114
102
|
|
|
115
|
-
// Pressing Escape should close it
|
|
116
103
|
await user.keyboard("[Escape]");
|
|
117
104
|
|
|
118
105
|
await waitFor(() => {
|
|
@@ -44,8 +44,6 @@ const SelectValue = React.forwardRef<
|
|
|
44
44
|
));
|
|
45
45
|
SelectValue.displayName = "SelectValue";
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
47
|
const selectTriggerVariants = cva(
|
|
50
48
|
"inline-flex items-center justify-between rounded-lg border border-input bg-transparent text-foreground text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:focus-visible:ring-3 aria-invalid:focus-visible:ring-destructive/50 data-placeholder:text-muted-foreground [&_[data-slot=select-value]]:line-clamp-1 [&_[data-slot=select-value]]:flex [&_[data-slot=select-value]]:items-center [&>svg]:pointer-events-none [&>svg]:shrink-0 [&>svg:not([class*='size-'])]:size-4 dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:focus-visible:ring-destructive/50",
|
|
51
49
|
{
|
|
@@ -120,9 +118,9 @@ const SelectContent = React.forwardRef<
|
|
|
120
118
|
data-align-trigger={alignItemWithTrigger}
|
|
121
119
|
className={cn(
|
|
122
120
|
"relative isolate z-50 max-h-(--available-height) w-[calc(var(--anchor-width)+8px)] overflow-x-hidden overflow-y-auto rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
121
|
+
{
|
|
122
|
+
"origin-(--transform-origin) data-[align-trigger=true]:animate-none": alignItemWithTrigger,
|
|
123
|
+
},
|
|
126
124
|
className,
|
|
127
125
|
)}
|
|
128
126
|
{...props}
|
|
@@ -26,7 +26,7 @@ function Separator({
|
|
|
26
26
|
data-slot="separator"
|
|
27
27
|
orientation={orientation}
|
|
28
28
|
className={cn(
|
|
29
|
-
"shrink-0 bg-border data-horizontal:h-px data-
|
|
29
|
+
"shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=vertical]:w-px",
|
|
30
30
|
className,
|
|
31
31
|
)}
|
|
32
32
|
{...props}
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
|
|
15
15
|
describe("Sheet", () => {
|
|
16
16
|
it("[3C.15-01] [P1] renders trigger correctly and content is hidden initially", () => {
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
render(
|
|
19
19
|
<Sheet>
|
|
20
20
|
<SheetTrigger>Open Sheet</SheetTrigger>
|
|
@@ -28,15 +28,14 @@ describe("Sheet", () => {
|
|
|
28
28
|
);
|
|
29
29
|
|
|
30
30
|
const trigger = screen.getByRole("button", { name: "Open Sheet" });
|
|
31
|
-
|
|
31
|
+
|
|
32
32
|
expect(trigger).toBeInTheDocument();
|
|
33
33
|
|
|
34
|
-
// Content should not be in the document
|
|
35
34
|
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
|
36
35
|
});
|
|
37
36
|
|
|
38
37
|
it("[3C.15-02] [P1] opens the sheet when trigger is clicked", async () => {
|
|
39
|
-
|
|
38
|
+
|
|
40
39
|
const user = userEvent.setup();
|
|
41
40
|
render(
|
|
42
41
|
<Sheet>
|
|
@@ -51,10 +50,9 @@ describe("Sheet", () => {
|
|
|
51
50
|
);
|
|
52
51
|
|
|
53
52
|
const trigger = screen.getByRole("button", { name: "Open Sheet" });
|
|
54
|
-
|
|
53
|
+
|
|
55
54
|
await user.click(trigger);
|
|
56
55
|
|
|
57
|
-
// Then
|
|
58
56
|
await waitFor(() => {
|
|
59
57
|
const sheet = screen.getByRole("dialog");
|
|
60
58
|
expect(sheet).toBeInTheDocument();
|
|
@@ -64,7 +62,7 @@ describe("Sheet", () => {
|
|
|
64
62
|
});
|
|
65
63
|
|
|
66
64
|
it("[3C.15-03] [P1] closes the sheet when default Close button is clicked", async () => {
|
|
67
|
-
|
|
65
|
+
|
|
68
66
|
const user = userEvent.setup();
|
|
69
67
|
render(
|
|
70
68
|
<Sheet>
|
|
@@ -75,16 +73,12 @@ describe("Sheet", () => {
|
|
|
75
73
|
</Sheet>,
|
|
76
74
|
);
|
|
77
75
|
|
|
78
|
-
// Open
|
|
79
|
-
// When
|
|
80
76
|
await user.click(screen.getByRole("button", { name: "Open Sheet" }));
|
|
81
77
|
|
|
82
|
-
// Then
|
|
83
78
|
await waitFor(() => {
|
|
84
79
|
expect(screen.getByRole("dialog")).toBeInTheDocument();
|
|
85
80
|
});
|
|
86
81
|
|
|
87
|
-
// Close via default 'X' button
|
|
88
82
|
const closeBtn = screen.getByRole("button", { name: "Close" });
|
|
89
83
|
await user.click(closeBtn);
|
|
90
84
|
|
|
@@ -94,7 +88,7 @@ describe("Sheet", () => {
|
|
|
94
88
|
});
|
|
95
89
|
|
|
96
90
|
it("[3C.15-04] [P1] closes the sheet when Escape is pressed", async () => {
|
|
97
|
-
|
|
91
|
+
|
|
98
92
|
const user = userEvent.setup();
|
|
99
93
|
render(
|
|
100
94
|
<Sheet>
|
|
@@ -105,10 +99,8 @@ describe("Sheet", () => {
|
|
|
105
99
|
</Sheet>,
|
|
106
100
|
);
|
|
107
101
|
|
|
108
|
-
// When
|
|
109
102
|
await user.click(screen.getByRole("button", { name: "Open Sheet" }));
|
|
110
103
|
|
|
111
|
-
// Then
|
|
112
104
|
await waitFor(() => {
|
|
113
105
|
expect(screen.getByRole("dialog")).toBeInTheDocument();
|
|
114
106
|
});
|
|
@@ -28,8 +28,6 @@ function SheetClose({ ...props }: SheetPrimitive.Close.Props) {
|
|
|
28
28
|
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
31
|
function SheetOverlay({ className, ...props }: SheetPrimitive.Backdrop.Props) {
|
|
34
32
|
return (
|
|
35
33
|
<SheetPrimitive.Backdrop
|