@delightui/components 0.1.109 → 0.1.111

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 (39) hide show
  1. package/dist/cjs/components/molecules/Search/Search.d.ts +18 -0
  2. package/dist/cjs/components/molecules/Search/Search.presenter.d.ts +320 -0
  3. package/dist/cjs/components/molecules/Search/Search.types.d.ts +53 -0
  4. package/dist/cjs/components/molecules/Search/index.d.ts +2 -0
  5. package/dist/cjs/components/molecules/index.d.ts +2 -0
  6. package/dist/cjs/components/utils/RenderStateView/RenderStateView.types.d.ts +4 -0
  7. package/dist/cjs/components/utils/RenderStateView/usePresenter.d.ts +1 -0
  8. package/dist/cjs/components/utils/index.d.ts +2 -0
  9. package/dist/cjs/components/utils/useDebounce/index.d.ts +1 -0
  10. package/dist/cjs/components/utils/useDebounce/useDebounce.d.ts +10 -0
  11. package/dist/cjs/components/utils/useInflateView/index.d.ts +0 -1
  12. package/dist/cjs/components/utils/useInflateView/useInflateView.d.ts +1 -1
  13. package/dist/cjs/library.css +66 -0
  14. package/dist/cjs/library.js +3 -3
  15. package/dist/cjs/library.js.map +1 -1
  16. package/dist/esm/components/molecules/Search/Search.d.ts +18 -0
  17. package/dist/esm/components/molecules/Search/Search.presenter.d.ts +320 -0
  18. package/dist/esm/components/molecules/Search/Search.types.d.ts +53 -0
  19. package/dist/esm/components/molecules/Search/index.d.ts +2 -0
  20. package/dist/esm/components/molecules/index.d.ts +2 -0
  21. package/dist/esm/components/utils/RenderStateView/RenderStateView.types.d.ts +4 -0
  22. package/dist/esm/components/utils/RenderStateView/usePresenter.d.ts +1 -0
  23. package/dist/esm/components/utils/index.d.ts +2 -0
  24. package/dist/esm/components/utils/useDebounce/index.d.ts +1 -0
  25. package/dist/esm/components/utils/useDebounce/useDebounce.d.ts +10 -0
  26. package/dist/esm/components/utils/useInflateView/index.d.ts +0 -1
  27. package/dist/esm/components/utils/useInflateView/useInflateView.d.ts +1 -1
  28. package/dist/esm/library.css +66 -0
  29. package/dist/esm/library.js +3 -3
  30. package/dist/esm/library.js.map +1 -1
  31. package/dist/index.d.ts +83 -10
  32. package/docs/README.md +6 -0
  33. package/docs/components/atoms/Input.md +0 -63
  34. package/docs/components/molecules/Search.md +710 -0
  35. package/docs/components/utils/RenderStateView.md +137 -38
  36. package/docs/components/utils/useDebounce.md +576 -0
  37. package/package.json +1 -1
  38. package/dist/cjs/components/utils/useInflateView/useInflateView.types.d.ts +0 -12
  39. package/dist/esm/components/utils/useInflateView/useInflateView.types.d.ts +0 -12
