@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.
Files changed (103) hide show
  1. package/.turbo/turbo-build.log +8 -8
  2. package/assets/fonts/hero-icons.ttf +0 -0
  3. package/es/index.js +3778 -728
  4. package/global-setup.js +3 -0
  5. package/jest.config.js +1 -0
  6. package/lib/assets/fonts/hero-icons.ttf +0 -0
  7. package/lib/index.js +3779 -726
  8. package/package.json +7 -3
  9. package/rollup.config.js +8 -1
  10. package/src/components/ContentNavigator/__tests__/__snapshots__/index.spec.tsx.snap +2 -0
  11. package/src/components/ContentNavigator/__tests__/index.spec.tsx +19 -2
  12. package/src/components/ContentNavigator/index.tsx +12 -1
  13. package/src/components/FAB/ActionGroup/__tests__/__snapshots__/index.spec.tsx.snap +4 -0
  14. package/src/components/FAB/ActionGroup/index.tsx +16 -5
  15. package/src/components/Icon/HeroIcon/selection.json +1 -1
  16. package/src/components/Icon/IconList.ts +1 -0
  17. package/src/components/PinInput/PinCell.tsx +34 -0
  18. package/src/components/PinInput/StyledPinInput.tsx +88 -0
  19. package/src/components/PinInput/__tests__/PinCell.spec.tsx +48 -0
  20. package/src/components/PinInput/__tests__/StyledPinInput.spec.tsx +22 -0
  21. package/src/components/PinInput/__tests__/__snapshots__/PinCell.spec.tsx.snap +186 -0
  22. package/src/components/PinInput/__tests__/__snapshots__/StyledPinInput.spec.tsx.snap +58 -0
  23. package/src/components/PinInput/__tests__/__snapshots__/index.spec.tsx.snap +1028 -0
  24. package/src/components/PinInput/__tests__/index.spec.tsx +91 -0
  25. package/src/components/PinInput/index.tsx +173 -0
  26. package/src/components/Select/MultiSelect/Option.tsx +1 -1
  27. package/src/components/Select/MultiSelect/OptionList.tsx +48 -26
  28. package/src/components/Select/MultiSelect/__tests__/OptionList.spec.tsx +13 -0
  29. package/src/components/Select/MultiSelect/__tests__/__snapshots__/OptionList.spec.tsx.snap +1062 -556
  30. package/src/components/Select/MultiSelect/__tests__/__snapshots__/index.spec.tsx.snap +983 -889
  31. package/src/components/Select/MultiSelect/index.tsx +59 -31
  32. package/src/components/Select/SingleSelect/OptionList.tsx +45 -26
  33. package/src/components/Select/SingleSelect/__tests__/OptionList.spec.tsx +8 -0
  34. package/src/components/Select/SingleSelect/__tests__/__snapshots__/OptionList.spec.tsx.snap +992 -500
  35. package/src/components/Select/SingleSelect/__tests__/__snapshots__/index.spec.tsx.snap +880 -786
  36. package/src/components/Select/SingleSelect/index.tsx +60 -31
  37. package/src/components/Select/StyledOptionList.tsx +88 -0
  38. package/src/components/Select/StyledSelect.tsx +18 -16
  39. package/src/components/Select/__tests__/StyledSelect.spec.tsx +1 -14
  40. package/src/components/Select/__tests__/__snapshots__/StyledSelect.spec.tsx.snap +0 -13
  41. package/src/components/Select/types.tsx +47 -0
  42. package/src/components/TextInput/__tests__/index.spec.tsx +15 -0
  43. package/src/components/TextInput/index.tsx +20 -16
  44. package/src/components/TimePicker/StyledTimePicker.tsx +8 -0
  45. package/src/components/TimePicker/TimePickerAndroid.tsx +61 -0
  46. package/src/components/TimePicker/TimePickerIOS.tsx +91 -0
  47. package/src/components/TimePicker/__tests__/TimePicker.spec.tsx +34 -0
  48. package/src/components/TimePicker/__tests__/TimePickerAndroid.spec.tsx +39 -0
  49. package/src/components/TimePicker/__tests__/TimePickerIOS.spec.tsx +46 -0
  50. package/src/components/TimePicker/__tests__/__snapshots__/TimePickerAndroid.spec.tsx.snap +200 -0
  51. package/src/components/TimePicker/__tests__/__snapshots__/TimePickerIOS.spec.tsx.snap +513 -0
  52. package/src/components/TimePicker/index.tsx +15 -0
  53. package/src/components/TimePicker/types.ts +50 -0
  54. package/src/components/Typography/Text/StyledText.tsx +1 -1
  55. package/src/components/Typography/Text/__tests__/StyledText.spec.tsx +1 -0
  56. package/src/components/Typography/Text/__tests__/__snapshots__/StyledText.spec.tsx.snap +22 -0
  57. package/src/components/Typography/Text/index.tsx +1 -1
  58. package/src/index.ts +4 -0
  59. package/src/theme/__tests__/__snapshots__/index.spec.ts.snap +44 -0
  60. package/src/theme/components/pinInput.ts +45 -0
  61. package/src/theme/components/select.ts +4 -0
  62. package/src/theme/components/timePicker.ts +11 -0
  63. package/src/theme/components/typography.ts +2 -0
  64. package/src/theme/global/colors.ts +1 -1
  65. package/src/theme/global/space.ts +10 -10
  66. package/src/theme/index.ts +9 -3
  67. package/testUtils/setup.tsx +10 -0
  68. package/types/components/ContentNavigator/index.d.ts +5 -1
  69. package/types/components/Icon/IconList.d.ts +1 -1
  70. package/types/components/Icon/utils.d.ts +1 -1
  71. package/types/components/PinInput/PinCell.d.ts +8 -0
  72. package/types/components/PinInput/StyledPinInput.d.ts +73 -0
  73. package/types/components/PinInput/__tests__/PinCell.spec.d.ts +1 -0
  74. package/types/components/PinInput/__tests__/StyledPinInput.spec.d.ts +1 -0
  75. package/types/components/PinInput/__tests__/index.spec.d.ts +1 -0
  76. package/types/components/PinInput/index.d.ts +48 -0
  77. package/types/components/Select/MultiSelect/OptionList.d.ts +1 -1
  78. package/types/components/Select/MultiSelect/index.d.ts +3 -25
  79. package/types/components/Select/SingleSelect/OptionList.d.ts +1 -1
  80. package/types/components/Select/SingleSelect/index.d.ts +4 -26
  81. package/types/components/Select/StyledOptionList.d.ts +17 -0
  82. package/types/components/Select/StyledSelect.d.ts +7 -7
  83. package/types/components/Select/index.d.ts +1 -1
  84. package/types/components/Select/types.d.ts +44 -0
  85. package/types/components/TimePicker/StyledTimePicker.d.ts +8 -0
  86. package/types/components/TimePicker/TimePickerAndroid.d.ts +3 -0
  87. package/types/components/TimePicker/TimePickerIOS.d.ts +3 -0
  88. package/types/components/TimePicker/__tests__/TimePicker.spec.d.ts +1 -0
  89. package/types/components/TimePicker/__tests__/TimePickerAndroid.spec.d.ts +1 -0
  90. package/types/components/TimePicker/__tests__/TimePickerIOS.spec.d.ts +1 -0
  91. package/types/components/TimePicker/index.d.ts +3 -0
  92. package/types/components/TimePicker/types.d.ts +49 -0
  93. package/types/components/Typography/Text/StyledText.d.ts +1 -1
  94. package/types/components/Typography/Text/index.d.ts +1 -1
  95. package/types/index.d.ts +3 -1
  96. package/types/theme/components/pinInput.d.ts +35 -0
  97. package/types/theme/components/select.d.ts +4 -0
  98. package/types/theme/components/timePicker.d.ts +6 -0
  99. package/types/theme/components/typography.d.ts +2 -0
  100. package/types/theme/index.d.ts +6 -2
  101. package/src/components/Select/types.ts +0 -1
  102. package/src/components/TextInput/__tests__/.log/ti-10343.log +0 -62
  103. package/src/components/TextInput/__tests__/.log/tsserver.log +0 -15584
