Make RN components fully theme-driven for multi-brand support

Refactor all 18 React Native components from static DTColors imports to
dynamic useDTTheme() context, enabling proper visual differentiation
when switching between DT and Classic brands.

Key changes:
- Replace static DTColors with theme context in all components
- Add shape tokens (bevel/radius) to DTExtendedTheme interface
- Conditionally render SVG bevels (DT) vs borderRadius (Classic)
- Configure fonts per brand (Tektur for DT, system sans-serif for Classic)
- Update buildThemeFromBrand to parse shape and typography tokens
- Fix Metro config for monorepo singleton resolution

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
michael
2026-03-04 12:19:25 -08:00
parent 1b0d09313c
commit 47e8085581
23 changed files with 436 additions and 239 deletions

View File

@@ -13,7 +13,7 @@
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 {useDTTheme} from '../theme/DTThemeProvider';
import {type DTVariant, getVariantColor} from '../utils/variantColors';
import {buildMediaFrameBevelPath} from '../utils/bevelPaths';
import {useComponentLayout} from '../utils/useComponentLayout';
@@ -91,9 +91,10 @@ export function DTMediaFrame({
style,
contentStyle,
}: DTMediaFrameProps) {
const theme = useDTTheme();
const {dimensions, onLayout, hasDimensions} = useComponentLayout();
const scaleAnim = useRef(new Animated.Value(animated ? 0 : 1)).current;
const accentColor = getVariantColor(variant, color);
const accentColor = getVariantColor(theme, variant, color);
useEffect(() => {
if (animated) {
@@ -107,13 +108,14 @@ export function DTMediaFrame({
}, [animated, animationDelay, scaleAnim]);
const {width, height} = dimensions;
const useBevels = theme.custom.bevelMd > 0;
// Outer bevel path (full frame shape)
const outerPath = hasDimensions
const outerPath = useBevels && hasDimensions
? buildMediaFrameBevelPath(width, height, bevelSize)
: '';
// Inner bevel path at border inset
const innerPath = hasDimensions
const innerPath = useBevels && hasDimensions
? buildMediaFrameBevelPath(width, height, bevelSize - borderWidth, borderWidth)
: '';
@@ -123,21 +125,27 @@ export function DTMediaFrame({
styles.container,
aspectRatio ? {aspectRatio} : undefined,
{transform: [{scale: scaleAnim}]},
!useBevels && {
borderWidth,
borderColor: accentColor,
borderRadius: theme.custom.radius,
overflow: 'hidden',
},
style,
]}
onLayout={onLayout}>
{/* Background fill (behind content) */}
{hasDimensions && (
{/* Background fill (behind content) — beveled mode only */}
{useBevels && hasDimensions && (
<Svg
style={StyleSheet.absoluteFill}
width={width}
height={height}
viewBox={`0 0 ${width} ${height}`}>
<Path d={innerPath} fill={DTColors.dark} />
<Path d={innerPath} fill={theme.colors.background} />
</Svg>
)}
{/* Content clipped to the inner bevel shape */}
{hasDimensions ? (
{/* Content */}
{useBevels && hasDimensions ? (
<View style={[styles.content, contentStyle]}>
<Svg
width={width}
@@ -155,7 +163,6 @@ export function DTMediaFrame({
StyleSheet.absoluteFill,
{
margin: borderWidth,
// Use borderRadius 0 to keep angular, overflow hidden clips rectangular part
overflow: 'hidden',
},
]}>
@@ -163,30 +170,27 @@ export function DTMediaFrame({
</View>
</View>
) : (
<View style={[styles.content, {padding: borderWidth}, contentStyle]}>
<View style={[styles.content, !useBevels ? undefined : {padding: borderWidth}, contentStyle]}>
{children}
</View>
)}
{/* Frame overlay (above content) — masks content at beveled corners */}
{hasDimensions && (
{/* Frame overlay (above content) — beveled mode only */}
{useBevels && hasDimensions && (
<Svg
style={[StyleSheet.absoluteFill, styles.frameOverlay]}
width={width}
height={height}
viewBox={`0 0 ${width} ${height}`}
pointerEvents="none">
{/* Outer frame border */}
<Path
d={outerPath + ' ' + innerPath}
fillRule="evenodd"
fill={accentColor}
/>
{/* Corner masks: fill the rectangular area outside the bevel shape with black
to hide content that overflows past the beveled corners */}
<Path
d={`M 0 0 L ${width} 0 L ${width} ${height} L 0 ${height} Z ` + outerPath}
fillRule="evenodd"
fill={DTColors.dark}
fill={theme.colors.background}
/>
</Svg>
)}