diff --git a/example/src/Examples/ButtonExample.tsx b/example/src/Examples/ButtonExample.tsx index 73d8819975..900069bb11 100644 --- a/example/src/Examples/ButtonExample.tsx +++ b/example/src/Examples/ButtonExample.tsx @@ -6,16 +6,16 @@ import ScreenWrapper from '../ScreenWrapper'; const ButtonExample = () => { const theme = useTheme(); - const color = theme.isV3 ? theme.colors.secondary : theme.colors.accent; + const color = theme.isV3 ? theme.colors.inversePrimary : theme.colors.accent; return ( - + - - + {theme.isV3 && ( + + + + + + + + + + + )} + - + + - + {theme.isV3 && ( + + + + + + + + + + + )} + + @@ -177,6 +299,9 @@ const styles = StyleSheet.create({ flexReverse: { flexDirection: 'row-reverse', }, + md3FontStyles: { + lineHeight: 32, + }, fontStyles: { fontWeight: '800', fontSize: 24, diff --git a/src/babel/__fixtures__/rewrite-imports/output.js b/src/babel/__fixtures__/rewrite-imports/output.js index 2aa67895de..e1d98641e7 100644 --- a/src/babel/__fixtures__/rewrite-imports/output.js +++ b/src/babel/__fixtures__/rewrite-imports/output.js @@ -2,7 +2,7 @@ import { Text } from 'react-native'; import PaperProvider from "react-native-paper/lib/module/core/Provider"; import BottomNavigation from "react-native-paper/lib/module/components/BottomNavigation/BottomNavigation"; -import Button from "react-native-paper/lib/module/components/Button"; +import Button from "react-native-paper/lib/module/components/Button/Button"; import FAB from "react-native-paper/lib/module/components/FAB"; import Appbar from "react-native-paper/lib/module/components/Appbar"; import * as MD2Colors from "react-native-paper/lib/module/styles/themes/v2/colors"; diff --git a/src/components/Banner.tsx b/src/components/Banner.tsx index e881783e20..0dbd815c30 100644 --- a/src/components/Banner.tsx +++ b/src/components/Banner.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { View, ViewStyle, StyleSheet, StyleProp, Animated } from 'react-native'; import Surface from './Surface'; import Text from './Typography/Text'; -import Button from './Button'; +import Button from './Button/Button'; import Icon, { IconSource } from './Icon'; import { withTheme } from '../core/theming'; import type { $RemoveChildren, Theme } from '../types'; @@ -238,7 +238,7 @@ const Banner = ({ compact mode="text" style={styles.button} - color={theme.colors?.primary} + textColor={theme.colors?.primary} {...others} > {label} diff --git a/src/components/Button.tsx b/src/components/Button/Button.tsx similarity index 59% rename from src/components/Button.tsx rename to src/components/Button/Button.tsx index 27abb0c530..7c38e3074e 100644 --- a/src/components/Button.tsx +++ b/src/components/Button/Button.tsx @@ -9,25 +9,29 @@ import { } from 'react-native'; import color from 'color'; -import ActivityIndicator from './ActivityIndicator'; -import Icon, { IconSource } from './Icon'; -import Surface from './Surface'; -import Text from './Typography/Text'; -import TouchableRipple from './TouchableRipple/TouchableRipple'; -import { black, white } from '../styles/themes/v2/colors'; -import { withTheme } from '../core/theming'; -import type { Theme } from '../types'; +import ActivityIndicator from '../ActivityIndicator'; +import Icon, { IconSource } from '../Icon'; +import Surface from '../Surface'; +import Text from '../Typography/Text'; +import TouchableRipple from '../TouchableRipple/TouchableRipple'; +import { withTheme } from '../../core/theming'; +import type { Theme } from '../../types'; +import { ButtonMode, getButtonColors } from './utils'; type Props = React.ComponentProps & { /** * Mode of the button. You can change the mode to adjust the styling to give it desired emphasis. - * - `text` - flat button without background or outline (low emphasis) - * - `outlined` - button with an outline (medium emphasis) - * - `contained` - button with a background color and elevation shadow (high emphasis) + * - `text` - flat button without background or outline, used for the lowest priority actions, especially when presenting multiple options. + * - `outlined` - button with an outline without background, typically used for important, but not primary action – represents medium emphasis. + * - `contained` - button with a background color, used for important action, have the most visual impact and high emphasis. + * - `elevated` - button with a background color and elevation, used when absolutely necessary e.g. button requires visual separation from a patterned background. @supported Available in v3.x with theme version 3 + * - `container-tonal` - button with a secondary background color, an alternative middle ground between contained and outlined buttons. @supported Available in v3.x with theme version 3 */ - mode?: 'text' | 'outlined' | 'contained'; + mode?: 'text' | 'outlined' | 'contained' | 'elevated' | 'contained-tonal'; /** - * Whether the color is a dark color. A dark button will render light text and vice-versa. Only applicable for `contained` mode. + * Whether the color is a dark color. A dark button will render light text and vice-versa. Only applicable for: + * * `contained` mode for theme version 2 + * * `contained`, `contained-tonal` and `elevated` modes for theme version 3. */ dark?: boolean; /** @@ -35,9 +39,20 @@ type Props = React.ComponentProps & { */ compact?: boolean; /** + * @deprecated Deprecated in v3.x - use `buttonColor` or `textColor` instead. * Custom text color for flat button, or background color for contained button. */ color?: string; + /** + * @supported Available in v3.x + * Custom button's background color. + */ + buttonColor?: string; + /** + * @supported Available in v3.x + * Custom button's text color. + */ + textColor?: string; /** * Whether to show a loading indicator. */ @@ -133,134 +148,109 @@ const Button = ({ dark, loading, icon, - color: buttonColor, + buttonColor: customButtonColor, + textColor: customTextColor, children, - uppercase = true, accessibilityLabel, accessibilityHint, onPress, onLongPress, style, theme, + uppercase = !theme.isV3, contentStyle, labelStyle, testID, accessible, ...rest }: Props) => { - const containedInitialElevation = theme.isV3 ? 1 : 2; - const containedActiveElevation = theme.isV3 ? 2 : 8; + const isMode = React.useCallback( + (modeToCompare: ButtonMode) => { + return mode === modeToCompare; + }, + [mode] + ); + const { roundness, isV3, animation, fonts } = theme; + + const isElevationEntitled = isV3 ? isMode('elevated') : isMode('contained'); + const initialElevation = isV3 ? 1 : 2; + const activeElevation = isV3 ? 2 : 8; const { current: elevation } = React.useRef( - new Animated.Value(mode === 'contained' ? containedInitialElevation : 0) + new Animated.Value(isElevationEntitled ? initialElevation : 0) ); + React.useEffect(() => { - elevation.setValue(mode === 'contained' ? containedInitialElevation : 0); - }, [mode, elevation, containedInitialElevation]); + elevation.setValue(isElevationEntitled ? initialElevation : 0); + }, [isElevationEntitled, elevation, initialElevation]); const handlePressIn = () => { - if (mode === 'contained') { - const { scale } = theme.animation; + if (isMode('contained')) { + const { scale } = animation; Animated.timing(elevation, { - toValue: containedActiveElevation, + toValue: activeElevation, duration: 200 * scale, - useNativeDriver: false, + useNativeDriver: true, }).start(); } }; const handlePressOut = () => { - if (mode === 'contained') { - const { scale } = theme.animation; + if (isMode('contained')) { + const { scale } = animation; Animated.timing(elevation, { - toValue: containedInitialElevation, + toValue: initialElevation, duration: 150 * scale, - useNativeDriver: false, + useNativeDriver: true, }).start(); } }; - const { colors, roundness } = theme; - const font = theme.fonts.medium; + const borderRadius = (isV3 ? 5 : 1) * roundness; + const iconSize = isV3 ? 18 : 16; - let backgroundColor: string, - borderColor: string, - textColor: string, - borderWidth: number; - - if (mode === 'contained') { - if (disabled) { - backgroundColor = color(theme.dark ? white : black) - .alpha(0.12) - .rgb() - .string(); - } else if (buttonColor) { - backgroundColor = buttonColor; - } else { - backgroundColor = colors?.primary || white; - } - } else { - backgroundColor = 'transparent'; - } - - if (mode === 'outlined') { - borderColor = color(theme.dark ? white : black) - .alpha(0.29) - .rgb() - .string(); - borderWidth = StyleSheet.hairlineWidth; - } else { - borderColor = 'transparent'; - borderWidth = 0; - } - - if (disabled) { - textColor = color(theme.dark ? white : black) - .alpha(0.32) - .rgb() - .string(); - } else if (mode === 'contained') { - let isDark; - - if (typeof dark === 'boolean') { - isDark = dark; - } else { - isDark = - backgroundColor === 'transparent' - ? false - : !color(backgroundColor).isLight(); - } + const { backgroundColor, borderColor, textColor, borderWidth } = + getButtonColors({ + customButtonColor, + customTextColor, + theme, + mode, + disabled, + dark, + }); - textColor = isDark ? white : black; - } else if (buttonColor) { - textColor = buttonColor; - } else { - textColor = colors?.primary || black; - } + const rippleColor = color(textColor).alpha(0.12).rgb().string(); - const rippleColor = color(textColor).alpha(0.32).rgb().string(); const buttonStyle = { backgroundColor, borderColor, borderWidth, - borderRadius: roundness, + borderRadius, }; const touchableStyle = { borderRadius: style ? ((StyleSheet.flatten(style) || {}) as ViewStyle).borderRadius || - roundness - : roundness, + borderRadius + : borderRadius, }; const { color: customLabelColor, fontSize: customLabelSize } = StyleSheet.flatten(labelStyle) || {}; - const textStyle = { color: textColor, ...font }; - const elevationRes = disabled || mode !== 'contained' ? 0 : elevation; + const textStyle = { color: textColor, ...(!isV3 && fonts.medium) }; + const elevationRes = disabled || !isElevationEntitled ? 0 : elevation; const iconStyle = StyleSheet.flatten(contentStyle)?.flexDirection === 'row-reverse' - ? styles.iconReverse - : styles.icon; + ? [ + styles.iconReverse, + isV3 && styles.md3IconReverse, + isV3 && isMode('text') && styles.md3IconReverseTextMode, + ] + : [ + styles.icon, + isV3 && styles.md3Icon, + isV3 && isMode('text') && styles.md3IconTextMode, + ]; return ( ) : null} @@ -360,18 +357,46 @@ const styles = StyleSheet.create({ marginRight: 12, marginLeft: -4, }, + md3Icon: { + marginLeft: 16, + marginRight: -16, + }, + md3IconReverse: { + marginLeft: -16, + marginRight: 16, + }, + md3IconTextMode: { + marginLeft: 12, + marginRight: -8, + }, + md3IconReverseTextMode: { + marginLeft: -8, + marginRight: 12, + }, label: { textAlign: 'center', - letterSpacing: 1, marginVertical: 9, marginHorizontal: 16, }, + md2Label: { + letterSpacing: 1, + }, compactLabel: { marginHorizontal: 8, }, uppercaseLabel: { textTransform: 'uppercase', }, + md3Label: { + marginVertical: 10, + marginHorizontal: 24, + }, + md3LabelText: { + marginHorizontal: 12, + }, + md3LabelTextAddons: { + marginHorizontal: 16, + }, }); export default withTheme(Button); diff --git a/src/components/Button/utils.tsx b/src/components/Button/utils.tsx new file mode 100644 index 0000000000..3e7ae978d0 --- /dev/null +++ b/src/components/Button/utils.tsx @@ -0,0 +1,230 @@ +import { StyleSheet } from 'react-native'; +import color from 'color'; +import { black, white } from '../../styles/themes/v2/colors'; +import type { Theme } from '../../types'; + +export type ButtonMode = + | 'text' + | 'outlined' + | 'contained' + | 'elevated' + | 'contained-tonal'; + +type BaseProps = { + isMode: (mode: ButtonMode) => boolean; + theme: Theme; + disabled?: boolean; +}; + +const isDark = ({ + dark, + backgroundColor, +}: { + dark?: boolean; + backgroundColor?: string; +}) => { + if (typeof dark === 'boolean') { + return dark; + } + + if (backgroundColor === 'transparent') { + return false; + } + + if (backgroundColor !== 'transparent') { + return !color(backgroundColor).isLight(); + } + + return false; +}; + +const getButtonBackgroundColor = ({ + isMode, + theme, + disabled, + customButtonColor, +}: BaseProps & { + customButtonColor?: string; +}) => { + if (customButtonColor && !disabled) { + return customButtonColor; + } + + if (theme.isV3) { + if (disabled) { + if (isMode('outlined') || isMode('text')) { + return 'transparent'; + } + + return theme.colors.surfaceDisabled; + } + + if (isMode('elevated')) { + return theme.colors.elevation.level1; + } + + if (isMode('contained')) { + return theme.colors.primary; + } + + if (isMode('contained-tonal')) { + return theme.colors.secondaryContainer; + } + } + + if (isMode('contained')) { + if (disabled) { + return color(theme.dark ? white : black) + .alpha(0.12) + .rgb() + .string(); + } + + return theme.colors.primary; + } + + return 'transparent'; +}; + +const getButtonTextColor = ({ + isMode, + theme, + disabled, + customTextColor, + backgroundColor, + dark, +}: BaseProps & { + customTextColor?: string; + backgroundColor: string; + dark?: boolean; +}) => { + if (customTextColor && !disabled) { + return customTextColor; + } + + if (theme.isV3) { + if (disabled) { + return theme.colors.onSurfaceDisabled; + } + + if (typeof dark === 'boolean') { + if ( + isMode('contained') || + isMode('contained-tonal') || + isMode('elevated') + ) { + return isDark({ dark, backgroundColor }) ? white : black; + } + } + + if (isMode('outlined') || isMode('text') || isMode('elevated')) { + return theme.colors.primary; + } + + if (isMode('contained')) { + return theme.colors.onPrimary; + } + + if (isMode('contained-tonal')) { + return theme.colors.onSecondaryContainer; + } + } + + if (disabled) { + return color(theme.dark ? white : black) + .alpha(0.32) + .rgb() + .string(); + } + + if (isMode('contained')) { + return isDark({ dark, backgroundColor }) ? white : black; + } + + return theme.colors.primary; +}; + +const getButtonBorderColor = ({ isMode, disabled, theme }: BaseProps) => { + if (theme.isV3) { + if (disabled && isMode('outlined')) { + return theme.colors.surfaceDisabled; + } + + if (isMode('outlined')) { + return theme.colors.outline; + } + } + + if (isMode('outlined')) { + return color(theme.dark ? white : black) + .alpha(0.29) + .rgb() + .string(); + } + + return 'transparent'; +}; + +const getButtonBorderWidth = ({ + isMode, + theme, +}: Omit) => { + if (theme.isV3) { + if (isMode('outlined')) { + return 1; + } + } + + if (isMode('outlined')) { + return StyleSheet.hairlineWidth; + } + + return 0; +}; + +export const getButtonColors = ({ + theme, + mode, + customButtonColor, + customTextColor, + disabled, + dark, +}: { + theme: Theme; + mode: ButtonMode; + customButtonColor?: string; + customTextColor?: string; + disabled?: boolean; + dark?: boolean; +}) => { + const isMode = (modeToCompare: ButtonMode) => { + return mode === modeToCompare; + }; + + const backgroundColor = getButtonBackgroundColor({ + isMode, + theme, + disabled, + customButtonColor, + }); + + const textColor = getButtonTextColor({ + isMode, + theme, + disabled, + customTextColor, + backgroundColor, + dark, + }); + + const borderColor = getButtonBorderColor({ isMode, theme, disabled }); + + const borderWidth = getButtonBorderWidth({ isMode, theme }); + + return { + backgroundColor, + borderColor, + textColor, + borderWidth, + }; +}; diff --git a/src/components/DataTable/DataTablePagination.tsx b/src/components/DataTable/DataTablePagination.tsx index 2ea2f5603f..ee849854c5 100644 --- a/src/components/DataTable/DataTablePagination.tsx +++ b/src/components/DataTable/DataTablePagination.tsx @@ -12,7 +12,7 @@ import Text from '../Typography/Text'; import { withTheme, useTheme } from '../../core/theming'; import MaterialCommunityIcon from '../MaterialCommunityIcon'; import Menu from '../Menu/Menu'; -import Button from '../Button'; +import Button from '../Button/Button'; import type { Theme } from '../../types'; type Props = React.ComponentPropsWithRef & diff --git a/src/components/Snackbar.tsx b/src/components/Snackbar.tsx index 8bda42c595..ff6c464618 100644 --- a/src/components/Snackbar.tsx +++ b/src/components/Snackbar.tsx @@ -9,7 +9,7 @@ import { Easing, } from 'react-native'; -import Button from './Button'; +import Button from './Button/Button'; import Surface from './Surface'; import Text from './Typography/Text'; import { withTheme } from '../core/theming'; @@ -188,7 +188,7 @@ const Snackbar = ({ const marginRight = action ? 0 : 16; const textColor = theme.isV3 ? theme.colors.inversePrimary - : theme.colors?.accent; + : theme.colors.accent; return ( { + const theme = isDark + ? isV3 + ? MD3DarkTheme + : MD2DarkTheme + : isV3 + ? MD3LightTheme + : MD2LightTheme; + return { + ...theme, + isV3, + md: (tokenKey) => get(theme.tokens, tokenKey), + }; +}; + it('renders text button by default', () => { const tree = renderer.create().toJSON(); @@ -78,7 +100,15 @@ it('renders disabled button', () => { it('renders button with color', () => { const tree = renderer - .create() + .create() + .toJSON(); + + expect(tree).toMatchSnapshot(); +}); + +it('renders button with button color', () => { + const tree = renderer + .create() .toJSON(); expect(tree).toMatchSnapshot(); @@ -113,3 +143,595 @@ it('renders button with an accessibility hint', () => { expect(tree).toMatchSnapshot(); }); + +describe('getButtonColors - background color', () => { + const customButtonColor = '#111111'; + + it('should return custom color no matter what is the theme version, when not disabled', () => { + expect( + getButtonColors({ + customButtonColor, + theme: getTheme(), + disabled: false, + }) + ).toMatchObject({ backgroundColor: customButtonColor }); + }); + + ['outlined', 'text'].forEach((mode) => + it(`should return correct disabled color, for theme version 3, ${mode} mode`, () => { + expect( + getButtonColors({ + customButtonColor, + theme: getTheme(), + mode, + disabled: true, + }) + ).toMatchObject({ backgroundColor: 'transparent' }); + }) + ); + + ['outlined', 'text'].forEach((mode) => + it(`should return correct disabled color, for theme version 3, dark theme, ${mode} mode`, () => { + expect( + getButtonColors({ + customButtonColor, + theme: getTheme(), + mode, + disabled: true, + }) + ).toMatchObject({ backgroundColor: 'transparent' }); + }) + ); + + ['contained', 'contained-tonal', 'elevated'].forEach((mode) => + it(`should return correct disabled color, for theme version 3, ${mode} mode`, () => { + return expect( + getButtonColors({ + customButtonColor, + theme: getTheme(), + mode, + disabled: true, + }) + ).toMatchObject({ + backgroundColor: getTheme().colors.surfaceDisabled, + }); + }) + ); + + ['contained', 'contained-tonal', 'elevated'].forEach((mode) => + it(`should return correct disabled color, for theme version 3, dark theme, ${mode} mode`, () => { + return expect( + getButtonColors({ + customButtonColor, + theme: getTheme(true), + mode, + disabled: true, + }) + ).toMatchObject({ + backgroundColor: getTheme(true).colors.surfaceDisabled, + }); + }) + ); + + it('should return correct theme color, for theme version 3, elevated mode', () => { + expect( + getButtonColors({ + theme: getTheme(), + mode: 'elevated', + }) + ).toMatchObject({ + backgroundColor: getTheme().colors.elevation.level1, + }); + }); + + it('should return correct theme color, for theme version 3, dark theme, elevated mode', () => { + expect( + getButtonColors({ + theme: getTheme(true), + mode: 'elevated', + }) + ).toMatchObject({ + backgroundColor: getTheme(true).colors.elevation.level1, + }); + }); + + it('should return correct theme color, for theme version 3, contained mode', () => { + expect( + getButtonColors({ + theme: getTheme(), + mode: 'contained', + }) + ).toMatchObject({ + backgroundColor: getTheme().colors.primary, + }); + }); + + it('should return correct theme color, for theme version 3, dark theme, contained mode', () => { + expect( + getButtonColors({ + theme: getTheme(true), + mode: 'contained', + }) + ).toMatchObject({ + backgroundColor: getTheme(true).colors.primary, + }); + }); + + it('should return correct theme color, for theme version 3, contained-tonal mode', () => { + expect( + getButtonColors({ + theme: getTheme(), + mode: 'contained-tonal', + }) + ).toMatchObject({ + backgroundColor: getTheme().colors.secondaryContainer, + }); + }); + + it('should return correct theme color, for theme version 3, dark theme, contained-tonal mode', () => { + expect( + getButtonColors({ + theme: getTheme(true), + mode: 'contained-tonal', + }) + ).toMatchObject({ + backgroundColor: getTheme(true).colors.secondaryContainer, + }); + }); + + ['text', 'outlined'].forEach((mode) => + it(`should return transparent color, for theme version 3, ${mode} mode`, () => { + return expect( + getButtonColors({ + theme: getTheme(), + mode, + }) + ).toMatchObject({ + backgroundColor: 'transparent', + }); + }) + ); + + ['text', 'outlined'].forEach((mode) => + it(`should return transparent color, for theme version 3, dark theme, ${mode} mode`, () => { + return expect( + getButtonColors({ + theme: getTheme(true), + mode, + }) + ).toMatchObject({ + backgroundColor: 'transparent', + }); + }) + ); + + it('should return correct theme color, for theme version 2, contained mode', () => { + expect( + getButtonColors({ + theme: getTheme(false, false), + mode: 'contained', + }) + ).toMatchObject({ + backgroundColor: getTheme(false, false).colors.primary, + }); + }); + + it('should return correct theme color, for theme version 2, when disabled, contained mode', () => { + expect( + getButtonColors({ + theme: getTheme(false, false), + mode: 'contained', + disabled: true, + }) + ).toMatchObject({ + backgroundColor: color(black).alpha(0.12).rgb().string(), + }); + }); + + it('should return correct theme color, for theme version 2, when disabled, dark theme, contained mode', () => { + expect( + getButtonColors({ + theme: getTheme(true, false), + mode: 'contained', + disabled: true, + }) + ).toMatchObject({ + backgroundColor: color(white).alpha(0.12).rgb().string(), + }); + }); + + ['text', 'outlined'].forEach((mode) => + it(`should return correct theme color, for theme version 2, ${mode} mode`, () => { + expect( + getButtonColors({ + theme: getTheme(false, false), + mode, + }) + ).toMatchObject({ + backgroundColor: 'transparent', + }); + }) + ); + + ['text', 'outlined'].forEach((mode) => + it(`should return correct theme color, for theme version 2, dark theme, ${mode} mode`, () => { + expect( + getButtonColors({ + theme: getTheme(true, false), + mode, + }) + ).toMatchObject({ + backgroundColor: 'transparent', + }); + }) + ); + + ['text', 'outlined'].forEach((mode) => + it(`should return correct theme color, for theme version 2, when disabled, ${mode} mode`, () => { + expect( + getButtonColors({ + theme: getTheme(false, false), + mode, + disabled: true, + }) + ).toMatchObject({ + backgroundColor: 'transparent', + }); + }) + ); + + ['text', 'outlined'].forEach((mode) => + it(`should return correct theme color, for theme version 2, when disabled, dark theme, ${mode} mode`, () => { + expect( + getButtonColors({ + theme: getTheme(true, false), + mode, + disabled: true, + }) + ).toMatchObject({ + backgroundColor: 'transparent', + }); + }) + ); +}); + +describe('getButtonColors - text color', () => { + const customTextColor = '#313131'; + + it('should return custom text color no matter what is the theme version, when not disabled', () => { + expect( + getButtonColors({ + customTextColor, + theme: getTheme(), + disabled: false, + }) + ).toMatchObject({ textColor: customTextColor }); + }); + + it('should return correct disabled text color, for theme version 3, no matter what the mode is', () => { + expect( + getButtonColors({ + customTextColor, + theme: getTheme(), + disabled: true, + }) + ).toMatchObject({ + textColor: getTheme().colors.onSurfaceDisabled, + }); + }); + + it('should return correct disabled text color, for theme version 3, dark theme, no matter what the mode is', () => { + expect( + getButtonColors({ + customTextColor, + theme: getTheme(true), + disabled: true, + }) + ).toMatchObject({ + textColor: getTheme(true).colors.onSurfaceDisabled, + }); + }); + + ['contained', 'contained-tonal', 'elevated'].forEach((mode) => + it(`should return correct text color for dark prop, for theme version 3, ${mode} mode`, () => { + expect( + getButtonColors({ + theme: getTheme(), + mode, + dark: true, + }) + ).toMatchObject({ + textColor: white, + }); + }) + ); + + ['outlined', 'text', 'elevated'].forEach((mode) => + it(`should return correct theme text color, for theme version 3, ${mode} mode`, () => { + expect( + getButtonColors({ + theme: getTheme(), + mode, + }) + ).toMatchObject({ + textColor: getTheme().colors.primary, + }); + }) + ); + + ['outlined', 'text', 'elevated'].forEach((mode) => + it(`should return correct theme text color, for theme version 3, dark theme, ${mode} mode`, () => { + expect( + getButtonColors({ + theme: getTheme(true), + mode, + }) + ).toMatchObject({ + textColor: getTheme(true).colors.primary, + }); + }) + ); + + it('should return correct theme text color, for theme version 3, contained mode', () => { + expect( + getButtonColors({ + theme: getTheme(), + mode: 'contained', + }) + ).toMatchObject({ + textColor: getTheme().colors.onPrimary, + }); + }); + + it('should return correct theme text color, for theme version 3, dark theme, contained mode', () => { + expect( + getButtonColors({ + theme: getTheme(true), + mode: 'contained', + }) + ).toMatchObject({ + textColor: getTheme(true).colors.onPrimary, + }); + }); + + it('should return correct theme text color, for theme version 3, contained-tonal mode', () => { + expect( + getButtonColors({ + theme: getTheme(), + mode: 'contained-tonal', + }) + ).toMatchObject({ + textColor: getTheme().colors.onSecondaryContainer, + }); + }); + + it('should return correct theme text color, for theme version 3, dark theme contained-tonal mode', () => { + expect( + getButtonColors({ + theme: getTheme(true), + mode: 'contained-tonal', + }) + ).toMatchObject({ + textColor: getTheme(true).colors.onSecondaryContainer, + }); + }); + + it('should return correct theme text color, for theme version 2, when disabled, no matter what the mode is', () => { + expect( + getButtonColors({ + theme: getTheme(false, false), + disabled: true, + }) + ).toMatchObject({ + textColor: color(black).alpha(0.32).rgb().string(), + }); + }); + + it('should return correct theme text color, for theme version 2, when disabled, dark theme, no matter what the mode is', () => { + expect( + getButtonColors({ + theme: getTheme(true, false), + disabled: true, + }) + ).toMatchObject({ + textColor: color(white).alpha(0.32).rgb().string(), + }); + }); + + it('should return correct theme text color, for theme version 2, contained mode', () => { + expect( + getButtonColors({ + theme: getTheme(false, false), + mode: 'contained', + dark: true, + }) + ).toMatchObject({ + textColor: '#ffffff', + }); + }); + + ['text', 'outlined'].forEach((mode) => + it(`should return correct theme text color, for theme version 2, ${mode} mode`, () => { + expect( + getButtonColors({ + theme: getTheme(false, false), + mode, + }) + ).toMatchObject({ + textColor: getTheme(false, false).colors.primary, + }); + }) + ); + + ['text', 'outlined'].forEach((mode) => + it(`should return correct theme text color, for theme version 2, dark theme, ${mode} mode`, () => { + expect( + getButtonColors({ + theme: getTheme(true, false), + mode, + }) + ).toMatchObject({ + textColor: getTheme(true, false).colors.primary, + }); + }) + ); +}); + +describe('getButtonColors - border color', () => { + it('should return correct border color, for theme version 3, when disabled, outlined mode', () => { + expect( + getButtonColors({ + theme: getTheme(), + disabled: true, + mode: 'outlined', + }) + ).toMatchObject({ + borderColor: getTheme().colors.surfaceDisabled, + }); + }); + + it('should return correct border color, for theme version 3, when disabled, dark theme, outlined mode', () => { + expect( + getButtonColors({ + theme: getTheme(true), + disabled: true, + mode: 'outlined', + }) + ).toMatchObject({ + borderColor: getTheme(true).colors.surfaceDisabled, + }); + }); + + it('should return correct border color, for theme version 3, outlined mode', () => { + expect( + getButtonColors({ + theme: getTheme(), + mode: 'outlined', + }) + ).toMatchObject({ + borderColor: getTheme().colors.outline, + }); + }); + + it('should return correct border color, for theme version 3, dark theme, outlined mode', () => { + expect( + getButtonColors({ + theme: getTheme(true), + mode: 'outlined', + }) + ).toMatchObject({ + borderColor: getTheme(true).colors.outline, + }); + }); + + ['text', 'contained', 'contained-tonal', 'elevated'].forEach((mode) => + it(`should return transparent border, for theme version 3, ${mode} mode`, () => { + expect( + getButtonColors({ + theme: getTheme(), + mode, + }) + ).toMatchObject({ + borderColor: 'transparent', + }); + }) + ); + + ['text', 'contained', 'contained-tonal', 'elevated'].forEach((mode) => + it(`should return transparent border, for theme version 3, dark theme, ${mode} mode`, () => { + expect( + getButtonColors({ + theme: getTheme(true), + mode, + }) + ).toMatchObject({ + borderColor: 'transparent', + }); + }) + ); + + it('should return correct border color, for theme version 2, outlined mode', () => { + expect( + getButtonColors({ + theme: getTheme(false, false), + mode: 'outlined', + }) + ).toMatchObject({ + borderColor: color(black).alpha(0.29).rgb().string(), + }); + }); + + it('should return correct border color, for theme version 2, dark theme, outlined mode', () => { + expect( + getButtonColors({ + theme: getTheme(true, false), + mode: 'outlined', + }) + ).toMatchObject({ + borderColor: color(white).alpha(0.29).rgb().string(), + }); + }); + + ['text', 'contained', 'contained-tonal', 'elevated'].forEach((mode) => + it(`should return transparent border, for theme version 2, ${mode} mode`, () => { + expect( + getButtonColors({ + theme: getTheme(false, false), + mode, + }) + ).toMatchObject({ + borderColor: 'transparent', + }); + }) + ); + + ['text', 'contained', 'contained-tonal', 'elevated'].forEach((mode) => + it(`should return transparent border, for theme version 2, dark theme, ${mode} mode`, () => { + expect( + getButtonColors({ + theme: getTheme(false, false), + mode, + }) + ).toMatchObject({ + borderColor: 'transparent', + }); + }) + ); +}); + +describe('getButtonColors - border width', () => { + it('should return correct border width, for theme version 3, outlined mode', () => { + expect( + getButtonColors({ + theme: getTheme(), + mode: 'outlined', + }) + ).toMatchObject({ + borderWidth: 1, + }); + }); + + it('should return correct border width, for theme version 2, outlined mode', () => { + expect( + getButtonColors({ + theme: getTheme(false, false), + mode: 'outlined', + }) + ).toMatchObject({ + borderWidth: StyleSheet.hairlineWidth, + }); + }); + + ['text', 'contained', 'contained-tonal', 'elevated'].forEach((mode) => + it(`should return correct border width, for ${mode} mode`, () => { + expect( + getButtonColors({ + theme: getTheme(), + mode, + }) + ).toMatchObject({ + borderWidth: 0, + }); + }) + ); +}); diff --git a/src/components/__tests__/Menu.test.js b/src/components/__tests__/Menu.test.js index f264ae0e5e..492e8f5fc5 100644 --- a/src/components/__tests__/Menu.test.js +++ b/src/components/__tests__/Menu.test.js @@ -2,7 +2,7 @@ import * as React from 'react'; import { StyleSheet } from 'react-native'; import renderer from 'react-test-renderer'; import Menu from '../Menu/Menu.tsx'; -import Button from '../Button.tsx'; +import Button from '../Button/Button.tsx'; const styles = StyleSheet.create({ contentStyle: { diff --git a/src/components/__tests__/__snapshots__/Banner.test.js.snap b/src/components/__tests__/__snapshots__/Banner.test.js.snap index 1bfe49d1d1..af0cffa26b 100644 --- a/src/components/__tests__/__snapshots__/Banner.test.js.snap +++ b/src/components/__tests__/__snapshots__/Banner.test.js.snap @@ -156,11 +156,14 @@ exports[`render visible banner, with custom theme 1`] = ` }, Array [ Object { - "letterSpacing": 1, "marginHorizontal": 16, "marginVertical": 9, "textAlign": "center", }, + Object { + "letterSpacing": 1, + }, + false, Object { "marginHorizontal": 8, }, @@ -172,10 +175,6 @@ exports[`render visible banner, with custom theme 1`] = ` "fontFamily": "System", "fontWeight": "500", }, - Object { - "fontFamily": "System", - "fontWeight": "500", - }, undefined, ], ] @@ -454,11 +453,14 @@ exports[`renders visible banner, with action buttons and with image 1`] = ` }, Array [ Object { - "letterSpacing": 1, "marginHorizontal": 16, "marginVertical": 9, "textAlign": "center", }, + Object { + "letterSpacing": 1, + }, + false, Object { "marginHorizontal": 8, }, @@ -470,10 +472,6 @@ exports[`renders visible banner, with action buttons and with image 1`] = ` "fontFamily": "System", "fontWeight": "500", }, - Object { - "fontFamily": "System", - "fontWeight": "500", - }, undefined, ], ] @@ -646,11 +644,14 @@ exports[`renders visible banner, with action buttons and without image 1`] = ` }, Array [ Object { - "letterSpacing": 1, "marginHorizontal": 16, "marginVertical": 9, "textAlign": "center", }, + Object { + "letterSpacing": 1, + }, + false, Object { "marginHorizontal": 8, }, @@ -662,10 +663,6 @@ exports[`renders visible banner, with action buttons and without image 1`] = ` "fontFamily": "System", "fontWeight": "500", }, - Object { - "fontFamily": "System", - "fontWeight": "500", - }, undefined, ], ] @@ -744,11 +741,14 @@ exports[`renders visible banner, with action buttons and without image 1`] = ` }, Array [ Object { - "letterSpacing": 1, "marginHorizontal": 16, "marginVertical": 9, "textAlign": "center", }, + Object { + "letterSpacing": 1, + }, + false, Object { "marginHorizontal": 8, }, @@ -760,10 +760,6 @@ exports[`renders visible banner, with action buttons and without image 1`] = ` "fontFamily": "System", "fontWeight": "500", }, - Object { - "fontFamily": "System", - "fontWeight": "500", - }, undefined, ], ] diff --git a/src/components/__tests__/__snapshots__/Button.test.js.snap b/src/components/__tests__/__snapshots__/Button.test.js.snap index a0d04b8b4d..4fb393b51b 100644 --- a/src/components/__tests__/__snapshots__/Button.test.js.snap +++ b/src/components/__tests__/__snapshots__/Button.test.js.snap @@ -69,11 +69,14 @@ exports[`renders button with an accessibility hint 1`] = ` }, Array [ Object { - "letterSpacing": 1, "marginHorizontal": 16, "marginVertical": 9, "textAlign": "center", }, + Object { + "letterSpacing": 1, + }, + false, undefined, Object { "textTransform": "uppercase", @@ -83,10 +86,6 @@ exports[`renders button with an accessibility hint 1`] = ` "fontFamily": "System", "fontWeight": "500", }, - Object { - "fontFamily": "System", - "fontWeight": "500", - }, undefined, ], ] @@ -168,11 +167,14 @@ exports[`renders button with an accessibility label 1`] = ` }, Array [ Object { - "letterSpacing": 1, "marginHorizontal": 16, "marginVertical": 9, "textAlign": "center", }, + Object { + "letterSpacing": 1, + }, + false, undefined, Object { "textTransform": "uppercase", @@ -182,7 +184,100 @@ exports[`renders button with an accessibility label 1`] = ` "fontFamily": "System", "fontWeight": "500", }, + undefined, + ], + ] + } + > + Button with accessibility label + + + + +`; + +exports[`renders button with button color 1`] = ` + + + + - Button with accessibility label + Custom Button @@ -266,11 +361,14 @@ exports[`renders button with color 1`] = ` }, Array [ Object { - "letterSpacing": 1, "marginHorizontal": 16, "marginVertical": 9, "textAlign": "center", }, + Object { + "letterSpacing": 1, + }, + false, undefined, Object { "textTransform": "uppercase", @@ -280,10 +378,6 @@ exports[`renders button with color 1`] = ` "fontFamily": "System", "fontWeight": "500", }, - Object { - "fontFamily": "System", - "fontWeight": "500", - }, undefined, ], ] @@ -365,11 +459,14 @@ exports[`renders button with custom testID 1`] = ` }, Array [ Object { - "letterSpacing": 1, "marginHorizontal": 16, "marginVertical": 9, "textAlign": "center", }, + Object { + "letterSpacing": 1, + }, + false, undefined, Object { "textTransform": "uppercase", @@ -379,10 +476,6 @@ exports[`renders button with custom testID 1`] = ` "fontFamily": "System", "fontWeight": "500", }, - Object { - "fontFamily": "System", - "fontWeight": "500", - }, undefined, ], ] @@ -450,10 +543,14 @@ exports[`renders button with icon 1`] = ` > @@ -1110,11 +1211,14 @@ exports[`renders loading button 1`] = ` }, Array [ Object { - "letterSpacing": 1, "marginHorizontal": 16, "marginVertical": 9, "textAlign": "center", }, + Object { + "letterSpacing": 1, + }, + false, undefined, Object { "textTransform": "uppercase", @@ -1124,10 +1228,6 @@ exports[`renders loading button 1`] = ` "fontFamily": "System", "fontWeight": "500", }, - Object { - "fontFamily": "System", - "fontWeight": "500", - }, undefined, ], ] @@ -1208,11 +1308,14 @@ exports[`renders outlined button with mode 1`] = ` }, Array [ Object { - "letterSpacing": 1, "marginHorizontal": 16, "marginVertical": 9, "textAlign": "center", }, + Object { + "letterSpacing": 1, + }, + false, undefined, Object { "textTransform": "uppercase", @@ -1222,10 +1325,6 @@ exports[`renders outlined button with mode 1`] = ` "fontFamily": "System", "fontWeight": "500", }, - Object { - "fontFamily": "System", - "fontWeight": "500", - }, undefined, ], ] @@ -1306,11 +1405,14 @@ exports[`renders text button by default 1`] = ` }, Array [ Object { - "letterSpacing": 1, "marginHorizontal": 16, "marginVertical": 9, "textAlign": "center", }, + Object { + "letterSpacing": 1, + }, + false, undefined, Object { "textTransform": "uppercase", @@ -1320,10 +1422,6 @@ exports[`renders text button by default 1`] = ` "fontFamily": "System", "fontWeight": "500", }, - Object { - "fontFamily": "System", - "fontWeight": "500", - }, undefined, ], ] @@ -1404,11 +1502,14 @@ exports[`renders text button with mode 1`] = ` }, Array [ Object { - "letterSpacing": 1, "marginHorizontal": 16, "marginVertical": 9, "textAlign": "center", }, + Object { + "letterSpacing": 1, + }, + false, undefined, Object { "textTransform": "uppercase", @@ -1418,10 +1519,6 @@ exports[`renders text button with mode 1`] = ` "fontFamily": "System", "fontWeight": "500", }, - Object { - "fontFamily": "System", - "fontWeight": "500", - }, undefined, ], ] diff --git a/src/components/__tests__/__snapshots__/DataTable.test.js.snap b/src/components/__tests__/__snapshots__/DataTable.test.js.snap index 6423882c25..9ea4bc91a0 100644 --- a/src/components/__tests__/__snapshots__/DataTable.test.js.snap +++ b/src/components/__tests__/__snapshots__/DataTable.test.js.snap @@ -1007,10 +1007,14 @@ exports[`renders data table pagination with options select 1`] = ` >