85 lines
2.4 KiB
TypeScript
85 lines
2.4 KiB
TypeScript
import React, { memo, type PropsWithChildren } from 'react';
|
|
import { StyleSheet, View, type StyleProp, type ViewStyle } from 'react-native';
|
|
|
|
import { useTheme } from '../../theme';
|
|
|
|
/**
|
|
* `default` is the resting card and the right answer almost always.
|
|
*
|
|
* `flat` has a border and no lift — use it for a card that sits inside another
|
|
* card, or for a grouped list where a shadow per row would be noise.
|
|
*
|
|
* `raised` is for the one object on a screen that has to sit above everything
|
|
* else, typically the hero balance card. Two raised cards on one screen means
|
|
* neither of them is.
|
|
*
|
|
* `muted` is a well rather than an object — a summary block, a read-only recap.
|
|
*
|
|
* `glass` is kept only because 13 call sites still pass it. There is no blur
|
|
* library in this app, so it never actually rendered as glass; it resolves to
|
|
* `default`.
|
|
*/
|
|
type CardVariant = 'default' | 'flat' | 'raised' | 'muted' | 'glass';
|
|
|
|
interface CardProps extends PropsWithChildren {
|
|
style?: StyleProp<ViewStyle>;
|
|
variant?: CardVariant;
|
|
/** Drops the built-in padding, for a card that holds an edge-to-edge list. */
|
|
noPadding?: boolean;
|
|
}
|
|
|
|
const CardComponent = ({
|
|
children,
|
|
style,
|
|
variant = 'default',
|
|
noPadding = false,
|
|
}: CardProps) => {
|
|
const theme = useTheme();
|
|
|
|
const { backgroundColor, elevation } = ((): {
|
|
backgroundColor: string;
|
|
elevation: ViewStyle;
|
|
} => {
|
|
switch (variant) {
|
|
case 'flat':
|
|
return { backgroundColor: theme.colors.surface, elevation: theme.elevation.level0 };
|
|
case 'raised':
|
|
return { backgroundColor: theme.colors.surface, elevation: theme.elevation.level2 };
|
|
case 'muted':
|
|
return { backgroundColor: theme.colors.surfaceMuted, elevation: theme.elevation.level0 };
|
|
case 'glass':
|
|
case 'default':
|
|
default:
|
|
return { backgroundColor: theme.colors.surface, elevation: theme.elevation.level1 };
|
|
}
|
|
})();
|
|
|
|
return (
|
|
<View
|
|
style={[
|
|
styles.card,
|
|
elevation,
|
|
{
|
|
backgroundColor,
|
|
borderRadius: theme.radius.lg,
|
|
padding: noPadding ? 0 : theme.spacing.md,
|
|
},
|
|
style,
|
|
]}
|
|
>
|
|
{children}
|
|
</View>
|
|
);
|
|
};
|
|
|
|
const styles = StyleSheet.create({
|
|
card: {
|
|
borderWidth: 1,
|
|
// Clipping is what lets a card hold an edge-to-edge list or image without
|
|
// the child squaring off the card's own corners.
|
|
overflow: 'hidden',
|
|
},
|
|
});
|
|
|
|
export const Card = memo(CardComponent);
|