react-native-molecules 0.5.0-beta.22 → 0.5.0-beta.24
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/Accordion/Accordion.tsx +1 -1
- package/components/Accordion/AccordionItem.tsx +1 -1
- package/components/Button/Button.tsx +3 -1
- package/components/Checkbox/Checkbox.tsx +2 -1
- package/components/DateField/useDateFieldState.ts +2 -2
- package/components/DatePicker/DatePickerProvider.tsx +1 -1
- package/components/DatePicker/utils.ts +2 -0
- package/components/DatePickerInline/DatePickerInline.tsx +1 -1
- package/components/DatePickerInline/DatePickerInlineBase.tsx +1 -1
- package/components/DatePickerInline/Day.tsx +1 -1
- package/components/DatePickerInline/MonthPicker.tsx +24 -40
- package/components/DatePickerInline/Swiper.tsx +1 -1
- package/components/DatePickerInline/SwiperUtils.ts +1 -1
- package/components/DatePickerInline/YearPicker.tsx +44 -79
- package/components/DatePickerInline/dateUtils.tsx +1 -1
- package/components/DatePickerInline/store.tsx +2 -1
- package/components/Divider/index.tsx +2 -3
- package/components/ElementGroup/ElementGroup.tsx +1 -1
- package/components/FilePicker/FilePicker.tsx +1 -1
- package/components/Icon/iconFactory.tsx +2 -1
- package/components/IconButton/IconButton.tsx +39 -13
- package/components/IconButton/index.tsx +1 -0
- package/components/IconButton/types.ts +2 -0
- package/components/List/List.tsx +156 -387
- package/components/List/context.tsx +4 -5
- 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/Portal/Portal.tsx +1 -2
- package/components/RadioButton/RadioButtonGroup.tsx +1 -2
- package/components/Rating/Rating.tsx +1 -1
- package/components/Select/Select.tsx +304 -71
- package/components/Select/context.tsx +30 -3
- package/components/Select/index.ts +20 -2
- package/components/Select/types.ts +43 -25
- package/components/Select/utils.ts +18 -4
- package/components/Switch/Switch.ios.tsx +1 -1
- package/components/Switch/Switch.tsx +2 -1
- package/components/Tabs/Tabs.tsx +2 -2
- package/components/TextInput/TextInput.tsx +4 -3
- package/components/TimePicker/AnalogClock.tsx +1 -1
- package/components/TimePicker/TimeInputs.tsx +1 -1
- package/components/TimePicker/TimePicker.tsx +1 -1
- package/components/TimePicker/TimePickerModal.tsx +1 -1
- package/components/Tooltip/Tooltip.tsx +1 -1
- package/components/TouchableRipple/TouchableRipple.tsx +76 -152
- package/hocs/index.tsx +1 -1
- package/hocs/withKeyboardAccessibility.tsx +2 -3
- package/hooks/index.tsx +2 -6
- package/hooks/useContrastColor.ts +1 -2
- package/hooks/useFilePicker.tsx +1 -1
- package/hooks/useHandleNumberFormat.tsx +2 -2
- package/hooks/useMediaQuery.tsx +1 -2
- package/package.json +5 -28
- package/shortcuts-manager/ShortcutsManager/ShortcutsManager.tsx +1 -1
- package/shortcuts-manager/ShortcutsManager/utils.tsx +1 -1
- package/shortcuts-manager/useSetScopes/useSetScopes.tsx +1 -1
- package/shortcuts-manager/useShortcut/useShortcut.tsx +1 -1
- package/utils/extractTextStyles.ts +1 -2
- package/utils/formatNumberWithMask/formatNumberWithMask.ts +2 -1
- package/utils/index.ts +0 -3
- package/utils/normalizeToNumberString/normalizeToNumberString.ts +1 -1
- package/context-bridge/index.tsx +0 -87
- package/fast-context/index.tsx +0 -190
- package/hocs/typedMemo.tsx +0 -5
- package/hooks/useControlledValue.tsx +0 -84
- package/hooks/useLatest.tsx +0 -9
- package/hooks/useMergedRefs.ts +0 -14
- package/hooks/usePrevious.ts +0 -13
- package/hooks/useToggle.tsx +0 -24
- package/hooks/useWhatHasUpdated.tsx +0 -48
- package/utils/color.ts +0 -22
- package/utils/compare/index.ts +0 -54
- package/utils/lodash.ts +0 -121
- package/utils/repository.ts +0 -53
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useControlledValue, useToggle } from '@react-native-molecules/utils/hooks';
|
|
2
|
+
import {
|
|
3
|
+
Fragment,
|
|
4
|
+
memo,
|
|
5
|
+
type ReactNode,
|
|
6
|
+
useCallback,
|
|
7
|
+
useEffect,
|
|
8
|
+
useMemo,
|
|
9
|
+
useRef,
|
|
10
|
+
useState,
|
|
11
|
+
} from 'react';
|
|
2
12
|
import {
|
|
3
13
|
type AccessibilityRole,
|
|
4
14
|
type GestureResponderEvent,
|
|
@@ -9,38 +19,112 @@ import {
|
|
|
9
19
|
} from 'react-native';
|
|
10
20
|
|
|
11
21
|
import { typedMemo } from '../../hocs';
|
|
12
|
-
import { useActionState } from '../../hooks';
|
|
13
|
-
import { useToggle } from '../../hooks';
|
|
14
22
|
import { resolveStateVariant } from '../../utils';
|
|
23
|
+
import { extractSubcomponents } from '../../utils/extractSubcomponents';
|
|
15
24
|
import { Chip } from '../Chip';
|
|
16
25
|
import { Icon } from '../Icon';
|
|
26
|
+
import { IconButton } from '../IconButton';
|
|
17
27
|
import { List } from '../List';
|
|
18
28
|
import { Popover } from '../Popover';
|
|
19
29
|
import { Text } from '../Text';
|
|
30
|
+
import { TextInput, type TextInputHandles, type TextInputProps } from '../TextInput';
|
|
20
31
|
import {
|
|
21
32
|
SelectDropdownContextProvider,
|
|
33
|
+
SelectSearchContextProvider,
|
|
22
34
|
useSelectContextValue,
|
|
23
35
|
useSelectDropdownContextValue,
|
|
36
|
+
useSelectDropdownStoreRef,
|
|
37
|
+
useSelectSearchContextValue,
|
|
24
38
|
} from './context';
|
|
25
39
|
import type {
|
|
26
40
|
DefaultItemT,
|
|
41
|
+
SelectContentProps,
|
|
27
42
|
SelectDropdownProps,
|
|
28
43
|
SelectOptionProps,
|
|
29
44
|
SelectProps,
|
|
45
|
+
SelectSearchContextValue,
|
|
46
|
+
SelectSearchInputProps,
|
|
47
|
+
SelectSearchKey,
|
|
48
|
+
SelectTriggerOutlineProps,
|
|
30
49
|
SelectTriggerProps,
|
|
31
50
|
SelectValueProps,
|
|
32
51
|
} from './types';
|
|
33
|
-
import {
|
|
52
|
+
import {
|
|
53
|
+
collectWebSelectKeyboardOptionElements,
|
|
54
|
+
selectOutlineStyles,
|
|
55
|
+
styles,
|
|
56
|
+
triggerStyles,
|
|
57
|
+
} from './utils';
|
|
34
58
|
|
|
35
59
|
const emptyArr: unknown[] = [];
|
|
36
60
|
|
|
37
|
-
const
|
|
61
|
+
export const getSelectTriggerState = ({
|
|
62
|
+
isOpen,
|
|
63
|
+
hovered,
|
|
64
|
+
disabled,
|
|
65
|
+
error,
|
|
66
|
+
}: {
|
|
67
|
+
isOpen: boolean;
|
|
68
|
+
hovered: boolean;
|
|
69
|
+
disabled: boolean;
|
|
70
|
+
error: boolean;
|
|
71
|
+
}) =>
|
|
72
|
+
resolveStateVariant({
|
|
73
|
+
focused: isOpen,
|
|
74
|
+
hovered,
|
|
75
|
+
disabled,
|
|
76
|
+
error,
|
|
77
|
+
hoveredAndFocused: hovered && isOpen,
|
|
78
|
+
errorFocused: error && isOpen,
|
|
79
|
+
errorHovered: error && hovered,
|
|
80
|
+
errorFocusedAndHovered: error && isOpen && hovered,
|
|
81
|
+
errorDisabled: error && disabled,
|
|
82
|
+
}) as any;
|
|
83
|
+
|
|
84
|
+
export const getDisplayLabel = (item: DefaultItemT, labelKey?: string) => {
|
|
85
|
+
const itemLabelKey = typeof item.labelKey === 'string' ? item.labelKey : undefined;
|
|
86
|
+
const key = labelKey ?? itemLabelKey ?? 'label';
|
|
87
|
+
const value = item[key];
|
|
88
|
+
return value == null ? String(item.id) : String(value);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export const getNested = (item: unknown, path: string): unknown => {
|
|
92
|
+
if (item == null || typeof item !== 'object') return undefined;
|
|
93
|
+
if (!path.includes('.')) return (item as Record<string, unknown>)[path];
|
|
94
|
+
let val: unknown = item;
|
|
95
|
+
for (const part of path.split('.')) {
|
|
96
|
+
if (val == null || typeof val !== 'object') return undefined;
|
|
97
|
+
val = (val as Record<string, unknown>)[part];
|
|
98
|
+
}
|
|
99
|
+
return val;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export const matchesByKey = (item: unknown, key: string, lowerQuery: string): boolean =>
|
|
103
|
+
String(getNested(item, key) ?? '')
|
|
104
|
+
.toLowerCase()
|
|
105
|
+
.includes(lowerQuery);
|
|
106
|
+
|
|
107
|
+
export const applySearch = <T extends object>(
|
|
108
|
+
items: T[],
|
|
109
|
+
searchKey: SelectSearchKey<T> | undefined,
|
|
110
|
+
query: string,
|
|
111
|
+
): T[] => {
|
|
112
|
+
if (!query) return items;
|
|
113
|
+
if (typeof searchKey === 'function') {
|
|
114
|
+
return items.filter(item => searchKey(item, query));
|
|
115
|
+
}
|
|
116
|
+
const keys = Array.isArray(searchKey) ? searchKey : [searchKey || 'label'];
|
|
117
|
+
const lowerQuery = query.toLowerCase();
|
|
118
|
+
return items.filter(item => keys.some(key => matchesByKey(item, key, lowerQuery)));
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export const SelectDropdownProvider = memo(
|
|
38
122
|
({
|
|
39
123
|
children,
|
|
40
124
|
isOpen: isOpenProp,
|
|
41
125
|
onClose: onCloseProp,
|
|
42
126
|
}: {
|
|
43
|
-
children:
|
|
127
|
+
children: ReactNode;
|
|
44
128
|
isOpen?: boolean;
|
|
45
129
|
onClose?: () => void;
|
|
46
130
|
}) => {
|
|
@@ -86,49 +170,124 @@ const SelectDropdownProvider = memo(
|
|
|
86
170
|
},
|
|
87
171
|
);
|
|
88
172
|
|
|
89
|
-
const
|
|
173
|
+
export const SelectRoot = typedMemo(
|
|
90
174
|
<Option extends DefaultItemT = DefaultItemT>({
|
|
91
175
|
children,
|
|
92
176
|
options = emptyArr as Option[],
|
|
177
|
+
searchKey,
|
|
178
|
+
searchQuery: searchQueryProp,
|
|
179
|
+
defaultSearchQuery,
|
|
180
|
+
onSearchChange,
|
|
181
|
+
searchMode = 'client',
|
|
182
|
+
getItemId,
|
|
93
183
|
...listProps
|
|
94
184
|
}: SelectProps<Option>) => {
|
|
185
|
+
const [searchQuery, setSearchQuery] = useControlledValue<string>({
|
|
186
|
+
value: searchQueryProp,
|
|
187
|
+
defaultValue: defaultSearchQuery ?? '',
|
|
188
|
+
onChange: onSearchChange,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const getOptionId = useMemo(
|
|
192
|
+
() => (getItemId ?? ((item: Option) => item.id)) as (item: Option) => string | number,
|
|
193
|
+
[getItemId],
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
const filteredOptions = useMemo(() => {
|
|
197
|
+
if (searchMode === 'external') return options;
|
|
198
|
+
return applySearch(options, searchKey, searchQuery);
|
|
199
|
+
}, [options, searchKey, searchMode, searchQuery]);
|
|
200
|
+
|
|
201
|
+
const optionById = useMemo(() => {
|
|
202
|
+
const map = new Map<string | number, Option>();
|
|
203
|
+
for (const option of options) {
|
|
204
|
+
map.set(getOptionId(option), option);
|
|
205
|
+
}
|
|
206
|
+
return map;
|
|
207
|
+
}, [getOptionId, options]);
|
|
208
|
+
|
|
209
|
+
const searchContextValue = useMemo(
|
|
210
|
+
() =>
|
|
211
|
+
({
|
|
212
|
+
searchQuery,
|
|
213
|
+
setSearchQuery,
|
|
214
|
+
allOptions: options,
|
|
215
|
+
options: filteredOptions,
|
|
216
|
+
optionById,
|
|
217
|
+
getOptionId,
|
|
218
|
+
} as unknown as SelectSearchContextValue<DefaultItemT>),
|
|
219
|
+
[filteredOptions, getOptionId, optionById, options, searchQuery, setSearchQuery],
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
return (
|
|
223
|
+
<SelectSearchContextProvider value={searchContextValue}>
|
|
224
|
+
<List {...listProps}>
|
|
225
|
+
<SelectDropdownProvider>{children}</SelectDropdownProvider>
|
|
226
|
+
</List>
|
|
227
|
+
</SelectSearchContextProvider>
|
|
228
|
+
);
|
|
229
|
+
},
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
export const SelectContent = typedMemo(
|
|
233
|
+
<Option extends DefaultItemT = DefaultItemT>({
|
|
234
|
+
children,
|
|
235
|
+
...rest
|
|
236
|
+
}: SelectContentProps<Option>) => {
|
|
237
|
+
const { options, getOptionId } = useSelectSearchContextValue(state => ({
|
|
238
|
+
options: state.options as Option[],
|
|
239
|
+
getOptionId: state.getOptionId as (item: Option) => string | number,
|
|
240
|
+
}));
|
|
241
|
+
const isSelectedId = useSelectContextValue(state => state.isSelectedId);
|
|
242
|
+
|
|
243
|
+
if (typeof children !== 'function') {
|
|
244
|
+
return <List.Content {...rest}>{children}</List.Content>;
|
|
245
|
+
}
|
|
246
|
+
|
|
95
247
|
return (
|
|
96
|
-
<List {...
|
|
97
|
-
|
|
98
|
-
|
|
248
|
+
<List.Content {...rest}>
|
|
249
|
+
{options.map(item => (
|
|
250
|
+
<Fragment key={String(getOptionId(item))}>
|
|
251
|
+
{children(item, isSelectedId(getOptionId(item)))}
|
|
252
|
+
</Fragment>
|
|
253
|
+
))}
|
|
254
|
+
</List.Content>
|
|
99
255
|
);
|
|
100
256
|
},
|
|
101
257
|
);
|
|
102
258
|
|
|
103
|
-
const SelectTrigger = ({ children, style, ...rest }: SelectTriggerProps) => {
|
|
104
|
-
const { onOpen,
|
|
259
|
+
export const SelectTrigger = memo(({ children, style, ...rest }: SelectTriggerProps) => {
|
|
260
|
+
const { isOpen, onOpen, onClose, triggerRef, setTriggerLayout } = useSelectDropdownContextValue(
|
|
105
261
|
state => ({
|
|
106
|
-
onOpen: state.onOpen,
|
|
107
262
|
isOpen: state.isOpen,
|
|
263
|
+
onOpen: state.onOpen,
|
|
264
|
+
onClose: state.onClose,
|
|
108
265
|
triggerRef: state.triggerRef,
|
|
109
266
|
setTriggerLayout: state.setTriggerLayout,
|
|
110
267
|
}),
|
|
111
268
|
);
|
|
269
|
+
const setSelectDropdownContext = useSelectDropdownStoreRef().set;
|
|
112
270
|
|
|
113
271
|
const { disabled, error } = useSelectContextValue(state => ({
|
|
114
272
|
disabled: state.disabled,
|
|
115
273
|
error: state.error,
|
|
116
274
|
}));
|
|
117
275
|
|
|
118
|
-
const
|
|
276
|
+
const [hovered, setHovered] = useState(false);
|
|
277
|
+
|
|
278
|
+
const { Select_TriggerOutline, rest: restChildren } = extractSubcomponents({
|
|
279
|
+
children,
|
|
280
|
+
allowedChildren: [{ name: 'Select_TriggerOutline', allowMultiple: false }] as const,
|
|
281
|
+
includeRest: true,
|
|
282
|
+
});
|
|
119
283
|
|
|
120
284
|
triggerStyles.useVariants({
|
|
121
|
-
state:
|
|
122
|
-
|
|
285
|
+
state: getSelectTriggerState({
|
|
286
|
+
isOpen,
|
|
123
287
|
hovered,
|
|
124
288
|
disabled: !!disabled,
|
|
125
289
|
error: !!error,
|
|
126
|
-
|
|
127
|
-
errorFocused: !!error && isOpen,
|
|
128
|
-
errorHovered: !!error && hovered,
|
|
129
|
-
errorFocusedAndHovered: !!error && isOpen && hovered,
|
|
130
|
-
errorDisabled: !!error && !!disabled,
|
|
131
|
-
}) as any,
|
|
290
|
+
}),
|
|
132
291
|
});
|
|
133
292
|
|
|
134
293
|
const handleLayout = useCallback(
|
|
@@ -140,72 +299,114 @@ const SelectTrigger = ({ children, style, ...rest }: SelectTriggerProps) => {
|
|
|
140
299
|
);
|
|
141
300
|
|
|
142
301
|
const handlePress = useCallback(() => {
|
|
143
|
-
if (
|
|
302
|
+
if (disabled) return;
|
|
303
|
+
if (!isOpen) {
|
|
144
304
|
onOpen();
|
|
305
|
+
} else {
|
|
306
|
+
onClose();
|
|
145
307
|
}
|
|
146
|
-
}, [isOpen, onOpen, disabled]);
|
|
308
|
+
}, [isOpen, onOpen, onClose, disabled]);
|
|
309
|
+
|
|
310
|
+
const handleHoverIn = useCallback(() => {
|
|
311
|
+
setHovered(true);
|
|
312
|
+
setSelectDropdownContext(() => ({ triggerHovered: true }));
|
|
313
|
+
}, [setSelectDropdownContext]);
|
|
314
|
+
|
|
315
|
+
const handleHoverOut = useCallback(() => {
|
|
316
|
+
setHovered(false);
|
|
317
|
+
setSelectDropdownContext(() => ({ triggerHovered: false }));
|
|
318
|
+
}, [setSelectDropdownContext]);
|
|
319
|
+
|
|
320
|
+
const outlineElement =
|
|
321
|
+
Select_TriggerOutline.length > 0 ? Select_TriggerOutline : <SelectTriggerOutline />;
|
|
147
322
|
|
|
148
323
|
return (
|
|
149
324
|
<Pressable
|
|
150
325
|
ref={triggerRef}
|
|
151
326
|
onPress={handlePress}
|
|
152
327
|
onLayout={handleLayout}
|
|
328
|
+
onHoverIn={handleHoverIn}
|
|
329
|
+
onHoverOut={handleHoverOut}
|
|
153
330
|
style={[triggerStyles.trigger, style]}
|
|
154
331
|
accessibilityRole="combobox"
|
|
155
332
|
accessibilityState={{ expanded: isOpen, disabled: !!disabled }}
|
|
156
333
|
disabled={disabled}
|
|
157
334
|
{...rest}>
|
|
158
|
-
{
|
|
335
|
+
{restChildren}
|
|
159
336
|
<Icon
|
|
160
337
|
name={isOpen ? 'chevron-up' : 'chevron-down'}
|
|
161
338
|
size={20}
|
|
162
339
|
style={triggerStyles.triggerIcon}
|
|
163
340
|
/>
|
|
164
|
-
|
|
341
|
+
{outlineElement}
|
|
165
342
|
</Pressable>
|
|
166
343
|
);
|
|
167
|
-
};
|
|
344
|
+
});
|
|
168
345
|
|
|
169
346
|
SelectTrigger.displayName = 'Select_Trigger';
|
|
170
347
|
|
|
171
|
-
const
|
|
348
|
+
export const SelectTriggerOutline = memo(({ style }: SelectTriggerOutlineProps) => {
|
|
349
|
+
const { isOpen, triggerHovered } = useSelectDropdownContextValue(state => ({
|
|
350
|
+
isOpen: state.isOpen,
|
|
351
|
+
triggerHovered: state.triggerHovered,
|
|
352
|
+
}));
|
|
353
|
+
const { disabled, error } = useSelectContextValue(state => ({
|
|
354
|
+
disabled: state.disabled,
|
|
355
|
+
error: state.error,
|
|
356
|
+
}));
|
|
357
|
+
selectOutlineStyles.useVariants({
|
|
358
|
+
state: getSelectTriggerState({
|
|
359
|
+
isOpen,
|
|
360
|
+
hovered: !!triggerHovered,
|
|
361
|
+
disabled: !!disabled,
|
|
362
|
+
error: !!error,
|
|
363
|
+
}),
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
return <View pointerEvents="none" style={[selectOutlineStyles.outline, style]} />;
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
SelectTriggerOutline.displayName = 'Select_TriggerOutline';
|
|
370
|
+
|
|
371
|
+
export const SelectValue = memo(
|
|
172
372
|
({ placeholder, labelKey, renderValue, style, ...rest }: SelectValueProps) => {
|
|
173
|
-
const { value, multiple, onRemove
|
|
373
|
+
const { value, multiple, onRemove } = useSelectContextValue(state => ({
|
|
174
374
|
value: state.value,
|
|
175
375
|
multiple: state.multiple,
|
|
176
376
|
onRemove: state.onRemove,
|
|
177
|
-
|
|
377
|
+
}));
|
|
378
|
+
const { optionById } = useSelectSearchContextValue(state => ({
|
|
379
|
+
optionById: state.optionById,
|
|
178
380
|
}));
|
|
179
381
|
|
|
180
382
|
const resolvedValue = useMemo(() => {
|
|
181
|
-
const resolve = (
|
|
182
|
-
if (
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
return found || item;
|
|
383
|
+
const resolve = (id: unknown) => {
|
|
384
|
+
if (id === null || id === undefined) return null;
|
|
385
|
+
const found = optionById.get(id as string | number);
|
|
386
|
+
return found || { id: id as string | number };
|
|
186
387
|
};
|
|
187
388
|
|
|
188
389
|
if (multiple) {
|
|
189
390
|
return (Array.isArray(value) ? value : []).map(resolve).filter(Boolean);
|
|
190
391
|
}
|
|
191
392
|
return resolve(value);
|
|
192
|
-
}, [value, multiple
|
|
393
|
+
}, [optionById, value, multiple]);
|
|
193
394
|
|
|
194
395
|
const displayValue = useMemo(() => {
|
|
195
396
|
if (!resolvedValue) return placeholder || '';
|
|
196
397
|
if (multiple && (resolvedValue as any[]).length === 0) return placeholder || '';
|
|
197
398
|
|
|
198
399
|
if (renderValue) {
|
|
199
|
-
return renderValue(resolvedValue as
|
|
400
|
+
return renderValue(resolvedValue as DefaultItemT | DefaultItemT[] | null);
|
|
200
401
|
}
|
|
201
402
|
|
|
202
403
|
if (multiple) {
|
|
203
404
|
const values = resolvedValue as DefaultItemT[];
|
|
204
405
|
// For multi-select, show chips
|
|
205
|
-
return values.map(item => item
|
|
406
|
+
return values.map(item => getDisplayLabel(item, labelKey)).join(', ');
|
|
206
407
|
} else {
|
|
207
408
|
const singleValue = resolvedValue as DefaultItemT;
|
|
208
|
-
return
|
|
409
|
+
return getDisplayLabel(singleValue, labelKey);
|
|
209
410
|
}
|
|
210
411
|
}, [resolvedValue, multiple, labelKey, placeholder, renderValue]);
|
|
211
412
|
|
|
@@ -225,7 +426,7 @@ const SelectValue = memo(
|
|
|
225
426
|
}
|
|
226
427
|
|
|
227
428
|
return (
|
|
228
|
-
<Text style={style} {...rest}>
|
|
429
|
+
<Text style={[styles.valueText, style]} selectable={false} {...rest}>
|
|
229
430
|
{displayValue}
|
|
230
431
|
</Text>
|
|
231
432
|
);
|
|
@@ -246,7 +447,7 @@ const SelectValueItem = typedMemo(
|
|
|
246
447
|
|
|
247
448
|
return (
|
|
248
449
|
<Chip.Input
|
|
249
|
-
label={
|
|
450
|
+
label={getDisplayLabel(item)}
|
|
250
451
|
size="sm"
|
|
251
452
|
selected
|
|
252
453
|
left={<></>}
|
|
@@ -259,7 +460,7 @@ const SelectValueItem = typedMemo(
|
|
|
259
460
|
SelectValue.displayName = 'Select_Value';
|
|
260
461
|
|
|
261
462
|
// Select.Dropdown - popover with keyboard navigation
|
|
262
|
-
const SelectDropdown = memo(
|
|
463
|
+
export const SelectDropdown = memo(
|
|
263
464
|
({
|
|
264
465
|
children,
|
|
265
466
|
WrapperComponent,
|
|
@@ -417,10 +618,8 @@ const KeyboardNavigationWrapper = memo(({ children }: { children: React.ReactNod
|
|
|
417
618
|
|
|
418
619
|
SelectDropdown.displayName = 'Select_Dropdown';
|
|
419
620
|
|
|
420
|
-
const SelectGroup = List.Group;
|
|
421
|
-
|
|
422
621
|
// Select.Item - select item that uses context
|
|
423
|
-
const SelectOption = memo(
|
|
622
|
+
export const SelectOption = memo(
|
|
424
623
|
<Option extends DefaultItemT = DefaultItemT>({
|
|
425
624
|
value,
|
|
426
625
|
children,
|
|
@@ -434,33 +633,29 @@ const SelectOption = memo(
|
|
|
434
633
|
onAdd,
|
|
435
634
|
onRemove,
|
|
436
635
|
disabled: selectDisabled,
|
|
437
|
-
|
|
636
|
+
isSelectedId,
|
|
438
637
|
} = useSelectContextValue(state => ({
|
|
439
638
|
multiple: state.multiple,
|
|
440
639
|
onAdd: state.onAdd,
|
|
441
640
|
onRemove: state.onRemove,
|
|
442
641
|
disabled: state.disabled,
|
|
443
|
-
|
|
642
|
+
isSelectedId: state.isSelectedId,
|
|
643
|
+
}));
|
|
644
|
+
const { allOptions, getOptionId } = useSelectSearchContextValue(state => ({
|
|
645
|
+
allOptions: state.allOptions,
|
|
646
|
+
getOptionId: state.getOptionId,
|
|
444
647
|
}));
|
|
445
648
|
|
|
446
649
|
const option = useMemo(() => {
|
|
447
|
-
const found =
|
|
650
|
+
const found = allOptions.find(i => getOptionId(i as Option) === value);
|
|
448
651
|
if (found) return found as Option;
|
|
449
652
|
return {
|
|
450
653
|
id: value,
|
|
451
654
|
...(optionDisabledProp ? { selectable: false } : {}),
|
|
452
655
|
} as Option;
|
|
453
|
-
}, [
|
|
656
|
+
}, [allOptions, getOptionId, optionDisabledProp, value]);
|
|
454
657
|
|
|
455
|
-
const isSelected =
|
|
456
|
-
if (multiple) {
|
|
457
|
-
const values = state.value as any[];
|
|
458
|
-
return values?.some(v => (v?.id ?? v) === option.id) || false;
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
const singleValue = state.value as any;
|
|
462
|
-
return (singleValue?.id ?? singleValue) === option.id || false;
|
|
463
|
-
});
|
|
658
|
+
const isSelected = isSelectedId(value);
|
|
464
659
|
|
|
465
660
|
const { onClose } = useSelectDropdownContextValue(state => ({
|
|
466
661
|
onClose: state.onClose,
|
|
@@ -495,7 +690,7 @@ const SelectOption = memo(
|
|
|
495
690
|
style={style}
|
|
496
691
|
value={value}
|
|
497
692
|
shouldToggleOnPress={false}
|
|
498
|
-
onPress={
|
|
693
|
+
onPress={handlePress}
|
|
499
694
|
disabled={isOptionDisabled}
|
|
500
695
|
accessibilityState={{ selected: isSelected, disabled: isOptionDisabled }}
|
|
501
696
|
{...(Platform.OS === 'web'
|
|
@@ -523,19 +718,57 @@ const SelectOption = memo(
|
|
|
523
718
|
|
|
524
719
|
SelectOption.displayName = 'Select_Option';
|
|
525
720
|
|
|
526
|
-
const SelectSearchInput =
|
|
721
|
+
export const SelectSearchInput = memo(({ children, ...textInputProps }: SelectSearchInputProps) => {
|
|
722
|
+
const { searchQuery, setSearchQuery } = useSelectSearchContextValue(state => ({
|
|
723
|
+
searchQuery: state.searchQuery,
|
|
724
|
+
setSearchQuery: state.setSearchQuery,
|
|
725
|
+
}));
|
|
527
726
|
|
|
528
|
-
|
|
727
|
+
const textInputRef = useRef<TextInputHandles>(null);
|
|
728
|
+
|
|
729
|
+
const handleChangeText = useCallback(
|
|
730
|
+
(text: string) => {
|
|
731
|
+
setSearchQuery(text);
|
|
732
|
+
},
|
|
733
|
+
[setSearchQuery],
|
|
734
|
+
);
|
|
735
|
+
|
|
736
|
+
const inputProps = {
|
|
737
|
+
...textInputProps,
|
|
738
|
+
value: searchQuery,
|
|
739
|
+
onChangeText: handleChangeText,
|
|
740
|
+
placeholder: textInputProps.placeholder || 'Search...',
|
|
741
|
+
inputStyle: styles.searchInputInput,
|
|
742
|
+
} as TextInputProps;
|
|
743
|
+
|
|
744
|
+
const onPressLeftIcon = useCallback(() => {
|
|
745
|
+
textInputRef.current?.focus();
|
|
746
|
+
}, []);
|
|
529
747
|
|
|
530
|
-
const
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
748
|
+
const onClearSearchQuery = useCallback(() => {
|
|
749
|
+
handleChangeText('');
|
|
750
|
+
}, [handleChangeText]);
|
|
751
|
+
|
|
752
|
+
return (
|
|
753
|
+
<TextInput
|
|
754
|
+
ref={textInputRef}
|
|
755
|
+
style={styles.searchInput}
|
|
756
|
+
size="sm"
|
|
757
|
+
variant="outlined"
|
|
758
|
+
{...inputProps}>
|
|
759
|
+
<TextInput.Left>
|
|
760
|
+
<Icon onPress={onPressLeftIcon} name="magnify" size={20} />
|
|
761
|
+
</TextInput.Left>
|
|
762
|
+
{searchQuery ? (
|
|
763
|
+
<TextInput.Right>
|
|
764
|
+
<IconButton name="close" size={20} onPress={onClearSearchQuery} />
|
|
765
|
+
</TextInput.Right>
|
|
766
|
+
) : null}
|
|
767
|
+
{children}
|
|
768
|
+
</TextInput>
|
|
769
|
+
);
|
|
538
770
|
});
|
|
539
771
|
|
|
540
|
-
|
|
541
|
-
|
|
772
|
+
SelectSearchInput.displayName = 'Select_SearchInput';
|
|
773
|
+
|
|
774
|
+
export default SelectRoot;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { createFastContext } from '@react-native-molecules/utils/fast-context';
|
|
1
2
|
import type { View } from 'react-native';
|
|
2
3
|
|
|
3
|
-
import { createFastContext } from '../../fast-context';
|
|
4
4
|
import {
|
|
5
5
|
ListContext,
|
|
6
6
|
ListContextProvider,
|
|
@@ -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,
|
|
@@ -24,6 +24,7 @@ export type SelectDropdownContextType = SelectDropdownContextValue & {
|
|
|
24
24
|
triggerRef: React.RefObject<View> | null;
|
|
25
25
|
triggerLayout: { width: number; height: number } | null;
|
|
26
26
|
setTriggerLayout: (layout: { width: number; height: number }) => void;
|
|
27
|
+
triggerHovered?: boolean;
|
|
27
28
|
};
|
|
28
29
|
|
|
29
30
|
const selectDropdownContextDefaultValue: SelectDropdownContextType = {
|
|
@@ -33,6 +34,7 @@ const selectDropdownContextDefaultValue: SelectDropdownContextType = {
|
|
|
33
34
|
triggerRef: null,
|
|
34
35
|
triggerLayout: null,
|
|
35
36
|
setTriggerLayout: () => {},
|
|
37
|
+
triggerHovered: false,
|
|
36
38
|
};
|
|
37
39
|
|
|
38
40
|
const {
|
|
@@ -43,12 +45,37 @@ const {
|
|
|
43
45
|
Context: SelectDropdownContext,
|
|
44
46
|
} = createFastContext<SelectDropdownContextType>(selectDropdownContextDefaultValue, true);
|
|
45
47
|
|
|
48
|
+
const selectSearchContextDefaultValue: SelectSearchContextValue<DefaultItemT> = {
|
|
49
|
+
searchQuery: '',
|
|
50
|
+
setSearchQuery: () => {},
|
|
51
|
+
allOptions: [],
|
|
52
|
+
options: [],
|
|
53
|
+
optionById: new Map(),
|
|
54
|
+
getOptionId: item => item.id,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const {
|
|
58
|
+
useStoreRef: useSelectSearchStoreRef,
|
|
59
|
+
Provider: SelectSearchContextProvider,
|
|
60
|
+
useContext: useSelectSearchContext,
|
|
61
|
+
useContextValue: useSelectSearchContextValue,
|
|
62
|
+
Context: SelectSearchContext,
|
|
63
|
+
} = createFastContext<SelectSearchContextValue<DefaultItemT>>(
|
|
64
|
+
selectSearchContextDefaultValue,
|
|
65
|
+
true,
|
|
66
|
+
);
|
|
67
|
+
|
|
46
68
|
export {
|
|
47
69
|
SelectDropdownContext,
|
|
48
70
|
SelectDropdownContextProvider,
|
|
71
|
+
SelectSearchContext,
|
|
72
|
+
SelectSearchContextProvider,
|
|
49
73
|
useSelectDropdownContext,
|
|
50
74
|
useSelectDropdownContextValue,
|
|
51
75
|
useSelectDropdownStoreRef,
|
|
76
|
+
useSelectSearchContext,
|
|
77
|
+
useSelectSearchContextValue,
|
|
78
|
+
useSelectSearchStoreRef,
|
|
52
79
|
};
|
|
53
80
|
|
|
54
|
-
registerPortalContext([SelectDropdownContext]);
|
|
81
|
+
registerPortalContext([SelectDropdownContext, SelectSearchContext]);
|
|
@@ -1,7 +1,25 @@
|
|
|
1
1
|
import { getRegisteredComponentWithFallback } from '../../core';
|
|
2
|
-
import
|
|
2
|
+
import SelectRoot, {
|
|
3
|
+
SelectContent,
|
|
4
|
+
SelectDropdown,
|
|
5
|
+
SelectOption,
|
|
6
|
+
SelectSearchInput,
|
|
7
|
+
SelectTrigger,
|
|
8
|
+
SelectTriggerOutline,
|
|
9
|
+
SelectValue,
|
|
10
|
+
} from './Select';
|
|
3
11
|
|
|
4
|
-
|
|
12
|
+
const SelectWithSubcomponents = Object.assign(SelectRoot, {
|
|
13
|
+
Trigger: SelectTrigger,
|
|
14
|
+
TriggerOutline: SelectTriggerOutline,
|
|
15
|
+
Value: SelectValue,
|
|
16
|
+
Dropdown: SelectDropdown,
|
|
17
|
+
Content: SelectContent,
|
|
18
|
+
Option: SelectOption,
|
|
19
|
+
SearchInput: SelectSearchInput,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export const Select = getRegisteredComponentWithFallback('Select', SelectWithSubcomponents);
|
|
5
23
|
|
|
6
24
|
export * from './context';
|
|
7
25
|
export type * from './types';
|