import type { StyleProp, TextStyle, ViewStyle } from 'react-native'; import type { ColorToken, ShapeToken, SpacingToken, StyleWire, TypeToken } from '@proulr/sdui-contract'; import { m3Colors, m3Shape, m3Type, spacing } from './proulr-theme'; function resolveColor(token: ColorToken | undefined): string | undefined { if (!token) return undefined; return m3Colors[token as keyof typeof m3Colors]; } function resolveSpacing(token: SpacingToken | number | undefined): number | undefined { if (token == null) return undefined; if (typeof token === 'number') return token; return spacing[token]; } function resolveShape(token: ShapeToken | number | undefined): number | undefined { if (token == null) return undefined; if (typeof token === 'number') return token; return m3Shape[token]; } function resolveTypography(token: TypeToken | undefined): TextStyle | undefined { if (!token) return undefined; return m3Type[token]; } function camelToSnake(key: string): string { return key.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`); } /** * Canonical StyleWire is snake_case. Accept camelCase inputs from older wires * without dropping underscore keys (previous normalize converted to camel and * broke every `*_` token read). */ function normalizeStyleWire(style: StyleWire | Record | undefined): StyleWire { if (!style) return {}; const normalized: Record = {}; for (const [key, value] of Object.entries(style)) { const snakeKey = key.includes('_') ? key : camelToSnake(key); normalized[snakeKey] = value; } return normalized as StyleWire; } export function resolveViewStyleProps( styleWire: StyleWire | Record | undefined, ): StyleProp { const style = normalizeStyleWire(styleWire); return { backgroundColor: resolveColor(style.background_color), padding: resolveSpacing(style.padding), paddingHorizontal: resolveSpacing(style.padding_horizontal), paddingVertical: resolveSpacing(style.padding_vertical), marginTop: resolveSpacing(style.margin_top), gap: resolveSpacing(style.gap), borderRadius: resolveShape(style.border_radius), borderWidth: style.border_width, borderColor: resolveColor(style.border_color), opacity: style.opacity, flex: style.flex, alignItems: style.align_items, }; } export function resolveTextStyleProps( styleWire: StyleWire | Record | undefined, variant?: string, ): StyleProp { const style = normalizeStyleWire(styleWire); const variantType = (style.typography ?? variant) as TypeToken | undefined; return { ...resolveTypography(variantType), color: resolveColor(style.color), marginTop: resolveSpacing(style.margin_top), opacity: style.opacity, textAlign: style.text_align, }; } export function resolveGapProp(gap: unknown, fallback: number): number { if (typeof gap === 'number') return gap; if (typeof gap === 'string' && gap in spacing) return spacing[gap as SpacingToken]; return fallback; } export function resolvePaddingProp(padding: unknown, fallback = 0): number { if (typeof padding === 'number') return padding; if (typeof padding === 'string' && padding in spacing) return spacing[padding as SpacingToken]; return fallback; }