@papernote/ui 1.3.1 → 1.6.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.
Files changed (108) hide show
  1. package/dist/components/ActionBar.d.ts +112 -0
  2. package/dist/components/ActionBar.d.ts.map +1 -0
  3. package/dist/components/BottomNavigation.d.ts +98 -0
  4. package/dist/components/BottomNavigation.d.ts.map +1 -0
  5. package/dist/components/Checkbox.d.ts +2 -0
  6. package/dist/components/Checkbox.d.ts.map +1 -1
  7. package/dist/components/CheckboxList.d.ts +81 -0
  8. package/dist/components/CheckboxList.d.ts.map +1 -0
  9. package/dist/components/Chip.d.ts +92 -1
  10. package/dist/components/Chip.d.ts.map +1 -1
  11. package/dist/components/ConfirmDialog.d.ts +43 -1
  12. package/dist/components/ConfirmDialog.d.ts.map +1 -1
  13. package/dist/components/DataTable.d.ts +10 -1
  14. package/dist/components/DataTable.d.ts.map +1 -1
  15. package/dist/components/DataTableCardView.d.ts +99 -0
  16. package/dist/components/DataTableCardView.d.ts.map +1 -0
  17. package/dist/components/ExpandablePanel.d.ts +142 -0
  18. package/dist/components/ExpandablePanel.d.ts.map +1 -0
  19. package/dist/components/FloatingActionButton.d.ts +98 -0
  20. package/dist/components/FloatingActionButton.d.ts.map +1 -0
  21. package/dist/components/Input.d.ts +45 -1
  22. package/dist/components/Input.d.ts.map +1 -1
  23. package/dist/components/MobileHeader.d.ts +98 -0
  24. package/dist/components/MobileHeader.d.ts.map +1 -0
  25. package/dist/components/MobileLayout.d.ts +121 -0
  26. package/dist/components/MobileLayout.d.ts.map +1 -0
  27. package/dist/components/Modal.d.ts +78 -1
  28. package/dist/components/Modal.d.ts.map +1 -1
  29. package/dist/components/PageHeader.d.ts +86 -0
  30. package/dist/components/PageHeader.d.ts.map +1 -0
  31. package/dist/components/PullToRefresh.d.ts +87 -0
  32. package/dist/components/PullToRefresh.d.ts.map +1 -0
  33. package/dist/components/QueryTransparency.d.ts +1 -1
  34. package/dist/components/QueryTransparency.d.ts.map +1 -1
  35. package/dist/components/SearchableList.d.ts +83 -0
  36. package/dist/components/SearchableList.d.ts.map +1 -0
  37. package/dist/components/Select.d.ts +16 -2
  38. package/dist/components/Select.d.ts.map +1 -1
  39. package/dist/components/Sidebar.d.ts +40 -1
  40. package/dist/components/Sidebar.d.ts.map +1 -1
  41. package/dist/components/SwipeActions.d.ts +93 -0
  42. package/dist/components/SwipeActions.d.ts.map +1 -0
  43. package/dist/components/Switch.d.ts +1 -0
  44. package/dist/components/Switch.d.ts.map +1 -1
  45. package/dist/components/Textarea.d.ts +13 -0
  46. package/dist/components/Textarea.d.ts.map +1 -1
  47. package/dist/components/index.d.ts +31 -3
  48. package/dist/components/index.d.ts.map +1 -1
  49. package/dist/context/MobileContext.d.ts +168 -0
  50. package/dist/context/MobileContext.d.ts.map +1 -0
  51. package/dist/hooks/useResponsive.d.ts +158 -0
  52. package/dist/hooks/useResponsive.d.ts.map +1 -0
  53. package/dist/index.d.ts +1871 -51
  54. package/dist/index.esm.js +3025 -196
  55. package/dist/index.esm.js.map +1 -1
  56. package/dist/index.js +3063 -194
  57. package/dist/index.js.map +1 -1
  58. package/dist/styles.css +434 -1
  59. package/dist/types/index.d.ts +2 -0
  60. package/dist/types/index.d.ts.map +1 -1
  61. package/package.json +1 -1
  62. package/src/components/ActionBar.stories.tsx +246 -0
  63. package/src/components/ActionBar.tsx +242 -0
  64. package/src/components/BottomNavigation.stories.tsx +142 -0
  65. package/src/components/BottomNavigation.tsx +225 -0
  66. package/src/components/Checkbox.stories.tsx +162 -0
  67. package/src/components/Checkbox.tsx +22 -6
  68. package/src/components/CheckboxList.stories.tsx +311 -0
  69. package/src/components/CheckboxList.tsx +433 -0
  70. package/src/components/Chip.stories.tsx +389 -0
  71. package/src/components/Chip.tsx +182 -3
  72. package/src/components/ConfirmDialog.tsx +56 -4
  73. package/src/components/DataTable.tsx +60 -1
  74. package/src/components/DataTableCardView.stories.tsx +307 -0
  75. package/src/components/DataTableCardView.tsx +419 -0
  76. package/src/components/ExpandablePanel.stories.tsx +620 -0
  77. package/src/components/ExpandablePanel.tsx +383 -0
  78. package/src/components/FloatingActionButton.stories.tsx +197 -0
  79. package/src/components/FloatingActionButton.tsx +301 -0
  80. package/src/components/Grid.stories.tsx +16 -16
  81. package/src/components/Input.stories.tsx +214 -0
  82. package/src/components/Input.tsx +81 -4
  83. package/src/components/MobileHeader.stories.tsx +205 -0
  84. package/src/components/MobileHeader.tsx +233 -0
  85. package/src/components/MobileLayout.stories.tsx +338 -0
  86. package/src/components/MobileLayout.tsx +313 -0
  87. package/src/components/Modal.stories.tsx +388 -0
  88. package/src/components/Modal.tsx +122 -4
  89. package/src/components/PageHeader.stories.tsx +198 -0
  90. package/src/components/PageHeader.tsx +217 -0
  91. package/src/components/PullToRefresh.stories.tsx +321 -0
  92. package/src/components/PullToRefresh.tsx +294 -0
  93. package/src/components/QueryTransparency.tsx +1 -1
  94. package/src/components/SearchableList.stories.tsx +437 -0
  95. package/src/components/SearchableList.tsx +326 -0
  96. package/src/components/Select.stories.tsx +190 -0
  97. package/src/components/Select.tsx +353 -137
  98. package/src/components/Sidebar.tsx +193 -10
  99. package/src/components/SwipeActions.stories.tsx +327 -0
  100. package/src/components/SwipeActions.tsx +387 -0
  101. package/src/components/Switch.stories.tsx +158 -0
  102. package/src/components/Switch.tsx +12 -3
  103. package/src/components/Textarea.tsx +31 -1
  104. package/src/components/index.ts +69 -3
  105. package/src/context/MobileContext.tsx +296 -0
  106. package/src/hooks/useResponsive.ts +360 -0
  107. package/src/types/index.ts +4 -0
  108. package/tailwind.config.js +56 -1