@@ -0,0 +1,91 @@
1
+ import React from 'react';
2
+ import { fireEvent } from '@testing-library/react-native';
3
+ import renderWithTheme from '../../../testHelpers/renderWithTheme';
4
+ import PinInput, { getState } from '..';
5
+
6
+ describe('rendering', () => {
7
+ it('renders correctly', () => {
8
+ const wrapper = renderWithTheme(
9
+ <PinInput value="12" onChangeText={jest.fn()} />
10
+ );
11
+
12
+ expect(wrapper.toJSON()).toMatchSnapshot();
13
+ expect(wrapper.queryAllByTestId('pin-cell-filled-mask')).toHaveLength(2);
14
+ expect(wrapper.queryAllByTestId('pin-cell-mask')).toHaveLength(2);
15
+ });
16
+
17
+ it('renders correctly when length is 6 and secure is false', () => {
18
+ const wrapper = renderWithTheme(
19
+ <PinInput
20
+ length={6}
21
+ value="123"
22
+ onChangeText={jest.fn()}
23
+ secure={false}
24
+ />
25
+ );
26
+
27
+ expect(wrapper.queryAllByText('1')).toHaveLength(1);
28
+ expect(wrapper.queryAllByText('2')).toHaveLength(1);
29
+ expect(wrapper.queryAllByText('3')).toHaveLength(1);
30
+ expect(wrapper.queryAllByTestId('pin-cell-mask')).toHaveLength(3);
31
+ expect(wrapper.toJSON()).toMatchSnapshot();
32
+ });
33
+
34
+ it('renders correctly when there is error', () => {
35
+ const wrapper = renderWithTheme(
36
+ <PinInput
37
+ value="12"
38
+ onChangeText={jest.fn()}
39
+ error="PIN is not correct"
40
+ />
41
+ );
42
+
43
+ expect(wrapper.queryAllByText('PIN is not correct')).toHaveLength(1);
44
+ expect(wrapper.queryAllByTestId('pin-error-icon')).toHaveLength(1);
45
+ expect(wrapper.toJSON()).toMatchSnapshot();
46
+ });
47
+
48
+ it('renders correctly when disabled', () => {
49
+ const wrapper = renderWithTheme(
50
+ <PinInput value="12" onChangeText={jest.fn()} disabled />
51
+ );
52
+
53
+ expect(wrapper.toJSON()).toMatchSnapshot();
54
+ });
55
+ });
56
+
57
+ describe('behaviors', () => {
58
+ it('calls onChangeText, onFulfill with correct params', () => {
59
+ const onChangeText = jest.fn();
60
+ const onFulfill = jest.fn();
61
+ const { getByTestId } = renderWithTheme(
62
+ <PinInput value="123" onChangeText={onChangeText} onFulfill={onFulfill} />
63
+ );
64
+
65
+ const hiddenInput = getByTestId('pin-hidden-input');
66
+ fireEvent.changeText(hiddenInput, '123.4'); // 123.4 is intended to test regex behavior
67
+
68
+ expect(onChangeText).toHaveBeenCalledWith('1234');
69
+ expect(onFulfill).toHaveBeenCalledWith('1234');
70
+ });
71
+ });
72
+
73
+ describe('getState', () => {
74
+ it.each`
75
+ disabled | error | state
76
+ ${true} | ${'PIN is not correct'} | ${'disabled'}
77
+ ${true} | ${undefined} | ${'disabled'}
78
+ ${false} | ${'This field is required'} | ${'error'}
79
+ ${false} | ${undefined} | ${'default'}
80
+ `(
81
+ 'returns $state when disabled is $disabled, error is $error',
82
+ ({ disabled, error, state }) => {
83
+ expect(
84
+ getState({
85
+ disabled,
86
+ error,
87
+ })
88
+ ).toBe(state);
89
+ }
90
+ );
91
+ });
@@ -0,0 +1,173 @@
1
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
2
+ import {
3
+ InteractionManager,
4
+ StyleProp,
5
+ TextInput,
6
+ ViewStyle,
7
+ } from 'react-native';
8
+ import Icon from '../Icon';
9
+ import PinCell from './PinCell';
10
+ import {
11
+ StyledErrorContainer,
12
+ StyledErrorMessage,
13
+ StyledHiddenInput,
14
+ StyledSpacer,
15
+ StyledPinWrapper,
16
+ StyledWrapper,
17
+ State,
18
+ } from './StyledPinInput';
19
+
20
+ interface PinInputProps {
21
+ /**
22
+ * The value to show for the input.
23
+ */
24
+ value?: string;
25
+ /**
26
+ * Callback function that's called when the text changed.
27
+ */
28
+ onChangeText: (value: string) => void;
29
+ /**
30
+ * Callback function that's called when the input is completely filled.
31
+ */
32
+ onFulfill?: (value: string) => void;
33
+ /**
34
+ * Number of character for the input.
35
+ */
36
+ length?: number;
37
+ /*
38
+ * Whether the input is disabled.
39
+ */
40
+ disabled?: boolean;
41
+ /**
42
+ * Whether the pin value is secured using masks.
43
+ * By default, this is true, meaning that masks are shown to hide pin value.
44
+ */
45
+ secure?: boolean;
46
+ /**
47
+ * If true, focuses the input on componentDidMount.
48
+ */
49
+ autoFocus?: boolean;
50
+ /**
51
+ * Error message.
52
+ */
53
+ error?: string;
54
+ /**
55
+ * Additional style.
56
+ */
57
+ style?: StyleProp<ViewStyle>;
58
+ /**
59
+ * Testing id of the component.
60
+ */
61
+ testID?: string;
62
+ }
63
+
64
+ export function getState({
65
+ disabled,
66
+ error,
67
+ }: {
68
+ disabled?: boolean;
69
+ error?: string;
70
+ }): State {
71
+ if (disabled) {
72
+ return 'disabled';
73
+ }
74
+ if (error) {
75
+ return 'error';
76
+ }
77
+ return 'default';
78
+ }
79
+
80
+ function PinInput({
81
+ value = '',
82
+ onChangeText,
83
+ onFulfill,
84
+ length = 4,
85
+ disabled = false,
86
+ secure = true,
87
+ autoFocus = false,
88
+ error,
89
+ style,
90
+ testID,
91
+ }: PinInputProps) {
92
+ const inputRef = useRef<TextInput>(null);
93
+ const [focused, setFocused] = useState(autoFocus);
94
+ const state = getState({ disabled, error });
95
+
96
+ const focus = useCallback(() => {
97
+ if (inputRef?.current) {
98
+ inputRef.current.focus();
99
+ setFocused(true);
100
+ }
101
+ }, []);
102
+
103
+ const blur = useCallback(() => {
104
+ if (inputRef?.current) {
105
+ inputRef.current.blur();
106
+ setFocused(false);
107
+ }
108
+ }, []);
109
+
110
+ const changeText = useCallback((text: string) => {
111
+ const pin = (text.match(/[0-9]/g) || []).join('');
112
+ if (onChangeText) {
113
+ onChangeText(pin);
114
+ }
115
+ if (pin.length === length && onFulfill) {
116
+ onFulfill(pin);
117
+ }
118
+ }, []);
119
+
120
+ useEffect(() => {
121
+ // Must run after animations for keyboard to automatically open
122
+ if (autoFocus) {
123
+ InteractionManager.runAfterInteractions(focus);
124
+ }
125
+ }, [inputRef]);
126
+
127
+ return (
128
+ <StyledWrapper style={style} testID={testID}>
129
+ <StyledPinWrapper>
130
+ {[...Array(length).keys()].map(index => (
131
+ <React.Fragment key={index}>
132
+ {index !== 0 && <StyledSpacer />}
133
+ <PinCell
134
+ value={value.charAt(index)}
135
+ secure={secure}
136
+ focused={focused && index === value.length}
137
+ state={state}
138
+ />
139
+ </React.Fragment>
140
+ ))}
141
+ </StyledPinWrapper>
142
+ {state === 'error' && (
143
+ <StyledErrorContainer>
144
+ <Icon
145
+ icon="circle-info"
146
+ size="xsmall"
147
+ intent="danger"
148
+ testID="pin-error-icon"
149
+ />
150
+ <StyledErrorMessage>{error}</StyledErrorMessage>
151
+ </StyledErrorContainer>
152
+ )}
153
+ <StyledHiddenInput
154
+ themePinLength={length}
155
+ ref={inputRef}
156
+ value={value}
157
+ onChangeText={changeText}
158
+ secureTextEntry={secure}
159
+ editable={!disabled}
160
+ autoFocus={autoFocus}
161
+ onFocus={focus}
162
+ onBlur={blur}
163
+ maxLength={length}
164
+ keyboardType="numeric"
165
+ contextMenuHidden
166
+ caretHidden
167
+ testID="pin-hidden-input"
168
+ />
169
+ </StyledWrapper>
170
+ );
171
+ }
172
+
173
+ export default PinInput;
@@ -1,8 +1,8 @@
1
1
  import React from 'react';
