@aws-amplify/ui-react-native 1.2.20 → 1.2.21
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/CHANGELOG.md +46 -0
- package/dist/Authenticator/Authenticator.d.ts +101 -148
- package/dist/Authenticator/Authenticator.js +2 -3
- package/dist/Authenticator/Defaults/ConfirmResetPassword/ConfirmResetPassword.d.ts +13 -2
- package/dist/Authenticator/Defaults/ConfirmResetPassword/ConfirmResetPassword.js +4 -3
- package/dist/Authenticator/Defaults/ConfirmSignIn/ConfirmSignIn.d.ts +13 -2
- package/dist/Authenticator/Defaults/ConfirmSignIn/ConfirmSignIn.js +4 -3
- package/dist/Authenticator/Defaults/ConfirmSignUp/ConfirmSignUp.d.ts +13 -2
- package/dist/Authenticator/Defaults/ConfirmSignUp/ConfirmSignUp.js +4 -3
- package/dist/Authenticator/Defaults/ConfirmVerifyUser/ConfirmVerifyUser.d.ts +13 -2
- package/dist/Authenticator/Defaults/ConfirmVerifyUser/ConfirmVerifyUser.js +4 -3
- package/dist/Authenticator/Defaults/ForceNewPassword/ForceNewPassword.d.ts +13 -2
- package/dist/Authenticator/Defaults/ForceNewPassword/ForceNewPassword.js +4 -3
- package/dist/Authenticator/Defaults/ResetPassword/ResetPassword.d.ts +13 -2
- package/dist/Authenticator/Defaults/ResetPassword/ResetPassword.js +4 -3
- package/dist/Authenticator/Defaults/SetupTOTP/SetupTOTP.d.ts +13 -2
- package/dist/Authenticator/Defaults/SetupTOTP/SetupTOTP.js +4 -3
- package/dist/Authenticator/Defaults/SignIn/SignIn.d.ts +13 -2
- package/dist/Authenticator/Defaults/SignIn/SignIn.js +4 -3
- package/dist/Authenticator/Defaults/SignUp/SignUp.d.ts +13 -2
- package/dist/Authenticator/Defaults/SignUp/SignUp.js +4 -3
- package/dist/Authenticator/Defaults/VerifyUser/VerifyUser.d.ts +13 -2
- package/dist/Authenticator/Defaults/VerifyUser/VerifyUser.js +4 -3
- package/dist/Authenticator/Defaults/types.d.ts +21 -20
- package/dist/Authenticator/common/DefaultContent/types.d.ts +1 -1
- package/dist/Authenticator/common/DefaultFormFields/DefaultRadioFormFields.d.ts +7 -3
- package/dist/Authenticator/common/DefaultFormFields/DefaultRadioFormFields.js +4 -3
- package/dist/Authenticator/common/DefaultFormFields/DefaultTextFormFields.d.ts +6 -2
- package/dist/Authenticator/common/DefaultFormFields/DefaultTextFormFields.js +3 -3
- package/dist/Authenticator/common/DefaultFormFields/types.d.ts +12 -3
- package/dist/Authenticator/hooks/types.d.ts +3 -2
- package/dist/Authenticator/hooks/useFieldValues/types.d.ts +4 -1
- package/dist/Authenticator/hooks/useFieldValues/useFieldValues.d.ts +1 -1
- package/dist/Authenticator/hooks/useFieldValues/useFieldValues.js +21 -3
- package/dist/Authenticator/hooks/useFieldValues/utils.d.ts +10 -1
- package/dist/Authenticator/hooks/useFieldValues/utils.js +32 -2
- package/dist/primitives/TextField/TextField.js +2 -1
- package/dist/primitives/TextField/styles.js +3 -0
- package/dist/primitives/TextField/types.d.ts +1 -0
- package/dist/theme/createTheme.js +24 -18
- package/dist/theme/types.d.ts +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/jest.config.js +1 -0
- package/package.json +5 -5
- package/src/Authenticator/Authenticator.tsx +2 -6
- package/src/Authenticator/Defaults/ConfirmResetPassword/ConfirmResetPassword.tsx +7 -3
- package/src/Authenticator/Defaults/ConfirmSignIn/ConfirmSignIn.tsx +7 -3
- package/src/Authenticator/Defaults/ConfirmSignIn/__tests__/ConfirmSignIn.spec.tsx +1 -0
- package/src/Authenticator/Defaults/ConfirmSignUp/ConfirmSignUp.tsx +7 -3
- package/src/Authenticator/Defaults/ConfirmSignUp/__tests__/ConfirmSignUp.spec.tsx +1 -0
- package/src/Authenticator/Defaults/ConfirmVerifyUser/ConfirmVerifyUser.tsx +7 -3
- package/src/Authenticator/Defaults/ConfirmVerifyUser/__tests__/ConfirmVerifyUser.spec.tsx +1 -0
- package/src/Authenticator/Defaults/ForceNewPassword/ForceNewPassword.tsx +7 -3
- package/src/Authenticator/Defaults/ForceNewPassword/__tests__/__snapshots__/ForceNewPassword.spec.tsx.snap +1 -1
- package/src/Authenticator/Defaults/ResetPassword/ResetPassword.tsx +7 -3
- package/src/Authenticator/Defaults/ResetPassword/__tests__/ResetPassword.spec.tsx +1 -0
- package/src/Authenticator/Defaults/SetupTOTP/SetupTOTP.tsx +7 -3
- package/src/Authenticator/Defaults/SetupTOTP/__tests__/SetupTOTP.spec.tsx +1 -0
- package/src/Authenticator/Defaults/SignIn/SignIn.tsx +7 -3
- package/src/Authenticator/Defaults/SignIn/__tests__/SignIn.spec.tsx +1 -0
- package/src/Authenticator/Defaults/SignUp/SignUp.tsx +7 -3
- package/src/Authenticator/Defaults/SignUp/__tests__/__snapshots__/SignUp.spec.tsx.snap +1 -1
- package/src/Authenticator/Defaults/VerifyUser/VerifyUser.tsx +7 -3
- package/src/Authenticator/Defaults/VerifyUser/__tests__/VerifyUser.spec.tsx +1 -0
- package/src/Authenticator/Defaults/types.ts +63 -49
- package/src/Authenticator/__tests__/Authenticator.spec.tsx +16 -19
- package/src/Authenticator/__tests__/__snapshots__/Authenticator.spec.tsx.snap +1 -9
- package/src/Authenticator/__tests__/withAuthenticator.spec.tsx +1 -1
- package/src/Authenticator/common/DefaultContent/types.ts +1 -4
- package/src/Authenticator/common/DefaultFormFields/DefaultRadioFormFields.tsx +8 -6
- package/src/Authenticator/common/DefaultFormFields/DefaultTextFormFields.tsx +10 -7
- package/src/Authenticator/common/DefaultFormFields/types.ts +15 -5
- package/src/Authenticator/hooks/types.ts +3 -0
- package/src/Authenticator/hooks/useFieldValues/__tests__/useFieldValues.spec.ts +75 -2
- package/src/Authenticator/hooks/useFieldValues/__tests__/utils.spec.ts +67 -1
- package/src/Authenticator/hooks/useFieldValues/types.ts +5 -0
- package/src/Authenticator/hooks/useFieldValues/useFieldValues.ts +26 -1
- package/src/Authenticator/hooks/useFieldValues/utils.ts +44 -1
- package/src/primitives/TextField/TextField.tsx +2 -1
- package/src/primitives/TextField/__tests__/TextField.spec.tsx +57 -8
- package/src/primitives/TextField/__tests__/__snapshots__/TextField.spec.tsx.snap +31 -31
- package/src/primitives/TextField/styles.ts +3 -0
- package/src/primitives/TextField/types.ts +1 -0
- package/src/theme/__tests__/createTheme.spec.ts +48 -0
- package/src/theme/createTheme.ts +44 -21
- package/src/theme/types.ts +17 -16
- package/src/version.ts +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useMemo, useState } from 'react';
|
|
2
2
|
import { Logger } from 'aws-amplify';
|
|
3
|
+
import { ValidationError } from '@aws-amplify/ui';
|
|
3
4
|
|
|
4
5
|
import { OnChangeText, TextFieldOnBlur, TypedField } from '../types';
|
|
5
6
|
|
|
@@ -8,6 +9,7 @@ import {
|
|
|
8
9
|
getSanitizedTextFields,
|
|
9
10
|
getSanitizedRadioFields,
|
|
10
11
|
isRadioFieldOptions,
|
|
12
|
+
runFieldValidation,
|
|
11
13
|
} from './utils';
|
|
12
14
|
|
|
13
15
|
const logger = new Logger('Authenticator');
|
|
@@ -18,8 +20,12 @@ export default function useFieldValues<FieldType extends TypedField>({
|
|
|
18
20
|
handleBlur,
|
|
19
21
|
handleChange,
|
|
20
22
|
handleSubmit,
|
|
23
|
+
validationErrors,
|
|
21
24
|
}: UseFieldValuesParams<FieldType>): UseFieldValues<FieldType> {
|
|
22
25
|
const [values, setValues] = useState<Record<string, string>>({});
|
|
26
|
+
const [touched, setTouched] = useState<Record<string, boolean>>({});
|
|
27
|
+
const [fieldValidationErrors, setFieldValidationErrors] =
|
|
28
|
+
useState<ValidationError>({});
|
|
23
29
|
const isRadioFieldComponent = componentName === 'VerifyUser';
|
|
24
30
|
|
|
25
31
|
const sanitizedFields = useMemo(() => {
|
|
@@ -53,11 +59,18 @@ export default function useFieldValues<FieldType extends TypedField>({
|
|
|
53
59
|
const { name, label, labelHidden, ...rest } = field;
|
|
54
60
|
|
|
55
61
|
const onBlur: TextFieldOnBlur = (event) => {
|
|
62
|
+
setTouched({ ...touched, [name]: true });
|
|
63
|
+
|
|
56
64
|
// call `onBlur` passed as text `field` option
|
|
57
65
|
field.onBlur?.(event);
|
|
58
66
|
|
|
59
67
|
// call machine blur handler
|
|
60
68
|
handleBlur({ name, value: values[name] });
|
|
69
|
+
|
|
70
|
+
setFieldValidationErrors({
|
|
71
|
+
...fieldValidationErrors,
|
|
72
|
+
[name]: runFieldValidation(field, values[name], validationErrors),
|
|
73
|
+
});
|
|
61
74
|
};
|
|
62
75
|
|
|
63
76
|
const onChangeText: OnChangeText = (value) => {
|
|
@@ -67,6 +80,13 @@ export default function useFieldValues<FieldType extends TypedField>({
|
|
|
67
80
|
// call machine change handler
|
|
68
81
|
handleChange({ name, value });
|
|
69
82
|
|
|
83
|
+
if (touched[name]) {
|
|
84
|
+
setFieldValidationErrors({
|
|
85
|
+
...fieldValidationErrors,
|
|
86
|
+
[name]: runFieldValidation(field, value, validationErrors),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
70
90
|
setValues({ ...values, [name]: value });
|
|
71
91
|
};
|
|
72
92
|
|
|
@@ -112,5 +132,10 @@ export default function useFieldValues<FieldType extends TypedField>({
|
|
|
112
132
|
handleSubmit?.(submitValue);
|
|
113
133
|
};
|
|
114
134
|
|
|
115
|
-
return {
|
|
135
|
+
return {
|
|
136
|
+
fields: fieldsWithHandlers,
|
|
137
|
+
disableFormSubmit,
|
|
138
|
+
fieldValidationErrors: { ...fieldValidationErrors, ...validationErrors },
|
|
139
|
+
handleFormSubmit,
|
|
140
|
+
};
|
|
116
141
|
}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { Logger } from 'aws-amplify';
|
|
2
2
|
import {
|
|
3
|
+
authenticatorTextUtil,
|
|
4
|
+
isString,
|
|
3
5
|
isUnverifiedContactMethodType,
|
|
6
|
+
isValidEmail,
|
|
4
7
|
UnverifiedContactMethodType,
|
|
8
|
+
ValidationError,
|
|
5
9
|
} from '@aws-amplify/ui';
|
|
6
10
|
import {
|
|
7
11
|
AuthenticatorLegacyField,
|
|
@@ -14,12 +18,15 @@ import {
|
|
|
14
18
|
AuthenticatorFieldTypeKey,
|
|
15
19
|
MachineFieldTypeKey,
|
|
16
20
|
RadioFieldOptions,
|
|
21
|
+
TextFieldOptionsType,
|
|
17
22
|
TypedField,
|
|
18
23
|
} from '../types';
|
|
19
24
|
import { KEY_ALLOW_LIST } from './constants';
|
|
20
25
|
|
|
21
26
|
const logger = new Logger('Authenticator');
|
|
22
27
|
|
|
28
|
+
const { getInvalidEmailText, getRequiredFieldText } = authenticatorTextUtil;
|
|
29
|
+
|
|
23
30
|
export const isRadioFieldOptions = (
|
|
24
31
|
field: TypedField
|
|
25
32
|
): field is RadioFieldOptions => field?.type === 'radio';
|
|
@@ -109,7 +116,8 @@ const isKeyAllowed = (key: string) =>
|
|
|
109
116
|
|
|
110
117
|
const isValidMachineFieldType = (
|
|
111
118
|
type: string | undefined
|
|
112
|
-
): type is MachineFieldTypeKey =>
|
|
119
|
+
): type is MachineFieldTypeKey =>
|
|
120
|
+
type === 'password' || type === 'tel' || type == 'email';
|
|
113
121
|
|
|
114
122
|
const getFieldType = (type: string | undefined): AuthenticatorFieldTypeKey => {
|
|
115
123
|
if (isValidMachineFieldType(type)) {
|
|
@@ -176,3 +184,38 @@ export function getRouteTypedFields({
|
|
|
176
184
|
|
|
177
185
|
return isVerifyUserRoute ? radioFields : getTypedFields(fields);
|
|
178
186
|
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
*
|
|
190
|
+
* @param {TextFieldOptionsType} field text field type
|
|
191
|
+
* @param {string | undefined} value text field value
|
|
192
|
+
* @param {string[]} stateValidations validation errors array from state machine
|
|
193
|
+
* @returns {string[]} field errors array
|
|
194
|
+
*/
|
|
195
|
+
export const runFieldValidation = (
|
|
196
|
+
field: TextFieldOptionsType,
|
|
197
|
+
value: string | undefined,
|
|
198
|
+
stateValidations: ValidationError | undefined
|
|
199
|
+
): string[] => {
|
|
200
|
+
const fieldErrors: string[] = [];
|
|
201
|
+
if (field.required && !value) {
|
|
202
|
+
fieldErrors.push(getRequiredFieldText());
|
|
203
|
+
}
|
|
204
|
+
if (field.type === 'email') {
|
|
205
|
+
if (!isValidEmail(value)) {
|
|
206
|
+
fieldErrors.push(getInvalidEmailText());
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// add state machine validation errors, if any
|
|
211
|
+
const stateFieldValidation = stateValidations?.[field.name];
|
|
212
|
+
if (stateFieldValidation) {
|
|
213
|
+
if (isString(stateFieldValidation)) {
|
|
214
|
+
fieldErrors.push(stateFieldValidation);
|
|
215
|
+
} else {
|
|
216
|
+
return fieldErrors.concat(stateFieldValidation);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return fieldErrors;
|
|
221
|
+
};
|
|
@@ -67,7 +67,7 @@ describe('TextField', () => {
|
|
|
67
67
|
expect(queryByText(message)).toBeNull();
|
|
68
68
|
});
|
|
69
69
|
|
|
70
|
-
it(`doesn't render the errorMessage if errorMessage prop is undefined`, () => {
|
|
70
|
+
it(`shows error style, but doesn't render the errorMessage if errorMessage prop is undefined`, () => {
|
|
71
71
|
const message = 'Error message';
|
|
72
72
|
const { toJSON, queryByText } = render(
|
|
73
73
|
<TextField {...defaultProps} error />
|
|
@@ -111,18 +111,13 @@ describe('TextField', () => {
|
|
|
111
111
|
});
|
|
112
112
|
|
|
113
113
|
it('applies theme and style props', () => {
|
|
114
|
-
const errorMessageText = 'Error!';
|
|
115
|
-
const customErrorMessageStyle = { color: 'red' };
|
|
116
114
|
const customFieldStyle = { color: 'orange' };
|
|
117
115
|
const customLabelStyle = { color: 'blue' };
|
|
118
116
|
const customStyle = { backgroundColor: 'purple' };
|
|
119
117
|
|
|
120
|
-
const { getByTestId
|
|
118
|
+
const { getByTestId } = render(
|
|
121
119
|
<TextField
|
|
122
120
|
{...defaultProps}
|
|
123
|
-
error
|
|
124
|
-
errorMessage={errorMessageText}
|
|
125
|
-
errorMessageStyle={customErrorMessageStyle}
|
|
126
121
|
fieldStyle={customFieldStyle}
|
|
127
122
|
labelStyle={customLabelStyle}
|
|
128
123
|
style={customStyle}
|
|
@@ -135,7 +130,6 @@ describe('TextField', () => {
|
|
|
135
130
|
const container = getByTestId(TEXTFIELD_CONTAINER_TEST_ID);
|
|
136
131
|
const inputContainer = getByTestId(INPUT_CONTAINER_TEST_ID);
|
|
137
132
|
const input = getByTestId(testID);
|
|
138
|
-
const errorMessage = getByText(errorMessageText);
|
|
139
133
|
|
|
140
134
|
expect(container.props.style).toStrictEqual([
|
|
141
135
|
themedStyle.container,
|
|
@@ -148,6 +142,61 @@ describe('TextField', () => {
|
|
|
148
142
|
themedStyle.field,
|
|
149
143
|
customFieldStyle,
|
|
150
144
|
]);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('applies theme and style props with error', () => {
|
|
148
|
+
const errorMessageText = 'Error!';
|
|
149
|
+
const customErrorMessageStyle = { color: 'red' };
|
|
150
|
+
const customStyle = { backgroundColor: 'purple' };
|
|
151
|
+
|
|
152
|
+
const { getByTestId, getByText } = render(
|
|
153
|
+
<TextField
|
|
154
|
+
{...defaultProps}
|
|
155
|
+
style={customStyle}
|
|
156
|
+
error
|
|
157
|
+
errorMessage={errorMessageText}
|
|
158
|
+
errorMessageStyle={customErrorMessageStyle}
|
|
159
|
+
/>
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
const { result } = renderHook(() => useTheme());
|
|
163
|
+
const themedStyle = getThemedStyles(result.current);
|
|
164
|
+
|
|
165
|
+
const container = getByTestId(TEXTFIELD_CONTAINER_TEST_ID);
|
|
166
|
+
const inputContainer = getByTestId(INPUT_CONTAINER_TEST_ID);
|
|
167
|
+
const errorMessage = getByText(errorMessageText);
|
|
168
|
+
|
|
169
|
+
expect(container.props.style).toStrictEqual([
|
|
170
|
+
themedStyle.container,
|
|
171
|
+
customStyle,
|
|
172
|
+
]);
|
|
173
|
+
expect(inputContainer.props.style).toStrictEqual({
|
|
174
|
+
...themedStyle.fieldContainer,
|
|
175
|
+
...themedStyle.error,
|
|
176
|
+
});
|
|
151
177
|
expect(errorMessage.props.style).toContain(customErrorMessageStyle);
|
|
152
178
|
});
|
|
179
|
+
|
|
180
|
+
it('applies theme and style props for disabled', () => {
|
|
181
|
+
const customStyle = { backgroundColor: 'purple' };
|
|
182
|
+
|
|
183
|
+
const { getByTestId } = render(
|
|
184
|
+
<TextField {...defaultProps} style={customStyle} disabled />
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
const { result } = renderHook(() => useTheme());
|
|
188
|
+
const themedStyle = getThemedStyles(result.current);
|
|
189
|
+
|
|
190
|
+
const container = getByTestId(TEXTFIELD_CONTAINER_TEST_ID);
|
|
191
|
+
const inputContainer = getByTestId(INPUT_CONTAINER_TEST_ID);
|
|
192
|
+
|
|
193
|
+
expect(container.props.style).toStrictEqual([
|
|
194
|
+
themedStyle.container,
|
|
195
|
+
customStyle,
|
|
196
|
+
]);
|
|
197
|
+
expect(inputContainer.props.style).toStrictEqual({
|
|
198
|
+
...themedStyle.fieldContainer,
|
|
199
|
+
...themedStyle.disabled,
|
|
200
|
+
});
|
|
201
|
+
});
|
|
153
202
|
});
|
|
@@ -80,7 +80,7 @@ exports[`TextField doesn't render the errorMessage if error prop is false 1`] =
|
|
|
80
80
|
</View>
|
|
81
81
|
`;
|
|
82
82
|
|
|
83
|
-
exports[`TextField
|
|
83
|
+
exports[`TextField renders as expected 1`] = `
|
|
84
84
|
<View
|
|
85
85
|
style={
|
|
86
86
|
Array [
|
|
@@ -160,7 +160,7 @@ exports[`TextField doesn't render the errorMessage if errorMessage prop is undef
|
|
|
160
160
|
</View>
|
|
161
161
|
`;
|
|
162
162
|
|
|
163
|
-
exports[`TextField renders as expected 1`] = `
|
|
163
|
+
exports[`TextField renders as expected as password field 1`] = `
|
|
164
164
|
<View
|
|
165
165
|
style={
|
|
166
166
|
Array [
|
|
@@ -223,6 +223,7 @@ exports[`TextField renders as expected 1`] = `
|
|
|
223
223
|
editable={true}
|
|
224
224
|
placeholder="Placeholder"
|
|
225
225
|
placeholderTextColor="hsl(210, 10%, 40%)"
|
|
226
|
+
secureTextEntry={true}
|
|
226
227
|
style={
|
|
227
228
|
Array [
|
|
228
229
|
Object {
|
|
@@ -240,7 +241,7 @@ exports[`TextField renders as expected 1`] = `
|
|
|
240
241
|
</View>
|
|
241
242
|
`;
|
|
242
243
|
|
|
243
|
-
exports[`TextField renders as expected
|
|
244
|
+
exports[`TextField renders as expected when disabled 1`] = `
|
|
244
245
|
<View
|
|
245
246
|
style={
|
|
246
247
|
Array [
|
|
@@ -287,6 +288,7 @@ exports[`TextField renders as expected as password field 1`] = `
|
|
|
287
288
|
"borderRadius": 4,
|
|
288
289
|
"borderWidth": 1,
|
|
289
290
|
"flexDirection": "row",
|
|
291
|
+
"opacity": 0.6,
|
|
290
292
|
"paddingHorizontal": 8,
|
|
291
293
|
}
|
|
292
294
|
}
|
|
@@ -295,15 +297,14 @@ exports[`TextField renders as expected as password field 1`] = `
|
|
|
295
297
|
<TextInput
|
|
296
298
|
accessibilityState={
|
|
297
299
|
Object {
|
|
298
|
-
"disabled":
|
|
300
|
+
"disabled": true,
|
|
299
301
|
}
|
|
300
302
|
}
|
|
301
303
|
accessible={true}
|
|
302
304
|
autoCapitalize="none"
|
|
303
|
-
editable={
|
|
305
|
+
editable={false}
|
|
304
306
|
placeholder="Placeholder"
|
|
305
307
|
placeholderTextColor="hsl(210, 10%, 40%)"
|
|
306
|
-
secureTextEntry={true}
|
|
307
308
|
style={
|
|
308
309
|
Array [
|
|
309
310
|
Object {
|
|
@@ -321,7 +322,7 @@ exports[`TextField renders as expected as password field 1`] = `
|
|
|
321
322
|
</View>
|
|
322
323
|
`;
|
|
323
324
|
|
|
324
|
-
exports[`TextField renders as expected
|
|
325
|
+
exports[`TextField renders as expected with error message 1`] = `
|
|
325
326
|
<View
|
|
326
327
|
style={
|
|
327
328
|
Array [
|
|
@@ -364,11 +365,10 @@ exports[`TextField renders as expected when disabled 1`] = `
|
|
|
364
365
|
style={
|
|
365
366
|
Object {
|
|
366
367
|
"alignItems": "center",
|
|
367
|
-
"borderColor": "hsl(
|
|
368
|
+
"borderColor": "hsl(0, 95%, 30%)",
|
|
368
369
|
"borderRadius": 4,
|
|
369
370
|
"borderWidth": 1,
|
|
370
371
|
"flexDirection": "row",
|
|
371
|
-
"opacity": 0.6,
|
|
372
372
|
"paddingHorizontal": 8,
|
|
373
373
|
}
|
|
374
374
|
}
|
|
@@ -377,12 +377,12 @@ exports[`TextField renders as expected when disabled 1`] = `
|
|
|
377
377
|
<TextInput
|
|
378
378
|
accessibilityState={
|
|
379
379
|
Object {
|
|
380
|
-
"disabled":
|
|
380
|
+
"disabled": undefined,
|
|
381
381
|
}
|
|
382
382
|
}
|
|
383
383
|
accessible={true}
|
|
384
384
|
autoCapitalize="none"
|
|
385
|
-
editable={
|
|
385
|
+
editable={true}
|
|
386
386
|
placeholder="Placeholder"
|
|
387
387
|
placeholderTextColor="hsl(210, 10%, 40%)"
|
|
388
388
|
style={
|
|
@@ -399,10 +399,28 @@ exports[`TextField renders as expected when disabled 1`] = `
|
|
|
399
399
|
testID="textInput"
|
|
400
400
|
/>
|
|
401
401
|
</View>
|
|
402
|
+
<Text
|
|
403
|
+
accessibilityRole="text"
|
|
404
|
+
style={
|
|
405
|
+
Array [
|
|
406
|
+
Object {
|
|
407
|
+
"fontSize": 14,
|
|
408
|
+
"fontWeight": "400",
|
|
409
|
+
"lineHeight": 21,
|
|
410
|
+
},
|
|
411
|
+
Object {
|
|
412
|
+
"color": "hsl(210, 50%, 10%)",
|
|
413
|
+
},
|
|
414
|
+
undefined,
|
|
415
|
+
]
|
|
416
|
+
}
|
|
417
|
+
>
|
|
418
|
+
Error message
|
|
419
|
+
</Text>
|
|
402
420
|
</View>
|
|
403
421
|
`;
|
|
404
422
|
|
|
405
|
-
exports[`TextField
|
|
423
|
+
exports[`TextField shows error style, but doesn't render the errorMessage if errorMessage prop is undefined 1`] = `
|
|
406
424
|
<View
|
|
407
425
|
style={
|
|
408
426
|
Array [
|
|
@@ -445,7 +463,7 @@ exports[`TextField renders as expected with error message 1`] = `
|
|
|
445
463
|
style={
|
|
446
464
|
Object {
|
|
447
465
|
"alignItems": "center",
|
|
448
|
-
"borderColor": "hsl(
|
|
466
|
+
"borderColor": "hsl(0, 95%, 30%)",
|
|
449
467
|
"borderRadius": 4,
|
|
450
468
|
"borderWidth": 1,
|
|
451
469
|
"flexDirection": "row",
|
|
@@ -479,23 +497,5 @@ exports[`TextField renders as expected with error message 1`] = `
|
|
|
479
497
|
testID="textInput"
|
|
480
498
|
/>
|
|
481
499
|
</View>
|
|
482
|
-
<Text
|
|
483
|
-
accessibilityRole="text"
|
|
484
|
-
style={
|
|
485
|
-
Array [
|
|
486
|
-
Object {
|
|
487
|
-
"fontSize": 14,
|
|
488
|
-
"fontWeight": "400",
|
|
489
|
-
"lineHeight": 21,
|
|
490
|
-
},
|
|
491
|
-
Object {
|
|
492
|
-
"color": "hsl(210, 50%, 10%)",
|
|
493
|
-
},
|
|
494
|
-
undefined,
|
|
495
|
-
]
|
|
496
|
-
}
|
|
497
|
-
>
|
|
498
|
-
Error message
|
|
499
|
-
</Text>
|
|
500
500
|
</View>
|
|
501
501
|
`;
|
|
@@ -19,6 +19,9 @@ export const getThemedStyles = (theme: StrictTheme): TextFieldStyles => {
|
|
|
19
19
|
opacity: opacities[60],
|
|
20
20
|
...components?.textField?.disabled,
|
|
21
21
|
},
|
|
22
|
+
error: {
|
|
23
|
+
borderColor: colors.border.error,
|
|
24
|
+
},
|
|
22
25
|
fieldContainer: {
|
|
23
26
|
alignItems: 'center',
|
|
24
27
|
borderColor: colors.border.primary,
|
|
@@ -21,6 +21,54 @@ describe('createTheme', () => {
|
|
|
21
21
|
});
|
|
22
22
|
});
|
|
23
23
|
|
|
24
|
+
describe('number conversions', () => {
|
|
25
|
+
it('should convert strings to numbers where applicable', () => {
|
|
26
|
+
const { tokens } = createTheme({
|
|
27
|
+
tokens: {
|
|
28
|
+
borderWidths: {
|
|
29
|
+
small: '4',
|
|
30
|
+
medium: '1rem',
|
|
31
|
+
large: 6,
|
|
32
|
+
},
|
|
33
|
+
opacities: {
|
|
34
|
+
'10': '0.2',
|
|
35
|
+
},
|
|
36
|
+
space: {
|
|
37
|
+
small: 4,
|
|
38
|
+
medium: '6',
|
|
39
|
+
large: '{space.small.value}',
|
|
40
|
+
},
|
|
41
|
+
fontSizes: {
|
|
42
|
+
small: '1rem',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
expect(tokens.borderWidths.small).toBe(4);
|
|
47
|
+
expect(tokens.borderWidths.medium).toBe(16);
|
|
48
|
+
expect(tokens.borderWidths.large).toBe(6);
|
|
49
|
+
expect(tokens.opacities['10']).toBe(0.2);
|
|
50
|
+
expect(tokens.space.small).toBe(4);
|
|
51
|
+
expect(tokens.space.medium).toBe(6);
|
|
52
|
+
expect(tokens.space.large).toBe(4);
|
|
53
|
+
expect(tokens.fontSizes.small).toBe(16);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should use the spaceModifier for space tokens with rem', () => {
|
|
57
|
+
const { tokens } = createTheme({
|
|
58
|
+
spaceModifier: 1.25,
|
|
59
|
+
tokens: {
|
|
60
|
+
space: {
|
|
61
|
+
small: 4,
|
|
62
|
+
medium: '1rem',
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
expect(tokens.space.small).toEqual(4);
|
|
68
|
+
expect(tokens.space.medium).toEqual(20);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
24
72
|
describe('with mixture of value and no value', () => {
|
|
25
73
|
const { tokens } = createTheme({
|
|
26
74
|
tokens: {
|
package/src/theme/createTheme.ts
CHANGED
|
@@ -2,21 +2,33 @@ import deepExtend from 'style-dictionary/lib/utils/deepExtend';
|
|
|
2
2
|
import resolveObject from 'style-dictionary/lib/utils/resolveObject';
|
|
3
3
|
import usesReference from 'style-dictionary/lib/utils/references/usesReference';
|
|
4
4
|
import { isFunction, setupTokens } from '@aws-amplify/ui';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
Theme,
|
|
7
|
+
StrictTheme,
|
|
8
|
+
ColorMode,
|
|
9
|
+
Components,
|
|
10
|
+
StrictTokens,
|
|
11
|
+
} from './types';
|
|
6
12
|
import { defaultTheme } from './defaultTheme';
|
|
7
13
|
|
|
8
14
|
// This will resolve all references in component themes by either
|
|
9
15
|
// calling the component theme function with the already resolved base tokens
|
|
10
16
|
// OR
|
|
11
17
|
// resolving the component theme object
|
|
12
|
-
const setupComponents = ({
|
|
18
|
+
const setupComponents = ({
|
|
19
|
+
components,
|
|
20
|
+
tokens,
|
|
21
|
+
}: {
|
|
22
|
+
components: Components;
|
|
23
|
+
tokens: StrictTokens;
|
|
24
|
+
}) => {
|
|
13
25
|
const output = components
|
|
14
26
|
? Object.entries(components).reduce(
|
|
15
27
|
(acc, [key, value]) => ({
|
|
16
28
|
...acc,
|
|
17
29
|
[key]: isFunction(value) ? (value(tokens) as typeof value) : value,
|
|
18
30
|
}),
|
|
19
|
-
{}
|
|
31
|
+
{}
|
|
20
32
|
)
|
|
21
33
|
: {};
|
|
22
34
|
|
|
@@ -26,6 +38,16 @@ const setupComponents = ({ components, tokens }: StrictTheme) => {
|
|
|
26
38
|
}).components;
|
|
27
39
|
};
|
|
28
40
|
|
|
41
|
+
const shouldParseFloatValue = (pathKey: string) =>
|
|
42
|
+
[
|
|
43
|
+
'space',
|
|
44
|
+
'borderWidths',
|
|
45
|
+
'opacities',
|
|
46
|
+
'fontSizes',
|
|
47
|
+
'lineHeights',
|
|
48
|
+
'radii',
|
|
49
|
+
].includes(pathKey);
|
|
50
|
+
|
|
29
51
|
const setupToken = ({
|
|
30
52
|
token,
|
|
31
53
|
path = [],
|
|
@@ -39,27 +61,28 @@ const setupToken = ({
|
|
|
39
61
|
}): string | number => {
|
|
40
62
|
const { value } = token;
|
|
41
63
|
if (typeof value === 'string') {
|
|
42
|
-
// Perform transforms
|
|
43
|
-
if (path[0] === 'space') {
|
|
44
|
-
if (value.includes('rem')) {
|
|
45
|
-
return Math.floor(parseFloat(value) * 16 * spaceModifier);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
if (value.includes('rem')) {
|
|
49
|
-
return Math.floor(parseFloat(value) * 16);
|
|
50
|
-
}
|
|
51
|
-
if (value.includes('px')) {
|
|
52
|
-
return parseInt(value, 10);
|
|
53
|
-
}
|
|
54
|
-
if (path[0] === 'opacities') {
|
|
55
|
-
return parseFloat(value);
|
|
56
|
-
}
|
|
57
64
|
// Remove .value from references if there is a reference
|
|
65
|
+
// this needs to come first so we don't get NaNs for references
|
|
58
66
|
if (usesReference(value)) {
|
|
59
67
|
return value.replace('.value', '');
|
|
60
68
|
}
|
|
69
|
+
|
|
70
|
+
if (shouldParseFloatValue(path[0])) {
|
|
71
|
+
if (value.includes('rem')) {
|
|
72
|
+
if (path[0] === 'space') {
|
|
73
|
+
return Math.floor(parseFloat(value) * 16 * spaceModifier);
|
|
74
|
+
}
|
|
75
|
+
return Math.floor(parseFloat(value) * 16);
|
|
76
|
+
}
|
|
77
|
+
if (value.includes('px')) {
|
|
78
|
+
return parseInt(value, 10);
|
|
79
|
+
}
|
|
80
|
+
return parseFloat(value);
|
|
81
|
+
}
|
|
82
|
+
|
|
61
83
|
return value;
|
|
62
84
|
}
|
|
85
|
+
|
|
63
86
|
// Font Weights in RN are strings
|
|
64
87
|
if (path[0] === 'fontWeights') {
|
|
65
88
|
return `${value}`;
|
|
@@ -118,14 +141,14 @@ export const createTheme = (
|
|
|
118
141
|
}) as StrictTheme['tokens']
|
|
119
142
|
);
|
|
120
143
|
|
|
121
|
-
let
|
|
144
|
+
let components;
|
|
122
145
|
|
|
123
146
|
// Resolve component token references too
|
|
124
147
|
if (mergedTheme.components) {
|
|
125
148
|
components = setupComponents({
|
|
126
|
-
|
|
149
|
+
components: mergedTheme.components,
|
|
127
150
|
tokens,
|
|
128
|
-
})
|
|
151
|
+
});
|
|
129
152
|
}
|
|
130
153
|
|
|
131
154
|
return { ...mergedTheme, tokens, components };
|
package/src/theme/types.ts
CHANGED
|
@@ -26,22 +26,23 @@ type ComponentTheme<ComponentType, Output> = Output extends 'output'
|
|
|
26
26
|
: ((tokens: StrictTheme['tokens']) => ComponentType) | ComponentType;
|
|
27
27
|
|
|
28
28
|
// TODO: make optional all the way down
|
|
29
|
-
export type Components<Output
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
29
|
+
export type Components<Output extends 'input' | 'output' | unknown = unknown> =
|
|
30
|
+
{
|
|
31
|
+
button?: ComponentTheme<ButtonStyles, Output>;
|
|
32
|
+
checkbox?: ComponentTheme<CheckboxStyles, Output>;
|
|
33
|
+
divider?: ComponentTheme<DividerStyles, Output>;
|
|
34
|
+
errorMessage?: ComponentTheme<ErrorMessageStyles, Output>;
|
|
35
|
+
heading?: ComponentTheme<HeadingStyles, Output>;
|
|
36
|
+
icon?: ComponentTheme<IconStyles, Output>;
|
|
37
|
+
iconButton?: ComponentTheme<IconButtonStyles, Output>;
|
|
38
|
+
label?: ComponentTheme<LabelStyles, Output>;
|
|
39
|
+
passwordField?: ComponentTheme<PasswordFieldStyles, Output>;
|
|
40
|
+
phoneNumberField?: ComponentTheme<PhoneNumberFieldStyles, Output>;
|
|
41
|
+
radio?: ComponentTheme<RadioStyles, Output>;
|
|
42
|
+
radioGroup?: ComponentTheme<RadioGroupStyles, Output>;
|
|
43
|
+
tabs?: ComponentTheme<TabsStyles, Output>;
|
|
44
|
+
textField?: ComponentTheme<TextFieldStyles, Output>;
|
|
45
|
+
};
|
|
45
46
|
|
|
46
47
|
export type ColorMode = 'light' | 'dark' | 'system' | null;
|
|
47
48
|
export type Override = Omit<Theme, 'overrides'> & {
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '1.2.
|
|
1
|
+
export const VERSION = '1.2.21';
|