@hero-design/rn 7.16.1 → 7.17.0
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 +228 -187
- package/lib/index.js +227 -186
- package/package.json +2 -2
- package/src/components/DatePicker/DatePickerIOS.tsx +2 -2
- package/src/components/List/BasicListItem.tsx +8 -4
- package/src/components/List/StyledBasicListItem.tsx +2 -2
- package/src/components/Select/MultiSelect/Option.tsx +21 -15
- 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 +41 -21
- package/src/components/Select/MultiSelect/__tests__/__snapshots__/OptionList.spec.tsx.snap +1995 -352
- package/src/components/Select/MultiSelect/__tests__/__snapshots__/index.spec.tsx.snap +5611 -523
- 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 +20 -13
- 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 +23 -14
- package/src/components/Select/SingleSelect/__tests__/__snapshots__/OptionList.spec.tsx.snap +1846 -258
- package/src/components/Select/SingleSelect/__tests__/__snapshots__/index.spec.tsx.snap +5140 -412
- 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 +6 -15
- package/src/components/Select/__tests__/StyledSelect.spec.tsx +1 -23
- package/src/components/Select/__tests__/__snapshots__/StyledSelect.spec.tsx.snap +0 -67
- 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/TextInput/__tests__/__snapshots__/index.spec.tsx.snap +157 -1
- package/src/components/TextInput/__tests__/index.spec.tsx +29 -8
- package/src/components/TextInput/index.tsx +18 -7
- package/src/components/TimePicker/TimePickerIOS.tsx +2 -2
- package/src/theme/__tests__/__snapshots__/index.spec.ts.snap +3 -5
- package/src/theme/components/select.ts +3 -5
- package/src/types.ts +7 -1
- package/types/components/List/BasicListItem.d.ts +1 -1
- 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 +5 -7
- 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/select.d.ts +3 -5
- 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
|
};
|
|
@@ -1,22 +1,13 @@
|
|
|
1
|
-
import { View
|
|
1
|
+
import { View } from 'react-native';
|
|
2
2
|
import styled from '@emotion/native';
|
|
3
3
|
import Typography from '../Typography';
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
}>(({ theme, themeSelected }) => ({
|
|
8
|
-
flexDirection: 'row',
|
|
9
|
-
justifyContent: 'space-between',
|
|
10
|
-
alignItems: 'center',
|
|
11
|
-
borderRadius: theme.__hd__.select.radii.option,
|
|
12
|
-
padding: theme.__hd__.select.space.optionPadding,
|
|
13
|
-
backgroundColor: themeSelected
|
|
14
|
-
? theme.__hd__.select.colors.checkedOption
|
|
15
|
-
: theme.__hd__.select.colors.option,
|
|
5
|
+
const SectionSpacer = styled(View)(({ theme }) => ({
|
|
6
|
+
marginTop: theme.__hd__.select.space.sectionSpacing,
|
|
16
7
|
}));
|
|
17
8
|
|
|
18
|
-
const
|
|
19
|
-
marginTop: theme.__hd__.select.space.
|
|
9
|
+
const OptionSpacer = styled(View)(({ theme }) => ({
|
|
10
|
+
marginTop: theme.__hd__.select.space.optionSpacing,
|
|
20
11
|
}));
|
|
21
12
|
|
|
22
13
|
const FooterText = styled(Typography.Text)(({ theme }) => ({
|
|
@@ -29,4 +20,4 @@ const StyledSearchBar = styled(View)(({ theme }) => ({
|
|
|
29
20
|
paddingBottom: theme.__hd__.select.space.searchBarBottomSpacing,
|
|
30
21
|
}));
|
|
31
22
|
|
|
32
|
-
export {
|
|
23
|
+
export { SectionSpacer, OptionSpacer, FooterText, StyledSearchBar };
|
|
@@ -1,28 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import renderWithTheme from '../../../testHelpers/renderWithTheme';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
describe('OptionWrapper', () => {
|
|
6
|
-
it.each`
|
|
7
|
-
themeSelected
|
|
8
|
-
${true}
|
|
9
|
-
${false}
|
|
10
|
-
`('has selected style: $themeSelected', ({ themeSelected }) => {
|
|
11
|
-
const { toJSON } = renderWithTheme(
|
|
12
|
-
<OptionWrapper themeSelected={themeSelected} />
|
|
13
|
-
);
|
|
14
|
-
|
|
15
|
-
expect(toJSON()).toMatchSnapshot();
|
|
16
|
-
});
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
describe('Spacer', () => {
|
|
20
|
-
it('has correct style', () => {
|
|
21
|
-
const { toJSON } = renderWithTheme(<Spacer />);
|
|
22
|
-
|
|
23
|
-
expect(toJSON()).toMatchSnapshot();
|
|
24
|
-
});
|
|
25
|
-
});
|
|
3
|
+
import { FooterText } from '../StyledSelect';
|
|
26
4
|
|
|
27
5
|
describe('FooterText', () => {
|
|
28
6
|
it('has correct style', () => {
|
|
@@ -26,70 +26,3 @@ exports[`FooterText has correct style 1`] = `
|
|
|
26
26
|
Confirm
|
|
27
27
|
</Text>
|
|
28
28
|
`;
|
|
29
|
-
|
|
30
|
-
exports[`OptionWrapper has selected style: false 1`] = `
|
|
31
|
-
<View
|
|
32
|
-
accessible={true}
|
|
33
|
-
collapsable={false}
|
|
34
|
-
focusable={false}
|
|
35
|
-
nativeID="animatedComponent"
|
|
36
|
-
onClick={[Function]}
|
|
37
|
-
onResponderGrant={[Function]}
|
|
38
|
-
onResponderMove={[Function]}
|
|
39
|
-
onResponderRelease={[Function]}
|
|
40
|
-
onResponderTerminate={[Function]}
|
|
41
|
-
onResponderTerminationRequest={[Function]}
|
|
42
|
-
onStartShouldSetResponder={[Function]}
|
|
43
|
-
style={
|
|
44
|
-
Object {
|
|
45
|
-
"alignItems": "center",
|
|
46
|
-
"backgroundColor": "#ffffff",
|
|
47
|
-
"borderRadius": 4,
|
|
48
|
-
"flexDirection": "row",
|
|
49
|
-
"justifyContent": "space-between",
|
|
50
|
-
"opacity": 1,
|
|
51
|
-
"padding": 16,
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
/>
|
|
55
|
-
`;
|
|
56
|
-
|
|
57
|
-
exports[`OptionWrapper has selected style: true 1`] = `
|
|
58
|
-
<View
|
|
59
|
-
accessible={true}
|
|
60
|
-
collapsable={false}
|
|
61
|
-
focusable={false}
|
|
62
|
-
nativeID="animatedComponent"
|
|
63
|
-
onClick={[Function]}
|
|
64
|
-
onResponderGrant={[Function]}
|
|
65
|
-
onResponderMove={[Function]}
|
|
66
|
-
onResponderRelease={[Function]}
|
|
67
|
-
onResponderTerminate={[Function]}
|
|
68
|
-
onResponderTerminationRequest={[Function]}
|
|
69
|
-
onStartShouldSetResponder={[Function]}
|
|
70
|
-
style={
|
|
71
|
-
Object {
|
|
72
|
-
"alignItems": "center",
|
|
73
|
-
"backgroundColor": "#f1e9fb",
|
|
74
|
-
"borderRadius": 4,
|
|
75
|
-
"flexDirection": "row",
|
|
76
|
-
"justifyContent": "space-between",
|
|
77
|
-
"opacity": 1,
|
|
78
|
-
"padding": 16,
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
/>
|
|
82
|
-
`;
|
|
83
|
-
|
|
84
|
-
exports[`Spacer has correct style 1`] = `
|
|
85
|
-
<View
|
|
86
|
-
style={
|
|
87
|
-
Array [
|
|
88
|
-
Object {
|
|
89
|
-
"marginTop": 4,
|
|
90
|
-
},
|
|
91
|
-
undefined,
|
|
92
|
-
]
|
|
93
|
-
}
|
|
94
|
-
/>
|
|
95
|
-
`;
|
|
@@ -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
|
+
};
|