@a-type/ui 4.1.0-beta.6 → 4.1.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/dist/cjs/components/autocomplete/Autocomplete.d.ts +12 -9
- package/dist/cjs/components/autocomplete/Autocomplete.js +19 -24
- package/dist/cjs/components/autocomplete/Autocomplete.js.map +1 -1
- package/dist/cjs/components/combobox/Combobox.d.ts +202 -0
- package/dist/cjs/components/combobox/Combobox.js +311 -0
- package/dist/cjs/components/combobox/Combobox.js.map +1 -0
- package/dist/cjs/components/combobox/Combobox.stories.d.ts +53 -0
- package/dist/cjs/components/combobox/Combobox.stories.js +115 -0
- package/dist/cjs/components/combobox/Combobox.stories.js.map +1 -0
- package/dist/cjs/components/input/Input.js +1 -1
- package/dist/cjs/components/input/Input.js.map +1 -1
- package/dist/cjs/components/input/Input.stories.d.ts +1 -0
- package/dist/cjs/components/input/Input.stories.js +4 -1
- package/dist/cjs/components/input/Input.stories.js.map +1 -1
- package/dist/css/main.css +4 -4
- package/dist/esm/components/autocomplete/Autocomplete.d.ts +12 -9
- package/dist/esm/components/autocomplete/Autocomplete.js +18 -24
- package/dist/esm/components/autocomplete/Autocomplete.js.map +1 -1
- package/dist/esm/components/combobox/Combobox.d.ts +202 -0
- package/dist/esm/components/combobox/Combobox.js +271 -0
- package/dist/esm/components/combobox/Combobox.js.map +1 -0
- package/dist/esm/components/combobox/Combobox.stories.d.ts +53 -0
- package/dist/esm/components/combobox/Combobox.stories.js +112 -0
- package/dist/esm/components/combobox/Combobox.stories.js.map +1 -0
- package/dist/esm/components/input/Input.js +1 -1
- package/dist/esm/components/input/Input.js.map +1 -1
- package/dist/esm/components/input/Input.stories.d.ts +1 -0
- package/dist/esm/components/input/Input.stories.js +3 -0
- package/dist/esm/components/input/Input.stories.js.map +1 -1
- package/package.json +3 -2
- package/src/components/autocomplete/Autocomplete.tsx +46 -66
- package/src/components/combobox/Combobox.stories.tsx +289 -0
- package/src/components/combobox/Combobox.tsx +757 -0
- package/src/components/input/Input.stories.tsx +10 -0
- package/src/components/input/Input.tsx +2 -1
|
@@ -0,0 +1,757 @@
|
|
|
1
|
+
import { ButtonProps } from '@base-ui/react/button';
|
|
2
|
+
import * as BaseCombobox from '@base-ui/react/combobox';
|
|
3
|
+
import clsx from 'clsx';
|
|
4
|
+
import {
|
|
5
|
+
createContext,
|
|
6
|
+
ReactNode,
|
|
7
|
+
Ref,
|
|
8
|
+
RefObject,
|
|
9
|
+
useContext,
|
|
10
|
+
useRef,
|
|
11
|
+
useState,
|
|
12
|
+
} from 'react';
|
|
13
|
+
import { withClassName } from '../../hooks.js';
|
|
14
|
+
import { PaletteName } from '../../uno/index.js';
|
|
15
|
+
import { Button } from '../button/Button.js';
|
|
16
|
+
import { Chip, ChipProps } from '../chip/Chip.js';
|
|
17
|
+
import { Icon } from '../icon/Icon.js';
|
|
18
|
+
import { Input, InputProps } from '../input/Input.js';
|
|
19
|
+
import {
|
|
20
|
+
arrowClassName,
|
|
21
|
+
itemClassName,
|
|
22
|
+
itemListClassName,
|
|
23
|
+
popupClassName,
|
|
24
|
+
separatorClassName,
|
|
25
|
+
} from '../primitives/menus.js';
|
|
26
|
+
import { ArrowSvg } from '../utility/ArrowSvg.js';
|
|
27
|
+
import { SlotDiv, SlotDivProps } from '../utility/SlotDiv.js';
|
|
28
|
+
|
|
29
|
+
export const comboboxPopupClassName = clsx(
|
|
30
|
+
popupClassName,
|
|
31
|
+
'layer-components:w-[--anchor-width]',
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
export const comboboxBackdropClassName = clsx(
|
|
35
|
+
'layer-components:(fixed inset-0)',
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
export const comboboxListClassName = clsx(
|
|
39
|
+
itemListClassName,
|
|
40
|
+
'layer-components:(flex flex-col overscroll-contain outline-none overflow-y-auto overflow-unstable)',
|
|
41
|
+
'layer-components:empty:(p-0)',
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
export const comboboxIconClassName = clsx(
|
|
45
|
+
'icon',
|
|
46
|
+
'layer-components:(flex shrink-0 items-center justify-center transition-transform)',
|
|
47
|
+
'layer-components:data-[open]:(rotate-180)',
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
export const comboboxEmptyClassName = clsx(
|
|
51
|
+
'layer-components:[&:not(:empty)]:(p-sm text-sm color-gray-dark)',
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
export const comboboxGroupClassName = clsx(
|
|
55
|
+
'layer-components:(flex flex-col gap-xs overflow-hidden p-sm)',
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
export const comboboxGroupItemListClassName = clsx(
|
|
59
|
+
'layer-components:(flex flex-row flex-wrap gap-xs)',
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
export const comboboxGroupLabelClassName = clsx(
|
|
63
|
+
'layer-components:(w-full px-xs text-xs font-medium uppercase color-gray-dark)',
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
export const comboboxRowClassName = clsx(
|
|
67
|
+
'layer-components:(flex items-center gap-xs)',
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
export const comboboxGroupItemClassName = clsx(
|
|
71
|
+
'layer-composed-2:(bg-white)',
|
|
72
|
+
'layer-composed-2:data-[highlighted]:(ring-2 bg-main-wash ring-primary)',
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
export interface ComboboxInputProps
|
|
76
|
+
extends Omit<BaseCombobox.ComboboxInputProps, 'className' | 'render'> {
|
|
77
|
+
ref?: React.Ref<HTMLInputElement>;
|
|
78
|
+
icon?: ReactNode;
|
|
79
|
+
disableCaret?: boolean;
|
|
80
|
+
disableClear?: boolean;
|
|
81
|
+
children?: ReactNode;
|
|
82
|
+
className?: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function ComboboxComposedInput({
|
|
86
|
+
open,
|
|
87
|
+
ref,
|
|
88
|
+
className,
|
|
89
|
+
icon,
|
|
90
|
+
children,
|
|
91
|
+
disableCaret,
|
|
92
|
+
disableClear,
|
|
93
|
+
...props
|
|
94
|
+
}: ComboboxInputProps & {
|
|
95
|
+
className?: string;
|
|
96
|
+
ref?: React.Ref<HTMLInputElement>;
|
|
97
|
+
open: boolean;
|
|
98
|
+
disableCaret?: boolean;
|
|
99
|
+
disableClear?: boolean;
|
|
100
|
+
children?: React.ReactNode;
|
|
101
|
+
icon?: React.ReactNode;
|
|
102
|
+
render?: InputProps['render'];
|
|
103
|
+
}) {
|
|
104
|
+
const hasValue = !!useContext(ComboboxValueContext);
|
|
105
|
+
const isInChips = useContext(ComboboxChipsContext);
|
|
106
|
+
|
|
107
|
+
const clearAndCaret = ((!disableClear && hasValue) || !disableCaret) && (
|
|
108
|
+
<div className="flex flex-shrink-0 items-center">
|
|
109
|
+
{!disableClear && hasValue && <ComboboxClear />}
|
|
110
|
+
{!disableCaret && <ComboboxTrigger open={open} />}
|
|
111
|
+
</div>
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
if (isInChips) {
|
|
115
|
+
return (
|
|
116
|
+
<div
|
|
117
|
+
className={clsx(
|
|
118
|
+
'max-w-full min-w-12ch flex flex-1 flex-row items-center gap-xs',
|
|
119
|
+
className,
|
|
120
|
+
)}
|
|
121
|
+
>
|
|
122
|
+
<Input.Input
|
|
123
|
+
autoComplete="off"
|
|
124
|
+
ref={ref}
|
|
125
|
+
className={clsx('layer-components:(min-w-3ch flex-1)')}
|
|
126
|
+
minLength={3}
|
|
127
|
+
{...props}
|
|
128
|
+
/>
|
|
129
|
+
{clearAndCaret}
|
|
130
|
+
{children}
|
|
131
|
+
</div>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<Input.Border ref={ref} className={className}>
|
|
137
|
+
{icon}
|
|
138
|
+
<Input.Input autoComplete="off" minLength={3} {...props} />
|
|
139
|
+
{clearAndCaret}
|
|
140
|
+
{children}
|
|
141
|
+
</Input.Border>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export const ComboboxValueContext = createContext<any>(null);
|
|
146
|
+
|
|
147
|
+
const ComboboxCreatableContext = createContext<{
|
|
148
|
+
onInputEnter?: (value: string) => void;
|
|
149
|
+
inputValue: string;
|
|
150
|
+
showCreatableItem?: boolean;
|
|
151
|
+
}>({
|
|
152
|
+
inputValue: '',
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const createableSymbol = Symbol('combobox-creatable');
|
|
156
|
+
function makeCreatableItem(input: string) {
|
|
157
|
+
const item: any = {
|
|
158
|
+
[createableSymbol]: true,
|
|
159
|
+
value: input,
|
|
160
|
+
label: input,
|
|
161
|
+
key: input,
|
|
162
|
+
id: input,
|
|
163
|
+
};
|
|
164
|
+
return item;
|
|
165
|
+
}
|
|
166
|
+
function isCreatableItem(item: any): item is { value: string; label: string } {
|
|
167
|
+
return !!item?.[createableSymbol];
|
|
168
|
+
}
|
|
169
|
+
function getCreateableItem(value: any) {
|
|
170
|
+
if (isCreatableItem(value)) {
|
|
171
|
+
return value;
|
|
172
|
+
}
|
|
173
|
+
if (Array.isArray(value)) {
|
|
174
|
+
return value.find((v) => isCreatableItem(v));
|
|
175
|
+
}
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
function matchItem(
|
|
179
|
+
items: readonly any[],
|
|
180
|
+
itemToStringLabel: ((item: any) => string) | undefined,
|
|
181
|
+
input: string | number | readonly string[],
|
|
182
|
+
) {
|
|
183
|
+
return items.find((item) => {
|
|
184
|
+
const label = itemToStringLabel
|
|
185
|
+
? itemToStringLabel(item)
|
|
186
|
+
: item?.label || item?.value || '';
|
|
187
|
+
return label.toLowerCase() === input.toString().trim().toLowerCase();
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export interface ComboboxProps<
|
|
192
|
+
TItem,
|
|
193
|
+
Multiple extends boolean | undefined = false,
|
|
194
|
+
> extends BaseCombobox.ComboboxRootProps<TItem, Multiple> {
|
|
195
|
+
onCreate?: (value: string) => void;
|
|
196
|
+
showCreatableItem?: boolean;
|
|
197
|
+
}
|
|
198
|
+
const ComboboxRoot = ({
|
|
199
|
+
onCreate,
|
|
200
|
+
items: userItems,
|
|
201
|
+
onItemHighlighted,
|
|
202
|
+
itemToStringLabel: userItemToStringLabel,
|
|
203
|
+
inputValue: userInputValue,
|
|
204
|
+
onInputValueChange: userOnInputValueChange,
|
|
205
|
+
onValueChange: userOnValueChange,
|
|
206
|
+
showCreatableItem,
|
|
207
|
+
...props
|
|
208
|
+
}: ComboboxProps<any, any>) => {
|
|
209
|
+
const highlightedItemRef = useRef<any>(null);
|
|
210
|
+
const handleItemHighlighted = (
|
|
211
|
+
item: any,
|
|
212
|
+
ev: BaseCombobox.ComboboxRootHighlightEventDetails,
|
|
213
|
+
) => {
|
|
214
|
+
highlightedItemRef.current = item;
|
|
215
|
+
onItemHighlighted?.(item, ev);
|
|
216
|
+
};
|
|
217
|
+
const onInputEnter = onCreate
|
|
218
|
+
? (value: string) => {
|
|
219
|
+
if (highlightedItemRef.current) return;
|
|
220
|
+
onCreate(value);
|
|
221
|
+
}
|
|
222
|
+
: undefined;
|
|
223
|
+
|
|
224
|
+
const [internalInputValue, setInternalInputValue] = useState(
|
|
225
|
+
userInputValue || props.defaultInputValue || '',
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
const inputValue = (
|
|
229
|
+
userInputValue === undefined ? internalInputValue : userInputValue
|
|
230
|
+
).toString();
|
|
231
|
+
const canCreate = !!(
|
|
232
|
+
showCreatableItem &&
|
|
233
|
+
onCreate &&
|
|
234
|
+
inputValue &&
|
|
235
|
+
!matchItem(userItems ?? [], userItemToStringLabel, inputValue)
|
|
236
|
+
);
|
|
237
|
+
const items = canCreate
|
|
238
|
+
? userItems
|
|
239
|
+
? [...userItems, makeCreatableItem(inputValue)]
|
|
240
|
+
: [makeCreatableItem(inputValue)]
|
|
241
|
+
: userItems;
|
|
242
|
+
|
|
243
|
+
const anchorRef = useRef<HTMLDivElement>(null);
|
|
244
|
+
|
|
245
|
+
return (
|
|
246
|
+
<ComboboxAnchorContext.Provider value={anchorRef}>
|
|
247
|
+
<ComboboxCreatableContext.Provider
|
|
248
|
+
value={{ inputValue, onInputEnter, showCreatableItem: canCreate }}
|
|
249
|
+
>
|
|
250
|
+
<ComboboxValueContext.Provider value={props.value || null}>
|
|
251
|
+
<BaseCombobox.Combobox.Root
|
|
252
|
+
{...props}
|
|
253
|
+
items={items}
|
|
254
|
+
inputValue={userInputValue}
|
|
255
|
+
itemToStringLabel={userItemToStringLabel}
|
|
256
|
+
onInputValueChange={(value, ev) => {
|
|
257
|
+
if (userInputValue === undefined) {
|
|
258
|
+
setInternalInputValue(value);
|
|
259
|
+
}
|
|
260
|
+
userOnInputValueChange?.(value, ev);
|
|
261
|
+
}}
|
|
262
|
+
onValueChange={(value, ev) => {
|
|
263
|
+
const creatable = getCreateableItem(value);
|
|
264
|
+
if (creatable) {
|
|
265
|
+
onCreate?.(creatable.value);
|
|
266
|
+
if (value === creatable) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (Array.isArray(value)) {
|
|
270
|
+
value = value.filter((v) => v !== creatable);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
userOnValueChange?.(value, ev);
|
|
275
|
+
}}
|
|
276
|
+
onItemHighlighted={handleItemHighlighted}
|
|
277
|
+
/>
|
|
278
|
+
</ComboboxValueContext.Provider>
|
|
279
|
+
</ComboboxCreatableContext.Provider>
|
|
280
|
+
</ComboboxAnchorContext.Provider>
|
|
281
|
+
);
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
function ComboboxTrigger({
|
|
285
|
+
children,
|
|
286
|
+
open,
|
|
287
|
+
...props
|
|
288
|
+
}: BaseCombobox.ComboboxTriggerProps & {
|
|
289
|
+
open?: boolean;
|
|
290
|
+
}) {
|
|
291
|
+
return (
|
|
292
|
+
<BaseCombobox.Combobox.Trigger
|
|
293
|
+
render={<Button emphasis="ghost" size="small" />}
|
|
294
|
+
{...props}
|
|
295
|
+
>
|
|
296
|
+
{children ?? <ComboboxIcon data-open={open} />}
|
|
297
|
+
</BaseCombobox.Combobox.Trigger>
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function ComboboxInput({
|
|
302
|
+
disableCaret,
|
|
303
|
+
disableClear,
|
|
304
|
+
icon,
|
|
305
|
+
children,
|
|
306
|
+
onKeyDown: userOnKeyDown,
|
|
307
|
+
...outerProps
|
|
308
|
+
}: ComboboxInputProps) {
|
|
309
|
+
const { onInputEnter } = useContext(ComboboxCreatableContext);
|
|
310
|
+
const handleKeyDown: ComboboxInputProps['onKeyDown'] = (ev) => {
|
|
311
|
+
if (ev.key === 'Enter' && onInputEnter) {
|
|
312
|
+
onInputEnter(ev.currentTarget.value);
|
|
313
|
+
}
|
|
314
|
+
userOnKeyDown?.(ev);
|
|
315
|
+
};
|
|
316
|
+
return (
|
|
317
|
+
<BaseCombobox.Combobox.Input
|
|
318
|
+
onKeyDown={handleKeyDown}
|
|
319
|
+
render={(props, state) => (
|
|
320
|
+
<ComboboxComposedInput
|
|
321
|
+
{...props}
|
|
322
|
+
open={state.open}
|
|
323
|
+
disableCaret={disableCaret}
|
|
324
|
+
disableClear={disableClear}
|
|
325
|
+
icon={icon}
|
|
326
|
+
>
|
|
327
|
+
{children}
|
|
328
|
+
</ComboboxComposedInput>
|
|
329
|
+
)}
|
|
330
|
+
{...outerProps}
|
|
331
|
+
/>
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const ComboboxAnchorContext =
|
|
336
|
+
createContext<RefObject<HTMLDivElement | null> | null>(null);
|
|
337
|
+
|
|
338
|
+
const ComboboxChipsContext = createContext(false);
|
|
339
|
+
|
|
340
|
+
function ComboboxChips({
|
|
341
|
+
className,
|
|
342
|
+
...props
|
|
343
|
+
}: BaseCombobox.ComboboxChipsProps) {
|
|
344
|
+
const anchorRef = useContext(ComboboxAnchorContext);
|
|
345
|
+
return (
|
|
346
|
+
<ComboboxChipsContext.Provider value={true}>
|
|
347
|
+
<BaseCombobox.Combobox.Chips
|
|
348
|
+
ref={anchorRef || undefined}
|
|
349
|
+
className={clsx(
|
|
350
|
+
'layer-components:(flex flex-row items-center gap-xs)',
|
|
351
|
+
className,
|
|
352
|
+
)}
|
|
353
|
+
render={<Input.Border />}
|
|
354
|
+
{...props}
|
|
355
|
+
/>
|
|
356
|
+
</ComboboxChipsContext.Provider>
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const ComboboxChipsList = withClassName(
|
|
361
|
+
SlotDiv,
|
|
362
|
+
'layer-components:(flex flex-row flex-wrap gap-xs p-xs)',
|
|
363
|
+
'layer-components:empty:hidden',
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
function ComboboxChip({
|
|
367
|
+
className,
|
|
368
|
+
children,
|
|
369
|
+
...props
|
|
370
|
+
}: BaseCombobox.ComboboxChipProps) {
|
|
371
|
+
return (
|
|
372
|
+
<BaseCombobox.Combobox.Chip
|
|
373
|
+
render={<Chip />}
|
|
374
|
+
className={clsx(
|
|
375
|
+
'layer-composed-2:(my-auto flex flex-row items-center gap-xs px-sm bg-white)',
|
|
376
|
+
className,
|
|
377
|
+
)}
|
|
378
|
+
{...props}
|
|
379
|
+
>
|
|
380
|
+
{children}
|
|
381
|
+
<BaseCombobox.Combobox.ChipRemove
|
|
382
|
+
render={
|
|
383
|
+
<Button
|
|
384
|
+
size="small"
|
|
385
|
+
emphasis="ghost"
|
|
386
|
+
className="min-h-0 min-w-0 p-0 leading-1"
|
|
387
|
+
>
|
|
388
|
+
<Icon name="x" size={10} />
|
|
389
|
+
</Button>
|
|
390
|
+
}
|
|
391
|
+
/>
|
|
392
|
+
</BaseCombobox.Combobox.Chip>
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function ComboboxIcon({ className, ...props }: BaseCombobox.ComboboxIconProps) {
|
|
397
|
+
return (
|
|
398
|
+
<BaseCombobox.Combobox.Icon
|
|
399
|
+
{...props}
|
|
400
|
+
className={clsx(comboboxIconClassName, className)}
|
|
401
|
+
>
|
|
402
|
+
<Icon name="chevron" />
|
|
403
|
+
</BaseCombobox.Combobox.Icon>
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const ComboboxPopup = withClassName(
|
|
408
|
+
BaseCombobox.Combobox.Popup,
|
|
409
|
+
comboboxPopupClassName,
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
const ComboboxBackdrop = withClassName(
|
|
413
|
+
BaseCombobox.Combobox.Backdrop,
|
|
414
|
+
comboboxBackdropClassName,
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
function ComboboxArrow({
|
|
418
|
+
className,
|
|
419
|
+
...props
|
|
420
|
+
}: BaseCombobox.ComboboxArrowProps) {
|
|
421
|
+
return (
|
|
422
|
+
<BaseCombobox.Combobox.Arrow
|
|
423
|
+
{...props}
|
|
424
|
+
className={clsx(arrowClassName, className)}
|
|
425
|
+
>
|
|
426
|
+
<ArrowSvg />
|
|
427
|
+
</BaseCombobox.Combobox.Arrow>
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
export interface ComboboxPopupProps extends BaseCombobox.ComboboxPopupProps {
|
|
432
|
+
positioner?: BaseCombobox.ComboboxPositionerProps;
|
|
433
|
+
ref?: Ref<HTMLDivElement>;
|
|
434
|
+
arrow?: boolean;
|
|
435
|
+
}
|
|
436
|
+
function ComboboxContent({
|
|
437
|
+
positioner,
|
|
438
|
+
arrow,
|
|
439
|
+
children,
|
|
440
|
+
...props
|
|
441
|
+
}: ComboboxPopupProps) {
|
|
442
|
+
const anchorRef = useContext(ComboboxAnchorContext);
|
|
443
|
+
return (
|
|
444
|
+
<BaseCombobox.Combobox.Portal>
|
|
445
|
+
<ComboboxBackdrop />
|
|
446
|
+
<BaseCombobox.Combobox.Positioner
|
|
447
|
+
sideOffset={8}
|
|
448
|
+
anchor={anchorRef}
|
|
449
|
+
{...positioner}
|
|
450
|
+
>
|
|
451
|
+
<ComboboxPopup {...props}>
|
|
452
|
+
{arrow && <ComboboxArrow />}
|
|
453
|
+
{children}
|
|
454
|
+
</ComboboxPopup>
|
|
455
|
+
</BaseCombobox.Combobox.Positioner>
|
|
456
|
+
</BaseCombobox.Combobox.Portal>
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function ComboboxList({ className, ...props }: BaseCombobox.ComboboxListProps) {
|
|
461
|
+
return (
|
|
462
|
+
<BaseCombobox.Combobox.List
|
|
463
|
+
className={clsx(comboboxListClassName, className)}
|
|
464
|
+
{...props}
|
|
465
|
+
/>
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const ComboboxEmpty = withClassName(
|
|
470
|
+
BaseCombobox.Combobox.Empty,
|
|
471
|
+
comboboxEmptyClassName,
|
|
472
|
+
);
|
|
473
|
+
|
|
474
|
+
export interface ComboboxItemProps extends BaseCombobox.ComboboxItemProps {
|
|
475
|
+
ref?: Ref<HTMLDivElement>;
|
|
476
|
+
color?: PaletteName;
|
|
477
|
+
}
|
|
478
|
+
function ComboboxItem({
|
|
479
|
+
className,
|
|
480
|
+
color = 'gray',
|
|
481
|
+
children,
|
|
482
|
+
...props
|
|
483
|
+
}: ComboboxItemProps) {
|
|
484
|
+
return (
|
|
485
|
+
<BaseCombobox.Combobox.Item
|
|
486
|
+
{...props}
|
|
487
|
+
className={clsx(
|
|
488
|
+
color && `palette-${color}`,
|
|
489
|
+
itemClassName,
|
|
490
|
+
'layer-components:data-[selected]:color-gray-dark',
|
|
491
|
+
className,
|
|
492
|
+
)}
|
|
493
|
+
>
|
|
494
|
+
{children}
|
|
495
|
+
<ComboboxItemIndicator />
|
|
496
|
+
</BaseCombobox.Combobox.Item>
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const ComboboxItemIndicator = withClassName(
|
|
501
|
+
(props: BaseCombobox.ComboboxItemIndicatorProps) => (
|
|
502
|
+
<BaseCombobox.Combobox.ItemIndicator
|
|
503
|
+
{...props}
|
|
504
|
+
render={({ children: _, ...rest }) => <Icon name="check" {...rest} />}
|
|
505
|
+
/>
|
|
506
|
+
),
|
|
507
|
+
'layer-components:(ml-auto)',
|
|
508
|
+
);
|
|
509
|
+
|
|
510
|
+
const ComboboxGroup = withClassName(
|
|
511
|
+
BaseCombobox.Combobox.Group,
|
|
512
|
+
comboboxGroupClassName,
|
|
513
|
+
);
|
|
514
|
+
|
|
515
|
+
const ComboboxGroupItemList = withClassName(
|
|
516
|
+
SlotDiv,
|
|
517
|
+
comboboxGroupItemListClassName,
|
|
518
|
+
);
|
|
519
|
+
|
|
520
|
+
const ComboboxGroupLabel = withClassName(
|
|
521
|
+
BaseCombobox.Combobox.GroupLabel,
|
|
522
|
+
comboboxGroupLabelClassName,
|
|
523
|
+
);
|
|
524
|
+
|
|
525
|
+
const ComboboxRow = withClassName(
|
|
526
|
+
BaseCombobox.Combobox.Row,
|
|
527
|
+
comboboxRowClassName,
|
|
528
|
+
);
|
|
529
|
+
|
|
530
|
+
const ComboboxSeparator = withClassName(
|
|
531
|
+
BaseCombobox.Combobox.Separator,
|
|
532
|
+
separatorClassName,
|
|
533
|
+
);
|
|
534
|
+
|
|
535
|
+
export interface ComboboxGroupItemProps
|
|
536
|
+
extends Omit<BaseCombobox.ComboboxItemProps, 'render'> {
|
|
537
|
+
ref?: Ref<HTMLDivElement>;
|
|
538
|
+
replace?: BaseCombobox.ComboboxItemProps['render'];
|
|
539
|
+
render?: ChipProps['render'];
|
|
540
|
+
color?: ChipProps['color'];
|
|
541
|
+
}
|
|
542
|
+
function ComboboxGroupItem({
|
|
543
|
+
className,
|
|
544
|
+
replace,
|
|
545
|
+
render,
|
|
546
|
+
color,
|
|
547
|
+
children,
|
|
548
|
+
...props
|
|
549
|
+
}: ComboboxGroupItemProps) {
|
|
550
|
+
return (
|
|
551
|
+
<BaseCombobox.Combobox.Item
|
|
552
|
+
render={
|
|
553
|
+
replace ?? <Button render={<Chip render={render} color={color} />} />
|
|
554
|
+
}
|
|
555
|
+
{...props}
|
|
556
|
+
className={clsx(
|
|
557
|
+
comboboxGroupItemClassName,
|
|
558
|
+
'layer-components:data-[selected]:color-gray-dark',
|
|
559
|
+
className,
|
|
560
|
+
)}
|
|
561
|
+
>
|
|
562
|
+
{children}
|
|
563
|
+
<ComboboxItemIndicator className="layer-components:(h-10px w-10px)" />
|
|
564
|
+
</BaseCombobox.Combobox.Item>
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
export interface ComboboxMultiValueProps
|
|
569
|
+
extends BaseCombobox.ComboboxValueProps,
|
|
570
|
+
Omit<SlotDivProps, 'children'> {}
|
|
571
|
+
function ComboboxMultiValue({
|
|
572
|
+
children,
|
|
573
|
+
className,
|
|
574
|
+
...props
|
|
575
|
+
}: ComboboxMultiValueProps) {
|
|
576
|
+
return (
|
|
577
|
+
<SlotDiv
|
|
578
|
+
className={clsx('max-w-full flex flex-row flex-wrap gap-xs', className)}
|
|
579
|
+
{...props}
|
|
580
|
+
>
|
|
581
|
+
<BaseCombobox.Combobox.Value>{children}</BaseCombobox.Combobox.Value>
|
|
582
|
+
</SlotDiv>
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
export type ComboboxClearProps = ButtonProps & {
|
|
587
|
+
ref?: Ref<HTMLButtonElement>;
|
|
588
|
+
children?: ReactNode;
|
|
589
|
+
};
|
|
590
|
+
function ComboboxClear({ children, ...props }: ComboboxClearProps) {
|
|
591
|
+
return (
|
|
592
|
+
<BaseCombobox.Combobox.Clear
|
|
593
|
+
render={<Button emphasis="ghost" size="small" />}
|
|
594
|
+
{...props}
|
|
595
|
+
>
|
|
596
|
+
{children ?? <Icon name="x" />}
|
|
597
|
+
</BaseCombobox.Combobox.Clear>
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const baseSubComponents = {
|
|
602
|
+
useFilter: BaseCombobox.Combobox.useFilter,
|
|
603
|
+
|
|
604
|
+
makeCreatableItem,
|
|
605
|
+
isCreatableItem,
|
|
606
|
+
|
|
607
|
+
Input: ComboboxInput,
|
|
608
|
+
Content: ComboboxContent,
|
|
609
|
+
Empty: ComboboxEmpty,
|
|
610
|
+
List: ComboboxList,
|
|
611
|
+
Item: ComboboxItem,
|
|
612
|
+
Group: ComboboxGroup,
|
|
613
|
+
GroupItemList: ComboboxGroupItemList,
|
|
614
|
+
GroupLabel: ComboboxGroupLabel,
|
|
615
|
+
Row: ComboboxRow,
|
|
616
|
+
Separator: ComboboxSeparator,
|
|
617
|
+
ItemGroup: ComboboxGroupItem,
|
|
618
|
+
Icon: ComboboxIcon,
|
|
619
|
+
Clear: ComboboxClear,
|
|
620
|
+
Chips: ComboboxChips,
|
|
621
|
+
Chip: ComboboxChip,
|
|
622
|
+
ChipsList: ComboboxChipsList,
|
|
623
|
+
|
|
624
|
+
Value: BaseCombobox.Combobox.Value,
|
|
625
|
+
MultiValue: ComboboxMultiValue,
|
|
626
|
+
Positioner: BaseCombobox.Combobox.Positioner,
|
|
627
|
+
Portal: BaseCombobox.Combobox.Portal,
|
|
628
|
+
Backdrop: ComboboxBackdrop,
|
|
629
|
+
Arrow: ComboboxArrow,
|
|
630
|
+
Popup: ComboboxPopup,
|
|
631
|
+
|
|
632
|
+
ListItem: ComboboxItem,
|
|
633
|
+
GroupItem: ComboboxGroupItem,
|
|
634
|
+
|
|
635
|
+
Unstyled: BaseCombobox.Combobox,
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
function createCombobox<TItem>() {
|
|
639
|
+
function TypedRoot(
|
|
640
|
+
props: Omit<ComboboxProps<TItem, false>, 'items'> & {
|
|
641
|
+
items?: readonly TItem[];
|
|
642
|
+
},
|
|
643
|
+
) {
|
|
644
|
+
return <ComboboxRoot {...props} />;
|
|
645
|
+
}
|
|
646
|
+
function TypedMultiRoot(
|
|
647
|
+
props: Omit<ComboboxProps<TItem, true>, 'items'> & {
|
|
648
|
+
items?: readonly TItem[];
|
|
649
|
+
},
|
|
650
|
+
) {
|
|
651
|
+
return <ComboboxRoot multiple {...props} />;
|
|
652
|
+
}
|
|
653
|
+
function TypedList(
|
|
654
|
+
props: Omit<BaseCombobox.ComboboxListProps, 'children'> & {
|
|
655
|
+
children?: ReactNode | ((item: TItem, index: number) => ReactNode);
|
|
656
|
+
},
|
|
657
|
+
) {
|
|
658
|
+
return <ComboboxList {...props} />;
|
|
659
|
+
}
|
|
660
|
+
function TypedItem(
|
|
661
|
+
props: Omit<ComboboxItemProps, 'value'> & {
|
|
662
|
+
value: TItem;
|
|
663
|
+
},
|
|
664
|
+
) {
|
|
665
|
+
return <ComboboxItem {...props} />;
|
|
666
|
+
}
|
|
667
|
+
function TypedValue(
|
|
668
|
+
props: Omit<BaseCombobox.ComboboxValueProps, 'children'> & {
|
|
669
|
+
children?: ReactNode | ((item: TItem | null) => ReactNode);
|
|
670
|
+
},
|
|
671
|
+
) {
|
|
672
|
+
return <BaseCombobox.Combobox.Value {...props} />;
|
|
673
|
+
}
|
|
674
|
+
function TypedMultiValue(
|
|
675
|
+
props: Omit<ComboboxMultiValueProps, 'children'> & {
|
|
676
|
+
children?: ReactNode | ((items: TItem[]) => ReactNode);
|
|
677
|
+
},
|
|
678
|
+
) {
|
|
679
|
+
return <ComboboxMultiValue {...props} />;
|
|
680
|
+
}
|
|
681
|
+
return Object.assign(TypedRoot, {
|
|
682
|
+
...baseSubComponents,
|
|
683
|
+
Multi: TypedMultiRoot,
|
|
684
|
+
Item: TypedItem,
|
|
685
|
+
List: TypedList,
|
|
686
|
+
Value: TypedValue,
|
|
687
|
+
MultiValue: TypedMultiValue,
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
function createComboboxGrouped<TItemGroup extends { items: readonly any[] }>() {
|
|
692
|
+
function TypedRoot(
|
|
693
|
+
props: Omit<ComboboxProps<TItemGroup['items'][number], false>, 'items'> & {
|
|
694
|
+
items?: readonly TItemGroup[];
|
|
695
|
+
},
|
|
696
|
+
) {
|
|
697
|
+
return <ComboboxRoot {...(props as any)} />;
|
|
698
|
+
}
|
|
699
|
+
function TypedMultiRoot(
|
|
700
|
+
props: Omit<ComboboxProps<TItemGroup['items'][number], true>, 'items'> & {
|
|
701
|
+
items?: readonly TItemGroup[];
|
|
702
|
+
},
|
|
703
|
+
) {
|
|
704
|
+
return <ComboboxRoot multiple {...(props as any)} />;
|
|
705
|
+
}
|
|
706
|
+
function TypedList(
|
|
707
|
+
props: Omit<BaseCombobox.ComboboxListProps, 'children'> & {
|
|
708
|
+
children?: ReactNode | ((item: TItemGroup, index: number) => ReactNode);
|
|
709
|
+
},
|
|
710
|
+
) {
|
|
711
|
+
return <ComboboxList {...(props as any)} />;
|
|
712
|
+
}
|
|
713
|
+
function TypedItem(
|
|
714
|
+
props: Omit<ComboboxGroupItemProps, 'value'> & {
|
|
715
|
+
value: TItemGroup['items'][number];
|
|
716
|
+
},
|
|
717
|
+
) {
|
|
718
|
+
return <ComboboxGroupItem {...(props as any)} />;
|
|
719
|
+
}
|
|
720
|
+
function TypedValue(
|
|
721
|
+
props: Omit<BaseCombobox.ComboboxValueProps, 'children'> & {
|
|
722
|
+
children?:
|
|
723
|
+
| ReactNode
|
|
724
|
+
| ((item: TItemGroup['items'][number] | null) => ReactNode);
|
|
725
|
+
},
|
|
726
|
+
) {
|
|
727
|
+
return <BaseCombobox.Combobox.Value {...props} />;
|
|
728
|
+
}
|
|
729
|
+
function TypedMultiValue(
|
|
730
|
+
props: Omit<ComboboxMultiValueProps, 'children'> & {
|
|
731
|
+
children?:
|
|
732
|
+
| ReactNode
|
|
733
|
+
| ((items: TItemGroup['items'][number][]) => ReactNode);
|
|
734
|
+
},
|
|
735
|
+
) {
|
|
736
|
+
return <ComboboxMultiValue {...props} />;
|
|
737
|
+
}
|
|
738
|
+
return Object.assign(TypedRoot, {
|
|
739
|
+
...baseSubComponents,
|
|
740
|
+
Multi: TypedMultiRoot,
|
|
741
|
+
Item: TypedItem,
|
|
742
|
+
List: TypedList,
|
|
743
|
+
Group: ComboboxGroup,
|
|
744
|
+
GroupList: ComboboxGroupItemList,
|
|
745
|
+
GroupLabel: ComboboxGroupLabel,
|
|
746
|
+
Row: ComboboxRow,
|
|
747
|
+
Value: TypedValue,
|
|
748
|
+
MultiValue: TypedMultiValue,
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
export const Combobox = Object.assign(ComboboxRoot, {
|
|
753
|
+
create: createCombobox,
|
|
754
|
+
createGrouped: createComboboxGrouped,
|
|
755
|
+
|
|
756
|
+
...baseSubComponents,
|
|
757
|
+
});
|