@kushagradhawan/kookie-ui 0.1.63 → 0.1.65
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/components.css +2314 -2224
- package/dist/cjs/components/combobox.d.ts +75 -0
- package/dist/cjs/components/combobox.d.ts.map +1 -0
- package/dist/cjs/components/combobox.js +2 -0
- package/dist/cjs/components/combobox.js.map +7 -0
- package/dist/cjs/components/combobox.props.d.ts +89 -0
- package/dist/cjs/components/combobox.props.d.ts.map +1 -0
- package/dist/cjs/components/combobox.props.js +2 -0
- package/dist/cjs/components/combobox.props.js.map +7 -0
- package/dist/cjs/components/index.d.ts +1 -0
- package/dist/cjs/components/index.d.ts.map +1 -1
- package/dist/cjs/components/index.js +1 -1
- package/dist/cjs/components/index.js.map +3 -3
- package/dist/esm/components/combobox.d.ts +75 -0
- package/dist/esm/components/combobox.d.ts.map +1 -0
- package/dist/esm/components/combobox.js +2 -0
- package/dist/esm/components/combobox.js.map +7 -0
- package/dist/esm/components/combobox.props.d.ts +89 -0
- package/dist/esm/components/combobox.props.d.ts.map +1 -0
- package/dist/esm/components/combobox.props.js +2 -0
- package/dist/esm/components/combobox.props.js.map +7 -0
- package/dist/esm/components/index.d.ts +1 -0
- package/dist/esm/components/index.d.ts.map +1 -1
- package/dist/esm/components/index.js +1 -1
- package/dist/esm/components/index.js.map +3 -3
- package/package.json +2 -1
- package/schemas/base-button.json +1 -1
- package/schemas/button.json +1 -1
- package/schemas/icon-button.json +1 -1
- package/schemas/index.json +6 -6
- package/schemas/toggle-button.json +1 -1
- package/schemas/toggle-icon-button.json +1 -1
- package/src/components/_internal/base-menu.css +3 -3
- package/src/components/combobox.css +125 -0
- package/src/components/combobox.props.tsx +60 -0
- package/src/components/combobox.tsx +470 -0
- package/src/components/index.css +1 -0
- package/src/components/index.tsx +1 -0
- package/styles.css +2314 -2224
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import classNames from 'classnames';
|
|
5
|
+
import { useControllableState } from 'radix-ui/internal';
|
|
6
|
+
import { Command as CommandPrimitive } from 'cmdk';
|
|
7
|
+
|
|
8
|
+
import { extractProps } from '../helpers/extract-props.js';
|
|
9
|
+
import { comboboxRootPropDefs, comboboxTriggerPropDefs, comboboxContentPropDefs } from './combobox.props.js';
|
|
10
|
+
import { marginPropDefs } from '../props/margin.props.js';
|
|
11
|
+
import { ChevronDownIcon, ThickCheckIcon } from './icons.js';
|
|
12
|
+
import { Theme, useThemeContext } from './theme.js';
|
|
13
|
+
import { requireReactElement } from '../helpers/require-react-element.js';
|
|
14
|
+
import * as Popover from './popover.js';
|
|
15
|
+
import { ScrollArea } from './scroll-area.js';
|
|
16
|
+
import { Slottable } from './slot.js';
|
|
17
|
+
import { textFieldRootPropDefs } from './text-field.props.js';
|
|
18
|
+
|
|
19
|
+
import type { MarginProps } from '../props/margin.props.js';
|
|
20
|
+
import type { ComponentPropsWithout, RemovedProps } from '../helpers/component-props.js';
|
|
21
|
+
import type { GetPropDefTypes } from '../props/prop-def.js';
|
|
22
|
+
|
|
23
|
+
type TextFieldVariant = (typeof textFieldRootPropDefs.variant.values)[number];
|
|
24
|
+
type ComboboxValue = string | null;
|
|
25
|
+
type CommandFilter = (value: string, search: string, keywords?: string[]) => number;
|
|
26
|
+
|
|
27
|
+
type ComboboxRootOwnProps = GetPropDefTypes<typeof comboboxRootPropDefs> & {
|
|
28
|
+
value?: ComboboxValue;
|
|
29
|
+
defaultValue?: ComboboxValue;
|
|
30
|
+
onValueChange?: (value: ComboboxValue) => void;
|
|
31
|
+
open?: boolean;
|
|
32
|
+
defaultOpen?: boolean;
|
|
33
|
+
onOpenChange?: (open: boolean) => void;
|
|
34
|
+
placeholder?: string;
|
|
35
|
+
searchPlaceholder?: string;
|
|
36
|
+
searchValue?: string;
|
|
37
|
+
defaultSearchValue?: string;
|
|
38
|
+
onSearchValueChange?: (value: string) => void;
|
|
39
|
+
filter?: CommandFilter;
|
|
40
|
+
shouldFilter?: boolean;
|
|
41
|
+
loop?: boolean;
|
|
42
|
+
disabled?: boolean;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
interface ComboboxContextValue extends ComboboxRootOwnProps {
|
|
46
|
+
open: boolean;
|
|
47
|
+
setOpen: (open: boolean) => void;
|
|
48
|
+
value: ComboboxValue;
|
|
49
|
+
setValue: (value: ComboboxValue) => void;
|
|
50
|
+
searchValue: string;
|
|
51
|
+
setSearchValue: (value: string) => void;
|
|
52
|
+
selectedLabel?: string;
|
|
53
|
+
registerItemLabel: (value: string, label: string) => void;
|
|
54
|
+
handleSelect: (value: string) => void;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const ComboboxContext = React.createContext<ComboboxContextValue | null>(null);
|
|
58
|
+
const useComboboxContext = (caller: string) => {
|
|
59
|
+
const ctx = React.useContext(ComboboxContext);
|
|
60
|
+
if (!ctx) {
|
|
61
|
+
throw new Error(`${caller} must be used within Combobox.Root`);
|
|
62
|
+
}
|
|
63
|
+
return ctx;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const ComboboxContentContext = React.createContext<{ variant: 'solid' | 'soft'; size?: string; color?: string; material?: string } | null>(null);
|
|
67
|
+
const useComboboxContentContext = () => {
|
|
68
|
+
const ctx = React.useContext(ComboboxContentContext);
|
|
69
|
+
return ctx; // Optional - Input might not always be in Content
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
type PopoverRootProps = React.ComponentPropsWithoutRef<typeof Popover.Root>;
|
|
73
|
+
interface ComboboxRootProps extends PopoverRootProps, ComboboxRootOwnProps {}
|
|
74
|
+
const ComboboxRoot: React.FC<ComboboxRootProps> = (props) => {
|
|
75
|
+
const {
|
|
76
|
+
children,
|
|
77
|
+
size = comboboxRootPropDefs.size.default,
|
|
78
|
+
value: valueProp,
|
|
79
|
+
defaultValue = null,
|
|
80
|
+
onValueChange,
|
|
81
|
+
open: openProp,
|
|
82
|
+
defaultOpen = false,
|
|
83
|
+
onOpenChange,
|
|
84
|
+
placeholder = 'Select an option',
|
|
85
|
+
searchPlaceholder = 'Search options...',
|
|
86
|
+
searchValue: searchValueProp,
|
|
87
|
+
defaultSearchValue = '',
|
|
88
|
+
onSearchValueChange,
|
|
89
|
+
filter,
|
|
90
|
+
shouldFilter = true,
|
|
91
|
+
loop = true,
|
|
92
|
+
disabled,
|
|
93
|
+
...rootProps
|
|
94
|
+
} = props;
|
|
95
|
+
|
|
96
|
+
const [open, setOpen] = useControllableState({
|
|
97
|
+
prop: openProp,
|
|
98
|
+
defaultProp: defaultOpen,
|
|
99
|
+
onChange: onOpenChange,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const [value, setValue] = useControllableState<ComboboxValue>({
|
|
103
|
+
prop: valueProp ?? null,
|
|
104
|
+
defaultProp: defaultValue,
|
|
105
|
+
onChange: onValueChange,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const [searchValue, setSearchValue] = useControllableState<string>({
|
|
109
|
+
prop: searchValueProp,
|
|
110
|
+
defaultProp: defaultSearchValue,
|
|
111
|
+
onChange: onSearchValueChange,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const labelMapRef = React.useRef(new Map<string, string>());
|
|
115
|
+
const registerItemLabel = React.useCallback((itemValue: string, label: string) => {
|
|
116
|
+
labelMapRef.current.set(itemValue, label);
|
|
117
|
+
}, []);
|
|
118
|
+
|
|
119
|
+
const selectedLabel = value != null ? labelMapRef.current.get(value) : undefined;
|
|
120
|
+
|
|
121
|
+
const handleSelect = React.useCallback(
|
|
122
|
+
(nextValue: string) => {
|
|
123
|
+
setValue(nextValue);
|
|
124
|
+
setOpen(false);
|
|
125
|
+
setSearchValue('');
|
|
126
|
+
},
|
|
127
|
+
[setOpen, setSearchValue, setValue],
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const contextValue = React.useMemo<ComboboxContextValue>(
|
|
131
|
+
() => ({
|
|
132
|
+
size,
|
|
133
|
+
placeholder,
|
|
134
|
+
searchPlaceholder,
|
|
135
|
+
filter,
|
|
136
|
+
shouldFilter,
|
|
137
|
+
loop,
|
|
138
|
+
disabled,
|
|
139
|
+
open,
|
|
140
|
+
setOpen,
|
|
141
|
+
value,
|
|
142
|
+
setValue,
|
|
143
|
+
searchValue,
|
|
144
|
+
setSearchValue,
|
|
145
|
+
selectedLabel,
|
|
146
|
+
registerItemLabel,
|
|
147
|
+
handleSelect,
|
|
148
|
+
}),
|
|
149
|
+
[size, placeholder, searchPlaceholder, filter, shouldFilter, loop, disabled, open, setOpen, value, setValue, searchValue, setSearchValue, selectedLabel, registerItemLabel, handleSelect],
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
<ComboboxContext.Provider value={contextValue}>
|
|
154
|
+
<Popover.Root open={open} onOpenChange={setOpen} {...rootProps}>
|
|
155
|
+
{children}
|
|
156
|
+
</Popover.Root>
|
|
157
|
+
</ComboboxContext.Provider>
|
|
158
|
+
);
|
|
159
|
+
};
|
|
160
|
+
ComboboxRoot.displayName = 'Combobox.Root';
|
|
161
|
+
|
|
162
|
+
type ComboboxTriggerElement = HTMLButtonElement;
|
|
163
|
+
type ComboboxTriggerOwnProps = GetPropDefTypes<typeof comboboxTriggerPropDefs>;
|
|
164
|
+
type NativeTriggerProps = Omit<React.ComponentPropsWithoutRef<'button'>, 'color'>;
|
|
165
|
+
interface ComboboxTriggerProps extends NativeTriggerProps, MarginProps, ComboboxTriggerOwnProps {}
|
|
166
|
+
const ComboboxTrigger = React.forwardRef<ComboboxTriggerElement, ComboboxTriggerProps>((props, forwardedRef) => {
|
|
167
|
+
const context = useComboboxContext('Combobox.Trigger');
|
|
168
|
+
const { children, className, placeholder, disabled, readOnly, ...triggerProps } = extractProps(
|
|
169
|
+
{ size: context.size, ...props },
|
|
170
|
+
{ size: comboboxRootPropDefs.size },
|
|
171
|
+
comboboxTriggerPropDefs,
|
|
172
|
+
marginPropDefs,
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
const isDisabled = disabled ?? context.disabled;
|
|
176
|
+
const ariaProps = React.useMemo(
|
|
177
|
+
() => ({
|
|
178
|
+
'aria-expanded': context.open,
|
|
179
|
+
'aria-disabled': isDisabled || undefined,
|
|
180
|
+
'aria-haspopup': 'listbox' as const,
|
|
181
|
+
}),
|
|
182
|
+
[context.open, isDisabled],
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
const defaultContent = (
|
|
186
|
+
<>
|
|
187
|
+
<span className="rt-SelectTriggerInner">
|
|
188
|
+
<ComboboxValue placeholder={placeholder ?? context.placeholder} />
|
|
189
|
+
</span>
|
|
190
|
+
<ChevronDownIcon className="rt-SelectIcon" />
|
|
191
|
+
</>
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
const triggerChild = (
|
|
195
|
+
<button
|
|
196
|
+
data-disabled={isDisabled || undefined}
|
|
197
|
+
data-read-only={readOnly || undefined}
|
|
198
|
+
{...triggerProps}
|
|
199
|
+
{...ariaProps}
|
|
200
|
+
ref={forwardedRef}
|
|
201
|
+
className={classNames('rt-reset', 'rt-SelectTrigger', 'rt-ComboboxTrigger', className)}
|
|
202
|
+
>
|
|
203
|
+
{children ? requireReactElement(children) : defaultContent}
|
|
204
|
+
</button>
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
return <Popover.Trigger disabled={isDisabled}>{triggerChild}</Popover.Trigger>;
|
|
208
|
+
});
|
|
209
|
+
ComboboxTrigger.displayName = 'Combobox.Trigger';
|
|
210
|
+
|
|
211
|
+
type ComboboxValueElement = HTMLSpanElement;
|
|
212
|
+
interface ComboboxValueProps extends React.ComponentPropsWithoutRef<'span'> {
|
|
213
|
+
placeholder?: string;
|
|
214
|
+
}
|
|
215
|
+
const ComboboxValue = React.forwardRef<ComboboxValueElement, ComboboxValueProps>(({ placeholder, children, className, ...valueProps }, forwardedRef) => {
|
|
216
|
+
const context = useComboboxContext('Combobox.Value');
|
|
217
|
+
const displayValue = context.selectedLabel ?? context.value ?? undefined;
|
|
218
|
+
const fallback = placeholder ?? context.placeholder;
|
|
219
|
+
return (
|
|
220
|
+
<span {...valueProps} ref={forwardedRef} className={classNames('rt-ComboboxValue', className)}>
|
|
221
|
+
{displayValue ?? children ?? fallback}
|
|
222
|
+
</span>
|
|
223
|
+
);
|
|
224
|
+
});
|
|
225
|
+
ComboboxValue.displayName = 'Combobox.Value';
|
|
226
|
+
|
|
227
|
+
type ComboboxContentElement = React.ElementRef<typeof Popover.Content>;
|
|
228
|
+
type ComboboxContentOwnProps = GetPropDefTypes<typeof comboboxContentPropDefs> & {
|
|
229
|
+
container?: React.ComponentPropsWithoutRef<typeof Popover.Content>['container'];
|
|
230
|
+
};
|
|
231
|
+
interface ComboboxContentProps extends Omit<ComponentPropsWithout<typeof Popover.Content, RemovedProps>, 'size'>, ComboboxContentOwnProps {}
|
|
232
|
+
const ComboboxContent = React.forwardRef<ComboboxContentElement, ComboboxContentProps>((props, forwardedRef) => {
|
|
233
|
+
const context = useComboboxContext('Combobox.Content');
|
|
234
|
+
const themeContext = useThemeContext();
|
|
235
|
+
const effectiveMaterial = themeContext.panelBackground;
|
|
236
|
+
|
|
237
|
+
const sizeProp = props.size ?? context.size ?? comboboxContentPropDefs.size.default;
|
|
238
|
+
const variantProp = props.variant ?? comboboxContentPropDefs.variant.default;
|
|
239
|
+
const highContrastProp = props.highContrast ?? comboboxContentPropDefs.highContrast.default;
|
|
240
|
+
|
|
241
|
+
const { className, children, color, forceMount, container, ...contentProps } = extractProps(
|
|
242
|
+
{ ...props, size: sizeProp, variant: variantProp, highContrast: highContrastProp },
|
|
243
|
+
comboboxContentPropDefs,
|
|
244
|
+
);
|
|
245
|
+
const resolvedColor = color || themeContext.accentColor;
|
|
246
|
+
let sanitizedClassName = className;
|
|
247
|
+
if (typeof sizeProp === 'string') {
|
|
248
|
+
sanitizedClassName =
|
|
249
|
+
className
|
|
250
|
+
?.split(/\s+/)
|
|
251
|
+
.filter(Boolean)
|
|
252
|
+
.filter((token) => !/^rt-r-size-\d$/.test(token))
|
|
253
|
+
.join(' ') || undefined;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return (
|
|
257
|
+
<Popover.Content
|
|
258
|
+
size={sizeProp}
|
|
259
|
+
data-accent-color={resolvedColor}
|
|
260
|
+
data-material={effectiveMaterial}
|
|
261
|
+
data-panel-background={effectiveMaterial}
|
|
262
|
+
align="start"
|
|
263
|
+
sideOffset={4}
|
|
264
|
+
collisionPadding={10}
|
|
265
|
+
{...contentProps}
|
|
266
|
+
forceMount={forceMount}
|
|
267
|
+
container={container}
|
|
268
|
+
ref={forwardedRef}
|
|
269
|
+
className={classNames('rt-PopperContent', 'rt-BaseMenuContent', 'rt-ComboboxContent', sanitizedClassName)}
|
|
270
|
+
>
|
|
271
|
+
<Theme asChild>
|
|
272
|
+
<ScrollArea type="auto">
|
|
273
|
+
<div className={classNames('rt-BaseMenuViewport', 'rt-ComboboxViewport')}>
|
|
274
|
+
<ComboboxContentContext.Provider value={{ variant: variantProp, size: String(sizeProp), color: resolvedColor, material: effectiveMaterial }}>
|
|
275
|
+
<CommandPrimitive
|
|
276
|
+
loop={context.loop}
|
|
277
|
+
value={context.searchValue}
|
|
278
|
+
onValueChange={context.setSearchValue}
|
|
279
|
+
shouldFilter={context.shouldFilter}
|
|
280
|
+
filter={context.filter}
|
|
281
|
+
className="rt-ComboboxCommand"
|
|
282
|
+
>
|
|
283
|
+
{children}
|
|
284
|
+
</CommandPrimitive>
|
|
285
|
+
</ComboboxContentContext.Provider>
|
|
286
|
+
</div>
|
|
287
|
+
</ScrollArea>
|
|
288
|
+
</Theme>
|
|
289
|
+
</Popover.Content>
|
|
290
|
+
);
|
|
291
|
+
});
|
|
292
|
+
ComboboxContent.displayName = 'Combobox.Content';
|
|
293
|
+
|
|
294
|
+
type ComboboxInputElement = React.ElementRef<typeof CommandPrimitive.Input>;
|
|
295
|
+
interface ComboboxInputProps extends React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input> {
|
|
296
|
+
startAdornment?: React.ReactNode;
|
|
297
|
+
endAdornment?: React.ReactNode;
|
|
298
|
+
variant?: TextFieldVariant;
|
|
299
|
+
}
|
|
300
|
+
const ComboboxInput = React.forwardRef<ComboboxInputElement, ComboboxInputProps>(({ className, startAdornment, endAdornment, placeholder, variant: inputVariant, ...inputProps }, forwardedRef) => {
|
|
301
|
+
const context = useComboboxContext('Combobox.Input');
|
|
302
|
+
const contentContext = useComboboxContentContext();
|
|
303
|
+
const contentVariant = contentContext?.variant ?? 'solid';
|
|
304
|
+
const color = contentContext?.color;
|
|
305
|
+
const material = contentContext?.material;
|
|
306
|
+
|
|
307
|
+
// Map combobox variant to textfield variant: solid -> surface, soft -> soft unless overridden
|
|
308
|
+
const textFieldVariant = inputVariant ?? (contentVariant === 'solid' ? 'surface' : 'soft');
|
|
309
|
+
|
|
310
|
+
return (
|
|
311
|
+
<div
|
|
312
|
+
className={classNames('rt-TextFieldRoot', 'rt-ComboboxInputRoot', `rt-r-size-${context.size}`, `rt-variant-${textFieldVariant}`)}
|
|
313
|
+
data-accent-color={color}
|
|
314
|
+
data-material={material}
|
|
315
|
+
data-panel-background={material}
|
|
316
|
+
>
|
|
317
|
+
{startAdornment ? <div className="rt-TextFieldSlot">{startAdornment}</div> : null}
|
|
318
|
+
<CommandPrimitive.Input {...inputProps} ref={forwardedRef} placeholder={placeholder ?? context.searchPlaceholder} className={classNames('rt-reset', 'rt-TextFieldInput', className)} />
|
|
319
|
+
{endAdornment ? (
|
|
320
|
+
<div className="rt-TextFieldSlot" data-side="right">
|
|
321
|
+
{endAdornment}
|
|
322
|
+
</div>
|
|
323
|
+
) : null}
|
|
324
|
+
</div>
|
|
325
|
+
);
|
|
326
|
+
});
|
|
327
|
+
ComboboxInput.displayName = 'Combobox.Input';
|
|
328
|
+
|
|
329
|
+
type ComboboxListElement = React.ElementRef<typeof CommandPrimitive.List>;
|
|
330
|
+
interface ComboboxListProps extends React.ComponentPropsWithoutRef<typeof CommandPrimitive.List> {}
|
|
331
|
+
const ComboboxList = React.forwardRef<ComboboxListElement, ComboboxListProps>(({ className, ...listProps }, forwardedRef) => (
|
|
332
|
+
<CommandPrimitive.List {...listProps} ref={forwardedRef} className={classNames('rt-ComboboxList', className)} />
|
|
333
|
+
));
|
|
334
|
+
ComboboxList.displayName = 'Combobox.List';
|
|
335
|
+
|
|
336
|
+
type ComboboxEmptyElement = React.ElementRef<typeof CommandPrimitive.Empty>;
|
|
337
|
+
interface ComboboxEmptyProps extends React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty> {}
|
|
338
|
+
const ComboboxEmpty = React.forwardRef<ComboboxEmptyElement, ComboboxEmptyProps>(({ className, ...emptyProps }, forwardedRef) => (
|
|
339
|
+
<CommandPrimitive.Empty {...emptyProps} ref={forwardedRef} className={classNames('rt-ComboboxEmpty', className)} />
|
|
340
|
+
));
|
|
341
|
+
ComboboxEmpty.displayName = 'Combobox.Empty';
|
|
342
|
+
|
|
343
|
+
type ComboboxGroupElement = React.ElementRef<typeof CommandPrimitive.Group>;
|
|
344
|
+
interface ComboboxGroupProps extends React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group> {}
|
|
345
|
+
const ComboboxGroup = React.forwardRef<ComboboxGroupElement, ComboboxGroupProps>(({ className, ...groupProps }, forwardedRef) => (
|
|
346
|
+
<CommandPrimitive.Group {...groupProps} ref={forwardedRef} className={classNames('rt-BaseMenuGroup', 'rt-ComboboxGroup', className)} />
|
|
347
|
+
));
|
|
348
|
+
ComboboxGroup.displayName = 'Combobox.Group';
|
|
349
|
+
|
|
350
|
+
type ComboboxLabelElement = React.ElementRef<'div'>;
|
|
351
|
+
interface ComboboxLabelProps extends React.ComponentPropsWithoutRef<'div'> {}
|
|
352
|
+
const ComboboxLabel = React.forwardRef<ComboboxLabelElement, ComboboxLabelProps>(({ className, ...labelProps }, forwardedRef) => (
|
|
353
|
+
<div {...labelProps} ref={forwardedRef} className={classNames('rt-BaseMenuLabel', 'rt-ComboboxLabel', className)} />
|
|
354
|
+
));
|
|
355
|
+
ComboboxLabel.displayName = 'Combobox.Label';
|
|
356
|
+
|
|
357
|
+
type ComboboxSeparatorElement = React.ElementRef<typeof CommandPrimitive.Separator>;
|
|
358
|
+
interface ComboboxSeparatorProps extends React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator> {}
|
|
359
|
+
const ComboboxSeparator = React.forwardRef<ComboboxSeparatorElement, ComboboxSeparatorProps>(({ className, ...separatorProps }, forwardedRef) => (
|
|
360
|
+
<CommandPrimitive.Separator {...separatorProps} ref={forwardedRef} className={classNames('rt-BaseMenuSeparator', 'rt-ComboboxSeparator', className)} />
|
|
361
|
+
));
|
|
362
|
+
ComboboxSeparator.displayName = 'Combobox.Separator';
|
|
363
|
+
|
|
364
|
+
type ComboboxItemElement = React.ElementRef<typeof CommandPrimitive.Item>;
|
|
365
|
+
interface ComboboxItemProps extends React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item> {
|
|
366
|
+
label?: string;
|
|
367
|
+
}
|
|
368
|
+
const ComboboxItem = React.forwardRef<ComboboxItemElement, ComboboxItemProps>(({ className, children, label, value, disabled, onSelect, ...itemProps }, forwardedRef) => {
|
|
369
|
+
const context = useComboboxContext('Combobox.Item');
|
|
370
|
+
const contentContext = useComboboxContentContext();
|
|
371
|
+
const itemLabel = label ?? (typeof children === 'string' ? children : String(value));
|
|
372
|
+
const isSelected = value != null && context.value === value;
|
|
373
|
+
const sizeClass = contentContext?.size ? `rt-r-size-${contentContext.size}` : undefined;
|
|
374
|
+
|
|
375
|
+
React.useEffect(() => {
|
|
376
|
+
if (value) {
|
|
377
|
+
context.registerItemLabel(value, itemLabel);
|
|
378
|
+
}
|
|
379
|
+
}, [context, value, itemLabel]);
|
|
380
|
+
|
|
381
|
+
const handleSelect = React.useCallback(
|
|
382
|
+
(selectedValue: string) => {
|
|
383
|
+
context.handleSelect(selectedValue);
|
|
384
|
+
onSelect?.(selectedValue);
|
|
385
|
+
},
|
|
386
|
+
[context, onSelect],
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
const isDisabled = disabled ?? context.disabled ?? false;
|
|
390
|
+
|
|
391
|
+
// Internal ref to clean up data-disabled attribute
|
|
392
|
+
const internalRef = React.useRef<ComboboxItemElement | null>(null);
|
|
393
|
+
|
|
394
|
+
// Ref callback to handle both forwarded ref and internal ref
|
|
395
|
+
const itemRef = React.useCallback(
|
|
396
|
+
(node: ComboboxItemElement | null) => {
|
|
397
|
+
internalRef.current = node;
|
|
398
|
+
if (typeof forwardedRef === 'function') {
|
|
399
|
+
forwardedRef(node);
|
|
400
|
+
} else if (forwardedRef) {
|
|
401
|
+
(forwardedRef as React.MutableRefObject<ComboboxItemElement | null>).current = node;
|
|
402
|
+
}
|
|
403
|
+
},
|
|
404
|
+
[forwardedRef],
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
// Remove data-disabled attribute if cmdk sets it incorrectly
|
|
408
|
+
React.useEffect(() => {
|
|
409
|
+
if (!isDisabled && internalRef.current) {
|
|
410
|
+
const node = internalRef.current;
|
|
411
|
+
// Check and remove immediately
|
|
412
|
+
if (node.getAttribute('data-disabled') === 'false' || node.getAttribute('data-disabled') === '') {
|
|
413
|
+
node.removeAttribute('data-disabled');
|
|
414
|
+
}
|
|
415
|
+
// Also watch for changes
|
|
416
|
+
const observer = new MutationObserver(() => {
|
|
417
|
+
if (node.getAttribute('data-disabled') === 'false' || node.getAttribute('data-disabled') === '') {
|
|
418
|
+
node.removeAttribute('data-disabled');
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
observer.observe(node, { attributes: true, attributeFilter: ['data-disabled'] });
|
|
422
|
+
return () => observer.disconnect();
|
|
423
|
+
}
|
|
424
|
+
}, [isDisabled]);
|
|
425
|
+
|
|
426
|
+
return (
|
|
427
|
+
<CommandPrimitive.Item
|
|
428
|
+
{...itemProps}
|
|
429
|
+
value={value}
|
|
430
|
+
{...(isDisabled ? { disabled: true } : {})}
|
|
431
|
+
ref={itemRef}
|
|
432
|
+
onSelect={handleSelect}
|
|
433
|
+
className={classNames('rt-reset', 'rt-BaseMenuItem', 'rt-ComboboxItem', className)}
|
|
434
|
+
>
|
|
435
|
+
{isSelected ? (
|
|
436
|
+
<span className={classNames('rt-BaseMenuItemIndicator', 'rt-ComboboxItemIndicator', sizeClass)}>
|
|
437
|
+
<ThickCheckIcon className={classNames('rt-BaseMenuItemIndicatorIcon', 'rt-ComboboxItemIndicatorIcon', sizeClass)} />
|
|
438
|
+
</span>
|
|
439
|
+
) : null}
|
|
440
|
+
<Slottable>{children}</Slottable>
|
|
441
|
+
</CommandPrimitive.Item>
|
|
442
|
+
);
|
|
443
|
+
});
|
|
444
|
+
ComboboxItem.displayName = 'Combobox.Item';
|
|
445
|
+
|
|
446
|
+
export {
|
|
447
|
+
ComboboxRoot as Root,
|
|
448
|
+
ComboboxTrigger as Trigger,
|
|
449
|
+
ComboboxValue as Value,
|
|
450
|
+
ComboboxContent as Content,
|
|
451
|
+
ComboboxInput as Input,
|
|
452
|
+
ComboboxList as List,
|
|
453
|
+
ComboboxEmpty as Empty,
|
|
454
|
+
ComboboxGroup as Group,
|
|
455
|
+
ComboboxLabel as Label,
|
|
456
|
+
ComboboxSeparator as Separator,
|
|
457
|
+
ComboboxItem as Item,
|
|
458
|
+
};
|
|
459
|
+
export type {
|
|
460
|
+
ComboboxRootProps as RootProps,
|
|
461
|
+
ComboboxTriggerProps as TriggerProps,
|
|
462
|
+
ComboboxContentProps as ContentProps,
|
|
463
|
+
ComboboxInputProps as InputProps,
|
|
464
|
+
ComboboxListProps as ListProps,
|
|
465
|
+
ComboboxEmptyProps as EmptyProps,
|
|
466
|
+
ComboboxGroupProps as GroupProps,
|
|
467
|
+
ComboboxLabelProps as LabelProps,
|
|
468
|
+
ComboboxSeparatorProps as SeparatorProps,
|
|
469
|
+
ComboboxItemProps as ItemProps,
|
|
470
|
+
};
|
package/src/components/index.css
CHANGED
package/src/components/index.tsx
CHANGED
|
@@ -18,6 +18,7 @@ export * as ContextMenu from './context-menu.js';
|
|
|
18
18
|
export * as DataList from './data-list.js';
|
|
19
19
|
export * as Dialog from './dialog.js';
|
|
20
20
|
export * as DropdownMenu from './dropdown-menu.js';
|
|
21
|
+
export * as Combobox from './combobox.js';
|
|
21
22
|
export { Em, type EmProps } from './em.js';
|
|
22
23
|
export { Flex, type FlexProps } from './flex.js';
|
|
23
24
|
export { Grid, type GridProps } from './grid.js';
|