@ceed/ads 1.23.4 → 1.24.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/components/inputs/Autocomplete.md +351 -107
- package/dist/components/inputs/FormControl.md +12 -19
- package/dist/index.browser.js +5 -5
- package/dist/index.browser.js.map +4 -4
- package/dist/index.cjs +266 -181
- package/dist/index.js +341 -256
- package/framer/index.js +43 -43
- package/package.json +1 -1
|
@@ -8,10 +8,36 @@ Autocomplete is an enhanced input component that provides real-time suggestions
|
|
|
8
8
|
<Autocomplete options={['Option1', 'Option2']} />
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
| Field
|
|
12
|
-
|
|
|
13
|
-
|
|
|
14
|
-
|
|
|
11
|
+
| Field | Description | Default |
|
|
12
|
+
| --------------------- | ----------- | ------- |
|
|
13
|
+
| variant | — | — |
|
|
14
|
+
| color | — | — |
|
|
15
|
+
| size | — | — |
|
|
16
|
+
| placeholder | — | — |
|
|
17
|
+
| label | — | — |
|
|
18
|
+
| helperText | — | — |
|
|
19
|
+
| error | — | — |
|
|
20
|
+
| required | — | — |
|
|
21
|
+
| disabled | — | — |
|
|
22
|
+
| readOnly | — | — |
|
|
23
|
+
| multiple | — | — |
|
|
24
|
+
| freeSolo | — | — |
|
|
25
|
+
| disableClearable | — | — |
|
|
26
|
+
| autoHighlight | — | — |
|
|
27
|
+
| clearOnEscape | — | — |
|
|
28
|
+
| disableCloseOnSelect | — | — |
|
|
29
|
+
| openOnFocus | — | — |
|
|
30
|
+
| blurOnSelect | — | — |
|
|
31
|
+
| filterSelectedOptions | — | — |
|
|
32
|
+
| selectOnFocus | — | — |
|
|
33
|
+
| clearOnBlur | — | — |
|
|
34
|
+
| forcePopupIcon | — | — |
|
|
35
|
+
| loading | — | — |
|
|
36
|
+
| noOptionsText | — | — |
|
|
37
|
+
| loadingText | — | — |
|
|
38
|
+
| limitTags | — | — |
|
|
39
|
+
| value | — | — |
|
|
40
|
+
| defaultValue | — | — |
|
|
15
41
|
|
|
16
42
|
> ⚠️ **Usage Warning** ⚠️
|
|
17
43
|
>
|
|
@@ -47,17 +73,19 @@ function CountrySelector() {
|
|
|
47
73
|
}
|
|
48
74
|
```
|
|
49
75
|
|
|
50
|
-
##
|
|
76
|
+
## Variants
|
|
51
77
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
Interactive example with basic string options.
|
|
78
|
+
The `variant` prop controls the visual style of the autocomplete input. Available variants: `outlined` (default), `soft`, `solid`, `plain`.
|
|
55
79
|
|
|
56
80
|
```tsx
|
|
57
|
-
<
|
|
81
|
+
<Stack gap={2} p={2}>
|
|
82
|
+
{variants.map(variant => <Autocomplete key={variant} variant={variant} options={sampleOptions} placeholder={variant} label={variant} sx={{
|
|
83
|
+
maxWidth: 300
|
|
84
|
+
}} />)}
|
|
85
|
+
</Stack>
|
|
58
86
|
```
|
|
59
87
|
|
|
60
|
-
|
|
88
|
+
## Sizes
|
|
61
89
|
|
|
62
90
|
Available size options: `sm`, `md`, `lg`.
|
|
63
91
|
|
|
@@ -72,7 +100,139 @@ Available size options: `sm`, `md`, `lg`.
|
|
|
72
100
|
</div>
|
|
73
101
|
```
|
|
74
102
|
|
|
75
|
-
|
|
103
|
+
## Colors
|
|
104
|
+
|
|
105
|
+
The `color` prop changes the color scheme. Available colors: `primary`, `neutral`, `danger`, `success`, `warning`.
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
<Stack gap={2} p={2}>
|
|
109
|
+
{colors.map(color => <Autocomplete key={color} color={color} options={sampleOptions} defaultValue="Apple" label={color} sx={{
|
|
110
|
+
maxWidth: 300
|
|
111
|
+
}} />)}
|
|
112
|
+
</Stack>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## States
|
|
116
|
+
|
|
117
|
+
Autocomplete supports several form states for different interaction contexts.
|
|
118
|
+
|
|
119
|
+
### Disabled
|
|
120
|
+
|
|
121
|
+
A disabled autocomplete cannot be focused or interacted with. Use for fields that are temporarily unavailable.
|
|
122
|
+
|
|
123
|
+
### Read Only
|
|
124
|
+
|
|
125
|
+
A read-only autocomplete can be focused and its value copied, but cannot be changed. Use when a value should be visible but not editable.
|
|
126
|
+
|
|
127
|
+
### Error
|
|
128
|
+
|
|
129
|
+
Set `error` along with `helperText` to indicate validation failures.
|
|
130
|
+
|
|
131
|
+
### Required
|
|
132
|
+
|
|
133
|
+
Set `required` to mark the field as required in a form.
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
<Stack gap={3} p={2} sx={{
|
|
137
|
+
maxWidth: 300
|
|
138
|
+
}}>
|
|
139
|
+
<Autocomplete label="Disabled" options={sampleOptions} defaultValue="Apple" disabled />
|
|
140
|
+
<Autocomplete label="Read Only" options={sampleOptions} defaultValue="Banana" readOnly />
|
|
141
|
+
<Autocomplete label="Error" options={sampleOptions} error helperText="This field is required" />
|
|
142
|
+
<Autocomplete label="Required" options={sampleOptions} required placeholder="Select a fruit..." />
|
|
143
|
+
</Stack>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Free Solo
|
|
147
|
+
|
|
148
|
+
The `freeSolo` prop allows users to type arbitrary values that are not in the options list. This is useful for inputs where the user can either pick a suggestion or enter a custom value.
|
|
149
|
+
|
|
150
|
+
> ⚠️ **Known limitation**: The component's internal `handleChange` accesses `newValue.value`, which is `undefined` for raw strings in freeSolo mode. Typed arbitrary values may not propagate correctly through `onChange`. Consider using `onInputChange` as an alternative for capturing free-text input.
|
|
151
|
+
|
|
152
|
+
```tsx
|
|
153
|
+
<Stack gap={2} p={2} sx={{
|
|
154
|
+
maxWidth: 300
|
|
155
|
+
}}>
|
|
156
|
+
<Autocomplete label="Free Solo" options={sampleOptions} freeSolo placeholder="Type anything..." value={value} onChange={e => setValue(e.target.value)} />
|
|
157
|
+
<Typography level="body-sm" textColor="text.tertiary">
|
|
158
|
+
Current value: {value ?? '(empty)'}
|
|
159
|
+
</Typography>
|
|
160
|
+
<Typography level="body-xs" textColor="text.tertiary">
|
|
161
|
+
Note: freeSolo allows arbitrary text input. However, the component's onChange handler accesses
|
|
162
|
+
newValue.value which is undefined for raw strings, so typed values may not propagate correctly.
|
|
163
|
+
</Typography>
|
|
164
|
+
</Stack>
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Disable Clearable
|
|
168
|
+
|
|
169
|
+
Set `disableClearable` to hide the clear (X) button. This forces the user to always have a selection.
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
<Autocomplete
|
|
173
|
+
options={sampleOptions}
|
|
174
|
+
defaultValue="Cherry"
|
|
175
|
+
disableClearable
|
|
176
|
+
label="Disable Clearable"
|
|
177
|
+
sx={{
|
|
178
|
+
maxWidth: 300
|
|
179
|
+
}}
|
|
180
|
+
/>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Custom Texts
|
|
184
|
+
|
|
185
|
+
Customize the messages shown when there are no matching options or while loading.
|
|
186
|
+
|
|
187
|
+
```tsx
|
|
188
|
+
<Stack gap={3} direction="row" p={2}>
|
|
189
|
+
<Autocomplete label="Custom No Options Text" options={[]} noOptionsText="No fruits found — try a different search" placeholder="Search fruits..." sx={{
|
|
190
|
+
minWidth: 280
|
|
191
|
+
}} />
|
|
192
|
+
<Autocomplete label="Custom Loading Text" options={[]} loading loadingText="Fetching fruits from the orchard..." placeholder="Loading fruits..." sx={{
|
|
193
|
+
minWidth: 280
|
|
194
|
+
}} />
|
|
195
|
+
</Stack>
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Multiple Selection
|
|
199
|
+
|
|
200
|
+
Enable `multiple` to allow selecting more than one option. Selected values are shown as chips.
|
|
201
|
+
|
|
202
|
+
```tsx
|
|
203
|
+
<div style={{
|
|
204
|
+
display: 'flex',
|
|
205
|
+
flexDirection: 'column',
|
|
206
|
+
gap: '10px'
|
|
207
|
+
}}>
|
|
208
|
+
<Autocomplete {...args} size="sm" />
|
|
209
|
+
<Autocomplete {...args} size="md" />
|
|
210
|
+
<Autocomplete {...args} size="lg" />
|
|
211
|
+
</div>
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Limit Tags
|
|
215
|
+
|
|
216
|
+
In multiple mode, use `limitTags` to control how many tags are visible before truncating. Use `filterSelectedOptions` to hide already-selected options from the dropdown.
|
|
217
|
+
|
|
218
|
+
> 💡 The component uses a custom `renderTags` implementation. JoyUI's `limitTags` truncates the array passed to `renderTags`, so tags will be limited, but the default "+N more" indicator text may not appear.
|
|
219
|
+
|
|
220
|
+
```tsx
|
|
221
|
+
<Stack gap={3} p={2} sx={{
|
|
222
|
+
maxWidth: 400
|
|
223
|
+
}}>
|
|
224
|
+
<Stack gap={1}>
|
|
225
|
+
<Typography level="title-sm">limitTags=2</Typography>
|
|
226
|
+
<Autocomplete multiple options={sampleOptions} defaultValue={['Apple', 'Banana', 'Cherry', 'Date']} limitTags={2} label="Limit Tags" />
|
|
227
|
+
</Stack>
|
|
228
|
+
<Stack gap={1}>
|
|
229
|
+
<Typography level="title-sm">filterSelectedOptions</Typography>
|
|
230
|
+
<Autocomplete multiple options={sampleOptions} defaultValue={['Apple', 'Banana']} filterSelectedOptions label="Filter Selected Options" />
|
|
231
|
+
</Stack>
|
|
232
|
+
</Stack>
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Option Groups
|
|
76
236
|
|
|
77
237
|
Group options into categories using the `groupBy` function.
|
|
78
238
|
|
|
@@ -100,7 +260,7 @@ Group options into categories using the `groupBy` function.
|
|
|
100
260
|
/>
|
|
101
261
|
```
|
|
102
262
|
|
|
103
|
-
|
|
263
|
+
## Custom Options
|
|
104
264
|
|
|
105
265
|
Options can include decorators for rich content display.
|
|
106
266
|
|
|
@@ -121,7 +281,28 @@ Options can include decorators for rich content display.
|
|
|
121
281
|
/>
|
|
122
282
|
```
|
|
123
283
|
|
|
124
|
-
|
|
284
|
+
## Secondary Text
|
|
285
|
+
|
|
286
|
+
Options can display a secondary line of text using the `secondaryText` property. This is useful for showing additional context like emails, phone numbers, or descriptions alongside the main label.
|
|
287
|
+
|
|
288
|
+
```tsx
|
|
289
|
+
<Stack gap={4} direction="row" alignItems="flex-start" p={2}>
|
|
290
|
+
{sizes.map(size => <Stack key={size} gap={1}>
|
|
291
|
+
<span style={{
|
|
292
|
+
color: '#6366f1',
|
|
293
|
+
fontSize: 12
|
|
294
|
+
}}>{size}</span>
|
|
295
|
+
<Autocomplete placeholder="Placeholder" options={optionsWithSecondaryText} value={values[size]} onChange={e => setValues(prev => ({
|
|
296
|
+
...prev,
|
|
297
|
+
[size]: e.target.value
|
|
298
|
+
}))} sx={{
|
|
299
|
+
minWidth: 200
|
|
300
|
+
}} size={size} />
|
|
301
|
+
</Stack>)}
|
|
302
|
+
</Stack>
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Loading State
|
|
125
306
|
|
|
126
307
|
Show a loading indicator while fetching options.
|
|
127
308
|
|
|
@@ -132,6 +313,8 @@ Show a loading indicator while fetching options.
|
|
|
132
313
|
/>
|
|
133
314
|
```
|
|
134
315
|
|
|
316
|
+
## Controlled / Uncontrolled
|
|
317
|
+
|
|
135
318
|
### Controlled
|
|
136
319
|
|
|
137
320
|
Manage value externally with controlled state.
|
|
@@ -158,6 +341,67 @@ Manage value externally with controlled state.
|
|
|
158
341
|
</Stack>
|
|
159
342
|
```
|
|
160
343
|
|
|
344
|
+
### Uncontrolled
|
|
345
|
+
|
|
346
|
+
Let the component manage its own state with `defaultValue`.
|
|
347
|
+
|
|
348
|
+
```tsx
|
|
349
|
+
<Autocomplete
|
|
350
|
+
options={['Uncontrolled', 'Controlled']}
|
|
351
|
+
label="Component Type"
|
|
352
|
+
defaultValue="Controlled"
|
|
353
|
+
/>
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
## Virtualization
|
|
357
|
+
|
|
358
|
+
Autocomplete automatically virtualizes long option lists for performance. This example renders 1,000 options efficiently.
|
|
359
|
+
|
|
360
|
+
```tsx
|
|
361
|
+
<Autocomplete
|
|
362
|
+
options={(() => {
|
|
363
|
+
const res: any[] = [];
|
|
364
|
+
for (let i = 0; i < 1000; i++) {
|
|
365
|
+
res.push(i);
|
|
366
|
+
}
|
|
367
|
+
return res;
|
|
368
|
+
})()}
|
|
369
|
+
/>
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
## Behavior Props
|
|
373
|
+
|
|
374
|
+
These boolean props fine-tune interaction behavior. Toggle them in the Controls panel below to see their effects.
|
|
375
|
+
|
|
376
|
+
| Prop | Default | Effect |
|
|
377
|
+
| ---------------------- | -------- | -------------------------------------------------------------- |
|
|
378
|
+
| `autoHighlight` | `false` | Automatically highlights the first option when the popup opens |
|
|
379
|
+
| `clearOnEscape` | `false` | Clears the input value when the Escape key is pressed |
|
|
380
|
+
| `disableCloseOnSelect` | `false` | Keeps the popup open after selecting an option |
|
|
381
|
+
| `openOnFocus` | `false` | Opens the popup when the input receives focus |
|
|
382
|
+
| `blurOnSelect` | `false` | Blurs the input after an option is selected |
|
|
383
|
+
| `selectOnFocus` | `false` | Selects all input text when the input is focused |
|
|
384
|
+
| `clearOnBlur` | `true` | Clears the input text on blur if it doesn't match any option |
|
|
385
|
+
| `forcePopupIcon` | `'auto'` | Controls whether the popup icon is always shown |
|
|
386
|
+
|
|
387
|
+
```tsx
|
|
388
|
+
<Autocomplete
|
|
389
|
+
options={sampleOptions}
|
|
390
|
+
label="Behavior Props"
|
|
391
|
+
placeholder="Toggle props in the Controls panel..."
|
|
392
|
+
autoHighlight={false}
|
|
393
|
+
clearOnEscape={false}
|
|
394
|
+
disableCloseOnSelect={false}
|
|
395
|
+
openOnFocus={false}
|
|
396
|
+
blurOnSelect={false}
|
|
397
|
+
selectOnFocus={false}
|
|
398
|
+
clearOnBlur
|
|
399
|
+
sx={{
|
|
400
|
+
maxWidth: 400
|
|
401
|
+
}}
|
|
402
|
+
/>
|
|
403
|
+
```
|
|
404
|
+
|
|
161
405
|
## When to Use
|
|
162
406
|
|
|
163
407
|
### ✅ Good Use Cases
|
|
@@ -232,7 +476,7 @@ function UserSearch({ onSelect }) {
|
|
|
232
476
|
label: user.name,
|
|
233
477
|
secondaryText: user.email,
|
|
234
478
|
startDecorator: <Avatar src={user.avatar} size="sm" />,
|
|
235
|
-
}))
|
|
479
|
+
})),
|
|
236
480
|
);
|
|
237
481
|
} finally {
|
|
238
482
|
setLoading(false);
|
|
@@ -309,7 +553,7 @@ function AddressAutocomplete({ onAddressSelect }) {
|
|
|
309
553
|
value: result.placeId,
|
|
310
554
|
label: result.formattedAddress,
|
|
311
555
|
secondaryText: result.city + ', ' + result.country,
|
|
312
|
-
}))
|
|
556
|
+
})),
|
|
313
557
|
);
|
|
314
558
|
} finally {
|
|
315
559
|
setLoading(false);
|
|
@@ -401,13 +645,7 @@ function ContactSelector() {
|
|
|
401
645
|
{ value: 'michael', label: 'Michael Chen', secondaryText: '(646) 555-0876' },
|
|
402
646
|
];
|
|
403
647
|
|
|
404
|
-
return
|
|
405
|
-
<Autocomplete
|
|
406
|
-
label="Contact"
|
|
407
|
-
placeholder="Search contacts..."
|
|
408
|
-
options={contacts}
|
|
409
|
-
/>
|
|
410
|
-
);
|
|
648
|
+
return <Autocomplete label="Contact" placeholder="Search contacts..." options={contacts} />;
|
|
411
649
|
}
|
|
412
650
|
```
|
|
413
651
|
|
|
@@ -448,30 +686,46 @@ function LazyAutocomplete({ fetchOptions }) {
|
|
|
448
686
|
|
|
449
687
|
### Key Props
|
|
450
688
|
|
|
451
|
-
| Prop
|
|
452
|
-
|
|
|
453
|
-
| `options`
|
|
454
|
-
| `value`
|
|
455
|
-
| `defaultValue`
|
|
456
|
-
| `onChange`
|
|
457
|
-
| `onInputChange`
|
|
458
|
-
| `label`
|
|
459
|
-
| `placeholder`
|
|
460
|
-
| `loading`
|
|
461
|
-
| `multiple`
|
|
462
|
-
| `groupBy`
|
|
463
|
-
| `size`
|
|
464
|
-
| `
|
|
689
|
+
| Prop | Type | Default | Description |
|
|
690
|
+
| ----------------------- | -------------------------------------------------------------- | -------------- | ----------------------------------------------------- |
|
|
691
|
+
| `options` | `string[] \| OptionObject[]` | `[]` | Array of options (strings or objects) |
|
|
692
|
+
| `value` | `string \| string[]` | - | Selected value(s) for controlled mode |
|
|
693
|
+
| `defaultValue` | `string \| string[]` | - | Initial value for uncontrolled mode |
|
|
694
|
+
| `onChange` | `(event: { target: { value } }) => void` | - | Callback when selection changes |
|
|
695
|
+
| `onInputChange` | `(event: { target: { value } }) => void` | - | Callback when input text changes |
|
|
696
|
+
| `label` | `ReactNode` | - | Label text above the input |
|
|
697
|
+
| `placeholder` | `string` | - | Placeholder text when empty |
|
|
698
|
+
| `loading` | `boolean` | `false` | Show loading indicator |
|
|
699
|
+
| `multiple` | `boolean` | `false` | Allow multiple selections |
|
|
700
|
+
| `groupBy` | `(option) => string` | - | Function to group options |
|
|
701
|
+
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Input size |
|
|
702
|
+
| `variant` | `'outlined' \| 'soft' \| 'solid' \| 'plain'` | `'outlined'` | Visual style variant |
|
|
703
|
+
| `color` | `'primary' \| 'neutral' \| 'danger' \| 'success' \| 'warning'` | `'neutral'` | Color scheme |
|
|
704
|
+
| `disabled` | `boolean` | `false` | Disable the input |
|
|
705
|
+
| `readOnly` | `boolean` | `false` | Make the input read-only (focusable but not editable) |
|
|
706
|
+
| `required` | `boolean` | `false` | Mark the field as required |
|
|
707
|
+
| `error` | `boolean` | `false` | Indicate an error state |
|
|
708
|
+
| `helperText` | `ReactNode` | - | Helper text below the input |
|
|
709
|
+
| `freeSolo` | `boolean` | `false` | Allow arbitrary values not in the options list |
|
|
710
|
+
| `disableClearable` | `boolean` | `false` | Hide the clear (X) button |
|
|
711
|
+
| `noOptionsText` | `ReactNode` | `'No options'` | Text when no options match |
|
|
712
|
+
| `loadingText` | `ReactNode` | `'Loading…'` | Text while loading |
|
|
713
|
+
| `limitTags` | `number` | `-1` | Max visible tags in multiple mode (-1 for unlimited) |
|
|
714
|
+
| `autoHighlight` | `boolean` | `false` | Auto-highlight first option |
|
|
715
|
+
| `clearOnEscape` | `boolean` | `false` | Clear value on Escape key |
|
|
716
|
+
| `disableCloseOnSelect` | `boolean` | `false` | Keep popup open after selection |
|
|
717
|
+
| `openOnFocus` | `boolean` | `false` | Open popup on focus |
|
|
718
|
+
| `filterSelectedOptions` | `boolean` | `false` | Hide selected options from dropdown (multiple mode) |
|
|
465
719
|
|
|
466
720
|
### Option Object Structure
|
|
467
721
|
|
|
468
722
|
```tsx
|
|
469
723
|
interface OptionObject {
|
|
470
|
-
value: string;
|
|
471
|
-
label: string;
|
|
472
|
-
secondaryText?: string;
|
|
724
|
+
value: string; // Unique value identifier
|
|
725
|
+
label: string; // Display text
|
|
726
|
+
secondaryText?: string; // Secondary line of text
|
|
473
727
|
startDecorator?: ReactNode; // Content before label
|
|
474
|
-
endDecorator?: ReactNode;
|
|
728
|
+
endDecorator?: ReactNode; // Content after label
|
|
475
729
|
}
|
|
476
730
|
|
|
477
731
|
// Example option objects
|
|
@@ -496,10 +750,7 @@ const options = [
|
|
|
496
750
|
|
|
497
751
|
```tsx
|
|
498
752
|
// Simplest usage with string array
|
|
499
|
-
<Autocomplete
|
|
500
|
-
label="Fruit"
|
|
501
|
-
options={['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry']}
|
|
502
|
-
/>
|
|
753
|
+
<Autocomplete label="Fruit" options={['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry']} />
|
|
503
754
|
```
|
|
504
755
|
|
|
505
756
|
### Controlled vs Uncontrolled
|
|
@@ -509,23 +760,12 @@ const options = [
|
|
|
509
760
|
function ControlledExample() {
|
|
510
761
|
const [value, setValue] = useState<string | undefined>();
|
|
511
762
|
|
|
512
|
-
return (
|
|
513
|
-
<Autocomplete
|
|
514
|
-
value={value}
|
|
515
|
-
onChange={(e) => setValue(e.target.value)}
|
|
516
|
-
options={options}
|
|
517
|
-
/>
|
|
518
|
-
);
|
|
763
|
+
return <Autocomplete value={value} onChange={(e) => setValue(e.target.value)} options={options} />;
|
|
519
764
|
}
|
|
520
765
|
|
|
521
766
|
// Uncontrolled - internal state management
|
|
522
767
|
function UncontrolledExample() {
|
|
523
|
-
return
|
|
524
|
-
<Autocomplete
|
|
525
|
-
defaultValue="option-1"
|
|
526
|
-
options={options}
|
|
527
|
-
/>
|
|
528
|
-
);
|
|
768
|
+
return <Autocomplete defaultValue="option-1" options={options} />;
|
|
529
769
|
}
|
|
530
770
|
```
|
|
531
771
|
|
|
@@ -559,12 +799,7 @@ function UncontrolledExample() {
|
|
|
559
799
|
### Multiple Selection
|
|
560
800
|
|
|
561
801
|
```tsx
|
|
562
|
-
<Autocomplete
|
|
563
|
-
multiple
|
|
564
|
-
value={['option-1', 'option-2']}
|
|
565
|
-
options={options}
|
|
566
|
-
onChange={(e) => setValues(e.target.value)}
|
|
567
|
-
/>
|
|
802
|
+
<Autocomplete multiple value={['option-1', 'option-2']} options={options} onChange={(e) => setValues(e.target.value)} />
|
|
568
803
|
```
|
|
569
804
|
|
|
570
805
|
## Accessibility
|
|
@@ -583,7 +818,7 @@ Autocomplete includes comprehensive accessibility features:
|
|
|
583
818
|
- **Arrow Down**: Open dropdown / move to next option
|
|
584
819
|
- **Arrow Up**: Move to previous option
|
|
585
820
|
- **Enter**: Select focused option
|
|
586
|
-
- **Escape**: Close dropdown
|
|
821
|
+
- **Escape**: Close dropdown (clears value if `clearOnEscape` is enabled)
|
|
587
822
|
- **Tab**: Move focus out of component
|
|
588
823
|
- **Home**: Jump to first option
|
|
589
824
|
- **End**: Jump to last option
|
|
@@ -594,7 +829,7 @@ Autocomplete includes comprehensive accessibility features:
|
|
|
594
829
|
```tsx
|
|
595
830
|
// Proper labeling for screen readers
|
|
596
831
|
<Autocomplete
|
|
597
|
-
label="Select a country"
|
|
832
|
+
label="Select a country" // Announces: "Select a country, combobox"
|
|
598
833
|
placeholder="Search..."
|
|
599
834
|
options={countries}
|
|
600
835
|
/>
|
|
@@ -608,6 +843,11 @@ Autocomplete includes comprehensive accessibility features:
|
|
|
608
843
|
- Focus returns to input when selecting an option
|
|
609
844
|
- Clear visual focus indicators on all interactive elements
|
|
610
845
|
|
|
846
|
+
### Read Only vs Disabled Semantics
|
|
847
|
+
|
|
848
|
+
- **`readOnly`**: The input is focusable, its value can be copied, and screen readers announce it as read-only. Use when a value should be visible but not editable.
|
|
849
|
+
- **`disabled`**: The input is not focusable and is skipped in tab order. Use when the field is temporarily unavailable.
|
|
850
|
+
|
|
611
851
|
## Best Practices
|
|
612
852
|
|
|
613
853
|
### ✅ Do
|
|
@@ -616,32 +856,21 @@ Autocomplete includes comprehensive accessibility features:
|
|
|
616
856
|
|
|
617
857
|
```tsx
|
|
618
858
|
// ✅ Good: Clear label and placeholder
|
|
619
|
-
<Autocomplete
|
|
620
|
-
label="Shipping Country"
|
|
621
|
-
placeholder="Type to search countries..."
|
|
622
|
-
options={countries}
|
|
623
|
-
/>
|
|
859
|
+
<Autocomplete label="Shipping Country" placeholder="Type to search countries..." options={countries} />
|
|
624
860
|
```
|
|
625
861
|
|
|
626
862
|
2. **Show loading state during async operations**: Keep users informed
|
|
627
863
|
|
|
628
864
|
```tsx
|
|
629
865
|
// ✅ Good: Loading indicator while fetching
|
|
630
|
-
<Autocomplete
|
|
631
|
-
options={options}
|
|
632
|
-
loading={isLoading}
|
|
633
|
-
placeholder={isLoading ? 'Loading...' : 'Search...'}
|
|
634
|
-
/>
|
|
866
|
+
<Autocomplete options={options} loading={isLoading} placeholder={isLoading ? 'Loading...' : 'Search...'} />
|
|
635
867
|
```
|
|
636
868
|
|
|
637
869
|
3. **Use grouping for better organization**: Help users scan large lists
|
|
638
870
|
|
|
639
871
|
```tsx
|
|
640
872
|
// ✅ Good: Logical grouping
|
|
641
|
-
<Autocomplete
|
|
642
|
-
options={allProducts}
|
|
643
|
-
groupBy={(product) => product.category}
|
|
644
|
-
/>
|
|
873
|
+
<Autocomplete options={allProducts} groupBy={(product) => product.category} />
|
|
645
874
|
```
|
|
646
875
|
|
|
647
876
|
4. **Debounce API searches**: Prevent excessive requests
|
|
@@ -654,6 +883,20 @@ useEffect(() => {
|
|
|
654
883
|
}, [query]);
|
|
655
884
|
```
|
|
656
885
|
|
|
886
|
+
5. **Use `disableCloseOnSelect` with `multiple`**: Keep the popup open for batch selection
|
|
887
|
+
|
|
888
|
+
```tsx
|
|
889
|
+
// ✅ Good: Popup stays open for multiple selections
|
|
890
|
+
<Autocomplete multiple disableCloseOnSelect options={tags} />
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
6. **Set `limitTags` for constrained layouts**: Prevent tag overflow
|
|
894
|
+
|
|
895
|
+
```tsx
|
|
896
|
+
// ✅ Good: Show at most 3 tags, truncate the rest
|
|
897
|
+
<Autocomplete multiple limitTags={3} options={allTags} />
|
|
898
|
+
```
|
|
899
|
+
|
|
657
900
|
### ❌ Don't
|
|
658
901
|
|
|
659
902
|
1. **Don't use for small option sets**: Use Select instead
|
|
@@ -691,20 +934,36 @@ useEffect(() => {
|
|
|
691
934
|
|
|
692
935
|
```tsx
|
|
693
936
|
// ✅ Good: Handle empty results
|
|
694
|
-
<Autocomplete
|
|
695
|
-
options={filteredOptions}
|
|
696
|
-
noOptionsText="No matches found. Try a different search."
|
|
697
|
-
/>
|
|
937
|
+
<Autocomplete options={filteredOptions} noOptionsText="No matches found. Try a different search." />
|
|
698
938
|
```
|
|
699
939
|
|
|
700
940
|
4. **Don't block interaction during initial load**: Show placeholders
|
|
701
941
|
|
|
702
942
|
```tsx
|
|
703
943
|
// ❌ Bad: Blocking the entire form
|
|
704
|
-
{
|
|
944
|
+
{
|
|
945
|
+
loading ? <Spinner /> : <Autocomplete options={options} />;
|
|
946
|
+
}
|
|
705
947
|
|
|
706
948
|
// ✅ Good: Allow interaction with loading state
|
|
707
|
-
<Autocomplete options={options} loading={loading}
|
|
949
|
+
<Autocomplete options={options} loading={loading} />;
|
|
950
|
+
```
|
|
951
|
+
|
|
952
|
+
5. **Don't rely on `freeSolo` for validated input without extra handling**: Validate free-text values
|
|
953
|
+
|
|
954
|
+
```tsx
|
|
955
|
+
// ❌ Bad: No validation for free-text input
|
|
956
|
+
<Autocomplete freeSolo options={emails} />
|
|
957
|
+
|
|
958
|
+
// ✅ Good: Validate free-text input via onInputChange
|
|
959
|
+
<Autocomplete
|
|
960
|
+
freeSolo
|
|
961
|
+
options={emails}
|
|
962
|
+
onInputChange={(e) => {
|
|
963
|
+
const value = e.target.value;
|
|
964
|
+
setError(!isValidEmail(value));
|
|
965
|
+
}}
|
|
966
|
+
/>
|
|
708
967
|
```
|
|
709
968
|
|
|
710
969
|
## Performance Considerations
|
|
@@ -715,9 +974,7 @@ Autocomplete automatically virtualizes long lists for performance:
|
|
|
715
974
|
|
|
716
975
|
```tsx
|
|
717
976
|
// Handles 1000+ options efficiently
|
|
718
|
-
<Autocomplete
|
|
719
|
-
options={Array.from({ length: 1000 }, (_, i) => `Option ${i + 1}`)}
|
|
720
|
-
/>
|
|
977
|
+
<Autocomplete options={Array.from({ length: 1000 }, (_, i) => `Option ${i + 1}`)} />
|
|
721
978
|
```
|
|
722
979
|
|
|
723
980
|
### Debounce Search Requests
|
|
@@ -744,16 +1001,10 @@ function SearchAutocomplete({ fetchOptions }) {
|
|
|
744
1001
|
setLoading(false);
|
|
745
1002
|
}
|
|
746
1003
|
}, 300),
|
|
747
|
-
[fetchOptions]
|
|
1004
|
+
[fetchOptions],
|
|
748
1005
|
);
|
|
749
1006
|
|
|
750
|
-
return (
|
|
751
|
-
<Autocomplete
|
|
752
|
-
options={options}
|
|
753
|
-
loading={loading}
|
|
754
|
-
onInputChange={(e) => debouncedFetch(e.target.value)}
|
|
755
|
-
/>
|
|
756
|
-
);
|
|
1007
|
+
return <Autocomplete options={options} loading={loading} onInputChange={(e) => debouncedFetch(e.target.value)} />;
|
|
757
1008
|
}
|
|
758
1009
|
```
|
|
759
1010
|
|
|
@@ -769,10 +1020,10 @@ const options = useMemo(
|
|
|
769
1020
|
label: item.name,
|
|
770
1021
|
startDecorator: <StatusChip status={item.status} />,
|
|
771
1022
|
})),
|
|
772
|
-
[data]
|
|
1023
|
+
[data],
|
|
773
1024
|
);
|
|
774
1025
|
|
|
775
|
-
<Autocomplete options={options}
|
|
1026
|
+
<Autocomplete options={options} />;
|
|
776
1027
|
```
|
|
777
1028
|
|
|
778
1029
|
### Lazy Load Options
|
|
@@ -798,14 +1049,7 @@ function LazyLoadAutocomplete() {
|
|
|
798
1049
|
}
|
|
799
1050
|
};
|
|
800
1051
|
|
|
801
|
-
return
|
|
802
|
-
<Autocomplete
|
|
803
|
-
options={options}
|
|
804
|
-
loading={loading}
|
|
805
|
-
onFocus={loadOptions}
|
|
806
|
-
onOpen={loadOptions}
|
|
807
|
-
/>
|
|
808
|
-
);
|
|
1052
|
+
return <Autocomplete options={options} loading={loading} onFocus={loadOptions} onOpen={loadOptions} />;
|
|
809
1053
|
}
|
|
810
1054
|
```
|
|
811
1055
|
|
|
@@ -833,7 +1077,7 @@ const options = useMemo(
|
|
|
833
1077
|
label: user.name,
|
|
834
1078
|
startDecorator: <MemoizedOption option={user} />,
|
|
835
1079
|
})),
|
|
836
|
-
[users]
|
|
1080
|
+
[users],
|
|
837
1081
|
);
|
|
838
1082
|
```
|
|
839
1083
|
|