import * as React from 'react'; import { AccessibilityState, Animated, ColorValue, GestureResponderEvent, Platform, StyleProp, StyleSheet, TextStyle, TouchableWithoutFeedback, View, ViewStyle, } from 'react-native'; import { useInternalTheme } from '../../core/theming'; import { white } from '../../styles/themes/v2/colors'; import type { $Omit, 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 Text from '../Typography/Text'; import { getChipColors } from './helpers'; export type Props = $Omit, 'mode'> & { /** * Mode of the chip. * - `flat` - flat chip without outline. * - `outlined` - chip with an outline. */ mode?: 'flat' | 'outlined'; /** * Text content of the `Chip`. */ children: React.ReactNode; /** * 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. */ avatar?: React.ReactNode; /** * Icon to display as the close button for the `Chip`. The icon appears only when the onClose prop is specified. */ closeIcon?: IconSource; /** * Whether chip is selected. */ 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. */ selectedColor?: string; /** * @supported Available in v5.x with theme version 3 * Whether to display overlay on selected chip */ showSelectedOverlay?: boolean; /** * Color of the ripple effect. */ rippleColor?: ColorValue; /** * Whether the chip is disabled. A disabled chip is greyed out and `onPress` is not called on touch. */ disabled?: boolean; /** * Accessibility label for the chip. This is read by the screen reader when the user taps the chip. */ accessibilityLabel?: string; /** * Accessibility label for the close icon. This is read by the screen reader when the user taps the close icon. */ closeIconAccessibilityLabel?: string; /** * Function to execute on press. */ onPress?: (e: GestureResponderEvent) => void; /** * @supported Available in v5.x with theme version 3 * Sets smaller horizontal paddings `12dp` around label, when there is only label. */ compact?: boolean; /** * @supported Available in v5.x with theme version 3 * Whether chip should have the elevation. */ elevated?: boolean; /** * Function to execute on long press. */ onLongPress?: () => void; /** * The number of milliseconds a user must touch the element before executing `onLongPress`. */ delayLongPress?: number; /** * Function to execute on close button press. The close button appears only when this prop is specified. */ onClose?: () => void; /** * Style of chip's text */ textStyle?: StyleProp; style?: Animated.WithAnimatedValue>; /** * @optional */ theme?: ThemeProp; /** * Pass down testID from chip props to touchable for Detox tests. */ testID?: string; /** * Ellipsize Mode for the children text */ ellipsizeMode?: EllipsizeProp; }; /** * Chips can be used to display entities in small blocks. * *
*
* *
Flat chip
*
*
* *
Outlined chip
*
*
* * ## Usage * ```js * import * as React from 'react'; * import { Chip } from 'react-native-paper'; * * const MyComponent = () => ( * console.log('Pressed')}>Example Chip * ); * * export default MyComponent; * ``` */ const Chip = ({ mode = 'flat', children, icon, avatar, selected = false, disabled = false, accessibilityLabel, closeIconAccessibilityLabel = 'Close', onPress, onLongPress, delayLongPress, onClose, closeIcon, textStyle, style, theme: themeOverrides, testID = 'chip', selectedColor, rippleColor: customRippleColor, showSelectedOverlay = false, ellipsizeMode, compact, elevated = false, ...rest }: Props) => { const theme = useInternalTheme(themeOverrides); const { isV3 } = theme; const { current: elevation } = React.useRef( new Animated.Value(isV3 && elevated ? 1 : 0) ); const hasPassedTouchHandler = hasTouchHandler({ onPress, onLongPress }); const isOutlined = mode === 'outlined'; const handlePressIn = () => { const { scale } = theme.animation; Animated.timing(elevation, { toValue: isV3 ? (elevated ? 2 : 0) : 4, duration: 200 * scale, useNativeDriver: true, }).start(); }; const handlePressOut = () => { const { scale } = theme.animation; Animated.timing(elevation, { toValue: isV3 && elevated ? 1 : 0, duration: 150 * scale, useNativeDriver: true, }).start(); }; const opacity = isV3 ? 0.38 : 0.26; const defaultBorderRadius = isV3 ? 8 : 16; const iconSize = isV3 ? 18 : 16; const { backgroundColor: customBackgroundColor, borderRadius = defaultBorderRadius, } = (StyleSheet.flatten(style) || {}) as ViewStyle; const { borderColor, textColor, iconColor, rippleColor, selectedBackgroundColor, backgroundColor, } = getChipColors({ isOutlined, theme, selectedColor, showSelectedOverlay, customBackgroundColor, disabled, customRippleColor, }); const accessibilityState: AccessibilityState = { selected, disabled, }; const elevationStyle = isV3 || Platform.OS === 'android' ? elevation : 0; const multiplier = isV3 ? (compact ? 1.5 : 2) : 1; const labelSpacings = { marginRight: onClose ? 0 : 8 * multiplier, marginLeft: avatar || icon || selected ? 4 * multiplier : 8 * multiplier, }; const contentSpacings = { paddingRight: isV3 ? (onClose ? 34 : 0) : onClose ? 32 : 4, }; const labelTextStyle = { color: textColor, ...(isV3 ? theme.fonts.labelLarge : theme.fonts.regular), }; return ( {avatar && !icon ? ( {React.isValidElement(avatar) ? React.cloneElement(avatar as React.ReactElement, { style: [styles.avatar, avatar.props.style], }) : avatar} ) : null} {icon || selected ? ( {icon ? ( ) : ( )} ) : null} {children} {onClose ? ( {closeIcon ? ( ) : ( )} ) : null} ); }; const styles = StyleSheet.create({ container: { borderWidth: StyleSheet.hairlineWidth, borderStyle: 'solid', flexDirection: Platform.select({ default: 'column', web: 'row' }), }, md3OutlineContainer: { borderWidth: 1, }, md3FlatContainer: { borderWidth: 0, }, content: { flexDirection: 'row', alignItems: 'center', paddingLeft: 4, position: 'relative', flexGrow: 1, }, md3Content: { paddingLeft: 0, }, icon: { padding: 4, alignSelf: 'center', }, md3Icon: { paddingLeft: 8, paddingRight: 0, }, closeIcon: { marginRight: 4, }, md3CloseIcon: { marginRight: 8, padding: 0, }, labelText: { minHeight: 24, lineHeight: 24, textAlignVertical: 'center', marginVertical: 4, }, md3LabelText: { textAlignVertical: 'center', marginVertical: 6, }, avatar: { width: 24, height: 24, borderRadius: 12, }, avatarWrapper: { marginRight: 4, }, md3AvatarWrapper: { marginLeft: 4, marginRight: 0, }, md3SelectedIcon: { paddingLeft: 4, }, // eslint-disable-next-line react-native/no-color-literals avatarSelected: { position: 'absolute', top: 4, left: 4, backgroundColor: 'rgba(0, 0, 0, .29)', }, closeButtonStyle: { position: 'absolute', right: 0, height: '100%', justifyContent: 'center', alignItems: 'center', }, touchable: { flexGrow: 1, }, }); export default Chip;