@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.
- package/.turbo/turbo-build.log +8 -8
- package/assets/fonts/hero-icons.ttf +0 -0
- package/es/index.js +741 -258
- package/lib/assets/fonts/hero-icons.ttf +0 -0
- package/lib/index.js +740 -257
- package/package.json +2 -2
- package/src/components/Button/Button.tsx +10 -2
- package/src/components/Button/LoadingIndicator/StyledLoadingIndicator.tsx +7 -1
- package/src/components/Button/LoadingIndicator/__tests__/StyledLoadingIndicator.spec.tsx +3 -0
- package/src/components/Button/LoadingIndicator/__tests__/__snapshots__/StyledLoadingIndicator.spec.tsx.snap +60 -0
- package/src/components/Button/LoadingIndicator/__tests__/__snapshots__/index.spec.tsx.snap +363 -0
- package/src/components/Button/LoadingIndicator/__tests__/index.spec.tsx +3 -0
- package/src/components/Button/LoadingIndicator/index.tsx +4 -1
- package/src/components/Button/StyledButton.tsx +57 -1
- package/src/components/Button/UtilityButton/__tests__/__snapshots__/index.spec.tsx.snap +167 -0
- package/src/components/Button/UtilityButton/__tests__/index.spec.tsx +55 -0
- package/src/components/Button/UtilityButton/index.tsx +53 -0
- package/src/components/Button/UtilityButton/styled.tsx +25 -0
- package/src/components/Button/__tests__/Button.spec.tsx +3 -0
- package/src/components/Button/__tests__/StyledButton.spec.tsx +18 -0
- package/src/components/Button/__tests__/__snapshots__/StyledButton.spec.tsx.snap +468 -0
- package/src/components/Button/index.tsx +3 -0
- package/src/components/Card/DataCard/StyledDataCard.tsx +1 -3
- package/src/components/Card/DataCard/__tests__/__snapshots__/StyledDataCard.spec.tsx.snap +0 -1
- package/src/components/Card/DataCard/__tests__/__snapshots__/index.spec.tsx.snap +0 -5
- package/src/components/Card/StyledCard.tsx +1 -3
- package/src/components/Card/__tests__/__snapshots__/StyledCard.spec.tsx.snap +0 -1
- package/src/components/Icon/HeroIcon/index.tsx +3 -1
- package/src/components/Icon/HeroIcon/selection.json +1 -1
- package/src/components/Icon/IconList.ts +2 -0
- package/src/components/Icon/index.tsx +2 -1
- package/src/components/Select/MultiSelect/__tests__/__snapshots__/index.spec.tsx.snap +248 -94
- package/src/components/Select/SingleSelect/__tests__/__snapshots__/index.spec.tsx.snap +248 -94
- package/src/components/TextInput/StyledTextInput.tsx +133 -11
- package/src/components/TextInput/__tests__/StyledTextInput.spec.tsx +143 -7
- package/src/components/TextInput/__tests__/__snapshots__/StyledTextInput.spec.tsx.snap +922 -15
- package/src/components/TextInput/__tests__/__snapshots__/index.spec.tsx.snap +2078 -0
- package/src/components/TextInput/__tests__/index.spec.tsx +302 -11
- package/src/components/TextInput/index.tsx +232 -28
- package/src/theme/__tests__/__snapshots__/index.spec.ts.snap +73 -3
- package/src/theme/components/button.ts +6 -0
- package/src/theme/components/card.ts +5 -1
- package/src/theme/components/icon.ts +1 -0
- package/src/theme/components/textInput.ts +62 -3
- package/src/theme/global/colors.ts +1 -0
- package/src/types.ts +8 -1
- package/types/components/Button/Button.d.ts +2 -2
- package/types/components/Button/LoadingIndicator/StyledLoadingIndicator.d.ts +1 -1
- package/types/components/Button/LoadingIndicator/index.d.ts +1 -1
- package/types/components/Button/StyledButton.d.ts +1 -1
- package/types/components/{Select/MultiSelect/__tests__/StyledMultiSelect.spec.d.ts → Button/UtilityButton/__tests__/index.spec.d.ts} +0 -0
- package/types/components/Button/UtilityButton/index.d.ts +23 -0
- package/types/components/Button/UtilityButton/styled.d.ts +17 -0
- package/types/components/Button/index.d.ts +2 -0
- package/types/components/Icon/HeroIcon/index.d.ts +1 -1
- package/types/components/Icon/IconList.d.ts +1 -1
- package/types/components/Icon/index.d.ts +1 -1
- package/types/components/Icon/utils.d.ts +1 -1
- package/types/components/TextInput/StyledTextInput.d.ts +82 -3
- package/types/components/TextInput/index.d.ts +33 -5
- package/types/theme/components/button.d.ts +6 -0
- package/types/theme/components/card.d.ts +3 -0
- package/types/theme/components/icon.d.ts +1 -0
- package/types/theme/components/textInput.d.ts +61 -2
- package/types/theme/global/colors.d.ts +1 -0
- package/types/theme/global/index.d.ts +1 -0
- package/types/types.d.ts +2 -1
- package/.expo/README.md +0 -15
- package/.expo/packager-info.json +0 -10
- package/.expo/prebuild/cached-packages.json +0 -4
- package/.expo/settings.json +0 -10
- package/.expo/xcodebuild-error.log +0 -2
- package/.expo/xcodebuild.log +0 -11199
- package/types/components/Select/MultiSelect/Footer.d.ts +0 -5
- package/types/components/Select/MultiSelect/StyledMultiSelect.d.ts +0 -26
- 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
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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 {
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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;
|