@hero-design/rn 7.9.0 → 7.10.2-rc.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 (62) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/assets/fonts/hero-icons.ttf +0 -0
  3. package/es/index.js +733 -252
  4. package/lib/assets/fonts/hero-icons.ttf +0 -0
  5. package/lib/index.js +732 -251
  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/Icon/HeroIcon/selection.json +1 -1
  24. package/src/components/Icon/IconList.ts +2 -0
  25. package/src/components/Select/MultiSelect/__tests__/__snapshots__/index.spec.tsx.snap +248 -94
  26. package/src/components/Select/SingleSelect/__tests__/__snapshots__/index.spec.tsx.snap +248 -94
  27. package/src/components/TextInput/StyledTextInput.tsx +133 -11
  28. package/src/components/TextInput/__tests__/.log/ti-10343.log +62 -0
  29. package/src/components/TextInput/__tests__/.log/tsserver.log +6983 -0
  30. package/src/components/TextInput/__tests__/StyledTextInput.spec.tsx +143 -7
  31. package/src/components/TextInput/__tests__/__snapshots__/StyledTextInput.spec.tsx.snap +922 -15
  32. package/src/components/TextInput/__tests__/__snapshots__/index.spec.tsx.snap +2561 -0
  33. package/src/components/TextInput/__tests__/index.spec.tsx +346 -11
  34. package/src/components/TextInput/index.tsx +235 -28
  35. package/src/theme/__tests__/__snapshots__/index.spec.ts.snap +69 -3
  36. package/src/theme/components/button.ts +6 -0
  37. package/src/theme/components/textInput.ts +62 -3
  38. package/src/theme/global/colors.ts +1 -0
  39. package/src/types.ts +8 -1
  40. package/types/components/Button/Button.d.ts +2 -2
  41. package/types/components/Button/LoadingIndicator/StyledLoadingIndicator.d.ts +1 -1
  42. package/types/components/Button/LoadingIndicator/index.d.ts +1 -1
  43. package/types/components/Button/StyledButton.d.ts +1 -1
  44. package/types/components/Button/UtilityButton/__tests__/index.spec.d.ts +1 -0
  45. package/types/components/Button/UtilityButton/index.d.ts +23 -0
  46. package/types/components/Button/UtilityButton/styled.d.ts +17 -0
  47. package/types/components/Button/index.d.ts +2 -0
  48. package/types/components/Icon/IconList.d.ts +1 -1
  49. package/types/components/Icon/utils.d.ts +1 -1
  50. package/types/components/TextInput/StyledTextInput.d.ts +82 -3
  51. package/types/components/TextInput/index.d.ts +33 -5
  52. package/types/theme/components/button.d.ts +6 -0
  53. package/types/theme/components/textInput.d.ts +61 -2
  54. package/types/theme/global/colors.d.ts +1 -0
  55. package/types/theme/global/index.d.ts +1 -0
  56. package/types/types.d.ts +2 -1
  57. package/.expo/README.md +0 -15
  58. package/.expo/packager-info.json +0 -10
  59. package/.expo/prebuild/cached-packages.json +0 -4
  60. package/.expo/settings.json +0 -10
  61. package/.expo/xcodebuild-error.log +0 -2
  62. package/.expo/xcodebuild.log +0 -11199
@@ -1,17 +1,352 @@
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
+ });
307
+ });
308
+
309
+ describe('defaultValue', () => {
310
+ describe('TextInput is idle', () => {
311
+ it('renders correctly', () => {
312
+ const wrapper = renderWithTheme(
313
+ <TextInput
314
+ label="Amount (AUD)"
315
+ prefix="dollar-sign"
316
+ required
317
+ helpText="This is helper text"
318
+ placeholder="Enter Amount"
319
+ defaultValue="1000"
320
+ maxLength={255}
321
+ />
322
+ );
323
+
324
+ expect(wrapper.toJSON()).toMatchSnapshot();
325
+ expect(wrapper.queryByDisplayValue('1000')).toBeTruthy();
326
+ expect(wrapper.queryByText('4/255')).toBeTruthy();
327
+ });
328
+ });
329
+
330
+ describe('default Value and Value', () => {
331
+ it('renders correctly with 2000', () => {
332
+ const wrapper = renderWithTheme(
333
+ <TextInput
334
+ label="Amount (AUD)"
335
+ prefix="dollar-sign"
336
+ required
337
+ helpText="This is helper text"
338
+ placeholder="Enter Amount"
339
+ defaultValue="1000"
340
+ value="2000"
341
+ maxLength={255}
342
+ />
343
+ );
344
+
345
+ expect(wrapper.toJSON()).toMatchSnapshot();
346
+ expect(wrapper.queryByDisplayValue('2000')).toBeTruthy();
347
+ expect(wrapper.queryByDisplayValue('1000')).toBeFalsy();
348
+ expect(wrapper.queryByText('4/255')).toBeTruthy();
349
+ });
350
+ });
16
351
  });
17
352
  });
@@ -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,154 @@ 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,
138
+ defaultValue,
50
139
  ...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
