react-native-molecules 0.5.0-beta.22 → 0.5.0-beta.23
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/components/Button/Button.tsx +3 -1
- package/components/DatePicker/utils.ts +2 -0
- package/components/DatePickerInline/MonthPicker.tsx +24 -40
- package/components/DatePickerInline/YearPicker.tsx +44 -79
- package/components/List/List.tsx +154 -386
- package/components/List/context.tsx +2 -4
- package/components/List/index.ts +0 -1
- package/components/List/types.ts +77 -109
- package/components/List/utils.ts +4 -37
- package/components/Menu/Menu.tsx +13 -30
- package/components/Menu/index.tsx +0 -2
- package/components/Popover/Popover.tsx +7 -10
- package/components/Popover/PopoverRoot.tsx +6 -20
- package/components/Popover/common.ts +4 -0
- package/components/Popover/index.ts +2 -8
- package/components/Popover/usePlatformMeasure.ts +4 -2
- package/components/Select/Select.tsx +211 -47
- package/components/Select/context.tsx +27 -2
- package/components/Select/types.ts +41 -25
- package/components/Select/utils.ts +7 -0
- package/components/TouchableRipple/TouchableRipple.tsx +76 -152
- package/package.json +3 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
1
|
+
import { Fragment, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
type AccessibilityRole,
|
|
4
4
|
type GestureResponderEvent,
|
|
@@ -9,24 +9,32 @@ import {
|
|
|
9
9
|
} from 'react-native';
|
|
10
10
|
|
|
11
11
|
import { typedMemo } from '../../hocs';
|
|
12
|
-
import { useActionState } from '../../hooks';
|
|
12
|
+
import { useActionState, useControlledValue } from '../../hooks';
|
|
13
13
|
import { useToggle } from '../../hooks';
|
|
14
14
|
import { resolveStateVariant } from '../../utils';
|
|
15
15
|
import { Chip } from '../Chip';
|
|
16
16
|
import { Icon } from '../Icon';
|
|
17
|
+
import { IconButton } from '../IconButton';
|
|
17
18
|
import { List } from '../List';
|
|
18
19
|
import { Popover } from '../Popover';
|
|
19
20
|
import { Text } from '../Text';
|
|
21
|
+
import { TextInput, type TextInputHandles, type TextInputProps } from '../TextInput';
|
|
20
22
|
import {
|
|
21
23
|
SelectDropdownContextProvider,
|
|
24
|
+
SelectSearchContextProvider,
|
|
22
25
|
useSelectContextValue,
|
|
23
26
|
useSelectDropdownContextValue,
|
|
27
|
+
useSelectSearchContextValue,
|
|
24
28
|
} from './context';
|
|
25
29
|
import type {
|
|
26
30
|
DefaultItemT,
|
|
31
|
+
SelectContentProps,
|
|
27
32
|
SelectDropdownProps,
|
|
28
33
|
SelectOptionProps,
|
|
29
34
|
SelectProps,
|
|
35
|
+
SelectSearchContextValue,
|
|
36
|
+
SelectSearchInputProps,
|
|
37
|
+
SelectSearchKey,
|
|
30
38
|
SelectTriggerProps,
|
|
31
39
|
SelectValueProps,
|
|
32
40
|
} from './types';
|
|
@@ -34,6 +42,43 @@ import { collectWebSelectKeyboardOptionElements, styles, triggerStyles } from '.
|
|
|
34
42
|
|
|
35
43
|
const emptyArr: unknown[] = [];
|
|
36
44
|
|
|
45
|
+
const getDisplayLabel = (item: DefaultItemT, labelKey?: string) => {
|
|
46
|
+
const itemLabelKey = typeof item.labelKey === 'string' ? item.labelKey : undefined;
|
|
47
|
+
const key = labelKey ?? itemLabelKey ?? 'label';
|
|
48
|
+
const value = item[key];
|
|
49
|
+
return value == null ? String(item.id) : String(value);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const getNested = (item: unknown, path: string): unknown => {
|
|
53
|
+
if (item == null || typeof item !== 'object') return undefined;
|
|
54
|
+
if (!path.includes('.')) return (item as Record<string, unknown>)[path];
|
|
55
|
+
let val: unknown = item;
|
|
56
|
+
for (const part of path.split('.')) {
|
|
57
|
+
if (val == null || typeof val !== 'object') return undefined;
|
|
58
|
+
val = (val as Record<string, unknown>)[part];
|
|
59
|
+
}
|
|
60
|
+
return val;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const matchesByKey = (item: unknown, key: string, lowerQuery: string): boolean =>
|
|
64
|
+
String(getNested(item, key) ?? '')
|
|
65
|
+
.toLowerCase()
|
|
66
|
+
.includes(lowerQuery);
|
|
67
|
+
|
|
68
|
+
const applySearch = <T extends object>(
|
|
69
|
+
items: T[],
|
|
70
|
+
searchKey: SelectSearchKey<T> | undefined,
|
|
71
|
+
query: string,
|
|
72
|
+
): T[] => {
|
|
73
|
+
if (!query) return items;
|
|
74
|
+
if (typeof searchKey === 'function') {
|
|
75
|
+
return items.filter(item => searchKey(item, query));
|
|
76
|
+
}
|
|
77
|
+
const keys = Array.isArray(searchKey) ? searchKey : [searchKey || 'label'];
|
|
78
|
+
const lowerQuery = query.toLowerCase();
|
|
79
|
+
return items.filter(item => keys.some(key => matchesByKey(item, key, lowerQuery)));
|
|
80
|
+
};
|
|
81
|
+
|
|
37
82
|
const SelectDropdownProvider = memo(
|
|
38
83
|
({
|
|
39
84
|
children,
|
|
@@ -86,25 +131,98 @@ const SelectDropdownProvider = memo(
|
|
|
86
131
|
},
|
|
87
132
|
);
|
|
88
133
|
|
|
89
|
-
const
|
|
134
|
+
export const SelectRoot = typedMemo(
|
|
90
135
|
<Option extends DefaultItemT = DefaultItemT>({
|
|
91
136
|
children,
|
|
92
137
|
options = emptyArr as Option[],
|
|
138
|
+
searchKey,
|
|
139
|
+
searchQuery: searchQueryProp,
|
|
140
|
+
defaultSearchQuery,
|
|
141
|
+
onSearchChange,
|
|
142
|
+
searchMode = 'client',
|
|
143
|
+
getItemId,
|
|
93
144
|
...listProps
|
|
94
145
|
}: SelectProps<Option>) => {
|
|
146
|
+
const [searchQuery, setSearchQuery] = useControlledValue<string>({
|
|
147
|
+
value: searchQueryProp,
|
|
148
|
+
defaultValue: defaultSearchQuery ?? '',
|
|
149
|
+
onChange: onSearchChange,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const getOptionId = useMemo(
|
|
153
|
+
() => (getItemId ?? ((item: Option) => item.id)) as (item: Option) => string | number,
|
|
154
|
+
[getItemId],
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
const filteredOptions = useMemo(() => {
|
|
158
|
+
if (searchMode === 'external') return options;
|
|
159
|
+
return applySearch(options, searchKey, searchQuery);
|
|
160
|
+
}, [options, searchKey, searchMode, searchQuery]);
|
|
161
|
+
|
|
162
|
+
const optionById = useMemo(() => {
|
|
163
|
+
const map = new Map<string | number, Option>();
|
|
164
|
+
for (const option of options) {
|
|
165
|
+
map.set(getOptionId(option), option);
|
|
166
|
+
}
|
|
167
|
+
return map;
|
|
168
|
+
}, [getOptionId, options]);
|
|
169
|
+
|
|
170
|
+
const searchContextValue = useMemo(
|
|
171
|
+
() =>
|
|
172
|
+
({
|
|
173
|
+
searchQuery,
|
|
174
|
+
setSearchQuery,
|
|
175
|
+
allOptions: options,
|
|
176
|
+
options: filteredOptions,
|
|
177
|
+
optionById,
|
|
178
|
+
getOptionId,
|
|
179
|
+
} as unknown as SelectSearchContextValue<DefaultItemT>),
|
|
180
|
+
[filteredOptions, getOptionId, optionById, options, searchQuery, setSearchQuery],
|
|
181
|
+
);
|
|
182
|
+
|
|
95
183
|
return (
|
|
96
|
-
<
|
|
97
|
-
<
|
|
98
|
-
|
|
184
|
+
<SelectSearchContextProvider value={searchContextValue}>
|
|
185
|
+
<List {...listProps}>
|
|
186
|
+
<SelectDropdownProvider>{children}</SelectDropdownProvider>
|
|
187
|
+
</List>
|
|
188
|
+
</SelectSearchContextProvider>
|
|
99
189
|
);
|
|
100
190
|
},
|
|
101
191
|
);
|
|
102
192
|
|
|
103
|
-
const
|
|
104
|
-
|
|
193
|
+
export const SelectContent = typedMemo(
|
|
194
|
+
<Option extends DefaultItemT = DefaultItemT>({
|
|
195
|
+
children,
|
|
196
|
+
...rest
|
|
197
|
+
}: SelectContentProps<Option>) => {
|
|
198
|
+
const { options, getOptionId } = useSelectSearchContextValue(state => ({
|
|
199
|
+
options: state.options as Option[],
|
|
200
|
+
getOptionId: state.getOptionId as (item: Option) => string | number,
|
|
201
|
+
}));
|
|
202
|
+
const isSelectedId = useSelectContextValue(state => state.isSelectedId);
|
|
203
|
+
|
|
204
|
+
if (typeof children !== 'function') {
|
|
205
|
+
return <List.Content {...rest}>{children}</List.Content>;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return (
|
|
209
|
+
<List.Content {...rest}>
|
|
210
|
+
{options.map(item => (
|
|
211
|
+
<Fragment key={String(getOptionId(item))}>
|
|
212
|
+
{children(item, isSelectedId(getOptionId(item)))}
|
|
213
|
+
</Fragment>
|
|
214
|
+
))}
|
|
215
|
+
</List.Content>
|
|
216
|
+
);
|
|
217
|
+
},
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
export const SelectTrigger = ({ children, style, ...rest }: SelectTriggerProps) => {
|
|
221
|
+
const { isOpen, onOpen, onClose, triggerRef, setTriggerLayout } = useSelectDropdownContextValue(
|
|
105
222
|
state => ({
|
|
106
|
-
onOpen: state.onOpen,
|
|
107
223
|
isOpen: state.isOpen,
|
|
224
|
+
onOpen: state.onOpen,
|
|
225
|
+
onClose: state.onClose,
|
|
108
226
|
triggerRef: state.triggerRef,
|
|
109
227
|
setTriggerLayout: state.setTriggerLayout,
|
|
110
228
|
}),
|
|
@@ -140,10 +258,13 @@ const SelectTrigger = ({ children, style, ...rest }: SelectTriggerProps) => {
|
|
|
140
258
|
);
|
|
141
259
|
|
|
142
260
|
const handlePress = useCallback(() => {
|
|
143
|
-
if (
|
|
261
|
+
if (disabled) return;
|
|
262
|
+
if (!isOpen) {
|
|
144
263
|
onOpen();
|
|
264
|
+
} else {
|
|
265
|
+
onClose();
|
|
145
266
|
}
|
|
146
|
-
}, [isOpen, onOpen, disabled]);
|
|
267
|
+
}, [isOpen, onOpen, onClose, disabled]);
|
|
147
268
|
|
|
148
269
|
return (
|
|
149
270
|
<Pressable
|
|
@@ -168,44 +289,45 @@ const SelectTrigger = ({ children, style, ...rest }: SelectTriggerProps) => {
|
|
|
168
289
|
|
|
169
290
|
SelectTrigger.displayName = 'Select_Trigger';
|
|
170
291
|
|
|
171
|
-
const SelectValue = memo(
|
|
292
|
+
export const SelectValue = memo(
|
|
172
293
|
({ placeholder, labelKey, renderValue, style, ...rest }: SelectValueProps) => {
|
|
173
|
-
const { value, multiple, onRemove
|
|
294
|
+
const { value, multiple, onRemove } = useSelectContextValue(state => ({
|
|
174
295
|
value: state.value,
|
|
175
296
|
multiple: state.multiple,
|
|
176
297
|
onRemove: state.onRemove,
|
|
177
|
-
|
|
298
|
+
}));
|
|
299
|
+
const { optionById } = useSelectSearchContextValue(state => ({
|
|
300
|
+
optionById: state.optionById,
|
|
178
301
|
}));
|
|
179
302
|
|
|
180
303
|
const resolvedValue = useMemo(() => {
|
|
181
|
-
const resolve = (
|
|
182
|
-
if (
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
return found || item;
|
|
304
|
+
const resolve = (id: unknown) => {
|
|
305
|
+
if (id === null || id === undefined) return null;
|
|
306
|
+
const found = optionById.get(id as string | number);
|
|
307
|
+
return found || { id: id as string | number };
|
|
186
308
|
};
|
|
187
309
|
|
|
188
310
|
if (multiple) {
|
|
189
311
|
return (Array.isArray(value) ? value : []).map(resolve).filter(Boolean);
|
|
190
312
|
}
|
|
191
313
|
return resolve(value);
|
|
192
|
-
}, [value, multiple
|
|
314
|
+
}, [optionById, value, multiple]);
|
|
193
315
|
|
|
194
316
|
const displayValue = useMemo(() => {
|
|
195
317
|
if (!resolvedValue) return placeholder || '';
|
|
196
318
|
if (multiple && (resolvedValue as any[]).length === 0) return placeholder || '';
|
|
197
319
|
|
|
198
320
|
if (renderValue) {
|
|
199
|
-
return renderValue(resolvedValue as
|
|
321
|
+
return renderValue(resolvedValue as DefaultItemT | DefaultItemT[] | null);
|
|
200
322
|
}
|
|
201
323
|
|
|
202
324
|
if (multiple) {
|
|
203
325
|
const values = resolvedValue as DefaultItemT[];
|
|
204
326
|
// For multi-select, show chips
|
|
205
|
-
return values.map(item => item
|
|
327
|
+
return values.map(item => getDisplayLabel(item, labelKey)).join(', ');
|
|
206
328
|
} else {
|
|
207
329
|
const singleValue = resolvedValue as DefaultItemT;
|
|
208
|
-
return
|
|
330
|
+
return getDisplayLabel(singleValue, labelKey);
|
|
209
331
|
}
|
|
210
332
|
}, [resolvedValue, multiple, labelKey, placeholder, renderValue]);
|
|
211
333
|
|
|
@@ -225,7 +347,7 @@ const SelectValue = memo(
|
|
|
225
347
|
}
|
|
226
348
|
|
|
227
349
|
return (
|
|
228
|
-
<Text style={style} {...rest}>
|
|
350
|
+
<Text style={[styles.valueText, style]} selectable={false} {...rest}>
|
|
229
351
|
{displayValue}
|
|
230
352
|
</Text>
|
|
231
353
|
);
|
|
@@ -246,7 +368,7 @@ const SelectValueItem = typedMemo(
|
|
|
246
368
|
|
|
247
369
|
return (
|
|
248
370
|
<Chip.Input
|
|
249
|
-
label={
|
|
371
|
+
label={getDisplayLabel(item)}
|
|
250
372
|
size="sm"
|
|
251
373
|
selected
|
|
252
374
|
left={<></>}
|
|
@@ -259,7 +381,7 @@ const SelectValueItem = typedMemo(
|
|
|
259
381
|
SelectValue.displayName = 'Select_Value';
|
|
260
382
|
|
|
261
383
|
// Select.Dropdown - popover with keyboard navigation
|
|
262
|
-
const SelectDropdown = memo(
|
|
384
|
+
export const SelectDropdown = memo(
|
|
263
385
|
({
|
|
264
386
|
children,
|
|
265
387
|
WrapperComponent,
|
|
@@ -417,10 +539,8 @@ const KeyboardNavigationWrapper = memo(({ children }: { children: React.ReactNod
|
|
|
417
539
|
|
|
418
540
|
SelectDropdown.displayName = 'Select_Dropdown';
|
|
419
541
|
|
|
420
|
-
const SelectGroup = List.Group;
|
|
421
|
-
|
|
422
542
|
// Select.Item - select item that uses context
|
|
423
|
-
const SelectOption = memo(
|
|
543
|
+
export const SelectOption = memo(
|
|
424
544
|
<Option extends DefaultItemT = DefaultItemT>({
|
|
425
545
|
value,
|
|
426
546
|
children,
|
|
@@ -434,33 +554,29 @@ const SelectOption = memo(
|
|
|
434
554
|
onAdd,
|
|
435
555
|
onRemove,
|
|
436
556
|
disabled: selectDisabled,
|
|
437
|
-
|
|
557
|
+
isSelectedId,
|
|
438
558
|
} = useSelectContextValue(state => ({
|
|
439
559
|
multiple: state.multiple,
|
|
440
560
|
onAdd: state.onAdd,
|
|
441
561
|
onRemove: state.onRemove,
|
|
442
562
|
disabled: state.disabled,
|
|
443
|
-
|
|
563
|
+
isSelectedId: state.isSelectedId,
|
|
564
|
+
}));
|
|
565
|
+
const { allOptions, getOptionId } = useSelectSearchContextValue(state => ({
|
|
566
|
+
allOptions: state.allOptions,
|
|
567
|
+
getOptionId: state.getOptionId,
|
|
444
568
|
}));
|
|
445
569
|
|
|
446
570
|
const option = useMemo(() => {
|
|
447
|
-
const found =
|
|
571
|
+
const found = allOptions.find(i => getOptionId(i as Option) === value);
|
|
448
572
|
if (found) return found as Option;
|
|
449
573
|
return {
|
|
450
574
|
id: value,
|
|
451
575
|
...(optionDisabledProp ? { selectable: false } : {}),
|
|
452
576
|
} as Option;
|
|
453
|
-
}, [
|
|
454
|
-
|
|
455
|
-
const isSelected = useSelectContextValue(state => {
|
|
456
|
-
if (multiple) {
|
|
457
|
-
const values = state.value as any[];
|
|
458
|
-
return values?.some(v => (v?.id ?? v) === option.id) || false;
|
|
459
|
-
}
|
|
577
|
+
}, [allOptions, getOptionId, optionDisabledProp, value]);
|
|
460
578
|
|
|
461
|
-
|
|
462
|
-
return (singleValue?.id ?? singleValue) === option.id || false;
|
|
463
|
-
});
|
|
579
|
+
const isSelected = isSelectedId(value);
|
|
464
580
|
|
|
465
581
|
const { onClose } = useSelectDropdownContextValue(state => ({
|
|
466
582
|
onClose: state.onClose,
|
|
@@ -495,7 +611,7 @@ const SelectOption = memo(
|
|
|
495
611
|
style={style}
|
|
496
612
|
value={value}
|
|
497
613
|
shouldToggleOnPress={false}
|
|
498
|
-
onPress={
|
|
614
|
+
onPress={handlePress}
|
|
499
615
|
disabled={isOptionDisabled}
|
|
500
616
|
accessibilityState={{ selected: isSelected, disabled: isOptionDisabled }}
|
|
501
617
|
{...(Platform.OS === 'web'
|
|
@@ -523,16 +639,64 @@ const SelectOption = memo(
|
|
|
523
639
|
|
|
524
640
|
SelectOption.displayName = 'Select_Option';
|
|
525
641
|
|
|
526
|
-
const SelectSearchInput =
|
|
642
|
+
export const SelectSearchInput = memo(({ children, ...textInputProps }: SelectSearchInputProps) => {
|
|
643
|
+
const { searchQuery, setSearchQuery } = useSelectSearchContextValue(state => ({
|
|
644
|
+
searchQuery: state.searchQuery,
|
|
645
|
+
setSearchQuery: state.setSearchQuery,
|
|
646
|
+
}));
|
|
647
|
+
|
|
648
|
+
const textInputRef = useRef<TextInputHandles>(null);
|
|
649
|
+
|
|
650
|
+
const handleChangeText = useCallback(
|
|
651
|
+
(text: string) => {
|
|
652
|
+
setSearchQuery(text);
|
|
653
|
+
},
|
|
654
|
+
[setSearchQuery],
|
|
655
|
+
);
|
|
656
|
+
|
|
657
|
+
const inputProps = {
|
|
658
|
+
...textInputProps,
|
|
659
|
+
value: searchQuery,
|
|
660
|
+
onChangeText: handleChangeText,
|
|
661
|
+
placeholder: textInputProps.placeholder || 'Search...',
|
|
662
|
+
inputStyle: styles.searchInputInput,
|
|
663
|
+
} as TextInputProps;
|
|
664
|
+
|
|
665
|
+
const onPressLeftIcon = useCallback(() => {
|
|
666
|
+
textInputRef.current?.focus();
|
|
667
|
+
}, []);
|
|
668
|
+
|
|
669
|
+
const onClearSearchQuery = useCallback(() => {
|
|
670
|
+
handleChangeText('');
|
|
671
|
+
}, [handleChangeText]);
|
|
672
|
+
|
|
673
|
+
return (
|
|
674
|
+
<TextInput
|
|
675
|
+
ref={textInputRef}
|
|
676
|
+
style={styles.searchInput}
|
|
677
|
+
size="sm"
|
|
678
|
+
variant="outlined"
|
|
679
|
+
{...inputProps}>
|
|
680
|
+
<TextInput.Left>
|
|
681
|
+
<Icon onPress={onPressLeftIcon} name="magnify" size={20} />
|
|
682
|
+
</TextInput.Left>
|
|
683
|
+
{searchQuery ? (
|
|
684
|
+
<TextInput.Right>
|
|
685
|
+
<IconButton name="close" size={20} onPress={onClearSearchQuery} />
|
|
686
|
+
</TextInput.Right>
|
|
687
|
+
) : null}
|
|
688
|
+
{children}
|
|
689
|
+
</TextInput>
|
|
690
|
+
);
|
|
691
|
+
});
|
|
527
692
|
|
|
528
693
|
SelectSearchInput.displayName = 'Select_SearchInput';
|
|
529
694
|
|
|
530
|
-
const SelectWithSubcomponents = Object.assign(
|
|
695
|
+
const SelectWithSubcomponents = Object.assign(SelectRoot, {
|
|
531
696
|
Trigger: SelectTrigger,
|
|
532
697
|
Value: SelectValue,
|
|
533
698
|
Dropdown: SelectDropdown,
|
|
534
|
-
Content:
|
|
535
|
-
Group: SelectGroup,
|
|
699
|
+
Content: SelectContent,
|
|
536
700
|
Option: SelectOption,
|
|
537
701
|
SearchInput: SelectSearchInput,
|
|
538
702
|
});
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
useListStoreRef,
|
|
10
10
|
} from '../List';
|
|
11
11
|
import { registerPortalContext } from '../Portal';
|
|
12
|
-
import type { SelectDropdownContextValue } from './types';
|
|
12
|
+
import type { DefaultItemT, SelectDropdownContextValue, SelectSearchContextValue } from './types';
|
|
13
13
|
|
|
14
14
|
export {
|
|
15
15
|
ListContext as SelectContext,
|
|
@@ -43,12 +43,37 @@ const {
|
|
|
43
43
|
Context: SelectDropdownContext,
|
|
44
44
|
} = createFastContext<SelectDropdownContextType>(selectDropdownContextDefaultValue, true);
|
|
45
45
|
|
|
46
|
+
const selectSearchContextDefaultValue: SelectSearchContextValue<DefaultItemT> = {
|
|
47
|
+
searchQuery: '',
|
|
48
|
+
setSearchQuery: () => {},
|
|
49
|
+
allOptions: [],
|
|
50
|
+
options: [],
|
|
51
|
+
optionById: new Map(),
|
|
52
|
+
getOptionId: item => item.id,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const {
|
|
56
|
+
useStoreRef: useSelectSearchStoreRef,
|
|
57
|
+
Provider: SelectSearchContextProvider,
|
|
58
|
+
useContext: useSelectSearchContext,
|
|
59
|
+
useContextValue: useSelectSearchContextValue,
|
|
60
|
+
Context: SelectSearchContext,
|
|
61
|
+
} = createFastContext<SelectSearchContextValue<DefaultItemT>>(
|
|
62
|
+
selectSearchContextDefaultValue,
|
|
63
|
+
true,
|
|
64
|
+
);
|
|
65
|
+
|
|
46
66
|
export {
|
|
47
67
|
SelectDropdownContext,
|
|
48
68
|
SelectDropdownContextProvider,
|
|
69
|
+
SelectSearchContext,
|
|
70
|
+
SelectSearchContextProvider,
|
|
49
71
|
useSelectDropdownContext,
|
|
50
72
|
useSelectDropdownContextValue,
|
|
51
73
|
useSelectDropdownStoreRef,
|
|
74
|
+
useSelectSearchContext,
|
|
75
|
+
useSelectSearchContextValue,
|
|
76
|
+
useSelectSearchStoreRef,
|
|
52
77
|
};
|
|
53
78
|
|
|
54
|
-
registerPortalContext([SelectDropdownContext]);
|
|
79
|
+
registerPortalContext([SelectDropdownContext, SelectSearchContext]);
|
|
@@ -1,21 +1,16 @@
|
|
|
1
1
|
import type { ComponentType, ReactNode } from 'react';
|
|
2
|
-
import type { GestureResponderEvent, ViewProps } from 'react-native';
|
|
2
|
+
import type { GestureResponderEvent, TextInputProps, ViewProps } from 'react-native';
|
|
3
3
|
|
|
4
|
-
import type { ListValue } from '../List';
|
|
4
|
+
import type { ListContentProps, ListItemId, ListValue } from '../List';
|
|
5
5
|
import type { PopoverProps } from '../Popover';
|
|
6
6
|
|
|
7
|
-
export type {
|
|
8
|
-
ListContentProps as SelectContentProps,
|
|
9
|
-
ListContextValue as SelectContextValue,
|
|
10
|
-
ListGroupProps as SelectGroupProps,
|
|
11
|
-
ListSearchInputProps as SelectSearchInputProps,
|
|
12
|
-
} from '../List';
|
|
7
|
+
export type { ListContextValue as SelectContextValue } from '../List';
|
|
13
8
|
|
|
14
9
|
export type DefaultItemT = {
|
|
15
10
|
id: string | number;
|
|
16
11
|
label?: string;
|
|
17
12
|
selectable?: boolean;
|
|
18
|
-
[key: string]:
|
|
13
|
+
[key: string]: unknown;
|
|
19
14
|
};
|
|
20
15
|
|
|
21
16
|
// SelectDropdownContext types
|
|
@@ -25,42 +20,63 @@ export type SelectDropdownContextValue = {
|
|
|
25
20
|
onOpen: () => void;
|
|
26
21
|
};
|
|
27
22
|
|
|
23
|
+
export type SelectSearchMode = 'client' | 'external';
|
|
24
|
+
|
|
25
|
+
export type SelectSearchKey<Option extends object = DefaultItemT> =
|
|
26
|
+
| string
|
|
27
|
+
| string[]
|
|
28
|
+
| ((item: Option, query: string) => boolean);
|
|
29
|
+
|
|
30
|
+
export type SelectSearchContextValue<Option extends DefaultItemT = DefaultItemT> = {
|
|
31
|
+
searchQuery: string;
|
|
32
|
+
setSearchQuery: (query: string) => void;
|
|
33
|
+
allOptions: Option[];
|
|
34
|
+
options: Option[];
|
|
35
|
+
optionById: Map<ListItemId, Option>;
|
|
36
|
+
getOptionId: (item: Option) => ListItemId;
|
|
37
|
+
};
|
|
38
|
+
|
|
28
39
|
// SelectProvider props
|
|
29
40
|
type SelectPropsBase<Option extends DefaultItemT = DefaultItemT> = {
|
|
30
41
|
children: ReactNode;
|
|
31
42
|
disabled?: boolean;
|
|
32
43
|
error?: boolean;
|
|
33
44
|
options: Option[];
|
|
34
|
-
searchKey?:
|
|
45
|
+
searchKey?: SelectSearchKey<Option>;
|
|
46
|
+
searchQuery?: string;
|
|
47
|
+
defaultSearchQuery?: string;
|
|
35
48
|
onSearchChange?: (query: string) => void;
|
|
36
|
-
|
|
49
|
+
searchMode?: SelectSearchMode;
|
|
50
|
+
allowDeselect?: boolean;
|
|
51
|
+
getItemId?: (item: Option) => ListItemId;
|
|
37
52
|
};
|
|
38
53
|
|
|
54
|
+
export type SelectSearchInputProps = Omit<TextInputProps, 'value' | 'onChangeText'>;
|
|
55
|
+
|
|
39
56
|
type SingleSelectProps<Option extends DefaultItemT = DefaultItemT> = {
|
|
40
57
|
multiple?: false | undefined;
|
|
41
|
-
value?: ListValue<
|
|
42
|
-
defaultValue?: ListValue<
|
|
43
|
-
onChange?: (
|
|
44
|
-
value: ListValue<Option, false>,
|
|
45
|
-
item: Option,
|
|
46
|
-
event?: GestureResponderEvent,
|
|
47
|
-
) => void;
|
|
58
|
+
value?: ListValue<false>;
|
|
59
|
+
defaultValue?: ListValue<false>;
|
|
60
|
+
onChange?: (value: ListValue<false>, item: Option, event?: GestureResponderEvent) => void;
|
|
48
61
|
};
|
|
49
62
|
|
|
50
63
|
type MultipleSelectProps<Option extends DefaultItemT = DefaultItemT> = {
|
|
51
64
|
multiple: true;
|
|
52
|
-
value?: ListValue<
|
|
53
|
-
defaultValue?: ListValue<
|
|
54
|
-
onChange?: (
|
|
55
|
-
value: ListValue<Option, true>,
|
|
56
|
-
item: Option,
|
|
57
|
-
event?: GestureResponderEvent,
|
|
58
|
-
) => void;
|
|
65
|
+
value?: ListValue<true>;
|
|
66
|
+
defaultValue?: ListValue<true>;
|
|
67
|
+
onChange?: (value: ListValue<true>, item: Option, event?: GestureResponderEvent) => void;
|
|
59
68
|
};
|
|
60
69
|
|
|
61
70
|
export type SelectProps<Option extends DefaultItemT = DefaultItemT> = SelectPropsBase<Option> &
|
|
62
71
|
(SingleSelectProps<Option> | MultipleSelectProps<Option>);
|
|
63
72
|
|
|
73
|
+
export type SelectContentProps<Option extends DefaultItemT = DefaultItemT> = Omit<
|
|
74
|
+
ListContentProps,
|
|
75
|
+
'children'
|
|
76
|
+
> & {
|
|
77
|
+
children?: ReactNode | ((item: Option, isSelected: boolean) => ReactNode);
|
|
78
|
+
};
|
|
79
|
+
|
|
64
80
|
// Select.Trigger props
|
|
65
81
|
export type SelectTriggerProps = ViewProps & {
|
|
66
82
|
children?: ReactNode;
|
|
@@ -92,16 +92,23 @@ const triggerDefaultStyles = StyleSheet.create(theme => ({
|
|
|
92
92
|
}));
|
|
93
93
|
|
|
94
94
|
export const defaultStyles = StyleSheet.create(theme => ({
|
|
95
|
+
valueText: {
|
|
96
|
+
flex: 1,
|
|
97
|
+
},
|
|
95
98
|
chipContainer: {
|
|
96
99
|
flexDirection: 'row',
|
|
97
100
|
flexWrap: 'wrap',
|
|
98
101
|
gap: 6,
|
|
99
102
|
maxWidth: '90%',
|
|
103
|
+
flex: 1,
|
|
100
104
|
},
|
|
101
105
|
searchInput: {
|
|
102
106
|
marginHorizontal: theme.spacings['2'],
|
|
103
107
|
marginVertical: theme.spacings['3'],
|
|
104
108
|
},
|
|
109
|
+
searchInputInput: {
|
|
110
|
+
height: 42,
|
|
111
|
+
},
|
|
105
112
|
}));
|
|
106
113
|
|
|
107
114
|
export const triggerStyles = getRegisteredComponentStylesWithFallback(
|