@@ -0,0 +1,326 @@
1
+ import { useState, useMemo, useCallback, useRef, useEffect, ReactNode } from 'react';
2
+ import { Search } from 'lucide-react';
3
+ import Input from './Input';
4
+ import { Loader2 } from 'lucide-react';
5
+
6
+ export interface SearchableListItem<T = unknown> {
7
+ key: string;
8
+ data: T;
9
+ }
10
+
11
+ export interface SearchableListProps<T = unknown> {
12
+ /** Array of items to display */
13
+ items: SearchableListItem<T>[];
14
+
15
+ // Search configuration
16
+ /** Search input placeholder */
17
+ searchPlaceholder?: string;
18
+ /** Controlled search value */
19
+ searchValue?: string;
20
+ /** Callback when search changes */
21
+ onSearchChange?: (value: string) => void;
22
+ /** Custom filter function */
23
+ filterFn?: (item: SearchableListItem<T>, searchTerm: string) => boolean;
24
+ /** Debounce delay for search in ms */
25
+ debounceMs?: number;
26
+
27
+ // Item rendering
28
+ /** Render function for each item */
29
+ renderItem: (item: SearchableListItem<T>, index: number, isSelected: boolean, isHighlighted: boolean) => ReactNode;
30
+
31
+ // Selection (optional)
32
+ /** Currently selected item key */
33
+ selectedKey?: string;
34
+ /** Callback when item is selected */
35
+ onSelect?: (item: SearchableListItem<T>) => void;
36
+
37
+ // Display
38
+ /** Maximum height with overflow scroll */
39
+ maxHeight?: string | number;
40
+ /** Show result count */
41
+ showResultCount?: boolean;
42
+ /** Result count format function */
43
+ formatResultCount?: (count: number, total: number) => string;
44
+
45
+ // Empty/Loading states
46
+ /** Message when no items available */
47
+ emptyMessage?: string | ReactNode;
48
+ /** Message when search has no results */
49
+ noResultsMessage?: string | ReactNode;
50
+ /** Loading state */
51
+ loading?: boolean;
52
+ /** Loading message */
53
+ loadingMessage?: string | ReactNode;
54
+
55
+ // Styling
56
+ /** Size variant */
57
+ size?: 'sm' | 'md' | 'lg';
58
+ /** Visual variant */
59
+ variant?: 'default' | 'bordered' | 'card';
60
+ /** Additional CSS classes */
61
+ className?: string;
62
+
63
+ // Keyboard navigation
64
+ /** Enable keyboard navigation (arrow keys, enter) */
65
+ enableKeyboardNavigation?: boolean;
66
+ /** Auto-focus search input */
67
+ autoFocus?: boolean;
68
+ }
69
+
70
+ const sizeClasses = {
71
+ sm: {
72
+ container: 'text-sm',
73
+ item: 'py-1.5 px-2',
74
+ searchPadding: 'p-2',
75
+ statusPadding: 'px-2 py-1.5',
76
+ },
77
+ md: {
78
+ container: 'text-sm',
79
+ item: 'py-2 px-3',
80
+ searchPadding: 'p-3',
81
+ statusPadding: 'px-3 py-2',
82
+ },
83
+ lg: {
84
+ container: 'text-base',
85
+ item: 'py-3 px-4',
86
+ searchPadding: 'p-4',
87
+ statusPadding: 'px-4 py-2.5',
88
+ },
89
+ };
90
+
91
+ const variantClasses = {
92
+ default: 'bg-white',
93
+ bordered: 'bg-white border border-paper-300 rounded-lg',
94
+ card: 'bg-white border border-paper-300 rounded-lg shadow-sm',
95
+ };
96
+
97
+ /**
98
+ * SearchableList - List component with integrated search/filter functionality
99
+ *
100
+ * @example Basic usage
101
+ * ```tsx
102
+ * <SearchableList
103
+ * items={users.map(u => ({ key: u.id, data: u }))}
104
+ * renderItem={(item) => <div>{item.data.name}</div>}
105
+ * onSelect={(item) => setSelectedUser(item.data)}
106
+ * searchable
107
+ * searchPlaceholder="Search users..."
108
+ * />
109
+ * ```
110
+ *
111
+ * @example With custom filter and loading
112
+ * ```tsx
113
+ * <SearchableList
114
+ * items={products}
115
+ * renderItem={(item, index, isSelected) => (
116
+ * <div className={isSelected ? 'bg-accent-50' : ''}>
117
+ * {item.data.name} - ${item.data.price}
118
+ * </div>
119
+ * )}
120
+ * filterFn={(item, term) =>
121
+ * item.data.name.toLowerCase().includes(term.toLowerCase())
122
+ * }
123
+ * loading={isLoading}
124
+ * loadingMessage="Fetching products..."
125
+ * maxHeight="400px"
126
+ * />
127
+ * ```
128
+ */
129
+ export default function SearchableList<T = unknown>({
130
+ items,
131
+ searchPlaceholder = 'Search...',
132
+ searchValue: controlledSearchValue,
133
+ onSearchChange,
134
+ filterFn,
135
+ debounceMs = 150,
136
+ renderItem,
137
+ selectedKey,
138
+ onSelect,
139
+ maxHeight,
140
+ showResultCount = false,
141
+ formatResultCount,
142
+ emptyMessage = 'No items available',
143
+ noResultsMessage = 'No items match your search',
144
+ loading = false,
145
+ loadingMessage = 'Loading...',
146
+ size = 'md',
147
+ variant = 'default',
148
+ className = '',
149
+ enableKeyboardNavigation = true,
150
+ autoFocus = false,
151
+ }: SearchableListProps<T>) {
152
+ const [internalSearchValue, setInternalSearchValue] = useState('');
153
+ const [debouncedSearchTerm, setDebouncedSearchTerm] = useState('');
154
+ const [highlightedIndex, setHighlightedIndex] = useState(-1);
155
+
156
+ const listRef = useRef<HTMLDivElement>(null);
157
+ const itemRefs = useRef<Map<number, HTMLDivElement>>(new Map());
158
+
159
+ const searchValue = controlledSearchValue !== undefined ? controlledSearchValue : internalSearchValue;
160
+
161
+ // Debounce search
162
+ useEffect(() => {
163
+ const timer = setTimeout(() => {
164
+ setDebouncedSearchTerm(searchValue);
165
+ }, debounceMs);
166
+ return () => clearTimeout(timer);
167
+ }, [searchValue, debounceMs]);
168
+
169
+ const handleSearchChange = useCallback((value: string) => {
170
+ if (controlledSearchValue === undefined) {
171
+ setInternalSearchValue(value);
172
+ }
173
+ onSearchChange?.(value);
174
+ setHighlightedIndex(-1);
175
+ }, [controlledSearchValue, onSearchChange]);
176
+
177
+ // Filter items based on search
178
+ const filteredItems = useMemo(() => {
179
+ if (!debouncedSearchTerm) return items;
180
+
181
+ return items.filter(item => {
182
+ if (filterFn) {
183
+ return filterFn(item, debouncedSearchTerm);
184
+ }
185
+ // Default filter: check if key includes search term
186
+ return item.key.toLowerCase().includes(debouncedSearchTerm.toLowerCase());
187
+ });
188
+ }, [items, debouncedSearchTerm, filterFn]);
189
+
190
+ // Keyboard navigation
191
+ const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
192
+ if (!enableKeyboardNavigation || filteredItems.length === 0) return;
193
+
194
+ switch (e.key) {
195
+ case 'ArrowDown':
196
+ e.preventDefault();
197
+ setHighlightedIndex(prev =>
198
+ prev < filteredItems.length - 1 ? prev + 1 : 0
199
+ );
200
+ break;
201
+ case 'ArrowUp':
202
+ e.preventDefault();
203
+ setHighlightedIndex(prev =>
204
+ prev > 0 ? prev - 1 : filteredItems.length - 1
205
+ );
206
+ break;
207
+ case 'Enter':
208
+ e.preventDefault();
209
+ if (highlightedIndex >= 0 && highlightedIndex < filteredItems.length) {
210
+ onSelect?.(filteredItems[highlightedIndex]);
211
+ }
212
+ break;
213
+ case 'Escape':
214
+ setHighlightedIndex(-1);
215
+ break;
216
+ }
217
+ }, [enableKeyboardNavigation, filteredItems, highlightedIndex, onSelect]);
218
+
219
+ // Scroll highlighted item into view
220
+ useEffect(() => {
221
+ if (highlightedIndex >= 0) {
222
+ const itemEl = itemRefs.current.get(highlightedIndex);
223
+ if (itemEl) {
224
+ itemEl.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
225
+ }
226
+ }
227
+ }, [highlightedIndex]);
228
+
229
+ const sizeStyle = sizeClasses[size];
230
+
231
+ const resultCountText = formatResultCount
232
+ ? formatResultCount(filteredItems.length, items.length)
233
+ : `${filteredItems.length} of ${items.length}`;
234
+
235
+ return (
236
+ <div
237
+ className={`${variantClasses[variant]} ${sizeStyle.container} ${className}`}
238
+ onKeyDown={handleKeyDown}
239
+ >
240
+ {/* Search Input */}
241
+ <div className={`${sizeStyle.searchPadding} border-b border-paper-200`}>
242
+ <Input
243
+ value={searchValue}
244
+ onChange={(e) => handleSearchChange(e.target.value)}
245
+ placeholder={searchPlaceholder}
246
+ prefixIcon={<Search className="h-4 w-4" />}
247
+ size={size === 'lg' ? 'md' : 'sm'}
248
+ clearable
249
+ onClear={() => handleSearchChange('')}
250
+ autoFocus={autoFocus}
251
+ />
252
+ </div>
253
+
254
+ {/* Result Count */}
255
+ {showResultCount && items.length > 0 && !loading && (
256
+ <div className={`${sizeStyle.statusPadding} text-ink-500 text-xs border-b border-paper-100`}>
257
+ {resultCountText}
258
+ </div>
259
+ )}
260
+
261
+ {/* List Content */}
262
+ <div
263
+ ref={listRef}
264
+ className="overflow-y-auto"
265
+ style={{ maxHeight: maxHeight || undefined }}
266
+ role="listbox"
267
+ aria-activedescendant={highlightedIndex >= 0 ? `item-${highlightedIndex}` : undefined}
268
+ >
269
+ {/* Loading State */}
270
+ {loading && (
271
+ <div className={`${sizeStyle.item} flex items-center justify-center gap-2 text-ink-500`}>
272
+ <Loader2 className="h-4 w-4 animate-spin" />
273
+ <span>{loadingMessage}</span>
274
+ </div>
275
+ )}
276
+
277
+ {/* Empty State */}
278
+ {!loading && items.length === 0 && (
279
+ <div className={`${sizeStyle.item} text-ink-500 text-center`}>
280
+ {emptyMessage}
281
+ </div>
282
+ )}
283
+
284
+ {/* No Results */}
285
+ {!loading && items.length > 0 && filteredItems.length === 0 && (
286
+ <div className={`${sizeStyle.item} text-ink-500 text-center`}>
287
+ {noResultsMessage}
288
+ </div>
289
+ )}
290
+
291
+ {/* Items */}
292
+ {!loading && filteredItems.map((item, index) => {
293
+ const isSelected = selectedKey === item.key;
294
+ const isHighlighted = highlightedIndex === index;
295
+
296
+ return (
297
+ <div
298
+ key={item.key}
299
+ id={`item-${index}`}
300
+ ref={(el) => {
301
+ if (el) {
302
+ itemRefs.current.set(index, el);
303
+ } else {
304
+ itemRefs.current.delete(index);
305
+ }
306
+ }}
307
+ role="option"
308
+ aria-selected={isSelected}
309
+ onClick={() => onSelect?.(item)}
310
+ className={`
311
+ ${sizeStyle.item}
312
+ cursor-pointer transition-colors
313
+ ${isSelected ? 'bg-accent-50' : ''}
314
+ ${isHighlighted ? 'bg-paper-100' : ''}
315
+ ${!isSelected && !isHighlighted ? 'hover:bg-paper-50' : ''}
316
+ border-b border-paper-100 last:border-b-0
317
+ `}
318
+ >
319
+ {renderItem(item, index, isSelected, isHighlighted)}
320
+ </div>
321
+ );
322
+ })}
323
+ </div>
324
+ </div>
325
+ );
326
+ }
@@ -388,3 +388,193 @@ export const MultiSelect: Story = {
388
388
  );
