@idealyst/components 1.0.82 → 1.0.83
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/package.json +7 -7
- package/src/Select/README.md +166 -0
- package/src/Select/Select.native.tsx +270 -0
- package/src/Select/Select.styles.tsx +325 -0
- package/src/Select/Select.web.tsx +436 -0
- package/src/Select/index.native.ts +2 -0
- package/src/Select/index.ts +2 -0
- package/src/Select/index.web.ts +2 -0
- package/src/Select/types.ts +118 -0
- package/src/examples/AllExamples.tsx +4 -0
- package/src/examples/SelectExamples.tsx +423 -0
- package/src/examples/index.ts +1 -0
- package/src/index.ts +4 -0
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
import React, { useState, useRef, useEffect } from 'react';
|
|
2
|
+
import { createPortal } from 'react-dom';
|
|
3
|
+
// @ts-ignore - web-specific import
|
|
4
|
+
import { getWebProps } from 'react-native-unistyles/web';
|
|
5
|
+
import { SelectProps, SelectOption } from './types';
|
|
6
|
+
import { selectStyles } from './Select.styles';
|
|
7
|
+
|
|
8
|
+
const Select: React.FC<SelectProps> = ({
|
|
9
|
+
options,
|
|
10
|
+
value,
|
|
11
|
+
onValueChange,
|
|
12
|
+
placeholder = 'Select an option',
|
|
13
|
+
disabled = false,
|
|
14
|
+
error = false,
|
|
15
|
+
helperText,
|
|
16
|
+
label,
|
|
17
|
+
variant = 'outlined',
|
|
18
|
+
intent = 'neutral',
|
|
19
|
+
size = 'medium',
|
|
20
|
+
searchable = false,
|
|
21
|
+
filterOption,
|
|
22
|
+
maxHeight = 240,
|
|
23
|
+
style,
|
|
24
|
+
testID,
|
|
25
|
+
accessibilityLabel,
|
|
26
|
+
}) => {
|
|
27
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
28
|
+
const [searchTerm, setSearchTerm] = useState('');
|
|
29
|
+
const [focusedIndex, setFocusedIndex] = useState(-1);
|
|
30
|
+
const triggerRef = useRef<HTMLButtonElement>(null);
|
|
31
|
+
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
32
|
+
const searchInputRef = useRef<HTMLInputElement>(null);
|
|
33
|
+
|
|
34
|
+
// Debug: Log when trigger ref is set
|
|
35
|
+
const setTriggerRef = (el: HTMLButtonElement | null) => {
|
|
36
|
+
console.log('Setting trigger ref to:', el);
|
|
37
|
+
triggerRef.current = el;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const selectedOption = options.find(option => option.value === value);
|
|
41
|
+
|
|
42
|
+
// Filter options based on search term
|
|
43
|
+
const filteredOptions = searchable && searchTerm
|
|
44
|
+
? options.filter(option => {
|
|
45
|
+
if (filterOption) {
|
|
46
|
+
return filterOption(option, searchTerm);
|
|
47
|
+
}
|
|
48
|
+
return option.label.toLowerCase().includes(searchTerm.toLowerCase());
|
|
49
|
+
})
|
|
50
|
+
: options;
|
|
51
|
+
|
|
52
|
+
// Apply styles with variants
|
|
53
|
+
selectStyles.useVariants({
|
|
54
|
+
variant: variant as any,
|
|
55
|
+
size,
|
|
56
|
+
intent,
|
|
57
|
+
disabled,
|
|
58
|
+
error,
|
|
59
|
+
focused: isOpen,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Position dropdown when it opens
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (!isOpen) return;
|
|
65
|
+
|
|
66
|
+
let retryCount = 0;
|
|
67
|
+
const maxRetries = 10;
|
|
68
|
+
|
|
69
|
+
const positionDropdown = () => {
|
|
70
|
+
if (!triggerRef.current || !dropdownRef.current) {
|
|
71
|
+
console.log(`[Attempt ${retryCount + 1}/${maxRetries}] Refs not ready:`, {
|
|
72
|
+
trigger: !!triggerRef.current,
|
|
73
|
+
dropdown: !!dropdownRef.current
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
if (retryCount < maxRetries) {
|
|
77
|
+
retryCount++;
|
|
78
|
+
setTimeout(positionDropdown, 10);
|
|
79
|
+
}
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const trigger = triggerRef.current;
|
|
84
|
+
const dropdown = dropdownRef.current;
|
|
85
|
+
const triggerRect = trigger.getBoundingClientRect();
|
|
86
|
+
|
|
87
|
+
console.log('Trigger button found at:', {
|
|
88
|
+
top: triggerRect.top,
|
|
89
|
+
left: triggerRect.left,
|
|
90
|
+
bottom: triggerRect.bottom,
|
|
91
|
+
right: triggerRect.right,
|
|
92
|
+
width: triggerRect.width,
|
|
93
|
+
height: triggerRect.height
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
console.log('Dropdown initial position:', {
|
|
97
|
+
currentTop: dropdown.style.top,
|
|
98
|
+
currentLeft: dropdown.style.left,
|
|
99
|
+
offsetHeight: dropdown.offsetHeight,
|
|
100
|
+
offsetWidth: dropdown.offsetWidth
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Calculate and set position
|
|
104
|
+
const top = triggerRect.bottom + 4;
|
|
105
|
+
const left = triggerRect.left;
|
|
106
|
+
const width = triggerRect.width;
|
|
107
|
+
|
|
108
|
+
dropdown.style.position = 'fixed';
|
|
109
|
+
dropdown.style.top = `${top}px`;
|
|
110
|
+
dropdown.style.left = `${left}px`;
|
|
111
|
+
dropdown.style.width = `${width}px`;
|
|
112
|
+
dropdown.style.maxHeight = `${maxHeight}px`;
|
|
113
|
+
|
|
114
|
+
console.log('Dropdown NEW position set to:', {
|
|
115
|
+
top: `${top}px`,
|
|
116
|
+
left: `${left}px`,
|
|
117
|
+
width: `${width}px`,
|
|
118
|
+
actualTop: dropdown.style.top,
|
|
119
|
+
actualLeft: dropdown.style.left,
|
|
120
|
+
actualWidth: dropdown.style.width
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Verify position was applied
|
|
124
|
+
const dropdownRect = dropdown.getBoundingClientRect();
|
|
125
|
+
console.log('Dropdown actual position after setting:', {
|
|
126
|
+
top: dropdownRect.top,
|
|
127
|
+
left: dropdownRect.left,
|
|
128
|
+
width: dropdownRect.width,
|
|
129
|
+
height: dropdownRect.height
|
|
130
|
+
});
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// Start positioning attempts
|
|
134
|
+
positionDropdown();
|
|
135
|
+
|
|
136
|
+
// Reposition on scroll/resize
|
|
137
|
+
const handleReposition = () => {
|
|
138
|
+
retryCount = 0;
|
|
139
|
+
positionDropdown();
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
window.addEventListener('scroll', handleReposition, true);
|
|
143
|
+
window.addEventListener('resize', handleReposition);
|
|
144
|
+
|
|
145
|
+
return () => {
|
|
146
|
+
window.removeEventListener('scroll', handleReposition, true);
|
|
147
|
+
window.removeEventListener('resize', handleReposition);
|
|
148
|
+
};
|
|
149
|
+
}, [isOpen, maxHeight]);
|
|
150
|
+
|
|
151
|
+
// Close dropdown when clicking outside
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
if (!isOpen) return;
|
|
154
|
+
|
|
155
|
+
const handleClickOutside = (event: MouseEvent) => {
|
|
156
|
+
const target = event.target as Node;
|
|
157
|
+
|
|
158
|
+
// Check if click is outside both trigger and dropdown
|
|
159
|
+
if (
|
|
160
|
+
triggerRef.current && !triggerRef.current.contains(target) &&
|
|
161
|
+
dropdownRef.current && !dropdownRef.current.contains(target)
|
|
162
|
+
) {
|
|
163
|
+
setIsOpen(false);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// Use capture phase for better event handling
|
|
168
|
+
document.addEventListener('mousedown', handleClickOutside, true);
|
|
169
|
+
|
|
170
|
+
return () => {
|
|
171
|
+
document.removeEventListener('mousedown', handleClickOutside, true);
|
|
172
|
+
};
|
|
173
|
+
}, [isOpen]);
|
|
174
|
+
|
|
175
|
+
// Handle keyboard navigation
|
|
176
|
+
useEffect(() => {
|
|
177
|
+
if (!isOpen) return;
|
|
178
|
+
|
|
179
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
180
|
+
switch (event.key) {
|
|
181
|
+
case 'Escape':
|
|
182
|
+
setIsOpen(false);
|
|
183
|
+
triggerRef.current?.focus();
|
|
184
|
+
break;
|
|
185
|
+
case 'ArrowDown':
|
|
186
|
+
event.preventDefault();
|
|
187
|
+
setFocusedIndex(prev =>
|
|
188
|
+
prev < filteredOptions.length - 1 ? prev + 1 : 0
|
|
189
|
+
);
|
|
190
|
+
break;
|
|
191
|
+
case 'ArrowUp':
|
|
192
|
+
event.preventDefault();
|
|
193
|
+
setFocusedIndex(prev =>
|
|
194
|
+
prev > 0 ? prev - 1 : filteredOptions.length - 1
|
|
195
|
+
);
|
|
196
|
+
break;
|
|
197
|
+
case 'Enter':
|
|
198
|
+
case ' ':
|
|
199
|
+
event.preventDefault();
|
|
200
|
+
if (focusedIndex >= 0 && focusedIndex < filteredOptions.length) {
|
|
201
|
+
const option = filteredOptions[focusedIndex];
|
|
202
|
+
if (!option.disabled) {
|
|
203
|
+
handleOptionSelect(option);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
211
|
+
return () => {
|
|
212
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
213
|
+
};
|
|
214
|
+
}, [isOpen, focusedIndex, filteredOptions]);
|
|
215
|
+
|
|
216
|
+
// Focus search input when dropdown opens
|
|
217
|
+
useEffect(() => {
|
|
218
|
+
if (isOpen && searchable && searchInputRef.current) {
|
|
219
|
+
// Delay to ensure dropdown is positioned
|
|
220
|
+
setTimeout(() => {
|
|
221
|
+
searchInputRef.current?.focus();
|
|
222
|
+
}, 50);
|
|
223
|
+
}
|
|
224
|
+
}, [isOpen, searchable]);
|
|
225
|
+
|
|
226
|
+
const handleTriggerClick = () => {
|
|
227
|
+
if (!disabled) {
|
|
228
|
+
setIsOpen(!isOpen);
|
|
229
|
+
setSearchTerm('');
|
|
230
|
+
setFocusedIndex(-1);
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const handleOptionSelect = (option: SelectOption) => {
|
|
235
|
+
if (!option.disabled) {
|
|
236
|
+
onValueChange(option.value);
|
|
237
|
+
setIsOpen(false);
|
|
238
|
+
setSearchTerm('');
|
|
239
|
+
triggerRef.current?.focus();
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
244
|
+
setSearchTerm(e.target.value);
|
|
245
|
+
setFocusedIndex(0);
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const containerWebProps = getWebProps([
|
|
249
|
+
selectStyles.container,
|
|
250
|
+
style
|
|
251
|
+
]);
|
|
252
|
+
|
|
253
|
+
const triggerWebProps = getWebProps([
|
|
254
|
+
selectStyles.trigger,
|
|
255
|
+
isOpen && selectStyles.triggerOpen
|
|
256
|
+
]);
|
|
257
|
+
|
|
258
|
+
// MUI-style dropdown portal
|
|
259
|
+
const renderDropdown = () => {
|
|
260
|
+
if (!isOpen) return null;
|
|
261
|
+
|
|
262
|
+
return createPortal(
|
|
263
|
+
<div
|
|
264
|
+
ref={dropdownRef}
|
|
265
|
+
style={{
|
|
266
|
+
position: 'fixed',
|
|
267
|
+
top: '0px', // Explicit initial position
|
|
268
|
+
left: '0px', // Explicit initial position
|
|
269
|
+
opacity: 1,
|
|
270
|
+
zIndex: 1300, // MUI's z-index for select
|
|
271
|
+
backgroundColor: 'white',
|
|
272
|
+
borderRadius: '4px',
|
|
273
|
+
boxShadow: '0px 5px 5px -3px rgba(0,0,0,0.2), 0px 8px 10px 1px rgba(0,0,0,0.14), 0px 3px 14px 2px rgba(0,0,0,0.12)',
|
|
274
|
+
overflow: 'auto',
|
|
275
|
+
minWidth: '200px', // Ensure minimum width
|
|
276
|
+
visibility: 'visible', // Ensure it's visible
|
|
277
|
+
}}
|
|
278
|
+
role="listbox"
|
|
279
|
+
>
|
|
280
|
+
{searchable && (
|
|
281
|
+
<div
|
|
282
|
+
style={{
|
|
283
|
+
padding: '8px 16px',
|
|
284
|
+
borderBottom: '1px solid rgba(0, 0, 0, 0.12)',
|
|
285
|
+
position: 'sticky',
|
|
286
|
+
top: 0,
|
|
287
|
+
backgroundColor: 'white',
|
|
288
|
+
zIndex: 1,
|
|
289
|
+
}}
|
|
290
|
+
>
|
|
291
|
+
<input
|
|
292
|
+
ref={searchInputRef}
|
|
293
|
+
type="text"
|
|
294
|
+
placeholder="Search options..."
|
|
295
|
+
value={searchTerm}
|
|
296
|
+
onChange={handleSearchChange}
|
|
297
|
+
style={{
|
|
298
|
+
width: '100%',
|
|
299
|
+
padding: '8px 12px',
|
|
300
|
+
border: '1px solid rgba(0, 0, 0, 0.23)',
|
|
301
|
+
borderRadius: '4px',
|
|
302
|
+
fontSize: '14px',
|
|
303
|
+
outline: 'none',
|
|
304
|
+
}}
|
|
305
|
+
onFocus={(e) => {
|
|
306
|
+
e.target.style.borderColor = '#1976d2';
|
|
307
|
+
}}
|
|
308
|
+
onBlur={(e) => {
|
|
309
|
+
e.target.style.borderColor = 'rgba(0, 0, 0, 0.23)';
|
|
310
|
+
}}
|
|
311
|
+
/>
|
|
312
|
+
</div>
|
|
313
|
+
)}
|
|
314
|
+
|
|
315
|
+
<div style={{ padding: '8px 0' }}>
|
|
316
|
+
{filteredOptions.map((option, index) => {
|
|
317
|
+
const isSelected = option.value === value;
|
|
318
|
+
const isFocused = index === focusedIndex;
|
|
319
|
+
|
|
320
|
+
return (
|
|
321
|
+
<div
|
|
322
|
+
key={option.value}
|
|
323
|
+
onClick={() => handleOptionSelect(option)}
|
|
324
|
+
role="option"
|
|
325
|
+
aria-selected={isSelected}
|
|
326
|
+
onMouseEnter={() => setFocusedIndex(index)}
|
|
327
|
+
style={{
|
|
328
|
+
padding: '6px 16px',
|
|
329
|
+
cursor: option.disabled ? 'default' : 'pointer',
|
|
330
|
+
backgroundColor: isFocused
|
|
331
|
+
? 'rgba(0, 0, 0, 0.04)'
|
|
332
|
+
: isSelected
|
|
333
|
+
? 'rgba(25, 118, 210, 0.08)'
|
|
334
|
+
: 'transparent',
|
|
335
|
+
color: option.disabled
|
|
336
|
+
? 'rgba(0, 0, 0, 0.38)'
|
|
337
|
+
: 'rgba(0, 0, 0, 0.87)',
|
|
338
|
+
fontSize: '14px',
|
|
339
|
+
lineHeight: '1.5',
|
|
340
|
+
transition: 'background-color 150ms cubic-bezier(0.4, 0, 0.2, 1)',
|
|
341
|
+
display: 'flex',
|
|
342
|
+
alignItems: 'center',
|
|
343
|
+
gap: '12px',
|
|
344
|
+
}}
|
|
345
|
+
>
|
|
346
|
+
{option.icon && (
|
|
347
|
+
<span style={{ display: 'flex', alignItems: 'center' }}>
|
|
348
|
+
{option.icon}
|
|
349
|
+
</span>
|
|
350
|
+
)}
|
|
351
|
+
<span>{option.label}</span>
|
|
352
|
+
</div>
|
|
353
|
+
);
|
|
354
|
+
})}
|
|
355
|
+
|
|
356
|
+
{filteredOptions.length === 0 && (
|
|
357
|
+
<div
|
|
358
|
+
style={{
|
|
359
|
+
padding: '6px 16px',
|
|
360
|
+
color: 'rgba(0, 0, 0, 0.54)',
|
|
361
|
+
fontSize: '14px',
|
|
362
|
+
}}
|
|
363
|
+
>
|
|
364
|
+
No options found
|
|
365
|
+
</div>
|
|
366
|
+
)}
|
|
367
|
+
</div>
|
|
368
|
+
</div>,
|
|
369
|
+
document.body
|
|
370
|
+
);
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
return (
|
|
374
|
+
<div {...containerWebProps} data-testid={testID}>
|
|
375
|
+
{label && (
|
|
376
|
+
<label {...getWebProps([selectStyles.label])}>
|
|
377
|
+
{label}
|
|
378
|
+
</label>
|
|
379
|
+
)}
|
|
380
|
+
|
|
381
|
+
<button
|
|
382
|
+
ref={setTriggerRef}
|
|
383
|
+
{...triggerWebProps}
|
|
384
|
+
onClick={handleTriggerClick}
|
|
385
|
+
disabled={disabled}
|
|
386
|
+
aria-label={accessibilityLabel || label}
|
|
387
|
+
aria-expanded={isOpen}
|
|
388
|
+
aria-haspopup="listbox"
|
|
389
|
+
type="button"
|
|
390
|
+
>
|
|
391
|
+
<div {...getWebProps([selectStyles.triggerContent])}>
|
|
392
|
+
{selectedOption?.icon && (
|
|
393
|
+
<span {...getWebProps([selectStyles.icon])}>
|
|
394
|
+
{selectedOption.icon}
|
|
395
|
+
</span>
|
|
396
|
+
)}
|
|
397
|
+
<span
|
|
398
|
+
{...getWebProps([
|
|
399
|
+
selectedOption ? selectStyles.triggerText : selectStyles.placeholder
|
|
400
|
+
])}
|
|
401
|
+
>
|
|
402
|
+
{selectedOption ? selectedOption.label : placeholder}
|
|
403
|
+
</span>
|
|
404
|
+
</div>
|
|
405
|
+
|
|
406
|
+
<svg
|
|
407
|
+
{...getWebProps([
|
|
408
|
+
selectStyles.chevron,
|
|
409
|
+
isOpen && selectStyles.chevronOpen
|
|
410
|
+
])}
|
|
411
|
+
width="16"
|
|
412
|
+
height="16"
|
|
413
|
+
viewBox="0 0 16 16"
|
|
414
|
+
fill="currentColor"
|
|
415
|
+
>
|
|
416
|
+
<path d="M4.427 9.573l3.396-3.396a.25.25 0 01.354 0l3.396 3.396a.25.25 0 01-.177.427H4.604a.25.25 0 01-.177-.427z" />
|
|
417
|
+
</svg>
|
|
418
|
+
</button>
|
|
419
|
+
|
|
420
|
+
{renderDropdown()}
|
|
421
|
+
|
|
422
|
+
{helperText && (
|
|
423
|
+
<div
|
|
424
|
+
{...getWebProps([
|
|
425
|
+
selectStyles.helperText,
|
|
426
|
+
{ error }
|
|
427
|
+
])}
|
|
428
|
+
>
|
|
429
|
+
{helperText}
|
|
430
|
+
</div>
|
|
431
|
+
)}
|
|
432
|
+
</div>
|
|
433
|
+
);
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
export default Select;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import type { IntentVariant } from '../theme/variants';
|
|
3
|
+
|
|
4
|
+
export interface SelectOption {
|
|
5
|
+
/**
|
|
6
|
+
* The unique value for this option
|
|
7
|
+
*/
|
|
8
|
+
value: string;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The display label for this option
|
|
12
|
+
*/
|
|
13
|
+
label: string;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Whether this option is disabled
|
|
17
|
+
*/
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Optional icon or custom content to display before the label
|
|
22
|
+
*/
|
|
23
|
+
icon?: ReactNode;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface SelectProps {
|
|
27
|
+
/**
|
|
28
|
+
* Array of options to display in the select
|
|
29
|
+
*/
|
|
30
|
+
options: SelectOption[];
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The currently selected value
|
|
34
|
+
*/
|
|
35
|
+
value?: string;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Called when the selected value changes
|
|
39
|
+
*/
|
|
40
|
+
onValueChange?: (value: string) => void;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Placeholder text when no value is selected
|
|
44
|
+
*/
|
|
45
|
+
placeholder?: string;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Whether the select is disabled
|
|
49
|
+
*/
|
|
50
|
+
disabled?: boolean;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Whether the select shows an error state
|
|
54
|
+
*/
|
|
55
|
+
error?: boolean;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Helper text to display below the select
|
|
59
|
+
*/
|
|
60
|
+
helperText?: string;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Label text to display above the select
|
|
64
|
+
*/
|
|
65
|
+
label?: string;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The visual variant of the select
|
|
69
|
+
*/
|
|
70
|
+
variant?: 'outlined' | 'filled';
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The intent/color scheme of the select
|
|
74
|
+
*/
|
|
75
|
+
intent?: IntentVariant;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The size of the select
|
|
79
|
+
*/
|
|
80
|
+
size?: 'small' | 'medium' | 'large';
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Whether to show a search/filter input (web only)
|
|
84
|
+
*/
|
|
85
|
+
searchable?: boolean;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Custom search filter function (used with searchable)
|
|
89
|
+
*/
|
|
90
|
+
filterOption?: (option: SelectOption, searchTerm: string) => boolean;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Native iOS presentation mode (native only)
|
|
94
|
+
* 'dropdown' uses a standard dropdown overlay
|
|
95
|
+
* 'actionSheet' uses iOS ActionSheet for selection
|
|
96
|
+
*/
|
|
97
|
+
presentationMode?: 'dropdown' | 'actionSheet';
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Maximum height for the dropdown content
|
|
101
|
+
*/
|
|
102
|
+
maxHeight?: number;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Additional styles (platform-specific)
|
|
106
|
+
*/
|
|
107
|
+
style?: any;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Test ID for testing
|
|
111
|
+
*/
|
|
112
|
+
testID?: string;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Accessibility label
|
|
116
|
+
*/
|
|
117
|
+
accessibilityLabel?: string;
|
|
118
|
+
}
|
|
@@ -14,6 +14,7 @@ import { ScreenExamples } from './ScreenExamples';
|
|
|
14
14
|
import { SVGImageExamples } from './SVGImageExamples';
|
|
15
15
|
import { DialogExamples } from './DialogExamples';
|
|
16
16
|
import { PopoverExamples } from './PopoverExamples';
|
|
17
|
+
import { SelectExamples } from './SelectExamples';
|
|
17
18
|
import { ThemeExtensionExamples } from './ThemeExtensionExamples';
|
|
18
19
|
|
|
19
20
|
export const AllExamples = () => {
|
|
@@ -73,6 +74,9 @@ export const AllExamples = () => {
|
|
|
73
74
|
<PopoverExamples />
|
|
74
75
|
<Divider spacing="medium" />
|
|
75
76
|
|
|
77
|
+
<SelectExamples />
|
|
78
|
+
<Divider spacing="medium" />
|
|
79
|
+
|
|
76
80
|
<Divider spacing="large" intent="success">
|
|
77
81
|
<Text size="small" weight="semibold" color="green">THEME SYSTEM</Text>
|
|
78
82
|
</Divider>
|