- );
140
+ }: TextInputProps): JSX.Element => {
141
+ const textInputReference = useRef<RNTextInput | null>(null);
142
+ const displayText = value || defaultValue || '';
143
+
144
+ const isEmptyValue = displayText.length === 0;
145
+
146
+ const [isFocused, setIsFocused] = React.useState(false);
147
+
148
+ const variant = getVariant({
149
+ disabled,
150
+ error,
151
+ editable,
152
+ isFocused,
153
+ isEmptyValue,
154
+ });
155
+
156
+ const shouldShowMaxLength = maxLength !== undefined;
157
+
158
+ const theme = useTheme();
159
+ return (
160
+ <StyledContainer
161
+ style={style}
162
+ pointerEvents={variant === 'disabled' ? 'none' : 'auto'}
163
+ testID={testID}
164
+ >
165
+ <StyledTextInputContainer>
166
+ <StyledBorderBackDrop themeVariant={variant} />
167
+ {(isFocused || (label && !isEmptyValue)) && (
168
+ <StyledLabelContainer pointerEvents="none">
169
+ {required && (
170
+ <StyledAsteriskLabel themeVariant={variant} fontSize="small">
171
+ *
172
+ </StyledAsteriskLabel>
173
+ )}
174
+ <StyledLabel
175
+ nativeID={accessibilityLabelledBy}
176
+ testID="input-label"
177
+ fontSize="small"
178
+ themeVariant={variant}
179
+ >
180
+ {label}
181
+ </StyledLabel>
182
+ </StyledLabelContainer>
183
+ )}
184
+ {typeof prefix === 'string' ? (
185
+ <Icon
186
+ intent={disabled ? 'disabled-text' : 'text'}
187
+ testID="input-prefix"
188
+ icon={prefix}
189
+ size="xsmall"
190
+ />
191
+ ) : (
192
+ prefix
193
+ )}
194
+
195
+ <StyledTextInputAndLabelContainer>
196
+ {!isFocused && isEmptyValue && (
197
+ <StyledLabelContainerInsideTextInput pointerEvents="none">
198
+ {required && (
199
+ <StyledAsteriskLabelInsideTextInput themeVariant={variant}>
200
+ *
201
+ </StyledAsteriskLabelInsideTextInput>
202
+ )}
203
+ <StyledLabelInsideTextInput
204
+ nativeID={accessibilityLabelledBy}
205
+ testID="input-label"
206
+ fontSize="medium"
207
+ themeVariant={variant}
208
+ >
209
+ {label}
210
+ </StyledLabelInsideTextInput>
211
+ </StyledLabelContainerInsideTextInput>
212
+ )}
213
+ <StyledTextInput
214
+ // when input is not editable on Android, the text color is gray
215
+ // hence, adding this to make the text color the same as iOS
216
+ style={StyleSheet.flatten([
217
+ { color: theme.__hd__.textInput.colors.text },
218
+ textStyle,
219
+ ])}
220
+ testID="text-input"
221
+ accessibilityState={{ disabled }}
222
+ // @ts-ignore
223
+ accessibilityLabelledBy={accessibilityLabelledBy}
224
+ {...nativeProps}
225
+ onFocus={event => {
226
+ setIsFocused(true);
227
+ nativeProps.onFocus?.(event);
228
+ }}
229
+ onBlur={event => {
230
+ setIsFocused(false);
231
+ nativeProps.onBlur?.(event);
232
+ }}
233
+ ref={textInputReference}
234
+ editable={editable}
235
+ maxLength={maxLength}
236
+ value={value}
237
+ onChangeText={text => {
238
+ nativeProps.onChangeText?.(text);
239
+ }}
240
+ defaultValue={defaultValue}
241
+ placeholder={
242
+ variant === 'focused' ? nativeProps.placeholder : undefined
243
+ }
244
+ />
245
+ </StyledTextInputAndLabelContainer>
246
+ {typeof suffix === 'string' ? (
247
+ <Icon
248
+ intent={disabled ? 'disabled-text' : 'text'}
249
+ testID="input-suffix"
250
+ icon={suffix}
251
+ size="xsmall"
252
+ />
253
+ ) : (
254
+ suffix
255
+ )}
256
+ </StyledTextInputContainer>
257
+ <StyledErrorAndHelpTextContainer>
258
+ {error && (
259
+ <StyledErrorContainer>
260
+ <Icon
261
+ testID="input-error-icon"
262
+ icon="circle-info"
263
+ size="xsmall"
264
+ intent="danger"
265
+ />
266
+
267
+ <StyledError testID="input-error-message">{error}</StyledError>
268
+ </StyledErrorContainer>
269
+ )}
270
+ {shouldShowMaxLength && (
271
+ <StyledMaxLengthMessage themeVariant={variant} fontSize="small">
272
+ {displayText.length}/{maxLength}
273
+ </StyledMaxLengthMessage>
274
+ )}
275
+ {helpText && <StyledHelperText>{helpText}</StyledHelperText>}
276
+ </StyledErrorAndHelpTextContainer>
277
+ </StyledContainer>
278
+ );
279
+ };
73
280
 
74
281
  export default TextInput;