From d3ee054159e1de81861bcd9273be9b1c87924cf4 Mon Sep 17 00:00:00 2001 From: Danny Banks Date: Wed, 12 Jul 2023 10:12:45 -0700 Subject: [PATCH] fix(react-native): border widths in the theme don't throw runtime errors (#4227) --- .changeset/cold-grapes-collect.md | 32 +++++++++++++ .../features/Authenticator/Styles/Example.tsx | 14 +++++- .../src/theme/__tests__/createTheme.spec.ts | 48 +++++++++++++++++++ .../react-native/src/theme/createTheme.ts | 41 ++++++++++------ .../ui/src/theme/tokens/types/designToken.ts | 4 +- 5 files changed, 120 insertions(+), 19 deletions(-) create mode 100644 .changeset/cold-grapes-collect.md diff --git a/.changeset/cold-grapes-collect.md b/.changeset/cold-grapes-collect.md new file mode 100644 index 00000000000..586fa91ecfe --- /dev/null +++ b/.changeset/cold-grapes-collect.md @@ -0,0 +1,32 @@ +--- +"@aws-amplify/ui-react-native": patch +"@aws-amplify/ui-react": patch +"@aws-amplify/ui": patch +--- + +fix(react-native): border widths, spacing, font sizes, opacities in the theme don't throw runtime errors. + +These are all valid in a theme now: + +```typescript +const theme: Theme = { + tokens: { + borderWidths: { + small: '4', + medium: '1rem', + large: 6, + }, + opacities: { + '10': '0.2', + }, + space: { + small: 4, + medium: '6', + large: '{space.small.value}', + }, + fontSizes: { + small: '1rem', + }, + }, +} +``` diff --git a/examples/react-native/src/features/Authenticator/Styles/Example.tsx b/examples/react-native/src/features/Authenticator/Styles/Example.tsx index c2834e2de72..aca6ea11f89 100644 --- a/examples/react-native/src/features/Authenticator/Styles/Example.tsx +++ b/examples/react-native/src/features/Authenticator/Styles/Example.tsx @@ -30,12 +30,17 @@ const MyHeader = ({ style?: StyleProp; }) => { const { - tokens: { colors, fontSizes }, + tokens: { colors, fontSizes, borderWidths }, } = useTheme(); + console.log({ borderWidths }); return ( {children} @@ -49,6 +54,11 @@ function SignOutButton() { } const theme: Theme = { + tokens: { + borderWidths: { + large: '1rem', + }, + }, overrides: [defaultDarkModeOverride], }; diff --git a/packages/react-native/src/theme/__tests__/createTheme.spec.ts b/packages/react-native/src/theme/__tests__/createTheme.spec.ts index 358661ed8f4..6dd9477f07b 100644 --- a/packages/react-native/src/theme/__tests__/createTheme.spec.ts +++ b/packages/react-native/src/theme/__tests__/createTheme.spec.ts @@ -21,6 +21,54 @@ describe('createTheme', () => { }); }); + describe('number conversions', () => { + it('should convert strings to numbers where applicable', () => { + const { tokens } = createTheme({ + tokens: { + borderWidths: { + small: '4', + medium: '1rem', + large: 6, + }, + opacities: { + '10': '0.2', + }, + space: { + small: 4, + medium: '6', + large: '{space.small.value}', + }, + fontSizes: { + small: '1rem', + }, + }, + }); + expect(tokens.borderWidths.small).toBe(4); + expect(tokens.borderWidths.medium).toBe(16); + expect(tokens.borderWidths.large).toBe(6); + expect(tokens.opacities['10']).toBe(0.2); + expect(tokens.space.small).toBe(4); + expect(tokens.space.medium).toBe(6); + expect(tokens.space.large).toBe(4); + expect(tokens.fontSizes.small).toBe(16); + }); + + it('should use the spaceModifier for space tokens with rem', () => { + const { tokens } = createTheme({ + spaceModifier: 1.25, + tokens: { + space: { + small: 4, + medium: '1rem', + }, + }, + }); + + expect(tokens.space.small).toEqual(4); + expect(tokens.space.medium).toEqual(20); + }); + }); + describe('with mixture of value and no value', () => { const { tokens } = createTheme({ tokens: { diff --git a/packages/react-native/src/theme/createTheme.ts b/packages/react-native/src/theme/createTheme.ts index b3f75ccd656..54be84406ce 100644 --- a/packages/react-native/src/theme/createTheme.ts +++ b/packages/react-native/src/theme/createTheme.ts @@ -26,6 +26,16 @@ const setupComponents = ({ components, tokens }: StrictTheme) => { }).components; }; +const shouldParseFloatValue = (pathKey: string) => + [ + 'space', + 'borderWidths', + 'opacities', + 'fontSizes', + 'lineHeights', + 'radii', + ].includes(pathKey); + const setupToken = ({ token, path = [], @@ -39,27 +49,28 @@ const setupToken = ({ }): string | number => { const { value } = token; if (typeof value === 'string') { - // Perform transforms - if (path[0] === 'space') { - if (value.includes('rem')) { - return Math.floor(parseFloat(value) * 16 * spaceModifier); - } - } - if (value.includes('rem')) { - return Math.floor(parseFloat(value) * 16); - } - if (value.includes('px')) { - return parseInt(value, 10); - } - if (path[0] === 'opacities') { - return parseFloat(value); - } // Remove .value from references if there is a reference + // this needs to come first so we don't get NaNs for references if (usesReference(value)) { return value.replace('.value', ''); } + + if (shouldParseFloatValue(path[0])) { + if (value.includes('rem')) { + if (path[0] === 'space') { + return Math.floor(parseFloat(value) * 16 * spaceModifier); + } + return Math.floor(parseFloat(value) * 16); + } + if (value.includes('px')) { + return parseInt(value, 10); + } + return parseFloat(value); + } + return value; } + // Font Weights in RN are strings if (path[0] === 'fontWeights') { return `${value}`; diff --git a/packages/ui/src/theme/tokens/types/designToken.ts b/packages/ui/src/theme/tokens/types/designToken.ts index da123f78268..9224f876345 100644 --- a/packages/ui/src/theme/tokens/types/designToken.ts +++ b/packages/ui/src/theme/tokens/types/designToken.ts @@ -47,8 +47,8 @@ export type BorderWidthValue< > = Output extends 'required' ? Platform extends 'react-native' ? number - : SpaceValue - : SpaceValue; + : SpaceValue + : SpaceValue; export type BorderValue = string; export type BoxSizingValue = string; export type BoxShadowValue = ShadowValue;