/** * DT Card Component * * A themed card following the Dangerous Things design language * with SVG-based beveled corners matching the web storefront style. * * Web CSS reference: * - Bottom-right bevel: 2em (~32px) * - Bottom-left bevel: 1em (~16px) * - Border width: 0.2em (~3px) */ import {ReactNode} from 'react'; import {StyleSheet, View, ViewStyle, StyleProp, Pressable} from 'react-native'; import {Text} from 'react-native-paper'; import Svg, {Path} from 'react-native-svg'; import {DTColors} from '../theme/colors'; import {type DTVariant, getVariantColor} from '../utils/variantColors'; import {buildCardBevelPath} from '../utils/bevelPaths'; import {useComponentLayout} from '../utils/useComponentLayout'; interface DTCardProps { children: ReactNode; /** * Color mode for the card accent * @default 'normal' */ mode?: DTVariant; /** * Custom border color (overrides mode) */ borderColor?: string; /** * Card title (displayed in header) */ title?: string; /** * Whether to show the accent header bar * @default true when title is provided */ showHeader?: boolean; /** * Additional styles for the card container */ style?: StyleProp; /** * Additional styles for the content area */ contentStyle?: StyleProp; /** * Inner content padding * @default 16 */ padding?: number; /** * Border width in pixels * @default 3 */ borderWidth?: number; /** * Bottom-right bevel size in pixels * @default 32 */ bevelSize?: number; /** * Bottom-left bevel size in pixels (smaller accent bevel) * @default 16 */ bevelSizeSmall?: number; /** * Background color * @default '#000000' */ backgroundColor?: string; /** * Press handler */ onPress?: () => void; } /** * DT-styled Card component with SVG beveled corners * * @example * * NTAG215 * * * @example * * Tap to scan * */ export function DTCard({ children, mode = 'normal', borderColor, title, showHeader, style, contentStyle, padding = 16, borderWidth = 3, bevelSize = 32, bevelSizeSmall = 16, backgroundColor = '#000000', onPress, }: DTCardProps) { const {dimensions, onLayout, hasDimensions} = useComponentLayout(); const accentColor = getVariantColor(mode, borderColor); const shouldShowHeader = showHeader ?? !!title; const {width, height} = dimensions; // Outer bevel path (full card shape) const outerPath = hasDimensions ? buildCardBevelPath(width, height, bevelSize, bevelSizeSmall, 0) : ''; // Inner bevel path at border inset (for background + frame cutout) const innerPath = hasDimensions ? buildCardBevelPath(width, height, bevelSize - borderWidth, bevelSizeSmall - borderWidth, borderWidth) : ''; const content = ( {/* Background fill (behind content) */} {hasDimensions && ( )} {shouldShowHeader && ( {title && ( {title} )} )} {children} {/* Frame overlay (above content) — clips content at beveled border */} {hasDimensions && ( )} ); if (onPress) { return ( ({opacity: pressed ? 0.8 : 1})}> {content} ); } return content; } /** * DT design system constants matching web CSS variables */ export const DTCardClipPath = { // CSS-style clip path (for web reference) css: `polygon( 0% 0%, 100% 0%, 100% calc(100% - 2em), calc(100% - 2em) 100%, 1em 100%, 0% calc(100% - 1em) )`, // Default bevel sizes in pixels (matching 2em and 1em at 16px base) bevelSize: 32, bevelSizeSmall: 16, borderWidth: 3, }; const styles = StyleSheet.create({ container: { position: 'relative', }, innerContainer: { position: 'relative', zIndex: 1, overflow: 'hidden', }, header: { paddingHorizontal: 16, paddingVertical: 12, }, headerText: { fontWeight: '700', letterSpacing: 0.5, }, content: {}, frameOverlay: { zIndex: 2, }, });