389
389
  },
390
390
  };
391
+
392
+ // Mobile Stories
393
+ export const MobileBottomSheet: Story = {
394
+ parameters: {
395
+ viewport: {
396
+ defaultViewport: 'mobile1',
397
+ },
398
+ docs: {
399
+ description: {
400
+ story: 'On mobile viewports, Select automatically uses a BottomSheet for option selection. Resize your browser or use mobile viewport to see the effect.',
401
+ },
402
+ },
403
+ },
404
+ render: () => {
405
+ const [value, setValue] = useState('');
406
+ return (
407
+ <div style={{ padding: '16px' }}>
408
+ <Select
409
+ label="Mobile Select"
410
+ options={countryOptions}
411
+ value={value}
412
+ onChange={setValue}
413
+ mobileMode="auto"
414
+ searchable
415
+ placeholder="Tap to select..."
416
+ />
417
+ </div>
418
+ );
419
+ },
420
+ };
421
+
422
+ export const MobileSearchable: Story = {
423
+ parameters: {
424
+ viewport: {
425
+ defaultViewport: 'mobile1',
426
+ },
427
+ },
428
+ render: () => {
429
+ const [value, setValue] = useState('');
430
+ // Generate many options to demonstrate mobile search
431
+ const manyOptions = Array.from({ length: 50 }, (_, i) => ({
432
+ value: `option-${i}`,
433
+ label: `Option ${i + 1}`,
434
+ }));
435
+ return (
436
+ <div style={{ padding: '16px' }}>
437
+ <Select
438
+ label="Searchable Mobile"
439
+ options={manyOptions}
440
+ value={value}
441
+ onChange={setValue}
442
+ mobileMode="auto"
443
+ searchable
444
+ placeholder="Search options..."
445
+ helperText="Opens as bottom sheet with search on mobile"
446
+ />
447
+ </div>
448
+ );
449
+ },
450
+ };
451
+
452
+ export const MobileNative: Story = {
453
+ parameters: {
454
+ viewport: {
455
+ defaultViewport: 'mobile1',
456
+ },
457
+ docs: {
458
+ description: {
459
+ story: 'Uses the native OS select picker on mobile for familiar UX.',
460
+ },
461
+ },
462
+ },
463
+ render: () => {
464
+ const [value, setValue] = useState('');
465
+ return (
466
+ <div style={{ padding: '16px' }}>
467
+ <Select
468
+ label="Native Select"
469
+ options={basicOptions}
470
+ value={value}
471
+ onChange={setValue}
472
+ mobileMode="native"
473
+ placeholder="Uses native picker on mobile..."
474
+ />
475
+ </div>
476
+ );
477
+ },
478
+ };
479
+
480
+ export const MobileLargeSize: Story = {
481
+ parameters: {
482
+ viewport: {
483
+ defaultViewport: 'mobile1',
484
+ },
485
+ docs: {
486
+ description: {
487
+ story: 'Large size provides 44px touch targets. On mobile, md size auto-upgrades to lg.',
488
+ },
489
+ },
490
+ },
491
+ render: () => {
492
+ const [value, setValue] = useState('');
493
+ return (
494
+ <div style={{ padding: '16px' }}>
495
+ <Select
496
+ label="Touch-Friendly Select"
497
+ options={basicOptions}
498
+ value={value}
499
+ onChange={setValue}
500
+ size="lg"
501
+ clearable
502
+ placeholder="Large touch target..."
503
+ />
504
+ </div>
505
+ );
506
+ },
507
+ };
508
+
509
+ export const MobileWithGroups: Story = {
510
+ parameters: {
511
+ viewport: {
512
+ defaultViewport: 'mobile1',
513
+ },
514
+ },
515
+ render: () => {
516
+ const [value, setValue] = useState('');
517
+ const groupedOptions = [
518
+ {
519
+ label: 'Fruits',
520
+ options: [
521
+ { value: 'apple', label: 'Apple' },
522
+ { value: 'banana', label: 'Banana' },
523
+ { value: 'orange', label: 'Orange' },
524
+ ],
525
+ },
526
+ {
527
+ label: 'Vegetables',
528
+ options: [
529
+ { value: 'carrot', label: 'Carrot' },
530
+ { value: 'broccoli', label: 'Broccoli' },
531
+ { value: 'spinach', label: 'Spinach' },
532
+ ],
533
+ },
534
+ ];
535
+ return (
536
+ <div style={{ padding: '16px' }}>
537
+ <Select
538
+ label="Grouped Options"
539
+ groups={groupedOptions}
540
+ value={value}
541
+ onChange={setValue}
542
+ mobileMode="auto"
543
+ searchable
544
+ placeholder="Select food..."
545
+ />
546
+ </div>
547
+ );
548
+ },
549
+ };
550
+
551
+ export const MobileWithIcons: Story = {
552
+ parameters: {
553
+ viewport: {
554
+ defaultViewport: 'mobile1',
555
+ },
556
+ },
557
+ render: () => {
558
+ const [value, setValue] = useState('');
559
+ const iconOptions = [
560
+ { value: 'location1', label: 'New York', icon: <MapPin className="w-4 h-4" /> },
561
+ { value: 'location2', label: 'Los Angeles', icon: <MapPin className="w-4 h-4" /> },
562
+ { value: 'location3', label: 'Chicago', icon: <MapPin className="w-4 h-4" /> },
563
+ { value: 'user1', label: 'John Doe', icon: <User className="w-4 h-4" /> },
564
+ { value: 'user2', label: 'Jane Smith', icon: <User className="w-4 h-4" /> },
565
+ ];
566
+ return (
567
+ <div style={{ padding: '16px' }}>
568
+ <Select
569
+ label="With Icons"
570
+ options={iconOptions}
571
+ value={value}
572
+ onChange={setValue}
573
+ mobileMode="auto"
574
+ clearable
575
+ placeholder="Select option..."
576
+ />
577
+ </div>
578
+ );
579
+ },
580
+ };