@ceed/ads 1.20.0 → 1.20.1-next.1

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.
Files changed (30) hide show
  1. package/dist/components/ProfileMenu/ProfileMenu.d.ts +1 -1
  2. package/dist/components/data-display/Markdown.md +832 -0
  3. package/dist/components/feedback/Dialog.md +605 -3
  4. package/dist/components/feedback/Modal.md +656 -24
  5. package/dist/components/feedback/llms.txt +1 -1
  6. package/dist/components/inputs/Autocomplete.md +734 -2
  7. package/dist/components/inputs/Calendar.md +655 -1
  8. package/dist/components/inputs/DatePicker.md +699 -3
  9. package/dist/components/inputs/DateRangePicker.md +815 -1
  10. package/dist/components/inputs/MonthPicker.md +626 -4
  11. package/dist/components/inputs/MonthRangePicker.md +682 -4
  12. package/dist/components/inputs/Select.md +600 -0
  13. package/dist/components/layout/Container.md +507 -0
  14. package/dist/components/navigation/Breadcrumbs.md +582 -0
  15. package/dist/components/navigation/IconMenuButton.md +693 -0
  16. package/dist/components/navigation/InsetDrawer.md +1150 -3
  17. package/dist/components/navigation/Link.md +526 -0
  18. package/dist/components/navigation/MenuButton.md +632 -0
  19. package/dist/components/navigation/NavigationGroup.md +401 -1
  20. package/dist/components/navigation/NavigationItem.md +311 -0
  21. package/dist/components/navigation/Navigator.md +373 -0
  22. package/dist/components/navigation/Pagination.md +521 -0
  23. package/dist/components/navigation/ProfileMenu.md +605 -0
  24. package/dist/components/navigation/Tabs.md +609 -7
  25. package/dist/components/surfaces/Accordions.md +947 -3
  26. package/dist/index.cjs +3 -1
  27. package/dist/index.js +3 -1
  28. package/dist/llms.txt +1 -1
  29. package/framer/index.js +1 -1
  30. package/package.json +3 -2
@@ -2,6 +2,8 @@
2
2
 
3
3
  ## Introduction
4
4
 
5
+ Autocomplete is an enhanced input component that provides real-time suggestions as users type, helping them quickly find and select values from a predefined list. It combines the flexibility of a text input with the convenience of a dropdown selection, supporting features like filtering, keyboard navigation, custom option rendering, and grouping. Autocomplete is ideal for scenarios where users need to select from a large set of options, such as searching for countries, products, or users.
6
+
5
7
  ```tsx
6
8
  <Autocomplete options={['Option1', 'Option2']} />
7
9
  ```
@@ -11,8 +13,49 @@
11
13
  | label | — | — |
12
14
  | loading | — | — |
13
15
 
16
+ > ⚠️ **Usage Warning** ⚠️
17
+ >
18
+ > Choose the right input component for your use case:
19
+ >
20
+ > - **Autocomplete**: For searchable selection from large option lists (20+ items)
21
+ > - **Select**: For simple selection from small lists (under 20 items)
22
+ > - **Dropdown**: For action menus, not form value selection
23
+ > - **Input**: For free-text entry without predefined options
24
+
25
+ ## Usage
26
+
27
+ ```tsx
28
+ import { Autocomplete } from '@ceed/ads';
29
+
30
+ function CountrySelector() {
31
+ const [country, setCountry] = useState<string | undefined>();
32
+
33
+ return (
34
+ <Autocomplete
35
+ label="Country"
36
+ placeholder="Search countries..."
37
+ options={['United States', 'United Kingdom', 'Canada', 'Australia']}
38
+ value={country}
39
+ onChange={(e) => setCountry(e.target.value)}
40
+ />
41
+ );
42
+ }
43
+ ```
44
+
45
+ ## Examples
46
+
47
+ ### Playground
48
+
49
+ Interactive example with basic string options.
50
+
51
+ ```tsx
52
+ <Autocomplete options={['Option1', 'Option2']} />
53
+ ```
54
+
14
55
  ### Sizes
