@robomous/ui-core 0.1.1 → 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.
- package/README.md +15 -7
- package/dist/{primitives → components}/alert.js +1 -1
- package/dist/{primitives → components}/badge.js +1 -1
- package/dist/{primitives → components}/button.d.ts +1 -1
- package/dist/{primitives → components}/button.js +4 -1
- package/dist/{primitives → components}/card.d.ts +1 -1
- package/dist/{primitives → components}/card.js +2 -2
- package/dist/{primitives → components}/combobox.js +3 -3
- package/dist/{primitives → components}/dialog.js +1 -1
- package/dist/{primitives → components}/dropdown-menu.js +11 -6
- package/dist/{primitives → components}/field.js +2 -4
- package/dist/{primitives → components}/input-group.js +1 -1
- package/dist/{primitives → components}/input.js +1 -1
- package/dist/{primitives → components}/label.js +1 -1
- package/dist/components/progress.js +7 -0
- package/dist/{primitives → components}/select.d.ts +3 -1
- package/dist/{primitives → components}/select.js +6 -4
- package/dist/{primitives → components}/separator.js +1 -1
- package/dist/{primitives → components}/sheet.js +1 -1
- package/dist/{primitives → components}/skeleton.js +1 -1
- package/dist/{primitives → components}/sonner.js +17 -12
- package/dist/{primitives → components}/table.d.ts +1 -1
- package/dist/{primitives → components}/table.js +3 -3
- package/dist/{primitives → components}/tabs.js +1 -1
- package/dist/{primitives → components}/textarea.js +1 -1
- package/dist/{primitives → components}/tooltip.js +1 -1
- package/dist/gates/index.d.ts +28 -0
- package/dist/gates/index.js +142 -0
- package/dist/index.d.ts +31 -32
- package/dist/index.js +32 -33
- package/dist/{tokens.d.ts → theme/tokens.d.ts} +5 -5
- package/dist/{tokens.js → theme/tokens.js} +12 -12
- package/package.json +23 -12
- package/{shadcn → src/components}/alert.tsx +14 -21
- package/src/{primitives → components}/badge.tsx +15 -21
- package/src/{primitives → components}/button.tsx +13 -10
- package/{shadcn → src/components}/card.tsx +16 -31
- package/src/{primitives → components}/combobox.test.tsx +15 -2
- package/src/{primitives → components}/combobox.tsx +45 -74
- package/src/{primitives/primitives.test.tsx → components/components.test.tsx} +106 -69
- package/src/{primitives → components}/dialog.tsx +31 -54
- package/src/{primitives → components}/dropdown-menu.tsx +51 -65
- package/src/{primitives → components}/field.tsx +48 -62
- package/src/{primitives → components}/input-group.tsx +37 -49
- package/{shadcn → src/components}/input.tsx +5 -5
- package/{shadcn → src/components}/label.tsx +7 -10
- package/src/{primitives → components}/progress.tsx +7 -6
- package/{shadcn → src/components}/select.tsx +39 -41
- package/{shadcn → src/components}/separator.tsx +7 -7
- package/src/{primitives → components}/sheet.tsx +27 -44
- package/{shadcn → src/components}/skeleton.tsx +3 -3
- package/src/{primitives → components}/sonner.test.tsx +12 -4
- package/src/components/sonner.tsx +67 -0
- package/{shadcn → src/components}/table.tsx +17 -44
- package/{shadcn → src/components}/tabs.tsx +16 -26
- package/{shadcn → src/components}/textarea.tsx +5 -5
- package/{shadcn → src/components}/tooltip.tsx +12 -16
- package/src/gates/design.test.ts +188 -0
- package/src/gates/index.test.ts +11 -0
- package/src/gates/index.ts +165 -0
- package/src/gates/tokens.test.ts +215 -0
- package/src/harness.test.tsx +1 -1
- package/src/index.ts +63 -33
- package/src/theme/imports.test.ts +160 -0
- package/src/{statusTone.test.ts → theme/statusTone.test.ts} +5 -1
- package/src/{styles.css → theme/styles.css} +45 -31
- package/src/theme/tailwind.css +646 -0
- package/src/{tokens.test.ts → theme/tokens.test.ts} +30 -23
- package/src/{tokens.ts → theme/tokens.ts} +12 -12
- package/components.json +0 -13
- package/dist/lib/button.d.ts +0 -1
- package/dist/lib/button.js +0 -4
- package/dist/lib/cn.d.ts +0 -18
- package/dist/lib/cn.js +0 -21
- package/dist/lib/menu.d.ts +0 -1
- package/dist/lib/menu.js +0 -16
- package/dist/lib/progress.d.ts +0 -3
- package/dist/lib/progress.js +0 -6
- package/dist/lib/select.d.ts +0 -1
- package/dist/lib/select.js +0 -3
- package/dist/primitives/progress.js +0 -7
- package/gates/canonical.test.mjs +0 -71
- package/gates/extensions.test.mjs +0 -373
- package/gates/index.d.mts +0 -51
- package/gates/index.mjs +0 -514
- package/gates/tokens.test.mjs +0 -276
- package/shadcn/README.md +0 -13
- package/shadcn/badge.tsx +0 -49
- package/shadcn/button.tsx +0 -67
- package/shadcn/combobox.tsx +0 -299
- package/shadcn/dialog.tsx +0 -166
- package/shadcn/dropdown-menu.tsx +0 -267
- package/shadcn/field.tsx +0 -236
- package/shadcn/input-group.tsx +0 -154
- package/shadcn/progress.tsx +0 -29
- package/shadcn/sheet.tsx +0 -145
- package/shadcn/sonner.tsx +0 -49
- package/src/lib/button.ts +0 -4
- package/src/lib/cn.test.ts +0 -33
- package/src/lib/cn.ts +0 -23
- package/src/lib/menu.ts +0 -16
- package/src/lib/progress.ts +0 -6
- package/src/lib/select.ts +0 -4
- package/src/primitives/alert.tsx +0 -76
- package/src/primitives/card.tsx +0 -103
- package/src/primitives/input.tsx +0 -19
- package/src/primitives/label.tsx +0 -22
- package/src/primitives/select.tsx +0 -192
- package/src/primitives/separator.tsx +0 -28
- package/src/primitives/skeleton.tsx +0 -13
- package/src/primitives/sonner.tsx +0 -65
- package/src/primitives/table.tsx +0 -114
- package/src/primitives/tabs.tsx +0 -88
- package/src/primitives/textarea.tsx +0 -18
- package/src/primitives/tooltip.tsx +0 -57
- /package/dist/{primitives → components}/alert.d.ts +0 -0
- /package/dist/{primitives → components}/badge.d.ts +0 -0
- /package/dist/{primitives → components}/combobox.d.ts +0 -0
- /package/dist/{primitives → components}/dialog.d.ts +0 -0
- /package/dist/{primitives → components}/dropdown-menu.d.ts +0 -0
- /package/dist/{primitives → components}/field.d.ts +0 -0
- /package/dist/{primitives → components}/input-group.d.ts +0 -0
- /package/dist/{primitives → components}/input.d.ts +0 -0
- /package/dist/{primitives → components}/label.d.ts +0 -0
- /package/dist/{primitives → components}/progress.d.ts +0 -0
- /package/dist/{primitives → components}/separator.d.ts +0 -0
- /package/dist/{primitives → components}/sheet.d.ts +0 -0
- /package/dist/{primitives → components}/skeleton.d.ts +0 -0
- /package/dist/{primitives → components}/sonner.d.ts +0 -0
- /package/dist/{primitives → components}/tabs.d.ts +0 -0
- /package/dist/{primitives → components}/textarea.d.ts +0 -0
- /package/dist/{primitives → components}/tooltip.d.ts +0 -0
- /package/dist/{statusTone.d.ts → theme/statusTone.d.ts} +0 -0
- /package/dist/{statusTone.js → theme/statusTone.js} +0 -0
- /package/src/{statusTone.ts → theme/statusTone.ts} +0 -0
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { render, screen } from "@testing-library/react";
|
|
2
2
|
import userEvent from "@testing-library/user-event";
|
|
3
3
|
import { describe, expect, it } from "vitest";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
Combobox,
|
|
6
|
+
ComboboxContent,
|
|
7
|
+
ComboboxEmpty,
|
|
8
|
+
ComboboxInput,
|
|
9
|
+
ComboboxItem,
|
|
10
|
+
ComboboxList,
|
|
11
|
+
} from "./combobox";
|
|
5
12
|
|
|
6
13
|
const FRUIT = ["apple", "banana", "cherry"];
|
|
7
14
|
|
|
@@ -11,7 +18,13 @@ function Demo() {
|
|
|
11
18
|
<ComboboxInput placeholder="Pick" aria-label="Fruit" />
|
|
12
19
|
<ComboboxContent>
|
|
13
20
|
<ComboboxEmpty>No matches</ComboboxEmpty>
|
|
14
|
-
<ComboboxList>
|
|
21
|
+
<ComboboxList>
|
|
22
|
+
{(item: string) => (
|
|
23
|
+
<ComboboxItem key={item} value={item}>
|
|
24
|
+
{item}
|
|
25
|
+
</ComboboxItem>
|
|
26
|
+
)}
|
|
27
|
+
</ComboboxList>
|
|
15
28
|
</ComboboxContent>
|
|
16
29
|
</Combobox>
|
|
17
30
|
);
|
|
@@ -1,29 +1,20 @@
|
|
|
1
|
-
"use client"
|
|
1
|
+
"use client";
|
|
2
2
|
|
|
3
|
-
import * as React from "react"
|
|
4
|
-
import { Combobox as ComboboxPrimitive } from "@base-ui/react"
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Combobox as ComboboxPrimitive } from "@base-ui/react";
|
|
5
5
|
|
|
6
|
-
import { cn } from "
|
|
7
|
-
import { Button } from "./button.js"
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
InputGroupAddon,
|
|
11
|
-
InputGroupButton,
|
|
12
|
-
InputGroupInput,
|
|
13
|
-
} from "./input-group.js"
|
|
14
|
-
import { ChevronDownIcon, XIcon, CheckIcon } from "lucide-react"
|
|
6
|
+
import { cn } from "cn";
|
|
7
|
+
import { Button } from "./button.js";
|
|
8
|
+
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "./input-group.js";
|
|
9
|
+
import { ChevronDownIcon, XIcon, CheckIcon } from "lucide-react";
|
|
15
10
|
|
|
16
|
-
const Combobox = ComboboxPrimitive.Root
|
|
11
|
+
const Combobox = ComboboxPrimitive.Root;
|
|
17
12
|
|
|
18
13
|
function ComboboxValue({ ...props }: ComboboxPrimitive.Value.Props) {
|
|
19
|
-
return <ComboboxPrimitive.Value data-slot="combobox-value" {...props}
|
|
14
|
+
return <ComboboxPrimitive.Value data-slot="combobox-value" {...props} />;
|
|
20
15
|
}
|
|
21
16
|
|
|
22
|
-
function ComboboxTrigger({
|
|
23
|
-
className,
|
|
24
|
-
children,
|
|
25
|
-
...props
|
|
26
|
-
}: ComboboxPrimitive.Trigger.Props) {
|
|
17
|
+
function ComboboxTrigger({ className, children, ...props }: ComboboxPrimitive.Trigger.Props) {
|
|
27
18
|
return (
|
|
28
19
|
<ComboboxPrimitive.Trigger
|
|
29
20
|
data-slot="combobox-trigger"
|
|
@@ -33,7 +24,7 @@ function ComboboxTrigger({
|
|
|
33
24
|
{children}
|
|
34
25
|
<ChevronDownIcon className="pointer-events-none size-4 text-muted-foreground" />
|
|
35
26
|
</ComboboxPrimitive.Trigger>
|
|
36
|
-
)
|
|
27
|
+
);
|
|
37
28
|
}
|
|
38
29
|
|
|
39
30
|
function ComboboxClear({ className, ...props }: ComboboxPrimitive.Clear.Props) {
|
|
@@ -46,7 +37,7 @@ function ComboboxClear({ className, ...props }: ComboboxPrimitive.Clear.Props) {
|
|
|
46
37
|
>
|
|
47
38
|
<XIcon className="pointer-events-none" />
|
|
48
39
|
</ComboboxPrimitive.Clear>
|
|
49
|
-
)
|
|
40
|
+
);
|
|
50
41
|
}
|
|
51
42
|
|
|
52
43
|
function ComboboxInput({
|
|
@@ -57,15 +48,12 @@ function ComboboxInput({
|
|
|
57
48
|
showClear = false,
|
|
58
49
|
...props
|
|
59
50
|
}: ComboboxPrimitive.Input.Props & {
|
|
60
|
-
showTrigger?: boolean
|
|
61
|
-
showClear?: boolean
|
|
51
|
+
showTrigger?: boolean;
|
|
52
|
+
showClear?: boolean;
|
|
62
53
|
}) {
|
|
63
54
|
return (
|
|
64
55
|
<InputGroup className={cn("w-auto", className)}>
|
|
65
|
-
<ComboboxPrimitive.Input
|
|
66
|
-
render={<InputGroupInput disabled={disabled} />}
|
|
67
|
-
{...props}
|
|
68
|
-
/>
|
|
56
|
+
<ComboboxPrimitive.Input render={<InputGroupInput disabled={disabled} />} {...props} />
|
|
69
57
|
<InputGroupAddon align="inline-end">
|
|
70
58
|
{showTrigger && (
|
|
71
59
|
<InputGroupButton
|
|
@@ -83,7 +71,7 @@ function ComboboxInput({
|
|
|
83
71
|
</InputGroupAddon>
|
|
84
72
|
{children}
|
|
85
73
|
</InputGroup>
|
|
86
|
-
)
|
|
74
|
+
);
|
|
87
75
|
}
|
|
88
76
|
|
|
89
77
|
function ComboboxContent({
|
|
@@ -112,12 +100,15 @@ function ComboboxContent({
|
|
|
112
100
|
<ComboboxPrimitive.Popup
|
|
113
101
|
data-slot="combobox-content"
|
|
114
102
|
data-chips={!!anchor}
|
|
115
|
-
className={cn(
|
|
103
|
+
className={cn(
|
|
104
|
+
"group/combobox-content relative max-h-(--available-height) w-(--anchor-width) max-w-(--available-width) min-w-[calc(var(--anchor-width)+--spacing(7))] origin-(--transform-origin) overflow-hidden rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[chips=true]:min-w-(--anchor-width) 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-[slot=input-group]:m-1 *:data-[slot=input-group]:mb-0 *:data-[slot=input-group]:h-8 *:data-[slot=input-group]:border-input/30 *:data-[slot=input-group]:bg-input/30 *:data-[slot=input-group]:shadow-none 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",
|
|
105
|
+
className,
|
|
106
|
+
)}
|
|
116
107
|
{...props}
|
|
117
108
|
/>
|
|
118
109
|
</ComboboxPrimitive.Positioner>
|
|
119
110
|
</ComboboxPrimitive.Portal>
|
|
120
|
-
)
|
|
111
|
+
);
|
|
121
112
|
}
|
|
122
113
|
|
|
123
114
|
function ComboboxList({ className, ...props }: ComboboxPrimitive.List.Props) {
|
|
@@ -126,24 +117,20 @@ function ComboboxList({ className, ...props }: ComboboxPrimitive.List.Props) {
|
|
|
126
117
|
data-slot="combobox-list"
|
|
127
118
|
className={cn(
|
|
128
119
|
"no-scrollbar max-h-[min(calc(--spacing(72)---spacing(9)),calc(var(--available-height)---spacing(9)))] scroll-py-1 overflow-y-auto overscroll-contain p-1 data-empty:p-0",
|
|
129
|
-
className
|
|
120
|
+
className,
|
|
130
121
|
)}
|
|
131
122
|
{...props}
|
|
132
123
|
/>
|
|
133
|
-
)
|
|
124
|
+
);
|
|
134
125
|
}
|
|
135
126
|
|
|
136
|
-
function ComboboxItem({
|
|
137
|
-
className,
|
|
138
|
-
children,
|
|
139
|
-
...props
|
|
140
|
-
}: ComboboxPrimitive.Item.Props) {
|
|
127
|
+
function ComboboxItem({ className, children, ...props }: ComboboxPrimitive.Item.Props) {
|
|
141
128
|
return (
|
|
142
129
|
<ComboboxPrimitive.Item
|
|
143
130
|
data-slot="combobox-item"
|
|
144
131
|
className={cn(
|
|
145
132
|
"relative flex w-full cursor-default items-center gap-2 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none data-highlighted:bg-accent data-highlighted:text-accent-foreground not-data-[variant=destructive]:data-highlighted:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
146
|
-
className
|
|
133
|
+
className,
|
|
147
134
|
)}
|
|
148
135
|
{...props}
|
|
149
136
|
>
|
|
@@ -156,36 +143,27 @@ function ComboboxItem({
|
|
|
156
143
|
<CheckIcon className="pointer-events-none" />
|
|
157
144
|
</ComboboxPrimitive.ItemIndicator>
|
|
158
145
|
</ComboboxPrimitive.Item>
|
|
159
|
-
)
|
|
146
|
+
);
|
|
160
147
|
}
|
|
161
148
|
|
|
162
149
|
function ComboboxGroup({ className, ...props }: ComboboxPrimitive.Group.Props) {
|
|
163
150
|
return (
|
|
164
|
-
<ComboboxPrimitive.Group
|
|
165
|
-
|
|
166
|
-
className={cn(className)}
|
|
167
|
-
{...props}
|
|
168
|
-
/>
|
|
169
|
-
)
|
|
151
|
+
<ComboboxPrimitive.Group data-slot="combobox-group" className={cn(className)} {...props} />
|
|
152
|
+
);
|
|
170
153
|
}
|
|
171
154
|
|
|
172
|
-
function ComboboxLabel({
|
|
173
|
-
className,
|
|
174
|
-
...props
|
|
175
|
-
}: ComboboxPrimitive.GroupLabel.Props) {
|
|
155
|
+
function ComboboxLabel({ className, ...props }: ComboboxPrimitive.GroupLabel.Props) {
|
|
176
156
|
return (
|
|
177
157
|
<ComboboxPrimitive.GroupLabel
|
|
178
158
|
data-slot="combobox-label"
|
|
179
159
|
className={cn("px-2 py-1.5 text-xs text-muted-foreground", className)}
|
|
180
160
|
{...props}
|
|
181
161
|
/>
|
|
182
|
-
)
|
|
162
|
+
);
|
|
183
163
|
}
|
|
184
164
|
|
|
185
165
|
function ComboboxCollection({ ...props }: ComboboxPrimitive.Collection.Props) {
|
|
186
|
-
return
|
|
187
|
-
<ComboboxPrimitive.Collection data-slot="combobox-collection" {...props} />
|
|
188
|
-
)
|
|
166
|
+
return <ComboboxPrimitive.Collection data-slot="combobox-collection" {...props} />;
|
|
189
167
|
}
|
|
190
168
|
|
|
191
169
|
function ComboboxEmpty({ className, ...props }: ComboboxPrimitive.Empty.Props) {
|
|
@@ -194,41 +172,37 @@ function ComboboxEmpty({ className, ...props }: ComboboxPrimitive.Empty.Props) {
|
|
|
194
172
|
data-slot="combobox-empty"
|
|
195
173
|
className={cn(
|
|
196
174
|
"hidden w-full justify-center py-2 text-center text-sm text-muted-foreground group-data-empty/combobox-content:flex",
|
|
197
|
-
className
|
|
175
|
+
className,
|
|
198
176
|
)}
|
|
199
177
|
{...props}
|
|
200
178
|
/>
|
|
201
|
-
)
|
|
179
|
+
);
|
|
202
180
|
}
|
|
203
181
|
|
|
204
|
-
function ComboboxSeparator({
|
|
205
|
-
className,
|
|
206
|
-
...props
|
|
207
|
-
}: ComboboxPrimitive.Separator.Props) {
|
|
182
|
+
function ComboboxSeparator({ className, ...props }: ComboboxPrimitive.Separator.Props) {
|
|
208
183
|
return (
|
|
209
184
|
<ComboboxPrimitive.Separator
|
|
210
185
|
data-slot="combobox-separator"
|
|
211
186
|
className={cn("-mx-1 my-1 h-px bg-border", className)}
|
|
212
187
|
{...props}
|
|
213
188
|
/>
|
|
214
|
-
)
|
|
189
|
+
);
|
|
215
190
|
}
|
|
216
191
|
|
|
217
192
|
function ComboboxChips({
|
|
218
193
|
className,
|
|
219
194
|
...props
|
|
220
|
-
}: React.ComponentPropsWithRef<typeof ComboboxPrimitive.Chips> &
|
|
221
|
-
ComboboxPrimitive.Chips.Props) {
|
|
195
|
+
}: React.ComponentPropsWithRef<typeof ComboboxPrimitive.Chips> & ComboboxPrimitive.Chips.Props) {
|
|
222
196
|
return (
|
|
223
197
|
<ComboboxPrimitive.Chips
|
|
224
198
|
data-slot="combobox-chips"
|
|
225
199
|
className={cn(
|
|
226
200
|
"flex min-h-8 flex-wrap items-center gap-1 rounded-lg border border-input bg-transparent bg-clip-padding px-2.5 py-1 text-sm transition-colors focus-within:border-ring focus-within:ring-3 focus-within:ring-ring/50 has-aria-invalid:border-destructive has-aria-invalid:ring-3 has-aria-invalid:ring-destructive/20 has-data-[slot=combobox-chip]:px-1 dark:bg-input/30 dark:has-aria-invalid:border-destructive/50 dark:has-aria-invalid:ring-destructive/40",
|
|
227
|
-
className
|
|
201
|
+
className,
|
|
228
202
|
)}
|
|
229
203
|
{...props}
|
|
230
204
|
/>
|
|
231
|
-
)
|
|
205
|
+
);
|
|
232
206
|
}
|
|
233
207
|
|
|
234
208
|
function ComboboxChip({
|
|
@@ -237,14 +211,14 @@ function ComboboxChip({
|
|
|
237
211
|
showRemove = true,
|
|
238
212
|
...props
|
|
239
213
|
}: ComboboxPrimitive.Chip.Props & {
|
|
240
|
-
showRemove?: boolean
|
|
214
|
+
showRemove?: boolean;
|
|
241
215
|
}) {
|
|
242
216
|
return (
|
|
243
217
|
<ComboboxPrimitive.Chip
|
|
244
218
|
data-slot="combobox-chip"
|
|
245
219
|
className={cn(
|
|
246
220
|
"flex h-[calc(--spacing(5.25))] w-fit items-center justify-center gap-1 rounded-sm bg-muted px-1.5 text-xs font-medium whitespace-nowrap text-foreground has-disabled:pointer-events-none has-disabled:cursor-not-allowed has-disabled:opacity-50 has-data-[slot=combobox-chip-remove]:pr-0",
|
|
247
|
-
className
|
|
221
|
+
className,
|
|
248
222
|
)}
|
|
249
223
|
{...props}
|
|
250
224
|
>
|
|
@@ -259,24 +233,21 @@ function ComboboxChip({
|
|
|
259
233
|
</ComboboxPrimitive.ChipRemove>
|
|
260
234
|
)}
|
|
261
235
|
</ComboboxPrimitive.Chip>
|
|
262
|
-
)
|
|
236
|
+
);
|
|
263
237
|
}
|
|
264
238
|
|
|
265
|
-
function ComboboxChipsInput({
|
|
266
|
-
className,
|
|
267
|
-
...props
|
|
268
|
-
}: ComboboxPrimitive.Input.Props) {
|
|
239
|
+
function ComboboxChipsInput({ className, ...props }: ComboboxPrimitive.Input.Props) {
|
|
269
240
|
return (
|
|
270
241
|
<ComboboxPrimitive.Input
|
|
271
242
|
data-slot="combobox-chip-input"
|
|
272
243
|
className={cn("min-w-16 flex-1 outline-none", className)}
|
|
273
244
|
{...props}
|
|
274
245
|
/>
|
|
275
|
-
)
|
|
246
|
+
);
|
|
276
247
|
}
|
|
277
248
|
|
|
278
249
|
function useComboboxAnchor() {
|
|
279
|
-
return React.useRef<HTMLDivElement | null>(null)
|
|
250
|
+
return React.useRef<HTMLDivElement | null>(null);
|
|
280
251
|
}
|
|
281
252
|
|
|
282
253
|
export {
|
|
@@ -296,4 +267,4 @@ export {
|
|
|
296
267
|
ComboboxTrigger,
|
|
297
268
|
ComboboxValue,
|
|
298
269
|
useComboboxAnchor,
|
|
299
|
-
}
|
|
270
|
+
};
|
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The component harness, proved on the
|
|
2
|
+
* The component harness, proved on the components that carry a decision.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* whatever it
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* Deliberately not an echo of every class string: pinning the design system to
|
|
5
|
+
* whatever it looked like on the day is the mistake this repository just spent
|
|
6
|
+
* a restructure undoing, and a restyle is coming. What is asserted here is the
|
|
7
|
+
* handful of behaviours a screen would silently lose — the merge that makes
|
|
8
|
+
* `className` a real override, the `asChild` that keeps a link a link, the role
|
|
9
|
+
* an error is announced with, and the value a `Progress` reports without being
|
|
10
|
+
* asked.
|
|
10
11
|
*
|
|
11
|
-
* The button
|
|
12
|
-
* submitting a form; that is a call-site property
|
|
13
|
-
* `tests/scripts/form_buttons.test.mjs` is what holds it.
|
|
14
|
-
*
|
|
15
|
-
* This file is also the reason the jsdom harness exists at all: standing the
|
|
16
|
-
* environment up once here is cheaper than the first screen that needs it doing so
|
|
17
|
-
* under deadline.
|
|
12
|
+
* The button does not default `type`, so nothing here stops a "Cancel"
|
|
13
|
+
* submitting a form; that is a call-site property, gated in the consumer.
|
|
18
14
|
*/
|
|
19
15
|
|
|
20
16
|
import { render, screen } from "@testing-library/react";
|
|
@@ -22,10 +18,6 @@ import userEvent from "@testing-library/user-event";
|
|
|
22
18
|
import type { JSX } from "react";
|
|
23
19
|
import { describe, expect, it } from "vitest";
|
|
24
20
|
|
|
25
|
-
import { inlineLink } from "../lib/button";
|
|
26
|
-
import { menuSurface } from "../lib/menu";
|
|
27
|
-
import { progressAria } from "../lib/progress";
|
|
28
|
-
import { twoLineTrigger } from "../lib/select";
|
|
29
21
|
import { Alert, AlertDescription, AlertTitle } from "./alert";
|
|
30
22
|
import { Badge } from "./badge";
|
|
31
23
|
import { Button } from "./button";
|
|
@@ -41,13 +33,7 @@ import { Progress } from "./progress";
|
|
|
41
33
|
import { FieldError } from "./field";
|
|
42
34
|
import { Input } from "./input";
|
|
43
35
|
import { Label } from "./label";
|
|
44
|
-
import {
|
|
45
|
-
Select,
|
|
46
|
-
SelectContent,
|
|
47
|
-
SelectItem,
|
|
48
|
-
SelectTrigger,
|
|
49
|
-
SelectValue,
|
|
50
|
-
} from "./select";
|
|
36
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./select";
|
|
51
37
|
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "./sheet";
|
|
52
38
|
import { Table, TableBody, TableHead, TableHeader, TableRow } from "./table";
|
|
53
39
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "./tabs";
|
|
@@ -59,14 +45,14 @@ describe("Button", () => {
|
|
|
59
45
|
});
|
|
60
46
|
|
|
61
47
|
it("lets a caller override a conflicting utility rather than emitting both", () => {
|
|
62
|
-
// Without `tailwind-merge` both `px-
|
|
48
|
+
// Without `tailwind-merge` both `px-2.5` and `px-6` survive and which one wins is
|
|
63
49
|
// decided by the order Tailwind wrote them into the stylesheet — a rule nobody
|
|
64
50
|
// can see from the call site. This is what makes `className` an extension
|
|
65
51
|
// point rather than a suggestion.
|
|
66
52
|
render(<Button className="px-6">Wide</Button>);
|
|
67
53
|
const className = screen.getByRole("button", { name: "Wide" }).className;
|
|
68
54
|
expect(className).toContain("px-6");
|
|
69
|
-
expect(className).not.toContain("px-
|
|
55
|
+
expect(className).not.toContain("px-2.5");
|
|
70
56
|
});
|
|
71
57
|
|
|
72
58
|
it("renders the child element with asChild, so a link stays a link", () => {
|
|
@@ -95,21 +81,31 @@ describe("Button", () => {
|
|
|
95
81
|
expect(classes).not.toMatch(/(^|\s)underline(\s|$)/);
|
|
96
82
|
});
|
|
97
83
|
|
|
98
|
-
it("
|
|
84
|
+
it("lets a caller's className outrank the inline size", () => {
|
|
99
85
|
render(
|
|
100
|
-
<Button variant="link" className=
|
|
86
|
+
<Button variant="link" size="inline" className="h-8">
|
|
101
87
|
More
|
|
102
88
|
</Button>,
|
|
103
89
|
);
|
|
104
90
|
|
|
105
|
-
// `
|
|
106
|
-
//
|
|
107
|
-
//
|
|
91
|
+
// `h-auto` and `h-8` are the same utility group, so tailwind-merge has to
|
|
92
|
+
// drop the one the variant supplied. Without the merge both survive and
|
|
93
|
+
// which wins is decided by stylesheet order — a rule the call site cannot see.
|
|
108
94
|
const classes = screen.getByRole("button").className.split(" ");
|
|
109
|
-
expect(classes).toContain("h-
|
|
110
|
-
expect(classes).toContain("
|
|
111
|
-
|
|
112
|
-
|
|
95
|
+
expect(classes).toContain("h-8");
|
|
96
|
+
expect(classes).not.toContain("h-auto");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("has an inline size that keeps a link button inside a sentence", () => {
|
|
100
|
+
render(
|
|
101
|
+
<Button variant="link" size="inline">
|
|
102
|
+
Read the docs
|
|
103
|
+
</Button>,
|
|
104
|
+
);
|
|
105
|
+
const button = screen.getByRole("button", { name: "Read the docs" });
|
|
106
|
+
expect(button.getAttribute("data-size")).toBe("inline");
|
|
107
|
+
expect(button.className).toContain("h-auto");
|
|
108
|
+
expect(button.className).toContain("p-0");
|
|
113
109
|
});
|
|
114
110
|
});
|
|
115
111
|
|
|
@@ -132,13 +128,16 @@ describe("Alert and Badge", () => {
|
|
|
132
128
|
expect(screen.getByText("done").getAttribute("data-slot")).toBe("badge");
|
|
133
129
|
});
|
|
134
130
|
|
|
135
|
-
it.each(["success", "warning", "info", "quiet"] as const)(
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
131
|
+
it.each(["success", "warning", "info", "quiet"] as const)(
|
|
132
|
+
"%s is a Badge variant keyed on data",
|
|
133
|
+
(variant) => {
|
|
134
|
+
render(<Badge variant={variant}>x</Badge>);
|
|
135
|
+
const el = screen.getByText("x");
|
|
136
|
+
expect(el.getAttribute("data-variant")).toBe(variant);
|
|
137
|
+
expect(el.className).toContain("border-transparent");
|
|
138
|
+
expect(el.className).not.toMatch(/border-(emerald|amber|sky)/);
|
|
139
|
+
},
|
|
140
|
+
);
|
|
142
141
|
});
|
|
143
142
|
|
|
144
143
|
describe("fields", () => {
|
|
@@ -159,9 +158,10 @@ describe("fields", () => {
|
|
|
159
158
|
});
|
|
160
159
|
|
|
161
160
|
/**
|
|
162
|
-
* The two-line option —
|
|
163
|
-
* which is why what is asserted here
|
|
164
|
-
*
|
|
161
|
+
* The two-line option — a `multiline` prop on the trigger itself rather than a
|
|
162
|
+
* class string composed at the call site, which is why what is asserted here
|
|
163
|
+
* is the trigger's own behaviour and not a layout this file would otherwise
|
|
164
|
+
* have to keep in step with a call site.
|
|
165
165
|
*
|
|
166
166
|
* The claim worth a test is the one that is easy to lose: the trigger shows the
|
|
167
167
|
* *same* two lines the list does, because Radix renders the selected item's own
|
|
@@ -172,7 +172,7 @@ describe("Select", () => {
|
|
|
172
172
|
function pickOne(): JSX.Element {
|
|
173
173
|
return (
|
|
174
174
|
<Select defaultValue="a">
|
|
175
|
-
<SelectTrigger data-testid="model"
|
|
175
|
+
<SelectTrigger data-testid="model" multiline>
|
|
176
176
|
<SelectValue />
|
|
177
177
|
</SelectTrigger>
|
|
178
178
|
<SelectContent>
|
|
@@ -202,15 +202,16 @@ describe("Select", () => {
|
|
|
202
202
|
|
|
203
203
|
it("grows rather than clipping, and leaves a one-line option where it was", () => {
|
|
204
204
|
render(pickOne());
|
|
205
|
-
// `h-8` would fix the height and squash the second line; `min-h-8` keeps
|
|
206
|
-
// one-line control
|
|
205
|
+
// `h-8` would fix the height and squash the second line; `min-h-8` keeps a
|
|
206
|
+
// one-line control at the trigger's usual height and lets a two-line one grow.
|
|
207
207
|
const trigger = screen.getByTestId("model");
|
|
208
208
|
expect(trigger.className).toContain("min-h-8");
|
|
209
209
|
// Nothing truncates: half a model id is not a model id.
|
|
210
210
|
expect(trigger.className).not.toContain("truncate");
|
|
211
|
-
// `
|
|
212
|
-
//
|
|
213
|
-
//
|
|
211
|
+
// `multiline` is a prop, not a class string arriving from outside, so it
|
|
212
|
+
// does not need to outrank anything — but `cn`'s merge still has to replace
|
|
213
|
+
// the fixed height and the value clamp rather than stack beside them, which
|
|
214
|
+
// is what this checks at render, not an echo of the prop's own classes.
|
|
214
215
|
expect(trigger.className).toContain("data-[size=default]:h-auto");
|
|
215
216
|
expect(trigger.className).toContain("line-clamp-none");
|
|
216
217
|
expect(trigger.className).not.toContain("data-[size=default]:h-8");
|
|
@@ -226,6 +227,21 @@ describe("Select", () => {
|
|
|
226
227
|
expect(className).toContain("w-full");
|
|
227
228
|
expect(className).toContain("min-w-(--radix-select-trigger-width)");
|
|
228
229
|
});
|
|
230
|
+
|
|
231
|
+
it("grows and stops clamping its value when multiline", () => {
|
|
232
|
+
render(
|
|
233
|
+
<Select>
|
|
234
|
+
<SelectTrigger multiline aria-label="Export target">
|
|
235
|
+
<SelectValue placeholder="Pick one" />
|
|
236
|
+
</SelectTrigger>
|
|
237
|
+
</Select>,
|
|
238
|
+
);
|
|
239
|
+
const trigger = screen.getByRole("combobox", { name: "Export target" });
|
|
240
|
+
expect(trigger.className).toContain("data-[size=default]:h-auto");
|
|
241
|
+
expect(trigger.className).not.toContain("data-[size=default]:h-8");
|
|
242
|
+
expect(trigger.className).toContain("line-clamp-none");
|
|
243
|
+
expect(trigger.className).not.toContain("line-clamp-1");
|
|
244
|
+
});
|
|
229
245
|
});
|
|
230
246
|
|
|
231
247
|
describe("Card and Table", () => {
|
|
@@ -323,18 +339,15 @@ describe("Tabs", () => {
|
|
|
323
339
|
});
|
|
324
340
|
|
|
325
341
|
describe("Progress", () => {
|
|
326
|
-
it("reports its value to assistive technology
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
expect(screen.getByRole("progressbar", { name: "Ingest" }).getAttribute("aria-valuenow")).toBe(
|
|
332
|
-
"42",
|
|
333
|
-
);
|
|
342
|
+
it("reports its value to assistive technology without help from the caller", () => {
|
|
343
|
+
render(<Progress value={42} aria-label="Ingest" />);
|
|
344
|
+
const bar = screen.getByRole("progressbar", { name: "Ingest" });
|
|
345
|
+
expect(bar.getAttribute("aria-valuenow")).toBe("42");
|
|
346
|
+
expect(bar.getAttribute("data-state")).not.toBe("indeterminate");
|
|
334
347
|
});
|
|
335
348
|
|
|
336
349
|
it("fills with the functional colour, and carries the data-slot a caller can restyle from", () => {
|
|
337
|
-
//
|
|
350
|
+
// `Progress` has no `variant` prop and no notion of status — a
|
|
338
351
|
// batch's completion is an amount, not a polarity, so the indicator is
|
|
339
352
|
// always `bg-primary`. A caller who needs to reach it targets the
|
|
340
353
|
// `data-slot` it renders with, from its own `className` on `Root`.
|
|
@@ -402,7 +415,16 @@ describe("Dialog", () => {
|
|
|
402
415
|
|
|
403
416
|
describe("sheet", () => {
|
|
404
417
|
it("renders a dialog with its title and description wired by Radix", async () => {
|
|
405
|
-
render(
|
|
418
|
+
render(
|
|
419
|
+
<Sheet open>
|
|
420
|
+
<SheetContent>
|
|
421
|
+
<SheetHeader>
|
|
422
|
+
<SheetTitle>Filters</SheetTitle>
|
|
423
|
+
<SheetDescription>Narrow the list</SheetDescription>
|
|
424
|
+
</SheetHeader>
|
|
425
|
+
</SheetContent>
|
|
426
|
+
</Sheet>,
|
|
427
|
+
);
|
|
406
428
|
const dialog = await screen.findByRole("dialog");
|
|
407
429
|
expect(dialog.getAttribute("data-slot")).toBe("sheet-content");
|
|
408
430
|
expect(dialog.getAttribute("data-side")).toBe("right");
|
|
@@ -418,23 +440,38 @@ describe("DropdownMenu", () => {
|
|
|
418
440
|
<DropdownMenuTrigger asChild>
|
|
419
441
|
<Button variant="ghost" size="icon" aria-label="Actions" />
|
|
420
442
|
</DropdownMenuTrigger>
|
|
421
|
-
<DropdownMenuContent
|
|
443
|
+
<DropdownMenuContent>
|
|
422
444
|
<DropdownMenuItem>Check integrity of this connection</DropdownMenuItem>
|
|
423
445
|
</DropdownMenuContent>
|
|
424
446
|
</DropdownMenu>,
|
|
425
447
|
);
|
|
426
448
|
await user.click(screen.getByRole("button", { name: "Actions" }));
|
|
427
449
|
const classes = (await screen.findByRole("menu")).className.split(" ");
|
|
428
|
-
//
|
|
429
|
-
//
|
|
430
|
-
//
|
|
431
|
-
//
|
|
432
|
-
// rendered class list is the assertion: this is the merge, not a hope about
|
|
433
|
-
// cascade order.
|
|
450
|
+
// The default sizes to items, not to the trigger: behind this icon-sized
|
|
451
|
+
// button, pinning to the trigger width would be a 128px ceiling that wraps
|
|
452
|
+
// every longer item. `min-w-32` is what is asserted below, surviving as
|
|
453
|
+
// the floor it was meant to be.
|
|
434
454
|
expect(classes).toContain("w-auto");
|
|
435
455
|
expect(classes).not.toContain("w-(--radix-dropdown-menu-trigger-width)");
|
|
436
456
|
// The floor is a different group and survives, which is what keeps a
|
|
437
457
|
// one-word menu from collapsing to its widest item.
|
|
438
458
|
expect(classes).toContain("min-w-32");
|
|
439
459
|
});
|
|
460
|
+
|
|
461
|
+
it("sizes to its items and leaves on the frame it is dismissed", async () => {
|
|
462
|
+
const user = userEvent.setup();
|
|
463
|
+
render(
|
|
464
|
+
<DropdownMenu>
|
|
465
|
+
<DropdownMenuTrigger>Actions</DropdownMenuTrigger>
|
|
466
|
+
<DropdownMenuContent>
|
|
467
|
+
<DropdownMenuItem>Duplicate this model</DropdownMenuItem>
|
|
468
|
+
</DropdownMenuContent>
|
|
469
|
+
</DropdownMenu>,
|
|
470
|
+
);
|
|
471
|
+
await user.click(screen.getByRole("button", { name: "Actions" }));
|
|
472
|
+
const menu = await screen.findByRole("menu");
|
|
473
|
+
expect(menu.className).toContain("w-auto");
|
|
474
|
+
expect(menu.className).not.toContain("--radix-dropdown-menu-trigger-width");
|
|
475
|
+
expect(menu.className).not.toContain("data-closed:animate-out");
|
|
476
|
+
});
|
|
440
477
|
});
|