@pantoia/ui 0.3.0 → 0.5.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 +46 -0
- package/accordion/index.module.css +4 -4
- package/accordion/index.tsx +21 -51
- package/alert-dialog/index.tsx +36 -42
- package/badge/index.tsx +1 -1
- package/button/index.tsx +31 -30
- package/callout/index.tsx +4 -4
- package/checkbox/index.tsx +20 -85
- package/combobox/index.tsx +122 -316
- package/command-palette/index.tsx +51 -32
- package/dialog/index.tsx +28 -24
- package/dist/index.js +5 -5
- package/drawer/index.module.css +2 -2
- package/drawer/index.tsx +52 -130
- package/dropdown-menu/index.tsx +87 -95
- package/field/index.tsx +27 -86
- package/input/index.tsx +7 -3
- package/kbd/index.tsx +2 -2
- package/label/index.tsx +6 -8
- package/number-field/index.tsx +122 -173
- package/package.json +19 -11
- package/popover/index.tsx +61 -32
- package/radio-group/index.tsx +35 -38
- package/registry.json +64 -154
- package/select/index.tsx +141 -70
- package/switch/index.tsx +13 -6
- package/table/index.tsx +1 -2
- package/tabs/index.module.css +3 -3
- package/tabs/index.tsx +22 -76
- package/textarea/index.tsx +19 -7
- package/toast/index.module.css +187 -0
- package/toast/index.tsx +141 -46
- package/toggle/index.tsx +4 -3
- package/tokens.css +7 -1
- package/tooltip/index.tsx +61 -30
- package/utilities.css +11 -6
- package/accordion/AGENTS.md +0 -60
- package/alert-dialog/AGENTS.md +0 -78
- package/badge/AGENTS.md +0 -35
- package/button/AGENTS.md +0 -39
- package/callout/AGENTS.md +0 -37
- package/checkbox/AGENTS.md +0 -54
- package/combobox/AGENTS.md +0 -150
- package/command-palette/AGENTS.md +0 -41
- package/dialog/AGENTS.md +0 -81
- package/drawer/AGENTS.md +0 -66
- package/dropdown-menu/AGENTS.md +0 -133
- package/field/AGENTS.md +0 -92
- package/input/AGENTS.md +0 -35
- package/kbd/AGENTS.md +0 -17
- package/label/AGENTS.md +0 -21
- package/number-field/AGENTS.md +0 -49
- package/popover/AGENTS.md +0 -64
- package/radio-group/AGENTS.md +0 -81
- package/select/AGENTS.md +0 -90
- package/switch/AGENTS.md +0 -37
- package/table/AGENTS.md +0 -82
- package/tabs/AGENTS.md +0 -61
- package/test-utils.ts +0 -10
- package/textarea/AGENTS.md +0 -35
- package/toast/AGENTS.md +0 -34
- package/toggle/AGENTS.md +0 -37
- package/tooltip/AGENTS.md +0 -50
package/combobox/index.tsx
CHANGED
|
@@ -1,154 +1,24 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
createContext,
|
|
6
|
-
use,
|
|
7
|
-
useEffect,
|
|
8
|
-
useId,
|
|
9
|
-
useRef,
|
|
10
|
-
useState,
|
|
11
|
-
type ComponentProps,
|
|
12
|
-
type InputHTMLAttributes,
|
|
13
|
-
type KeyboardEvent as ReactKeyboardEvent,
|
|
14
|
-
type ReactNode,
|
|
15
|
-
} from 'react'
|
|
3
|
+
import { Combobox as ComboboxPrimitive, type ComboboxFilter, type ComboboxFilterOptions } from '@base-ui/react/combobox'
|
|
4
|
+
import { isValidElement, type ComponentProps, type ReactNode } from 'react'
|
|
16
5
|
|
|
17
6
|
import { tv } from '../tv'
|
|
18
7
|
|
|
19
|
-
|
|
20
|
-
id: string
|
|
21
|
-
value: string | undefined
|
|
22
|
-
open: boolean
|
|
23
|
-
active: string | undefined
|
|
24
|
-
search: string
|
|
25
|
-
setSearch: (search: string) => void
|
|
26
|
-
setNotify: (notify: ((search: string) => void) | undefined) => void
|
|
27
|
-
setOpen: (open: boolean) => void
|
|
28
|
-
pick: (value: string) => void
|
|
29
|
-
move: (step: number) => void
|
|
30
|
-
setActive: (value: string | undefined) => void
|
|
31
|
-
itemId: (value: string) => string
|
|
32
|
-
count: number
|
|
33
|
-
ignoreFilter: boolean
|
|
34
|
-
see: (visible: boolean) => () => void
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const USABLE = '[role="option"]:not([data-disabled])'
|
|
38
|
-
|
|
39
|
-
const Context = createContext<ComboboxContext | null>(null)
|
|
40
|
-
|
|
41
|
-
const useCombobox = () => {
|
|
42
|
-
const ctx = use(Context)
|
|
43
|
-
if (!ctx) throw new Error('Combobox 부품은 Combobox 안에서만 선다')
|
|
44
|
-
return ctx
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export interface ComboboxProps {
|
|
48
|
-
value?: string
|
|
49
|
-
defaultValue?: string
|
|
50
|
-
onValueChange?: (value: string) => void
|
|
51
|
-
open?: boolean
|
|
52
|
-
defaultOpen?: boolean
|
|
53
|
-
onOpenChange?: (open: boolean) => void
|
|
54
|
-
ignoreFilter?: boolean
|
|
8
|
+
export type ComboboxProps<Value = string, Item = Value> = Omit<ComboboxPrimitive.Root.Props<Value, false, Item>, 'className'> & {
|
|
55
9
|
className?: string
|
|
56
|
-
children?: ReactNode
|
|
57
10
|
}
|
|
58
11
|
|
|
59
|
-
export function Combobox({
|
|
60
|
-
|
|
61
|
-
defaultValue,
|
|
62
|
-
onValueChange,
|
|
63
|
-
open,
|
|
64
|
-
defaultOpen = false,
|
|
65
|
-
onOpenChange,
|
|
66
|
-
ignoreFilter = true,
|
|
12
|
+
export function Combobox<Value = string, Item = Value>({
|
|
13
|
+
openOnInputClick = false,
|
|
67
14
|
className,
|
|
68
15
|
children,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const [ownValue, setOwnValue] = useState(defaultValue)
|
|
72
|
-
const [ownOpen, setOwnOpen] = useState(defaultOpen)
|
|
73
|
-
const [active, setActive] = useState<string | undefined>(undefined)
|
|
74
|
-
const [search, setSearch] = useState('')
|
|
75
|
-
const [count, setCount] = useState(0)
|
|
76
|
-
const [seen, setSeen] = useState(false)
|
|
77
|
-
const notifyRef = useRef<((search: string) => void) | undefined>(undefined)
|
|
78
|
-
|
|
79
|
-
const isOpen = open ?? ownOpen
|
|
80
|
-
const current = value ?? ownValue
|
|
81
|
-
|
|
82
|
-
if (seen !== isOpen) {
|
|
83
|
-
setSeen(isOpen)
|
|
84
|
-
if (!isOpen) setActive(undefined)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const setOpen = (next: boolean) => {
|
|
88
|
-
if (open === undefined) setOwnOpen(next)
|
|
89
|
-
onOpenChange?.(next)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const setNotify = (next: ((search: string) => void) | undefined) => {
|
|
93
|
-
notifyRef.current = next
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const changeSearch = (next: string) => {
|
|
97
|
-
setSearch(next)
|
|
98
|
-
notifyRef.current?.(next)
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const pick = (next: string) => {
|
|
102
|
-
if (value === undefined) setOwnValue(next)
|
|
103
|
-
onValueChange?.(next)
|
|
104
|
-
changeSearch(next)
|
|
105
|
-
setOpen(false)
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const see = (visible: boolean) => {
|
|
109
|
-
if (!visible) return () => {}
|
|
110
|
-
setCount(n => n + 1)
|
|
111
|
-
return () => setCount(n => n - 1)
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const move = (step: number) => {
|
|
115
|
-
const list = document.getElementById(`${id}-list`)
|
|
116
|
-
if (!list) return
|
|
117
|
-
|
|
118
|
-
const usable = [...list.querySelectorAll<HTMLElement>(USABLE)].map(el => el.dataset.value!)
|
|
119
|
-
if (usable.length === 0) return
|
|
120
|
-
|
|
121
|
-
const at = active === undefined ? -1 : usable.indexOf(active)
|
|
122
|
-
const next = at === -1
|
|
123
|
-
? (step > 0 ? 0 : usable.length - 1)
|
|
124
|
-
: (at + step + usable.length) % usable.length
|
|
125
|
-
|
|
126
|
-
setActive(usable[next])
|
|
127
|
-
}
|
|
128
|
-
|
|
16
|
+
...rest
|
|
17
|
+
}: ComboboxProps<Value, Item>) {
|
|
129
18
|
return (
|
|
130
|
-
<
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
open: isOpen,
|
|
134
|
-
active,
|
|
135
|
-
search,
|
|
136
|
-
setSearch: changeSearch,
|
|
137
|
-
setNotify,
|
|
138
|
-
setOpen,
|
|
139
|
-
pick,
|
|
140
|
-
see,
|
|
141
|
-
move,
|
|
142
|
-
setActive,
|
|
143
|
-
itemId: (item: string) => `${id}-${item}`,
|
|
144
|
-
count,
|
|
145
|
-
ignoreFilter,
|
|
146
|
-
}}
|
|
147
|
-
>
|
|
148
|
-
<PopoverPrimitive.Root open={isOpen} onOpenChange={setOpen}>
|
|
149
|
-
<div className={className}>{children}</div>
|
|
150
|
-
</PopoverPrimitive.Root>
|
|
151
|
-
</Context>
|
|
19
|
+
<ComboboxPrimitive.Root<Value, false, Item> {...rest} openOnInputClick={openOnInputClick}>
|
|
20
|
+
<div className={className}>{children}</div>
|
|
21
|
+
</ComboboxPrimitive.Root>
|
|
152
22
|
)
|
|
153
23
|
}
|
|
154
24
|
|
|
@@ -171,24 +41,18 @@ export const comboboxAnchor = tv({
|
|
|
171
41
|
defaultVariants: { surface: 'bare', size: 'md' },
|
|
172
42
|
})
|
|
173
43
|
|
|
174
|
-
export
|
|
44
|
+
export type ComboboxAnchorProps = Omit<ComponentProps<typeof ComboboxPrimitive.InputGroup>, 'className'> & {
|
|
175
45
|
surface?: 'bare' | 'well'
|
|
176
46
|
size?: 'sm' | 'md' | 'lg'
|
|
177
47
|
className?: string
|
|
178
|
-
children?: ReactNode
|
|
179
48
|
}
|
|
180
49
|
|
|
181
|
-
export function ComboboxAnchor({ surface, size, className,
|
|
182
|
-
return (
|
|
183
|
-
<PopoverPrimitive.Anchor className={comboboxAnchor({ surface, size, className })}>
|
|
184
|
-
{children}
|
|
185
|
-
</PopoverPrimitive.Anchor>
|
|
186
|
-
)
|
|
50
|
+
export function ComboboxAnchor({ surface, size, className, ...rest }: ComboboxAnchorProps) {
|
|
51
|
+
return <ComboboxPrimitive.InputGroup {...rest} className={comboboxAnchor({ surface, size, className })} />
|
|
187
52
|
}
|
|
188
53
|
|
|
189
|
-
export
|
|
190
|
-
|
|
191
|
-
onValueChange?: (value: string) => void
|
|
54
|
+
export type ComboboxInputProps = Omit<ComponentProps<typeof ComboboxPrimitive.Input>, 'className'> & {
|
|
55
|
+
className?: string
|
|
192
56
|
}
|
|
193
57
|
|
|
194
58
|
const comboboxInput = tv({
|
|
@@ -197,55 +61,8 @@ const comboboxInput = tv({
|
|
|
197
61
|
+ ' disabled:text-fg-disabled disabled:cursor-not-allowed',
|
|
198
62
|
})
|
|
199
63
|
|
|
200
|
-
export function ComboboxInput({
|
|
201
|
-
|
|
202
|
-
const text = value ?? ctx.search
|
|
203
|
-
const { setNotify } = ctx
|
|
204
|
-
|
|
205
|
-
useEffect(() => {
|
|
206
|
-
setNotify(onValueChange)
|
|
207
|
-
return () => setNotify(undefined)
|
|
208
|
-
})
|
|
209
|
-
|
|
210
|
-
const change = (next: string) => {
|
|
211
|
-
ctx.setSearch(next)
|
|
212
|
-
if (!ctx.open) ctx.setOpen(true)
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const onKeyDown = (e: ReactKeyboardEvent<HTMLInputElement>) => {
|
|
216
|
-
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
|
217
|
-
e.preventDefault()
|
|
218
|
-
if (!ctx.open) ctx.setOpen(true)
|
|
219
|
-
else ctx.move(e.key === 'ArrowDown' ? 1 : -1)
|
|
220
|
-
return
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
if (e.key === 'Enter' && ctx.open && ctx.active !== undefined) {
|
|
224
|
-
e.preventDefault()
|
|
225
|
-
ctx.pick(ctx.active)
|
|
226
|
-
return
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
if (e.key === 'Escape' && ctx.open) {
|
|
230
|
-
e.preventDefault()
|
|
231
|
-
ctx.setOpen(false)
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
return (
|
|
236
|
-
<input
|
|
237
|
-
{...rest}
|
|
238
|
-
role="combobox"
|
|
239
|
-
aria-expanded={ctx.open}
|
|
240
|
-
aria-controls={ctx.open ? `${ctx.id}-list` : undefined}
|
|
241
|
-
aria-autocomplete="list"
|
|
242
|
-
aria-activedescendant={ctx.active === undefined ? undefined : ctx.itemId(ctx.active)}
|
|
243
|
-
value={text}
|
|
244
|
-
onChange={e => change(e.target.value)}
|
|
245
|
-
onKeyDown={onKeyDown}
|
|
246
|
-
className={comboboxInput({ className })}
|
|
247
|
-
/>
|
|
248
|
-
)
|
|
64
|
+
export function ComboboxInput({ className, ...rest }: ComboboxInputProps) {
|
|
65
|
+
return <ComboboxPrimitive.Input {...rest} className={comboboxInput({ className })} />
|
|
249
66
|
}
|
|
250
67
|
|
|
251
68
|
const comboboxTrigger = tv({
|
|
@@ -255,27 +72,20 @@ const comboboxTrigger = tv({
|
|
|
255
72
|
+ ' focus-visible:outline-line-focus-ring',
|
|
256
73
|
})
|
|
257
74
|
|
|
258
|
-
export
|
|
75
|
+
export type ComboboxTriggerProps = Omit<ComponentProps<typeof ComboboxPrimitive.Trigger>, 'className'> & {
|
|
259
76
|
label?: string
|
|
260
|
-
disabled?: boolean
|
|
261
77
|
className?: string
|
|
262
|
-
}
|
|
263
|
-
const ctx = useCombobox()
|
|
78
|
+
}
|
|
264
79
|
|
|
80
|
+
export function ComboboxTrigger({ label = '목록 열기', className, children, ...rest }: ComboboxTriggerProps) {
|
|
265
81
|
return (
|
|
266
|
-
<
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
className={comboboxTrigger({ className })}
|
|
274
|
-
>
|
|
275
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className="size-4 transition-transform open:rotate-180 motion-reduce:transition-none">
|
|
276
|
-
<path d="m6 9 6 6 6-6" />
|
|
277
|
-
</svg>
|
|
278
|
-
</button>
|
|
82
|
+
<ComboboxPrimitive.Trigger aria-label={label} {...rest} className={comboboxTrigger({ className })}>
|
|
83
|
+
{children ?? (
|
|
84
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className="size-4 transition-transform in-data-popup-open:rotate-180 motion-reduce:transition-none">
|
|
85
|
+
<path d="m6 9 6 6 6-6" />
|
|
86
|
+
</svg>
|
|
87
|
+
)}
|
|
88
|
+
</ComboboxPrimitive.Trigger>
|
|
279
89
|
)
|
|
280
90
|
}
|
|
281
91
|
|
|
@@ -286,81 +96,78 @@ const comboboxCancel = tv({
|
|
|
286
96
|
+ ' focus-visible:outline-line-focus-ring',
|
|
287
97
|
})
|
|
288
98
|
|
|
289
|
-
export
|
|
290
|
-
|
|
99
|
+
export type ComboboxCancelProps = Omit<ComponentProps<typeof ComboboxPrimitive.Clear>, 'className'> & {
|
|
100
|
+
label?: string
|
|
101
|
+
className?: string
|
|
102
|
+
}
|
|
291
103
|
|
|
104
|
+
export function ComboboxCancel({ label = '지우기', keepMounted = true, className, children, ...rest }: ComboboxCancelProps) {
|
|
292
105
|
return (
|
|
293
|
-
<
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
<path d="M18 6 6 18" />
|
|
302
|
-
<path d="m6 6 12 12" />
|
|
303
|
-
</svg>
|
|
304
|
-
</button>
|
|
106
|
+
<ComboboxPrimitive.Clear aria-label={label} keepMounted={keepMounted} {...rest} className={comboboxCancel({ className })}>
|
|
107
|
+
{children ?? (
|
|
108
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className="size-4">
|
|
109
|
+
<path d="M18 6 6 18" />
|
|
110
|
+
<path d="m6 6 12 12" />
|
|
111
|
+
</svg>
|
|
112
|
+
)}
|
|
113
|
+
</ComboboxPrimitive.Clear>
|
|
305
114
|
)
|
|
306
115
|
}
|
|
307
116
|
|
|
308
|
-
export type ComboboxContentProps =
|
|
309
|
-
|
|
117
|
+
export type ComboboxContentProps = Omit<ComponentProps<typeof ComboboxPrimitive.Popup>, 'className' | 'children'> & {
|
|
118
|
+
className?: string
|
|
119
|
+
children?: ComboboxPrimitive.List.Props['children'] | ComboboxPrimitive.List.Props['children'][]
|
|
120
|
+
}
|
|
310
121
|
|
|
311
122
|
const comboboxContent = tv({
|
|
312
123
|
base: 'float text-fg rounded-r12 z-overlay'
|
|
313
|
-
+ ' w-(--
|
|
124
|
+
+ ' w-(--anchor-width)'
|
|
314
125
|
+ ' open:animate-enter closed:animate-exit',
|
|
315
126
|
})
|
|
316
127
|
|
|
317
|
-
|
|
318
|
-
|
|
128
|
+
const isEmpty = (child: unknown) => isValidElement(child) && child.type === ComboboxEmpty
|
|
129
|
+
|
|
130
|
+
export function ComboboxContent({ 'aria-label': label, className, children, ...rest }: ComboboxContentProps) {
|
|
131
|
+
const nodes = Array.isArray(children) ? children : [children]
|
|
132
|
+
const empty = nodes.filter(isEmpty) as ReactNode[]
|
|
133
|
+
const list = nodes.filter(node => !isEmpty(node))
|
|
319
134
|
|
|
320
135
|
return (
|
|
321
|
-
<
|
|
322
|
-
<
|
|
323
|
-
{...rest}
|
|
136
|
+
<ComboboxPrimitive.Portal>
|
|
137
|
+
<ComboboxPrimitive.Positioner
|
|
324
138
|
align="start"
|
|
325
139
|
sideOffset={4}
|
|
326
|
-
|
|
327
|
-
className={comboboxContent({ className })}
|
|
140
|
+
className="z-overlay min-w-max will-change-transform"
|
|
328
141
|
>
|
|
329
|
-
<
|
|
330
|
-
{
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
142
|
+
<ComboboxPrimitive.Popup {...rest} className={comboboxContent({ className })}>
|
|
143
|
+
{empty}
|
|
144
|
+
<ComboboxViewport aria-label={label} className="max-h-72">
|
|
145
|
+
{list.length === 1 ? list[0] : list as ReactNode[]}
|
|
146
|
+
</ComboboxViewport>
|
|
147
|
+
</ComboboxPrimitive.Popup>
|
|
148
|
+
</ComboboxPrimitive.Positioner>
|
|
149
|
+
</ComboboxPrimitive.Portal>
|
|
334
150
|
)
|
|
335
151
|
}
|
|
336
152
|
|
|
337
|
-
const comboboxViewport = tv({ base: 'flex flex-col gap-px overflow-y-auto p-1' })
|
|
153
|
+
const comboboxViewport = tv({ base: 'flex flex-col gap-px overflow-y-auto p-1 data-empty:p-0' })
|
|
338
154
|
|
|
339
|
-
export
|
|
340
|
-
label?: string
|
|
155
|
+
export type ComboboxViewportProps = Omit<ComponentProps<typeof ComboboxPrimitive.List>, 'className'> & {
|
|
341
156
|
className?: string
|
|
342
|
-
|
|
343
|
-
}) {
|
|
344
|
-
const ctx = useCombobox()
|
|
157
|
+
}
|
|
345
158
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
id={`${ctx.id}-list`}
|
|
349
|
-
role={ctx.count > 0 ? 'listbox' : undefined}
|
|
350
|
-
aria-label={ctx.count > 0 ? label : undefined}
|
|
351
|
-
className={comboboxViewport({ className })}
|
|
352
|
-
>
|
|
353
|
-
{children}
|
|
354
|
-
</div>
|
|
355
|
-
)
|
|
159
|
+
export function ComboboxViewport({ className, ...rest }: ComboboxViewportProps) {
|
|
160
|
+
return <ComboboxPrimitive.List {...rest} className={comboboxViewport({ className })} />
|
|
356
161
|
}
|
|
357
162
|
|
|
358
|
-
export
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
163
|
+
export type ComboboxCollectionProps = ComboboxPrimitive.Collection.Props
|
|
164
|
+
|
|
165
|
+
export function ComboboxCollection(props: ComboboxCollectionProps) {
|
|
166
|
+
return <ComboboxPrimitive.Collection {...props} />
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export type ComboboxItemProps = Omit<ComponentProps<typeof ComboboxPrimitive.Item>, 'className'> & {
|
|
362
170
|
className?: string
|
|
363
|
-
children?: ReactNode
|
|
364
171
|
}
|
|
365
172
|
|
|
366
173
|
const comboboxItem = tv({
|
|
@@ -370,44 +177,30 @@ const comboboxItem = tv({
|
|
|
370
177
|
+ ' data-disabled:text-fg-disabled data-disabled:pointer-events-none',
|
|
371
178
|
})
|
|
372
179
|
|
|
373
|
-
export function ComboboxItem({
|
|
374
|
-
const ctx = useCombobox()
|
|
375
|
-
const shown = ctx.ignoreFilter || contains(textValue ?? value, ctx.search)
|
|
376
|
-
|
|
377
|
-
useEffect(() => ctx.see(shown), [ctx, shown])
|
|
378
|
-
|
|
379
|
-
if (!shown) return null
|
|
380
|
-
|
|
180
|
+
export function ComboboxItem({ className, children, ...rest }: ComboboxItemProps) {
|
|
381
181
|
return (
|
|
382
|
-
<
|
|
383
|
-
id={ctx.itemId(value)}
|
|
384
|
-
role="option"
|
|
385
|
-
data-value={value}
|
|
386
|
-
aria-selected={ctx.value === value}
|
|
387
|
-
data-highlighted={ctx.active === value ? '' : undefined}
|
|
388
|
-
data-disabled={disabled ? '' : undefined}
|
|
389
|
-
onPointerMove={() => !disabled && ctx.setActive(value)}
|
|
390
|
-
onClick={() => !disabled && ctx.pick(value)}
|
|
391
|
-
className={comboboxItem({ className })}
|
|
392
|
-
>
|
|
182
|
+
<ComboboxPrimitive.Item {...rest} className={comboboxItem({ className })}>
|
|
393
183
|
<span className="flex min-w-0 flex-col gap-0.5">
|
|
394
184
|
{children}
|
|
395
185
|
</span>
|
|
396
186
|
|
|
397
|
-
|
|
398
|
-
<
|
|
399
|
-
<
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
)}
|
|
404
|
-
</div>
|
|
187
|
+
<ComboboxPrimitive.ItemIndicator className="text-fg-muted flex shrink-0">
|
|
188
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className="size-4">
|
|
189
|
+
<path d="M20 6 9 17l-5-5" />
|
|
190
|
+
</svg>
|
|
191
|
+
</ComboboxPrimitive.ItemIndicator>
|
|
192
|
+
</ComboboxPrimitive.Item>
|
|
405
193
|
)
|
|
406
194
|
}
|
|
407
195
|
|
|
196
|
+
export interface ComboboxItemTextProps {
|
|
197
|
+
className?: string
|
|
198
|
+
children?: ReactNode
|
|
199
|
+
}
|
|
200
|
+
|
|
408
201
|
const comboboxItemText = tv({ base: 'text-label text-fg' })
|
|
409
202
|
|
|
410
|
-
export function ComboboxItemText({ className, children }:
|
|
203
|
+
export function ComboboxItemText({ className, children }: ComboboxItemTextProps) {
|
|
411
204
|
return (
|
|
412
205
|
<span className={comboboxItemText({ className })}>
|
|
413
206
|
{children}
|
|
@@ -415,9 +208,14 @@ export function ComboboxItemText({ className, children }: { className?: string,
|
|
|
415
208
|
)
|
|
416
209
|
}
|
|
417
210
|
|
|
211
|
+
export interface ComboboxItemDescriptionProps {
|
|
212
|
+
className?: string
|
|
213
|
+
children?: ReactNode
|
|
214
|
+
}
|
|
215
|
+
|
|
418
216
|
const comboboxItemDescription = tv({ base: 'text-caption text-fg-subtle' })
|
|
419
217
|
|
|
420
|
-
export function ComboboxItemDescription({ className, children }:
|
|
218
|
+
export function ComboboxItemDescription({ className, children }: ComboboxItemDescriptionProps) {
|
|
421
219
|
return (
|
|
422
220
|
<span className={comboboxItemDescription({ className })}>
|
|
423
221
|
{children}
|
|
@@ -425,44 +223,52 @@ export function ComboboxItemDescription({ className, children }: { className?: s
|
|
|
425
223
|
)
|
|
426
224
|
}
|
|
427
225
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
export function ComboboxGroup({ label, className, children }: {
|
|
226
|
+
export type ComboboxGroupProps = Omit<ComponentProps<typeof ComboboxPrimitive.Group>, 'className'> & {
|
|
431
227
|
label?: string
|
|
432
228
|
className?: string
|
|
433
|
-
|
|
434
|
-
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const comboboxGroup = tv({ base: 'flex flex-col gap-px' })
|
|
232
|
+
|
|
233
|
+
export function ComboboxGroup({ label, className, children, ...rest }: ComboboxGroupProps) {
|
|
435
234
|
return (
|
|
436
|
-
<
|
|
235
|
+
<ComboboxPrimitive.Group {...rest} className={comboboxGroup({ className })}>
|
|
437
236
|
{label && (
|
|
438
|
-
<
|
|
237
|
+
<ComboboxPrimitive.GroupLabel className="text-caption text-fg-subtle px-2 pb-1 pt-2">
|
|
439
238
|
{label}
|
|
440
|
-
</
|
|
239
|
+
</ComboboxPrimitive.GroupLabel>
|
|
441
240
|
)}
|
|
442
241
|
{children}
|
|
443
|
-
</
|
|
242
|
+
</ComboboxPrimitive.Group>
|
|
444
243
|
)
|
|
445
244
|
}
|
|
446
245
|
|
|
447
|
-
|
|
246
|
+
export type ComboboxEmptyProps = Omit<ComponentProps<typeof ComboboxPrimitive.Empty>, 'className'> & {
|
|
247
|
+
className?: string
|
|
248
|
+
}
|
|
448
249
|
|
|
449
|
-
|
|
450
|
-
const ctx = useCombobox()
|
|
451
|
-
if (ctx.count > 0) return null
|
|
250
|
+
const comboboxEmpty = tv({ base: 'text-caption text-fg-subtle m-1 px-2 py-3 text-center' })
|
|
452
251
|
|
|
252
|
+
export function ComboboxEmpty({ className, children, ...rest }: ComboboxEmptyProps) {
|
|
453
253
|
return (
|
|
454
|
-
<
|
|
455
|
-
{
|
|
456
|
-
|
|
254
|
+
<ComboboxPrimitive.Empty {...rest}>
|
|
255
|
+
<div className={comboboxEmpty({ className })}>
|
|
256
|
+
{children}
|
|
257
|
+
</div>
|
|
258
|
+
</ComboboxPrimitive.Empty>
|
|
457
259
|
)
|
|
458
260
|
}
|
|
459
261
|
|
|
262
|
+
export type ComboboxSeparatorProps = Omit<ComponentProps<typeof ComboboxPrimitive.Separator>, 'className'> & {
|
|
263
|
+
className?: string
|
|
264
|
+
}
|
|
265
|
+
|
|
460
266
|
const comboboxSeparator = tv({ base: 'bg-line -mx-1 my-1 h-px' })
|
|
461
267
|
|
|
462
|
-
export function ComboboxSeparator({ className }:
|
|
463
|
-
return <
|
|
268
|
+
export function ComboboxSeparator({ className, ...rest }: ComboboxSeparatorProps) {
|
|
269
|
+
return <ComboboxPrimitive.Separator {...rest} className={comboboxSeparator({ className })} />
|
|
464
270
|
}
|
|
465
271
|
|
|
466
|
-
export function
|
|
467
|
-
return
|
|
272
|
+
export function useComboboxFilter(options?: ComboboxFilterOptions): ComboboxFilter {
|
|
273
|
+
return ComboboxPrimitive.useFilter(options)
|
|
468
274
|
}
|