15
56
 
57
+ Available size options: `sm`, `md`, `lg`.
58
+
16
59
  ```tsx
17
60
  <div style={{
18
61
  display: 'flex',
@@ -26,6 +69,8 @@
26
69
 
27
70
  ### Option Groups
28
71
 
72
+ Group options into categories using the `groupBy` function.
73
+
29
74
  ```tsx
30
75
  <Autocomplete
31
76
  options={[{
@@ -50,7 +95,9 @@
50
95
  />
51
96
  ```
52
97
 
53
- ### WithCustomOptions
98
+ ### Custom Options
99
+
100
+ Options can include decorators for rich content display.
54
101
 
55
102
  ```tsx
56
103
  <Autocomplete
@@ -69,7 +116,9 @@
69
116
  />
70
117
  ```
71
118
 
72
- ### Loading
119
+ ### Loading State
120
+
121
+ Show a loading indicator while fetching options.
73
122
 
74
123
  ```tsx
75
124
  <Autocomplete
@@ -80,6 +129,8 @@
80
129
 
81
130
  ### Controlled
82
131
 
132
+ Manage value externally with controlled state.
133
+
83
134
  ```tsx
84
135
  <Stack gap={4}>
85
136
  <Autocomplete value={value} label="Select a brand" options={[{
@@ -101,3 +152,684 @@
101
152
  <Button onClick={() => setValue("Johnson's baby")}>Set Johnson's baby</Button>
102
153
  </Stack>
103
154
  ```
155
+
156
+ ## When to Use
157
+
158
+ ### ✅ Good Use Cases
159
+
160
+ - **Large option lists**: When users need to search through 20+ options
161
+ - **Dynamic data**: When options come from an API or database
162
+ - **User search**: Finding users, contacts, or entities by name
163
+ - **Location selection**: Countries, cities, addresses
164
+ - **Product search**: Searching product catalogs
165
+ - **Tag/category selection**: When users can search for categories
166
+ - **Form fields with many choices**: When a simple Select would be overwhelming
167
+
168
+ ### ❌ When Not to Use
169
+
170
+ - **Few options (\< 10)**: Use Select or RadioGroup instead
171
+ - **Free text entry**: Use Input if no predefined options exist
172
+ - **Action menus**: Use Dropdown or MenuButton for actions
173
+ - **Yes/No choices**: Use Switch or Checkbox
174
+ - **Numeric ranges**: Use Slider or NumberInput
175
+ - **Date selection**: Use DatePicker
176
+
177
+ ## Common Use Cases
178
+
179
+ ### Country Selector
180
+
181
+ ```tsx
182
+ function CountrySelector({ value, onChange }) {
183
+ const countries = [
184
+ { value: 'us', label: 'United States' },
185
+ { value: 'uk', label: 'United Kingdom' },
186
+ { value: 'ca', label: 'Canada' },
187
+ { value: 'au', label: 'Australia' },
188
+ { value: 'de', label: 'Germany' },
189
+ { value: 'fr', label: 'France' },
190
+ { value: 'jp', label: 'Japan' },
191
+ { value: 'kr', label: 'South Korea' },
192
+ ];
193
+
194
+ return (
195
+ <Autocomplete
196
+ label="Country"
197
+ placeholder="Select a country..."
198
+ options={countries}
199
+ value={value}
200
+ onChange={(e) => onChange(e.target.value)}
201
+ />
202
+ );
203
+ }
204
+ ```
205
+
206
+ ### User Search
207
+
208
+ ```tsx
209
+ function UserSearch({ onSelect }) {
210
+ const [users, setUsers] = useState([]);
211
+ const [loading, setLoading] = useState(false);
212
+ const [search, setSearch] = useState('');
213
+
214
+ useEffect(() => {
215
+ if (!search) {
216
+ setUsers([]);
217
+ return;
218
+ }
219
+
220
+ const fetchUsers = async () => {
221
+ setLoading(true);
222
+ try {
223
+ const response = await api.searchUsers(search);
224
+ setUsers(
225
+ response.data.map((user) => ({
226
+ value: user.id,
227
+ label: user.name,
228
+ secondaryText: user.email,
229
+ startDecorator: <Avatar src={user.avatar} size="sm" />,
230
+ }))
231
+ );
232
+ } finally {
233
+ setLoading(false);
234
+ }
235
+ };
236
+
237
+ const debounce = setTimeout(fetchUsers, 300);
238
+ return () => clearTimeout(debounce);
239
+ }, [search]);
240
+
241
+ return (
242
+ <Autocomplete
243
+ label="Search Users"
244
+ placeholder="Type to search..."
245
+ options={users}
246
+ loading={loading}
247
+ onInputChange={(e) => setSearch(e.target.value)}
248
+ onChange={(e) => onSelect(e.target.value)}
249
+ />
250
+ );
251
+ }
252
+ ```
253
+
254
+ ### Product Search with Categories
255
+
256
+ ```tsx
257
+ function ProductSearch({ products, onSelect }) {
258
+ const productOptions = products.map((product) => ({
259
+ value: product.id,
260
+ label: product.name,
261
+ startDecorator: (
262
+ <Chip size="sm" color={product.inStock ? 'success' : 'neutral'}>
263
+ {product.inStock ? 'In Stock' : 'Out of Stock'}
264
+ </Chip>
265
+ ),
266
+ endDecorator: (
267
+ <Typography level="body-sm" color="neutral">
268
+ ${product.price}
269
+ </Typography>
270
+ ),
271
+ }));
272
+
273
+ return (
274
+ <Autocomplete
275
+ label="Search Products"
276
+ placeholder="Enter product name..."
277
+ options={productOptions}
278
+ groupBy={(option) => option.category}
279
+ onChange={(e) => onSelect(e.target.value)}
280
+ />
281
+ );
282
+ }
283
+ ```
284
+
285
+ ### Address Autocomplete
286
+
287
+ ```tsx
288
+ function AddressAutocomplete({ onAddressSelect }) {
289
+ const [options, setOptions] = useState([]);
290
+ const [loading, setLoading] = useState(false);
291
+
292
+ const handleInputChange = async (e) => {
293
+ const query = e.target.value;
294
+ if (query.length < 3) {
295
+ setOptions([]);
296
+ return;
297
+ }
298
+
299
+ setLoading(true);
300
+ try {
301
+ const results = await geocodingApi.search(query);
302
+ setOptions(
303
+ results.map((result) => ({
304
+ value: result.placeId,
305
+ label: result.formattedAddress,
306
+ secondaryText: result.city + ', ' + result.country,
307
+ }))
308
+ );
309
+ } finally {
310
+ setLoading(false);
311
+ }
312
+ };
313
+
314
+ return (
315
+ <Autocomplete
316
+ label="Address"
317
+ placeholder="Start typing an address..."
318
+ options={options}
319
+ loading={loading}
320
+ onInputChange={handleInputChange}
321
+ onChange={(e) => {
322
+ const selected = options.find((opt) => opt.value === e.target.value);
323
+ onAddressSelect(selected);
324
+ }}
325
+ />
326
+ );
327
+ }
328
+ ```
329
+
330
+ ### Tag Selection (Multiple)
331
+
332
+ ```tsx
333
+ function TagSelector({ availableTags, selectedTags, onChange }) {
334
+ const tagOptions = availableTags.map((tag) => ({
335
+ value: tag.id,
336
+ label: tag.name,
337
+ startDecorator: (
338
+ <Box
339
+ sx={{
340
+ width: 12,
341
+ height: 12,
342
+ borderRadius: '50%',
343
+ bgcolor: tag.color,
344
+ }}
345
+ />
346
+ ),
347
+ }));
348
+
349
+ return (
350
+ <Autocomplete
351
+ label="Tags"
352
+ placeholder="Add tags..."
353
+ options={tagOptions}
354
+ value={selectedTags}
355
+ onChange={(e) => onChange(e.target.value)}
356
+ multiple
357
+ />
358
+ );
359
+ }
360
+ ```
361
+
362
+ ### Grouped Options
363
+
364
+ ```tsx
365
+ function CategorySelector() {
366
+ const items = [
367
+ { value: 'electronics-phone', label: 'Smartphones', category: 'Electronics' },
368
+ { value: 'electronics-laptop', label: 'Laptops', category: 'Electronics' },
369
+ { value: 'electronics-tablet', label: 'Tablets', category: 'Electronics' },
370
+ { value: 'clothing-shirt', label: 'Shirts', category: 'Clothing' },
371
+ { value: 'clothing-pants', label: 'Pants', category: 'Clothing' },
372
+ { value: 'clothing-shoes', label: 'Shoes', category: 'Clothing' },
373
+ { value: 'home-furniture', label: 'Furniture', category: 'Home' },
374
+ { value: 'home-decor', label: 'Decor', category: 'Home' },
375
+ ];
376
+
377
+ return (
378
+ <Autocomplete
379
+ label="Category"
380
+ placeholder="Select a category..."
381
+ options={items}
382
+ groupBy={(option) => option.category}
383
+ />
384
+ );
385
+ }
386
+ ```
387
+
388
+ ### With Secondary Text
389
+
390
+ ```tsx
391
+ function ContactSelector() {
392
+ const contacts = [
393
+ { value: 'emily', label: 'Emily Carter', secondaryText: '(415) 555-0198' },
394
+ { value: 'daniel', label: 'Daniel Kim', secondaryText: '(212) 555-0421' },
395
+ { value: 'sophia', label: 'Sophia Martinez', secondaryText: '(646) 555-0734' },
396
+ { value: 'michael', label: 'Michael Chen', secondaryText: '(646) 555-0876' },
397
+ ];
398
+
399
+ return (
400
+ <Autocomplete
401
+ label="Contact"
402
+ placeholder="Search contacts..."
403
+ options={contacts}
404
+ />
405
+ );
406
+ }
407
+ ```
408
+
409
+ ### Lazy Loading Options
410
+
411
+ ```tsx
412
+ function LazyAutocomplete({ fetchOptions }) {
413
+ const [options, setOptions] = useState([]);
414
+ const [loading, setLoading] = useState(false);
415
+ const [hasLoaded, setHasLoaded] = useState(false);
416
+
417
+ const handleFocus = async () => {
418
+ if (hasLoaded) return;
419
+
420
+ setLoading(true);
421
+ try {
422
+ const data = await fetchOptions();
423
+ setOptions(data);
424
+ setHasLoaded(true);
425
+ } finally {
426
+ setLoading(false);
427
+ }
428
+ };
429
+
430
+ return (
431
+ <Autocomplete
432
+ label="Select Option"
433
+ placeholder="Click to load options..."
434
+ options={options}
435
+ loading={loading}
436
+ onFocus={handleFocus}
437
+ />
438
+ );
439
+ }
440
+ ```
441
+
442
+ ## Props and Customization
443
+
444
+ ### Key Props
445
+
446
+ | Prop | Type | Default | Description |
447
+ | --------------- | ---------------------------------------- | ------- | ------------------------------------- |
448
+ | `options` | `string[] \| OptionObject[]` | `[]` | Array of options (strings or objects) |
449
+ | `value` | `string \| string[]` | - | Selected value(s) for controlled mode |
450
+ | `defaultValue` | `string \| string[]` | - | Initial value for uncontrolled mode |
451
+ | `onChange` | `(event: { target: { value } }) => void` | - | Callback when selection changes |
452
+ | `onInputChange` | `(event: { target: { value } }) => void` | - | Callback when input text changes |
453
+ | `label` | `string` | - | Label text above the input |
454
+ | `placeholder` | `string` | - | Placeholder text when empty |
455
+ | `loading` | `boolean` | `false` | Show loading indicator |
456
+ | `multiple` | `boolean` | `false` | Allow multiple selections |
457
+ | `groupBy` | `(option) => string` | - | Function to group options |
458
+ | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Input size |
459
+ | `disabled` | `boolean` | `false` | Disable the input |
460
+
461
+ ### Option Object Structure
462
+
463
+ ```tsx
464
+ interface OptionObject {
465
+ value: string; // Unique value identifier
466
+ label: string; // Display text
467
+ secondaryText?: string; // Secondary line of text
468
+ startDecorator?: ReactNode; // Content before label
469
+ endDecorator?: ReactNode; // Content after label
470
+ }
471
+
472
+ // Example option objects
473
+ const options = [
474
+ {
475
+ value: 'user-1',
476
+ label: 'John Doe',
477
+ secondaryText: 'john@example.com',
478
+ startDecorator: <Avatar src="/john.jpg" size="sm" />,
479
+ endDecorator: <Chip size="sm">Admin</Chip>,
480
+ },
481
+ {
482
+ value: 'user-2',
483
+ label: 'Jane Smith',
484
+ secondaryText: 'jane@example.com',
485
+ startDecorator: <Avatar src="/jane.jpg" size="sm" />,
486
+ },
487
+ ];
488
+ ```
489
+
490
+ ### Simple String Options
491
+
492
+ ```tsx
493
+ // Simplest usage with string array
494
+ <Autocomplete
495
+ label="Fruit"
496
+ options={['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry']}
497
+ />
498
+ ```
499
+
500
+ ### Controlled vs Uncontrolled
501
+
502
+ ```tsx
503
+ // Controlled - manage value externally
504
+ function ControlledExample() {
505
+ const [value, setValue] = useState<string | undefined>();
506
+
507
+ return (
508
+ <Autocomplete
509
+ value={value}
510
+ onChange={(e) => setValue(e.target.value)}
511
+ options={options}
512
+ />
513
+ );
514
+ }
515
+
516
+ // Uncontrolled - internal state management
517
+ function UncontrolledExample() {
518
+ return (
519
+ <Autocomplete
520
+ defaultValue="option-1"
521
+ options={options}
522
+ />
523
+ );
524
+ }
525
+ ```
526
+
527
+ ### Size Options
528
+
529
+ ```tsx
530
+ // Small
531
+ <Autocomplete options={options} size="sm" />
532
+
533
+ // Medium (default)
534
+ <Autocomplete options={options} size="md" />
535
+
536
+ // Large
537
+ <Autocomplete options={options} size="lg" />
538
+ ```
539
+
540
+ ### With Grouping
541
+
542
+ ```tsx
543
+ <Autocomplete
544
+ options={[
545
+ { value: 'a1', label: 'Apple', category: 'Fruits' },
546
+ { value: 'b1', label: 'Banana', category: 'Fruits' },
547
+ { value: 'c1', label: 'Carrot', category: 'Vegetables' },
548
+ { value: 'b2', label: 'Broccoli', category: 'Vegetables' },
549
+ ]}
550
+ groupBy={(option) => option.category}
551
+ />
552
+ ```
553
+
554
+ ### Multiple Selection
555
+
556
+ ```tsx
557
+ <Autocomplete
558
+ multiple
559
+ value={['option-1', 'option-2']}
560
+ options={options}
561
+ onChange={(e) => setValues(e.target.value)}
562
+ />
563
+ ```
564
+
565
+ ## Accessibility
566
+
567
+ Autocomplete includes comprehensive accessibility features:
568
+
569
+ ### ARIA Attributes
570
+
571
+ - Input has `role="combobox"` with `aria-autocomplete="list"`
572
+ - Listbox has `role="listbox"` with proper `aria-label`
573
+ - Options have `role="option"` with `aria-selected` state
574
+ - Groups are announced with proper hierarchy
575
+
576
+ ### Keyboard Navigation
577
+
578
+ - **Arrow Down**: Open dropdown / move to next option
579
+ - **Arrow Up**: Move to previous option
580
+ - **Enter**: Select focused option
581
+ - **Escape**: Close dropdown
582
+ - **Tab**: Move focus out of component
583
+ - **Home**: Jump to first option
584
+ - **End**: Jump to last option
585
+ - **Type ahead**: Filter options by typing
586
+
587
+ ### Screen Reader Support
588
+
589
+ ```tsx
590
+ // Proper labeling for screen readers
591
+ <Autocomplete
592
+ label="Select a country" // Announces: "Select a country, combobox"
593
+ placeholder="Search..."
594
+ options={countries}
595
+ />
596
+
597
+ // Options announce: "United States, option, 1 of 10"
598
+ ```
599
+
600
+ ### Focus Management
601
+
602
+ - Focus automatically moves to input when dropdown opens
603
+ - Focus returns to input when selecting an option
604
+ - Clear visual focus indicators on all interactive elements
605
+
606
+ ## Best Practices
607
+
608
+ ### ✅ Do
609
+
610
+ 1. **Provide helpful labels and placeholders**: Guide users on what to search
611
+
612
+ ```tsx
613
+ // ✅ Good: Clear label and placeholder
614
+ <Autocomplete
615
+ label="Shipping Country"
616
+ placeholder="Type to search countries..."
617
+ options={countries}
618
+ />
619
+ ```
620
+
621
+ 2. **Show loading state during async operations**: Keep users informed
622
+
623
+ ```tsx
624
+ // ✅ Good: Loading indicator while fetching
625
+ <Autocomplete
626
+ options={options}
627
+ loading={isLoading}
628
+ placeholder={isLoading ? 'Loading...' : 'Search...'}
629
+ />
630
+ ```
631
+
632
+ 3. **Use grouping for better organization**: Help users scan large lists
633
+
634
+ ```tsx
635
+ // ✅ Good: Logical grouping
636
+ <Autocomplete
637
+ options={allProducts}
638
+ groupBy={(product) => product.category}
639
+ />
640
+ ```
641
+
642
+ 4. **Debounce API searches**: Prevent excessive requests
643
+
644
+ ```tsx
645
+ // ✅ Good: Debounced search
646
+ useEffect(() => {
647
+ const timer = setTimeout(() => fetchResults(query), 300);
648
+ return () => clearTimeout(timer);
649
+ }, [query]);
650
+ ```
651
+
652
+ ### ❌ Don't
653
+
654
+ 1. **Don't use for small option sets**: Use Select instead
655
+
656
+ ```tsx
657
+ // ❌ Bad: Too few options for Autocomplete
658
+ <Autocomplete options={['Yes', 'No']} />
659
+
660
+ // ✅ Good: Use Select or RadioGroup
661
+ <Select>
662
+ <Option value="yes">Yes</Option>
663
+ <Option value="no">No</Option>
664
+ </Select>
665
+ ```
666
+
667
+ 2. **Don't hide important selection context**: Show enough information
668
+
669
+ ```tsx
670
+ // ❌ Bad: Not enough context
671
+ <Autocomplete
672
+ options={users.map((u) => ({ value: u.id, label: u.name }))}
673
+ />
674
+
675
+ // ✅ Good: Include helpful secondary info
676
+ <Autocomplete
677
+ options={users.map((u) => ({
678
+ value: u.id,
679
+ label: u.name,
680
+ secondaryText: u.email,
681
+ }))}
682
+ />
683
+ ```
684
+
685
+ 3. **Don't forget empty states**: Handle no results gracefully
686
+
687
+ ```tsx
688
+ // ✅ Good: Handle empty results
689
+ <Autocomplete
690
+ options={filteredOptions}
691
+ noOptionsText="No matches found. Try a different search."
692
+ />
693
+ ```
694
+
695
+ 4. **Don't block interaction during initial load**: Show placeholders
696
+
697
+ ```tsx
698
+ // ❌ Bad: Blocking the entire form
699
+ {loading ? <Spinner /> : <Autocomplete options={options} />}
700
+
701
+ // ✅ Good: Allow interaction with loading state
702
+ <Autocomplete options={options} loading={loading} />
703
+ ```
704
+
705
+ ## Performance Considerations
706
+
707
+ ### Built-in Virtualization
708
+
709
+ Autocomplete automatically virtualizes long lists for performance:
710
+
711
+ ```tsx
712
+ // Handles 1000+ options efficiently
713
+ <Autocomplete
714
+ options={Array.from({ length: 1000 }, (_, i) => `Option ${i + 1}`)}
715
+ />
716
+ ```
717
+
718
+ ### Debounce Search Requests
719
+
720
+ Prevent excessive API calls:
721
+
722
+ ```tsx
723
+ function SearchAutocomplete({ fetchOptions }) {
724
+ const [options, setOptions] = useState([]);
725
+ const [loading, setLoading] = useState(false);
726
+
727
+ const debouncedFetch = useMemo(
728
+ () =>
729
+ debounce(async (query: string) => {
730
+ if (!query) {
731
+ setOptions([]);
732
+ return;
733
+ }
734
+ setLoading(true);
735
+ try {
736
+ const results = await fetchOptions(query);
737
+ setOptions(results);
738
+ } finally {
739
+ setLoading(false);
740
+ }
741
+ }, 300),
742
+ [fetchOptions]
743
+ );
744
+
745
+ return (
746
+ <Autocomplete
747
+ options={options}
748
+ loading={loading}
749
+ onInputChange={(e) => debouncedFetch(e.target.value)}
750
+ />
751
+ );
752
+ }
753
+ ```
754
+
755
+ ### Memoize Options
756
+
757
+ Prevent unnecessary re-renders:
758
+
759
+ ```tsx
760
+ const options = useMemo(
761
+ () =>
762
+ data.map((item) => ({
763
+ value: item.id,
764
+ label: item.name,
765
+ startDecorator: <StatusChip status={item.status} />,
766
+ })),
767
+ [data]
768
+ );
769
+
770
+ <Autocomplete options={options} />
771
+ ```
772
+
773
+ ### Lazy Load Options
774
+
775
+ Fetch options only when needed:
776
+
777
+ ```tsx
778
+ function LazyLoadAutocomplete() {
779
+ const [options, setOptions] = useState([]);
780
+ const [loading, setLoading] = useState(false);
781
+ const [initialized, setInitialized] = useState(false);
782
+
783
+ const loadOptions = async () => {
784
+ if (initialized) return;
785
+
786
+ setLoading(true);
787
+ try {
788
+ const data = await fetchAllOptions();
789
+ setOptions(data);
790
+ setInitialized(true);
791
+ } finally {
792
+ setLoading(false);
793
+ }
794
+ };
795
+
796
+ return (
797
+ <Autocomplete
798
+ options={options}
799
+ loading={loading}
800
+ onFocus={loadOptions}
801
+ onOpen={loadOptions}
802
+ />
803
+ );
804
+ }
805
+ ```
806
+
807
+ ### Optimize Option Rendering
808
+
809
+ For complex option content, memoize components:
810
+
811
+ ```tsx
812
+ const MemoizedOption = memo(({ option }) => (
813
+ <Stack direction="row" alignItems="center" gap={1}>
814
+ <Avatar src={option.avatar} size="sm" />
815
+ <Box>
816
+ <Typography level="body-sm">{option.name}</Typography>
817
+ <Typography level="body-xs" color="neutral">
818
+ {option.email}
819
+ </Typography>
820
+ </Box>
821
+ </Stack>
822
+ ));
823
+
824
+ const options = useMemo(
825
+ () =>
826
+ users.map((user) => ({
827
+ value: user.id,
828
+ label: user.name,
829
+ startDecorator: <MemoizedOption option={user} />,
830
+ })),
831
+ [users]
832
+ );
833
+ ```
834
+
835
+ Autocomplete provides a powerful search-and-select experience for large option sets. Use it when users benefit from filtering options by typing, and ensure you provide clear labels, handle loading states gracefully, and optimize performance for large datasets. For simpler use cases with fewer options, prefer Select instead.