@hero-design/rn 7.5.0 → 7.6.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/es/index.js +474 -110
- package/lib/index.js +474 -108
- package/package.json +2 -2
- package/playground/.turbo/turbo-type-check.log +7 -0
- package/playground/package.json +3 -3
- package/playground/src/App.tsx +6 -0
- package/playground/src/ContentNavigator.tsx +3 -10
- package/playground/src/Select.tsx +32 -0
- package/playground/src/Switch.tsx +80 -0
- package/src/components/BottomSheet/Header.tsx +1 -1
- package/src/components/BottomSheet/__tests__/__snapshots__/index.spec.tsx.snap +8 -8
- package/src/components/BottomSheet/index.tsx +6 -0
- package/src/components/Radio/RadioGroup.tsx +31 -7
- package/src/components/Radio/types.ts +1 -0
- package/src/components/SectionHeading/StyledHeading.tsx +5 -5
- package/src/components/SectionHeading/__tests__/{StyledHeading.tsx → StyledHeading.spec.tsx} +0 -0
- package/src/components/SectionHeading/__tests__/__snapshots__/{StyledHeading.tsx.snap → StyledHeading.spec.tsx.snap} +0 -0
- package/src/components/Select/MultiSelect/Footer.tsx +15 -0
- package/src/components/Select/MultiSelect/OptionList.tsx +76 -0
- package/src/components/Select/MultiSelect/StyledMultiSelect.tsx +30 -0
- package/src/components/Select/MultiSelect/__tests__/StyledMultiSelect.spec.tsx +49 -0
- package/src/components/Select/MultiSelect/__tests__/__snapshots__/StyledMultiSelect.spec.tsx.snap +108 -0
- package/src/components/Select/MultiSelect/__tests__/__snapshots__/index.spec.tsx.snap +1630 -0
- package/src/components/Select/MultiSelect/__tests__/index.spec.tsx +94 -0
- package/src/components/Select/MultiSelect/index.tsx +103 -0
- package/src/components/Select/MultiSelect/types.ts +1 -0
- package/src/components/Select/index.tsx +5 -0
- package/src/components/Switch/StyledSwitch.tsx +50 -0
- package/src/components/Switch/__tests__/StyledHeading.spec.tsx +42 -0
- package/src/components/Switch/__tests__/__snapshots__/StyledHeading.spec.tsx.snap +74 -0
- package/src/components/Switch/__tests__/__snapshots__/index.spec.tsx.snap +129 -0
- package/src/components/Switch/__tests__/index.spec.tsx +24 -0
- package/src/components/Switch/index.tsx +87 -0
- package/src/components/TextInput/StyledTextInput.tsx +2 -6
- package/src/components/TextInput/__tests__/StyledTextInput.spec.tsx +1 -7
- package/src/components/TextInput/__tests__/__snapshots__/StyledTextInput.spec.tsx.snap +1 -22
- package/src/components/TextInput/__tests__/index.spec.tsx +2 -1
- package/src/components/TextInput/index.tsx +19 -6
- package/src/index.ts +4 -0
- package/src/theme/__tests__/__snapshots__/index.spec.ts.snap +58 -1
- package/src/theme/components/sectionHeading.ts +18 -0
- package/src/theme/components/select.ts +23 -0
- package/src/theme/components/switch.ts +50 -0
- package/src/theme/components/textInput.ts +1 -1
- package/src/theme/global/colors.ts +1 -0
- package/src/theme/index.ts +12 -3
- package/types/components/BottomSheet/index.d.ts +5 -1
- package/types/components/Radio/RadioGroup.d.ts +11 -8
- package/types/components/Radio/index.d.ts +1 -1
- package/types/components/Radio/types.d.ts +5 -0
- package/types/components/SectionHeading/__tests__/{StyledHeading.d.ts → StyledHeading.spec.d.ts} +0 -0
- package/types/components/Select/MultiSelect/Footer.d.ts +5 -0
- package/types/components/Select/MultiSelect/OptionList.d.ts +3 -0
- package/types/components/Select/MultiSelect/StyledMultiSelect.d.ts +26 -0
- package/types/components/Select/MultiSelect/__tests__/StyledMultiSelect.spec.d.ts +1 -0
- package/types/components/Select/MultiSelect/__tests__/index.spec.d.ts +1 -0
- package/types/components/Select/MultiSelect/index.d.ts +39 -0
- package/types/components/Select/MultiSelect/types.d.ts +5 -0
- package/types/components/Select/index.d.ts +5 -0
- package/types/components/Switch/StyledSwitch.d.ts +36 -0
- package/types/components/Switch/__tests__/StyledHeading.spec.d.ts +1 -0
- package/types/components/Switch/__tests__/index.spec.d.ts +1 -0
- package/types/components/Switch/index.d.ts +30 -0
- package/types/components/TextInput/StyledTextInput.d.ts +1 -5
- package/types/components/TextInput/index.d.ts +13 -5
- package/types/index.d.ts +3 -1
- package/types/theme/components/sectionHeading.d.ts +13 -0
- package/types/theme/components/select.d.ts +17 -0
- package/types/theme/components/switch.d.ts +32 -0
- package/types/theme/components/textInput.d.ts +1 -1
- package/types/theme/global/colors.d.ts +1 -0
- package/types/theme/global/index.d.ts +1 -0
- package/types/theme/index.d.ts +8 -2
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { fireEvent } from '@testing-library/react-native';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import renderWithTheme from '../../../../testHelpers/renderWithTheme';
|
|
4
|
+
import MultiSelect from '..';
|
|
5
|
+
|
|
6
|
+
const options = [
|
|
7
|
+
{ text: 'Monday', value: 'mon' },
|
|
8
|
+
{ text: 'Tuesday', value: 'tue' },
|
|
9
|
+
{ text: 'Wednesday', value: 'wed' },
|
|
10
|
+
{ text: 'Thursday', value: 'thu' },
|
|
11
|
+
{ text: 'Friday', value: 'fri' },
|
|
12
|
+
{ text: 'Saturday', value: 'sat' },
|
|
13
|
+
{ text: 'Sunday', value: 'sun' },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
describe('rendering', () => {
|
|
17
|
+
it('renders correctly when bottom sheet is NOT visible', () => {
|
|
18
|
+
const { queryAllByText, toJSON, getByTestId } = renderWithTheme(
|
|
19
|
+
<MultiSelect
|
|
20
|
+
label="Allow notifications"
|
|
21
|
+
footerLabel="Confirm"
|
|
22
|
+
options={options}
|
|
23
|
+
value={['mon', 'tue']}
|
|
24
|
+
onPress={jest.fn()}
|
|
25
|
+
/>
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
expect(toJSON()).toMatchSnapshot();
|
|
29
|
+
expect(queryAllByText('Allow notifications')).toHaveLength(2);
|
|
30
|
+
expect(getByTestId('text-input').props.value).toBe('Monday, Tuesday');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('renders correctly when bottom sheet is visible', () => {
|
|
34
|
+
const { queryAllByText, getByText, toJSON, getByTestId } = renderWithTheme(
|
|
35
|
+
<MultiSelect
|
|
36
|
+
label="Allow notifications"
|
|
37
|
+
footerLabel="Confirm"
|
|
38
|
+
options={options}
|
|
39
|
+
value={['mon', 'tue']}
|
|
40
|
+
onPress={jest.fn()}
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
fireEvent.press(getByTestId('text-input'));
|
|
44
|
+
|
|
45
|
+
expect(toJSON()).toMatchSnapshot();
|
|
46
|
+
expect(queryAllByText('Allow notifications')).toHaveLength(2);
|
|
47
|
+
expect(getByText('Monday')).toBeDefined();
|
|
48
|
+
expect(getByText('Tuesday')).toBeDefined();
|
|
49
|
+
expect(getByText('Wednesday')).toBeDefined();
|
|
50
|
+
expect(getByText('Thursday')).toBeDefined();
|
|
51
|
+
expect(getByText('Friday')).toBeDefined();
|
|
52
|
+
expect(getByText('Saturday')).toBeDefined();
|
|
53
|
+
expect(getByText('Sunday')).toBeDefined();
|
|
54
|
+
expect(getByText('Confirm')).toBeDefined();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('behavior', () => {
|
|
59
|
+
it('calls onPress when pressing footer of bottom sheet', () => {
|
|
60
|
+
const onPress = jest.fn();
|
|
61
|
+
const { getByText, getByTestId } = renderWithTheme(
|
|
62
|
+
<MultiSelect
|
|
63
|
+
label="Allow notifications"
|
|
64
|
+
footerLabel="Confirm"
|
|
65
|
+
options={options}
|
|
66
|
+
value={['mon', 'tue']}
|
|
67
|
+
onPress={onPress}
|
|
68
|
+
testID="multi-select"
|
|
69
|
+
/>
|
|
70
|
+
);
|
|
71
|
+
fireEvent.press(getByTestId('multi-select'));
|
|
72
|
+
fireEvent.press(getByText('Confirm'));
|
|
73
|
+
|
|
74
|
+
expect(onPress).toBeCalledTimes(1);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('does NOT call onPress when pressing closing button of bottom sheet', () => {
|
|
78
|
+
const onPress = jest.fn();
|
|
79
|
+
const { getByTestId } = renderWithTheme(
|
|
80
|
+
<MultiSelect
|
|
81
|
+
label="Allow notifications"
|
|
82
|
+
footerLabel="Confirm"
|
|
83
|
+
options={options}
|
|
84
|
+
value={['mon', 'tue']}
|
|
85
|
+
onPress={onPress}
|
|
86
|
+
testID="multi-select"
|
|
87
|
+
/>
|
|
88
|
+
);
|
|
89
|
+
fireEvent.press(getByTestId('multi-select'));
|
|
90
|
+
fireEvent.press(getByTestId('bottom-sheet-close-icon'));
|
|
91
|
+
|
|
92
|
+
expect(onPress).toBeCalledTimes(0);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { useTheme } from '@emotion/react';
|
|
2
|
+
import React, { useState } from 'react';
|
|
3
|
+
import { StyleProp, ViewStyle, TouchableOpacity } from 'react-native';
|
|
4
|
+
import BottomSheet from '../../BottomSheet';
|
|
5
|
+
import TextInput from '../../TextInput';
|
|
6
|
+
import Footer from './Footer';
|
|
7
|
+
import OptionList from './OptionList';
|
|
8
|
+
import { OptionType } from './types';
|
|
9
|
+
|
|
10
|
+
export interface MultiSelectProps<T> {
|
|
11
|
+
/**
|
|
12
|
+
* An array of options to be selected.
|
|
13
|
+
*/
|
|
14
|
+
options: OptionType<T>[];
|
|
15
|
+
/**
|
|
16
|
+
* Current selected value.
|
|
17
|
+
*/
|
|
18
|
+
value: T[];
|
|
19
|
+
/**
|
|
20
|
+
* onPress event handler.
|
|
21
|
+
*/
|
|
22
|
+
onPress: (value: T[]) => void;
|
|
23
|
+
/**
|
|
24
|
+
* Field label.
|
|
25
|
+
*/
|
|
26
|
+
label: string;
|
|
27
|
+
/**
|
|
28
|
+
* Footer label.
|
|
29
|
+
*/
|
|
30
|
+
footerLabel: string;
|
|
31
|
+
/**
|
|
32
|
+
* 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.
|
|
33
|
+
* The default extractor checks option.key, and then falls back to using the index, like React does.
|
|
34
|
+
*/
|
|
35
|
+
keyExtractor?: (option: OptionType<T>, index?: number) => string;
|
|
36
|
+
/**
|
|
37
|
+
* Additional style.
|
|
38
|
+
*/
|
|
39
|
+
style?: StyleProp<ViewStyle>;
|
|
40
|
+
/**
|
|
41
|
+
* Testing id of the component.
|
|
42
|
+
*/
|
|
43
|
+
testID?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function MultiSelect<T>({
|
|
47
|
+
options,
|
|
48
|
+
value,
|
|
49
|
+
testID,
|
|
50
|
+
style,
|
|
51
|
+
label,
|
|
52
|
+
footerLabel,
|
|
53
|
+
onPress,
|
|
54
|
+
}: MultiSelectProps<T>) {
|
|
55
|
+
const theme = useTheme();
|
|
56
|
+
const [open, setOpen] = useState(false);
|
|
57
|
+
const [selectingValue, setSelectingValue] = useState(value);
|
|
58
|
+
const displayedValue = options
|
|
59
|
+
.filter(opt => value.includes(opt.value))
|
|
60
|
+
.map(opt => opt.text)
|
|
61
|
+
.join(', ');
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<TouchableOpacity onPress={() => setOpen(true)}>
|
|
65
|
+
<TextInput
|
|
66
|
+
label={label}
|
|
67
|
+
value={displayedValue}
|
|
68
|
+
onPressIn={() => setOpen(true)}
|
|
69
|
+
editable={false}
|
|
70
|
+
// when input is not editable on Android, the text color is gray
|
|
71
|
+
// hence, adding this to make the text color the same as iOS
|
|
72
|
+
textStyle={{ color: theme.colors.text }}
|
|
73
|
+
suffix="arrow-down"
|
|
74
|
+
multiline
|
|
75
|
+
style={style}
|
|
76
|
+
testID={testID}
|
|
77
|
+
/>
|
|
78
|
+
<BottomSheet
|
|
79
|
+
open={open}
|
|
80
|
+
onRequestClose={() => setOpen(false)}
|
|
81
|
+
onDismiss={() => setSelectingValue(value)}
|
|
82
|
+
header={label}
|
|
83
|
+
footer={
|
|
84
|
+
<Footer
|
|
85
|
+
label={footerLabel}
|
|
86
|
+
onPress={() => {
|
|
87
|
+
setOpen(false);
|
|
88
|
+
onPress(selectingValue);
|
|
89
|
+
}}
|
|
90
|
+
/>
|
|
91
|
+
}
|
|
92
|
+
>
|
|
93
|
+
<OptionList
|
|
94
|
+
options={options}
|
|
95
|
+
value={selectingValue}
|
|
96
|
+
onPress={setSelectingValue}
|
|
97
|
+
/>
|
|
98
|
+
</BottomSheet>
|
|
99
|
+
</TouchableOpacity>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export default MultiSelect;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type OptionType<T> = { value: T; text: string; key?: string };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import styled from '@emotion/native';
|
|
2
|
+
import { Animated, View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
type ThemeSize = 'small' | 'medium';
|
|
5
|
+
|
|
6
|
+
const StyledWrapper = styled(View)<{
|
|
7
|
+
themeChecked: boolean | undefined;
|
|
8
|
+
themeSize: ThemeSize;
|
|
9
|
+
}>(({ theme, themeChecked, themeSize }) => ({
|
|
10
|
+
height: theme.__hd__.switch.heights[themeSize],
|
|
11
|
+
width: theme.__hd__.switch.widths[themeSize],
|
|
12
|
+
paddingHorizontal: theme.__hd__.switch.spaces[themeSize],
|
|
13
|
+
borderRadius: theme.__hd__.switch.radii.rounded,
|
|
14
|
+
backgroundColor: themeChecked
|
|
15
|
+
? theme.__hd__.switch.colors.active
|
|
16
|
+
: theme.__hd__.switch.colors.inactive,
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
const StyledDisabledWrapper = styled(View)<{
|
|
20
|
+
themeSize: ThemeSize;
|
|
21
|
+
}>(({ theme, themeSize }) => ({
|
|
22
|
+
position: 'absolute',
|
|
23
|
+
height: theme.__hd__.switch.heights[themeSize],
|
|
24
|
+
width: theme.__hd__.switch.widths[themeSize],
|
|
25
|
+
borderRadius: theme.__hd__.switch.radii.rounded,
|
|
26
|
+
backgroundColor: theme.__hd__.switch.colors.thumb,
|
|
27
|
+
zIndex: 9999,
|
|
28
|
+
opacity: 0.8,
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
const StyledThumbWrapper = styled(View)<{
|
|
32
|
+
themeSize: ThemeSize;
|
|
33
|
+
}>(({ theme, themeSize }) => ({
|
|
34
|
+
height: theme.__hd__.switch.heights[themeSize],
|
|
35
|
+
width: theme.__hd__.switch.widths[themeSize],
|
|
36
|
+
borderRadius: theme.__hd__.switch.radii.rounded,
|
|
37
|
+
display: 'flex',
|
|
38
|
+
justifyContent: 'center',
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
const StyledKnot = styled(Animated.View)<{
|
|
42
|
+
themeSize: ThemeSize;
|
|
43
|
+
}>(({ theme, themeSize }) => ({
|
|
44
|
+
width: theme.__hd__.switch.thumbSizes[themeSize],
|
|
45
|
+
height: theme.__hd__.switch.thumbSizes[themeSize],
|
|
46
|
+
backgroundColor: theme.__hd__.switch.colors.thumb,
|
|
47
|
+
borderRadius: theme.__hd__.switch.radii.rounded,
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
export { StyledWrapper, StyledDisabledWrapper, StyledThumbWrapper, StyledKnot };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import renderWithTheme from '../../../testHelpers/renderWithTheme';
|
|
3
|
+
import {
|
|
4
|
+
StyledWrapper,
|
|
5
|
+
StyledDisabledWrapper,
|
|
6
|
+
StyledThumbWrapper,
|
|
7
|
+
StyledKnot,
|
|
8
|
+
} from '../StyledSwitch';
|
|
9
|
+
|
|
10
|
+
describe('StyledWrapper', () => {
|
|
11
|
+
it('renders correct style', () => {
|
|
12
|
+
const { toJSON } = renderWithTheme(
|
|
13
|
+
<StyledWrapper themeChecked themeSize="medium" />
|
|
14
|
+
);
|
|
15
|
+
expect(toJSON()).toMatchSnapshot();
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('StyledDisabledWrapper', () => {
|
|
20
|
+
it('renders correct style', () => {
|
|
21
|
+
const { toJSON } = renderWithTheme(
|
|
22
|
+
<StyledDisabledWrapper themeSize="medium" />
|
|
23
|
+
);
|
|
24
|
+
expect(toJSON()).toMatchSnapshot();
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('StyledThumbWrapper', () => {
|
|
29
|
+
it('renders correct style', () => {
|
|
30
|
+
const { toJSON } = renderWithTheme(
|
|
31
|
+
<StyledThumbWrapper themeSize="medium" />
|
|
32
|
+
);
|
|
33
|
+
expect(toJSON()).toMatchSnapshot();
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('StyledKnot', () => {
|
|
38
|
+
it('renders correct style', () => {
|
|
39
|
+
const { toJSON } = renderWithTheme(<StyledKnot themeSize="medium" />);
|
|
40
|
+
expect(toJSON()).toMatchSnapshot();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`StyledDisabledWrapper renders correct style 1`] = `
|
|
4
|
+
<View
|
|
5
|
+
style={
|
|
6
|
+
Array [
|
|
7
|
+
Object {
|
|
8
|
+
"backgroundColor": "#ffffff",
|
|
9
|
+
"borderRadius": 999,
|
|
10
|
+
"height": 28.799999999999997,
|
|
11
|
+
"opacity": 0.8,
|
|
12
|
+
"position": "absolute",
|
|
13
|
+
"width": 56,
|
|
14
|
+
"zIndex": 9999,
|
|
15
|
+
},
|
|
16
|
+
undefined,
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
themeSize="medium"
|
|
20
|
+
/>
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
exports[`StyledKnot renders correct style 1`] = `
|
|
24
|
+
<View
|
|
25
|
+
collapsable={false}
|
|
26
|
+
nativeID="animatedComponent"
|
|
27
|
+
style={
|
|
28
|
+
Object {
|
|
29
|
+
"backgroundColor": "#ffffff",
|
|
30
|
+
"borderRadius": 999,
|
|
31
|
+
"height": 20.8,
|
|
32
|
+
"width": 20.8,
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
themeSize="medium"
|
|
36
|
+
/>
|
|
37
|
+
`;
|
|
38
|
+
|
|
39
|
+
exports[`StyledThumbWrapper renders correct style 1`] = `
|
|
40
|
+
<View
|
|
41
|
+
style={
|
|
42
|
+
Array [
|
|
43
|
+
Object {
|
|
44
|
+
"borderRadius": 999,
|
|
45
|
+
"display": "flex",
|
|
46
|
+
"height": 28.799999999999997,
|
|
47
|
+
"justifyContent": "center",
|
|
48
|
+
"width": 56,
|
|
49
|
+
},
|
|
50
|
+
undefined,
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
themeSize="medium"
|
|
54
|
+
/>
|
|
55
|
+
`;
|
|
56
|
+
|
|
57
|
+
exports[`StyledWrapper renders correct style 1`] = `
|
|
58
|
+
<View
|
|
59
|
+
style={
|
|
60
|
+
Array [
|
|
61
|
+
Object {
|
|
62
|
+
"backgroundColor": "#7622d7",
|
|
63
|
+
"borderRadius": 999,
|
|
64
|
+
"height": 28.799999999999997,
|
|
65
|
+
"paddingHorizontal": 4,
|
|
66
|
+
"width": 56,
|
|
67
|
+
},
|
|
68
|
+
undefined,
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
themeChecked={true}
|
|
72
|
+
themeSize="medium"
|
|
73
|
+
/>
|
|
74
|
+
`;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`Switch renders correctly 1`] = `
|
|
4
|
+
<View
|
|
5
|
+
accessibilityState={
|
|
6
|
+
Object {
|
|
7
|
+
"disabled": false,
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
accessible={true}
|
|
11
|
+
focusable={false}
|
|
12
|
+
onClick={[Function]}
|
|
13
|
+
onResponderGrant={[Function]}
|
|
14
|
+
onResponderMove={[Function]}
|
|
15
|
+
onResponderRelease={[Function]}
|
|
16
|
+
onResponderTerminate={[Function]}
|
|
17
|
+
onResponderTerminationRequest={[Function]}
|
|
18
|
+
onStartShouldSetResponder={[Function]}
|
|
19
|
+
style={
|
|
20
|
+
Array [
|
|
21
|
+
Object {
|
|
22
|
+
"backgroundColor": "#727478",
|
|
23
|
+
"borderRadius": 999,
|
|
24
|
+
"height": 28.799999999999997,
|
|
25
|
+
"paddingHorizontal": 4,
|
|
26
|
+
"width": 56,
|
|
27
|
+
},
|
|
28
|
+
undefined,
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
testID="switch"
|
|
32
|
+
themeSize="medium"
|
|
33
|
+
>
|
|
34
|
+
<View
|
|
35
|
+
style={
|
|
36
|
+
Array [
|
|
37
|
+
Object {
|
|
38
|
+
"borderRadius": 999,
|
|
39
|
+
"display": "flex",
|
|
40
|
+
"height": 28.799999999999997,
|
|
41
|
+
"justifyContent": "center",
|
|
42
|
+
"width": 56,
|
|
43
|
+
},
|
|
44
|
+
undefined,
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
themeSize="medium"
|
|
48
|
+
>
|
|
49
|
+
<View
|
|
50
|
+
collapsable={false}
|
|
51
|
+
nativeID="animatedComponent"
|
|
52
|
+
style={
|
|
53
|
+
Object {
|
|
54
|
+
"backgroundColor": "#ffffff",
|
|
55
|
+
"borderRadius": 999,
|
|
56
|
+
"height": 20.8,
|
|
57
|
+
"left": 0,
|
|
58
|
+
"width": 20.8,
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
themeSize="medium"
|
|
62
|
+
/>
|
|
63
|
+
</View>
|
|
64
|
+
</View>
|
|
65
|
+
`;
|
|
66
|
+
|
|
67
|
+
exports[`Switch trigger press function correctly 1`] = `
|
|
68
|
+
<View
|
|
69
|
+
accessibilityState={
|
|
70
|
+
Object {
|
|
71
|
+
"disabled": false,
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
accessible={true}
|
|
75
|
+
focusable={true}
|
|
76
|
+
onClick={[Function]}
|
|
77
|
+
onResponderGrant={[Function]}
|
|
78
|
+
onResponderMove={[Function]}
|
|
79
|
+
onResponderRelease={[Function]}
|
|
80
|
+
onResponderTerminate={[Function]}
|
|
81
|
+
onResponderTerminationRequest={[Function]}
|
|
82
|
+
onStartShouldSetResponder={[Function]}
|
|
83
|
+
style={
|
|
84
|
+
Array [
|
|
85
|
+
Object {
|
|
86
|
+
"backgroundColor": "#727478",
|
|
87
|
+
"borderRadius": 999,
|
|
88
|
+
"height": 28.799999999999997,
|
|
89
|
+
"paddingHorizontal": 4,
|
|
90
|
+
"width": 56,
|
|
91
|
+
},
|
|
92
|
+
undefined,
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
testID="switch"
|
|
96
|
+
themeSize="medium"
|
|
97
|
+
>
|
|
98
|
+
<View
|
|
99
|
+
style={
|
|
100
|
+
Array [
|
|
101
|
+
Object {
|
|
102
|
+
"borderRadius": 999,
|
|
103
|
+
"display": "flex",
|
|
104
|
+
"height": 28.799999999999997,
|
|
105
|
+
"justifyContent": "center",
|
|
106
|
+
"width": 56,
|
|
107
|
+
},
|
|
108
|
+
undefined,
|
|
109
|
+
]
|
|
110
|
+
}
|
|
111
|
+
themeSize="medium"
|
|
112
|
+
>
|
|
113
|
+
<View
|
|
114
|
+
collapsable={false}
|
|
115
|
+
nativeID="animatedComponent"
|
|
116
|
+
style={
|
|
117
|
+
Object {
|
|
118
|
+
"backgroundColor": "#ffffff",
|
|
119
|
+
"borderRadius": 999,
|
|
120
|
+
"height": 20.8,
|
|
121
|
+
"left": 0,
|
|
122
|
+
"width": 20.8,
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
themeSize="medium"
|
|
126
|
+
/>
|
|
127
|
+
</View>
|
|
128
|
+
</View>
|
|
129
|
+
`;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { fireEvent } from '@testing-library/react-native';
|
|
3
|
+
|
|
4
|
+
import renderWithTheme from '../../../testHelpers/renderWithTheme';
|
|
5
|
+
import Switch from '..';
|
|
6
|
+
|
|
7
|
+
describe('Switch', () => {
|
|
8
|
+
it('renders correctly', () => {
|
|
9
|
+
const wrapper = renderWithTheme(<Switch testID="switch" />);
|
|
10
|
+
expect(wrapper.queryAllByTestId('switch')).toHaveLength(1);
|
|
11
|
+
expect(wrapper.toJSON()).toMatchSnapshot();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('trigger press function correctly', () => {
|
|
15
|
+
const pressFn = jest.fn();
|
|
16
|
+
const wrapper = renderWithTheme(
|
|
17
|
+
<Switch testID="switch" onPress={pressFn} />
|
|
18
|
+
);
|
|
19
|
+
fireEvent.press(wrapper.getByTestId('switch'));
|
|
20
|
+
|
|
21
|
+
expect(wrapper.toJSON()).toMatchSnapshot();
|
|
22
|
+
expect(pressFn).toBeCalled();
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { useTheme } from '@emotion/react';
|
|
2
|
+
import React, { ReactElement, useState, useEffect } from 'react';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
StyleProp,
|
|
6
|
+
ViewStyle,
|
|
7
|
+
Animated,
|
|
8
|
+
Easing,
|
|
9
|
+
TouchableWithoutFeedback,
|
|
10
|
+
} from 'react-native';
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
StyledDisabledWrapper,
|
|
14
|
+
StyledKnot,
|
|
15
|
+
StyledThumbWrapper,
|
|
16
|
+
StyledWrapper,
|
|
17
|
+
} from './StyledSwitch';
|
|
18
|
+
|
|
19
|
+
export interface SwitchProps {
|
|
20
|
+
/**
|
|
21
|
+
* Control whether the switch is checked
|
|
22
|
+
*/
|
|
23
|
+
checked?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Whether the switch is disabled
|
|
26
|
+
*/
|
|
27
|
+
disabled?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Event handler.
|
|
30
|
+
*/
|
|
31
|
+
onPress?: () => void;
|
|
32
|
+
/**
|
|
33
|
+
* Size of the switch
|
|
34
|
+
*/
|
|
35
|
+
size?: 'small' | 'medium';
|
|
36
|
+
/**
|
|
37
|
+
* Addditional style.
|
|
38
|
+
*/
|
|
39
|
+
style?: StyleProp<ViewStyle>;
|
|
40
|
+
/**
|
|
41
|
+
* Testing id of the component.
|
|
42
|
+
*/
|
|
43
|
+
testID?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const Switch = ({
|
|
47
|
+
size = 'medium',
|
|
48
|
+
disabled = false,
|
|
49
|
+
checked,
|
|
50
|
+
onPress,
|
|
51
|
+
style,
|
|
52
|
+
testID,
|
|
53
|
+
}: SwitchProps): ReactElement => {
|
|
54
|
+
const theme = useTheme();
|
|
55
|
+
|
|
56
|
+
const offset = checked
|
|
57
|
+
? theme.__hd__.switch.widths[size] -
|
|
58
|
+
theme.__hd__.switch.thumbSizes[size] -
|
|
59
|
+
theme.__hd__.switch.spaces[size] * 2
|
|
60
|
+
: theme.__hd__.switch.spaces.inactive;
|
|
61
|
+
|
|
62
|
+
const [animatedOffset] = useState(() => new Animated.Value(offset));
|
|
63
|
+
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
Animated.timing(animatedOffset, {
|
|
66
|
+
toValue: offset,
|
|
67
|
+
easing: Easing.inOut(Easing.cubic),
|
|
68
|
+
useNativeDriver: false,
|
|
69
|
+
}).start();
|
|
70
|
+
}, [checked]);
|
|
71
|
+
return (
|
|
72
|
+
<TouchableWithoutFeedback
|
|
73
|
+
testID={testID}
|
|
74
|
+
onPress={onPress}
|
|
75
|
+
disabled={disabled}
|
|
76
|
+
>
|
|
77
|
+
<StyledWrapper themeChecked={checked} themeSize={size} style={style}>
|
|
78
|
+
{disabled && <StyledDisabledWrapper themeSize={size} />}
|
|
79
|
+
<StyledThumbWrapper themeSize={size}>
|
|
80
|
+
<StyledKnot themeSize={size} style={{ left: animatedOffset }} />
|
|
81
|
+
</StyledThumbWrapper>
|
|
82
|
+
</StyledWrapper>
|
|
83
|
+
</TouchableWithoutFeedback>
|
|
84
|
+
);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export default Switch;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { TextInput, View } from 'react-native';
|
|
2
2
|
import styled from '@emotion/native';
|
|
3
3
|
import Typography from '../Typography';
|
|
4
|
-
import Icon from '../Icon';
|
|
5
4
|
|
|
6
5
|
const Container = styled(View)(({ theme }) => ({
|
|
7
6
|
position: 'relative',
|
|
@@ -22,13 +21,10 @@ const Label = styled(Typography.Text)(({ theme }) => ({
|
|
|
22
21
|
paddingHorizontal: theme.__hd__.textInput.space.labelHorizontalPadding,
|
|
23
22
|
}));
|
|
24
23
|
|
|
25
|
-
const Prefix = styled(Icon)(({ theme }) => ({
|
|
26
|
-
marginRight: theme.__hd__.textInput.space.prefixMarginRight,
|
|
27
|
-
}));
|
|
28
|
-
|
|
29
24
|
const StyledTextInput = styled(TextInput)(({ theme }) => ({
|
|
30
25
|
flex: 1,
|
|
31
26
|
fontSize: theme.__hd__.textInput.fontSizes.text,
|
|
27
|
+
marginHorizontal: theme.__hd__.textInput.space.inputHorizontalMargin,
|
|
32
28
|
}));
|
|
33
29
|
|
|
34
|
-
export { Container, Label,
|
|
30
|
+
export { Container, Label, StyledTextInput };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import renderWithTheme from '../../../testHelpers/renderWithTheme';
|
|
3
|
-
import { Container, Label,
|
|
3
|
+
import { Container, Label, StyledTextInput } from '../StyledTextInput';
|
|
4
4
|
|
|
5
5
|
describe('Container', () => {
|
|
6
6
|
it('renders correctly', () => {
|
|
@@ -17,13 +17,7 @@ describe('Label', () => {
|
|
|
17
17
|
expect(toJSON()).toMatchSnapshot();
|
|
18
18
|
});
|
|
19
19
|
});
|
|
20
|
-
describe('Prefix', () => {
|
|
21
|
-
it('renders correctly', () => {
|
|
22
|
-
const { toJSON } = renderWithTheme(<Prefix icon="user" />);
|
|
23
20
|
|
|
24
|
-
expect(toJSON()).toMatchSnapshot();
|
|
25
|
-
});
|
|
26
|
-
});
|
|
27
21
|
describe('StyledTextInput', () => {
|
|
28
22
|
it('renders correctly', () => {
|
|
29
23
|
const { toJSON } = renderWithTheme(<StyledTextInput />);
|
|
@@ -51,28 +51,6 @@ exports[`Label renders correctly 1`] = `
|
|
|
51
51
|
</Text>
|
|
52
52
|
`;
|
|
53
53
|
|
|
54
|
-
exports[`Prefix renders correctly 1`] = `
|
|
55
|
-
<HeroIcon
|
|
56
|
-
name="user"
|
|
57
|
-
style={
|
|
58
|
-
Array [
|
|
59
|
-
Object {
|
|
60
|
-
"color": "#292a2b",
|
|
61
|
-
"fontSize": 24,
|
|
62
|
-
},
|
|
63
|
-
Array [
|
|
64
|
-
Object {
|
|
65
|
-
"marginRight": 8,
|
|
66
|
-
},
|
|
67
|
-
undefined,
|
|
68
|
-
],
|
|
69
|
-
]
|
|
70
|
-
}
|
|
71
|
-
themeIntent="text"
|
|
72
|
-
themeSize="medium"
|
|
73
|
-
/>
|
|
74
|
-
`;
|
|
75
|
-
|
|
76
54
|
exports[`StyledTextInput renders correctly 1`] = `
|
|
77
55
|
<TextInput
|
|
78
56
|
style={
|
|
@@ -80,6 +58,7 @@ exports[`StyledTextInput renders correctly 1`] = `
|
|
|
80
58
|
Object {
|
|
81
59
|
"flex": 1,
|
|
82
60
|
"fontSize": 16,
|
|
61
|
+
"marginHorizontal": 8,
|
|
83
62
|
},
|
|
84
63
|
undefined,
|
|
85
64
|
]
|