@@ -0,0 +1,710 @@
1
+ # Search
2
+
3
+ ## Description
4
+
5
+ A versatile search input component that supports both automatic and manual search modes. In auto mode, searches are triggered automatically with debouncing as the user types. In manual mode, searches are triggered only when the user presses Enter or clicks the search button. The component includes built-in search, clear, and loading states with full keyboard navigation support.
6
+
7
+ ## Aliases
8
+
9
+ - Search
10
+ - SearchInput
11
+ - SearchField
12
+ - SearchBox
13
+ - QueryInput
14
+
15
+ ## Props Breakdown
16
+
17
+ **Extends:** `ControlledFormComponentProps<string>` & `Omit<InputHTMLAttributes<HTMLInputElement>, 'type' | 'value'>`
18
+
19
+ | Prop | Type | Default | Required | Description |
20
+ |------|------|---------|----------|-------------|
21
+ | `mode` | `'Auto' \| 'Manual'` | `'Auto'` | No | Search mode - 'Auto' for debounced search on input, 'Manual' for search on enter/submit |
22
+ | `onSearch` | `SearchCallback` | - | Yes | Callback function to handle search with the query string |
23
+ | `debounceMs` | `number` | `300` | No | Debounce delay in milliseconds for auto mode |
24
+ | `minCharacters` | `number` | `1` | No | Minimum characters required to trigger search in auto mode |
25
+ | `showSubmitButton` | `boolean` | `true` | No | Show submit button in manual mode |
26
+ | `showClearButton` | `boolean` | `true` | No | Show clear button when there is text |
27
+ | `loading` | `boolean` | `false` | No | Loading state while search is in progress |
28
+ | `component-variant` | `string` | - | No | Provide a way to override the styling |
29
+
30
+ ## Examples
31
+
32
+ ### Basic Auto Search
33
+ ```tsx
34
+ import { Search } from '@delightui/components';
35
+ import { useState } from 'react';
36
+
37
+ function BasicAutoSearch() {
38
+ const [results, setResults] = useState([]);
39
+ const [loading, setLoading] = useState(false);
40
+
41
+ const handleSearch = async (query: string) => {
42
+ setLoading(true);
43
+ try {
44
+ // Simulate API call
45
+ const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
46
+ const data = await response.json();
47
+ setResults(data.results);
48
+ } finally {
49
+ setLoading(false);
50
+ }
51
+ };
52
+
53
+ return (
54
+ <div>
55
+ <Search
56
+ mode="Auto"
57
+ onSearch={handleSearch}
58
+ loading={loading}
59
+ placeholder="Search products..."
60
+ minCharacters={2}
61
+ debounceMs={500}
62
+ />
63
+ {results.length > 0 && (
64
+ <div className="search-results">
65
+ {results.map(result => (
66
+ <div key={result.id}>{result.name}</div>
67
+ ))}
68
+ </div>
69
+ )}
70
+ </div>
71
+ );
72
+ }
73
+ ```
74
+
75
+ ### Manual Search with Validation
76
+ ```tsx
77
+ function ManualSearchExample() {
78
+ const [query, setQuery] = useState('');
79
+ const [results, setResults] = useState([]);
80
+ const [error, setError] = useState('');
81
+
82
+ const handleSearch = async (searchQuery: string) => {
83
+ if (searchQuery.length < 3) {
84
+ setError('Search query must be at least 3 characters');
85
+ return;
86
+ }
87
+
88
+ setError('');
89
+ try {
90
+ const response = await fetch(`/api/search?q=${encodeURIComponent(searchQuery)}`);
91
+ if (!response.ok) throw new Error('Search failed');
92
+
93
+ const data = await response.json();
94
+ setResults(data.results);
95
+ } catch (err) {
96
+ setError('Search failed. Please try again.');
97
+ }
98
+ };
99
+
100
+ return (
101
+ <div>
102
+ <Search
103
+ mode="Manual"
104
+ value={query}
105
+ onValueChange={setQuery}
106
+ onSearch={handleSearch}
107
+ placeholder="Enter search terms and press Enter..."
108
+ minCharacters={3}
109
+ showSubmitButton={true}
110
+ />
111
+ {error && <div className="error">{error}</div>}
112
+ {results.length > 0 && (
113
+ <div className="results-count">
114
+ Found {results.length} results
115
+ </div>
116
+ )}
117
+ </div>
118
+ );
119
+ }
120
+ ```
121
+
122
+ ### Advanced Search with Filters
123
+ ```tsx
124
+ function AdvancedSearchExample() {
125
+ const [searchState, setSearchState] = useState({
126
+ query: '',
127
+ filters: {
128
+ category: '',
129
+ priceRange: '',
130
+ sortBy: 'relevance'
131
+ },
132
+ results: [],
133
+ loading: false,
134
+ hasSearched: false
135
+ });
136
+
137
+ const performSearch = async (query: string) => {
138
+ setSearchState(prev => ({ ...prev, loading: true }));
139
+
140
+ try {
141
+ const params = new URLSearchParams({
142
+ q: query,
143
+ category: searchState.filters.category,
144
+ priceRange: searchState.filters.priceRange,
145
+ sortBy: searchState.filters.sortBy
146
+ });
147
+
148
+ const response = await fetch(`/api/search?${params}`);
149
+ const data = await response.json();
150
+
151
+ setSearchState(prev => ({
152
+ ...prev,
153
+ results: data.results,
154
+ loading: false,
155
+ hasSearched: true
156
+ }));
157
+ } catch (error) {
158
+ setSearchState(prev => ({
159
+ ...prev,
160
+ loading: false,
161
+ hasSearched: true,
162
+ results: []
163
+ }));
164
+ }
165
+ };
166
+
167
+ const updateFilter = (key: string, value: string) => {
168
+ setSearchState(prev => ({
169
+ ...prev,
170
+ filters: { ...prev.filters, [key]: value }
171
+ }));
172
+
173
+ // Re-search if we have a query
174
+ if (searchState.query) {
175
+ performSearch(searchState.query);
176
+ }
177
+ };
178
+
179
+ return (
180
+ <div className="advanced-search">
181
+ <div className="search-header">
182
+ <Search
183
+ mode="Auto"
184
+ value={searchState.query}
185
+ onValueChange={(query) => setSearchState(prev => ({ ...prev, query }))}
186
+ onSearch={performSearch}
187
+ loading={searchState.loading}
188
+ placeholder="Search for products..."
189
+ debounceMs={400}
190
+ minCharacters={2}
191
+ />
192
+ </div>
193
+
194
+ <div className="search-filters">
195
+ <Select
196
+ value={searchState.filters.category}
197
+ onValueChange={(value) => updateFilter('category', value)}
198
+ placeholder="All Categories"
199
+ >
200
+ <Option value="">All Categories</Option>
201
+ <Option value="electronics">Electronics</Option>
202
+ <Option value="clothing">Clothing</Option>
203
+ <Option value="books">Books</Option>
204
+ </Select>
205
+
206
+ <Select
207
+ value={searchState.filters.priceRange}
208
+ onValueChange={(value) => updateFilter('priceRange', value)}
209
+ placeholder="Any Price"
210
+ >
211
+ <Option value="">Any Price</Option>
212
+ <Option value="0-25">Under $25</Option>
213
+ <Option value="25-100">$25 - $100</Option>
214
+ <Option value="100+">Over $100</Option>
215
+ </Select>
216
+
217
+ <Select
218
+ value={searchState.filters.sortBy}
219
+ onValueChange={(value) => updateFilter('sortBy', value)}
220
+ >
221
+ <Option value="relevance">Relevance</Option>
222
+ <Option value="price-low">Price: Low to High</Option>
223
+ <Option value="price-high">Price: High to Low</Option>
224
+ <Option value="newest">Newest First</Option>
225
+ </Select>
226
+ </div>
227
+
228
+ <div className="search-results">
229
+ {searchState.loading && <Spinner />}
230
+
231
+ {searchState.hasSearched && !searchState.loading && (
232
+ <>
233
+ <div className="results-summary">
234
+ {searchState.results.length > 0 ? (
235
+ <Text type="BodyMedium">
236
+ Found {searchState.results.length} results for "{searchState.query}"
237
+ </Text>
238
+ ) : (
239
+ <Text type="BodyMedium">
240
+ No results found for "{searchState.query}"
241
+ </Text>
242
+ )}
243
+ </div>
244
+
245
+ <div className="results-grid">
246
+ {searchState.results.map(product => (
247
+ <ProductCard key={product.id} product={product} />
248
+ ))}
249
+ </div>
250
+ </>
251
+ )}
252
+ </div>
253
+ </div>
254
+ );
255
+ }
256
+ ```
257
+
258
+ ### User/Contact Search
259
+ ```tsx
260
+ function UserSearchExample() {
261
+ const [selectedUsers, setSelectedUsers] = useState([]);
262
+ const [searchResults, setSearchResults] = useState([]);
263
+ const [searching, setSearching] = useState(false);
264
+
265
+ const searchUsers = async (query: string) => {
266
+ if (!query.trim()) {
267
+ setSearchResults([]);
268
+ return;
269
+ }
270
+
271
+ setSearching(true);
272
+ try {
273
+ const response = await fetch(`/api/users/search?q=${encodeURIComponent(query)}`);
274
+ const users = await response.json();
275
+
276
+ // Filter out already selected users
277
+ const availableUsers = users.filter(
278
+ user => !selectedUsers.find(selected => selected.id === user.id)
279
+ );
280
+
281
+ setSearchResults(availableUsers);
282
+ } finally {
283
+ setSearching(false);
284
+ }
285
+ };
286
+
287
+ const addUser = (user) => {
288
+ setSelectedUsers(prev => [...prev, user]);
289
+ setSearchResults([]);
290
+ };
291
+
292
+ const removeUser = (userId) => {
293
+ setSelectedUsers(prev => prev.filter(user => user.id !== userId));
294
+ };
295
+
296
+ return (
297
+ <div className="user-search">
298
+ <div className="search-section">
299
+ <Text type="Heading6">Add Team Members</Text>
300
+ <Search
301
+ mode="Auto"
302
+ onSearch={searchUsers}
303
+ loading={searching}
304
+ placeholder="Search by name or email..."
305
+ minCharacters={2}
306
+ debounceMs={300}
307
+ />
308
+ </div>
309
+
310
+ {searchResults.length > 0 && (
311
+ <div className="search-results">
312
+ <Text type="BodySmall">Search Results:</Text>
313
+ {searchResults.map(user => (
314
+ <div key={user.id} className="user-result">
315
+ <div className="user-info">
316
+ <img src={user.avatar} alt={user.name} className="avatar" />
317
+ <div>
318
+ <Text type="BodyMedium">{user.name}</Text>
319
+ <Text type="BodySmall">{user.email}</Text>
320
+ </div>
321
+ </div>
322
+ <Button size="Small" onClick={() => addUser(user)}>
323
+ Add
324
+ </Button>
325
+ </div>
326
+ ))}
327
+ </div>
328
+ )}
329
+
330
+ {selectedUsers.length > 0 && (
331
+ <div className="selected-users">
332
+ <Text type="BodySmall">Selected Members ({selectedUsers.length}):</Text>
333
+ <div className="user-chips">
334
+ {selectedUsers.map(user => (
335
+ <Chip
336
+ key={user.id}
337
+ onRemove={() => removeUser(user.id)}
338
+ >
339
+ {user.name}
340
+ </Chip>
341
+ ))}
342
+ </div>
343
+ </div>
344
+ )}
345
+ </div>
346
+ );
347
+ }
348
+ ```
349
+
350
+ ### Document/File Search
351
+ ```tsx
352
+ function DocumentSearchExample() {
353
+ const [searchState, setSearchState] = useState({
354
+ query: '',
355
+ documents: [],
356
+ loading: false,
357
+ selectedDoc: null
358
+ });
359
+
360
+ const searchDocuments = async (query: string) => {
361
+ setSearchState(prev => ({ ...prev, loading: true }));
362
+
363
+ try {
364
+ const response = await fetch(`/api/documents/search`, {
365
+ method: 'POST',
366
+ headers: { 'Content-Type': 'application/json' },
367
+ body: JSON.stringify({
368
+ query,
369
+ filters: {
370
+ fileTypes: ['pdf', 'doc', 'txt'],
371
+ dateRange: 'last-month'
372
+ }
373
+ })
374
+ });
375
+
376
+ const results = await response.json();
377
+ setSearchState(prev => ({
378
+ ...prev,
379
+ documents: results.documents,
380
+ loading: false
381
+ }));
382
+ } catch (error) {
383
+ setSearchState(prev => ({
384
+ ...prev,
385
+ documents: [],
386
+ loading: false
387
+ }));
388
+ }
389
+ };
390
+
391
+ const openDocument = (doc) => {
392
+ setSearchState(prev => ({ ...prev, selectedDoc: doc }));
393
+ };
394
+
395
+ return (
396
+ <div className="document-search">
397
+ <div className="search-bar">
398
+ <Search
399
+ mode="Auto"
400
+ value={searchState.query}
401
+ onValueChange={(query) => setSearchState(prev => ({ ...prev, query }))}
402
+ onSearch={searchDocuments}
403
+ loading={searchState.loading}
404
+ placeholder="Search documents, files, and content..."
405
+ debounceMs={600}
406
+ minCharacters={3}
407
+ />
408
+ </div>
409
+
410
+ <div className="search-results">
411
+ {searchState.documents.length > 0 && (
412
+ <>
413
+ <div className="results-header">
414
+ <Text type="BodyMedium">
415
+ {searchState.documents.length} documents found
416
+ </Text>
417
+ </div>
418
+
419
+ <div className="document-list">
420
+ {searchState.documents.map(doc => (
421
+ <div key={doc.id} className="document-item">
422
+ <div className="doc-icon">
423
+ <Icon icon={getFileIcon(doc.type)} />
424
+ </div>
425
+ <div className="doc-info">
426
+ <Text type="BodyMedium">{doc.title}</Text>
427
+ <Text type="BodySmall">{doc.summary}</Text>
428
+ <div className="doc-meta">
429
+ <Text type="BodySmall">
430
+ {doc.type.toUpperCase()} • {formatFileSize(doc.size)} • {formatDate(doc.modified)}
431
+ </Text>
432
+ </div>
433
+ </div>
434
+ <div className="doc-actions">
435
+ <Button size="Small" onClick={() => openDocument(doc)}>
436
+ Open
437
+ </Button>
438
+ </div>
439
+ </div>
440
+ ))}
441
+ </div>
442
+ </>
443
+ )}
444
+
445
+ {searchState.query && searchState.documents.length === 0 && !searchState.loading && (
446
+ <div className="no-results">
447
+ <Text type="BodyMedium">No documents found for "{searchState.query}"</Text>
448
+ <Text type="BodySmall">Try different keywords or check your spelling</Text>
449
+ </div>
450
+ )}
451
+ </div>
452
+
453
+ {searchState.selectedDoc && (
454
+ <Modal onClose={() => setSearchState(prev => ({ ...prev, selectedDoc: null }))}>
455
+ <ModalHeader>
456
+ <Text type="Heading4">{searchState.selectedDoc.title}</Text>
457
+ </ModalHeader>
458
+ <div className="document-preview">
459
+ {/* Document content preview */}
460
+ </div>
461
+ </Modal>
462
+ )}
463
+ </div>
464
+ );
465
+ }
466
+ ```
467
+
468
+ ### Search with Recent History
469
+ ```tsx
470
+ function SearchWithHistoryExample() {
471
+ const [searchHistory, setSearchHistory] = useState([]);
472
+ const [showHistory, setShowHistory] = useState(false);
473
+ const [currentQuery, setCurrentQuery] = useState('');
474
+ const [results, setResults] = useState([]);
475
+
476
+ const addToHistory = (query: string) => {
477
+ if (!query.trim()) return;
478
+
479
+ setSearchHistory(prev => {
480
+ const filtered = prev.filter(item => item !== query);
481
+ return [query, ...filtered].slice(0, 5); // Keep last 5 searches
482
+ });
483
+ };
484
+
485
+ const performSearch = async (query: string) => {
486
+ if (!query.trim()) return;
487
+
488
+ addToHistory(query);
489
+ setShowHistory(false);
490
+
491
+ // Perform actual search
492
+ const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
493
+ const data = await response.json();
494
+ setResults(data.results);
495
+ };
496
+
497
+ const selectFromHistory = (query: string) => {
498
+ setCurrentQuery(query);
499
+ performSearch(query);
500
+ };
501
+
502
+ const clearHistory = () => {
503
+ setSearchHistory([]);
504
+ setShowHistory(false);
505
+ };
506
+
507
+ return (
508
+ <div className="search-with-history">
509
+ <div className="search-container">
510
+ <Search
511
+ mode="Manual"
512
+ value={currentQuery}
513
+ onValueChange={setCurrentQuery}
514
+ onSearch={performSearch}
515
+ placeholder="Search... (Press Enter)"
516
+ onFocus={() => setShowHistory(true)}
517
+ />
518
+
519
+ {showHistory && searchHistory.length > 0 && (
520
+ <div className="search-history">
521
+ <div className="history-header">
522
+ <Text type="BodySmall">Recent Searches</Text>
523
+ <Button size="Small" type="Ghost" onClick={clearHistory}>
524
+ Clear
525
+ </Button>
526
+ </div>
527
+ <div className="history-items">
528
+ {searchHistory.map((query, index) => (
529
+ <div
530
+ key={index}
531
+ className="history-item"
532
+ onClick={() => selectFromHistory(query)}
533
+ >
534
+ <Icon icon="Search" size="Small" />
535
+ <Text type="BodyMedium">{query}</Text>
536
+ </div>
537
+ ))}
538
+ </div>
539
+ </div>
540
+ )}
541
+ </div>
542
+
543
+ <div className="search-results">
544
+ {results.map(result => (
545
+ <div key={result.id} className="result-item">
546
+ {/* Result content */}
547
+ </div>
548
+ ))}
549
+ </div>
550
+ </div>
551
+ );
552
+ }
553
+ ```
554
+
555
+ ### Real-time Search with Suggestions
556
+ ```tsx
557
+ function RealTimeSearchExample() {
558
+ const [searchState, setSearchState] = useState({
559
+ query: '',
560
+ suggestions: [],
561
+ results: [],
562
+ showSuggestions: false,
563
+ loading: false
564
+ });
565
+
566
+ const fetchSuggestions = async (query: string) => {
567
+ if (query.length < 2) {
568
+ setSearchState(prev => ({ ...prev, suggestions: [], showSuggestions: false }));
569
+ return;
570
+ }
571
+
572
+ try {
573
+ const response = await fetch(`/api/search/suggestions?q=${encodeURIComponent(query)}`);
574
+ const data = await response.json();
575
+
576
+ setSearchState(prev => ({
577
+ ...prev,
578
+ suggestions: data.suggestions,
579
+ showSuggestions: true
580
+ }));
581
+ } catch (error) {
582
+ console.error('Failed to fetch suggestions:', error);
583
+ }
584
+ };
585
+
586
+ const performFullSearch = async (query: string) => {
587
+ setSearchState(prev => ({
588
+ ...prev,
589
+ loading: true,
590
+ showSuggestions: false
591
+ }));
592
+
593
+ try {
594
+ const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
595
+ const data = await response.json();
596
+
597
+ setSearchState(prev => ({
598
+ ...prev,
599
+ results: data.results,
600
+ loading: false
601
+ }));
602
+ } catch (error) {
603
+ setSearchState(prev => ({
604
+ ...prev,
605
+ results: [],
606
+ loading: false
607
+ }));
608
+ }
609
+ };
610
+
611
+ const handleQueryChange = (query: string) => {
612
+ setSearchState(prev => ({ ...prev, query }));
613
+ fetchSuggestions(query);
614
+ };
615
+
616
+ const selectSuggestion = (suggestion: string) => {
617
+ setSearchState(prev => ({
618
+ ...prev,
619
+ query: suggestion,
620
+ showSuggestions: false
621
+ }));
622
+ performFullSearch(suggestion);
623
+ };
624
+
625
+ return (
626
+ <div className="realtime-search">
627
+ <div className="search-input-container">
628
+ <Search
629
+ mode="Auto"
630
+ value={searchState.query}
631
+ onValueChange={handleQueryChange}
632
+ onSearch={performFullSearch}
633
+ loading={searchState.loading}
634
+ placeholder="Start typing to see suggestions..."
635
+ debounceMs={200}
636
+ minCharacters={2}
637
+ />
638
+
639
+ {searchState.showSuggestions && searchState.suggestions.length > 0 && (
640
+ <div className="suggestions-dropdown">
641
+ {searchState.suggestions.map((suggestion, index) => (
642
+ <div
643
+ key={index}
644
+ className="suggestion-item"
645
+ onClick={() => selectSuggestion(suggestion.text)}
646
+ >
647
+ <Icon icon="Search" size="Small" />
648
+ <div className="suggestion-content">
649
+ <Text type="BodyMedium">{suggestion.text}</Text>
650
+ {suggestion.category && (
651
+ <Text type="BodySmall">in {suggestion.category}</Text>
652
+ )}
653
+ </div>
654
+ {suggestion.count && (
655
+ <Text type="BodySmall">{suggestion.count} results</Text>
656
+ )}
657
+ </div>
658
+ ))}
659
+ </div>
660
+ )}
661
+ </div>
662
+
663
+ <div className="search-results">
664
+ {searchState.results.map(result => (
665
+ <div key={result.id} className="search-result">
666
+ <Text type="Heading6">{result.title}</Text>
667
+ <Text type="BodyMedium">{result.description}</Text>
668
+ <Text type="BodySmall">{result.url}</Text>
669
+ </div>
670
+ ))}
671
+ </div>
672
+ </div>
673
+ );
674
+ }
675
+ ```
676
+
677
+ ## Search Modes
678
+
679
+ ### Auto Mode
680
+ - Searches automatically as user types
681
+ - Uses debouncing to prevent excessive API calls
682
+ - Configurable minimum character threshold
683
+ - Ideal for real-time search experiences
684
+
685
+ ### Manual Mode
686
+ - Searches only when user presses Enter or clicks search button
687
+ - Better for expensive search operations
688
+ - Gives users full control over when to search
689
+ - Includes visual submit button
690
+
691
+ ## Accessibility Features
692
+
693
+ - Full keyboard navigation support
694
+ - ARIA labels for screen readers
695
+ - Focus management for search and clear buttons
696
+ - Semantic form structure in manual mode
697
+
698
+ ## Performance Considerations
699
+
700
+ - Built-in debouncing for auto mode prevents API spam
701
+ - Duplicate search prevention
702
+ - Efficient state management with presenter pattern
703
+ - Memoized components to prevent unnecessary re-renders
704
+
705
+ ## Related Components
706
+
707
+ - **[Input](Input.md)** - Base input component
708
+ - **[Button](../atoms/Button.md)** - Submit button component
709
+ - **[IconButton](../atoms/IconButton.md)** - Clear button component
710
+ - **[Icon](../atoms/Icon.md)** - Search and close icons