@hero-design/rn 7.16.2 → 7.17.1
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/.turbo/turbo-build.log +2 -2
- package/es/index.js +331 -259
- package/lib/index.js +330 -258
- package/package.json +2 -2
- package/src/components/Accordion/__tests__/__snapshots__/AccordionItem.spec.tsx.snap +12 -12
- package/src/components/Accordion/__tests__/__snapshots__/index.spec.tsx.snap +18 -18
- package/src/components/Alert/__tests__/__snapshots__/index.spec.tsx.snap +26 -26
- package/src/components/Avatar/__tests__/__snapshots__/StyledAvatar.spec.tsx.snap +3 -3
- package/src/components/Avatar/__tests__/__snapshots__/index.spec.tsx.snap +2 -2
- package/src/components/Badge/__tests__/__snapshots__/Badge.spec.tsx.snap +1 -1
- package/src/components/Badge/__tests__/__snapshots__/Status.spec.tsx.snap +2 -2
- package/src/components/BottomNavigation/__tests__/__snapshots__/index.spec.tsx.snap +9 -9
- package/src/components/BottomSheet/__tests__/__snapshots__/index.spec.tsx.snap +12 -12
- package/src/components/Button/LoadingIndicator/__tests__/__snapshots__/StyledLoadingIndicator.spec.tsx.snap +2 -2
- package/src/components/Button/LoadingIndicator/__tests__/__snapshots__/index.spec.tsx.snap +6 -6
- package/src/components/Button/UtilityButton/__tests__/__snapshots__/index.spec.tsx.snap +6 -6
- package/src/components/Button/__tests__/__snapshots__/IconButton.spec.tsx.snap +1 -1
- package/src/components/Button/__tests__/__snapshots__/StyledButton.spec.tsx.snap +67 -67
- package/src/components/Calendar/__tests__/__snapshots__/CalendarRowItem.spec.tsx.snap +12 -12
- package/src/components/Card/__tests__/__snapshots__/StyledCard.spec.tsx.snap +1 -1
- package/src/components/Checkbox/__tests__/__snapshots__/StyledCheckbox.spec.tsx.snap +1 -1
- package/src/components/Checkbox/__tests__/__snapshots__/index.spec.tsx.snap +2 -2
- package/src/components/ContentNavigator/__tests__/__snapshots__/StyledContentNavigator.spec.tsx.snap +1 -1
- package/src/components/ContentNavigator/__tests__/__snapshots__/index.spec.tsx.snap +6 -6
- package/src/components/DatePicker/__tests__/__snapshots__/DatePickerAndroid.spec.tsx.snap +5 -5
- package/src/components/DatePicker/__tests__/__snapshots__/DatePickerIOS.spec.tsx.snap +12 -12
- package/src/components/Divider/__tests__/__snapshots__/StyledDivider.spec.tsx.snap +12 -12
- package/src/components/Drawer/__tests__/__snapshots__/index.spec.tsx.snap +3 -3
- package/src/components/Empty/__tests__/__snapshots__/index.spec.tsx.snap +3 -3
- package/src/components/FAB/ActionGroup/__tests__/__snapshots__/index.spec.tsx.snap +28 -28
- package/src/components/FAB/__tests__/__snapshots__/AnimatedFABIcon.spec.tsx.snap +2 -2
- package/src/components/FAB/__tests__/__snapshots__/StyledFAB.spec.tsx.snap +3 -3
- package/src/components/FAB/__tests__/__snapshots__/index.spec.tsx.snap +9 -9
- package/src/components/Icon/__tests__/__snapshots__/index.spec.tsx.snap +3 -3
- package/src/components/List/BasicListItem.tsx +8 -4
- package/src/components/List/__tests__/__snapshots__/BasicListItem.spec.tsx.snap +3 -3
- package/src/components/List/__tests__/__snapshots__/ListItem.spec.tsx.snap +20 -20
- package/src/components/List/__tests__/__snapshots__/StyledBasicListItem.spec.tsx.snap +6 -6
- package/src/components/List/__tests__/__snapshots__/StyledListItem.spec.tsx.snap +12 -12
- package/src/components/PinInput/__tests__/__snapshots__/PinCell.spec.tsx.snap +4 -4
- package/src/components/PinInput/__tests__/__snapshots__/index.spec.tsx.snap +22 -22
- package/src/components/Progress/ProgressCircle.tsx +25 -22
- package/src/components/Progress/StyledProgressCircle.tsx +33 -28
- package/src/components/Progress/__tests__/__snapshots__/index.spec.js.snap +102 -92
- package/src/components/Radio/__tests__/__snapshots__/Radio.spec.tsx.snap +5 -5
- package/src/components/Radio/__tests__/__snapshots__/RadioGroup.spec.tsx.snap +6 -6
- package/src/components/Radio/__tests__/__snapshots__/StyledRadio.spec.tsx.snap +3 -3
- package/src/components/RichTextEditor/__tests__/__snapshots__/EditorToolbar.spec.tsx.snap +11 -11
- package/src/components/RichTextEditor/__tests__/__snapshots__/RichTextEditor.spec.tsx.snap +6 -6
- package/src/components/SectionHeading/__tests__/__snapshots__/StyledHeading.spec.tsx.snap +1 -1
- package/src/components/SectionHeading/__tests__/__snapshots__/index.spec.tsx.snap +9 -9
- package/src/components/Select/MultiSelect/Option.tsx +20 -11
- package/src/components/Select/MultiSelect/OptionList.tsx +47 -41
- package/src/components/Select/MultiSelect/__tests__/OptionList.spec.tsx +25 -14
- package/src/components/Select/MultiSelect/__tests__/__snapshots__/Option.spec.tsx.snap +6 -4
- package/src/components/Select/MultiSelect/__tests__/__snapshots__/OptionList.spec.tsx.snap +1638 -134
- package/src/components/Select/MultiSelect/__tests__/__snapshots__/index.spec.tsx.snap +5312 -366
- package/src/components/Select/MultiSelect/__tests__/index.spec.tsx +122 -1
- package/src/components/Select/MultiSelect/index.tsx +26 -36
- package/src/components/Select/SingleSelect/Option.tsx +19 -3
- package/src/components/Select/SingleSelect/OptionList.tsx +47 -39
- package/src/components/Select/SingleSelect/__tests__/OptionList.spec.tsx +23 -12
- package/src/components/Select/SingleSelect/__tests__/__snapshots__/Option.spec.tsx.snap +5 -3
- package/src/components/Select/SingleSelect/__tests__/__snapshots__/OptionList.spec.tsx.snap +1632 -128
- package/src/components/Select/SingleSelect/__tests__/__snapshots__/index.spec.tsx.snap +4932 -302
- package/src/components/Select/SingleSelect/__tests__/index.spec.tsx +117 -1
- package/src/components/Select/SingleSelect/index.tsx +26 -37
- package/src/components/Select/StyledOptionList.tsx +43 -44
- package/src/components/Select/StyledSelect.tsx +7 -3
- package/src/components/Select/__tests__/StyledSelect.spec.tsx +1 -9
- package/src/components/Select/__tests__/__snapshots__/StyledSelect.spec.tsx.snap +2 -15
- package/src/components/Select/__tests__/helpers.spec.tsx +74 -0
- package/src/components/Select/helpers.tsx +87 -4
- package/src/components/Select/types.ts +99 -0
- package/src/components/Spinner/__tests__/__snapshots__/AnimatedSpinner.spec.tsx.snap +4 -4
- package/src/components/Spinner/__tests__/__snapshots__/StyledSpinner.spec.tsx.snap +8 -8
- package/src/components/Spinner/__tests__/__snapshots__/index.spec.tsx.snap +4 -4
- package/src/components/Switch/__tests__/__snapshots__/StyledHeading.spec.tsx.snap +1 -1
- package/src/components/Switch/__tests__/__snapshots__/index.spec.tsx.snap +2 -2
- package/src/components/Tabs/__tests__/__snapshots__/ScrollableTabs.spec.tsx.snap +6 -6
- package/src/components/Tabs/__tests__/__snapshots__/index.spec.tsx.snap +8 -8
- package/src/components/Tag/__tests__/__snapshots__/Tag.spec.tsx.snap +8 -8
- package/src/components/TextInput/__tests__/__snapshots__/StyledTextInput.spec.tsx.snap +50 -50
- package/src/components/TextInput/__tests__/__snapshots__/index.spec.tsx.snap +241 -85
- package/src/components/TextInput/__tests__/index.spec.tsx +29 -8
- package/src/components/TextInput/index.tsx +18 -7
- package/src/components/TimePicker/__tests__/__snapshots__/TimePickerAndroid.spec.tsx.snap +5 -5
- package/src/components/TimePicker/__tests__/__snapshots__/TimePickerIOS.spec.tsx.snap +12 -12
- package/src/components/Toast/__tests__/__snapshots__/Toast.spec.tsx.snap +22 -22
- package/src/components/Toolbar/__tests__/__snapshots__/ToolbarGroup.spec.tsx.snap +12 -12
- package/src/components/Toolbar/__tests__/__snapshots__/ToolbarItem.spec.tsx.snap +8 -8
- package/src/components/Typography/Text/__tests__/__snapshots__/StyledText.spec.tsx.snap +13 -13
- package/src/theme/__tests__/__snapshots__/index.spec.ts.snap +145 -146
- package/src/theme/components/alert.ts +3 -3
- package/src/theme/components/badge.ts +1 -1
- package/src/theme/components/card.ts +4 -4
- package/src/theme/components/list.ts +4 -4
- package/src/theme/components/pinInput.ts +2 -2
- package/src/theme/components/progress.ts +5 -5
- package/src/theme/components/select.ts +3 -3
- package/src/theme/components/toast.ts +3 -3
- package/src/theme/global/colors.ts +40 -39
- package/src/types.ts +7 -1
- package/types/components/List/BasicListItem.d.ts +1 -1
- package/types/components/Progress/StyledProgressCircle.d.ts +12 -6
- package/types/components/Select/MultiSelect/Option.d.ts +4 -2
- package/types/components/Select/MultiSelect/OptionList.d.ts +6 -7
- package/types/components/Select/MultiSelect/index.d.ts +5 -5
- package/types/components/Select/SingleSelect/Option.d.ts +4 -2
- package/types/components/Select/SingleSelect/OptionList.d.ts +6 -7
- package/types/components/Select/SingleSelect/index.d.ts +5 -5
- package/types/components/Select/StyledOptionList.d.ts +10 -16
- package/types/components/Select/StyledSelect.d.ts +8 -2
- package/types/components/Select/__tests__/helpers.spec.d.ts +1 -0
- package/types/components/Select/helpers.d.ts +14 -2
- package/types/components/Select/index.d.ts +1 -1
- package/types/components/Select/types.d.ts +32 -7
- package/types/components/TextInput/index.d.ts +4 -2
- package/types/theme/components/progress.d.ts +1 -2
- package/types/theme/components/select.d.ts +3 -3
- package/types/types.d.ts +2 -1
- package/src/components/Select/types.tsx +0 -52
|
@@ -2,6 +2,9 @@ import { fireEvent } from '@testing-library/react-native';
|
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import renderWithTheme from '../../../../testHelpers/renderWithTheme';
|
|
4
4
|
import MultiSelect from '..';
|
|
5
|
+
import Typography from '../../../Typography';
|
|
6
|
+
import List from '../../../List';
|
|
7
|
+
import { ListRenderOptionInfo } from '../../types';
|
|
5
8
|
|
|
6
9
|
const options = [
|
|
7
10
|
{ text: 'Monday', value: 'mon' },
|
|
@@ -10,9 +13,65 @@ const options = [
|
|
|
10
13
|
{ text: 'Thursday', value: 'thu' },
|
|
11
14
|
{ text: 'Friday', value: 'fri' },
|
|
12
15
|
{ text: 'Saturday', value: 'sat' },
|
|
13
|
-
{ text: 'Sunday', value: 'sun' },
|
|
16
|
+
{ text: 'Sunday', value: 'sun', disabled: true },
|
|
14
17
|
];
|
|
15
18
|
|
|
19
|
+
const sections = [
|
|
20
|
+
{ category: 'A', data: [{ text: 'A1', value: 'a1' }] },
|
|
21
|
+
{
|
|
22
|
+
category: 'B',
|
|
23
|
+
data: [
|
|
24
|
+
{ text: 'B1', value: 'b1' },
|
|
25
|
+
{ text: 'B2', value: 'b2' },
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
type CustomOptionType = {
|
|
31
|
+
text: string;
|
|
32
|
+
value: string;
|
|
33
|
+
role: string;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const collaboratorSections = [
|
|
37
|
+
{
|
|
38
|
+
category: 'D',
|
|
39
|
+
data: [
|
|
40
|
+
{ text: 'Daniel', value: 'daniel', role: 'Senior Developer' },
|
|
41
|
+
{ text: 'Daemon', value: 'daemon', role: 'Manager' },
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
category: 'J',
|
|
46
|
+
data: [
|
|
47
|
+
{ text: 'Jennifer', value: 'jennifer', role: 'UX Designer' },
|
|
48
|
+
{ text: 'Josh ', value: 'josh', role: 'Junior Developer' },
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
const renderMultipleOption = ({
|
|
54
|
+
item,
|
|
55
|
+
selected,
|
|
56
|
+
onPress,
|
|
57
|
+
}: ListRenderOptionInfo<string, CustomOptionType>) => (
|
|
58
|
+
<List.BasicItem
|
|
59
|
+
selected={selected}
|
|
60
|
+
suffix={selected ? 'checkmark' : undefined}
|
|
61
|
+
onPress={onPress}
|
|
62
|
+
title={
|
|
63
|
+
<>
|
|
64
|
+
<Typography.Text fontSize="large" fontWeight="semi-bold">
|
|
65
|
+
{item.text}
|
|
66
|
+
</Typography.Text>
|
|
67
|
+
<Typography.Text fontSize="large" intent="subdued">
|
|
68
|
+
{item.role}
|
|
69
|
+
</Typography.Text>
|
|
70
|
+
</>
|
|
71
|
+
}
|
|
72
|
+
/>
|
|
73
|
+
);
|
|
74
|
+
|
|
16
75
|
describe('rendering', () => {
|
|
17
76
|
it('renders correctly when bottom sheet is NOT visible', () => {
|
|
18
77
|
const { queryAllByText, toJSON, getByTestId } = renderWithTheme(
|
|
@@ -53,6 +112,68 @@ describe('rendering', () => {
|
|
|
53
112
|
expect(getByText('Sunday')).toBeDefined();
|
|
54
113
|
expect(getByText('Confirm')).toBeDefined();
|
|
55
114
|
});
|
|
115
|
+
|
|
116
|
+
it('renders correctly when input is loading', () => {
|
|
117
|
+
const { toJSON, getByTestId } = renderWithTheme(
|
|
118
|
+
<MultiSelect
|
|
119
|
+
label="Allow notifications"
|
|
120
|
+
footerLabel="Confirm"
|
|
121
|
+
options={options}
|
|
122
|
+
value={['mon', 'tue']}
|
|
123
|
+
inputProps={{ loading: true }}
|
|
124
|
+
onConfirm={jest.fn()}
|
|
125
|
+
/>
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
expect(toJSON()).toMatchSnapshot();
|
|
129
|
+
expect(getByTestId('input-suffix')).toHaveProp('name', 'loading');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('renders correctly when receives sections', () => {
|
|
133
|
+
const { queryAllByText, getByText, toJSON, getByTestId } = renderWithTheme(
|
|
134
|
+
<MultiSelect
|
|
135
|
+
label="Allow notifications"
|
|
136
|
+
footerLabel="Confirm"
|
|
137
|
+
options={sections}
|
|
138
|
+
value={['b1', 'b2']}
|
|
139
|
+
onConfirm={jest.fn()}
|
|
140
|
+
/>
|
|
141
|
+
);
|
|
142
|
+
fireEvent.press(getByTestId('text-input'));
|
|
143
|
+
|
|
144
|
+
expect(toJSON()).toMatchSnapshot();
|
|
145
|
+
expect(queryAllByText('Allow notifications')).toHaveLength(2);
|
|
146
|
+
expect(getByText('A')).toBeTruthy();
|
|
147
|
+
expect(getByText('A1')).toBeTruthy();
|
|
148
|
+
expect(getByText('B')).toBeTruthy();
|
|
149
|
+
expect(getByText('B1')).toBeTruthy();
|
|
150
|
+
expect(getByText('B2')).toBeTruthy();
|
|
151
|
+
expect(getByText('Confirm')).toBeDefined();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('allows custom renderer', () => {
|
|
155
|
+
const { getByText, toJSON, getByTestId } = renderWithTheme(
|
|
156
|
+
<MultiSelect<string, CustomOptionType>
|
|
157
|
+
label="Choose collaborators"
|
|
158
|
+
footerLabel="Confirm"
|
|
159
|
+
options={collaboratorSections}
|
|
160
|
+
renderOption={renderMultipleOption}
|
|
161
|
+
value={[]}
|
|
162
|
+
onConfirm={jest.fn()}
|
|
163
|
+
/>
|
|
164
|
+
);
|
|
165
|
+
fireEvent.press(getByTestId('text-input'));
|
|
166
|
+
|
|
167
|
+
expect(toJSON()).toMatchSnapshot();
|
|
168
|
+
expect(getByText('Daniel')).toBeTruthy();
|
|
169
|
+
expect(getByText('Senior Developer')).toBeTruthy();
|
|
170
|
+
expect(getByText('Daemon')).toBeTruthy();
|
|
171
|
+
expect(getByText('Manager')).toBeTruthy();
|
|
172
|
+
expect(getByText('Jennifer')).toBeTruthy();
|
|
173
|
+
expect(getByText('UX Designer')).toBeTruthy();
|
|
174
|
+
expect(getByText('Josh')).toBeTruthy();
|
|
175
|
+
expect(getByText('Junior Developer')).toBeTruthy();
|
|
176
|
+
});
|
|
56
177
|
});
|
|
57
178
|
|
|
58
179
|
describe('behavior', () => {
|
|
@@ -1,37 +1,41 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
import { TouchableOpacity,
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { TouchableOpacity, View } from 'react-native';
|
|
3
3
|
|
|
4
|
-
import { SelectProps } from '../types';
|
|
4
|
+
import { OptionType, SelectProps } from '../types';
|
|
5
5
|
import BottomSheet from '../../BottomSheet';
|
|
6
6
|
import Footer from '../Footer';
|
|
7
7
|
import OptionList from './OptionList';
|
|
8
8
|
import TextInput from '../../TextInput';
|
|
9
9
|
import { StyledSearchBar } from '../StyledSelect';
|
|
10
|
+
import { toFlatOptions, toSections, useKeyboard } from '../helpers';
|
|
10
11
|
|
|
11
|
-
export interface MultiSelectProps<T
|
|
12
|
+
export interface MultiSelectProps<V, T extends OptionType<V> = OptionType<V>>
|
|
13
|
+
extends SelectProps<V, T> {
|
|
12
14
|
/**
|
|
13
15
|
* Current selected value.
|
|
14
16
|
*/
|
|
15
|
-
value:
|
|
17
|
+
value: V[];
|
|
16
18
|
/**
|
|
17
19
|
* event handler for footer button.
|
|
18
20
|
*/
|
|
19
|
-
onConfirm: (value:
|
|
21
|
+
onConfirm: (value: V[]) => void;
|
|
20
22
|
/**
|
|
21
23
|
* Footer label.
|
|
22
24
|
*/
|
|
23
25
|
footerLabel: string;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
function MultiSelect<T
|
|
28
|
+
function MultiSelect<V, T extends OptionType<V>>({
|
|
27
29
|
footerLabel,
|
|
28
30
|
label,
|
|
29
|
-
loading,
|
|
31
|
+
loading = false,
|
|
32
|
+
inputProps,
|
|
30
33
|
onConfirm,
|
|
31
34
|
onDimiss,
|
|
32
35
|
onEndReached,
|
|
33
36
|
onQueryChange,
|
|
34
37
|
options,
|
|
38
|
+
renderOption,
|
|
35
39
|
query,
|
|
36
40
|
error,
|
|
37
41
|
editable = true,
|
|
@@ -40,41 +44,24 @@ function MultiSelect<T>({
|
|
|
40
44
|
style,
|
|
41
45
|
testID,
|
|
42
46
|
value,
|
|
43
|
-
}: MultiSelectProps<T>) {
|
|
47
|
+
}: MultiSelectProps<V, T>) {
|
|
48
|
+
const { isKeyboardVisible, keyboardHeight } = useKeyboard();
|
|
44
49
|
const [open, setOpen] = useState(false);
|
|
45
50
|
const [selectingValue, setSelectingValue] = useState(value);
|
|
46
|
-
const
|
|
51
|
+
const sections = toSections(options);
|
|
52
|
+
const flatOptions = toFlatOptions(options);
|
|
53
|
+
const displayedValue = flatOptions
|
|
47
54
|
.filter(opt => value.includes(opt.value))
|
|
48
55
|
.map(opt => opt.text)
|
|
49
56
|
.join(', ');
|
|
50
57
|
|
|
51
|
-
const [isKeyboardVisible, setKeyboardVisible] = useState(false);
|
|
52
|
-
const [keyboardHeight, setKeyboardHeight] = useState(0);
|
|
53
|
-
|
|
54
|
-
useEffect(() => {
|
|
55
|
-
const keyboardDidShowListener = Keyboard.addListener(
|
|
56
|
-
'keyboardDidShow',
|
|
57
|
-
(e: KeyboardEvent) => {
|
|
58
|
-
setKeyboardVisible(true);
|
|
59
|
-
setKeyboardHeight(e.endCoordinates.height);
|
|
60
|
-
}
|
|
61
|
-
);
|
|
62
|
-
const keyboardDidHideListener = Keyboard.addListener(
|
|
63
|
-
'keyboardDidHide',
|
|
64
|
-
() => {
|
|
65
|
-
setKeyboardVisible(false);
|
|
66
|
-
}
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
return () => {
|
|
70
|
-
keyboardDidHideListener.remove();
|
|
71
|
-
keyboardDidShowListener.remove();
|
|
72
|
-
};
|
|
73
|
-
}, []);
|
|
74
|
-
|
|
75
58
|
return (
|
|
76
59
|
<>
|
|
77
|
-
<View
|
|
60
|
+
<View
|
|
61
|
+
pointerEvents={
|
|
62
|
+
!editable || disabled || inputProps?.loading ? 'none' : 'auto'
|
|
63
|
+
}
|
|
64
|
+
>
|
|
78
65
|
<TouchableOpacity onPress={() => setOpen(true)}>
|
|
79
66
|
<TextInput
|
|
80
67
|
label={label}
|
|
@@ -84,6 +71,7 @@ function MultiSelect<T>({
|
|
|
84
71
|
error={error}
|
|
85
72
|
editable={editable}
|
|
86
73
|
disabled={disabled}
|
|
74
|
+
loading={inputProps?.loading}
|
|
87
75
|
numberOfLines={numberOfLines}
|
|
88
76
|
pointerEvents="none"
|
|
89
77
|
style={style}
|
|
@@ -124,9 +112,11 @@ function MultiSelect<T>({
|
|
|
124
112
|
</StyledSearchBar>
|
|
125
113
|
)}
|
|
126
114
|
<OptionList
|
|
115
|
+
onQueryChange={onQueryChange}
|
|
127
116
|
onEndReached={onEndReached}
|
|
128
117
|
loading={loading}
|
|
129
|
-
|
|
118
|
+
sections={sections}
|
|
119
|
+
renderOption={renderOption}
|
|
130
120
|
value={selectingValue}
|
|
131
121
|
onPress={setSelectingValue}
|
|
132
122
|
/>
|
|
@@ -1,14 +1,30 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { ReactElement } from 'react';
|
|
2
|
+
import { useTheme } from '../../../theme';
|
|
2
3
|
import List from '../../List';
|
|
3
4
|
|
|
4
5
|
const Option = ({
|
|
5
6
|
text,
|
|
7
|
+
disabled = false,
|
|
6
8
|
selected,
|
|
7
9
|
onPress,
|
|
8
10
|
}: {
|
|
9
|
-
text: string;
|
|
11
|
+
text: string | ReactElement;
|
|
12
|
+
disabled?: boolean;
|
|
10
13
|
selected: boolean;
|
|
11
14
|
onPress: () => void;
|
|
12
|
-
}) =>
|
|
15
|
+
}) => {
|
|
16
|
+
const theme = useTheme();
|
|
17
|
+
return (
|
|
18
|
+
<List.BasicItem
|
|
19
|
+
selected={selected}
|
|
20
|
+
disabled={disabled}
|
|
21
|
+
onPress={onPress}
|
|
22
|
+
title={text}
|
|
23
|
+
style={{
|
|
24
|
+
marginHorizontal: theme.__hd__.select.space.optionHorizontalMargin,
|
|
25
|
+
}}
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
13
29
|
|
|
14
30
|
export default Option;
|
|
@@ -1,60 +1,68 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { SectionListRenderItemInfo } from 'react-native';
|
|
2
3
|
import { SingleSelectProps } from '.';
|
|
3
|
-
|
|
4
|
-
import StyledOptionList
|
|
4
|
+
import { getScrollParams } from '../helpers';
|
|
5
|
+
import StyledOptionList from '../StyledOptionList';
|
|
6
|
+
import { OptionType, SectionData, SectionType } from '../types';
|
|
5
7
|
import Option from './Option';
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
type OptionListProps<V, T extends OptionType<V>> = Pick<
|
|
10
|
+
SingleSelectProps<V, T>,
|
|
11
|
+
| 'keyExtractor'
|
|
12
|
+
| 'loading'
|
|
13
|
+
| 'onEndReached'
|
|
14
|
+
| 'onQueryChange'
|
|
15
|
+
| 'value'
|
|
16
|
+
| 'renderOption'
|
|
17
|
+
> & {
|
|
18
|
+
onPress: (value: V | null) => void;
|
|
19
|
+
sections: SectionData<V, T>[];
|
|
20
|
+
};
|
|
13
21
|
|
|
14
|
-
const OptionList = <T
|
|
22
|
+
const OptionList = <V, T extends OptionType<V>>({
|
|
15
23
|
keyExtractor,
|
|
16
24
|
loading,
|
|
17
25
|
onEndReached,
|
|
18
26
|
onPress,
|
|
19
27
|
onQueryChange,
|
|
20
|
-
|
|
28
|
+
sections,
|
|
29
|
+
renderOption,
|
|
21
30
|
value,
|
|
22
|
-
}:
|
|
23
|
-
|
|
24
|
-
| 'keyExtractor'
|
|
25
|
-
| 'loading'
|
|
26
|
-
| 'onEndReached'
|
|
27
|
-
| 'onPress'
|
|
28
|
-
| 'onQueryChange'
|
|
29
|
-
| 'options'
|
|
30
|
-
| 'value'
|
|
31
|
-
>) => {
|
|
32
|
-
const rawScrollIndex = options.findIndex(option => option.value === value);
|
|
33
|
-
const scrollIndex = rawScrollIndex - 2 >= 0 ? rawScrollIndex - 2 : 0;
|
|
31
|
+
}: OptionListProps<V, T>) => {
|
|
32
|
+
const scrollParams = getScrollParams(value, sections);
|
|
34
33
|
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
34
|
+
const renderItem = (info: SectionListRenderItemInfo<T, SectionType>) => {
|
|
35
|
+
const { item } = info;
|
|
36
|
+
const selected = item.value === value;
|
|
37
|
+
const onItemPress = () => {
|
|
38
|
+
if (value === item.value) {
|
|
39
|
+
onPress(null);
|
|
40
|
+
} else {
|
|
41
|
+
onPress(item.value);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
return renderOption ? (
|
|
46
|
+
renderOption({ ...info, selected, onPress: onItemPress })
|
|
47
|
+
) : (
|
|
48
|
+
<Option
|
|
49
|
+
selected={selected}
|
|
50
|
+
text={item.text}
|
|
51
|
+
disabled={item.disabled}
|
|
52
|
+
onPress={onItemPress}
|
|
53
|
+
/>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
48
56
|
|
|
49
57
|
return (
|
|
50
|
-
<StyledOptionList
|
|
58
|
+
<StyledOptionList
|
|
51
59
|
keyExtractor={keyExtractor}
|
|
52
60
|
loading={loading}
|
|
53
61
|
onEndReached={onEndReached}
|
|
54
62
|
onQueryChange={onQueryChange}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
63
|
+
sections={sections}
|
|
64
|
+
renderItem={renderItem}
|
|
65
|
+
scrollParams={scrollParams}
|
|
58
66
|
/>
|
|
59
67
|
);
|
|
60
68
|
};
|
|
@@ -3,39 +3,50 @@ import { fireEvent } from '@testing-library/react-native';
|
|
|
3
3
|
import OptionList from '../OptionList';
|
|
4
4
|
import renderWithTheme from '../../../../testHelpers/renderWithTheme';
|
|
5
5
|
|
|
6
|
-
const
|
|
7
|
-
{
|
|
8
|
-
{
|
|
9
|
-
|
|
6
|
+
const sections = [
|
|
7
|
+
{ category: 'A', data: [{ text: 'A1', value: 'a1' }] },
|
|
8
|
+
{
|
|
9
|
+
category: 'B',
|
|
10
|
+
data: [
|
|
11
|
+
{ text: 'B1', value: 'b1' },
|
|
12
|
+
{ text: 'B2', value: 'b2' },
|
|
13
|
+
],
|
|
14
|
+
},
|
|
10
15
|
];
|
|
11
16
|
|
|
12
17
|
describe('OptionList', () => {
|
|
13
18
|
it('renders correctly', () => {
|
|
14
19
|
const pressFn = jest.fn();
|
|
15
|
-
const { toJSON } = renderWithTheme(
|
|
16
|
-
<OptionList value="
|
|
20
|
+
const { toJSON, getByText } = renderWithTheme(
|
|
21
|
+
<OptionList value="a1" sections={sections} onPress={pressFn} />
|
|
17
22
|
);
|
|
23
|
+
|
|
24
|
+
expect(getByText('A')).toBeTruthy();
|
|
25
|
+
expect(getByText('A1')).toBeTruthy();
|
|
26
|
+
expect(getByText('B')).toBeTruthy();
|
|
27
|
+
expect(getByText('B1')).toBeTruthy();
|
|
28
|
+
expect(getByText('B2')).toBeTruthy();
|
|
18
29
|
expect(toJSON()).toMatchSnapshot();
|
|
19
30
|
});
|
|
20
31
|
|
|
21
32
|
it('trigger onPress correctly on select new value', () => {
|
|
22
33
|
const pressFn = jest.fn();
|
|
23
34
|
const { toJSON, getByText } = renderWithTheme(
|
|
24
|
-
<OptionList value="
|
|
35
|
+
<OptionList value="a1" sections={sections} onPress={pressFn} />
|
|
25
36
|
);
|
|
26
37
|
expect(toJSON()).toMatchSnapshot();
|
|
27
|
-
fireEvent.press(getByText('
|
|
38
|
+
fireEvent.press(getByText('B1'));
|
|
28
39
|
expect(pressFn).toBeCalledTimes(1);
|
|
29
|
-
expect(pressFn).toHaveBeenCalledWith('
|
|
40
|
+
expect(pressFn).toHaveBeenCalledWith('b1');
|
|
30
41
|
});
|
|
31
42
|
|
|
32
43
|
it('trigger onPress correctly on unselect current value', () => {
|
|
33
44
|
const pressFn = jest.fn();
|
|
34
45
|
const { toJSON, getByText } = renderWithTheme(
|
|
35
|
-
<OptionList value="
|
|
46
|
+
<OptionList value="a1" sections={sections} onPress={pressFn} />
|
|
36
47
|
);
|
|
37
48
|
expect(toJSON()).toMatchSnapshot();
|
|
38
|
-
fireEvent.press(getByText('
|
|
49
|
+
fireEvent.press(getByText('A1'));
|
|
39
50
|
expect(pressFn).toBeCalledTimes(1);
|
|
40
51
|
expect(pressFn).toHaveBeenCalledWith(null);
|
|
41
52
|
});
|
|
@@ -43,7 +54,7 @@ describe('OptionList', () => {
|
|
|
43
54
|
it('render isLoading correctly', () => {
|
|
44
55
|
const pressFn = jest.fn();
|
|
45
56
|
const { toJSON } = renderWithTheme(
|
|
46
|
-
<OptionList value="
|
|
57
|
+
<OptionList value="a1" sections={sections} onPress={pressFn} loading />
|
|
47
58
|
);
|
|
48
59
|
expect(toJSON()).toMatchSnapshot();
|
|
49
60
|
});
|
|
@@ -20,12 +20,14 @@ exports[`Option renders correctly 1`] = `
|
|
|
20
20
|
Array [
|
|
21
21
|
Object {
|
|
22
22
|
"alignItems": "center",
|
|
23
|
-
"backgroundColor": "#
|
|
23
|
+
"backgroundColor": "#f3e6f6",
|
|
24
24
|
"flexDirection": "row",
|
|
25
25
|
"opacity": 1,
|
|
26
26
|
"padding": 16,
|
|
27
27
|
},
|
|
28
|
-
|
|
28
|
+
Object {
|
|
29
|
+
"marginHorizontal": 12,
|
|
30
|
+
},
|
|
29
31
|
]
|
|
30
32
|
}
|
|
31
33
|
>
|
|
@@ -43,7 +45,7 @@ exports[`Option renders correctly 1`] = `
|
|
|
43
45
|
style={
|
|
44
46
|
Array [
|
|
45
47
|
Object {
|
|
46
|
-
"color": "#
|
|
48
|
+
"color": "#001f23",
|
|
47
49
|
"fontFamily": "BeVietnamPro-Regular",
|
|
48
50
|
"fontSize": 16,
|
|
49
51
|
"letterSpacing": 0.48,
|