@hero-design/rn 7.10.2 → 7.12.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 +8 -8
- package/assets/fonts/hero-icons.ttf +0 -0
- package/es/index.js +3778 -728
- package/global-setup.js +3 -0
- package/jest.config.js +1 -0
- package/lib/assets/fonts/hero-icons.ttf +0 -0
- package/lib/index.js +3779 -726
- package/package.json +7 -3
- package/rollup.config.js +8 -1
- package/src/components/ContentNavigator/__tests__/__snapshots__/index.spec.tsx.snap +2 -0
- package/src/components/ContentNavigator/__tests__/index.spec.tsx +19 -2
- package/src/components/ContentNavigator/index.tsx +12 -1
- package/src/components/FAB/ActionGroup/__tests__/__snapshots__/index.spec.tsx.snap +4 -0
- package/src/components/FAB/ActionGroup/index.tsx +16 -5
- package/src/components/Icon/HeroIcon/selection.json +1 -1
- package/src/components/Icon/IconList.ts +1 -0
- package/src/components/PinInput/PinCell.tsx +34 -0
- package/src/components/PinInput/StyledPinInput.tsx +88 -0
- package/src/components/PinInput/__tests__/PinCell.spec.tsx +48 -0
- package/src/components/PinInput/__tests__/StyledPinInput.spec.tsx +22 -0
- package/src/components/PinInput/__tests__/__snapshots__/PinCell.spec.tsx.snap +186 -0
- package/src/components/PinInput/__tests__/__snapshots__/StyledPinInput.spec.tsx.snap +58 -0
- package/src/components/PinInput/__tests__/__snapshots__/index.spec.tsx.snap +1028 -0
- package/src/components/PinInput/__tests__/index.spec.tsx +91 -0
- package/src/components/PinInput/index.tsx +173 -0
- package/src/components/Select/MultiSelect/Option.tsx +1 -1
- package/src/components/Select/MultiSelect/OptionList.tsx +48 -26
- package/src/components/Select/MultiSelect/__tests__/OptionList.spec.tsx +13 -0
- package/src/components/Select/MultiSelect/__tests__/__snapshots__/OptionList.spec.tsx.snap +1062 -556
- package/src/components/Select/MultiSelect/__tests__/__snapshots__/index.spec.tsx.snap +983 -889
- package/src/components/Select/MultiSelect/index.tsx +59 -31
- package/src/components/Select/SingleSelect/OptionList.tsx +45 -26
- package/src/components/Select/SingleSelect/__tests__/OptionList.spec.tsx +8 -0
- package/src/components/Select/SingleSelect/__tests__/__snapshots__/OptionList.spec.tsx.snap +992 -500
- package/src/components/Select/SingleSelect/__tests__/__snapshots__/index.spec.tsx.snap +880 -786
- package/src/components/Select/SingleSelect/index.tsx +60 -31
- package/src/components/Select/StyledOptionList.tsx +88 -0
- package/src/components/Select/StyledSelect.tsx +18 -16
- package/src/components/Select/__tests__/StyledSelect.spec.tsx +1 -14
- package/src/components/Select/__tests__/__snapshots__/StyledSelect.spec.tsx.snap +0 -13
- package/src/components/Select/types.tsx +47 -0
- package/src/components/TextInput/__tests__/index.spec.tsx +15 -0
- package/src/components/TextInput/index.tsx +20 -16
- package/src/components/TimePicker/StyledTimePicker.tsx +8 -0
- package/src/components/TimePicker/TimePickerAndroid.tsx +61 -0
- package/src/components/TimePicker/TimePickerIOS.tsx +91 -0
- package/src/components/TimePicker/__tests__/TimePicker.spec.tsx +34 -0
- package/src/components/TimePicker/__tests__/TimePickerAndroid.spec.tsx +39 -0
- package/src/components/TimePicker/__tests__/TimePickerIOS.spec.tsx +46 -0
- package/src/components/TimePicker/__tests__/__snapshots__/TimePickerAndroid.spec.tsx.snap +200 -0
- package/src/components/TimePicker/__tests__/__snapshots__/TimePickerIOS.spec.tsx.snap +513 -0
- package/src/components/TimePicker/index.tsx +15 -0
- package/src/components/TimePicker/types.ts +50 -0
- package/src/components/Typography/Text/StyledText.tsx +1 -1
- package/src/components/Typography/Text/__tests__/StyledText.spec.tsx +1 -0
- package/src/components/Typography/Text/__tests__/__snapshots__/StyledText.spec.tsx.snap +22 -0
- package/src/components/Typography/Text/index.tsx +1 -1
- package/src/index.ts +4 -0
- package/src/theme/__tests__/__snapshots__/index.spec.ts.snap +44 -0
- package/src/theme/components/pinInput.ts +45 -0
- package/src/theme/components/select.ts +4 -0
- package/src/theme/components/timePicker.ts +11 -0
- package/src/theme/components/typography.ts +2 -0
- package/src/theme/global/colors.ts +1 -1
- package/src/theme/global/space.ts +10 -10
- package/src/theme/index.ts +9 -3
- package/testUtils/setup.tsx +10 -0
- package/types/components/ContentNavigator/index.d.ts +5 -1
- package/types/components/Icon/IconList.d.ts +1 -1
- package/types/components/Icon/utils.d.ts +1 -1
- package/types/components/PinInput/PinCell.d.ts +8 -0
- package/types/components/PinInput/StyledPinInput.d.ts +73 -0
- package/types/components/PinInput/__tests__/PinCell.spec.d.ts +1 -0
- package/types/components/PinInput/__tests__/StyledPinInput.spec.d.ts +1 -0
- package/types/components/PinInput/__tests__/index.spec.d.ts +1 -0
- package/types/components/PinInput/index.d.ts +48 -0
- package/types/components/Select/MultiSelect/OptionList.d.ts +1 -1
- package/types/components/Select/MultiSelect/index.d.ts +3 -25
- package/types/components/Select/SingleSelect/OptionList.d.ts +1 -1
- package/types/components/Select/SingleSelect/index.d.ts +4 -26
- package/types/components/Select/StyledOptionList.d.ts +17 -0
- package/types/components/Select/StyledSelect.d.ts +7 -7
- package/types/components/Select/index.d.ts +1 -1
- package/types/components/Select/types.d.ts +44 -0
- package/types/components/TimePicker/StyledTimePicker.d.ts +8 -0
- package/types/components/TimePicker/TimePickerAndroid.d.ts +3 -0
- package/types/components/TimePicker/TimePickerIOS.d.ts +3 -0
- package/types/components/TimePicker/__tests__/TimePicker.spec.d.ts +1 -0
- package/types/components/TimePicker/__tests__/TimePickerAndroid.spec.d.ts +1 -0
- package/types/components/TimePicker/__tests__/TimePickerIOS.spec.d.ts +1 -0
- package/types/components/TimePicker/index.d.ts +3 -0
- package/types/components/TimePicker/types.d.ts +49 -0
- package/types/components/Typography/Text/StyledText.d.ts +1 -1
- package/types/components/Typography/Text/index.d.ts +1 -1
- package/types/index.d.ts +3 -1
- package/types/theme/components/pinInput.d.ts +35 -0
- package/types/theme/components/select.d.ts +4 -0
- package/types/theme/components/timePicker.d.ts +6 -0
- package/types/theme/components/typography.d.ts +2 -0
- package/types/theme/index.d.ts +6 -2
- package/src/components/Select/types.ts +0 -1
- package/src/components/TextInput/__tests__/.log/ti-10343.log +0 -62
- package/src/components/TextInput/__tests__/.log/tsserver.log +0 -15584
|
@@ -1,55 +1,64 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
import {
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { TouchableOpacity, Keyboard, KeyboardEvent, View } from 'react-native';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { SelectProps } from '../types';
|
|
5
5
|
import BottomSheet from '../../BottomSheet';
|
|
6
6
|
import OptionList from './OptionList';
|
|
7
7
|
import TextInput from '../../TextInput';
|
|
8
|
+
import { StyledSearchBar } from '../StyledSelect';
|
|
8
9
|
|
|
9
|
-
export interface SingleSelectProps<T> {
|
|
10
|
-
/**
|
|
11
|
-
* An array of options to be selected.
|
|
12
|
-
*/
|
|
13
|
-
options: OptionType<T>[];
|
|
10
|
+
export interface SingleSelectProps<T> extends SelectProps<T> {
|
|
14
11
|
/**
|
|
15
12
|
* Current selected value.
|
|
16
13
|
*/
|
|
17
14
|
value: T | null;
|
|
18
15
|
/**
|
|
19
|
-
* event handler
|
|
16
|
+
* on select event handler
|
|
20
17
|
*/
|
|
21
18
|
onConfirm: (value: T | null) => void;
|
|
22
|
-
/**
|
|
23
|
-
* Field label.
|
|
24
|
-
*/
|
|
25
|
-
label: string;
|
|
26
|
-
/**
|
|
27
|
-
* Used to extract a unique key for a given option at the specified index. Key is used for caching and as the react key to track item re-ordering.
|
|
28
|
-
* The default extractor checks option.key, and then falls back to using the index, like React does.
|
|
29
|
-
*/
|
|
30
|
-
keyExtractor?: (option: OptionType<T>, index?: number) => string;
|
|
31
|
-
/**
|
|
32
|
-
* Additional style.
|
|
33
|
-
*/
|
|
34
|
-
style?: StyleProp<ViewStyle>;
|
|
35
|
-
/**
|
|
36
|
-
* Testing id of the component.
|
|
37
|
-
*/
|
|
38
|
-
testID?: string;
|
|
39
19
|
}
|
|
40
20
|
|
|
41
21
|
const SingleSelect = <T,>({
|
|
42
|
-
options,
|
|
43
|
-
value,
|
|
44
|
-
testID,
|
|
45
|
-
style,
|
|
46
22
|
label,
|
|
23
|
+
loading,
|
|
47
24
|
onConfirm,
|
|
25
|
+
onDimiss,
|
|
26
|
+
onEndReached,
|
|
27
|
+
onQueryChange,
|
|
28
|
+
options,
|
|
29
|
+
query,
|
|
30
|
+
style,
|
|
31
|
+
testID,
|
|
32
|
+
value,
|
|
48
33
|
}: SingleSelectProps<T>) => {
|
|
49
34
|
const [open, setOpen] = useState(false);
|
|
50
35
|
const [selectingValue, setSelectingValue] = useState<T | null>(value);
|
|
51
36
|
const displayedValue = options.find(opt => value === opt.value)?.text;
|
|
52
37
|
|
|
38
|
+
const [isKeyboardVisible, setKeyboardVisible] = useState(false);
|
|
39
|
+
const [keyboardHeight, setKeyboardHeight] = useState(0);
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
const keyboardDidShowListener = Keyboard.addListener(
|
|
43
|
+
'keyboardWillShow',
|
|
44
|
+
(e: KeyboardEvent) => {
|
|
45
|
+
setKeyboardVisible(true);
|
|
46
|
+
setKeyboardHeight(e.endCoordinates.height);
|
|
47
|
+
}
|
|
48
|
+
);
|
|
49
|
+
const keyboardDidHideListener = Keyboard.addListener(
|
|
50
|
+
'keyboardWillHide',
|
|
51
|
+
() => {
|
|
52
|
+
setKeyboardVisible(false);
|
|
53
|
+
}
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
return () => {
|
|
57
|
+
keyboardDidHideListener.remove();
|
|
58
|
+
keyboardDidShowListener.remove();
|
|
59
|
+
};
|
|
60
|
+
}, []);
|
|
61
|
+
|
|
53
62
|
return (
|
|
54
63
|
<TouchableOpacity onPress={() => setOpen(true)}>
|
|
55
64
|
<View pointerEvents="none">
|
|
@@ -65,10 +74,30 @@ const SingleSelect = <T,>({
|
|
|
65
74
|
<BottomSheet
|
|
66
75
|
open={open}
|
|
67
76
|
onRequestClose={() => setOpen(false)}
|
|
68
|
-
onDismiss={() =>
|
|
77
|
+
onDismiss={() => {
|
|
78
|
+
setSelectingValue(value);
|
|
79
|
+
if (onDimiss) onDimiss();
|
|
80
|
+
}}
|
|
69
81
|
header={label}
|
|
82
|
+
style={{
|
|
83
|
+
paddingBottom: isKeyboardVisible ? keyboardHeight : 0,
|
|
84
|
+
}}
|
|
70
85
|
>
|
|
86
|
+
{onQueryChange && (
|
|
87
|
+
<StyledSearchBar>
|
|
88
|
+
<TextInput
|
|
89
|
+
editable
|
|
90
|
+
placeholder="Search"
|
|
91
|
+
suffix="search-outlined"
|
|
92
|
+
onChangeText={onQueryChange}
|
|
93
|
+
value={query}
|
|
94
|
+
/>
|
|
95
|
+
</StyledSearchBar>
|
|
96
|
+
)}
|
|
71
97
|
<OptionList
|
|
98
|
+
onQueryChange={onQueryChange}
|
|
99
|
+
onEndReached={onEndReached}
|
|
100
|
+
loading={loading}
|
|
72
101
|
options={options}
|
|
73
102
|
value={selectingValue}
|
|
74
103
|
onPress={selectedValue => {
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { useTheme } from '@emotion/react';
|
|
2
|
+
import React, { useRef, useState } from 'react';
|
|
3
|
+
import { Dimensions, FlatList, ListRenderItemInfo, View } from 'react-native';
|
|
4
|
+
import Spinner from '../Spinner';
|
|
5
|
+
import { getKey } from './helpers';
|
|
6
|
+
import { OptionType, SelectProps } from './types';
|
|
7
|
+
|
|
8
|
+
export interface RenderItemProps<T> {
|
|
9
|
+
item: OptionType<T>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface OptionListProps<T> extends SelectProps<T> {
|
|
13
|
+
/**
|
|
14
|
+
* FlatList renderItem Element
|
|
15
|
+
*/
|
|
16
|
+
RenderItem: React.FC<RenderItemProps<T>>;
|
|
17
|
+
/**
|
|
18
|
+
* Selected scroll index
|
|
19
|
+
*/
|
|
20
|
+
scrollIndex?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const StyledOptionList = <T,>({
|
|
24
|
+
keyExtractor,
|
|
25
|
+
loading,
|
|
26
|
+
onEndReached,
|
|
27
|
+
onQueryChange,
|
|
28
|
+
options,
|
|
29
|
+
RenderItem,
|
|
30
|
+
scrollIndex = 0,
|
|
31
|
+
}: Pick<
|
|
32
|
+
OptionListProps<T>,
|
|
33
|
+
| 'scrollIndex'
|
|
34
|
+
| 'keyExtractor'
|
|
35
|
+
| 'loading'
|
|
36
|
+
| 'onEndReached'
|
|
37
|
+
| 'options'
|
|
38
|
+
| 'RenderItem'
|
|
39
|
+
| 'onQueryChange'
|
|
40
|
+
>) => {
|
|
41
|
+
const theme = useTheme();
|
|
42
|
+
const flatListRef = useRef<FlatList>(null);
|
|
43
|
+
|
|
44
|
+
const [onEndReachedCalled, setOnEndReachedCalled] = useState(false);
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<FlatList
|
|
48
|
+
ref={flatListRef}
|
|
49
|
+
style={{
|
|
50
|
+
paddingHorizontal: theme.__hd__.select.space.optionListPadding,
|
|
51
|
+
...(onQueryChange ? { height: Dimensions.get('screen').height } : {}),
|
|
52
|
+
}}
|
|
53
|
+
data={options}
|
|
54
|
+
keyExtractor={keyExtractor}
|
|
55
|
+
onEndReachedThreshold={0.1}
|
|
56
|
+
onEndReached={() => setOnEndReachedCalled(true)}
|
|
57
|
+
onScrollToIndexFailed={() => {}}
|
|
58
|
+
onContentSizeChange={() =>
|
|
59
|
+
options.length &&
|
|
60
|
+
flatListRef.current?.scrollToIndex({ index: scrollIndex })
|
|
61
|
+
}
|
|
62
|
+
onMomentumScrollBegin={() => {
|
|
63
|
+
if (onEndReached && onEndReachedCalled && !loading) onEndReached();
|
|
64
|
+
setOnEndReachedCalled(false);
|
|
65
|
+
}}
|
|
66
|
+
ListFooterComponent={
|
|
67
|
+
loading ? (
|
|
68
|
+
<View
|
|
69
|
+
style={{
|
|
70
|
+
display: 'flex',
|
|
71
|
+
alignItems: 'center',
|
|
72
|
+
height: theme.space.xxxxlarge,
|
|
73
|
+
}}
|
|
74
|
+
>
|
|
75
|
+
<Spinner />
|
|
76
|
+
</View>
|
|
77
|
+
) : null
|
|
78
|
+
}
|
|
79
|
+
renderItem={({ item, index }: ListRenderItemInfo<OptionType<T>>) => (
|
|
80
|
+
<View key={getKey(item, index, keyExtractor)}>
|
|
81
|
+
<RenderItem item={item} />
|
|
82
|
+
</View>
|
|
83
|
+
)}
|
|
84
|
+
/>
|
|
85
|
+
);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export default StyledOptionList;
|
|
@@ -2,21 +2,17 @@ import { View, TouchableOpacity } from 'react-native';
|
|
|
2
2
|
import styled from '@emotion/native';
|
|
3
3
|
import Typography from '../Typography';
|
|
4
4
|
|
|
5
|
-
const OptionWrapper = styled(TouchableOpacity)<{
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
);
|
|
17
|
-
|
|
18
|
-
const OptionListWrapper = styled(View)(({ theme }) => ({
|
|
19
|
-
padding: theme.__hd__.select.space.optionListPadding,
|
|
5
|
+
const OptionWrapper = styled(TouchableOpacity)<{
|
|
6
|
+
themeSelected: boolean;
|
|
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,
|
|
20
16
|
}));
|
|
21
17
|
|
|
22
18
|
const Spacer = styled(View)(({ theme }) => ({
|
|
@@ -27,4 +23,10 @@ const FooterText = styled(Typography.Text)(({ theme }) => ({
|
|
|
27
23
|
color: theme.__hd__.select.colors.footerText,
|
|
28
24
|
}));
|
|
29
25
|
|
|
30
|
-
|
|
26
|
+
const StyledSearchBar = styled(View)(({ theme }) => ({
|
|
27
|
+
marginTop: theme.__hd__.select.space.searchBarMarginTopSpacing,
|
|
28
|
+
paddingHorizontal: theme.__hd__.select.space.searchBarHorizontalSpacing,
|
|
29
|
+
paddingBottom: theme.__hd__.select.space.searchBarBottomSpacing,
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
export { OptionWrapper, Spacer, FooterText, StyledSearchBar };
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import renderWithTheme from '../../../testHelpers/renderWithTheme';
|
|
3
|
-
import {
|
|
4
|
-
OptionWrapper,
|
|
5
|
-
OptionListWrapper,
|
|
6
|
-
Spacer,
|
|
7
|
-
FooterText,
|
|
8
|
-
} from '../StyledSelect';
|
|
3
|
+
import { OptionWrapper, Spacer, FooterText } from '../StyledSelect';
|
|
9
4
|
|
|
10
5
|
describe('OptionWrapper', () => {
|
|
11
6
|
it.each`
|
|
@@ -21,14 +16,6 @@ describe('OptionWrapper', () => {
|
|
|
21
16
|
});
|
|
22
17
|
});
|
|
23
18
|
|
|
24
|
-
describe('OptionListWrapper', () => {
|
|
25
|
-
it('has correct style', () => {
|
|
26
|
-
const { toJSON } = renderWithTheme(<OptionListWrapper />);
|
|
27
|
-
|
|
28
|
-
expect(toJSON()).toMatchSnapshot();
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
|
|
32
19
|
describe('Spacer', () => {
|
|
33
20
|
it('has correct style', () => {
|
|
34
21
|
const { toJSON } = renderWithTheme(<Spacer />);
|
|
@@ -27,19 +27,6 @@ exports[`FooterText has correct style 1`] = `
|
|
|
27
27
|
</Text>
|
|
28
28
|
`;
|
|
29
29
|
|
|
30
|
-
exports[`OptionListWrapper has correct style 1`] = `
|
|
31
|
-
<View
|
|
32
|
-
style={
|
|
33
|
-
Array [
|
|
34
|
-
Object {
|
|
35
|
-
"padding": 16,
|
|
36
|
-
},
|
|
37
|
-
undefined,
|
|
38
|
-
]
|
|
39
|
-
}
|
|
40
|
-
/>
|
|
41
|
-
`;
|
|
42
|
-
|
|
43
30
|
exports[`OptionWrapper has selected style: false 1`] = `
|
|
44
31
|
<View
|
|
45
32
|
accessible={true}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { StyleProp, ViewStyle } from 'react-native';
|
|
2
|
+
|
|
3
|
+
export type OptionType<T> = { value: T; text: string; key?: string };
|
|
4
|
+
|
|
5
|
+
export interface SelectProps<T> {
|
|
6
|
+
/**
|
|
7
|
+
* An array of options to be selected.
|
|
8
|
+
*/
|
|
9
|
+
options: OptionType<T>[];
|
|
10
|
+
/**
|
|
11
|
+
* Used to extract a unique key for a given option at the specified index. Key is used for caching and as the react key to track item re-ordering.
|
|
12
|
+
* The default extractor checks option.key, and then falls back to using the index, like React does.
|
|
13
|
+
*/
|
|
14
|
+
keyExtractor?: (option: OptionType<T>, index?: number) => string;
|
|
15
|
+
/**
|
|
16
|
+
* Current search value.
|
|
17
|
+
*/
|
|
18
|
+
query?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Search bar onChangeText event handler
|
|
21
|
+
*/
|
|
22
|
+
onQueryChange?: (value: string) => void;
|
|
23
|
+
/**
|
|
24
|
+
* Event handler when selection dimiss
|
|
25
|
+
*/
|
|
26
|
+
onDimiss?: () => void;
|
|
27
|
+
/**
|
|
28
|
+
* Event handler when end of the list reached
|
|
29
|
+
*/
|
|
30
|
+
onEndReached?: () => void;
|
|
31
|
+
/**
|
|
32
|
+
* Show indicator at bottom of option list
|
|
33
|
+
*/
|
|
34
|
+
loading?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Field label.
|
|
37
|
+
*/
|
|
38
|
+
label: string;
|
|
39
|
+
/**
|
|
40
|
+
* Additional style.
|
|
41
|
+
*/
|
|
42
|
+
style?: StyleProp<ViewStyle>;
|
|
43
|
+
/**
|
|
44
|
+
* Testing id of the component.
|
|
45
|
+
*/
|
|
46
|
+
testID?: string;
|
|
47
|
+
}
|
|
@@ -60,6 +60,21 @@ describe('TextInput', () => {
|
|
|
60
60
|
).toHaveLength(1);
|
|
61
61
|
});
|
|
62
62
|
|
|
63
|
+
it('should not render input-label if label is empty', () => {
|
|
64
|
+
const { getByTestId } = renderWithTheme(
|
|
65
|
+
<TextInput
|
|
66
|
+
prefix="dollar-sign"
|
|
67
|
+
suffix="arrow-down"
|
|
68
|
+
testID="idle-text-input"
|
|
69
|
+
/>
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
expect(getByTestId('idle-text-input')).toBeTruthy();
|
|
73
|
+
expect(
|
|
74
|
+
within(getByTestId('idle-text-input')).queryAllByTestId('input-label')
|
|
75
|
+
).toHaveLength(0);
|
|
76
|
+
});
|
|
77
|
+
|
|
63
78
|
it('onChangeText, onBlur, onFocus', () => {
|
|
64
79
|
const onChangeText = jest.fn();
|
|
65
80
|
const onBlur = jest.fn();
|
|
@@ -171,14 +171,16 @@ const TextInput = ({
|
|
|
171
171
|
*
|
|
172
172
|
</StyledAsteriskLabel>
|
|
173
173
|
)}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
174
|
+
{!!label && (
|
|
175
|
+
<StyledLabel
|
|
176
|
+
nativeID={accessibilityLabelledBy}
|
|
177
|
+
testID="input-label"
|
|
178
|
+
fontSize="small"
|
|
179
|
+
themeVariant={variant}
|
|
180
|
+
>
|
|
181
|
+
{label}
|
|
182
|
+
</StyledLabel>
|
|
183
|
+
)}
|
|
182
184
|
</StyledLabelContainer>
|
|
183
185
|
)}
|
|
184
186
|
{typeof prefix === 'string' ? (
|
|
@@ -200,14 +202,16 @@ const TextInput = ({
|
|
|
200
202
|
*
|
|
201
203
|
</StyledAsteriskLabelInsideTextInput>
|
|
202
204
|
)}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
205
|
+
{!!label && (
|
|
206
|
+
<StyledLabelInsideTextInput
|
|
207
|
+
nativeID={accessibilityLabelledBy}
|
|
208
|
+
testID="input-label"
|
|
209
|
+
fontSize="medium"
|
|
210
|
+
themeVariant={variant}
|
|
211
|
+
>
|
|
212
|
+
{label}
|
|
213
|
+
</StyledLabelInsideTextInput>
|
|
214
|
+
)}
|
|
211
215
|
</StyledLabelContainerInsideTextInput>
|
|
212
216
|
)}
|
|
213
217
|
<StyledTextInput
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import DateTimePicker from '@react-native-community/datetimepicker';
|
|
2
|
+
import React, { useState } from 'react';
|
|
3
|
+
import { TouchableOpacity, View } from 'react-native';
|
|
4
|
+
import formatTime from 'date-fns/fp/format';
|
|
5
|
+
|
|
6
|
+
import TextInput from '../TextInput';
|
|
7
|
+
import { TimePickerProps } from './types';
|
|
8
|
+
|
|
9
|
+
const TimePickerAndroid = ({
|
|
10
|
+
value,
|
|
11
|
+
label,
|
|
12
|
+
placeholder,
|
|
13
|
+
onChange,
|
|
14
|
+
displayFormat = 'hh:mm aa',
|
|
15
|
+
disabled = false,
|
|
16
|
+
required,
|
|
17
|
+
error,
|
|
18
|
+
style,
|
|
19
|
+
testID,
|
|
20
|
+
}: TimePickerProps) => {
|
|
21
|
+
const [open, setOpen] = useState(false);
|
|
22
|
+
|
|
23
|
+
const is12Hour = displayFormat.includes('hh');
|
|
24
|
+
const displayValue = value ? formatTime(displayFormat, value) : '';
|
|
25
|
+
const pickerInitValue = value || new Date();
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<TouchableOpacity onPress={() => setOpen(true)} disabled={disabled}>
|
|
29
|
+
<View pointerEvents="none" testID="timePickerInputAndroid">
|
|
30
|
+
<TextInput
|
|
31
|
+
label={label}
|
|
32
|
+
value={displayValue}
|
|
33
|
+
suffix="clock-3"
|
|
34
|
+
placeholder={placeholder || displayFormat}
|
|
35
|
+
disabled={disabled}
|
|
36
|
+
error={error}
|
|
37
|
+
required={required}
|
|
38
|
+
style={style}
|
|
39
|
+
testID={testID}
|
|
40
|
+
/>
|
|
41
|
+
</View>
|
|
42
|
+
{open ? (
|
|
43
|
+
<DateTimePicker
|
|
44
|
+
testID="timePickerAndroid"
|
|
45
|
+
mode="time"
|
|
46
|
+
value={pickerInitValue}
|
|
47
|
+
display="default"
|
|
48
|
+
onChange={(_: any, date: Date | undefined) => {
|
|
49
|
+
setOpen(false);
|
|
50
|
+
if (date) {
|
|
51
|
+
onChange(date);
|
|
52
|
+
}
|
|
53
|
+
}}
|
|
54
|
+
is24Hour={!is12Hour}
|
|
55
|
+
/>
|
|
56
|
+
) : null}
|
|
57
|
+
</TouchableOpacity>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export default TimePickerAndroid;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import DateTimePicker from '@react-native-community/datetimepicker';
|
|
2
|
+
import React, { useState } from 'react';
|
|
3
|
+
import { TouchableOpacity, View } from 'react-native';
|
|
4
|
+
import formatTime from 'date-fns/fp/format';
|
|
5
|
+
|
|
6
|
+
import BottomSheet from '../BottomSheet';
|
|
7
|
+
import TextInput from '../TextInput';
|
|
8
|
+
import Typography from '../Typography';
|
|
9
|
+
import { StyledPickerWrapper } from './StyledTimePicker';
|
|
10
|
+
import { TimePickerProps } from './types';
|
|
11
|
+
|
|
12
|
+
const TimePickerIOS = ({
|
|
13
|
+
value,
|
|
14
|
+
label,
|
|
15
|
+
placeholder,
|
|
16
|
+
onChange,
|
|
17
|
+
confirmLabel,
|
|
18
|
+
displayFormat = 'hh:mm aa',
|
|
19
|
+
disabled = false,
|
|
20
|
+
required,
|
|
21
|
+
error,
|
|
22
|
+
style,
|
|
23
|
+
testID,
|
|
24
|
+
}: TimePickerProps) => {
|
|
25
|
+
const [selectingDate, setSelectingDate] = useState<Date | null>(value);
|
|
26
|
+
const [open, setOpen] = useState(false);
|
|
27
|
+
|
|
28
|
+
const is12Hour = displayFormat.includes('hh');
|
|
29
|
+
const displayValue = value ? formatTime(displayFormat, value) : '';
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<TouchableOpacity onPress={() => setOpen(true)} disabled={disabled}>
|
|
33
|
+
<View pointerEvents="none" testID="timePickerInputIOS">
|
|
34
|
+
<TextInput
|
|
35
|
+
label={label}
|
|
36
|
+
value={displayValue}
|
|
37
|
+
suffix="clock-3"
|
|
38
|
+
placeholder={placeholder || displayFormat}
|
|
39
|
+
disabled={disabled}
|
|
40
|
+
error={error}
|
|
41
|
+
required={required}
|
|
42
|
+
testID={testID}
|
|
43
|
+
style={style}
|
|
44
|
+
/>
|
|
45
|
+
</View>
|
|
46
|
+
<BottomSheet
|
|
47
|
+
open={open}
|
|
48
|
+
onRequestClose={() => setOpen(false)}
|
|
49
|
+
header={label}
|
|
50
|
+
footer={
|
|
51
|
+
<TouchableOpacity
|
|
52
|
+
onPress={() => {
|
|
53
|
+
if (selectingDate) {
|
|
54
|
+
onChange(selectingDate);
|
|
55
|
+
}
|
|
56
|
+
setOpen(false);
|
|
57
|
+
}}
|
|
58
|
+
>
|
|
59
|
+
<Typography.Text
|
|
60
|
+
fontSize="large"
|
|
61
|
+
fontWeight="semi-bold"
|
|
62
|
+
intent="primary"
|
|
63
|
+
>
|
|
64
|
+
{confirmLabel}
|
|
65
|
+
</Typography.Text>
|
|
66
|
+
</TouchableOpacity>
|
|
67
|
+
}
|
|
68
|
+
>
|
|
69
|
+
<StyledPickerWrapper>
|
|
70
|
+
<DateTimePicker
|
|
71
|
+
testID="timePickerIOS"
|
|
72
|
+
value={selectingDate || new Date()}
|
|
73
|
+
mode="time"
|
|
74
|
+
// Current prop is24Hour config only available for Android.
|
|
75
|
+
// This is a work around to get the picker to display 24 hour format for iOS.
|
|
76
|
+
locale={is12Hour ? undefined : 'en-GB'}
|
|
77
|
+
onChange={(_: any, date: Date | undefined) => {
|
|
78
|
+
if (date) {
|
|
79
|
+
setSelectingDate(date);
|
|
80
|
+
}
|
|
81
|
+
}}
|
|
82
|
+
display="spinner"
|
|
83
|
+
style={{ flex: 1 }}
|
|
84
|
+
/>
|
|
85
|
+
</StyledPickerWrapper>
|
|
86
|
+
</BottomSheet>
|
|
87
|
+
</TouchableOpacity>
|
|
88
|
+
);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export default TimePickerIOS;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Platform } from 'react-native';
|
|
3
|
+
import TimePicker from '..';
|
|
4
|
+
import renderWithTheme from '../../../testHelpers/renderWithTheme';
|
|
5
|
+
|
|
6
|
+
describe('TimePicker', () => {
|
|
7
|
+
it('renders TimePickerIOS when OS is iOS', () => {
|
|
8
|
+
Platform.OS = 'ios';
|
|
9
|
+
const { getByTestId } = renderWithTheme(
|
|
10
|
+
<TimePicker
|
|
11
|
+
label="Start time"
|
|
12
|
+
value={new Date('December 17, 1995 03:24:00')}
|
|
13
|
+
confirmLabel="Confirm"
|
|
14
|
+
onChange={jest.fn()}
|
|
15
|
+
/>
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
expect(getByTestId('timePickerInputIOS')).toBeDefined();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('renders TimePickerAndroid when OS is android', () => {
|
|
22
|
+
Platform.OS = 'android';
|
|
23
|
+
const { getByTestId } = renderWithTheme(
|
|
24
|
+
<TimePicker
|
|
25
|
+
label="Start time"
|
|
26
|
+
value={new Date('December 17, 1995 03:24:00')}
|
|
27
|
+
confirmLabel="Confirm"
|
|
28
|
+
onChange={jest.fn()}
|
|
29
|
+
/>
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
expect(getByTestId('timePickerInputAndroid')).toBeDefined();
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { fireEvent } from '@testing-library/react-native';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import renderWithTheme from '../../../testHelpers/renderWithTheme';
|
|
4
|
+
import TimePickerAndroid from '../TimePickerAndroid';
|
|
5
|
+
|
|
6
|
+
describe('TimePickerAndroid', () => {
|
|
7
|
+
it('renders correctly', () => {
|
|
8
|
+
const onChange = jest.fn();
|
|
9
|
+
|
|
10
|
+
const { getByText, queryByTestId, toJSON } = renderWithTheme(
|
|
11
|
+
<TimePickerAndroid
|
|
12
|
+
value={new Date('December 17, 1995 03:24:00')}
|
|
13
|
+
label="Break time"
|
|
14
|
+
confirmLabel="Confirm"
|
|
15
|
+
onChange={onChange}
|
|
16
|
+
/>
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
expect(getByText('Break time')).toBeDefined();
|
|
20
|
+
expect(queryByTestId('text-input').props.value).toBe('03:24 AM');
|
|
21
|
+
expect(queryByTestId('timePickerAndroid')).toBeNull();
|
|
22
|
+
|
|
23
|
+
// Open time picker
|
|
24
|
+
fireEvent.press(getByText('Break time'));
|
|
25
|
+
expect(queryByTestId('timePickerAndroid')).toBeTruthy();
|
|
26
|
+
|
|
27
|
+
expect(toJSON()).toMatchSnapshot();
|
|
28
|
+
|
|
29
|
+
// Change time
|
|
30
|
+
fireEvent(
|
|
31
|
+
queryByTestId('timePickerAndroid'),
|
|
32
|
+
'onChange',
|
|
33
|
+
null,
|
|
34
|
+
new Date('December 17, 1995 05:30:00')
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
expect(onChange).toBeCalledWith(new Date('December 17, 1995 05:30:00'));
|
|
38
|
+
});
|
|
39
|
+
});
|