import color from 'color'; import * as React from 'react'; import { Animated, StyleProp, StyleSheet, TouchableWithoutFeedback, View, ViewStyle, } from 'react-native'; import { black, white } from '../../styles/colors'; import CardActions from './CardActions'; import CardContent from './CardContent'; // eslint-disable-next-line @typescript-eslint/no-unused-vars import CardCover from './CardCover'; // eslint-disable-next-line @typescript-eslint/no-unused-vars import { withTheme } from '../../core/theming'; import isNativeAnimationSupported from '../../utils/isNativeAnimationSupported'; import Surface from '../Surface'; import CardTitle from './CardTitle'; type OutlinedCardProps = { mode: 'outlined'; elevation?: never; }; type ElevatedCardProps = { mode?: 'elevated'; elevation?: number; }; type HandlePressType = 'in' | 'out'; type Props = React.ComponentProps & { /** * Resting elevation of the card which controls the drop shadow. */ elevation?: never | number; /** * Function to execute on long press. */ onLongPress?: () => void; /** * Function to execute on press. */ onPress?: () => void; /** * Mode of the Card. * - `elevated` - Card with elevation. * - `outlined` - Card with an outline. */ mode?: 'elevated' | 'outlined'; /** * Content of the `Card`. */ children: React.ReactNode; style?: StyleProp; /** * @optional */ theme: ReactNativePaper.Theme; /** * Pass down testID from card props to touchable */ testID?: string; /** * Pass down accessible from card props to touchable */ accessible?: boolean; }; /** * A card is a sheet of material that serves as an entry point to more detailed information. * *
* * *
* * ## Usage * ```js * import * as React from 'react'; * import { Avatar, Button, Card, Title, Paragraph } from 'react-native-paper'; * * const LeftContent = props => * * const MyComponent = () => ( * * * * Card title * Card content * * * * * * * * ); * * export default MyComponent; * ``` */ const Card = ({ elevation: cardElevation = 1, onLongPress, onPress, mode: cardMode = 'elevated', children, style, theme, testID, accessible, ...rest }: (OutlinedCardProps | ElevatedCardProps) & Props) => { // Default animated value const { current: elevation } = React.useRef( new Animated.Value(cardElevation) ); // Dark adaptive animated value, used in case of toggling the theme, // it prevents animating the background with native drivers inside Surface const { current: elevationDarkAdaptive } = React.useRef( new Animated.Value(cardElevation) ); const { animation, dark, mode, roundness } = theme; const prevDarkRef = React.useRef(dark); React.useEffect(() => { prevDarkRef.current = dark; }); const prevDark = prevDarkRef.current; const isAdaptiveMode = mode === 'adaptive'; const animationDuration = 150 * animation.scale; React.useEffect(() => { /** * Resets animations values if updating to dark adaptive mode, * otherwise, any card that is in the middle of animation while * toggling the theme will stay at that animated value until * the next press-in */ if (dark && isAdaptiveMode && !prevDark) { elevation.setValue(cardElevation); elevationDarkAdaptive.setValue(cardElevation); } }, [ prevDark, dark, isAdaptiveMode, cardElevation, elevation, elevationDarkAdaptive, ]); const runElevationAnimation = (pressType: HandlePressType) => { const isPressTypeIn = pressType === 'in'; if (dark && isAdaptiveMode) { Animated.timing(elevationDarkAdaptive, { toValue: isPressTypeIn ? 8 : cardElevation, duration: animationDuration, useNativeDriver: false, }).start(); } else { Animated.timing(elevation, { toValue: isPressTypeIn ? 8 : cardElevation, duration: animationDuration, useNativeDriver: isNativeAnimationSupported(), }).start(); } }; const handlePressIn = () => { runElevationAnimation('in'); }; const handlePressOut = () => { runElevationAnimation('out'); }; const total = React.Children.count(children); const siblings = React.Children.map(children, (child) => React.isValidElement(child) && child.type ? (child.type as any).displayName : null ); const borderColor = color(dark ? white : black) .alpha(0.12) .rgb() .string(); const computedElevation = dark && isAdaptiveMode ? elevationDarkAdaptive : elevation; return ( {React.Children.map(children, (child, index) => React.isValidElement(child) ? React.cloneElement(child, { index, total, siblings, }) : child )} ); }; // @component ./CardContent.tsx Card.Content = CardContent; // @component ./CardActions.tsx Card.Actions = CardActions; // @component ./CardCover.tsx Card.Cover = CardCover; // @component ./CardTitle.tsx Card.Title = CardTitle; const styles = StyleSheet.create({ innerContainer: { flexGrow: 1, flexShrink: 1, }, outlined: { elevation: 0, borderWidth: 1, }, }); export default withTheme(Card);