From 25e379462e2ac412da9db262de1922f02e9da158 Mon Sep 17 00:00:00 2001 From: Joe Buono Date: Fri, 11 Nov 2022 13:38:09 -0600 Subject: [PATCH 1/3] add theming to IconButton --- .../src/primitives/IconButton/IconButton.tsx | 12 ++++++---- .../IconButton/__tests__/IconButton.spec.tsx | 22 +++++++++++-------- .../src/primitives/IconButton/styles.ts | 13 +++++++---- packages/react-native/src/theme/types.ts | 2 ++ 4 files changed, 32 insertions(+), 17 deletions(-) diff --git a/packages/react-native/src/primitives/IconButton/IconButton.tsx b/packages/react-native/src/primitives/IconButton/IconButton.tsx index d4ea21c0b02..2c32040f2e2 100644 --- a/packages/react-native/src/primitives/IconButton/IconButton.tsx +++ b/packages/react-native/src/primitives/IconButton/IconButton.tsx @@ -5,9 +5,10 @@ import { StyleProp, ViewStyle, } from 'react-native'; -import { Icon } from '../Icon'; -import { styles } from './styles'; +import { useTheme } from '../../theme'; +import { Icon } from '../Icon'; +import { getThemedStyles } from './styles'; import { IconButtonProps } from './types'; export default function IconButton({ @@ -19,13 +20,16 @@ export default function IconButton({ style, ...rest }: IconButtonProps): JSX.Element { + const theme = useTheme(); + const themedStyle = getThemedStyles(theme); + const pressableStyle = useCallback( ({ pressed }: PressableStateCallbackType): StyleProp => { const pressedStateStyle = (typeof style === 'function' ? style({ pressed }) : style) ?? null; - return [pressed ? styles.pressed : null, pressedStateStyle]; + return [pressed ? themedStyle.pressed : null, pressedStateStyle]; }, - [style] + [style, themedStyle] ); return ( diff --git a/packages/react-native/src/primitives/IconButton/__tests__/IconButton.spec.tsx b/packages/react-native/src/primitives/IconButton/__tests__/IconButton.spec.tsx index 49097a2b2f9..4943372ff7d 100644 --- a/packages/react-native/src/primitives/IconButton/__tests__/IconButton.spec.tsx +++ b/packages/react-native/src/primitives/IconButton/__tests__/IconButton.spec.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import TestRenderer from 'react-test-renderer'; +import { render } from '@testing-library/react-native'; import IconButton from '../IconButton'; @@ -7,17 +7,21 @@ const source = { uri: 'icon.png' }; describe('IconButton', () => { it('renders as expected', () => { - const iconButton = TestRenderer.create(); - expect(iconButton.toJSON()).toMatchSnapshot(); + const { toJSON, getByRole } = render(); + + expect(getByRole('button')).toBeDefined(); + expect(getByRole('image')).toBeDefined(); + expect(toJSON()).toMatchSnapshot(); }); it('renders as expected with custom icon style', () => { - const iconButton = TestRenderer.create( - + const customIconStyle = { backgroundColor: 'antiquewhite' }; + const { toJSON, getByRole } = render( + ); - expect(iconButton.toJSON()).toMatchSnapshot(); + + const icon = getByRole('image'); + expect(icon.props.style).toContain(customIconStyle); + expect(toJSON()).toMatchSnapshot(); }); }); diff --git a/packages/react-native/src/primitives/IconButton/styles.ts b/packages/react-native/src/primitives/IconButton/styles.ts index dfa12cd48f8..a7acfa409d6 100644 --- a/packages/react-native/src/primitives/IconButton/styles.ts +++ b/packages/react-native/src/primitives/IconButton/styles.ts @@ -1,7 +1,12 @@ import { StyleSheet } from 'react-native'; + +import { StrictTheme } from '../../theme'; import { IconButtonStyles } from './types'; -export const styles: IconButtonStyles = StyleSheet.create({ - pressed: { opacity: 0.6 }, - text: { alignSelf: 'center' }, -}); +export const getThemedStyles = (theme: StrictTheme): IconButtonStyles => { + const { components, opacities } = theme.tokens; + + return StyleSheet.create({ + pressed: { opacity: opacities[60], ...components?.iconbutton.pressed }, + }); +}; diff --git a/packages/react-native/src/theme/types.ts b/packages/react-native/src/theme/types.ts index 71d21acb015..0726691b80b 100644 --- a/packages/react-native/src/theme/types.ts +++ b/packages/react-native/src/theme/types.ts @@ -4,6 +4,7 @@ import baseTokens from '@aws-amplify/ui/dist/react-native/tokens'; import { HeadingStyles, + IconButtonStyles, LabelStyles, RadioGroupStyles, RadioStyles, @@ -41,6 +42,7 @@ export interface Theme { export interface ComponentStyles { // TODO: add components heading: HeadingStyles; + iconbutton: IconButtonStyles; label: LabelStyles; radio: RadioStyles; radiogroup: RadioGroupStyles; From 9dd29ff17986fcf6eb82d1556e129ea723d1047c Mon Sep 17 00:00:00 2001 From: Joe Buono Date: Tue, 15 Nov 2022 15:42:02 -0600 Subject: [PATCH 2/3] add container and disabled styles --- .../ConfirmResetPassword.spec.tsx.snap | 8 ++ .../__snapshots__/SetupTOTP.spec.tsx.snap | 2 + .../__snapshots__/SignIn.spec.tsx.snap | 6 + .../__snapshots__/SignUp.spec.tsx.snap | 28 ++++- .../__snapshots__/BannerMessage.spec.tsx.snap | 4 + .../__snapshots__/ModalMessage.spec.tsx.snap | 6 + .../src/primitives/IconButton/IconButton.tsx | 13 ++- .../IconButton/__tests__/IconButton.spec.tsx | 46 +++++++- .../__snapshots__/IconButton.spec.tsx.snap | 105 ++++++++++++++++++ .../src/primitives/IconButton/styles.ts | 9 +- .../src/primitives/IconButton/types.ts | 2 + .../__snapshots__/PasswordField.spec.tsx.snap | 10 +- packages/react-native/src/theme/types.ts | 2 +- 13 files changed, 232 insertions(+), 9 deletions(-) diff --git a/packages/react-native/src/Authenticator/Defaults/ConfirmResetPassword/__tests__/__snapshots__/ConfirmResetPassword.spec.tsx.snap b/packages/react-native/src/Authenticator/Defaults/ConfirmResetPassword/__tests__/__snapshots__/ConfirmResetPassword.spec.tsx.snap index e9c1b053caa..60676fdaed6 100644 --- a/packages/react-native/src/Authenticator/Defaults/ConfirmResetPassword/__tests__/__snapshots__/ConfirmResetPassword.spec.tsx.snap +++ b/packages/react-native/src/Authenticator/Defaults/ConfirmResetPassword/__tests__/__snapshots__/ConfirmResetPassword.spec.tsx.snap @@ -183,8 +183,10 @@ Array [ onStartShouldSetResponder={[Function]} style={ Array [ + Object {}, null, null, + undefined, ] } > @@ -301,8 +303,10 @@ Array [ onStartShouldSetResponder={[Function]} style={ Array [ + Object {}, null, null, + undefined, ] } > @@ -648,8 +652,10 @@ Array [ onStartShouldSetResponder={[Function]} style={ Array [ + Object {}, null, null, + undefined, ] } > @@ -766,8 +772,10 @@ Array [ onStartShouldSetResponder={[Function]} style={ Array [ + Object {}, null, null, + undefined, ] } > diff --git a/packages/react-native/src/Authenticator/Defaults/SetupTOTP/__tests__/__snapshots__/SetupTOTP.spec.tsx.snap b/packages/react-native/src/Authenticator/Defaults/SetupTOTP/__tests__/__snapshots__/SetupTOTP.spec.tsx.snap index 22cf3fae425..752647a1bba 100644 --- a/packages/react-native/src/Authenticator/Defaults/SetupTOTP/__tests__/__snapshots__/SetupTOTP.spec.tsx.snap +++ b/packages/react-native/src/Authenticator/Defaults/SetupTOTP/__tests__/__snapshots__/SetupTOTP.spec.tsx.snap @@ -638,8 +638,10 @@ Array [ onStartShouldSetResponder={[Function]} style={ Array [ + Object {}, null, null, + undefined, ] } testID="amplify__copy-text-button" diff --git a/packages/react-native/src/Authenticator/Defaults/SignIn/__tests__/__snapshots__/SignIn.spec.tsx.snap b/packages/react-native/src/Authenticator/Defaults/SignIn/__tests__/__snapshots__/SignIn.spec.tsx.snap index ef09d17c6a9..a051e032bae 100644 --- a/packages/react-native/src/Authenticator/Defaults/SignIn/__tests__/__snapshots__/SignIn.spec.tsx.snap +++ b/packages/react-native/src/Authenticator/Defaults/SignIn/__tests__/__snapshots__/SignIn.spec.tsx.snap @@ -314,8 +314,10 @@ Array [ onStartShouldSetResponder={[Function]} style={ Array [ + Object {}, null, null, + undefined, ] } > @@ -609,8 +611,10 @@ Array [ onStartShouldSetResponder={[Function]} style={ Array [ + Object {}, null, null, + undefined, ] } > @@ -1037,8 +1041,10 @@ Array [ onStartShouldSetResponder={[Function]} style={ Array [ + Object {}, null, null, + undefined, ] } > diff --git a/packages/react-native/src/Authenticator/Defaults/SignUp/__tests__/__snapshots__/SignUp.spec.tsx.snap b/packages/react-native/src/Authenticator/Defaults/SignUp/__tests__/__snapshots__/SignUp.spec.tsx.snap index ffb25e3ad5c..99686719654 100644 --- a/packages/react-native/src/Authenticator/Defaults/SignUp/__tests__/__snapshots__/SignUp.spec.tsx.snap +++ b/packages/react-native/src/Authenticator/Defaults/SignUp/__tests__/__snapshots__/SignUp.spec.tsx.snap @@ -314,8 +314,10 @@ Array [ onStartShouldSetResponder={[Function]} style={ Array [ + Object {}, null, null, + undefined, ] } > @@ -432,8 +434,10 @@ Array [ onStartShouldSetResponder={[Function]} style={ Array [ + Object {}, null, null, + undefined, ] } > @@ -822,8 +826,10 @@ Array [ onStartShouldSetResponder={[Function]} style={ Array [ + Object {}, null, null, + undefined, ] } > @@ -940,8 +946,10 @@ Array [ onStartShouldSetResponder={[Function]} style={ Array [ + Object {}, null, null, + undefined, ] } > @@ -1465,8 +1473,12 @@ Array [ onStartShouldSetResponder={[Function]} style={ Array [ + Object {}, + Object { + "opacity": 0.6, + }, null, - null, + undefined, ] } > @@ -1584,8 +1596,12 @@ Array [ onStartShouldSetResponder={[Function]} style={ Array [ + Object {}, + Object { + "opacity": 0.6, + }, null, - null, + undefined, ] } > @@ -2108,8 +2124,10 @@ Array [ onStartShouldSetResponder={[Function]} style={ Array [ + Object {}, null, null, + undefined, ] } > @@ -2226,8 +2244,10 @@ Array [ onStartShouldSetResponder={[Function]} style={ Array [ + Object {}, null, null, + undefined, ] } > @@ -2801,8 +2821,10 @@ Array [ onStartShouldSetResponder={[Function]} style={ Array [ + Object {}, null, null, + undefined, ] } > @@ -2919,8 +2941,10 @@ Array [ onStartShouldSetResponder={[Function]} style={ Array [ + Object {}, null, null, + undefined, ] } > diff --git a/packages/react-native/src/InAppMessaging/components/BannerMessage/__tests__/__snapshots__/BannerMessage.spec.tsx.snap b/packages/react-native/src/InAppMessaging/components/BannerMessage/__tests__/__snapshots__/BannerMessage.spec.tsx.snap index 97a8f6d8510..6c77379e63e 100644 --- a/packages/react-native/src/InAppMessaging/components/BannerMessage/__tests__/__snapshots__/BannerMessage.spec.tsx.snap +++ b/packages/react-native/src/InAppMessaging/components/BannerMessage/__tests__/__snapshots__/BannerMessage.spec.tsx.snap @@ -95,6 +95,8 @@ exports[`BannerMessage renders a message as expected with an image 1`] = ` onStartShouldSetResponder={[Function]} style={ Array [ + Object {}, + null, null, Array [ Object { @@ -202,6 +204,8 @@ exports[`BannerMessage renders a message as expected without an image 1`] = ` onStartShouldSetResponder={[Function]} style={ Array [ + Object {}, + null, null, Array [ Object { diff --git a/packages/react-native/src/InAppMessaging/components/ModalMessage/__tests__/__snapshots__/ModalMessage.spec.tsx.snap b/packages/react-native/src/InAppMessaging/components/ModalMessage/__tests__/__snapshots__/ModalMessage.spec.tsx.snap index 5fe5229a78e..0c418a2c176 100644 --- a/packages/react-native/src/InAppMessaging/components/ModalMessage/__tests__/__snapshots__/ModalMessage.spec.tsx.snap +++ b/packages/react-native/src/InAppMessaging/components/ModalMessage/__tests__/__snapshots__/ModalMessage.spec.tsx.snap @@ -61,6 +61,8 @@ exports[`ModalMessage renders a message as expected with an image 1`] = ` onStartShouldSetResponder={[Function]} style={ Array [ + Object {}, + null, null, Array [ Object { @@ -203,6 +205,8 @@ exports[`ModalMessage renders as expected in landscape mode 1`] = ` onStartShouldSetResponder={[Function]} style={ Array [ + Object {}, + null, null, Array [ Object { @@ -302,6 +306,8 @@ exports[`ModalMessage renders as expected in portrait mode 1`] = ` onStartShouldSetResponder={[Function]} style={ Array [ + Object {}, + null, null, Array [ Object { diff --git a/packages/react-native/src/primitives/IconButton/IconButton.tsx b/packages/react-native/src/primitives/IconButton/IconButton.tsx index 434ab179f60..3e47456a910 100644 --- a/packages/react-native/src/primitives/IconButton/IconButton.tsx +++ b/packages/react-native/src/primitives/IconButton/IconButton.tsx @@ -14,6 +14,7 @@ import { IconButtonProps } from './types'; export default function IconButton({ accessibilityRole = 'button', color, + disabled, iconStyle, size = iconSizes.medium, source, @@ -26,16 +27,22 @@ export default function IconButton({ const pressableStyle = useCallback( ({ pressed }: PressableStateCallbackType): StyleProp => { const pressedStateStyle = - (typeof style === 'function' ? style({ pressed }) : style) ?? null; - return [pressed ? themedStyle.pressed : null, pressedStateStyle]; + typeof style === 'function' ? style({ pressed }) : style; + return [ + themedStyle.container, + disabled ? themedStyle.disabled : null, + pressed ? themedStyle.pressed : null, + pressedStateStyle, + ]; }, - [style, themedStyle] + [disabled, style, themedStyle] ); return ( diff --git a/packages/react-native/src/primitives/IconButton/__tests__/IconButton.spec.tsx b/packages/react-native/src/primitives/IconButton/__tests__/IconButton.spec.tsx index 4943372ff7d..55aeb0ccba4 100644 --- a/packages/react-native/src/primitives/IconButton/__tests__/IconButton.spec.tsx +++ b/packages/react-native/src/primitives/IconButton/__tests__/IconButton.spec.tsx @@ -1,7 +1,9 @@ import React from 'react'; -import { render } from '@testing-library/react-native'; +import { fireEvent, render, renderHook } from '@testing-library/react-native'; +import { useTheme } from '../../../theme'; import IconButton from '../IconButton'; +import { getThemedStyles } from '../styles'; const source = { uri: 'icon.png' }; @@ -14,6 +16,29 @@ describe('IconButton', () => { expect(toJSON()).toMatchSnapshot(); }); + it('handles disabled state', () => { + const onPressMock = jest.fn(); + + const { toJSON, getByRole } = render( + + ); + + const button = getByRole('button'); + fireEvent.press(button); + expect(onPressMock).not.toHaveBeenCalled(); + + const { result } = renderHook(() => useTheme()); + const themedStyle = getThemedStyles(result.current); + + expect(button.props.style).toStrictEqual([ + themedStyle.container, + themedStyle.disabled, + null, + undefined, + ]); + expect(toJSON()).toMatchSnapshot(); + }); + it('renders as expected with custom icon style', () => { const customIconStyle = { backgroundColor: 'antiquewhite' }; const { toJSON, getByRole } = render( @@ -24,4 +49,23 @@ describe('IconButton', () => { expect(icon.props.style).toContain(customIconStyle); expect(toJSON()).toMatchSnapshot(); }); + + it('applies theme and style props', () => { + const customStyle = { backgroundColor: 'blue' }; + + const { toJSON, getByRole } = render( + + ); + + const { result } = renderHook(() => useTheme()); + const themedStyle = getThemedStyles(result.current); + + expect(getByRole('button').props.style).toStrictEqual([ + themedStyle.container, + null, + null, + customStyle, + ]); + expect(toJSON()).toMatchSnapshot(); + }); }); diff --git a/packages/react-native/src/primitives/IconButton/__tests__/__snapshots__/IconButton.spec.tsx.snap b/packages/react-native/src/primitives/IconButton/__tests__/__snapshots__/IconButton.spec.tsx.snap index 4c2843bd2e5..49737a6dc71 100644 --- a/packages/react-native/src/primitives/IconButton/__tests__/__snapshots__/IconButton.spec.tsx.snap +++ b/packages/react-native/src/primitives/IconButton/__tests__/__snapshots__/IconButton.spec.tsx.snap @@ -1,5 +1,106 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`IconButton applies theme and style props 1`] = ` + + + +`; + +exports[`IconButton handles disabled state 1`] = ` + + + +`; + exports[`IconButton renders as expected 1`] = ` @@ -61,8 +164,10 @@ exports[`IconButton renders as expected with custom icon style 1`] = ` onStartShouldSetResponder={[Function]} style={ Array [ + Object {}, null, null, + undefined, ] } > diff --git a/packages/react-native/src/primitives/IconButton/styles.ts b/packages/react-native/src/primitives/IconButton/styles.ts index a7acfa409d6..7c59915fd09 100644 --- a/packages/react-native/src/primitives/IconButton/styles.ts +++ b/packages/react-native/src/primitives/IconButton/styles.ts @@ -7,6 +7,13 @@ export const getThemedStyles = (theme: StrictTheme): IconButtonStyles => { const { components, opacities } = theme.tokens; return StyleSheet.create({ - pressed: { opacity: opacities[60], ...components?.iconbutton.pressed }, + container: { + ...components?.iconButton.container, + }, + disabled: { + opacity: opacities[60], + ...components?.iconButton.disabled, + }, + pressed: { opacity: opacities[60], ...components?.iconButton.pressed }, }); }; diff --git a/packages/react-native/src/primitives/IconButton/types.ts b/packages/react-native/src/primitives/IconButton/types.ts index 76c2a7fff5e..616714ac939 100644 --- a/packages/react-native/src/primitives/IconButton/types.ts +++ b/packages/react-native/src/primitives/IconButton/types.ts @@ -8,6 +8,8 @@ import { } from 'react-native'; export interface IconButtonStyles { + container: ViewStyle; + disabled: ViewStyle; pressed: ViewStyle; } diff --git a/packages/react-native/src/primitives/PasswordField/__tests__/__snapshots__/PasswordField.spec.tsx.snap b/packages/react-native/src/primitives/PasswordField/__tests__/__snapshots__/PasswordField.spec.tsx.snap index a2a63431f7c..18418875db5 100644 --- a/packages/react-native/src/primitives/PasswordField/__tests__/__snapshots__/PasswordField.spec.tsx.snap +++ b/packages/react-native/src/primitives/PasswordField/__tests__/__snapshots__/PasswordField.spec.tsx.snap @@ -82,8 +82,10 @@ exports[`PasswordField renders as expected 1`] = ` onStartShouldSetResponder={[Function]} style={ Array [ + Object {}, null, null, + undefined, ] } > @@ -199,8 +201,12 @@ exports[`PasswordField renders as expected when disabled 1`] = ` onStartShouldSetResponder={[Function]} style={ Array [ + Object {}, + Object { + "opacity": 0.6, + }, null, - null, + undefined, ] } > @@ -380,8 +386,10 @@ exports[`PasswordField should be able to obscure text programmatically 1`] = ` onStartShouldSetResponder={[Function]} style={ Array [ + Object {}, null, null, + undefined, ] } > diff --git a/packages/react-native/src/theme/types.ts b/packages/react-native/src/theme/types.ts index 89992bdea5b..4a67865a533 100644 --- a/packages/react-native/src/theme/types.ts +++ b/packages/react-native/src/theme/types.ts @@ -52,7 +52,7 @@ export interface ComponentStyles { divider: DividerStyles; errorMessage: ErrorMessageStyles; heading: HeadingStyles; - iconbutton: IconButtonStyles; + iconButton: IconButtonStyles; icon: IconStyles; label: LabelStyles; radio: RadioStyles; From ddd70648a3f34f9c8d2edb8fb669b4ea1d7e2338 Mon Sep 17 00:00:00 2001 From: Caleb Pollman Date: Thu, 17 Nov 2022 18:19:18 -0800 Subject: [PATCH 3/3] fix IconButton getThemedStyles post theme refactor --- .../src/primitives/IconButton/styles.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/react-native/src/primitives/IconButton/styles.ts b/packages/react-native/src/primitives/IconButton/styles.ts index 7c59915fd09..ac721f0cb7f 100644 --- a/packages/react-native/src/primitives/IconButton/styles.ts +++ b/packages/react-native/src/primitives/IconButton/styles.ts @@ -4,16 +4,22 @@ import { StrictTheme } from '../../theme'; import { IconButtonStyles } from './types'; export const getThemedStyles = (theme: StrictTheme): IconButtonStyles => { - const { components, opacities } = theme.tokens; + const { + components, + tokens: { opacities }, + } = theme; return StyleSheet.create({ container: { - ...components?.iconButton.container, + ...components?.iconButton?.container, }, disabled: { opacity: opacities[60], - ...components?.iconButton.disabled, + ...components?.iconButton?.disabled, + }, + pressed: { + opacity: opacities[60], + ...components?.iconButton?.pressed, }, - pressed: { opacity: opacities[60], ...components?.iconButton.pressed }, }); };