@hero-design/rn 7.8.0 → 7.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/.turbo/turbo-build.log +8 -8
  2. package/assets/fonts/hero-icons.ttf +0 -0
  3. package/es/index.js +741 -258
  4. package/lib/assets/fonts/hero-icons.ttf +0 -0
  5. package/lib/index.js +740 -257
  6. package/package.json +2 -2
  7. package/src/components/Button/Button.tsx +10 -2
  8. package/src/components/Button/LoadingIndicator/StyledLoadingIndicator.tsx +7 -1
  9. package/src/components/Button/LoadingIndicator/__tests__/StyledLoadingIndicator.spec.tsx +3 -0
  10. package/src/components/Button/LoadingIndicator/__tests__/__snapshots__/StyledLoadingIndicator.spec.tsx.snap +60 -0
  11. package/src/components/Button/LoadingIndicator/__tests__/__snapshots__/index.spec.tsx.snap +363 -0
  12. package/src/components/Button/LoadingIndicator/__tests__/index.spec.tsx +3 -0
  13. package/src/components/Button/LoadingIndicator/index.tsx +4 -1
  14. package/src/components/Button/StyledButton.tsx +57 -1
  15. package/src/components/Button/UtilityButton/__tests__/__snapshots__/index.spec.tsx.snap +167 -0
  16. package/src/components/Button/UtilityButton/__tests__/index.spec.tsx +55 -0
  17. package/src/components/Button/UtilityButton/index.tsx +53 -0
  18. package/src/components/Button/UtilityButton/styled.tsx +25 -0
  19. package/src/components/Button/__tests__/Button.spec.tsx +3 -0
  20. package/src/components/Button/__tests__/StyledButton.spec.tsx +18 -0
  21. package/src/components/Button/__tests__/__snapshots__/StyledButton.spec.tsx.snap +468 -0
  22. package/src/components/Button/index.tsx +3 -0
  23. package/src/components/Card/DataCard/StyledDataCard.tsx +1 -3
  24. package/src/components/Card/DataCard/__tests__/__snapshots__/StyledDataCard.spec.tsx.snap +0 -1
  25. package/src/components/Card/DataCard/__tests__/__snapshots__/index.spec.tsx.snap +0 -5
  26. package/src/components/Card/StyledCard.tsx +1 -3
  27. package/src/components/Card/__tests__/__snapshots__/StyledCard.spec.tsx.snap +0 -1
  28. package/src/components/Icon/HeroIcon/index.tsx +3 -1
  29. package/src/components/Icon/HeroIcon/selection.json +1 -1
  30. package/src/components/Icon/IconList.ts +2 -0
  31. package/src/components/Icon/index.tsx +2 -1
  32. package/src/components/Select/MultiSelect/__tests__/__snapshots__/index.spec.tsx.snap +248 -94
  33. package/src/components/Select/SingleSelect/__tests__/__snapshots__/index.spec.tsx.snap +248 -94
  34. package/src/components/TextInput/StyledTextInput.tsx +133 -11
  35. package/src/components/TextInput/__tests__/StyledTextInput.spec.tsx +143 -7
  36. package/src/components/TextInput/__tests__/__snapshots__/StyledTextInput.spec.tsx.snap +922 -15
  37. package/src/components/TextInput/__tests__/__snapshots__/index.spec.tsx.snap +2078 -0
  38. package/src/components/TextInput/__tests__/index.spec.tsx +302 -11
  39. package/src/components/TextInput/index.tsx +232 -28
  40. package/src/theme/__tests__/__snapshots__/index.spec.ts.snap +73 -3
  41. package/src/theme/components/button.ts +6 -0
  42. package/src/theme/components/card.ts +5 -1
  43. package/src/theme/components/icon.ts +1 -0
  44. package/src/theme/components/textInput.ts +62 -3
  45. package/src/theme/global/colors.ts +1 -0
  46. package/src/types.ts +8 -1
  47. package/types/components/Button/Button.d.ts +2 -2
  48. package/types/components/Button/LoadingIndicator/StyledLoadingIndicator.d.ts +1 -1
  49. package/types/components/Button/LoadingIndicator/index.d.ts +1 -1
  50. package/types/components/Button/StyledButton.d.ts +1 -1
  51. package/types/components/{Select/MultiSelect/__tests__/StyledMultiSelect.spec.d.ts → Button/UtilityButton/__tests__/index.spec.d.ts} +0 -0
  52. package/types/components/Button/UtilityButton/index.d.ts +23 -0
  53. package/types/components/Button/UtilityButton/styled.d.ts +17 -0
  54. package/types/components/Button/index.d.ts +2 -0
  55. package/types/components/Icon/HeroIcon/index.d.ts +1 -1
  56. package/types/components/Icon/IconList.d.ts +1 -1
  57. package/types/components/Icon/index.d.ts +1 -1
  58. package/types/components/Icon/utils.d.ts +1 -1
  59. package/types/components/TextInput/StyledTextInput.d.ts +82 -3
  60. package/types/components/TextInput/index.d.ts +33 -5
  61. package/types/theme/components/button.d.ts +6 -0
  62. package/types/theme/components/card.d.ts +3 -0
  63. package/types/theme/components/icon.d.ts +1 -0
  64. package/types/theme/components/textInput.d.ts +61 -2
  65. package/types/theme/global/colors.d.ts +1 -0
  66. package/types/theme/global/index.d.ts +1 -0
  67. package/types/types.d.ts +2 -1
  68. package/.expo/README.md +0 -15
  69. package/.expo/packager-info.json +0 -10
  70. package/.expo/prebuild/cached-packages.json +0 -4
  71. package/.expo/settings.json +0 -10
  72. package/.expo/xcodebuild-error.log +0 -2
  73. package/.expo/xcodebuild.log +0 -11199
  74. package/types/components/Select/MultiSelect/Footer.d.ts +0 -5
  75. package/types/components/Select/MultiSelect/StyledMultiSelect.d.ts +0 -26
  76. package/types/components/Select/MultiSelect/types.d.ts +0 -5
