diff --git a/example/src/Examples/ChipExample.tsx b/example/src/Examples/ChipExample.tsx index 20f0393153..d8a0c66d5e 100644 --- a/example/src/Examples/ChipExample.tsx +++ b/example/src/Examples/ChipExample.tsx @@ -249,10 +249,10 @@ const ChipExample = () => { - - - {isV3 && ( - <> + + {isV3 && ( + + {}} @@ -265,7 +265,7 @@ const ChipExample = () => { } style={[styles.chip, styles.customBorderRadius]} > - Compact with custom border radius + Compact with custom border radius (outlined) { } style={[styles.chip, styles.customBorderRadius]} > - Compact with custom border radius + Compact with custom border radius (flat) - - )} - {}} - onLongPress={() => - setSnackbarProperties({ visible: true, text: '' }) - } - style={styles.chip} - > - With onLongPress - - {}} - style={[ - styles.chip, - { - backgroundColor: color(customColor).alpha(0.2).rgb().string(), - }, - ]} - selectedColor={customColor} - > - Flat selected chip with custom color - - {}} - style={styles.chip} - selectedColor={customColor} - > - Flat unselected chip with custom color - + + + )} + + + {}} + style={[ + styles.chip, + { + backgroundColor: color(customColor) + .alpha(0.2) + .rgb() + .string(), + }, + ]} + > + Flat selected chip with custom background color + + {}} + style={[ + styles.chip, + { + backgroundColor: color(customColor) + .alpha(0.2) + .rgb() + .string(), + }, + ]} + > + Flat unselected chip with custom background color + + {}} + style={[ + styles.chip, + { + backgroundColor: color(customColor) + .alpha(0.2) + .rgb() + .string(), + }, + ]} + > + Flat disabled chip with custom background color + + + + + + {}} + style={[styles.chip]} + selectedColor={customColor} + > + Flat selected chip with custom selected color + + {}} + style={styles.chip} + selectedColor={customColor} + > + Flat unselected chip with custom selected color + + {}} + style={[styles.chip]} + selectedColor={customColor} + > + Flat selected chip with custom selected color (no check) + + {}} + style={styles.chip} + selectedColor={customColor} + > + Flat unselected chip with custom selected color (no check) + + {}} + style={styles.chip} + selectedColor={customColor} + > + Flat disabled unselected chip with custom selected color + + {}} + style={[ + styles.chip, + { + backgroundColor: color(customColor) + .alpha(0.2) + .rgb() + .string(), + }, + ]} + selectedColor={customColor} + > + Outlined selected chip with custom color + + {}} + style={styles.chip} + selectedColor={customColor} + > + Outlined unselected chip with custom color + + + + + + {}} + style={[styles.chip]} + selectedColor="rgba(255, 255, 255, 1)" + selectedBackgroundColor="rgba(0, 0, 0, 1)" + > + Flat selected chip with custom selected color & background color + + {}} + style={[styles.chip]} + selectedColor="rgba(255, 255, 255, 1)" + selectedBackgroundColor="rgba(0, 0, 0, 1)" + > + Flat disabled chip with custom selected & background color + + {}} + style={styles.chip} + textStyle={styles.tiny} + > + With custom size + + {}} + style={styles.chip} + textStyle={styles.tiny} + > + With custom text + + {}} + onClose={() => + setSnackbarProperties({ + visible: true, + text: 'Custom icon close button pressed', + }) + } + closeIcon="arrow-down" + style={styles.chip} + closeIconAccessibilityLabel="Custom Close icon accessibility label" + > + With custom close icon + + {}} + onLongPress={() => + setSnackbarProperties({ visible: true, text: '' }) + } + style={styles.chip} + > + With onLongPress + + + + + + {}} + onClose={() => + setSnackbarProperties({ + visible: true, + text: 'Close button pressed', + }) + } + style={styles.bigTextFlex} + textStyle={styles.bigTextStyle} + ellipsizeMode="middle" + > + With a very big text: React Native Paper is a high-quality, + standard-compliant Material Design library that has you covered + in all major use-cases. + + {}} - style={[ - styles.chip, - { - backgroundColor: color(customColor).alpha(0.2).rgb().string(), - }, - ]} - selectedColor={customColor} - > - Outlined selected chip with custom color - - {}} - style={styles.chip} - selectedColor={customColor} - > - Outlined unselected chip with custom color - - {}} - style={styles.chip} - textStyle={styles.tiny} - > - With custom size - - {}} - onClose={() => - setSnackbarProperties({ - visible: true, - text: 'Close button pressed', - }) - } - style={styles.bigTextFlex} - textStyle={styles.bigTextStyle} - ellipsizeMode="middle" - > - With a very big text: React Native Paper is a high-quality, - standard-compliant Material Design library that has you covered in - all major use-cases. - - {}} - onClose={() => - setSnackbarProperties({ - visible: true, - text: 'Custom icon close button pressed', - }) - } - closeIcon="arrow-down" - style={styles.chip} - closeIconAccessibilityLabel="Custom Close icon accessibility label" + style={styles.fullWidthChip} > - With custom close icon + Full width chip - {}} - style={styles.chip} - textStyle={styles.tiny} - > - With custom text - - - {}} style={styles.fullWidthChip}> - Full width chip - + , 'mode'> & { selected?: boolean; /** * Whether to style the chip color as selected. - * Note: With theme version 3 `selectedColor` doesn't apply to the `icon`. + * Note: With theme version 3 `selectedColor` doesn't apply to the `icon`. Only text color is affected. * If you want specify custom color for the `icon`, render your own `Icon` component. */ selectedColor?: string; + /** + * Background color for the selected chip. + */ + selectedBackgroundColor?: string; /** * @supported Available in v5.x with theme version 3 * Whether to display overlay on selected chip @@ -139,6 +143,29 @@ export type Props = $Omit, 'mode'> & { hitSlop?: TouchableRippleProps['hitSlop']; /** * @optional + * Theme object containing styling properties. The following theme properties are used: + * + * **V3 Theme:** + * + * - `colors.onSurfaceDisabled` - Text and icon color when disabled + * - `colors.onSurfaceVariant` - Text/icon color for outlined mode, disabled border color, overlay mixing + * - `colors.outline` - Border color for outlined mode (when not disabled/selected) + * - `colors.surface` - Background color for outlined mode + * - `colors.secondaryContainer` - Background color for flat mode + * - `colors.onSecondaryContainer` - Text/icon color for flat mode, overlay mixing + * - `colors.primary` - Icon color when not disabled (for custom icons) + * + * **V2 Theme:** + * + * - `colors.disabled` - Text and icon color when disabled + * - `colors.text` - Base text and icon color (with alpha applied) + * - `colors.surface` - Background color for outlined mode + * **Common:** + * - `roundness` - Used to calculate default border radius (V3: roundness * 2, V2: roundness * 4) + * - `animation.scale` - Used for elevation animation timing + * - `fonts.labelLarge` (V3) / `fonts.regular` (V2) - Text typography + * - `dark` - Determines color calculations for V2 theme + * - `isV3` - Determines which theme version logic to apply */ theme?: ThemeProp; /** @@ -201,6 +228,7 @@ const Chip = ({ theme: themeOverrides, testID = 'chip', selectedColor, + selectedBackgroundColor: customSelectedBackgroundColor, rippleColor: customRippleColor, showSelectedOverlay = false, showSelectedCheck = true, @@ -259,22 +287,18 @@ const Chip = ({ borderRadius = defaultBorderRadius, } = (StyleSheet.flatten(style) || {}) as ViewStyle; - const { - borderColor, - textColor, - iconColor, - rippleColor, - selectedBackgroundColor, - backgroundColor, - } = getChipColors({ - isOutlined, - theme, - selectedColor, - showSelectedOverlay, - customBackgroundColor, - disabled, - customRippleColor, - }); + const { borderColor, textColor, iconColor, rippleColor, backgroundColor } = + getChipColors({ + isOutlined, + theme, + selectedColor, + showSelectedOverlay, + customBackgroundColor, + disabled, + customRippleColor, + selected, + customSelectedBackgroundColor, + }); const accessibilityState: AccessibilityState = { selected, @@ -306,7 +330,7 @@ const Chip = ({ elevation: elevationStyle, }, { - backgroundColor: selected ? selectedBackgroundColor : backgroundColor, + backgroundColor, borderColor, borderRadius, }, diff --git a/src/components/Chip/helpers.tsx b/src/components/Chip/helpers.tsx index 47d0993c86..879d8ebc7c 100644 --- a/src/components/Chip/helpers.tsx +++ b/src/components/Chip/helpers.tsx @@ -13,6 +13,7 @@ type BaseProps = { theme: InternalTheme; isOutlined: boolean; disabled?: boolean; + selected?: boolean; }; const getBorderColor = ({ @@ -21,6 +22,7 @@ const getBorderColor = ({ disabled, selectedColor, backgroundColor, + selected, }: BaseProps & { backgroundColor: string; selectedColor?: string }) => { const isSelectedColor = selectedColor !== undefined; @@ -34,7 +36,7 @@ const getBorderColor = ({ return color(theme.colors.onSurfaceVariant).alpha(0.12).rgb().string(); } - if (isSelectedColor) { + if (isSelectedColor && selected) { return color(selectedColor).alpha(0.29).rgb().string(); } @@ -42,7 +44,7 @@ const getBorderColor = ({ } if (isOutlined) { - if (isSelectedColor) { + if (isSelectedColor && selected) { return color(selectedColor).alpha(0.29).rgb().string(); } @@ -61,6 +63,7 @@ const getTextColor = ({ isOutlined, disabled, selectedColor, + selected, }: BaseProps & { selectedColor?: string; }) => { @@ -70,7 +73,7 @@ const getTextColor = ({ return theme.colors.onSurfaceDisabled; } - if (isSelectedColor) { + if (isSelectedColor && selected) { return selectedColor; } @@ -85,7 +88,7 @@ const getTextColor = ({ return theme.colors.disabled; } - if (isSelectedColor) { + if (isSelectedColor && selected) { return color(selectedColor).alpha(0.87).rgb().string(); } @@ -119,6 +122,7 @@ const getBackgroundColor = ({ theme, isOutlined, disabled, + selected, customBackgroundColor, }: BaseProps & { customBackgroundColor?: ColorValue; @@ -136,26 +140,37 @@ const getBackgroundColor = ({ } } - return getDefaultBackgroundColor({ theme, isOutlined }); + return getDefaultBackgroundColor({ theme, isOutlined, selected }); }; const getSelectedBackgroundColor = ({ theme, isOutlined, disabled, + selected, customBackgroundColor, showSelectedOverlay, + customSelectedBackgroundColor, }: BaseProps & { customBackgroundColor?: ColorValue; showSelectedOverlay?: boolean; + customSelectedBackgroundColor?: ColorValue; }) => { + // If customSelectedBackgroundColor is provided, use it as-is without processing + if (typeof customSelectedBackgroundColor === 'string') { + return customSelectedBackgroundColor; + } + + // Get the base background color (which may be custom or theme-based) const backgroundColor = getBackgroundColor({ theme, disabled, isOutlined, customBackgroundColor, + selected, }); + // Process the background color to get the selected background color if (theme.isV3) { if (isOutlined) { if (showSelectedOverlay) { @@ -202,6 +217,7 @@ const getIconColor = ({ isOutlined, disabled, selectedColor, + selected, }: BaseProps & { selectedColor?: string; }) => { @@ -211,7 +227,7 @@ const getIconColor = ({ return theme.colors.onSurfaceDisabled; } - if (isSelectedColor) { + if (isSelectedColor && selected) { return selectedColor; } @@ -226,7 +242,7 @@ const getIconColor = ({ return theme.colors.disabled; } - if (isSelectedColor) { + if (isSelectedColor && selected) { return color(selectedColor).alpha(0.54).rgb().string(); } @@ -237,6 +253,7 @@ const getRippleColor = ({ theme, isOutlined, disabled, + selected, selectedColor, selectedBackgroundColor, customRippleColor, @@ -255,17 +272,18 @@ const getRippleColor = ({ disabled, selectedColor, isOutlined, + selected, }); if (theme.isV3) { - if (isSelectedColor) { + if (isSelectedColor && selected) { return color(selectedColor).alpha(0.12).rgb().string(); } return color(textColor).alpha(0.12).rgb().string(); } - if (isSelectedColor) { + if (isSelectedColor && selected) { return color(selectedColor).fade(0.5).rgb().string(); } @@ -280,14 +298,18 @@ export const getChipColors = ({ customBackgroundColor, disabled, customRippleColor, + selected = false, + customSelectedBackgroundColor, }: BaseProps & { customBackgroundColor?: ColorValue; disabled?: boolean; showSelectedOverlay?: boolean; selectedColor?: string; customRippleColor?: ColorValue; + selected?: boolean; + customSelectedBackgroundColor?: ColorValue; }) => { - const baseChipColorProps = { theme, isOutlined, disabled }; + const baseChipColorProps = { theme, isOutlined, disabled, selected }; const backgroundColor = getBackgroundColor({ ...baseChipColorProps, @@ -298,6 +320,7 @@ export const getChipColors = ({ ...baseChipColorProps, customBackgroundColor, showSelectedOverlay, + customSelectedBackgroundColor, }); return { @@ -320,7 +343,6 @@ export const getChipColors = ({ selectedBackgroundColor, customRippleColor, }), - backgroundColor, - selectedBackgroundColor, + backgroundColor: selected ? selectedBackgroundColor : backgroundColor, }; }; diff --git a/src/components/__tests__/Chip.test.tsx b/src/components/__tests__/Chip.test.tsx index ca3055e35b..5000fb5cc3 100644 --- a/src/components/__tests__/Chip.test.tsx +++ b/src/components/__tests__/Chip.test.tsx @@ -152,29 +152,57 @@ describe('getChipColors - text color', () => { }); }); - it('should return custom color, for theme version 3', () => { + it('should return custom color, for theme version 3, when selected', () => { expect( getChipColors({ theme: getTheme(), selectedColor: 'purple', isOutlined: false, + selected: true, }) ).toMatchObject({ textColor: 'purple', }); }); - it('should return custom color, for theme version 2', () => { + it('should not return custom color, for theme version 3, when unselected', () => { + expect( + getChipColors({ + theme: getTheme(), + selectedColor: 'purple', + isOutlined: false, + selected: false, + }) + ).not.toMatchObject({ + textColor: 'purple', + }); + }); + + it('should return custom color, for theme version 2, when selected', () => { expect( getChipColors({ theme: getTheme(false, false), selectedColor: 'purple', isOutlined: false, + selected: true, }) ).toMatchObject({ textColor: color('purple').alpha(0.87).rgb().string(), }); }); + + it('should not return custom color, for theme version 2, when unselected', () => { + expect( + getChipColors({ + theme: getTheme(false, false), + selectedColor: 'purple', + isOutlined: false, + selected: false, + }) + ).not.toMatchObject({ + textColor: color('purple').alpha(0.87).rgb().string(), + }); + }); }); describe('getChipColors - icon color', () => { @@ -244,6 +272,7 @@ describe('getChipColors - icon color', () => { theme: getTheme(), selectedColor: 'purple', isOutlined: false, + selected: true, }) ).toMatchObject({ iconColor: 'purple', @@ -256,6 +285,7 @@ describe('getChipColors - icon color', () => { theme: getTheme(false, false), selectedColor: 'purple', isOutlined: false, + selected: true, }) ).toMatchObject({ iconColor: color('purple').alpha(0.54).rgb().string(), @@ -298,6 +328,7 @@ describe('getChipColors - ripple color', () => { theme: getTheme(), selectedColor: 'purple', isOutlined: false, + selected: true, }) ).toMatchObject({ rippleColor: color('purple').alpha(0.12).rgb().string(), @@ -310,6 +341,7 @@ describe('getChipColors - ripple color', () => { theme: getTheme(false, false), selectedColor: 'purple', isOutlined: false, + selected: true, }) ).toMatchObject({ rippleColor: color('purple').fade(0.5).rgb().string(), @@ -422,9 +454,10 @@ describe('getChipColor - selected background color', () => { theme: getTheme(), customBackgroundColor: 'purple', isOutlined: true, + selected: true, }) ).toMatchObject({ - selectedBackgroundColor: color('purple') + backgroundColor: color('purple') .mix(color(getTheme().colors.onSurfaceVariant), 0) .rgb() .string(), @@ -437,9 +470,10 @@ describe('getChipColor - selected background color', () => { theme: getTheme(), customBackgroundColor: 'purple', isOutlined: false, + selected: true, }) ).toMatchObject({ - selectedBackgroundColor: color('purple') + backgroundColor: color('purple') .mix(color(getTheme().colors.onSecondaryContainer), 0) .rgb() .string(), @@ -453,9 +487,10 @@ describe('getChipColor - selected background color', () => { customBackgroundColor: 'purple', showSelectedOverlay: true, isOutlined: true, + selected: true, }) ).toMatchObject({ - selectedBackgroundColor: color('purple') + backgroundColor: color('purple') .mix(color(getTheme().colors.onSurfaceVariant), 0.12) .rgb() .string(), @@ -469,9 +504,10 @@ describe('getChipColor - selected background color', () => { customBackgroundColor: 'purple', showSelectedOverlay: true, isOutlined: false, + selected: true, }) ).toMatchObject({ - selectedBackgroundColor: color('purple') + backgroundColor: color('purple') .mix(color(getTheme().colors.onSecondaryContainer), 0.12) .rgb() .string(), @@ -483,9 +519,10 @@ describe('getChipColor - selected background color', () => { getChipColors({ theme: getTheme(), isOutlined: true, + selected: true, }) ).toMatchObject({ - selectedBackgroundColor: color(getTheme().colors.surface) + backgroundColor: color(getTheme().colors.surface) .mix(color(getTheme().colors.onSurfaceVariant), 0) .rgb() .string(), @@ -497,9 +534,10 @@ describe('getChipColor - selected background color', () => { getChipColors({ theme: getTheme(), isOutlined: false, + selected: true, }) ).toMatchObject({ - selectedBackgroundColor: color(getTheme().colors.secondaryContainer) + backgroundColor: color(getTheme().colors.secondaryContainer) .mix(color(getTheme().colors.onSecondaryContainer), 0) .rgb() .string(), @@ -512,9 +550,10 @@ describe('getChipColor - selected background color', () => { theme: getTheme(false, false), customBackgroundColor: 'purple', isOutlined: true, + selected: true, }) ).toMatchObject({ - selectedBackgroundColor: color('purple').darken(0.08).rgb().string(), + backgroundColor: color('purple').darken(0.08).rgb().string(), }); }); @@ -524,9 +563,10 @@ describe('getChipColor - selected background color', () => { theme: getTheme(false, false), customBackgroundColor: 'purple', isOutlined: false, + selected: true, }) ).toMatchObject({ - selectedBackgroundColor: color('purple').darken(0.2).rgb().string(), + backgroundColor: color('purple').darken(0.2).rgb().string(), }); }); @@ -536,9 +576,10 @@ describe('getChipColor - selected background color', () => { theme: getTheme(true, false), customBackgroundColor: 'purple', isOutlined: true, + selected: true, }) ).toMatchObject({ - selectedBackgroundColor: color('purple').lighten(0.2).rgb().string(), + backgroundColor: color('purple').lighten(0.2).rgb().string(), }); }); @@ -548,9 +589,10 @@ describe('getChipColor - selected background color', () => { theme: getTheme(true, false), customBackgroundColor: 'purple', isOutlined: false, + selected: true, }) ).toMatchObject({ - selectedBackgroundColor: color('purple').lighten(0.4).rgb().string(), + backgroundColor: color('purple').lighten(0.4).rgb().string(), }); }); @@ -559,9 +601,10 @@ describe('getChipColor - selected background color', () => { getChipColors({ theme: getTheme(false, false), isOutlined: true, + selected: true, }) ).toMatchObject({ - selectedBackgroundColor: color(getTheme(false, false).colors.surface) + backgroundColor: color(getTheme(false, false).colors.surface) .darken(0.08) .rgb() .string(), @@ -573,9 +616,10 @@ describe('getChipColor - selected background color', () => { getChipColors({ theme: getTheme(false, false), isOutlined: false, + selected: true, }) ).toMatchObject({ - selectedBackgroundColor: color('#ebebeb').darken(0.2).rgb().string(), + backgroundColor: color('#ebebeb').darken(0.2).rgb().string(), }); }); @@ -584,9 +628,36 @@ describe('getChipColor - selected background color', () => { getChipColors({ theme: getTheme(true, false), isOutlined: false, + selected: true, + }) + ).toMatchObject({ + backgroundColor: color('#383838').lighten(0.4).rgb().string(), + }); + }); + + it('should return custom selectedBackgroundColor when provided & chip is selected', () => { + expect( + getChipColors({ + theme: getTheme(), + customSelectedBackgroundColor: 'red', + isOutlined: false, + selected: true, + }) + ).toMatchObject({ + backgroundColor: 'red', + }); + }); + + it('should return custom selectedBackgroundColor for version 2 theme, when chip is selected', () => { + expect( + getChipColors({ + theme: getTheme(false, false), + customSelectedBackgroundColor: 'red', + isOutlined: false, + selected: true, }) ).toMatchObject({ - selectedBackgroundColor: color('#383838').lighten(0.4).rgb().string(), + backgroundColor: 'red', }); }); }); @@ -673,15 +744,31 @@ describe('getChipColor - border color', () => { }); }); - it('should return custom color, for theme version 3', () => { + it('should return custom color, for theme version 3, when selected', () => { expect( getChipColors({ theme: getTheme(), selectedColor: 'purple', isOutlined: false, + selected: true, + }) + ).toMatchObject({ + borderColor: 'transparent', + textColor: 'purple', + }); + }); + + it('should return theme color, for theme version 3, when unselected', () => { + expect( + getChipColors({ + theme: getTheme(false, true), + selectedColor: 'purple', + isOutlined: false, + selected: false, }) ).toMatchObject({ borderColor: 'transparent', + textColor: getTheme(false, true).colors.onSecondaryContainer, }); }); @@ -702,6 +789,7 @@ describe('getChipColor - border color', () => { theme: getTheme(false, false), selectedColor: 'purple', isOutlined: true, + selected: true, }) ).toMatchObject({ borderColor: color('purple').alpha(0.29).rgb().string(),