diff --git a/example/src/Examples/CardExample.tsx b/example/src/Examples/CardExample.tsx
index ad3451e8b0..6fb043d455 100644
--- a/example/src/Examples/CardExample.tsx
+++ b/example/src/Examples/CardExample.tsx
@@ -31,13 +31,12 @@ const CardExample = () => {
{modes.map((mode) => (
setSelectedMode(mode)}
style={styles.chip}
- >
- {mode}
-
+ />
))}
{
+ const [selectedFilter, setSelectedFilter] = React.useState(filters[0]);
const [snackbarProperties, setSnackbarProperties] = React.useState({
visible: false,
text: '',
@@ -16,168 +19,65 @@ const ChipExample = () => {
return (
<>
-
+
- {}} style={styles.chip}>
- Simple
-
- (
+ setSelectedFilter(filter)}
+ style={styles.chip}
+ />
+ ))}
+ {}}
- style={styles.chip}
- >
- With selected overlay
-
- {}} style={styles.chip}>
- Elevated
-
- {}}>
- Compact chip
-
- {}}
- onClose={() =>
- setSnackbarProperties({
- visible: true,
- text: 'Close button pressed',
- })
- }
style={styles.chip}
- closeIconAccessibilityLabel="Close icon accessibility label"
- >
- Close button
-
- {}}
- onClose={() =>
- setSnackbarProperties({
- visible: true,
- text: 'Heart icon close button pressed',
- })
- }
- style={styles.chip}
- >
- Icon
-
-
- }
- onPress={() => {}}
- onClose={() =>
- setSnackbarProperties({
- visible: true,
- text: 'Avatar close button pressed',
- })
- }
- style={styles.chip}
- >
- Avatar
-
+ />
- }
+ showSelectedCheck={false}
onPress={() => {}}
style={styles.chip}
- >
- Avatar (selected)
-
-
- setSnackbarProperties({
- visible: true,
- text: 'Disabled heart icon close button pressed',
- })
- }
- style={styles.chip}
- >
- Icon (disabled)
-
-
- }
- style={styles.chip}
- >
- Avatar (disabled)
-
+ />
+
-
+
+
- {}} style={styles.chip}>
- Simple
-
- {}}
- style={styles.chip}
- >
- With selected overlay
-
- {}}
- style={styles.chip}
- >
- Elevated
-
{}}
style={styles.chip}
- >
- Compact chip
-
+ />
{}}
- onClose={() =>
- setSnackbarProperties({
- visible: true,
- text: 'Close button pressed',
- })
- }
style={styles.chip}
- >
- Close button
-
+ />
{}}
- onClose={() =>
- setSnackbarProperties({
- visible: true,
- text: 'Heart icon close button pressed',
- })
- }
style={styles.chip}
- >
- Icon
-
+ />
+
+
+
+
+
{
}
onPress={() => {}}
style={styles.chip}
- >
- Avatar
-
+ />
{
}
onPress={() => {}}
style={styles.chip}
- >
- Avatar (selected)
-
+ />
{}}
onClose={() =>
setSnackbarProperties({
visible: true,
- text: 'Disabled close button pressed',
+ text: 'Close button pressed',
})
}
style={styles.chip}
- >
- Icon (disabled)
-
+ />
+ label="Custom close"
+ closeIcon="arrow-down"
+ onPress={() => {}}
+ onClose={() =>
+ setSnackbarProperties({
+ visible: true,
+ text: 'Custom close button pressed',
+ })
}
style={styles.chip}
- >
- Avatar (disabled)
-
+ closeIconAccessibilityLabel="Custom close icon accessibility label"
+ />
-
+
+
{}}
- compact
- avatar={
-
- }
- style={[styles.chip, styles.customBorderRadius]}
- >
- Compact with custom border radius
-
- {}}
- compact
- avatar={
-
- }
- style={[styles.chip, styles.customBorderRadius]}
- >
- Compact with custom border radius
-
- {}}
- 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(),
},
]}
- 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"
- >
- With custom close icon
-
+ style={[styles.chip, styles.customBorderRadius]}
+ />
{}}
- style={styles.chip}
- textStyle={styles.tiny}
- >
- With custom text
-
+ style={styles.fullWidthChip}
+ />
- {}} style={styles.fullWidthChip}>
- Full width chip
-
({
{options.map((option) => (
onChange(option)}
- >
- {option}
-
+ />
))}
diff --git a/example/src/Examples/ListSectionExample.tsx b/example/src/Examples/ListSectionExample.tsx
index d7efbe6adc..b0d008f5f0 100644
--- a/example/src/Examples/ListSectionExample.tsx
+++ b/example/src/Examples/ListSectionExample.tsx
@@ -117,9 +117,7 @@ const ListSectionExample = () => {
- {}}>
- DOCS.pdf
-
+ {}} />
)}
diff --git a/example/src/Examples/TeamDetails.tsx b/example/src/Examples/TeamDetails.tsx
index 5970274f31..fbd7086ef3 100644
--- a/example/src/Examples/TeamDetails.tsx
+++ b/example/src/Examples/TeamDetails.tsx
@@ -58,25 +58,15 @@ const News = () => {
contentContainerStyle={styles.chipsContent}
>
{}}
style={styles.chip}
- showSelectedOverlay
- >
- Latest
-
- {}} style={styles.chip}>
- Popular
-
- {}} style={styles.chip}>
- Interviews
-
- {}} style={styles.chip}>
- Transfers
-
- {}} style={styles.chip}>
- League
-
+ />
+ {}} style={styles.chip} />
+ {}} style={styles.chip} />
+ {}} style={styles.chip} />
+ {}} style={styles.chip} />
diff --git a/example/src/Examples/TooltipExample.tsx b/example/src/Examples/TooltipExample.tsx
index 8e0802d4a4..483433331a 100644
--- a/example/src/Examples/TooltipExample.tsx
+++ b/example/src/Examples/TooltipExample.tsx
@@ -120,6 +120,7 @@ const TooltipExample = () => {
{
accessibilityIgnoresInvertColors
/>
}
- >
- John Doe
-
+ />
diff --git a/src/components/Chip/Chip.tsx b/src/components/Chip/Chip.tsx
index c59d02fa6e..d2f9d0a8fb 100644
--- a/src/components/Chip/Chip.tsx
+++ b/src/components/Chip/Chip.tsx
@@ -10,39 +10,44 @@ import type {
ViewStyle,
} from 'react-native';
-import useLatestCallback from 'use-latest-callback';
-
import { getChipColors } from './helpers';
import type { ChipAvatarProps } from './helpers';
+import { ChipTokens } from './tokens';
import { useInternalTheme } from '../../core/theming';
-import { white } from '../../theme/colors';
-import type { $Omit, EllipsizeProp, Theme, ThemeProp } from '../../types';
+import type { EllipsizeProp, ThemeProp } from '../../types';
import hasTouchHandler from '../../utils/hasTouchHandler';
import type { IconSource } from '../Icon';
import Icon from '../Icon';
-import MaterialCommunityIcon from '../MaterialCommunityIcon';
import Surface from '../Surface';
import TouchableRipple from '../TouchableRipple/TouchableRipple';
import type { Props as TouchableRippleProps } from '../TouchableRipple/TouchableRipple';
import Text from '../Typography/Text';
-export type Props = $Omit, 'mode'> & {
+export type Props = Omit<
+ React.ComponentProps,
+ 'children' | 'mode'
+> & {
/**
* Mode of the chip.
- * - `flat` - flat chip without outline.
- * - `outlined` - chip with an outline.
+ * - `flat` - chip with a filled container.
+ * - `outlined` - chip with an outline when unselected.
*/
mode?: 'flat' | 'outlined';
/**
- * Text content of the `Chip`.
+ * Text label of the `Chip`.
+ */
+ label?: string;
+ /**
+ * @deprecated Use `label` instead. Children are kept as a compatibility
+ * fallback and should be plain text.
*/
- children: React.ReactNode;
+ children?: React.ReactNode;
/**
- * Icon to display for the `Chip`. Both icon and avatar cannot be specified.
+ * Leading icon to display for the `Chip`. Both icon and avatar cannot be specified.
*/
icon?: IconSource;
/**
- * Avatar to display for the `Chip`. Both icon and avatar cannot be specified.
+ * Leading avatar to display for the `Chip`. Both icon and avatar cannot be specified.
*/
avatar?: React.ReactNode;
/**
@@ -55,13 +60,12 @@ export type Props = $Omit, 'mode'> & {
selected?: boolean;
/**
* Whether to style the chip color as selected.
- * Note: With theme version 3 `selectedColor` doesn't apply to the `icon`.
- * If you want specify custom color for the `icon`, render your own `Icon` component.
+ * Applies to label, leading icon, trailing icon, and custom outlined border.
*/
selectedColor?: ColorValue;
/**
- * @supported Available in v5.x with theme version 3
- * Whether to display overlay on selected chip
+ * @deprecated Selected chips use the MD3 selected container and state layer.
+ * This prop is kept as a compatibility overlay.
*/
showSelectedOverlay?: boolean;
/**
@@ -74,7 +78,7 @@ export type Props = $Omit, 'mode'> & {
*/
disabled?: boolean;
/**
- * Type of background drawabale to display the feedback (Android).
+ * Type of background drawable to display the feedback (Android).
* https://reactnative.dev/docs/pressable#rippleconfig
*/
background?: PressableAndroidRippleConfig;
@@ -111,17 +115,16 @@ export type Props = $Omit, 'mode'> & {
*/
delayLongPress?: number;
/**
- * @supported Available in v5.x with theme version 3
- * Sets smaller horizontal paddings `12dp` around label, when there is only label.
+ * @deprecated MD3 chips have a fixed density. This only reduces label-only
+ * horizontal padding for compatibility.
*/
compact?: boolean;
/**
- * @supported Available in v5.x with theme version 3
* Whether chip should have the elevation.
*/
elevated?: boolean;
/**
- * Style of chip's text
+ * Style of chip's text.
*/
textStyle?: StyleProp;
style?: Animated.WithAnimatedValue>;
@@ -138,7 +141,7 @@ export type Props = $Omit, 'mode'> & {
*/
testID?: string;
/**
- * Ellipsize Mode for the children text
+ * Ellipsize Mode for the label text.
*/
ellipsizeMode?: EllipsizeProp;
/**
@@ -164,14 +167,15 @@ export type Props = $Omit, 'mode'> & {
* import { Chip } from 'react-native-paper';
*
* const MyComponent = () => (
- * console.log('Pressed')}>Example Chip
+ * console.log('Pressed')} />
* );
*
* export default MyComponent;
* ```
*/
const Chip = ({
- mode = 'flat',
+ mode = 'outlined',
+ label,
children,
icon,
avatar,
@@ -193,6 +197,7 @@ const Chip = ({
theme: themeOverrides,
testID = 'chip',
selectedColor,
+ showSelectedOverlay = false,
showSelectedCheck = true,
ellipsizeMode,
compact,
@@ -202,11 +207,8 @@ const Chip = ({
...rest
}: Props) => {
const theme = useInternalTheme(themeOverrides);
- const isWeb = Platform.OS === 'web';
-
- const { current: elevation } = React.useRef(
- new Animated.Value(elevated ? 1 : 0)
- );
+ const isOutlined = mode === 'outlined';
+ const labelContent = label ?? children;
const hasPassedTouchHandler = hasTouchHandler({
onPress,
@@ -214,35 +216,9 @@ const Chip = ({
onPressIn,
onPressOut,
});
+ const isTouchableDisabled = disabled || !hasPassedTouchHandler;
- const isOutlined = mode === 'outlined';
-
- const handlePressIn = useLatestCallback((e: GestureResponderEvent) => {
- const { scale } = theme.animation;
- onPressIn?.(e);
- Animated.timing(elevation, {
- toValue: elevated ? 2 : 0,
- duration: 200 * scale,
- useNativeDriver:
- isWeb || Platform.constants.reactNativeVersion.minor <= 72,
- }).start();
- });
-
- const handlePressOut = useLatestCallback((e: GestureResponderEvent) => {
- const { scale } = theme.animation;
- onPressOut?.(e);
- Animated.timing(elevation, {
- toValue: elevated ? 1 : 0,
- duration: 150 * scale,
- useNativeDriver:
- isWeb || Platform.constants.reactNativeVersion.minor <= 72,
- }).start();
- });
-
- const opacity = 0.38;
const defaultBorderRadius = theme.shapes.corner.small;
- const iconSize = 18;
-
const {
backgroundColor: customBackgroundColor,
borderRadius = defaultBorderRadius,
@@ -252,43 +228,61 @@ const Chip = ({
borderColor,
textColor,
iconColor,
+ closeIconColor,
contentOpacity,
selectedBackgroundColor,
backgroundColor,
+ rippleColor,
+ avatarOverlayColor,
} = getChipColors({
isOutlined,
+ selected,
+ elevated,
theme,
selectedColor,
customBackgroundColor,
disabled,
});
+ const hasAvatar = !!avatar && !icon;
+ const showSelectedIcon = selected && showSelectedCheck && !icon;
+ const showLeadingIcon = !!icon || showSelectedIcon;
+ const hasLeading = hasAvatar || showLeadingIcon;
+ const hasClose = !!onClose;
+ const labelOnly = !hasLeading && !hasClose;
+
+ const leftPadding = hasAvatar
+ ? ChipTokens.avatarLeadingPadding
+ : hasLeading
+ ? ChipTokens.iconLeadingPadding
+ : compact
+ ? ChipTokens.compactPadding
+ : ChipTokens.leadingPadding;
+ const rightPadding = hasClose
+ ? ChipTokens.closeTrailingPadding
+ : compact && labelOnly
+ ? ChipTokens.compactPadding
+ : ChipTokens.trailingPadding;
+ const touchTargetInset =
+ (ChipTokens.minimumTouchTarget - ChipTokens.containerHeight) / 2;
+ const touchTargetHitSlop = {
+ top: touchTargetInset,
+ bottom: touchTargetInset,
+ };
+ const closeAndroidRipple =
+ Platform.OS === 'android'
+ ? { color: rippleColor, borderless: false }
+ : undefined;
+
const accessibilityState: AccessibilityState = {
selected,
- disabled,
+ disabled: isTouchableDisabled,
};
- const elevationStyle = elevation;
- const multiplier = compact ? 1.5 : 2;
- const labelSpacings = {
- marginRight: onClose ? 0 : 8 * multiplier,
- marginLeft:
- avatar || icon || (selected && showSelectedCheck)
- ? 4 * multiplier
- : 8 * multiplier,
- };
- const contentSpacings = {
- paddingRight: onClose ? 34 : 0,
- };
- const labelTextStyle = {
- color: textColor,
- ...(theme as Theme).fonts.labelLarge,
- };
return (
- {avatar && !icon ? (
+ {showSelectedOverlay && selected ? (
+ pointerEvents="none"
+ style={[styles.selectedOverlay, { backgroundColor: rippleColor }]}
+ />
+ ) : null}
+ {hasAvatar ? (
+
{React.isValidElement(avatar)
? React.cloneElement(avatar, {
style: [styles.avatar, avatar.props.style],
})
: avatar}
+ {showSelectedIcon ? (
+
+
+
+ ) : null}
) : null}
- {icon || (selected && showSelectedCheck) ? (
-
- {icon ? (
-
- ) : (
-
- )}
+ {showLeadingIcon && !hasAvatar ? (
+
+
) : null}
- {children}
+ {labelContent}
- {onClose ? (
-
-
-
- {closeIcon ? (
-
- ) : (
-
- )}
-
-
-
+ {hasClose ? (
+ [
+ styles.closeButton,
+ Platform.OS === 'web' && pressed
+ ? { backgroundColor: rippleColor }
+ : null,
+ ]}
+ >
+
+
) : null}
);
@@ -425,72 +409,64 @@ const Chip = ({
const styles = StyleSheet.create({
container: {
- borderWidth: StyleSheet.hairlineWidth,
+ height: ChipTokens.containerHeight,
+ borderWidth: ChipTokens.outlineWidth,
borderStyle: 'solid',
- flexDirection: Platform.select({ default: 'column', web: 'row' }),
+ flexDirection: 'row',
+ alignItems: 'center',
+ alignSelf: 'flex-start',
+ overflow: 'hidden',
},
- md3Container: {
- borderWidth: 1,
+ touchable: {
+ height: '100%',
+ flexShrink: 1,
},
content: {
+ height: '100%',
flexDirection: 'row',
alignItems: 'center',
- paddingLeft: 4,
position: 'relative',
+ overflow: 'hidden',
},
- md3Content: {
- paddingLeft: 0,
- },
- icon: {
- padding: 4,
- alignSelf: 'center',
- },
- md3Icon: {
- paddingLeft: 8,
- paddingRight: 0,
- },
- closeIcon: {
- marginRight: 4,
- },
- md3CloseIcon: {
- marginRight: 8,
- padding: 0,
+ selectedOverlay: {
+ ...StyleSheet.absoluteFill,
},
- md3LabelText: {
- textAlignVertical: 'center',
- marginVertical: 6,
+ avatarWrapper: {
+ width: ChipTokens.avatarSize,
+ height: ChipTokens.avatarSize,
+ borderRadius: ChipTokens.avatarSize / 2,
+ marginRight: ChipTokens.leadingLabelGap,
+ overflow: 'hidden',
},
avatar: {
- width: 24,
- height: 24,
- borderRadius: 12,
- },
- avatarWrapper: {
- marginRight: 4,
+ width: ChipTokens.avatarSize,
+ height: ChipTokens.avatarSize,
+ borderRadius: ChipTokens.avatarSize / 2,
},
- md3AvatarWrapper: {
- marginLeft: 4,
- marginRight: 0,
+ avatarSelectedOverlay: {
+ ...StyleSheet.absoluteFill,
+ alignItems: 'center',
+ justifyContent: 'center',
},
- md3SelectedIcon: {
- paddingLeft: 4,
+ leadingIcon: {
+ width: ChipTokens.leadingIconSize,
+ height: ChipTokens.leadingIconSize,
+ marginRight: ChipTokens.leadingLabelGap,
+ alignItems: 'center',
+ justifyContent: 'center',
},
- // eslint-disable-next-line react-native/no-color-literals
- avatarSelected: {
- position: 'absolute',
- top: 4,
- left: 4,
- backgroundColor: 'rgba(0, 0, 0, .29)',
+ labelText: {
+ textAlignVertical: 'center',
+ includeFontPadding: false,
},
- closeButtonStyle: {
- position: 'absolute',
- right: 0,
+ closeButton: {
+ width: ChipTokens.trailingIconTouchTarget,
height: '100%',
- justifyContent: 'center',
alignItems: 'center',
+ justifyContent: 'center',
},
- touchable: {
- width: '100%',
+ disabled: {
+ opacity: ChipTokens.disabledContent,
},
});
diff --git a/src/components/Chip/helpers.tsx b/src/components/Chip/helpers.tsx
index 4b0fdf9e06..ccc3d05673 100644
--- a/src/components/Chip/helpers.tsx
+++ b/src/components/Chip/helpers.tsx
@@ -2,10 +2,9 @@ import type { ColorValue, StyleProp, ViewStyle } from 'react-native';
import color from 'color';
+import { ChipTokens } from './tokens';
import { tokens } from '../../theme/tokens';
-import type { InternalTheme, Theme } from '../../types';
-
-const md3 = (theme: InternalTheme) => theme as Theme;
+import type { InternalTheme } from '../../types';
const stateOpacity = tokens.md.sys.state.opacity;
@@ -16,182 +15,227 @@ export type ChipAvatarProps = {
type BaseProps = {
theme: InternalTheme;
isOutlined: boolean;
+ selected?: boolean;
disabled?: boolean;
+ elevated?: boolean;
};
-const getBorderColor = ({
- theme,
- isOutlined,
- disabled,
- selectedColor,
-}: BaseProps & { backgroundColor: ColorValue; selectedColor?: ColorValue }) => {
- const isSelectedColor = selectedColor !== undefined;
- const { colors } = md3(theme);
-
- if (!isOutlined) {
- // If the Chip mode is "flat", set border color to transparent
- return 'transparent';
+const withAlpha = (value: ColorValue, alpha: number) => {
+ if (typeof value === 'string') {
+ return color(value).alpha(alpha).rgb().string();
}
- if (disabled) {
- return colors.surfaceContainer;
- }
+ return value;
+};
- if (isSelectedColor) {
- if (typeof selectedColor === 'string') {
- return color(selectedColor).alpha(0.29).rgb().string();
- }
- // PlatformColor / OpaqueColorValue: skip the alpha pass and render opaque.
- return selectedColor;
- }
+const withStringAlpha = (value: ColorValue, alpha: number) => {
+ const alphaColor = withAlpha(value, alpha);
- return colors.outlineVariant;
+ return typeof alphaColor === 'string' ? alphaColor : undefined;
};
-const getTextColor = ({
+const getContainerColor = ({
theme,
isOutlined,
+ selected,
disabled,
- selectedColor,
+ elevated,
+ customBackgroundColor,
}: BaseProps & {
- selectedColor?: ColorValue;
+ customBackgroundColor?: ColorValue;
}) => {
- const isSelectedColor = selectedColor !== undefined;
- const { colors } = md3(theme);
if (disabled) {
- return colors.onSurface;
+ return isOutlined
+ ? 'transparent'
+ : withAlpha(
+ theme.colors[ChipTokens.disabledColor],
+ ChipTokens.disabledContainer
+ );
}
- if (isSelectedColor) {
- return selectedColor;
+ if (customBackgroundColor !== undefined) {
+ return customBackgroundColor;
+ }
+
+ if (selected) {
+ return theme.colors[ChipTokens.selectedContainerColor];
}
if (isOutlined) {
- return colors.onSurfaceVariant;
+ return theme.colors[ChipTokens.outlinedContainerColor];
}
- return colors.onSecondaryContainer;
+ return elevated
+ ? theme.colors[ChipTokens.elevatedContainerColor]
+ : theme.colors[ChipTokens.flatContainerColor];
};
-const getDefaultBackgroundColor = ({
+const getBorderColor = ({
theme,
isOutlined,
-}: Omit) => {
- const { colors } = md3(theme);
- if (isOutlined) {
- return colors.surface;
+ selected,
+ disabled,
+ selectedColor,
+}: BaseProps & {
+ selectedColor?: ColorValue;
+}) => {
+ if (!isOutlined || selected) {
+ return 'transparent';
+ }
+
+ if (disabled) {
+ return withAlpha(
+ theme.colors[ChipTokens.disabledColor],
+ ChipTokens.disabledOutline
+ );
}
- return colors.secondaryContainer;
+ if (selectedColor !== undefined) {
+ return withAlpha(selectedColor, 0.29);
+ }
+
+ return theme.colors[ChipTokens.outlineColor];
};
-const getBackgroundColor = ({
+const getLabelColor = ({
theme,
- isOutlined,
+ selected,
disabled,
- customBackgroundColor,
+ selectedColor,
}: BaseProps & {
- customBackgroundColor?: ColorValue;
+ selectedColor?: ColorValue;
}) => {
- const { colors } = md3(theme);
- if (typeof customBackgroundColor === 'string') {
- return customBackgroundColor;
+ if (disabled) {
+ return theme.colors[ChipTokens.disabledColor];
}
- if (disabled) {
- if (isOutlined) {
- return 'transparent';
- }
- return colors.surfaceContainerLow;
+ if (selectedColor !== undefined) {
+ return selectedColor;
+ }
+
+ if (selected) {
+ return theme.colors[ChipTokens.selectedLabelColor];
}
- return getDefaultBackgroundColor({ theme, isOutlined });
+ return theme.colors[ChipTokens.labelColor];
};
-const getSelectedBackgroundColor = ({
+const getLeadingIconColor = ({
theme,
- isOutlined,
+ selected,
disabled,
- customBackgroundColor,
+ selectedColor,
}: BaseProps & {
- customBackgroundColor?: ColorValue;
+ selectedColor?: ColorValue;
}) => {
- return getBackgroundColor({
- theme,
- disabled,
- isOutlined,
- customBackgroundColor,
- });
+ if (disabled) {
+ return theme.colors[ChipTokens.disabledColor];
+ }
+
+ if (selectedColor !== undefined) {
+ return selectedColor;
+ }
+
+ if (selected) {
+ return theme.colors[ChipTokens.selectedIconColor];
+ }
+
+ return theme.colors[ChipTokens.leadingIconColor];
};
-const getIconColor = ({
+const getTrailingIconColor = ({
theme,
- isOutlined,
+ selected,
disabled,
selectedColor,
}: BaseProps & {
selectedColor?: ColorValue;
}) => {
- const isSelectedColor = selectedColor !== undefined;
- const { colors } = md3(theme);
if (disabled) {
- return colors.onSurface;
+ return theme.colors[ChipTokens.disabledColor];
}
- if (isSelectedColor) {
+ if (selectedColor !== undefined) {
return selectedColor;
}
- if (isOutlined) {
- return colors.onSurfaceVariant;
+ if (selected) {
+ return theme.colors[ChipTokens.selectedTrailingIconColor];
}
- return colors.onSecondaryContainer;
+ return theme.colors[ChipTokens.trailingIconColor];
+};
+
+const getStateLayerColor = ({
+ theme,
+ selected,
+}: Pick) => {
+ const colorRole = selected
+ ? ChipTokens.selectedStateLayerColor
+ : ChipTokens.stateLayerColor;
+
+ return (
+ withStringAlpha(theme.colors[colorRole], stateOpacity.pressed) ??
+ theme.colors.stateLayerPressed
+ );
};
export const getChipColors = ({
isOutlined,
theme,
+ selected,
selectedColor,
customBackgroundColor,
disabled,
+ elevated,
}: BaseProps & {
customBackgroundColor?: ColorValue;
disabled?: boolean;
selectedColor?: ColorValue;
}) => {
- const baseChipColorProps = { theme, isOutlined, disabled };
-
- const backgroundColor = getBackgroundColor({
- ...baseChipColorProps,
- customBackgroundColor,
- });
-
- const selectedBackgroundColor = getSelectedBackgroundColor({
- ...baseChipColorProps,
- customBackgroundColor,
- });
+ const baseChipColorProps = {
+ theme,
+ isOutlined,
+ selected,
+ disabled,
+ elevated,
+ };
const contentOpacity = disabled
- ? stateOpacity.disabled
+ ? ChipTokens.disabledContent
: stateOpacity.enabled;
return {
borderColor: getBorderColor({
...baseChipColorProps,
selectedColor,
- backgroundColor,
}),
- textColor: getTextColor({
+ textColor: getLabelColor({
+ ...baseChipColorProps,
+ selectedColor,
+ }),
+ iconColor: getLeadingIconColor({
...baseChipColorProps,
selectedColor,
}),
- iconColor: getIconColor({
+ closeIconColor: getTrailingIconColor({
...baseChipColorProps,
selectedColor,
}),
contentOpacity,
- backgroundColor,
- selectedBackgroundColor,
+ backgroundColor: getContainerColor({
+ ...baseChipColorProps,
+ customBackgroundColor,
+ }),
+ selectedBackgroundColor: getContainerColor({
+ ...baseChipColorProps,
+ selected: true,
+ customBackgroundColor,
+ }),
+ rippleColor: getStateLayerColor({ theme, selected }),
+ avatarOverlayColor: withAlpha(
+ theme.colors.scrim,
+ ChipTokens.selectedAvatarOverlay
+ ),
};
};
diff --git a/src/components/Chip/tokens.ts b/src/components/Chip/tokens.ts
new file mode 100644
index 0000000000..26131a51a7
--- /dev/null
+++ b/src/components/Chip/tokens.ts
@@ -0,0 +1,60 @@
+import type { ColorRole, Elevation, TypescaleKey } from '../../theme/types';
+
+/**
+ * MD3 Chip component tokens.
+ * @see https://m3.material.io/components/chips/specs
+ */
+const dimensions = {
+ containerHeight: 32,
+ minimumTouchTarget: 48,
+ outlineWidth: 1,
+ leadingIconSize: 18,
+ trailingIconSize: 18,
+ avatarSize: 24,
+ selectedIconSize: 18,
+ leadingPadding: 16,
+ trailingPadding: 16,
+ compactPadding: 12,
+ iconLeadingPadding: 8,
+ avatarLeadingPadding: 4,
+ closeTrailingPadding: 8,
+ leadingLabelGap: 8,
+ trailingIconTouchTarget: 32,
+ labelTypescale: 'labelLarge',
+} as const satisfies Record;
+
+const colors = {
+ elevatedContainerColor: 'surfaceContainerLow',
+ flatContainerColor: 'surfaceContainerLow',
+ selectedContainerColor: 'secondaryContainer',
+ outlinedContainerColor: 'surface',
+ labelColor: 'onSurfaceVariant',
+ selectedLabelColor: 'onSecondaryContainer',
+ leadingIconColor: 'primary',
+ selectedIconColor: 'onSecondaryContainer',
+ trailingIconColor: 'onSurfaceVariant',
+ selectedTrailingIconColor: 'onSecondaryContainer',
+ outlineColor: 'outlineVariant',
+ disabledColor: 'onSurface',
+ stateLayerColor: 'onSurfaceVariant',
+ selectedStateLayerColor: 'onSecondaryContainer',
+} as const satisfies Record;
+
+const opacity = {
+ disabledContainer: 0.12,
+ disabledContent: 0.38,
+ disabledOutline: 0.12,
+ selectedAvatarOverlay: 0.29,
+} as const;
+
+const elevation = {
+ flat: 0,
+ elevated: 1,
+} as const satisfies Record;
+
+export const ChipTokens = {
+ ...dimensions,
+ ...colors,
+ ...opacity,
+ ...elevation,
+};
diff --git a/src/components/__tests__/Chip.test.tsx b/src/components/__tests__/Chip.test.tsx
index 1c04130ebb..87b9c494d9 100644
--- a/src/components/__tests__/Chip.test.tsx
+++ b/src/components/__tests__/Chip.test.tsx
@@ -13,22 +13,24 @@ import { getChipColors } from '../Chip/helpers';
const stateOpacity = tokens.md.sys.state.opacity;
it('renders chip with onPress', () => {
- const tree = render( {}}>Example Chip).toJSON();
+ const tree = render(
+ {}} />
+ ).toJSON();
expect(tree).toMatchSnapshot();
});
it('renders chip with icon', () => {
- const tree = render(Example Chip).toJSON();
+ const tree = render(
+
+ ).toJSON();
expect(tree).toMatchSnapshot();
});
it('renders chip with close button', () => {
const tree = render(
- {}}>
- Example Chip
-
+ {}} />
).toJSON();
expect(tree).toMatchSnapshot();
@@ -36,9 +38,12 @@ it('renders chip with close button', () => {
it('renders chip with custom close button', () => {
const tree = render(
- {}} closeIcon="arrow-down">
- Example Chip
-
+ {}}
+ closeIcon="arrow-down"
+ />
).toJSON();
expect(tree).toMatchSnapshot();
@@ -46,23 +51,21 @@ it('renders chip with custom close button', () => {
it('renders outlined disabled chip', () => {
const tree = render(
-
- Example Chip
-
+
).toJSON();
expect(tree).toMatchSnapshot();
});
it('renders selected chip', () => {
- const tree = render(Example Chip).toJSON();
+ const tree = render().toJSON();
expect(tree).toMatchSnapshot();
});
it('renders disabled chip if there is no touch handler passed', () => {
const { getByTestId } = render(
- Disabled chip
+
);
expect(getByTestId('disabled-chip').props.accessibilityState).toMatchObject({
@@ -72,9 +75,7 @@ it('renders disabled chip if there is no touch handler passed', () => {
it('renders active chip if only onLongPress handler is passed', () => {
const { getByTestId } = render(
- {}} testID="active-chip">
- Active chip
-
+ {}} testID="active-chip" />
);
expect(getByTestId('active-chip').props.accessibilityState).toMatchObject({
@@ -84,9 +85,11 @@ it('renders active chip if only onLongPress handler is passed', () => {
it('renders chip with zero border radius', () => {
const { getByTestId } = render(
-
- Active chip
-
+
);
expect(getByTestId('active-chip')).toHaveStyle({
@@ -94,6 +97,15 @@ it('renders chip with zero border radius', () => {
});
});
+it('uses label before deprecated children', () => {
+ const { getByText, queryByText } = render(
+ Children chip
+ );
+
+ expect(getByText('Label chip')).toBeTruthy();
+ expect(queryByText('Children chip')).toBeNull();
+});
+
describe('getChipColors - text color', () => {
it('should return correct disabled color, for theme version 3', () => {
expect(
@@ -115,7 +127,7 @@ describe('getChipColors - text color', () => {
isOutlined: false,
})
).toMatchObject({
- textColor: getTheme().colors.onSecondaryContainer,
+ textColor: getTheme().colors.onSurfaceVariant,
});
});
@@ -164,7 +176,7 @@ describe('getChipColors - icon color', () => {
isOutlined: false,
})
).toMatchObject({
- iconColor: getTheme().colors.onSecondaryContainer,
+ iconColor: getTheme().colors.primary,
});
});
@@ -175,7 +187,7 @@ describe('getChipColors - icon color', () => {
isOutlined: true,
})
).toMatchObject({
- iconColor: getTheme().colors.onSurfaceVariant,
+ iconColor: getTheme().colors.primary,
});
});
@@ -222,6 +234,7 @@ describe('getChipColor - selected background color', () => {
getChipColors({
theme: getTheme(),
isOutlined: false,
+ selected: true,
})
).toMatchObject({
selectedBackgroundColor: getTheme().colors.secondaryContainer,
@@ -260,7 +273,32 @@ describe('getChipColor - background color', () => {
isOutlined: false,
})
).toMatchObject({
- backgroundColor: getTheme().colors.secondaryContainer,
+ backgroundColor: getTheme().colors.surfaceContainerLow,
+ });
+ });
+});
+
+describe('getChipColor - ripple color', () => {
+ it('falls back to precomputed state layer color for platform colors', () => {
+ const platformColor = {
+ resource_paths: ['@android:color/system_on_surface_variant_light'],
+ } as unknown as string;
+ const theme = {
+ ...getTheme(),
+ colors: {
+ ...getTheme().colors,
+ onSurfaceVariant: platformColor,
+ stateLayerPressed: 'rgba(29, 27, 32, 0.1)',
+ },
+ };
+
+ expect(
+ getChipColors({
+ theme,
+ isOutlined: true,
+ })
+ ).toMatchObject({
+ rippleColor: 'rgba(29, 27, 32, 0.1)',
});
});
});
@@ -374,12 +412,11 @@ it('animated value changes correctly', () => {
const value = new Animated.Value(1);
const { getByTestId } = render(
{}}
testID="chip"
style={[{ transform: [{ scale: value }] }]}
- >
- Example Chip
-
+ />
);
expect(getByTestId('chip-container-outer-layer')).toHaveStyle({
transform: [{ scale: 1 }],
diff --git a/src/components/__tests__/ListItem.test.tsx b/src/components/__tests__/ListItem.test.tsx
index 088be073fc..3ee49d728f 100644
--- a/src/components/__tests__/ListItem.test.tsx
+++ b/src/components/__tests__/ListItem.test.tsx
@@ -105,9 +105,7 @@ it('renders list item with custom description', () => {
Design library that has you covered in all major use-cases.
- {}}>
- DOCS.pdf
-
+ {}} />
)}
diff --git a/src/components/__tests__/__snapshots__/Chip.test.tsx.snap b/src/components/__tests__/__snapshots__/Chip.test.tsx.snap
index 736f79b6fe..1b713433ee 100644
--- a/src/components/__tests__/__snapshots__/Chip.test.tsx.snap
+++ b/src/components/__tests__/__snapshots__/Chip.test.tsx.snap
@@ -5,8 +5,10 @@ exports[`renders chip with close button 1`] = `
collapsable={false}
style={
{
- "backgroundColor": "rgba(232, 222, 248, 1)",
+ "alignSelf": "flex-start",
+ "backgroundColor": "rgba(254, 247, 255, 1)",
"borderRadius": 8,
+ "height": 32,
"shadowColor": "rgba(0, 0, 0, 1)",
"shadowOffset": {
"height": 0,
@@ -22,13 +24,15 @@ exports[`renders chip with close button 1`] = `
collapsable={false}
style={
{
- "backgroundColor": "rgba(232, 222, 248, 1)",
- "borderColor": "transparent",
+ "alignItems": "center",
+ "backgroundColor": "rgba(254, 247, 255, 1)",
+ "borderColor": "rgba(202, 196, 208, 1)",
"borderRadius": 8,
"borderStyle": "solid",
"borderWidth": 1,
- "flex": undefined,
- "flexDirection": "column",
+ "flex": 1,
+ "flexDirection": "row",
+ "overflow": "hidden",
"shadowColor": "rgba(0, 0, 0, 1)",
"shadowOffset": {
"height": 0,
@@ -41,6 +45,7 @@ exports[`renders chip with close button 1`] = `
testID="chip-container"
>
-
-
+
-
- close
-
-
-
+ },
+ {
+ "backgroundColor": "transparent",
+ },
+ ],
+ ]
+ }
+ >
+ close
+
@@ -303,8 +287,10 @@ exports[`renders chip with custom close button 1`] = `
collapsable={false}
style={
{
- "backgroundColor": "rgba(232, 222, 248, 1)",
+ "alignSelf": "flex-start",
+ "backgroundColor": "rgba(254, 247, 255, 1)",
"borderRadius": 8,
+ "height": 32,
"shadowColor": "rgba(0, 0, 0, 1)",
"shadowOffset": {
"height": 0,
@@ -320,13 +306,15 @@ exports[`renders chip with custom close button 1`] = `
collapsable={false}
style={
{
- "backgroundColor": "rgba(232, 222, 248, 1)",
- "borderColor": "transparent",
+ "alignItems": "center",
+ "backgroundColor": "rgba(254, 247, 255, 1)",
+ "borderColor": "rgba(202, 196, 208, 1)",
"borderRadius": 8,
"borderStyle": "solid",
"borderWidth": 1,
- "flex": undefined,
- "flexDirection": "column",
+ "flex": 1,
+ "flexDirection": "row",
+ "overflow": "hidden",
"shadowColor": "rgba(0, 0, 0, 1)",
"shadowOffset": {
"height": 0,
@@ -339,6 +327,7 @@ exports[`renders chip with custom close button 1`] = `
testID="chip-container"
>
-
-
+
-
- arrow-down
-
-
-
+ },
+ {
+ "backgroundColor": "transparent",
+ },
+ ],
+ ]
+ }
+ >
+ arrow-down
+
@@ -601,8 +569,10 @@ exports[`renders chip with icon 1`] = `
collapsable={false}
style={
{
- "backgroundColor": "rgba(232, 222, 248, 1)",
+ "alignSelf": "flex-start",
+ "backgroundColor": "rgba(254, 247, 255, 1)",
"borderRadius": 8,
+ "height": 32,
"shadowColor": "rgba(0, 0, 0, 1)",
"shadowOffset": {
"height": 0,
@@ -618,13 +588,15 @@ exports[`renders chip with icon 1`] = `
collapsable={false}
style={
{
- "backgroundColor": "rgba(232, 222, 248, 1)",
- "borderColor": "transparent",
+ "alignItems": "center",
+ "backgroundColor": "rgba(254, 247, 255, 1)",
+ "borderColor": "rgba(202, 196, 208, 1)",
"borderRadius": 8,
"borderStyle": "solid",
"borderWidth": 1,
- "flex": undefined,
- "flexDirection": "column",
+ "flex": 1,
+ "flexDirection": "row",
+ "overflow": "hidden",
"shadowColor": "rgba(0, 0, 0, 1)",
"shadowOffset": {
"height": 0,
@@ -637,6 +609,7 @@ exports[`renders chip with icon 1`] = `
testID="chip-container"
>