@@ -1,17 +1,308 @@
1
+ import { fireEvent, within } from '@testing-library/react-native';
1
2
  import React from 'react';
2
3
  import renderWithTheme from '../../../testHelpers/renderWithTheme';
3
- import TextInput from '../index';
4
+ import Icon from '../../Icon';
5
+ import TextInput, { getVariant } from '../index';
6
+
7
+ describe('getVariant', () => {
8
+ it.each`
9
+ disabled | error | editable | isFocused | isEmptyValue | expected
10
+ ${false} | ${undefined} | ${true} | ${false} | ${true} | ${'default'}
11
+ ${false} | ${undefined} | ${true} | ${false} | ${false} | ${'filled'}
12
+ ${false} | ${undefined} | ${true} | ${true} | ${true} | ${'focused'}
13
+ ${false} | ${undefined} | ${false} | ${true} | ${true} | ${'readonly'}
14
+ ${false} | ${'This field is required'} | ${false} | ${true} | ${true} | ${'error'}
15
+ ${true} | ${'This field is required'} | ${false} | ${true} | ${true} | ${'disabled'}
16
+ `(
17
+ 'should return the correct variant when disabled $disabled, errorMessage $errorMessage, editable $false, isFocused $isFocused, isEmptyValue $isEmptyValue',
18
+ ({ disabled, error, editable, isFocused, isEmptyValue, expected }) => {
19
+ expect(
20
+ getVariant({
21
+ disabled,
22
+ error,
23
+ editable,
24
+ isFocused,
25
+ isEmptyValue,
26
+ })
27
+ ).toBe(expected);
28
+ }
29
+ );
30
+ });
4
31
 
