diff --git a/example/src/Examples/MenuExample.tsx b/example/src/Examples/MenuExample.tsx index f01827abff..5f79d5a64d 100644 --- a/example/src/Examples/MenuExample.tsx +++ b/example/src/Examples/MenuExample.tsx @@ -14,8 +14,6 @@ import { List, TouchableRipple, useTheme, - MD2Colors, - MD3Colors, } from 'react-native-paper'; import ScreenWrapper from '../ScreenWrapper'; diff --git a/src/components/Menu/MenuItem.tsx b/src/components/Menu/MenuItem.tsx index ac7e541cfc..5f67b2ba01 100644 --- a/src/components/Menu/MenuItem.tsx +++ b/src/components/Menu/MenuItem.tsx @@ -1,4 +1,3 @@ -import color from 'color'; import * as React from 'react'; import { StyleProp, @@ -10,12 +9,14 @@ import { import Icon, { IconSource } from '../Icon'; import TouchableRipple from '../TouchableRipple/TouchableRipple'; import Text from '../Typography/Text'; -import { black, white } from '../../styles/themes/v2/colors'; import { withTheme } from '../../core/theming'; import type { Theme } from '../../types'; - -const MIN_WIDTH = 112; -const MAX_WIDTH = 280; +import { + getContentMaxWidth, + getMenuItemColor, + MAX_WIDTH, + MIN_WIDTH, +} from './utils'; type Props = { /** @@ -110,62 +111,24 @@ const MenuItem = ({ accessibilityLabel, theme, }: Props) => { - const disabledColor = theme.isV3 - ? theme.colors.onSurfaceDisabled - : color(theme.dark ? white : black) - .alpha(0.32) - .rgb() - .string(); - - const titleColor = disabled - ? disabledColor - : theme.isV3 - ? theme.colors.onSurface - : color(theme.colors.text).alpha(0.87).rgb().string(); - - const iconColor = disabled - ? disabledColor - : theme.isV3 - ? theme.colors.onSurfaceVariant - : color(theme.colors.text).alpha(0.54).rgb().string(); - - const underlayColor = theme.isV3 - ? color(theme.colors.primary).alpha(0.12).rgb().string() - : undefined; - - const iconWidth = theme.isV3 ? 24 : 40; - - const getContentWidth = React.useMemo(() => { - let maxWidth = MAX_WIDTH; - let minWidth = MIN_WIDTH; + const { titleColor, iconColor, underlayColor } = getMenuItemColor({ + theme, + disabled, + }); + const { isV3 } = theme; - if (theme.isV3) { - minWidth = minWidth - 12; - if (leadingIcon || trailingIcon) { - maxWidth = maxWidth - (iconWidth + 24); - } else if (leadingIcon && trailingIcon) { - maxWidth = maxWidth - (2 * iconWidth + 24); - } else { - maxWidth = maxWidth - 12; - } - } else { - minWidth = minWidth - 16; - if (leadingIcon) { - maxWidth = maxWidth - (iconWidth + 48); - } else { - maxWidth = maxWidth - 16; - } - } + const containerPadding = isV3 ? 12 : 8; - return { - minWidth, - maxWidth, - }; - }, [theme.isV3, leadingIcon, trailingIcon]); + const iconWidth = isV3 ? 24 : 40; - const { minWidth, maxWidth } = getContentWidth; + const minWidth = MIN_WIDTH - (isV3 ? 12 : 16); - const containerPadding = theme.isV3 ? 12 : 8; + const maxWidth = getContentMaxWidth({ + isV3, + iconWidth, + leadingIcon, + trailingIcon, + }); return ( {leadingIcon ? ( @@ -194,10 +157,10 @@ const MenuItem = ({ ) : null} {title} - {theme.isV3 && trailingIcon ? ( + {isV3 && trailingIcon ? ( diff --git a/src/components/Menu/utils.ts b/src/components/Menu/utils.ts new file mode 100644 index 0000000000..3ec9b32af1 --- /dev/null +++ b/src/components/Menu/utils.ts @@ -0,0 +1,89 @@ +import color from 'color'; +import type { Theme } from '../../types'; +import { white, black } from '../../styles/themes/v2/colors'; +import type { IconSource } from '../Icon'; + +export const MIN_WIDTH = 112; +export const MAX_WIDTH = 280; + +type ContentProps = { + isV3: boolean; + iconWidth: number; + leadingIcon?: IconSource; + trailingIcon?: IconSource; +}; + +type ColorProps = { + theme: Theme; + disabled?: boolean; +}; + +const getDisabledColor = (theme: Theme) => { + if (theme.isV3) { + return theme.colors.onSurfaceDisabled; + } + + return color(theme.dark ? white : black) + .alpha(0.32) + .rgb() + .string(); +}; + +const getTitleColor = ({ theme, disabled }: ColorProps) => { + if (disabled) { + return getDisabledColor(theme); + } + + if (theme.isV3) { + return theme.colors.onSurface; + } + + return color(theme.colors.text).alpha(0.87).rgb().string(); +}; + +const getIconColor = ({ theme, disabled }: ColorProps) => { + if (disabled) { + return getDisabledColor(theme); + } + + if (theme.isV3) { + return theme.colors.onSurfaceVariant; + } + + return color(theme.colors.text).alpha(0.54).rgb().string(); +}; + +export const getMenuItemColor = ({ theme, disabled }: ColorProps) => { + return { + titleColor: getTitleColor({ theme, disabled }), + iconColor: getIconColor({ theme, disabled }), + underlayColor: theme.isV3 + ? color(theme.colors.primary).alpha(0.12).rgb().string() + : undefined, + }; +}; + +export const getContentMaxWidth = ({ + isV3, + iconWidth, + leadingIcon, + trailingIcon, +}: ContentProps) => { + if (isV3) { + if (leadingIcon && trailingIcon) { + return MAX_WIDTH - (2 * iconWidth + 24); + } + + if (leadingIcon || trailingIcon) { + return MAX_WIDTH - (iconWidth + 24); + } + + return MAX_WIDTH - 12; + } + + if (leadingIcon) { + return MAX_WIDTH - (iconWidth + 48); + } + + return MAX_WIDTH - 16; +}; diff --git a/src/components/__tests__/MenuItem.test.js b/src/components/__tests__/MenuItem.test.js index 98f421eec5..73afd1422c 100644 --- a/src/components/__tests__/MenuItem.test.js +++ b/src/components/__tests__/MenuItem.test.js @@ -1,7 +1,167 @@ import * as React from 'react'; +import color from 'color'; import renderer from 'react-test-renderer'; import Menu from '../Menu/Menu.tsx'; +import { getMenuItemColor } from '../Menu/utils'; + +import MD3LightTheme from '../../styles/themes/v3/LightTheme'; +import MD2LightTheme from '../../styles/themes/v2/LightTheme'; +import MD3DarkTheme from '../../styles/themes/v3/DarkTheme'; +import MD2DarkTheme from '../../styles/themes/v2/DarkTheme'; +import { black, white } from '../../styles/themes/v2/colors'; + +const getTheme = (isDark = false, isV3 = true) => { + const theme = isDark + ? isV3 + ? MD3DarkTheme + : MD2DarkTheme + : isV3 + ? MD3LightTheme + : MD2LightTheme; + return { + ...theme, + isV3, + }; +}; + +describe('getMenuItemColor - title color', () => { + it('should return disabled color if disabled, for theme version 3', () => { + expect( + getMenuItemColor({ + theme: getTheme(), + disabled: true, + }) + ).toMatchObject({ titleColor: getTheme().colors.onSurfaceDisabled }); + }); + + it('should return disabled color if disabled, for theme version 2, light theme', () => { + expect( + getMenuItemColor({ + theme: getTheme(false, false), + disabled: true, + }) + ).toMatchObject({ + titleColor: color(black).alpha(0.32).rgb().string(), + }); + }); + + it('should return disabled color if disabled, for theme version 2, dark theme', () => { + expect( + getMenuItemColor({ + theme: getTheme(true, false), + disabled: true, + }) + ).toMatchObject({ + titleColor: color(white).alpha(0.32).rgb().string(), + }); + }); + + it('should return correct theme color, for theme version 3', () => { + expect( + getMenuItemColor({ + theme: getTheme(), + }) + ).toMatchObject({ + titleColor: getTheme().colors.onSurface, + }); + }); + + it('should return correct theme color, for theme version 2', () => { + expect( + getMenuItemColor({ + theme: getTheme(false, false), + }) + ).toMatchObject({ + titleColor: color(getTheme(false, false).colors.text) + .alpha(0.87) + .rgb() + .string(), + }); + }); +}); + +describe('getMenuItemColor - icon color', () => { + it('should return disabled color if disabled, for theme version 3', () => { + expect( + getMenuItemColor({ + theme: getTheme(), + disabled: true, + }) + ).toMatchObject({ iconColor: getTheme().colors.onSurfaceDisabled }); + }); + + it('should return disabled color if disabled, for theme version 2, light theme', () => { + expect( + getMenuItemColor({ + theme: getTheme(false, false), + disabled: true, + }) + ).toMatchObject({ + iconColor: color(black).alpha(0.32).rgb().string(), + }); + }); + + it('should return disabled color if disabled, for theme version 2, dark theme', () => { + expect( + getMenuItemColor({ + theme: getTheme(true, false), + disabled: true, + }) + ).toMatchObject({ + iconColor: color(white).alpha(0.32).rgb().string(), + }); + }); + + it('should return correct theme color, for theme version 3', () => { + expect( + getMenuItemColor({ + theme: getTheme(), + }) + ).toMatchObject({ + iconColor: getTheme().colors.onSurfaceVariant, + }); + }); + + it('should return correct theme color, for theme version 2', () => { + expect( + getMenuItemColor({ + theme: getTheme(false, false), + }) + ).toMatchObject({ + iconColor: color(getTheme(false, false).colors.text) + .alpha(0.54) + .rgb() + .string(), + }); + }); +}); + +describe('getMenuItemColor - underlay color', () => { + it('should return correct theme color, for theme version 3', () => { + expect( + getMenuItemColor({ + theme: getTheme(), + }) + ).toMatchObject({ + underlayColor: color(getTheme().colors.primary) + .alpha(0.12) + .rgb() + .string(), + }); + }); + + it('should return undefined, for theme version 2', () => { + expect( + getMenuItemColor({ + theme: getTheme(false, false), + }) + ).toMatchObject({ + underlayColor: undefined, + }); + }); +}); + it('renders menu item', () => { const tree = renderer .create(