/** * DT MediaFrame Component * * A beveled frame for images and video content following the * Dangerous Things design language. No RNP equivalent. * * Web CSS reference (CyberMediaFrame / .media-frame-clipped): * - Top-left + bottom-right diagonal symmetry bevels * - Colored border with dark inner background * - Optional mount scale animation */ import {ReactNode, useRef, useEffect} from 'react'; import {StyleSheet, View, ViewStyle, StyleProp, Animated} from 'react-native'; import Svg, {Path, Defs, ClipPath} from 'react-native-svg'; import {DTColors} from '../theme/colors'; import {type DTVariant, getVariantColor} from '../utils/variantColors'; import {buildMediaFrameBevelPath} from '../utils/bevelPaths'; import {useComponentLayout} from '../utils/useComponentLayout'; interface DTMediaFrameProps { /** * Content to frame (Image, Video, etc.) */ children: ReactNode; /** * Visual variant * @default 'normal' */ variant?: DTVariant; /** * Aspect ratio (e.g., 16/9, 1, 4/3) */ aspectRatio?: number; /** * Border width in pixels * @default 3 */ borderWidth?: number; /** * Bevel size in pixels (top-left and bottom-right) * @default 32 */ bevelSize?: number; /** * Whether to animate on mount * @default true */ animated?: boolean; /** * Animation delay in milliseconds * @default 0 */ animationDelay?: number; /** * Custom color (overrides variant) */ color?: string; /** * Additional styles for outer container */ style?: StyleProp; /** * Additional styles for the content area */ contentStyle?: StyleProp; } /** * DT-styled MediaFrame with diagonal-symmetry beveled corners * * @example * * * * * @example * * */ export function DTMediaFrame({ children, variant = 'normal', aspectRatio, borderWidth = 3, bevelSize = 32, animated = true, animationDelay = 0, color, style, contentStyle, }: DTMediaFrameProps) { const {dimensions, onLayout, hasDimensions} = useComponentLayout(); const scaleAnim = useRef(new Animated.Value(animated ? 0 : 1)).current; const accentColor = getVariantColor(variant, color); useEffect(() => { if (animated) { Animated.timing(scaleAnim, { toValue: 1, duration: 300, delay: animationDelay, useNativeDriver: true, }).start(); } }, [animated, animationDelay, scaleAnim]); const {width, height} = dimensions; // Outer bevel path (full frame shape) const outerPath = hasDimensions ? buildMediaFrameBevelPath(width, height, bevelSize) : ''; // Inner bevel path at border inset const innerPath = hasDimensions ? buildMediaFrameBevelPath(width, height, bevelSize - borderWidth, borderWidth) : ''; return ( {/* Background fill (behind content) */} {hasDimensions && ( )} {/* Content clipped to the inner bevel shape */} {hasDimensions ? ( ) : ( {children} )} {/* Frame overlay (above content) — masks content at beveled corners */} {hasDimensions && ( {/* Outer frame border */} {/* Corner masks: fill the rectangular area outside the bevel shape with black to hide content that overflows past the beveled corners */} )} ); } const styles = StyleSheet.create({ container: { position: 'relative', }, content: { position: 'relative', zIndex: 1, overflow: 'hidden', flex: 1, }, frameOverlay: { zIndex: 2, }, });