5
32
  describe('TextInput', () => {
6
- it('renders label, prefix and native TextInput', () => {
7
- const { queryAllByTestId, queryAllByText } = renderWithTheme(
8
- <TextInput label="Input Label" prefix="user" suffix="arrow-down" />
9
- );
10
-
11
- expect(queryAllByText('Input Label')).toHaveLength(1);
12
- expect(queryAllByTestId('input-label')).toHaveLength(1);
13
- expect(queryAllByTestId('input-prefix')).toHaveLength(1);
14
- expect(queryAllByTestId('input-suffix')).toHaveLength(1);
15
- expect(queryAllByTestId('text-input')).toHaveLength(1);
33
+ describe('idle', () => {
34
+ it('renders correctly', () => {
35
+ const { getByTestId, toJSON } = renderWithTheme(
36
+ <TextInput
37
+ label="Amount (AUD)"
38
+ prefix="dollar-sign"
39
+ suffix="arrow-down"
40
+ testID="idle-text-input"
41
+ />
42
+ );
43
+
44
+ expect(toJSON()).toMatchSnapshot();
45
+ expect(getByTestId('idle-text-input')).toBeTruthy();
46
+ expect(
47
+ within(getByTestId('idle-text-input')).queryAllByTestId('text-input')
48
+ ).toHaveLength(1);
49
+ expect(
50
+ within(getByTestId('idle-text-input')).queryAllByText('Amount (AUD)')
51
+ ).toHaveLength(1);
52
+ expect(
53
+ within(getByTestId('idle-text-input')).queryAllByTestId('input-label')
54
+ ).toHaveLength(1);
55
+ expect(
56
+ within(getByTestId('idle-text-input')).queryAllByTestId('input-prefix')
57
+ ).toHaveLength(1);
58
+ expect(
59
+ within(getByTestId('idle-text-input')).queryAllByTestId('input-suffix')
60
+ ).toHaveLength(1);
61
+ });
62
+
63
+ it('onChangeText, onBlur, onFocus', () => {
64
+ const onChangeText = jest.fn();
65
+ const onBlur = jest.fn();
66
+ const onFocus = jest.fn();
67
+ const { getByTestId } = renderWithTheme(
68
+ <TextInput
69
+ label="Amount (AUD)"
70
+ prefix="dollar-sign"
71
+ suffix="arrow-down"
72
+ testID="idle-text-input"
73
+ onChangeText={onChangeText}
74
+ onBlur={onBlur}
75
+ onFocus={onFocus}
76
+ />
77
+ );
78
+
79
+ const testInput = within(getByTestId('idle-text-input')).getByTestId(
80
+ 'text-input'
81
+ );
82
+
83
+ fireEvent.changeText(testInput, 'Thong Quach');
84
+ expect(onChangeText).toHaveBeenCalledWith('Thong Quach');
85
+
86
+ fireEvent(testInput, 'blur');
87
+ expect(onBlur).toHaveBeenCalledTimes(1);
88
+
89
+ fireEvent(testInput, 'focus');
90
+ expect(onFocus).toHaveBeenCalledTimes(1);
91
+ });
92
+ });
93
+
94
+ describe('idle with suffix and prefix are React Element', () => {
95
+ it('renders correctly', () => {
96
+ const { toJSON, queryAllByTestId, queryAllByText } = renderWithTheme(
97
+ <TextInput
98
+ label="Amount (AUD)"
99
+ prefix={<Icon icon="eye-circle" testID="prefix-element" />}
100
+ suffix={<Icon icon="eye-invisible" testID="suffix-element" />}
101
+ required
102
+ />
103
+ );
104
+
105
+ expect(toJSON()).toMatchSnapshot();
106
+ expect(queryAllByText('Amount (AUD)')).toHaveLength(1);
107
+ expect(queryAllByText('*')).toHaveLength(1);
108
+ expect(queryAllByTestId('input-label')).toHaveLength(1);
109
+ expect(queryAllByTestId('prefix-element')).toHaveLength(1);
110
+ expect(queryAllByTestId('suffix-element')).toHaveLength(1);
111
+ expect(queryAllByTestId('text-input')).toHaveLength(1);
112
+ });
113
+ });
114
+ describe('required', () => {
115
+ it('renders correctly', () => {
116
+ const { toJSON, queryAllByTestId, queryAllByText } = renderWithTheme(
117
+ <TextInput
118
+ label="Amount (AUD)"
119
+ prefix="dollar-sign"
120
+ suffix="arrow-down"
121
+ required
122
+ />
123
+ );
124
+
125
+ expect(toJSON()).toMatchSnapshot();
126
+ expect(queryAllByText('Amount (AUD)')).toHaveLength(1);
127
+ expect(queryAllByText('*')).toHaveLength(1);
128
+ expect(queryAllByTestId('input-label')).toHaveLength(1);
129
+ expect(queryAllByTestId('input-prefix')).toHaveLength(1);
130
+ expect(queryAllByTestId('input-suffix')).toHaveLength(1);
131
+ expect(queryAllByTestId('text-input')).toHaveLength(1);
132
+ });
133
+ });
134
+
135
+ describe('filled', () => {
136
+ it('renders correctly', () => {
137
+ const {
138
+ toJSON,
139
+ queryAllByTestId,
140
+ queryAllByText,
141
+ queryAllByDisplayValue,
142
+ } = renderWithTheme(
143
+ <TextInput
144
+ label="Amount (AUD)"
145
+ prefix="dollar-sign"
146
+ suffix="arrow-down"
147
+ value="100"
148
+ />
149
+ );
150
+
151
+ expect(toJSON()).toMatchSnapshot();
152
+ expect(queryAllByText('Amount (AUD)')).toHaveLength(1);
153
+ expect(queryAllByDisplayValue('100')).toHaveLength(1);
154
+ expect(queryAllByTestId('input-label')).toHaveLength(1);
155
+ expect(queryAllByTestId('input-prefix')).toHaveLength(1);
156
+ expect(queryAllByTestId('input-suffix')).toHaveLength(1);
157
+ expect(queryAllByTestId('text-input')).toHaveLength(1);
158
+ });
159
+ });
160
+
161
+ describe('readonly', () => {
162
+ it('renders correctly', () => {
163
+ const onChangeText = jest.fn();
164
+ const {
165
+ toJSON,
166
+ queryAllByTestId,
167
+ queryAllByText,
168
+ queryAllByDisplayValue,
169
+ getByTestId,
170
+ } = renderWithTheme(
171
+ <TextInput
172
+ label="Amount (AUD)"
173
+ prefix="dollar-sign"
174
+ suffix="arrow-down"
175
+ editable={false}
176
+ value="100"
177
+ required
178
+ onChangeText={onChangeText}
179
+ />
180
+ );
181
+
182
+ expect(toJSON()).toMatchSnapshot();
183
+ expect(queryAllByText('Amount (AUD)')).toHaveLength(1);
184
+ expect(queryAllByDisplayValue('100')).toHaveLength(1);
185
+ expect(queryAllByTestId('input-label')).toHaveLength(1);
186
+ expect(queryAllByTestId('input-prefix')).toHaveLength(1);
187
+ expect(queryAllByTestId('input-suffix')).toHaveLength(1);
188
+ expect(queryAllByTestId('text-input')).toHaveLength(1);
189
+
190
+ expect(getByTestId('text-input')).not.toHaveProp('editable', 'false');
191
+ });
192
+ });
193
+
194
+ describe('max length', () => {
195
+ it('renders correctly', () => {
196
+ const {
197
+ toJSON,
198
+ queryAllByTestId,
199
+ queryAllByText,
200
+ queryAllByDisplayValue,
201
+ getByTestId,
202
+ } = renderWithTheme(
203
+ <TextInput
204
+ label="Shout out"
205
+ value="shout out Tung Van"
206
+ required
207
+ maxLength={255}
208
+ multiline
209
+ error="must not exceed character limit"
210
+ />
211
+ );
212
+
213
+ expect(toJSON()).toMatchSnapshot();
214
+ expect(queryAllByText('Shout out')).toHaveLength(1);
215
+ expect(queryAllByDisplayValue('shout out Tung Van')).toHaveLength(1);
216
+ expect(queryAllByText('18/255')).toHaveLength(1);
217
+ expect(queryAllByTestId('input-label')).toHaveLength(1);
218
+ expect(queryAllByTestId('text-input')).toHaveLength(1);
219
+
220
+ expect(getByTestId('text-input')).not.toHaveProp('multiline', 'true');
221
+ });
222
+ });
223
+
224
+ describe('disabled', () => {
225
+ it('renders correctly', () => {
226
+ const {
227
+ toJSON,
228
+ queryAllByTestId,
229
+ queryAllByText,
230
+ getByTestId,
231
+ } = renderWithTheme(
232
+ <TextInput
233
+ label="Amount (AUD)"
234
+ required
235
+ disabled
236
+ value="100"
237
+ testID="disabled-text-input"
238
+ />
239
+ );
240
+
241
+ expect(toJSON()).toMatchSnapshot();
242
+ expect(queryAllByText('Amount (AUD)')).toHaveLength(1);
243
+ expect(queryAllByTestId('input-label')).toHaveLength(1);
244
+ expect(queryAllByTestId('text-input')).toHaveLength(1);
245
+ expect(getByTestId('disabled-text-input')).toBeDisabled();
246
+
247
+ expect(getByTestId('text-input')).not.toHaveProp('multiline', 'true');
248
+ });
249
+ });
250
+
251
+ describe('error', () => {
252
+ it('renders correctly', () => {
253
+ const { toJSON, queryAllByText } = renderWithTheme(
254
+ <TextInput
255
+ label="Amount (AUD)"
256
+ prefix="dollar-sign"
257
+ required
258
+ error="This field is required"
259
+ />
260
+ );
261
+
262
+ expect(toJSON()).toMatchSnapshot();
263
+ expect(queryAllByText('Amount (AUD)')).toHaveLength(1);
264
+ expect(queryAllByText('This field is required')).toHaveLength(1);
265
+ });
266
+ });
267
+ describe('helper text', () => {
268
+ it('renders correctly', () => {
269
+ const { toJSON, queryAllByText } = renderWithTheme(
270
+ <TextInput
271
+ label="Amount (AUD)"
272
+ prefix="dollar-sign"
273
+ required
274
+ helpText="This is helper text"
275
+ />
276
+ );
277
+
278
+ expect(toJSON()).toMatchSnapshot();
279
+ expect(queryAllByText('Amount (AUD)')).toHaveLength(1);
280
+ expect(queryAllByText('This is helper text')).toHaveLength(1);
281
+ });
282
+ });
283
+
284
+ describe('placeholder', () => {
285
+ describe('TextInput is idle', () => {
286
+ it('renders correctly', () => {
287
+ const wrapper = renderWithTheme(
288
+ <TextInput
289
+ label="Amount (AUD)"
290
+ prefix="dollar-sign"
291
+ required
292
+ helpText="This is helper text"
293
+ placeholder="Enter Amount"
294
+ />
295
+ );
296
+
297
+ expect(wrapper.toJSON()).toMatchSnapshot();
298
+ expect(wrapper.queryByPlaceholderText('Enter Amount')).toBeFalsy();
299
+
300
+ fireEvent(wrapper.getByTestId('text-input'), 'focus');
301
+ expect(wrapper.queryByPlaceholderText('Enter Amount')).toBeTruthy();
302
+
303
+ fireEvent(wrapper.getByTestId('text-input'), 'blur');
304
+ expect(wrapper.queryByPlaceholderText('Enter Amount')).toBeFalsy();
305
+ });
306
+ });
16
307
  });
17
308
  });
@@ -1,12 +1,33 @@
1
- import React from 'react';
1
+ import React, { useRef } from 'react';
2
2
  import {
3
3
  TextInputProps as NativeTextInputProps,
4
4
  StyleProp,
5
5
  ViewStyle,
6
6
  TextStyle,
7
+ TextInput as RNTextInput,
8
+ StyleSheet,
7
9
  } from 'react-native';
8
- import { Container, Label, StyledTextInput } from './StyledTextInput';
10
+ import {
11
+ StyledTextInputContainer,
12
+ StyledLabel,
13
+ StyledLabelContainer,
14
+ StyledLabelInsideTextInput,
15
+ StyledAsteriskLabel,
16
+ StyledError,
17
+ StyledTextInput,
18
+ Variant,
19
+ StyledContainer,
20
+ StyledMaxLengthMessage,
21
+ StyledErrorContainer,
22
+ StyledHelperText,
23
+ StyledAsteriskLabelInsideTextInput,
24
+ StyledTextInputAndLabelContainer,
25
+ StyledLabelContainerInsideTextInput,
26
+ StyledErrorAndHelpTextContainer,
27
+ StyledBorderBackDrop,
28
+ } from './StyledTextInput';
9
29
  import Icon, { IconName } from '../Icon';
30
+ import { useTheme } from '../../theme';
10
31
 
11
32
  export interface TextInputProps extends NativeTextInputProps {
12
33
  /**
@@ -14,13 +35,13 @@ export interface TextInputProps extends NativeTextInputProps {
14
35
  */
15
36
  label?: string;
16
37
  /**
17
- * Name of Icon to render on the left side of the input, before the user's cursor.
38
+ * Name of Icon or ReactElement to render on the left side of the input, before the user's cursor.
18
39
  */
19
- prefix?: IconName;
40
+ prefix?: IconName | React.ReactElement;
20
41
  /**
21
- * Name of Icon to render on the right side of the input.
42
+ * Name of Icon or ReactElement to render on the right side of the input.
22
43
  */
23
- suffix?: IconName;
44
+ suffix?: IconName | React.ReactElement;
24
45
  /**
25
46
  * Additional wrapper style.
26
47
  */
@@ -37,8 +58,68 @@ export interface TextInputProps extends NativeTextInputProps {
37
58
  * Accessibility label for the input (Android).
38
59
  */
39
60
  accessibilityLabelledBy?: string;
61
+ /**
62
+ * Error message to display.
63
+ */
64
+ error?: string;
65
+ /**
66
+ * Whether the input is required, if true, an asterisk will be appended to the label.
67
+ * */
68
+ required?: boolean;
69
+ /**
70
+ * Placeholder text to display.
71
+ * */
72
+ placeholder?: string;
73
+ /**
74
+ * Whether the input is editable.
75
+ * */
76
+ editable?: boolean;
77
+ /*
78
+ * Whether the input is disabled.
79
+ */
80
+ disabled?: boolean;
81
+ /*
82
+ * The max length of the input.
83
+ * If the max length is set, the input will display the current length and the max length.
84
+ * */
85
+ maxLength?: number;
86
+ /*
87
+ * The helper text to display.
88
+ */
89
+ helpText?: string;
40
90
  }
41
91
 
92
+ export const getVariant = ({
93
+ disabled,
94
+ error,
95
+ editable,
96
+ isFocused,
97
+ isEmptyValue,
98
+ }: {
99
+ disabled?: boolean;
100
+ error?: string;
101
+ editable?: boolean;
102
+ isFocused?: boolean;
103
+ isEmptyValue?: boolean;
104
+ }): Variant => {
105
+ if (disabled) {
106
+ return 'disabled';
107
+ }
108
+ if (error) {
109
+ return 'error';
110
+ }
111
+ if (!editable) {
112
+ return 'readonly';
113
+ }
114
+ if (isFocused) {
115
+ return 'focused';
116
+ }
117
+ if (!isEmptyValue) {
118
+ return 'filled';
119
+ }
120
+ return 'default';
121
+ };
122
+
42
123
  const TextInput = ({
43
124
  label,
44
125
  prefix,
@@ -47,28 +128,151 @@ const TextInput = ({
47
128
  textStyle,
48
129
  testID,
49
130
  accessibilityLabelledBy,
131
+ error,
132
+ required,
133
+ editable = true,
134
+ disabled = false,
135
+ maxLength,
136
+ helpText,
137
+ value = '',
50
138
  ...nativeProps
51
- }: TextInputProps) => (
52
- <Container style={style} testID={testID}>
53
- {label && (
54
- <Label
55
- nativeID={accessibilityLabelledBy}
56
- testID="input-label"
57
- fontSize="small"
58
- >
59
- {label}
60
- </Label>
61
- )}
62
- {prefix && <Icon testID="input-prefix" icon={prefix} size="xsmall" />}
63
- <StyledTextInput
64
- style={textStyle}
65
- testID="text-input"
66
- // @ts-ignore
67
- accessibilityLabelledBy={accessibilityLabelledBy}
68
- {...nativeProps}
69
- />
70
- {suffix && <Icon testID="input-suffix" icon={suffix} size="xsmall" />}
71
- </Container>
72
- );
139
+ }: TextInputProps): JSX.Element => {
140
+ const textInputReference = useRef<RNTextInput | null>(null);
141
+
142
+ const isEmptyValue = value.length === 0;
143
+
144
+ const [isFocused, setIsFocused] = React.useState(false);
145
+
146
+ const variant = getVariant({
147
+ disabled,
148
+ error,
149
+ editable,
150
+ isFocused,
151
+ isEmptyValue,
152
+ });
153
+
154
+ const shouldShowMaxLength = maxLength !== undefined;
155
+
156
+ const theme = useTheme();
157
+ return (
158
+ <StyledContainer
159
+ style={style}
160
+ pointerEvents={variant === 'disabled' ? 'none' : 'auto'}
161
+ testID={testID}
162
+ >
163
+ <StyledTextInputContainer>
164
+ <StyledBorderBackDrop themeVariant={variant} />
165
+ {(isFocused || (label && !isEmptyValue)) && (
166
+ <StyledLabelContainer pointerEvents="none">
167
+ {required && (
168
+ <StyledAsteriskLabel themeVariant={variant} fontSize="small">
169
+ *
170
+ </StyledAsteriskLabel>
171
+ )}
172
+ <StyledLabel
173
+ nativeID={accessibilityLabelledBy}
174
+ testID="input-label"
175
+ fontSize="small"
176
+ themeVariant={variant}
177
+ >
178
+ {label}
179
+ </StyledLabel>
180
+ </StyledLabelContainer>
181
+ )}
182
+ {typeof prefix === 'string' ? (
183
+ <Icon
184
+ intent={disabled ? 'disabled-text' : 'text'}
185
+ testID="input-prefix"
186
+ icon={prefix}
187
+ size="xsmall"
188
+ />
189
+ ) : (
190
+ prefix
191
+ )}
192
+
193
+ <StyledTextInputAndLabelContainer>
194
+ {!isFocused && isEmptyValue && (
195
+ <StyledLabelContainerInsideTextInput pointerEvents="none">
196
+ {required && (
197
+ <StyledAsteriskLabelInsideTextInput themeVariant={variant}>
198
+ *
199
+ </StyledAsteriskLabelInsideTextInput>
200
+ )}
201
+ <StyledLabelInsideTextInput
202
+ nativeID={accessibilityLabelledBy}
203
+ testID="input-label"
204
+ fontSize="medium"
205
+ themeVariant={variant}
206
+ >
207
+ {label}
208
+ </StyledLabelInsideTextInput>
209
+ </StyledLabelContainerInsideTextInput>
210
+ )}
211
+ <StyledTextInput
212
+ // when input is not editable on Android, the text color is gray
213
+ // hence, adding this to make the text color the same as iOS
214
+ style={StyleSheet.flatten([
215
+ { color: theme.__hd__.textInput.colors.text },
216
+ textStyle,
217
+ ])}
218
+ testID="text-input"
219
+ accessibilityState={{ disabled }}
220
+ // @ts-ignore
221
+ accessibilityLabelledBy={accessibilityLabelledBy}
222
+ {...nativeProps}
223
+ onFocus={event => {
224
+ setIsFocused(true);
225
+ nativeProps.onFocus?.(event);
226
+ }}
227
+ onBlur={event => {
228
+ setIsFocused(false);
229
+ nativeProps.onBlur?.(event);
230
+ }}
231
+ ref={textInputReference}
232
+ editable={editable}
233
+ maxLength={maxLength}
234
+ value={value}
235
+ onChangeText={text => {
236
+ nativeProps.onChangeText?.(text);
237
+ }}
238
+ placeholder={
239
+ variant === 'focused' ? nativeProps.placeholder : undefined
240
+ }
241
+ />
242
+ </StyledTextInputAndLabelContainer>
243
+ {typeof suffix === 'string' ? (
244
+ <Icon
245
+ intent={disabled ? 'disabled-text' : 'text'}
246
+ testID="input-suffix"
247
+ icon={suffix}
248
+ size="xsmall"
249
+ />
250
+ ) : (
251
+ suffix
252
+ )}
253
+ </StyledTextInputContainer>
254
+ <StyledErrorAndHelpTextContainer>
255
+ {error && (
256
+ <StyledErrorContainer>
257
+ <Icon
258
+ testID="input-error-icon"
259
+ icon="circle-info"
260
+ size="xsmall"
261
+ intent="danger"
262
+ />
263
+
264
+ <StyledError testID="input-error-message">{error}</StyledError>
265
+ </StyledErrorContainer>
266
+ )}
267
+ {shouldShowMaxLength && (
268
+ <StyledMaxLengthMessage themeVariant={variant} fontSize="small">
269
+ {value.length}/{maxLength}
270
+ </StyledMaxLengthMessage>
271
+ )}
272
+ {helpText && <StyledHelperText>{helpText}</StyledHelperText>}
273
+ </StyledErrorAndHelpTextContainer>
274
+ </StyledContainer>
275
+ );
276
+ };
73
277
 
74
278
  export default TextInput;