react-native-molecules 0.5.0-beta.2 → 0.5.0-beta.20
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/README.md +1 -1
- package/components/Accordion/Accordion.tsx +2 -6
- package/components/Accordion/AccordionItem.tsx +16 -12
- package/components/Accordion/AccordionItemContent.tsx +6 -1
- package/components/Accordion/AccordionItemHeader.tsx +1 -1
- package/components/Accordion/utils.ts +6 -0
- package/components/ActivityIndicator/ActivityIndicator.tsx +6 -15
- package/components/Appbar/AppbarBase.tsx +18 -13
- package/components/Button/Button.tsx +209 -264
- package/components/Button/index.tsx +9 -3
- package/components/Button/types.ts +16 -2
- package/components/Button/utils.ts +230 -208
- package/components/Checkbox/CheckboxBase.tsx +23 -128
- package/components/Checkbox/utils.ts +0 -25
- package/components/Chip/Chip.tsx +40 -52
- package/components/Chip/utils.ts +3 -7
- package/components/DateField/DateField.tsx +110 -0
- package/components/DateField/index.tsx +6 -0
- package/components/{DatePickerInput/inputUtils.ts → DateField/useDateFieldState.ts} +17 -49
- package/components/DatePicker/DateCalendar.tsx +83 -0
- package/components/DatePicker/DatePickerActions.tsx +73 -0
- package/components/DatePicker/DatePickerModal.tsx +234 -0
- package/components/DatePicker/DatePickerPopover.tsx +79 -0
- package/components/DatePicker/DatePickerProvider.tsx +152 -0
- package/components/DatePicker/DatePickerTrigger.tsx +23 -0
- package/components/DatePicker/context.tsx +82 -0
- package/components/DatePicker/index.tsx +44 -0
- package/components/DatePicker/utils.ts +293 -0
- package/components/DatePickerInline/DatePickerContext.tsx +1 -0
- package/components/DatePickerInline/DatePickerDockedHeader.tsx +113 -0
- package/components/DatePickerInline/DatePickerInline.tsx +16 -15
- package/components/DatePickerInline/DatePickerInlineBase.tsx +7 -1
- package/components/DatePickerInline/Day.tsx +25 -1
- package/components/DatePickerInline/DayRange.tsx +2 -4
- package/components/DatePickerInline/HeaderItem.tsx +42 -27
- package/components/DatePickerInline/Month.tsx +45 -65
- package/components/DatePickerInline/MonthPicker.tsx +25 -41
- package/components/DatePickerInline/Swiper.native.tsx +21 -4
- package/components/DatePickerInline/Swiper.tsx +168 -13
- package/components/DatePickerInline/Week.tsx +6 -1
- package/components/DatePickerInline/YearPicker.tsx +206 -53
- package/components/DatePickerInline/dateUtils.tsx +17 -12
- package/components/DatePickerInline/types.ts +3 -0
- package/components/DatePickerInline/utils.ts +66 -29
- package/components/Drawer/Drawer.tsx +17 -6
- package/components/ElementGroup/ElementGroup.tsx +16 -14
- package/components/FilePicker/FilePicker.tsx +48 -78
- package/components/FilePicker/index.tsx +2 -1
- package/components/FilePicker/utils.ts +9 -0
- package/components/HelperText/HelperText.tsx +0 -35
- package/components/Icon/iconFactory.tsx +3 -3
- package/components/Icon/index.tsx +1 -1
- package/components/Icon/types.ts +17 -6
- package/components/IconButton/IconButton.tsx +42 -57
- package/components/IconButton/utils.ts +142 -33
- package/components/ListItem/ListItem.tsx +3 -1
- package/components/ListItem/utils.ts +1 -1
- package/components/LoadingIndicator/LoadingIndicator.tsx +253 -0
- package/components/LoadingIndicator/LoadingIndicator.web.tsx +136 -0
- package/components/LoadingIndicator/index.tsx +13 -0
- package/components/LoadingIndicator/utils.ts +117 -0
- package/components/Menu/Menu.tsx +3 -18
- package/components/NavigationRail/NavigationRail.tsx +15 -9
- package/components/Popover/Popover.tsx +122 -145
- package/components/Popover/PopoverRoot.tsx +74 -0
- package/components/Popover/common.ts +50 -34
- package/components/Popover/index.ts +18 -1
- package/components/Popover/usePlatformMeasure.native.ts +90 -0
- package/components/Popover/usePlatformMeasure.ts +118 -0
- package/components/Popover/utils.ts +34 -0
- package/components/Select/Select.tsx +368 -507
- package/components/Select/context.tsx +72 -0
- package/components/Select/index.ts +8 -14
- package/components/Select/types.ts +2 -4
- package/components/Select/utils.ts +144 -0
- package/components/Slot/Slot.tsx +244 -0
- package/components/Slot/compose-refs.tsx +62 -0
- package/components/Slot/index.tsx +8 -0
- package/components/Surface/Surface.android.tsx +34 -8
- package/components/Surface/Surface.ios.tsx +36 -29
- package/components/Surface/Surface.tsx +31 -4
- package/components/Surface/utils.ts +44 -30
- package/components/Switch/Switch.tsx +8 -2
- package/components/Tabs/TabItem.tsx +35 -58
- package/components/Tabs/TabLabel.tsx +5 -9
- package/components/Tabs/Tabs.tsx +154 -148
- package/components/Tabs/utils.ts +15 -2
- package/components/TextInput/TextInput.tsx +658 -575
- package/components/TextInput/index.tsx +19 -3
- package/components/TextInput/types.ts +76 -27
- package/components/TextInput/utils.ts +225 -145
- package/components/TimeField/TimeField.tsx +75 -0
- package/components/TimeField/index.tsx +6 -0
- package/components/TimeField/useTimeFieldState.ts +70 -0
- package/components/{TimePickerField/sanitizeTime.ts → TimeField/utils.ts} +77 -10
- package/components/TimePicker/TimeInput.tsx +87 -37
- package/components/TimePicker/TimeInputs.tsx +137 -49
- package/components/TimePicker/TimePicker.tsx +73 -10
- package/components/TimePicker/TimePickerModal.tsx +186 -0
- package/components/TimePicker/context.tsx +17 -0
- package/components/TimePicker/index.tsx +15 -3
- package/components/TimePicker/utils.ts +93 -0
- package/components/Tooltip/Tooltip.tsx +42 -67
- package/components/Tooltip/TooltipContent.tsx +32 -5
- package/components/Tooltip/TooltipTrigger.tsx +20 -20
- package/components/Tooltip/index.tsx +1 -1
- package/components/TouchableRipple/TouchableRipple.native.tsx +50 -14
- package/components/TouchableRipple/TouchableRipple.tsx +137 -47
- package/hocs/withPortal.tsx +1 -1
- package/hooks/index.tsx +0 -6
- package/hooks/useActionState.tsx +19 -8
- package/hooks/useControlledValue.tsx +20 -4
- package/hooks/useFilePicker.tsx +6 -16
- package/hooks/useWhatHasUpdated.tsx +48 -0
- package/package.json +17 -13
- package/shortcuts-manager/ShortcutsManager/ShortcutsManager.tsx +5 -2
- package/styles/shadow.ts +2 -1
- package/styles/themes/LightTheme.tsx +1 -1
- package/utils/DocumentPicker/documentPicker.ts +78 -27
- package/utils/DocumentPicker/types.ts +0 -1
- package/utils/extractPropertiesFromStyles.ts +25 -0
- package/utils/extractSubcomponents.ts +89 -0
- package/utils/lodash.ts +77 -5
- package/components/DatePickerDocked/DatePickerDocked.tsx +0 -30
- package/components/DatePickerDocked/DatePickerDockedHeader.tsx +0 -129
- package/components/DatePickerDocked/index.tsx +0 -17
- package/components/DatePickerDocked/types.ts +0 -11
- package/components/DatePickerDocked/utils.ts +0 -157
- package/components/DatePickerInput/DatePickerInput.tsx +0 -139
- package/components/DatePickerInput/DatePickerInputModal.tsx +0 -48
- package/components/DatePickerInput/DatePickerInputWithoutModal.tsx +0 -77
- package/components/DatePickerInput/DateRangeInput.tsx +0 -88
- package/components/DatePickerInput/index.tsx +0 -10
- package/components/DatePickerInput/types.ts +0 -28
- package/components/DatePickerInput/utils.ts +0 -15
- package/components/DatePickerModal/AnimatedCrossView.tsx +0 -94
- package/components/DatePickerModal/CalendarEdit.tsx +0 -139
- package/components/DatePickerModal/DatePickerModal.tsx +0 -85
- package/components/DatePickerModal/DatePickerModalContent.tsx +0 -155
- package/components/DatePickerModal/DatePickerModalContentHeader.tsx +0 -213
- package/components/DatePickerModal/DatePickerModalHeader.tsx +0 -74
- package/components/DatePickerModal/DatePickerModalHeaderBackground.tsx +0 -13
- package/components/DatePickerModal/index.tsx +0 -16
- package/components/DatePickerModal/types.ts +0 -92
- package/components/DatePickerModal/utils.ts +0 -122
- package/components/DateTimePicker/DateTimePicker.tsx +0 -172
- package/components/DateTimePicker/index.tsx +0 -10
- package/components/DateTimePicker/utils.ts +0 -12
- package/components/Popover/Popover.native.tsx +0 -185
- package/components/TimePickerField/TimePickerField.tsx +0 -152
- package/components/TimePickerField/index.tsx +0 -10
- package/components/TimePickerField/utils.ts +0 -94
- package/components/TimePickerModal/TimePickerModal.tsx +0 -115
- package/components/TimePickerModal/index.tsx +0 -10
- package/components/TimePickerModal/utils.ts +0 -47
- package/hooks/useSearchable.tsx +0 -74
- package/hooks/useSubcomponents.tsx +0 -59
|
@@ -1,13 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createContext,
|
|
3
|
-
memo,
|
|
4
|
-
useCallback,
|
|
5
|
-
useContext,
|
|
6
|
-
useEffect,
|
|
7
|
-
useMemo,
|
|
8
|
-
useRef,
|
|
9
|
-
useState,
|
|
10
|
-
} from 'react';
|
|
1
|
+
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
11
2
|
import {
|
|
12
3
|
type AccessibilityRole,
|
|
13
4
|
type GestureResponderEvent,
|
|
@@ -17,281 +8,235 @@ import {
|
|
|
17
8
|
ScrollView,
|
|
18
9
|
View,
|
|
19
10
|
} from 'react-native';
|
|
20
|
-
import { StyleSheet } from 'react-native-unistyles';
|
|
21
11
|
|
|
22
|
-
import {
|
|
12
|
+
import { typedMemo } from '../../hocs';
|
|
13
|
+
import { useActionState, useControlledValue, useLatest } from '../../hooks';
|
|
23
14
|
import { useToggle } from '../../hooks';
|
|
24
15
|
import { resolveStateVariant } from '../../utils';
|
|
25
16
|
import { Chip } from '../Chip';
|
|
26
17
|
import { Icon } from '../Icon';
|
|
27
18
|
import { IconButton } from '../IconButton';
|
|
28
19
|
import { Popover } from '../Popover';
|
|
29
|
-
import { registerPortalContext } from '../Portal';
|
|
30
20
|
import { Text } from '../Text';
|
|
31
21
|
import { TextInput, type TextInputHandles, type TextInputProps } from '../TextInput';
|
|
22
|
+
import {
|
|
23
|
+
SelectContextProvider,
|
|
24
|
+
SelectDropdownContextProvider,
|
|
25
|
+
useSelectContextValue,
|
|
26
|
+
useSelectDropdownContextValue,
|
|
27
|
+
} from './context';
|
|
32
28
|
import type {
|
|
33
29
|
DefaultItemT,
|
|
34
30
|
SelectContentProps,
|
|
35
31
|
SelectContextValue,
|
|
36
|
-
SelectDropdownContextValue,
|
|
37
32
|
SelectDropdownProps,
|
|
38
33
|
SelectGroupProps,
|
|
39
34
|
SelectOptionProps,
|
|
40
|
-
|
|
35
|
+
SelectProps,
|
|
41
36
|
SelectSearchInputProps,
|
|
42
37
|
SelectTriggerProps,
|
|
43
38
|
SelectValueProps,
|
|
44
39
|
} from './types';
|
|
40
|
+
import { styles, triggerStyles } from './utils';
|
|
45
41
|
|
|
46
|
-
|
|
47
|
-
export const SelectContext = createContext<SelectContextValue<DefaultItemT>>({
|
|
48
|
-
value: null,
|
|
49
|
-
multiple: false,
|
|
50
|
-
onAdd: () => {},
|
|
51
|
-
onRemove: () => {},
|
|
52
|
-
disabled: false,
|
|
53
|
-
error: false,
|
|
54
|
-
labelKey: 'label',
|
|
55
|
-
options: [],
|
|
56
|
-
searchQuery: '',
|
|
57
|
-
setSearchQuery: () => {},
|
|
58
|
-
filteredOptions: [],
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
export const useSelectContext = <Option extends DefaultItemT = DefaultItemT>() => {
|
|
62
|
-
return useContext(SelectContext) as unknown as SelectContextValue<Option>;
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
export const useSelectContextValue = <Option extends DefaultItemT = DefaultItemT, T = any>(
|
|
66
|
-
selector: (state: SelectContextValue<Option>) => T,
|
|
67
|
-
): T => {
|
|
68
|
-
const context = useContext(SelectContext) as unknown as SelectContextValue<Option>;
|
|
69
|
-
return selector(context);
|
|
70
|
-
};
|
|
42
|
+
const emptyArr: unknown[] = [];
|
|
71
43
|
|
|
72
|
-
//
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
44
|
+
// SelectProvider - manages controlled/uncontrolled state
|
|
45
|
+
const SelectProvider = typedMemo(
|
|
46
|
+
<Option extends DefaultItemT = DefaultItemT>({
|
|
47
|
+
children,
|
|
48
|
+
value: valueProp,
|
|
49
|
+
defaultValue,
|
|
50
|
+
onChange,
|
|
51
|
+
multiple = false,
|
|
52
|
+
disabled = false,
|
|
53
|
+
error = false,
|
|
54
|
+
labelKey = 'label',
|
|
55
|
+
options = emptyArr as Option[],
|
|
56
|
+
searchKey,
|
|
57
|
+
onSearchChange,
|
|
58
|
+
hideSelected: hideSelectedProp,
|
|
59
|
+
}: SelectProps<Option>) => {
|
|
60
|
+
const [value, onValueChange] = useControlledValue<Option['id'] | Option['id'][] | null>({
|
|
61
|
+
value: valueProp,
|
|
62
|
+
defaultValue: defaultValue ?? (multiple ? (emptyArr as Option['id'][]) : null),
|
|
63
|
+
onChange,
|
|
64
|
+
});
|
|
65
|
+
const valueRef = useLatest(value);
|
|
89
66
|
|
|
90
|
-
|
|
67
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
91
68
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
69
|
+
const handleSearchQueryChange = useCallback(
|
|
70
|
+
(query: string) => {
|
|
71
|
+
setSearchQuery(query);
|
|
72
|
+
onSearchChange?.(query);
|
|
73
|
+
},
|
|
74
|
+
[onSearchChange],
|
|
75
|
+
);
|
|
95
76
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
): T => {
|
|
99
|
-
const context = useContext(SelectDropdownContext);
|
|
100
|
-
return selector(context);
|
|
101
|
-
};
|
|
77
|
+
// Default hideSelected to multiple (true for multi-select, false for single select)
|
|
78
|
+
const hideSelected = hideSelectedProp !== undefined ? hideSelectedProp : multiple;
|
|
102
79
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
children,
|
|
106
|
-
value: valueProp,
|
|
107
|
-
defaultValue,
|
|
108
|
-
onChange,
|
|
109
|
-
multiple = false,
|
|
110
|
-
disabled = false,
|
|
111
|
-
error = false,
|
|
112
|
-
labelKey = 'label',
|
|
113
|
-
options = [],
|
|
114
|
-
searchKey,
|
|
115
|
-
onSearchChange,
|
|
116
|
-
hideSelected: hideSelectedProp,
|
|
117
|
-
}: SelectProviderProps<Option>) => {
|
|
118
|
-
const [value, onValueChange] = useControlledValue<Option['id'] | Option['id'][] | null>({
|
|
119
|
-
value: valueProp,
|
|
120
|
-
defaultValue: defaultValue ?? (multiple ? [] : null),
|
|
121
|
-
onChange: (newValue, item, event) => {
|
|
122
|
-
onChange?.(newValue, item as Option, event);
|
|
123
|
-
},
|
|
124
|
-
});
|
|
80
|
+
const filteredOptions = useMemo(() => {
|
|
81
|
+
let result = options;
|
|
125
82
|
|
|
126
|
-
|
|
83
|
+
// Filter out selected items if hideSelected is true
|
|
84
|
+
if (hideSelected) {
|
|
85
|
+
result = result.filter(item => {
|
|
86
|
+
if (multiple) {
|
|
87
|
+
const values = (value as Option['id'][]) || [];
|
|
88
|
+
return !values.some(v => v === item.id);
|
|
89
|
+
} else {
|
|
90
|
+
const singleValue = value as Option['id'] | null;
|
|
91
|
+
return singleValue !== item.id;
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
127
95
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
96
|
+
// Apply search filter if there's a search query
|
|
97
|
+
if (searchQuery) {
|
|
98
|
+
const key = searchKey || labelKey || 'label';
|
|
99
|
+
const lowerQuery = searchQuery.toLowerCase();
|
|
100
|
+
result = result.filter(item => {
|
|
101
|
+
const itemValue = item[key];
|
|
102
|
+
return String(itemValue || '')
|
|
103
|
+
.toLowerCase()
|
|
104
|
+
.includes(lowerQuery);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
135
107
|
|
|
136
|
-
|
|
137
|
-
|
|
108
|
+
return result;
|
|
109
|
+
}, [options, searchQuery, searchKey, labelKey, hideSelected, multiple, value]);
|
|
138
110
|
|
|
139
|
-
|
|
140
|
-
|
|
111
|
+
const onAdd = useCallback(
|
|
112
|
+
(item: Option) => {
|
|
113
|
+
if (multiple) {
|
|
114
|
+
const currentValue = (valueRef.current as Option['id'][]) || [];
|
|
115
|
+
if (!currentValue.find(v => v === item.id)) {
|
|
116
|
+
onValueChange([...currentValue, item.id] as Option['id'][], item);
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
onValueChange(item.id, item);
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
[multiple, valueRef, onValueChange],
|
|
123
|
+
);
|
|
141
124
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
result = result.filter(item => {
|
|
125
|
+
const onRemove = useCallback(
|
|
126
|
+
(item: Option) => {
|
|
145
127
|
if (multiple) {
|
|
146
|
-
const
|
|
147
|
-
|
|
128
|
+
const currentValue = (valueRef.current as Option['id'][]) || [];
|
|
129
|
+
onValueChange(currentValue.filter(v => v !== item.id) as Option['id'][], item);
|
|
148
130
|
} else {
|
|
149
|
-
|
|
150
|
-
return singleValue !== item.id;
|
|
131
|
+
onValueChange(null, item);
|
|
151
132
|
}
|
|
152
|
-
}
|
|
153
|
-
|
|
133
|
+
},
|
|
134
|
+
[multiple, valueRef, onValueChange],
|
|
135
|
+
);
|
|
154
136
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
137
|
+
const contextValue = useMemo(
|
|
138
|
+
() => ({
|
|
139
|
+
value: value,
|
|
140
|
+
multiple,
|
|
141
|
+
onAdd: onAdd as (item: DefaultItemT) => void,
|
|
142
|
+
onRemove: onRemove as (item: DefaultItemT) => void,
|
|
143
|
+
disabled,
|
|
144
|
+
error,
|
|
145
|
+
labelKey,
|
|
146
|
+
options,
|
|
147
|
+
searchQuery,
|
|
148
|
+
setSearchQuery: handleSearchQueryChange,
|
|
149
|
+
filteredOptions,
|
|
150
|
+
}),
|
|
151
|
+
[
|
|
152
|
+
value,
|
|
153
|
+
multiple,
|
|
154
|
+
onAdd,
|
|
155
|
+
onRemove,
|
|
156
|
+
disabled,
|
|
157
|
+
error,
|
|
158
|
+
labelKey,
|
|
159
|
+
options,
|
|
160
|
+
searchQuery,
|
|
161
|
+
handleSearchQueryChange,
|
|
162
|
+
filteredOptions,
|
|
163
|
+
],
|
|
164
|
+
);
|
|
166
165
|
|
|
167
|
-
return
|
|
168
|
-
|
|
166
|
+
return (
|
|
167
|
+
<SelectContextProvider
|
|
168
|
+
value={contextValue as unknown as SelectContextValue<DefaultItemT>}>
|
|
169
|
+
{children}
|
|
170
|
+
</SelectContextProvider>
|
|
171
|
+
);
|
|
172
|
+
},
|
|
173
|
+
);
|
|
169
174
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
175
|
+
// SelectDropdownProvider - manages dropdown state
|
|
176
|
+
const SelectDropdownProvider = memo(
|
|
177
|
+
({
|
|
178
|
+
children,
|
|
179
|
+
isOpen: isOpenProp,
|
|
180
|
+
onClose: onCloseProp,
|
|
181
|
+
}: {
|
|
182
|
+
children: React.ReactNode;
|
|
183
|
+
isOpen?: boolean;
|
|
184
|
+
onClose?: () => void;
|
|
185
|
+
}) => {
|
|
186
|
+
const { state: isOpen, handleOpen, handleClose } = useToggle(false);
|
|
187
|
+
const triggerRef = useRef<View>(null);
|
|
188
|
+
const contentRef = useRef<any>(null);
|
|
189
|
+
const [triggerLayout, setTriggerLayout] = useState<{
|
|
190
|
+
width: number;
|
|
191
|
+
height: number;
|
|
192
|
+
} | null>(null);
|
|
193
|
+
const isControlled = isOpenProp !== undefined;
|
|
194
|
+
|
|
195
|
+
const onClose = useCallback(() => {
|
|
196
|
+
if (isControlled) {
|
|
197
|
+
onCloseProp?.();
|
|
177
198
|
} else {
|
|
178
|
-
|
|
199
|
+
handleClose();
|
|
179
200
|
}
|
|
180
|
-
},
|
|
181
|
-
[multiple, value, onValueChange],
|
|
182
|
-
);
|
|
201
|
+
}, [isControlled, onCloseProp, handleClose]);
|
|
183
202
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const currentValue = (value as Option['id'][]) || [];
|
|
188
|
-
onValueChange(currentValue.filter(v => v !== item.id) as Option['id'][], item);
|
|
189
|
-
} else {
|
|
190
|
-
onValueChange(null, item);
|
|
203
|
+
const onOpen = useCallback(() => {
|
|
204
|
+
if (!isControlled) {
|
|
205
|
+
handleOpen();
|
|
191
206
|
}
|
|
192
|
-
},
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
searchQuery,
|
|
207
|
-
setSearchQuery: handleSearchQueryChange,
|
|
208
|
-
filteredOptions,
|
|
209
|
-
}),
|
|
210
|
-
[
|
|
211
|
-
value,
|
|
212
|
-
multiple,
|
|
213
|
-
onAdd,
|
|
214
|
-
onRemove,
|
|
215
|
-
disabled,
|
|
216
|
-
error,
|
|
217
|
-
labelKey,
|
|
218
|
-
options,
|
|
219
|
-
searchQuery,
|
|
220
|
-
handleSearchQueryChange,
|
|
221
|
-
filteredOptions,
|
|
222
|
-
],
|
|
223
|
-
);
|
|
224
|
-
|
|
225
|
-
return (
|
|
226
|
-
<SelectContext.Provider value={contextValue as unknown as SelectContextValue<DefaultItemT>}>
|
|
227
|
-
{children}
|
|
228
|
-
</SelectContext.Provider>
|
|
229
|
-
);
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
// SelectDropdownProvider - manages dropdown state
|
|
233
|
-
const SelectDropdownProvider = ({
|
|
234
|
-
children,
|
|
235
|
-
isOpen: isOpenProp,
|
|
236
|
-
onClose: onCloseProp,
|
|
237
|
-
}: {
|
|
238
|
-
children: React.ReactNode;
|
|
239
|
-
isOpen?: boolean;
|
|
240
|
-
onClose?: () => void;
|
|
241
|
-
}) => {
|
|
242
|
-
const { state: isOpen, handleOpen, handleClose } = useToggle(false);
|
|
243
|
-
const triggerRef = useRef<View>(null);
|
|
244
|
-
const contentRef = useRef<any>(null);
|
|
245
|
-
const [triggerLayout, setTriggerLayout] = useState<{ width: number; height: number } | null>(
|
|
246
|
-
null,
|
|
247
|
-
);
|
|
248
|
-
const isControlled = isOpenProp !== undefined;
|
|
249
|
-
|
|
250
|
-
const onClose = useCallback(() => {
|
|
251
|
-
if (isControlled) {
|
|
252
|
-
onCloseProp?.();
|
|
253
|
-
} else {
|
|
254
|
-
handleClose();
|
|
255
|
-
}
|
|
256
|
-
}, [isControlled, onCloseProp, handleClose]);
|
|
257
|
-
|
|
258
|
-
const onOpen = useCallback(() => {
|
|
259
|
-
if (!isControlled) {
|
|
260
|
-
handleOpen();
|
|
261
|
-
}
|
|
262
|
-
}, [handleOpen, isControlled]);
|
|
263
|
-
|
|
264
|
-
const contextValue = useMemo(
|
|
265
|
-
() => ({
|
|
266
|
-
isOpen: isControlled ? isOpenProp! : isOpen,
|
|
267
|
-
onClose,
|
|
268
|
-
onOpen,
|
|
269
|
-
triggerRef: triggerRef as React.RefObject<View>,
|
|
270
|
-
contentRef,
|
|
271
|
-
triggerLayout,
|
|
272
|
-
setTriggerLayout,
|
|
273
|
-
}),
|
|
274
|
-
[isControlled, isOpenProp, isOpen, onClose, onOpen, triggerLayout],
|
|
275
|
-
);
|
|
207
|
+
}, [handleOpen, isControlled]);
|
|
208
|
+
|
|
209
|
+
const contextValue = useMemo(
|
|
210
|
+
() => ({
|
|
211
|
+
isOpen: isControlled ? isOpenProp! : isOpen,
|
|
212
|
+
onClose,
|
|
213
|
+
onOpen,
|
|
214
|
+
triggerRef: triggerRef as React.RefObject<View>,
|
|
215
|
+
contentRef,
|
|
216
|
+
triggerLayout,
|
|
217
|
+
setTriggerLayout,
|
|
218
|
+
}),
|
|
219
|
+
[isControlled, isOpenProp, isOpen, onClose, onOpen, triggerLayout],
|
|
220
|
+
);
|
|
276
221
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
222
|
+
return (
|
|
223
|
+
<SelectDropdownContextProvider value={contextValue}>
|
|
224
|
+
{children}
|
|
225
|
+
</SelectDropdownContextProvider>
|
|
226
|
+
);
|
|
227
|
+
},
|
|
228
|
+
);
|
|
283
229
|
|
|
284
230
|
// Select - wrapper component
|
|
285
|
-
const Select =
|
|
286
|
-
children,
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
};
|
|
231
|
+
const Select = typedMemo(
|
|
232
|
+
<Option extends DefaultItemT = DefaultItemT>({ children, ...props }: SelectProps<Option>) => {
|
|
233
|
+
return (
|
|
234
|
+
<SelectProvider<Option> {...props}>
|
|
235
|
+
<SelectDropdownProvider>{children}</SelectDropdownProvider>
|
|
236
|
+
</SelectProvider>
|
|
237
|
+
);
|
|
238
|
+
},
|
|
239
|
+
);
|
|
295
240
|
|
|
296
241
|
// Select.Trigger - opens the dropdown
|
|
297
242
|
const SelectTrigger = ({ children, style, ...rest }: SelectTriggerProps) => {
|
|
@@ -363,7 +308,7 @@ const SelectTrigger = ({ children, style, ...rest }: SelectTriggerProps) => {
|
|
|
363
308
|
SelectTrigger.displayName = 'Select_Trigger';
|
|
364
309
|
|
|
365
310
|
// Select.Value - displays the value
|
|
366
|
-
const SelectValue = ({ placeholder, renderValue, style, ...rest }: SelectValueProps) => {
|
|
311
|
+
const SelectValue = memo(({ placeholder, renderValue, style, ...rest }: SelectValueProps) => {
|
|
367
312
|
const { value, multiple, labelKey, onRemove, options } = useSelectContextValue(state => ({
|
|
368
313
|
value: state.value,
|
|
369
314
|
multiple: state.multiple,
|
|
@@ -409,13 +354,10 @@ const SelectValue = ({ placeholder, renderValue, style, ...rest }: SelectValuePr
|
|
|
409
354
|
return (
|
|
410
355
|
<View style={[styles.chipContainer, style]} {...rest}>
|
|
411
356
|
{(resolvedValue as DefaultItemT[]).map(item => (
|
|
412
|
-
<
|
|
357
|
+
<SelectValueItem
|
|
413
358
|
key={item.id || String(item)}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
selected
|
|
417
|
-
left={<></>}
|
|
418
|
-
onClose={() => onRemove(item)}
|
|
359
|
+
item={item}
|
|
360
|
+
onRemoveItem={onRemove}
|
|
419
361
|
/>
|
|
420
362
|
))}
|
|
421
363
|
</View>
|
|
@@ -427,67 +369,95 @@ const SelectValue = ({ placeholder, renderValue, style, ...rest }: SelectValuePr
|
|
|
427
369
|
{displayValue}
|
|
428
370
|
</Text>
|
|
429
371
|
);
|
|
430
|
-
};
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
const SelectValueItem = typedMemo(
|
|
375
|
+
({
|
|
376
|
+
item,
|
|
377
|
+
onRemoveItem,
|
|
378
|
+
}: {
|
|
379
|
+
item: DefaultItemT;
|
|
380
|
+
onRemoveItem: (item: DefaultItemT) => void;
|
|
381
|
+
}) => {
|
|
382
|
+
const onRemove = useCallback(() => {
|
|
383
|
+
onRemoveItem(item);
|
|
384
|
+
}, [item, onRemoveItem]);
|
|
385
|
+
|
|
386
|
+
return (
|
|
387
|
+
<Chip.Input
|
|
388
|
+
label={item[item.labelKey || 'label'] || String(item.id || item)}
|
|
389
|
+
size="sm"
|
|
390
|
+
selected
|
|
391
|
+
left={<></>}
|
|
392
|
+
onClose={onRemove}
|
|
393
|
+
/>
|
|
394
|
+
);
|
|
395
|
+
},
|
|
396
|
+
);
|
|
431
397
|
|
|
432
398
|
SelectValue.displayName = 'Select_Value';
|
|
433
399
|
|
|
434
400
|
// Select.Dropdown - popover with keyboard navigation
|
|
435
|
-
const SelectDropdown = (
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
401
|
+
const SelectDropdown = memo(
|
|
402
|
+
({
|
|
403
|
+
children,
|
|
404
|
+
WrapperComponent,
|
|
405
|
+
wrapperComponentProps,
|
|
406
|
+
enableKeyboardNavigation = true,
|
|
407
|
+
style: popoverStyleProp,
|
|
408
|
+
...popoverProps
|
|
409
|
+
}: SelectDropdownProps & { enableKeyboardNavigation?: boolean }) => {
|
|
410
|
+
const { isOpen, onClose, triggerRef, triggerLayout } = useSelectDropdownContextValue(
|
|
411
|
+
state => ({
|
|
412
|
+
isOpen: state.isOpen,
|
|
413
|
+
onClose: state.onClose,
|
|
414
|
+
triggerRef: state.triggerRef,
|
|
415
|
+
triggerLayout: state.triggerLayout,
|
|
416
|
+
}),
|
|
417
|
+
);
|
|
449
418
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
419
|
+
const popoverStyle = useMemo(() => {
|
|
420
|
+
const baseStyle = popoverStyleProp ? [popoverStyleProp] : [];
|
|
421
|
+
if (triggerLayout) {
|
|
422
|
+
return [{ width: triggerLayout.width }, ...baseStyle];
|
|
423
|
+
}
|
|
424
|
+
return baseStyle;
|
|
425
|
+
}, [triggerLayout, popoverStyleProp]);
|
|
457
426
|
|
|
458
|
-
|
|
427
|
+
if (!triggerLayout) return null;
|
|
428
|
+
|
|
429
|
+
if (WrapperComponent) {
|
|
430
|
+
return (
|
|
431
|
+
<WrapperComponent isOpen={isOpen} onClose={onClose} {...wrapperComponentProps}>
|
|
432
|
+
{enableKeyboardNavigation && Platform.OS === 'web' ? (
|
|
433
|
+
<KeyboardNavigationWrapper>{children}</KeyboardNavigationWrapper>
|
|
434
|
+
) : (
|
|
435
|
+
children
|
|
436
|
+
)}
|
|
437
|
+
</WrapperComponent>
|
|
438
|
+
);
|
|
439
|
+
}
|
|
459
440
|
|
|
460
|
-
if (WrapperComponent) {
|
|
461
441
|
return (
|
|
462
|
-
<
|
|
442
|
+
<Popover
|
|
443
|
+
triggerRef={triggerRef as React.RefObject<View>}
|
|
444
|
+
isOpen={isOpen}
|
|
445
|
+
onClose={onClose}
|
|
446
|
+
style={popoverStyle}
|
|
447
|
+
triggerDimensions={triggerLayout}
|
|
448
|
+
{...popoverProps}>
|
|
463
449
|
{enableKeyboardNavigation && Platform.OS === 'web' ? (
|
|
464
450
|
<KeyboardNavigationWrapper>{children}</KeyboardNavigationWrapper>
|
|
465
451
|
) : (
|
|
466
452
|
children
|
|
467
453
|
)}
|
|
468
|
-
</
|
|
454
|
+
</Popover>
|
|
469
455
|
);
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
return (
|
|
473
|
-
<Popover
|
|
474
|
-
triggerRef={triggerRef as React.RefObject<View>}
|
|
475
|
-
isOpen={isOpen}
|
|
476
|
-
onClose={onClose}
|
|
477
|
-
style={popoverStyle}
|
|
478
|
-
triggerDimensions={triggerLayout}
|
|
479
|
-
{...popoverProps}>
|
|
480
|
-
{enableKeyboardNavigation && Platform.OS === 'web' ? (
|
|
481
|
-
<KeyboardNavigationWrapper>{children}</KeyboardNavigationWrapper>
|
|
482
|
-
) : (
|
|
483
|
-
children
|
|
484
|
-
)}
|
|
485
|
-
</Popover>
|
|
486
|
-
);
|
|
487
|
-
};
|
|
456
|
+
},
|
|
457
|
+
);
|
|
488
458
|
|
|
489
459
|
// Keyboard navigation wrapper for web
|
|
490
|
-
const KeyboardNavigationWrapper = ({ children }: { children: React.ReactNode }) => {
|
|
460
|
+
const KeyboardNavigationWrapper = memo(({ children }: { children: React.ReactNode }) => {
|
|
491
461
|
const { onClose, contentRef, isOpen } = useSelectDropdownContextValue(state => ({
|
|
492
462
|
onClose: state.onClose,
|
|
493
463
|
contentRef: state.contentRef,
|
|
@@ -533,8 +503,14 @@ const KeyboardNavigationWrapper = ({ children }: { children: React.ReactNode })
|
|
|
533
503
|
break;
|
|
534
504
|
case 'Enter':
|
|
535
505
|
e.preventDefault();
|
|
506
|
+
e.stopImmediatePropagation();
|
|
536
507
|
if (currentIndex !== -1) {
|
|
537
|
-
|
|
508
|
+
// Store reference to the focused element before triggering click
|
|
509
|
+
// to prevent issues with DOM updates during the click handler
|
|
510
|
+
const focusedOption = options[currentIndex];
|
|
511
|
+
if (focusedOption) {
|
|
512
|
+
focusedOption.click();
|
|
513
|
+
}
|
|
538
514
|
}
|
|
539
515
|
break;
|
|
540
516
|
case 'Escape':
|
|
@@ -599,75 +575,81 @@ const KeyboardNavigationWrapper = ({ children }: { children: React.ReactNode })
|
|
|
599
575
|
}, [handleKeyDown, contentRef, isOpen]);
|
|
600
576
|
|
|
601
577
|
return <>{children}</>;
|
|
602
|
-
};
|
|
578
|
+
});
|
|
603
579
|
|
|
604
580
|
SelectDropdown.displayName = 'Select_Dropdown';
|
|
605
581
|
|
|
606
582
|
// Select.Content - ScrollView that renders children
|
|
607
|
-
const SelectContent = (
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
contentRef
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
const { filteredOptions, value, multiple, searchQuery, options } = useSelectContextValue(
|
|
619
|
-
state => ({
|
|
620
|
-
filteredOptions: state.filteredOptions,
|
|
621
|
-
value: state.value,
|
|
622
|
-
multiple: state.multiple,
|
|
623
|
-
searchQuery: state.searchQuery,
|
|
624
|
-
options: state.options,
|
|
625
|
-
}),
|
|
626
|
-
);
|
|
583
|
+
const SelectContent = memo(
|
|
584
|
+
({
|
|
585
|
+
children,
|
|
586
|
+
ContainerComponent = ScrollView,
|
|
587
|
+
style,
|
|
588
|
+
emptyState,
|
|
589
|
+
...rest
|
|
590
|
+
}: SelectContentProps) => {
|
|
591
|
+
const { contentRef } = useSelectDropdownContextValue(state => ({
|
|
592
|
+
contentRef: state.contentRef,
|
|
593
|
+
}));
|
|
627
594
|
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
:
|
|
595
|
+
const { filteredOptions, value, multiple, searchQuery, options } = useSelectContextValue(
|
|
596
|
+
state => ({
|
|
597
|
+
filteredOptions: state.filteredOptions,
|
|
598
|
+
value: state.value,
|
|
599
|
+
multiple: state.multiple,
|
|
600
|
+
searchQuery: state.searchQuery,
|
|
601
|
+
options: state.options,
|
|
602
|
+
}),
|
|
603
|
+
);
|
|
633
604
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
605
|
+
const content = useMemo(() => {
|
|
606
|
+
return filteredOptions.map(option => {
|
|
607
|
+
const isSelected = multiple
|
|
608
|
+
? (value as any[])?.some(v => (v?.id ?? v) === option.id)
|
|
609
|
+
: (value as any)?.id === option.id || (value as any) === option.id;
|
|
637
610
|
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
611
|
+
return children(option, !!isSelected);
|
|
612
|
+
});
|
|
613
|
+
}, [filteredOptions, value, multiple, children]);
|
|
614
|
+
|
|
615
|
+
const defaultEmptyState = useMemo(() => {
|
|
616
|
+
const hasSearchQuery = searchQuery && searchQuery.trim().length > 0;
|
|
617
|
+
const hasNoOptions = options.length === 0;
|
|
618
|
+
|
|
619
|
+
if (hasNoOptions) {
|
|
620
|
+
return (
|
|
621
|
+
<View style={styles.emptyState}>
|
|
622
|
+
<Text style={styles.emptyStateText}>No options available</Text>
|
|
623
|
+
</View>
|
|
624
|
+
);
|
|
625
|
+
}
|
|
641
626
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
627
|
+
if (hasSearchQuery) {
|
|
628
|
+
return (
|
|
629
|
+
<View style={styles.emptyState}>
|
|
630
|
+
<Text style={styles.emptyStateText}>No results found</Text>
|
|
631
|
+
</View>
|
|
632
|
+
);
|
|
633
|
+
}
|
|
649
634
|
|
|
650
|
-
if (hasSearchQuery) {
|
|
651
635
|
return (
|
|
652
636
|
<View style={styles.emptyState}>
|
|
653
|
-
<Text style={styles.emptyStateText}>No
|
|
637
|
+
<Text style={styles.emptyStateText}>No options</Text>
|
|
654
638
|
</View>
|
|
655
639
|
);
|
|
656
|
-
}
|
|
640
|
+
}, [searchQuery, options.length]);
|
|
657
641
|
|
|
658
642
|
return (
|
|
659
|
-
<
|
|
660
|
-
|
|
661
|
-
|
|
643
|
+
<ContainerComponent
|
|
644
|
+
ref={contentRef}
|
|
645
|
+
style={style}
|
|
646
|
+
{...rest}
|
|
647
|
+
accessibilityRole="listbox">
|
|
648
|
+
{filteredOptions.length === 0 ? emptyState ?? defaultEmptyState : content}
|
|
649
|
+
</ContainerComponent>
|
|
662
650
|
);
|
|
663
|
-
},
|
|
664
|
-
|
|
665
|
-
return (
|
|
666
|
-
<ContainerComponent ref={contentRef} style={style} {...rest} accessibilityRole="listbox">
|
|
667
|
-
{filteredOptions.length === 0 ? emptyState ?? defaultEmptyState : content}
|
|
668
|
-
</ContainerComponent>
|
|
669
|
-
);
|
|
670
|
-
};
|
|
651
|
+
},
|
|
652
|
+
);
|
|
671
653
|
|
|
672
654
|
SelectContent.displayName = 'Select_Content';
|
|
673
655
|
|
|
@@ -695,23 +677,17 @@ const SelectOption = memo(
|
|
|
695
677
|
...rest
|
|
696
678
|
}: SelectOptionProps<Option>) => {
|
|
697
679
|
const {
|
|
698
|
-
value: selectionValue,
|
|
699
680
|
multiple,
|
|
700
681
|
onAdd,
|
|
701
682
|
onRemove,
|
|
702
683
|
disabled: selectDisabled,
|
|
703
|
-
} = useSelectContextValue
|
|
704
|
-
value: state.value,
|
|
684
|
+
} = useSelectContextValue(state => ({
|
|
705
685
|
multiple: state.multiple,
|
|
706
686
|
onAdd: state.onAdd,
|
|
707
687
|
onRemove: state.onRemove,
|
|
708
688
|
disabled: state.disabled,
|
|
709
689
|
}));
|
|
710
690
|
|
|
711
|
-
const { onClose } = useSelectDropdownContextValue(state => ({
|
|
712
|
-
onClose: state.onClose,
|
|
713
|
-
}));
|
|
714
|
-
|
|
715
691
|
const option = useMemo(() => {
|
|
716
692
|
return {
|
|
717
693
|
id: value,
|
|
@@ -720,15 +696,19 @@ const SelectOption = memo(
|
|
|
720
696
|
} as Option;
|
|
721
697
|
}, [children, optionDisabledProp, value]);
|
|
722
698
|
|
|
723
|
-
const isSelected =
|
|
699
|
+
const isSelected = useSelectContextValue(state => {
|
|
724
700
|
if (multiple) {
|
|
725
|
-
const values =
|
|
701
|
+
const values = state.value as any[];
|
|
726
702
|
return values?.some(v => (v?.id ?? v) === option.id) || false;
|
|
727
703
|
} else {
|
|
728
|
-
const singleValue =
|
|
704
|
+
const singleValue = state.value as any;
|
|
729
705
|
return (singleValue?.id ?? singleValue) === option.id || false;
|
|
730
706
|
}
|
|
731
|
-
}
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
const { onClose } = useSelectDropdownContextValue(state => ({
|
|
710
|
+
onClose: state.onClose,
|
|
711
|
+
}));
|
|
732
712
|
|
|
733
713
|
const isOptionDisabled = Boolean(
|
|
734
714
|
selectDisabled || optionDisabledProp || option.selectable === false,
|
|
@@ -779,6 +759,14 @@ const SelectOption = memo(
|
|
|
779
759
|
tabIndex: -1 as 0 | -1 | undefined,
|
|
780
760
|
// Use a dataset attribute to help the keyboard navigator find this
|
|
781
761
|
'data-option-id': String(option.id),
|
|
762
|
+
// Prevent Pressable's native Enter key handling since we handle it in KeyboardNavigationWrapper
|
|
763
|
+
// This prevents double-triggering of onPress when Enter is pressed
|
|
764
|
+
onKeyDown: (e: React.KeyboardEvent) => {
|
|
765
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
766
|
+
e.preventDefault();
|
|
767
|
+
e.stopPropagation();
|
|
768
|
+
}
|
|
769
|
+
},
|
|
782
770
|
},
|
|
783
771
|
}),
|
|
784
772
|
};
|
|
@@ -818,7 +806,7 @@ SelectOption.displayName = 'Select_Option';
|
|
|
818
806
|
|
|
819
807
|
// Select.SearchInput - handles search
|
|
820
808
|
const SelectSearchInput = memo(
|
|
821
|
-
({
|
|
809
|
+
({ autoFocus = true, children, ...textInputProps }: SelectSearchInputProps) => {
|
|
822
810
|
const { searchQuery, setSearchQuery } = useSelectContextValue(state => ({
|
|
823
811
|
searchQuery: state.searchQuery,
|
|
824
812
|
setSearchQuery: state.setSearchQuery,
|
|
@@ -828,15 +816,13 @@ const SelectSearchInput = memo(
|
|
|
828
816
|
const handleChangeText = useCallback(
|
|
829
817
|
(text: string) => {
|
|
830
818
|
setSearchQuery(text);
|
|
831
|
-
onQueryChange?.(text);
|
|
832
|
-
textInputProps.onChangeText?.(text);
|
|
833
819
|
},
|
|
834
|
-
[
|
|
820
|
+
[setSearchQuery],
|
|
835
821
|
);
|
|
836
822
|
|
|
837
823
|
const inputProps = {
|
|
838
824
|
...textInputProps,
|
|
839
|
-
value:
|
|
825
|
+
value: searchQuery,
|
|
840
826
|
onChangeText: handleChangeText,
|
|
841
827
|
placeholder: textInputProps.placeholder || 'Search...',
|
|
842
828
|
inputStyle: styles.searchInputInput,
|
|
@@ -866,23 +852,32 @@ const SelectSearchInput = memo(
|
|
|
866
852
|
requestAnimationFrame(focusField);
|
|
867
853
|
}, [autoFocus]);
|
|
868
854
|
|
|
855
|
+
const onPressLeftIcon = useCallback(() => {
|
|
856
|
+
textInputRef.current?.focus();
|
|
857
|
+
}, []);
|
|
858
|
+
|
|
859
|
+
const onClearSearchQuery = useCallback(() => {
|
|
860
|
+
handleChangeText('');
|
|
861
|
+
}, [handleChangeText]);
|
|
862
|
+
|
|
869
863
|
return (
|
|
870
864
|
<TextInput
|
|
871
865
|
ref={textInputRef}
|
|
872
866
|
autoFocus={Platform.OS !== 'web' && autoFocus}
|
|
873
867
|
style={styles.searchInput}
|
|
874
|
-
left={
|
|
875
|
-
<Icon onPress={() => textInputRef.current?.focus()} name="magnify" size={20} />
|
|
876
|
-
}
|
|
877
|
-
right={
|
|
878
|
-
searchQuery ? (
|
|
879
|
-
<IconButton name="close" size={20} onPress={() => setSearchQuery('')} />
|
|
880
|
-
) : undefined
|
|
881
|
-
}
|
|
882
868
|
size="sm"
|
|
883
869
|
variant="outlined"
|
|
884
|
-
{...inputProps}
|
|
885
|
-
|
|
870
|
+
{...inputProps}>
|
|
871
|
+
<TextInput.Left>
|
|
872
|
+
<Icon onPress={onPressLeftIcon} name="magnify" size={20} />
|
|
873
|
+
</TextInput.Left>
|
|
874
|
+
{searchQuery && (
|
|
875
|
+
<TextInput.Right>
|
|
876
|
+
<IconButton name="close" size={20} onPress={onClearSearchQuery} />
|
|
877
|
+
</TextInput.Right>
|
|
878
|
+
)}
|
|
879
|
+
{children}
|
|
880
|
+
</TextInput>
|
|
886
881
|
);
|
|
887
882
|
},
|
|
888
883
|
);
|
|
@@ -900,139 +895,5 @@ const SelectWithSubcomponents = Object.assign(Select, {
|
|
|
900
895
|
SearchInput: SelectSearchInput,
|
|
901
896
|
});
|
|
902
897
|
|
|
903
|
-
const triggerStyles = StyleSheet.create(theme => ({
|
|
904
|
-
trigger: {
|
|
905
|
-
borderRadius: theme.shapes.corner.extraSmall,
|
|
906
|
-
paddingHorizontal: theme.spacings['3'],
|
|
907
|
-
paddingVertical: theme.spacings['2'],
|
|
908
|
-
minHeight: 56,
|
|
909
|
-
flexDirection: 'row',
|
|
910
|
-
alignItems: 'center',
|
|
911
|
-
justifyContent: 'space-between',
|
|
912
|
-
width: '100%',
|
|
913
|
-
variants: {
|
|
914
|
-
state: {
|
|
915
|
-
disabled: {
|
|
916
|
-
opacity: 0.38,
|
|
917
|
-
backgroundColor: theme.colors.surfaceVariant,
|
|
918
|
-
},
|
|
919
|
-
errorDisabled: {
|
|
920
|
-
opacity: 0.38,
|
|
921
|
-
},
|
|
922
|
-
},
|
|
923
|
-
},
|
|
924
|
-
},
|
|
925
|
-
outline: {
|
|
926
|
-
position: 'absolute',
|
|
927
|
-
top: 0,
|
|
928
|
-
left: 0,
|
|
929
|
-
right: 0,
|
|
930
|
-
bottom: 0,
|
|
931
|
-
borderRadius: theme.shapes.corner.extraSmall,
|
|
932
|
-
borderWidth: 1,
|
|
933
|
-
borderColor: theme.colors.outline,
|
|
934
|
-
pointerEvents: 'none',
|
|
935
|
-
variants: {
|
|
936
|
-
state: {
|
|
937
|
-
focused: {
|
|
938
|
-
borderWidth: 2,
|
|
939
|
-
borderColor: theme.colors.primary,
|
|
940
|
-
},
|
|
941
|
-
hovered: {
|
|
942
|
-
borderColor: theme.colors.onSurface,
|
|
943
|
-
},
|
|
944
|
-
hoveredAndFocused: {
|
|
945
|
-
borderWidth: 2,
|
|
946
|
-
borderColor: theme.colors.primary,
|
|
947
|
-
},
|
|
948
|
-
disabled: {
|
|
949
|
-
borderColor: theme.colors.onSurface,
|
|
950
|
-
},
|
|
951
|
-
error: {
|
|
952
|
-
borderColor: theme.colors.error,
|
|
953
|
-
},
|
|
954
|
-
errorFocused: {
|
|
955
|
-
borderWidth: 2,
|
|
956
|
-
borderColor: theme.colors.error,
|
|
957
|
-
},
|
|
958
|
-
errorHovered: {
|
|
959
|
-
borderColor: theme.colors.onErrorContainer,
|
|
960
|
-
},
|
|
961
|
-
errorFocusedAndHovered: {
|
|
962
|
-
borderWidth: 2,
|
|
963
|
-
borderColor: theme.colors.error,
|
|
964
|
-
},
|
|
965
|
-
errorDisabled: {
|
|
966
|
-
borderColor: theme.colors.error,
|
|
967
|
-
},
|
|
968
|
-
},
|
|
969
|
-
},
|
|
970
|
-
},
|
|
971
|
-
triggerIcon: {
|
|
972
|
-
marginLeft: theme.spacings['2'],
|
|
973
|
-
color: theme.colors.onSurfaceVariant,
|
|
974
|
-
},
|
|
975
|
-
}));
|
|
976
|
-
|
|
977
|
-
const styles = StyleSheet.create(theme => ({
|
|
978
|
-
chipContainer: {
|
|
979
|
-
flexDirection: 'row',
|
|
980
|
-
flexWrap: 'wrap',
|
|
981
|
-
gap: 6,
|
|
982
|
-
maxWidth: '90%',
|
|
983
|
-
},
|
|
984
|
-
groupLabel: {
|
|
985
|
-
paddingHorizontal: theme.spacings['4'],
|
|
986
|
-
paddingVertical: theme.spacings['2'],
|
|
987
|
-
fontWeight: '600',
|
|
988
|
-
color: theme.colors.onSurface,
|
|
989
|
-
},
|
|
990
|
-
item: {
|
|
991
|
-
paddingHorizontal: theme.spacings['4'],
|
|
992
|
-
paddingVertical: theme.spacings['3'],
|
|
993
|
-
backgroundColor: 'transparent',
|
|
994
|
-
|
|
995
|
-
_web: {
|
|
996
|
-
cursor: 'pointer',
|
|
997
|
-
outlineStyle: 'none',
|
|
998
|
-
_hover: {
|
|
999
|
-
backgroundColor: theme.colors.stateLayer.hover.primary,
|
|
1000
|
-
},
|
|
1001
|
-
_focus: {
|
|
1002
|
-
backgroundColor: theme.colors.stateLayer.hover.primary,
|
|
1003
|
-
},
|
|
1004
|
-
},
|
|
1005
|
-
},
|
|
1006
|
-
itemSelected: {
|
|
1007
|
-
backgroundColor: theme.colors.stateLayer.hover.primary,
|
|
1008
|
-
},
|
|
1009
|
-
itemDisabled: {
|
|
1010
|
-
opacity: 0.38,
|
|
1011
|
-
_web: {
|
|
1012
|
-
cursor: 'not-allowed',
|
|
1013
|
-
},
|
|
1014
|
-
},
|
|
1015
|
-
itemDisabledText: {
|
|
1016
|
-
color: theme.colors.onSurfaceVariant,
|
|
1017
|
-
},
|
|
1018
|
-
searchInput: {
|
|
1019
|
-
marginHorizontal: theme.spacings['2'],
|
|
1020
|
-
marginVertical: theme.spacings['3'],
|
|
1021
|
-
},
|
|
1022
|
-
searchInputInput: {
|
|
1023
|
-
height: 42,
|
|
1024
|
-
},
|
|
1025
|
-
emptyState: {
|
|
1026
|
-
paddingHorizontal: theme.spacings['4'],
|
|
1027
|
-
paddingVertical: theme.spacings['6'],
|
|
1028
|
-
alignItems: 'center',
|
|
1029
|
-
justifyContent: 'center',
|
|
1030
|
-
},
|
|
1031
|
-
emptyStateText: {
|
|
1032
|
-
color: theme.colors.onSurfaceVariant,
|
|
1033
|
-
fontSize: 14,
|
|
1034
|
-
},
|
|
1035
|
-
}));
|
|
1036
|
-
|
|
1037
898
|
export default SelectWithSubcomponents;
|
|
1038
899
|
export { SelectDropdownProvider, SelectProvider };
|