2
2
  import { View } from 'react-native';
3
+ import Icon from '../../Icon';
3
4
 
4
5
  import Typography from '../../Typography';
5
- import Icon from '../../Icon';
6
6
  import { OptionWrapper } from '../StyledSelect';
7
7
 
8
8
  const Option = ({
@@ -1,8 +1,8 @@
1
1
  import React from 'react';
2
2
  import { MultiSelectProps } from '.';
3
- import { getKey } from '../helpers';
3
+
4
+ import StyledOptionList, { RenderItemProps } from '../StyledOptionList';
4
5
  import Option from './Option';
5
- import { OptionListWrapper, Spacer } from '../StyledSelect';
6
6
 
7
7
  interface OptionListProps<T> extends MultiSelectProps<T> {
8
8
  /**
@@ -12,32 +12,54 @@ interface OptionListProps<T> extends MultiSelectProps<T> {
12
12
  }
13
13
 
14
14
  const OptionList = <T,>({
15
- value,
16
- options,
17
- onPress,
18
15
  keyExtractor,
16
+ loading,
17
+ onEndReached,
18
+ onPress,
19
+ onQueryChange,
20
+ options,
21
+ value,
19
22
  }: Pick<
20
23
  OptionListProps<T>,
21
- 'value' | 'options' | 'onPress' | 'keyExtractor'
22
- >) => (
23
- <OptionListWrapper>
24
- {options.map((opt, index) => (
25
- <React.Fragment key={getKey(opt, index, keyExtractor)}>
26
- {index !== 0 && <Spacer />}
27
- <Option
28
- text={opt.text}
29
- selected={value.includes(opt.value)}
30
- onPress={() => {
31
- if (value.includes(opt.value)) {
32
- onPress(value.filter(val => val !== opt.value));
33
- } else {
34
- onPress([...value, opt.value]);
35
- }
36
- }}
37
- />
38
- </React.Fragment>
39
- ))}
40
- </OptionListWrapper>
41
- );
24
+ | 'keyExtractor'
25
+ | 'loading'
26
+ | 'onEndReached'
27
+ | 'onPress'
28
+ | 'onQueryChange'
29
+ | 'options'
30
+ | 'value'
31
+ >) => {
32
+ const firstValue = value?.[0];
33
+ const rawScrollIndex = firstValue
34
+ ? options.findIndex(option => option.value === firstValue)
35
+ : 0;
36
+ const scrollIndex = rawScrollIndex - 2 >= 0 ? rawScrollIndex - 2 : 0;
37
+
38
+ const RenderItem = React.memo(({ item }: RenderItemProps<T>) => (
39
+ <Option
40
+ text={item.text}
41
+ selected={value.includes(item.value)}
42
+ onPress={() => {
43
+ if (value.includes(item.value)) {
44
+ onPress(value.filter(val => val !== item.value));
45
+ } else {
46
+ onPress([...value, item.value]);
47
+ }
48
+ }}
49
+ />
50
+ ));
51
+
52
+ return (
53
+ <StyledOptionList<T>
54
+ keyExtractor={keyExtractor}
55
+ loading={loading}
56
+ onEndReached={onEndReached}
57
+ onQueryChange={onQueryChange}
58
+ options={options}
59
+ RenderItem={RenderItem}
60
+ scrollIndex={scrollIndex}
61
+ />
62
+ );
63
+ };
42
64
 
43
65
  export default OptionList;
@@ -39,4 +39,17 @@ describe('OptionList', () => {
39
39
  expect(pressFn).toBeCalledTimes(1);
40
40
  expect(pressFn).toHaveBeenCalledWith(['b']);
41
41
  });
42
+
43
+ it('render isLoading correctly', () => {
44
+ const pressFn = jest.fn();
45
+ const { toJSON } = renderWithTheme(
46
+ <OptionList
47
+ value={['a']}
48
+ options={mockOptions}
49
+ onPress={pressFn}
50
+ loading
51
+ />
52
+ );
53
+ expect(toJSON()).toMatchSnapshot();
54
+ });
42
55
  });