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
package/components/List/List.tsx
CHANGED
|
@@ -1,68 +1,34 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
memo,
|
|
4
|
-
type RefObject,
|
|
5
|
-
useCallback,
|
|
6
|
-
useMemo,
|
|
7
|
-
useRef,
|
|
8
|
-
useState,
|
|
9
|
-
} from 'react';
|
|
10
|
-
import { ScrollView, type StyleProp, View, type ViewStyle } from 'react-native';
|
|
1
|
+
import { memo, useCallback, useMemo } from 'react';
|
|
2
|
+
import { ScrollView, type StyleProp, type ViewStyle } from 'react-native';
|
|
11
3
|
|
|
12
4
|
import { typedMemo } from '../../hocs';
|
|
13
5
|
import { useActionState, useControlledValue, useLatest } from '../../hooks';
|
|
14
|
-
import type { WithElements } from '../../types';
|
|
15
6
|
import { resolveStateVariant } from '../../utils';
|
|
16
|
-
import { Divider } from '../Divider';
|
|
17
|
-
import { Icon } from '../Icon';
|
|
18
|
-
import { IconButton } from '../IconButton';
|
|
19
7
|
import { StateLayer } from '../StateLayer';
|
|
20
|
-
import {
|
|
21
|
-
import { TextInput, type TextInputHandles, type TextInputProps } from '../TextInput';
|
|
22
|
-
import { TouchableRipple, type TouchableRippleProps } from '../TouchableRipple';
|
|
8
|
+
import { TouchableRipple } from '../TouchableRipple';
|
|
23
9
|
import { ListContextProvider, useListContextValue } from './context';
|
|
24
10
|
import type {
|
|
25
11
|
DefaultListItemT,
|
|
26
12
|
ListContentProps,
|
|
27
13
|
ListContextValue,
|
|
28
|
-
|
|
29
|
-
|
|
14
|
+
ListItemId,
|
|
15
|
+
ListItemProps,
|
|
30
16
|
ListProps,
|
|
31
|
-
ListSearchInputProps,
|
|
32
17
|
} from './types';
|
|
33
|
-
import { listItemStyles
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
type InternalListItemProps = Omit<TouchableRippleProps, 'children'> &
|
|
38
|
-
WithElements<React.ReactNode | ((renderArgs: { hovered: boolean }) => React.ReactNode)> & {
|
|
39
|
-
ref?: RefObject<any>;
|
|
40
|
-
hovered?: boolean;
|
|
41
|
-
children: React.ReactNode;
|
|
42
|
-
style?: StyleProp<ViewStyle>;
|
|
43
|
-
divider?: boolean;
|
|
44
|
-
variant?: 'default' | 'menuItem';
|
|
45
|
-
selected?: boolean;
|
|
46
|
-
hoverable?: boolean;
|
|
47
|
-
contentStyle?: StyleProp<ViewStyle>;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
const _InternalListItem = ({
|
|
18
|
+
import { listItemStyles } from './utils';
|
|
19
|
+
|
|
20
|
+
const _ListItemBase = ({
|
|
51
21
|
ref,
|
|
52
|
-
left,
|
|
53
|
-
right,
|
|
54
22
|
children,
|
|
55
23
|
style: styleProp,
|
|
56
24
|
disabled = false,
|
|
57
|
-
|
|
58
|
-
variant = 'default',
|
|
25
|
+
variant = 'menuItem',
|
|
59
26
|
selected = false,
|
|
60
27
|
onPress,
|
|
61
28
|
hoverable: hoverableProp = false,
|
|
62
29
|
hovered: hoveredProp = false,
|
|
63
|
-
contentStyle: contentStyleProp,
|
|
64
30
|
...props
|
|
65
|
-
}:
|
|
31
|
+
}: ListItemProps) => {
|
|
66
32
|
const {
|
|
67
33
|
hovered: _hovered,
|
|
68
34
|
focused,
|
|
@@ -77,63 +43,140 @@ const _InternalListItem = ({
|
|
|
77
43
|
disabled,
|
|
78
44
|
hovered: hoverable && hovered,
|
|
79
45
|
focused,
|
|
80
|
-
})
|
|
46
|
+
});
|
|
81
47
|
|
|
82
48
|
listItemStyles.useVariants({
|
|
83
|
-
state,
|
|
84
|
-
variant: variant as
|
|
49
|
+
state: state as never,
|
|
50
|
+
variant: variant as never,
|
|
85
51
|
});
|
|
86
52
|
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
innerContainerStyle,
|
|
90
|
-
contentStyle,
|
|
91
|
-
leftElementStyle,
|
|
92
|
-
rightElementStyle,
|
|
93
|
-
} = useMemo(() => {
|
|
94
|
-
const { innerContainer, content, leftElement, rightElement } = listItemStyles;
|
|
95
|
-
return {
|
|
96
|
-
containerStyles: [listItemStyles.root, styleProp],
|
|
97
|
-
innerContainerStyle: innerContainer,
|
|
98
|
-
contentStyle: content,
|
|
99
|
-
leftElementStyle: leftElement,
|
|
100
|
-
rightElementStyle: rightElement,
|
|
101
|
-
};
|
|
53
|
+
const containerStyles = useMemo(
|
|
54
|
+
() => [listItemStyles.root, styleProp],
|
|
102
55
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
103
|
-
|
|
56
|
+
[styleProp, state, variant],
|
|
57
|
+
);
|
|
104
58
|
|
|
105
59
|
return (
|
|
106
60
|
<TouchableRipple
|
|
107
61
|
{...props}
|
|
108
|
-
style={containerStyles}
|
|
62
|
+
style={containerStyles as StyleProp<ViewStyle>}
|
|
109
63
|
disabled={disabled}
|
|
110
64
|
onPress={onPress}
|
|
111
65
|
ref={actionsRef}>
|
|
112
66
|
<>
|
|
113
|
-
|
|
114
|
-
{left ? (
|
|
115
|
-
<View style={leftElementStyle}>
|
|
116
|
-
{typeof left === 'function' ? left({ hovered }) : left}
|
|
117
|
-
</View>
|
|
118
|
-
) : null}
|
|
119
|
-
<View style={[contentStyle, contentStyleProp]}>{children}</View>
|
|
120
|
-
{right ? (
|
|
121
|
-
<View style={rightElementStyle}>
|
|
122
|
-
{typeof right === 'function' ? right({ hovered }) : right}
|
|
123
|
-
</View>
|
|
124
|
-
) : null}
|
|
125
|
-
</View>
|
|
126
|
-
{divider ? <Divider /> : null}
|
|
67
|
+
{children}
|
|
127
68
|
<StateLayer style={listItemStyles.stateLayer} />
|
|
128
69
|
</>
|
|
129
70
|
</TouchableRipple>
|
|
130
71
|
);
|
|
131
72
|
};
|
|
132
73
|
|
|
133
|
-
const
|
|
74
|
+
const ListItemBase = memo(_ListItemBase);
|
|
75
|
+
|
|
76
|
+
const _ListItemSelectable = <Option extends object = DefaultListItemT>({
|
|
77
|
+
value,
|
|
78
|
+
children,
|
|
79
|
+
onPress,
|
|
80
|
+
onBeforeToggle,
|
|
81
|
+
disabled: itemDisabledProp = false,
|
|
82
|
+
shouldToggleOnPress = true,
|
|
83
|
+
accessibilityRole,
|
|
84
|
+
accessibilityState,
|
|
85
|
+
variant,
|
|
86
|
+
...rest
|
|
87
|
+
}: ListItemProps<Option> & { value: ListItemId }) => {
|
|
88
|
+
const {
|
|
89
|
+
onAdd,
|
|
90
|
+
onRemove,
|
|
91
|
+
disabled: listDisabled,
|
|
92
|
+
allowDeselect,
|
|
93
|
+
isSelectedId,
|
|
94
|
+
} = useListContextValue(state => ({
|
|
95
|
+
onAdd: state.onAdd as (item: Option) => void,
|
|
96
|
+
onRemove: state.onRemove as (item: Option) => void,
|
|
97
|
+
disabled: state.disabled,
|
|
98
|
+
allowDeselect: state.allowDeselect,
|
|
99
|
+
isSelectedId: state.isSelectedId,
|
|
100
|
+
}));
|
|
101
|
+
|
|
102
|
+
const option = useMemo(
|
|
103
|
+
() =>
|
|
104
|
+
({
|
|
105
|
+
id: value,
|
|
106
|
+
...(itemDisabledProp ? { selectable: false } : {}),
|
|
107
|
+
} as Option),
|
|
108
|
+
[itemDisabledProp, value],
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const isSelected = isSelectedId(value);
|
|
112
|
+
|
|
113
|
+
const isSelectable = (option as Record<string, unknown>).selectable;
|
|
114
|
+
const isOptionDisabled = Boolean(listDisabled || itemDisabledProp || isSelectable === false);
|
|
115
|
+
|
|
116
|
+
const handlePress = useCallback(
|
|
117
|
+
(
|
|
118
|
+
event: NonNullable<ListItemProps<Option>['onPress']> extends (event: infer E) => void
|
|
119
|
+
? E
|
|
120
|
+
: never,
|
|
121
|
+
) => {
|
|
122
|
+
if (isOptionDisabled) return;
|
|
123
|
+
onPress?.(event);
|
|
124
|
+
|
|
125
|
+
if (!shouldToggleOnPress) return;
|
|
126
|
+
onBeforeToggle?.(event);
|
|
127
|
+
|
|
128
|
+
if (isSelected) {
|
|
129
|
+
if (allowDeselect) onRemove(option);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
onAdd(option);
|
|
133
|
+
},
|
|
134
|
+
[
|
|
135
|
+
allowDeselect,
|
|
136
|
+
isOptionDisabled,
|
|
137
|
+
isSelected,
|
|
138
|
+
onAdd,
|
|
139
|
+
onBeforeToggle,
|
|
140
|
+
onPress,
|
|
141
|
+
onRemove,
|
|
142
|
+
option,
|
|
143
|
+
shouldToggleOnPress,
|
|
144
|
+
],
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
<ListItemBase
|
|
149
|
+
{...(rest as ListItemProps)}
|
|
150
|
+
selected={isSelected}
|
|
151
|
+
disabled={isOptionDisabled}
|
|
152
|
+
onPress={handlePress}
|
|
153
|
+
variant={variant ?? 'menuItem'}
|
|
154
|
+
accessibilityRole={accessibilityRole}
|
|
155
|
+
accessibilityState={
|
|
156
|
+
accessibilityState ?? { selected: isSelected, disabled: isOptionDisabled }
|
|
157
|
+
}>
|
|
158
|
+
{children}
|
|
159
|
+
</ListItemBase>
|
|
160
|
+
);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const ListItemSelectable = typedMemo(_ListItemSelectable);
|
|
164
|
+
|
|
165
|
+
const _ListItem = <Option extends object = DefaultListItemT>(props: ListItemProps<Option>) => {
|
|
166
|
+
if (props.value !== undefined) {
|
|
167
|
+
return <ListItemSelectable {...(props as ListItemProps<Option> & { value: ListItemId })} />;
|
|
168
|
+
}
|
|
169
|
+
return <ListItemBase {...(props as ListItemProps)} />;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const ListItem = typedMemo(_ListItem);
|
|
173
|
+
|
|
174
|
+
type ControlledListValue = ListItemId | ListItemId[] | null;
|
|
175
|
+
|
|
176
|
+
const emptyArr: ListItemId[] = [];
|
|
134
177
|
|
|
135
178
|
const ListProvider = typedMemo(
|
|
136
|
-
<Option extends
|
|
179
|
+
<Option extends object = DefaultListItemT>({
|
|
137
180
|
children,
|
|
138
181
|
value: valueProp,
|
|
139
182
|
defaultValue,
|
|
@@ -141,81 +184,54 @@ const ListProvider = typedMemo(
|
|
|
141
184
|
multiple = false,
|
|
142
185
|
disabled = false,
|
|
143
186
|
error = false,
|
|
144
|
-
|
|
145
|
-
searchKey,
|
|
146
|
-
onSearchChange,
|
|
147
|
-
hideSelected: hideSelectedProp,
|
|
187
|
+
allowDeselect: allowDeselectProp,
|
|
148
188
|
}: ListProps<Option>) => {
|
|
149
|
-
|
|
150
|
-
const [value, onValueChange] = useControlledValue<Option['id'] | Option['id'][] | null>({
|
|
189
|
+
const [value, onValueChange] = useControlledValue<ControlledListValue>({
|
|
151
190
|
value: valueProp,
|
|
152
|
-
defaultValue: defaultValue ?? (multiple ? (emptyArr as
|
|
191
|
+
defaultValue: defaultValue ?? (multiple ? (emptyArr as ListItemId[]) : null),
|
|
153
192
|
onChange: onChange as
|
|
154
|
-
| ((value: ControlledListValue, item: Option, event?:
|
|
193
|
+
| ((value: ControlledListValue, item: Option, event?: unknown) => void)
|
|
155
194
|
| undefined,
|
|
156
195
|
});
|
|
157
196
|
const valueRef = useLatest(value);
|
|
158
|
-
const [searchQuery, setSearchQuery] = useState('');
|
|
159
197
|
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
198
|
+
const allowDeselect = allowDeselectProp !== undefined ? allowDeselectProp : multiple;
|
|
199
|
+
const isSelectedId = useCallback(
|
|
200
|
+
(id: ListItemId) => {
|
|
201
|
+
if (multiple) {
|
|
202
|
+
const values = (value as ListItemId[] | null | undefined) ?? [];
|
|
203
|
+
return values.some(v => v === id);
|
|
204
|
+
}
|
|
205
|
+
return (value as ListItemId | null) === id;
|
|
164
206
|
},
|
|
165
|
-
[
|
|
207
|
+
[multiple, value],
|
|
166
208
|
);
|
|
167
209
|
|
|
168
|
-
const hideSelected = hideSelectedProp !== undefined ? hideSelectedProp : multiple;
|
|
169
|
-
|
|
170
|
-
const filteredItems = useMemo(() => {
|
|
171
|
-
let result = items;
|
|
172
|
-
|
|
173
|
-
if (hideSelected) {
|
|
174
|
-
result = result.filter(item => {
|
|
175
|
-
if (multiple) {
|
|
176
|
-
const values = (value as Option['id'][]) || [];
|
|
177
|
-
return !values.some(v => v === item.id);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const singleValue = value as Option['id'] | null;
|
|
181
|
-
return singleValue !== item.id;
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (searchQuery) {
|
|
186
|
-
const key = searchKey || 'label';
|
|
187
|
-
const lowerQuery = searchQuery.toLowerCase();
|
|
188
|
-
result = result.filter(item => {
|
|
189
|
-
const itemValue = item[key];
|
|
190
|
-
return String(itemValue || '')
|
|
191
|
-
.toLowerCase()
|
|
192
|
-
.includes(lowerQuery);
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
return result;
|
|
197
|
-
}, [hideSelected, items, multiple, searchKey, searchQuery, value]);
|
|
198
|
-
|
|
199
210
|
const onAdd = useCallback(
|
|
200
211
|
(item: Option) => {
|
|
212
|
+
const id = (item as { id?: ListItemId }).id as ListItemId;
|
|
201
213
|
if (multiple) {
|
|
202
|
-
const currentValue = (valueRef.current as
|
|
203
|
-
if (!currentValue.find(v => v ===
|
|
204
|
-
onValueChange([...currentValue,
|
|
214
|
+
const currentValue = (valueRef.current as ListItemId[]) || [];
|
|
215
|
+
if (!currentValue.find(v => v === id)) {
|
|
216
|
+
onValueChange([...currentValue, id], item);
|
|
205
217
|
}
|
|
206
218
|
return;
|
|
207
219
|
}
|
|
208
220
|
|
|
209
|
-
onValueChange(
|
|
221
|
+
onValueChange(id, item);
|
|
210
222
|
},
|
|
211
223
|
[multiple, onValueChange, valueRef],
|
|
212
224
|
);
|
|
213
225
|
|
|
214
226
|
const onRemove = useCallback(
|
|
215
227
|
(item: Option) => {
|
|
228
|
+
const id = (item as { id?: ListItemId }).id as ListItemId;
|
|
216
229
|
if (multiple) {
|
|
217
|
-
const currentValue = (valueRef.current as
|
|
218
|
-
onValueChange(
|
|
230
|
+
const currentValue = (valueRef.current as ListItemId[]) || [];
|
|
231
|
+
onValueChange(
|
|
232
|
+
currentValue.filter(v => v !== id),
|
|
233
|
+
item,
|
|
234
|
+
);
|
|
219
235
|
return;
|
|
220
236
|
}
|
|
221
237
|
|
|
@@ -229,12 +245,10 @@ const ListProvider = typedMemo(
|
|
|
229
245
|
multiple,
|
|
230
246
|
onAdd: onAdd as (item: DefaultListItemT) => void,
|
|
231
247
|
onRemove: onRemove as (item: DefaultListItemT) => void,
|
|
248
|
+
isSelectedId,
|
|
232
249
|
disabled,
|
|
233
250
|
error,
|
|
234
|
-
|
|
235
|
-
searchQuery,
|
|
236
|
-
setSearchQuery: handleSearchQueryChange,
|
|
237
|
-
filteredItems,
|
|
251
|
+
allowDeselect,
|
|
238
252
|
} as ListContextValue<DefaultListItemT>;
|
|
239
253
|
|
|
240
254
|
return <ListContextProvider value={contextValue}>{children}</ListContextProvider>;
|
|
@@ -242,266 +256,20 @@ const ListProvider = typedMemo(
|
|
|
242
256
|
);
|
|
243
257
|
|
|
244
258
|
const ListContent = typedMemo(
|
|
245
|
-
<
|
|
246
|
-
Option extends DefaultListItemT = DefaultListItemT,
|
|
247
|
-
C extends ComponentType<any> = typeof ScrollView,
|
|
248
|
-
>({
|
|
249
|
-
ref,
|
|
250
|
-
children,
|
|
251
|
-
ContainerComponent,
|
|
252
|
-
style,
|
|
253
|
-
emptyState,
|
|
254
|
-
processProps,
|
|
255
|
-
...rest
|
|
256
|
-
}: ListContentProps<Option, C> & { ref?: React.ForwardedRef<any> }) => {
|
|
257
|
-
const { filteredItems, value, multiple, searchQuery, items } = useListContextValue(
|
|
258
|
-
state => ({
|
|
259
|
-
filteredItems: state.filteredItems,
|
|
260
|
-
value: state.value,
|
|
261
|
-
multiple: state.multiple,
|
|
262
|
-
searchQuery: state.searchQuery,
|
|
263
|
-
items: state.items,
|
|
264
|
-
}),
|
|
265
|
-
);
|
|
266
|
-
|
|
267
|
-
const selectedIds = useMemo(() => {
|
|
268
|
-
const map: Record<string, true> = {};
|
|
269
|
-
const values = multiple ? (value as any[]) ?? [] : value == null ? [] : [value];
|
|
270
|
-
for (const v of values) {
|
|
271
|
-
const id = v?.id ?? v;
|
|
272
|
-
if (id != null) map[id] = true;
|
|
273
|
-
}
|
|
274
|
-
return map;
|
|
275
|
-
}, [multiple, value]);
|
|
276
|
-
|
|
277
|
-
const isSelected = useCallback(
|
|
278
|
-
(item: DefaultListItemT) => selectedIds[item.id as any] === true,
|
|
279
|
-
[selectedIds],
|
|
280
|
-
);
|
|
281
|
-
|
|
282
|
-
const content = useMemo(() => {
|
|
283
|
-
if (children === undefined) {
|
|
284
|
-
return null;
|
|
285
|
-
}
|
|
286
|
-
return filteredItems.map(item =>
|
|
287
|
-
children(item as Option, isSelected(item as DefaultListItemT)),
|
|
288
|
-
);
|
|
289
|
-
}, [children, filteredItems, isSelected]);
|
|
290
|
-
|
|
291
|
-
const defaultEmptyState = useMemo(() => {
|
|
292
|
-
const hasSearchQuery = searchQuery && searchQuery.trim().length > 0;
|
|
293
|
-
const hasNoItems = items.length === 0;
|
|
294
|
-
|
|
295
|
-
if (hasNoItems) {
|
|
296
|
-
return (
|
|
297
|
-
<View style={listStyles.emptyState}>
|
|
298
|
-
<Text style={listStyles.emptyStateText}>No items available</Text>
|
|
299
|
-
</View>
|
|
300
|
-
);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
if (hasSearchQuery) {
|
|
304
|
-
return (
|
|
305
|
-
<View style={listStyles.emptyState}>
|
|
306
|
-
<Text style={listStyles.emptyStateText}>No results found</Text>
|
|
307
|
-
</View>
|
|
308
|
-
);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
return (
|
|
312
|
-
<View style={listStyles.emptyState}>
|
|
313
|
-
<Text style={listStyles.emptyStateText}>No items</Text>
|
|
314
|
-
</View>
|
|
315
|
-
);
|
|
316
|
-
}, [items.length, searchQuery]);
|
|
317
|
-
|
|
318
|
-
const resolvedEmptyState = emptyState ?? defaultEmptyState;
|
|
319
|
-
const isEmpty = filteredItems.length === 0;
|
|
320
|
-
|
|
321
|
-
const Container = (ContainerComponent ?? ScrollView) as ComponentType<any>;
|
|
322
|
-
|
|
323
|
-
if (processProps) {
|
|
324
|
-
const baseProps = { style, ...rest, accessibilityRole: 'listbox' } as Record<
|
|
325
|
-
string,
|
|
326
|
-
any
|
|
327
|
-
>;
|
|
328
|
-
const processedProps = processProps({
|
|
329
|
-
props: baseProps as any,
|
|
330
|
-
items: filteredItems as Option[],
|
|
331
|
-
isEmpty,
|
|
332
|
-
emptyState: resolvedEmptyState,
|
|
333
|
-
isSelected,
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
return <Container {...(processedProps as any)} ref={ref} />;
|
|
337
|
-
}
|
|
338
|
-
|
|
259
|
+
({ ref, children, style, ...rest }: ListContentProps & { ref?: React.ForwardedRef<any> }) => {
|
|
339
260
|
return (
|
|
340
|
-
<
|
|
341
|
-
{isEmpty ? resolvedEmptyState : content}
|
|
342
|
-
</Container>
|
|
343
|
-
);
|
|
344
|
-
},
|
|
345
|
-
);
|
|
346
|
-
|
|
347
|
-
const ListGroup = memo(({ children, label, style, ...rest }: ListGroupProps) => {
|
|
348
|
-
return (
|
|
349
|
-
<View style={style} {...rest}>
|
|
350
|
-
{label ? <Text style={listStyles.groupLabel}>{label}</Text> : null}
|
|
351
|
-
{children}
|
|
352
|
-
</View>
|
|
353
|
-
);
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
const ListOption = memo(
|
|
357
|
-
<Option extends DefaultListItemT = DefaultListItemT>({
|
|
358
|
-
value,
|
|
359
|
-
children,
|
|
360
|
-
onPress,
|
|
361
|
-
disabled: itemDisabledProp = false,
|
|
362
|
-
shouldToggleOnPress = true,
|
|
363
|
-
accessibilityRole,
|
|
364
|
-
accessibilityState,
|
|
365
|
-
...rest
|
|
366
|
-
}: ListItemOptionProps<Option>) => {
|
|
367
|
-
const {
|
|
368
|
-
multiple,
|
|
369
|
-
onAdd,
|
|
370
|
-
onRemove,
|
|
371
|
-
disabled: listDisabled,
|
|
372
|
-
items,
|
|
373
|
-
} = useListContextValue(state => ({
|
|
374
|
-
multiple: state.multiple,
|
|
375
|
-
onAdd: state.onAdd,
|
|
376
|
-
onRemove: state.onRemove,
|
|
377
|
-
disabled: state.disabled,
|
|
378
|
-
items: state.items,
|
|
379
|
-
}));
|
|
380
|
-
|
|
381
|
-
const option = useMemo(() => {
|
|
382
|
-
const foundItem = items.find(i => i.id === value);
|
|
383
|
-
if (foundItem) return foundItem as Option;
|
|
384
|
-
|
|
385
|
-
return {
|
|
386
|
-
id: value,
|
|
387
|
-
...(itemDisabledProp ? { selectable: false } : {}),
|
|
388
|
-
} as Option;
|
|
389
|
-
}, [itemDisabledProp, items, value]);
|
|
390
|
-
|
|
391
|
-
const isSelected = useListContextValue(state => {
|
|
392
|
-
if (multiple) {
|
|
393
|
-
const values = state.value as any[];
|
|
394
|
-
return values?.some(v => (v?.id ?? v) === option.id) || false;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
const singleValue = state.value as any;
|
|
398
|
-
return (singleValue?.id ?? singleValue) === option.id || false;
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
const isOptionDisabled = Boolean(
|
|
402
|
-
listDisabled || itemDisabledProp || option.selectable === false,
|
|
403
|
-
);
|
|
404
|
-
|
|
405
|
-
const handlePress = useCallback(
|
|
406
|
-
(event: any) => {
|
|
407
|
-
if (isOptionDisabled) return;
|
|
408
|
-
onPress?.(option, event);
|
|
409
|
-
|
|
410
|
-
if (shouldToggleOnPress) {
|
|
411
|
-
if (isSelected) {
|
|
412
|
-
onRemove(option);
|
|
413
|
-
} else {
|
|
414
|
-
onAdd(option);
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
},
|
|
418
|
-
[isOptionDisabled, isSelected, onAdd, onPress, onRemove, option, shouldToggleOnPress],
|
|
419
|
-
);
|
|
420
|
-
|
|
421
|
-
return (
|
|
422
|
-
<ListItem
|
|
423
|
-
{...rest}
|
|
424
|
-
selected={isSelected}
|
|
425
|
-
disabled={isOptionDisabled}
|
|
426
|
-
onPress={handlePress}
|
|
427
|
-
variant={rest.variant || 'menuItem'}
|
|
428
|
-
accessibilityRole={accessibilityRole}
|
|
429
|
-
accessibilityState={
|
|
430
|
-
accessibilityState ?? { selected: isSelected, disabled: isOptionDisabled }
|
|
431
|
-
}>
|
|
261
|
+
<ScrollView style={style} {...rest} ref={ref}>
|
|
432
262
|
{children}
|
|
433
|
-
</
|
|
263
|
+
</ScrollView>
|
|
434
264
|
);
|
|
435
265
|
},
|
|
436
266
|
);
|
|
437
267
|
|
|
438
|
-
const ListSearchInput = memo(({ children, ...textInputProps }: ListSearchInputProps) => {
|
|
439
|
-
const { searchQuery, setSearchQuery } = useListContextValue(state => ({
|
|
440
|
-
searchQuery: state.searchQuery,
|
|
441
|
-
setSearchQuery: state.setSearchQuery,
|
|
442
|
-
}));
|
|
443
|
-
|
|
444
|
-
const textInputRef = useRef<TextInputHandles>(null);
|
|
445
|
-
|
|
446
|
-
const handleChangeText = useCallback(
|
|
447
|
-
(text: string) => {
|
|
448
|
-
setSearchQuery(text);
|
|
449
|
-
},
|
|
450
|
-
[setSearchQuery],
|
|
451
|
-
);
|
|
452
|
-
|
|
453
|
-
const inputProps = {
|
|
454
|
-
...textInputProps,
|
|
455
|
-
value: searchQuery,
|
|
456
|
-
onChangeText: handleChangeText,
|
|
457
|
-
placeholder: textInputProps.placeholder || 'Search...',
|
|
458
|
-
inputStyle: listStyles.searchInputInput,
|
|
459
|
-
} as TextInputProps;
|
|
460
|
-
|
|
461
|
-
const onPressLeftIcon = useCallback(() => {
|
|
462
|
-
textInputRef.current?.focus();
|
|
463
|
-
}, []);
|
|
464
|
-
|
|
465
|
-
const onClearSearchQuery = useCallback(() => {
|
|
466
|
-
handleChangeText('');
|
|
467
|
-
}, [handleChangeText]);
|
|
468
|
-
|
|
469
|
-
return (
|
|
470
|
-
<TextInput
|
|
471
|
-
ref={textInputRef}
|
|
472
|
-
style={listStyles.searchInput}
|
|
473
|
-
size="sm"
|
|
474
|
-
variant="outlined"
|
|
475
|
-
{...inputProps}>
|
|
476
|
-
<TextInput.Left>
|
|
477
|
-
<Icon onPress={onPressLeftIcon} name="magnify" size={20} />
|
|
478
|
-
</TextInput.Left>
|
|
479
|
-
{searchQuery ? (
|
|
480
|
-
<TextInput.Right>
|
|
481
|
-
<IconButton name="close" size={20} onPress={onClearSearchQuery} />
|
|
482
|
-
</TextInput.Right>
|
|
483
|
-
) : null}
|
|
484
|
-
{children}
|
|
485
|
-
</TextInput>
|
|
486
|
-
);
|
|
487
|
-
});
|
|
488
|
-
|
|
489
268
|
const List = Object.assign(ListProvider, {
|
|
490
269
|
Content: ListContent,
|
|
491
|
-
Item:
|
|
492
|
-
SearchInput: ListSearchInput,
|
|
493
|
-
Group: ListGroup,
|
|
494
|
-
Row: ListItem,
|
|
270
|
+
Item: ListItem,
|
|
495
271
|
});
|
|
496
272
|
|
|
497
273
|
export default List;
|
|
498
274
|
|
|
499
|
-
export {
|
|
500
|
-
type InternalListItemProps,
|
|
501
|
-
ListContent,
|
|
502
|
-
ListGroup,
|
|
503
|
-
ListItem,
|
|
504
|
-
ListOption,
|
|
505
|
-
ListProvider,
|
|
506
|
-
ListSearchInput,
|
|
507
|
-
};
|
|
275
|
+
export { ListContent, ListItem, ListProvider };
|
|
@@ -7,12 +7,10 @@ const listContextDefaultValue: ListContextValue<DefaultListItemT> = {
|
|
|
7
7
|
multiple: false,
|
|
8
8
|
onAdd: () => {},
|
|
9
9
|
onRemove: () => {},
|
|
10
|
+
isSelectedId: () => false,
|
|
10
11
|
disabled: false,
|
|
11
12
|
error: false,
|
|
12
|
-
|
|
13
|
-
searchQuery: '',
|
|
14
|
-
setSearchQuery: () => {},
|
|
15
|
-
filteredItems: [],
|
|
13
|
+
allowDeselect: false,
|
|
16
14
|
};
|
|
17
15
|
|
|
18
16
|
const {
|
package/components/List/index.ts
CHANGED