@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 SingleSelect 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,64 @@ 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 renderSingleOption = ({
|
|
54
|
+
item,
|
|
55
|
+
selected,
|
|
56
|
+
onPress,
|
|
57
|
+
}: ListRenderOptionInfo<string, CustomOptionType>) => (
|
|
58
|
+
<List.BasicItem
|
|
59
|
+
selected={selected}
|
|
60
|
+
onPress={onPress}
|
|
61
|
+
title={
|
|
62
|
+
<>
|
|
63
|
+
<Typography.Text fontSize="large" fontWeight="semi-bold">
|
|
64
|
+
{item.text}
|
|
65
|
+
</Typography.Text>
|
|
66
|
+
<Typography.Text fontSize="large" intent="subdued">
|
|
67
|
+
{item.role}
|
|
68
|
+
</Typography.Text>
|
|
69
|
+
</>
|
|
70
|
+
}
|
|
71
|
+
/>
|
|
72
|
+
);
|
|
73
|
+
|
|
16
74
|
describe('rendering', () => {
|
|
17
75
|
it('renders correctly when bottom sheet is NOT visible', () => {
|
|
18
76
|
const { queryAllByText, toJSON, getByTestId } = renderWithTheme(
|
|
@@ -50,6 +108,64 @@ describe('rendering', () => {
|
|
|
50
108
|
expect(getByText('Saturday')).toBeDefined();
|
|
51
109
|
expect(getByText('Sunday')).toBeDefined();
|
|
52
110
|
});
|
|
111
|
+
|
|
112
|
+
it('renders correctly when input is loading', () => {
|
|
113
|
+
const { toJSON, getByTestId } = renderWithTheme(
|
|
114
|
+
<SingleSelect
|
|
115
|
+
label="Allow notifications"
|
|
116
|
+
options={options}
|
|
117
|
+
value="mon"
|
|
118
|
+
inputProps={{ loading: true }}
|
|
119
|
+
onConfirm={jest.fn()}
|
|
120
|
+
/>
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
expect(toJSON()).toMatchSnapshot();
|
|
124
|
+
expect(getByTestId('input-suffix')).toHaveProp('name', 'loading');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('renders correctly when receives sections', () => {
|
|
128
|
+
const { queryAllByText, getByText, toJSON, getByTestId } = renderWithTheme(
|
|
129
|
+
<SingleSelect
|
|
130
|
+
label="Allow notifications"
|
|
131
|
+
options={sections}
|
|
132
|
+
value="a1"
|
|
133
|
+
onConfirm={jest.fn()}
|
|
134
|
+
/>
|
|
135
|
+
);
|
|
136
|
+
fireEvent.press(getByTestId('text-input'));
|
|
137
|
+
|
|
138
|
+
expect(toJSON()).toMatchSnapshot();
|
|
139
|
+
expect(queryAllByText('Allow notifications')).toHaveLength(2);
|
|
140
|
+
expect(getByText('A')).toBeTruthy();
|
|
141
|
+
expect(getByText('A1')).toBeTruthy();
|
|
142
|
+
expect(getByText('B')).toBeTruthy();
|
|
143
|
+
expect(getByText('B1')).toBeTruthy();
|
|
144
|
+
expect(getByText('B2')).toBeTruthy();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('allows custom renderer', () => {
|
|
148
|
+
const { getByText, toJSON, getByTestId } = renderWithTheme(
|
|
149
|
+
<SingleSelect<string, CustomOptionType>
|
|
150
|
+
label="Choose collaborators"
|
|
151
|
+
options={collaboratorSections}
|
|
152
|
+
renderOption={renderSingleOption}
|
|
153
|
+
value=""
|
|
154
|
+
onConfirm={jest.fn()}
|
|
155
|
+
/>
|
|
156
|
+
);
|
|
157
|
+
fireEvent.press(getByTestId('text-input'));
|
|
158
|
+
|
|
159
|
+
expect(toJSON()).toMatchSnapshot();
|
|
160
|
+
expect(getByText('Daniel')).toBeTruthy();
|
|
161
|
+
expect(getByText('Senior Developer')).toBeTruthy();
|
|
162
|
+
expect(getByText('Daemon')).toBeTruthy();
|
|
163
|
+
expect(getByText('Manager')).toBeTruthy();
|
|
164
|
+
expect(getByText('Jennifer')).toBeTruthy();
|
|
165
|
+
expect(getByText('UX Designer')).toBeTruthy();
|
|
166
|
+
expect(getByText('Josh')).toBeTruthy();
|
|
167
|
+
expect(getByText('Junior Developer')).toBeTruthy();
|
|
168
|
+
});
|
|
53
169
|
});
|
|
54
170
|
|
|
55
171
|
describe('behavior', () => {
|
|
@@ -1,31 +1,35 @@
|
|
|
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 OptionList from './OptionList';
|
|
7
7
|
import TextInput from '../../TextInput';
|
|
8
8
|
import { StyledSearchBar } from '../StyledSelect';
|
|
9
|
+
import { toFlatOptions, toSections, useKeyboard } from '../helpers';
|
|
9
10
|
|
|
10
|
-
export interface SingleSelectProps<T
|
|
11
|
+
export interface SingleSelectProps<V, T extends OptionType<V> = OptionType<V>>
|
|
12
|
+
extends SelectProps<V, T> {
|
|
11
13
|
/**
|
|
12
14
|
* Current selected value.
|
|
13
15
|
*/
|
|
14
|
-
value:
|
|
16
|
+
value: V | null;
|
|
15
17
|
/**
|
|
16
18
|
* on select event handler
|
|
17
19
|
*/
|
|
18
|
-
onConfirm: (value:
|
|
20
|
+
onConfirm: (value: V | null) => void;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
|
-
const SingleSelect = <T
|
|
23
|
+
const SingleSelect = <V, T extends OptionType<V>>({
|
|
22
24
|
label,
|
|
23
|
-
loading,
|
|
25
|
+
loading = false,
|
|
26
|
+
inputProps,
|
|
24
27
|
onConfirm,
|
|
25
28
|
onDimiss,
|
|
26
29
|
onEndReached,
|
|
27
30
|
onQueryChange,
|
|
28
31
|
options,
|
|
32
|
+
renderOption,
|
|
29
33
|
query,
|
|
30
34
|
error,
|
|
31
35
|
editable = true,
|
|
@@ -34,38 +38,21 @@ const SingleSelect = <T,>({
|
|
|
34
38
|
style,
|
|
35
39
|
testID,
|
|
36
40
|
value,
|
|
37
|
-
}: SingleSelectProps<T>) => {
|
|
41
|
+
}: SingleSelectProps<V, T>) => {
|
|
42
|
+
const { isKeyboardVisible, keyboardHeight } = useKeyboard();
|
|
38
43
|
const [open, setOpen] = useState(false);
|
|
39
|
-
const [selectingValue, setSelectingValue] = useState<
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
const [keyboardHeight, setKeyboardHeight] = useState(0);
|
|
44
|
-
|
|
45
|
-
useEffect(() => {
|
|
46
|
-
const keyboardDidShowListener = Keyboard.addListener(
|
|
47
|
-
'keyboardWillShow',
|
|
48
|
-
(e: KeyboardEvent) => {
|
|
49
|
-
setKeyboardVisible(true);
|
|
50
|
-
setKeyboardHeight(e.endCoordinates.height);
|
|
51
|
-
}
|
|
52
|
-
);
|
|
53
|
-
const keyboardDidHideListener = Keyboard.addListener(
|
|
54
|
-
'keyboardWillHide',
|
|
55
|
-
() => {
|
|
56
|
-
setKeyboardVisible(false);
|
|
57
|
-
}
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
return () => {
|
|
61
|
-
keyboardDidHideListener.remove();
|
|
62
|
-
keyboardDidShowListener.remove();
|
|
63
|
-
};
|
|
64
|
-
}, []);
|
|
44
|
+
const [selectingValue, setSelectingValue] = useState<V | null>(value);
|
|
45
|
+
const sections = toSections(options);
|
|
46
|
+
const flatOptions = toFlatOptions(options);
|
|
47
|
+
const displayedValue = flatOptions.find(opt => value === opt.value)?.text;
|
|
65
48
|
|
|
66
49
|
return (
|
|
67
50
|
<>
|
|
68
|
-
<View
|
|
51
|
+
<View
|
|
52
|
+
pointerEvents={
|
|
53
|
+
!editable || disabled || inputProps?.loading ? 'none' : 'auto'
|
|
54
|
+
}
|
|
55
|
+
>
|
|
69
56
|
<TouchableOpacity onPress={() => setOpen(true)}>
|
|
70
57
|
<TextInput
|
|
71
58
|
label={label}
|
|
@@ -75,6 +62,7 @@ const SingleSelect = <T,>({
|
|
|
75
62
|
error={error}
|
|
76
63
|
editable={editable}
|
|
77
64
|
disabled={disabled}
|
|
65
|
+
loading={inputProps?.loading}
|
|
78
66
|
numberOfLines={numberOfLines}
|
|
79
67
|
pointerEvents="none"
|
|
80
68
|
style={style}
|
|
@@ -109,7 +97,8 @@ const SingleSelect = <T,>({
|
|
|
109
97
|
onQueryChange={onQueryChange}
|
|
110
98
|
onEndReached={onEndReached}
|
|
111
99
|
loading={loading}
|
|
112
|
-
|
|
100
|
+
sections={sections}
|
|
101
|
+
renderOption={renderOption}
|
|
113
102
|
value={selectingValue}
|
|
114
103
|
onPress={selectedValue => {
|
|
115
104
|
setOpen(false);
|
|
@@ -1,63 +1,54 @@
|
|
|
1
1
|
import { useTheme } from '@emotion/react';
|
|
2
|
-
import React, { useRef, useState } from 'react';
|
|
3
|
-
import {
|
|
2
|
+
import React, { ReactElement, useRef, useState } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
Dimensions,
|
|
5
|
+
SectionList,
|
|
6
|
+
SectionListRenderItemInfo,
|
|
7
|
+
View,
|
|
8
|
+
} from 'react-native';
|
|
9
|
+
import SectionHeading from '../SectionHeading';
|
|
4
10
|
import Spinner from '../Spinner';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
11
|
+
import { ScrollParams } from './helpers';
|
|
12
|
+
import { OptionSpacer, SectionSpacer } from './StyledSelect';
|
|
13
|
+
import { SectionData, OptionType, SelectProps, SectionType } from './types';
|
|
7
14
|
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
RenderItem: React.FC<RenderItemProps<T>>;
|
|
17
|
-
/**
|
|
18
|
-
* Selected scroll index
|
|
19
|
-
*/
|
|
20
|
-
scrollIndex?: number;
|
|
21
|
-
}
|
|
15
|
+
export type StyledOptionListProps<V, T extends OptionType<V>> = Pick<
|
|
16
|
+
SelectProps<V, T>,
|
|
17
|
+
'keyExtractor' | 'loading' | 'onEndReached' | 'onQueryChange'
|
|
18
|
+
> & {
|
|
19
|
+
scrollParams: ScrollParams;
|
|
20
|
+
sections: SectionData<V, T>[];
|
|
21
|
+
renderItem: (info: SectionListRenderItemInfo<T, SectionType>) => ReactElement;
|
|
22
|
+
};
|
|
22
23
|
|
|
23
|
-
const StyledOptionList = <T
|
|
24
|
+
const StyledOptionList = <V, T extends OptionType<V>>({
|
|
24
25
|
keyExtractor,
|
|
25
26
|
loading,
|
|
26
27
|
onEndReached,
|
|
27
28
|
onQueryChange,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}:
|
|
32
|
-
OptionListProps<T>,
|
|
33
|
-
| 'scrollIndex'
|
|
34
|
-
| 'keyExtractor'
|
|
35
|
-
| 'loading'
|
|
36
|
-
| 'onEndReached'
|
|
37
|
-
| 'options'
|
|
38
|
-
| 'RenderItem'
|
|
39
|
-
| 'onQueryChange'
|
|
40
|
-
>) => {
|
|
29
|
+
sections,
|
|
30
|
+
renderItem,
|
|
31
|
+
scrollParams,
|
|
32
|
+
}: StyledOptionListProps<V, T>) => {
|
|
41
33
|
const theme = useTheme();
|
|
42
|
-
const
|
|
34
|
+
const sectionListRef = useRef<SectionList<T, SectionType>>(null);
|
|
43
35
|
|
|
44
36
|
const [onEndReachedCalled, setOnEndReachedCalled] = useState(false);
|
|
45
37
|
|
|
46
38
|
return (
|
|
47
|
-
<
|
|
48
|
-
ref={
|
|
39
|
+
<SectionList<T, SectionType>
|
|
40
|
+
ref={sectionListRef}
|
|
49
41
|
style={{
|
|
50
|
-
paddingHorizontal: theme.__hd__.select.space.optionListPadding,
|
|
51
42
|
...(onQueryChange ? { height: Dimensions.get('screen').height } : {}),
|
|
52
43
|
}}
|
|
53
|
-
|
|
44
|
+
sections={sections}
|
|
54
45
|
keyExtractor={keyExtractor}
|
|
55
46
|
onEndReachedThreshold={0.1}
|
|
56
47
|
onEndReached={() => setOnEndReachedCalled(true)}
|
|
57
48
|
onScrollToIndexFailed={() => {}}
|
|
58
49
|
onContentSizeChange={() =>
|
|
59
|
-
|
|
60
|
-
|
|
50
|
+
sections.length &&
|
|
51
|
+
sectionListRef.current?.scrollToLocation(scrollParams)
|
|
61
52
|
}
|
|
62
53
|
onMomentumScrollBegin={() => {
|
|
63
54
|
if (onEndReached && onEndReachedCalled && !loading) onEndReached();
|
|
@@ -76,11 +67,19 @@ const StyledOptionList = <T,>({
|
|
|
76
67
|
</View>
|
|
77
68
|
) : null
|
|
78
69
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
<
|
|
82
|
-
|
|
83
|
-
|
|
70
|
+
renderSectionHeader={({ section: { category } }) =>
|
|
71
|
+
category !== '' ? (
|
|
72
|
+
<SectionHeading
|
|
73
|
+
text={category}
|
|
74
|
+
style={{
|
|
75
|
+
marginBottom: 0,
|
|
76
|
+
}}
|
|
77
|
+
/>
|
|
78
|
+
) : null
|
|
79
|
+
}
|
|
80
|
+
renderItem={renderItem}
|
|
81
|
+
ItemSeparatorComponent={OptionSpacer}
|
|
82
|
+
SectionSeparatorComponent={SectionSpacer}
|
|
84
83
|
/>
|
|
85
84
|
);
|
|
86
85
|
};
|
|
@@ -2,8 +2,12 @@ import { View } from 'react-native';
|
|
|
2
2
|
import styled from '@emotion/native';
|
|
3
3
|
import Typography from '../Typography';
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
marginTop: theme.__hd__.select.space.
|
|
5
|
+
const SectionSpacer = styled(View)(({ theme }) => ({
|
|
6
|
+
marginTop: theme.__hd__.select.space.sectionSpacing,
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
const OptionSpacer = styled(View)(({ theme }) => ({
|
|
10
|
+
marginTop: theme.__hd__.select.space.optionSpacing,
|
|
7
11
|
}));
|
|
8
12
|
|
|
9
13
|
const FooterText = styled(Typography.Text)(({ theme }) => ({
|
|
@@ -16,4 +20,4 @@ const StyledSearchBar = styled(View)(({ theme }) => ({
|
|
|
16
20
|
paddingBottom: theme.__hd__.select.space.searchBarBottomSpacing,
|
|
17
21
|
}));
|
|
18
22
|
|
|
19
|
-
export {
|
|
23
|
+
export { SectionSpacer, OptionSpacer, FooterText, StyledSearchBar };
|
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import renderWithTheme from '../../../testHelpers/renderWithTheme';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
describe('Spacer', () => {
|
|
6
|
-
it('has correct style', () => {
|
|
7
|
-
const { toJSON } = renderWithTheme(<Spacer />);
|
|
8
|
-
|
|
9
|
-
expect(toJSON()).toMatchSnapshot();
|
|
10
|
-
});
|
|
11
|
-
});
|
|
3
|
+
import { FooterText } from '../StyledSelect';
|
|
12
4
|
|
|
13
5
|
describe('FooterText', () => {
|
|
14
6
|
it('has correct style', () => {
|
|
@@ -5,7 +5,7 @@ exports[`FooterText has correct style 1`] = `
|
|
|
5
5
|
style={
|
|
6
6
|
Array [
|
|
7
7
|
Object {
|
|
8
|
-
"color": "#
|
|
8
|
+
"color": "#001f23",
|
|
9
9
|
"fontFamily": "BeVietnamPro-Regular",
|
|
10
10
|
"fontSize": 14,
|
|
11
11
|
"letterSpacing": 0.42,
|
|
@@ -13,7 +13,7 @@ exports[`FooterText has correct style 1`] = `
|
|
|
13
13
|
},
|
|
14
14
|
Array [
|
|
15
15
|
Object {
|
|
16
|
-
"color": "#
|
|
16
|
+
"color": "#8505a2",
|
|
17
17
|
},
|
|
18
18
|
undefined,
|
|
19
19
|
],
|
|
@@ -26,16 +26,3 @@ exports[`FooterText has correct style 1`] = `
|
|
|
26
26
|
Confirm
|
|
27
27
|
</Text>
|
|
28
28
|
`;
|
|
29
|
-
|
|
30
|
-
exports[`Spacer has correct style 1`] = `
|
|
31
|
-
<View
|
|
32
|
-
style={
|
|
33
|
-
Array [
|
|
34
|
-
Object {
|
|
35
|
-
"marginTop": 4,
|
|
36
|
-
},
|
|
37
|
-
undefined,
|
|
38
|
-
]
|
|
39
|
-
}
|
|
40
|
-
/>
|
|
41
|
-
`;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getScrollParams,
|
|
3
|
+
isSections,
|
|
4
|
+
toFlatOptions,
|
|
5
|
+
toSections,
|
|
6
|
+
} from '../helpers';
|
|
7
|
+
|
|
8
|
+
const sections = [
|
|
9
|
+
{ category: 'A', data: [{ text: 'A1', value: 'a1' }] },
|
|
10
|
+
{
|
|
11
|
+
category: 'B',
|
|
12
|
+
data: [
|
|
13
|
+
{ text: 'B1', value: 'b1' },
|
|
14
|
+
{ text: 'B2', value: 'b2' },
|
|
15
|
+
],
|
|
16
|
+
},
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
const flatOptions = [
|
|
20
|
+
{ text: 'A', value: 'a' },
|
|
21
|
+
{ text: 'B', value: 'b' },
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
describe('isSections', () => {
|
|
25
|
+
it('returns true if receives sections', () => {
|
|
26
|
+
expect(isSections(sections)).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('returns false if receives flat options', () => {
|
|
30
|
+
expect(isSections(flatOptions)).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('toSections', () => {
|
|
35
|
+
it('converts flat options to sections', () => {
|
|
36
|
+
expect(toSections(flatOptions)).toStrictEqual([
|
|
37
|
+
{ category: '', data: flatOptions },
|
|
38
|
+
]);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('does not convert sections', () => {
|
|
42
|
+
expect(toSections(sections)).toStrictEqual(sections);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('toFlatOptions', () => {
|
|
47
|
+
it('converts sections to flat options', () => {
|
|
48
|
+
expect(toFlatOptions(sections)).toStrictEqual([
|
|
49
|
+
{ text: 'A1', value: 'a1' },
|
|
50
|
+
{ text: 'B1', value: 'b1' },
|
|
51
|
+
{ text: 'B2', value: 'b2' },
|
|
52
|
+
]);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('does not convert flat options', () => {
|
|
56
|
+
expect(toFlatOptions(flatOptions)).toStrictEqual(flatOptions);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('getScrollParams', () => {
|
|
61
|
+
it('returns correctly when found value in sections', () => {
|
|
62
|
+
expect(getScrollParams('b2', sections)).toStrictEqual({
|
|
63
|
+
sectionIndex: 1,
|
|
64
|
+
itemIndex: 1,
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('returns 0s when not found value in sections', () => {
|
|
69
|
+
expect(getScrollParams('c', sections)).toStrictEqual({
|
|
70
|
+
sectionIndex: 0,
|
|
71
|
+
itemIndex: 0,
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { Keyboard } from 'react-native';
|
|
3
|
+
import { SectionData, CombinedOptionsType, OptionType } from './types';
|
|
2
4
|
|
|
3
|
-
export const getKey = <T
|
|
4
|
-
option:
|
|
5
|
+
export const getKey = <V, T extends OptionType<V>>(
|
|
6
|
+
option: T,
|
|
5
7
|
index: number,
|
|
6
|
-
keyExtractor?: (opt:
|
|
8
|
+
keyExtractor?: (opt: T, i?: number) => string
|
|
7
9
|
) => {
|
|
8
10
|
let key: React.Key = '';
|
|
9
11
|
if (keyExtractor !== undefined) {
|
|
@@ -16,3 +18,84 @@ export const getKey = <T,>(
|
|
|
16
18
|
|
|
17
19
|
return key;
|
|
18
20
|
};
|
|
21
|
+
|
|
22
|
+
export const isSections = <V, T extends OptionType<V>>(
|
|
23
|
+
options: CombinedOptionsType<V, T>
|
|
24
|
+
): options is SectionData<V, T>[] => {
|
|
25
|
+
const firstOption = options[0];
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
firstOption !== undefined &&
|
|
29
|
+
(firstOption as SectionData<V, T>).category !== undefined
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const toSections = <V, T extends OptionType<V>>(
|
|
34
|
+
options: CombinedOptionsType<V, T>
|
|
35
|
+
): SectionData<V, T>[] => {
|
|
36
|
+
if (isSections(options)) {
|
|
37
|
+
return options;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return [{ category: '', data: options }];
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const toFlatOptions = <V, T extends OptionType<V>>(
|
|
44
|
+
options: CombinedOptionsType<V, T>
|
|
45
|
+
): OptionType<V>[] => {
|
|
46
|
+
if (isSections(options)) {
|
|
47
|
+
return options.flatMap(opt => opt.data);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return options;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export type ScrollParams = {
|
|
54
|
+
itemIndex: number;
|
|
55
|
+
sectionIndex: number;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const getScrollParams = <V, T extends OptionType<V>>(
|
|
59
|
+
value: V,
|
|
60
|
+
sections: SectionData<V, T>[]
|
|
61
|
+
): ScrollParams => {
|
|
62
|
+
let itemIndex = -1;
|
|
63
|
+
|
|
64
|
+
const sectionIndex = sections.findIndex(section => {
|
|
65
|
+
itemIndex = section.data.findIndex(opt => opt.value === value);
|
|
66
|
+
return itemIndex !== -1;
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
sectionIndex: sectionIndex < 0 ? 0 : sectionIndex,
|
|
71
|
+
itemIndex: itemIndex < 0 ? 0 : itemIndex,
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const useKeyboard = () => {
|
|
76
|
+
const [isKeyboardVisible, setKeyboardVisible] = useState(false);
|
|
77
|
+
const [keyboardHeight, setKeyboardHeight] = useState(0);
|
|
78
|
+
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
const keyboardWillShowListener = Keyboard.addListener(
|
|
81
|
+
'keyboardWillShow',
|
|
82
|
+
e => {
|
|
83
|
+
setKeyboardVisible(true);
|
|
84
|
+
setKeyboardHeight(e.endCoordinates.height);
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
const keyboardWillHideListener = Keyboard.addListener(
|
|
88
|
+
'keyboardWillHide',
|
|
89
|
+
() => {
|
|
90
|
+
setKeyboardVisible(false);
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
return () => {
|
|
95
|
+
keyboardWillShowListener.remove();
|
|
96
|
+
keyboardWillHideListener.remove();
|
|
97
|
+
};
|
|
98
|
+
}, []);
|
|
99
|
+
|
|
100
|
+
return { isKeyboardVisible, keyboardHeight };
|
|
101
|
+
};
|