UNPKG

@productshiv/baapui

Version:

A truly cross-platform multi-design UI framework that works with React, Next.js, React Native, and any React-based framework.

1,491 lines (1,482 loc) 326 kB
import React, { createContext, useContext, useState, useCallback, useMemo, useEffect } from 'react'; import { jsx, jsxs } from 'react/jsx-runtime'; // Platform abstraction layer for cross-platform compatibility // Platform detection using environment-specific globals const PlatformInfo = { isReactNative: (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') || (typeof global !== 'undefined' && // @ts-ignore global.__DEV__ !== undefined && typeof window === 'undefined'), isWeb: typeof window !== 'undefined' && typeof document !== 'undefined', }; // Enhanced style conversion function const convertRNStyleToCSS = (style) => { var _a, _b, _c, _d; if (!style) return {}; const cssStyle = {}; // Handle transform array FIRST before copying other properties if (style.transform && Array.isArray(style.transform)) { const transforms = style.transform.map((transform) => { const key = Object.keys(transform)[0]; let value = transform[key]; // Convert Animated.Value to number if needed if (value && typeof value.valueOf === 'function') { value = value.valueOf(); } // Handle different transform types if (key === 'translateX' || key === 'translateY') { return `${key}(${value}px)`; } else if (key === 'scale' || key === 'scaleX' || key === 'scaleY') { return `${key}(${value})`; } else if (key === 'rotate') { return `${key}(${value}deg)`; } return `${key}(${value})`; }); cssStyle.transform = transforms.join(' '); } // Copy only valid CSS properties (excluding transform which we handled above) Object.keys(style).forEach(key => { if (key === 'transform') return; // Skip transform, we handled it above const value = style[key]; // Skip undefined, null, or function values if (value === undefined || value === null || typeof value === 'function') { return; } // Skip array values that aren't transform (they shouldn't be in CSS) if (Array.isArray(value)) { return; } cssStyle[key] = value; }); // Handle React Native specific properties if (style.paddingHorizontal !== undefined) { cssStyle.paddingLeft = style.paddingHorizontal; cssStyle.paddingRight = style.paddingHorizontal; delete cssStyle.paddingHorizontal; } if (style.paddingVertical !== undefined) { cssStyle.paddingTop = style.paddingVertical; cssStyle.paddingBottom = style.paddingVertical; delete cssStyle.paddingVertical; } if (style.marginHorizontal !== undefined) { cssStyle.marginLeft = style.marginHorizontal; cssStyle.marginRight = style.marginHorizontal; delete cssStyle.marginHorizontal; } if (style.marginVertical !== undefined) { cssStyle.marginTop = style.marginVertical; cssStyle.marginBottom = style.marginVertical; delete cssStyle.marginVertical; } // Handle text shadow properties if (style.textShadowColor || style.textShadowOffset || style.textShadowRadius) { const color = style.textShadowColor || 'rgba(0,0,0,0.3)'; const offsetX = ((_a = style.textShadowOffset) === null || _a === void 0 ? void 0 : _a.width) || 1; const offsetY = ((_b = style.textShadowOffset) === null || _b === void 0 ? void 0 : _b.height) || 1; const radius = style.textShadowRadius || 1; cssStyle.textShadow = `${offsetX}px ${offsetY}px ${radius}px ${color}`; delete cssStyle.textShadowColor; delete cssStyle.textShadowOffset; delete cssStyle.textShadowRadius; } // Handle shadow properties if (style.shadowColor || style.shadowOffset || style.shadowRadius || style.shadowOpacity) { style.shadowColor || 'rgba(0,0,0,0.3)'; const offsetX = ((_c = style.shadowOffset) === null || _c === void 0 ? void 0 : _c.width) || 0; const offsetY = ((_d = style.shadowOffset) === null || _d === void 0 ? void 0 : _d.height) || 2; const radius = style.shadowRadius || 4; const opacity = style.shadowOpacity || 0.3; cssStyle.boxShadow = `${offsetX}px ${offsetY}px ${radius}px rgba(0,0,0,${opacity})`; delete cssStyle.shadowColor; delete cssStyle.shadowOffset; delete cssStyle.shadowRadius; delete cssStyle.shadowOpacity; } // Handle elevation (Android) if (style.elevation !== undefined) { cssStyle.boxShadow = `0px ${style.elevation}px ${style.elevation * 2}px rgba(0,0,0,0.2)`; delete cssStyle.elevation; } // Remove React Native-specific properties that don't exist in CSS const rnOnlyProps = [ 'includeFontPadding', 'textAlignVertical', 'fontVariant', 'letterSpacing', // CSS has letter-spacing but RN uses letterSpacing 'lineHeight', // Can cause issues if not a number 'textDecorationColor', 'textDecorationStyle', 'writingDirection', 'backfaceVisibility', 'borderBottomEndRadius', 'borderBottomStartRadius', 'borderTopEndRadius', 'borderTopStartRadius', 'borderEndColor', 'borderStartColor', 'borderEndWidth', 'borderStartWidth', 'end', 'start', 'marginEnd', 'marginStart', 'paddingEnd', 'paddingStart', 'overlayColor', 'resizeMode', 'tintColor', ]; rnOnlyProps.forEach(prop => { delete cssStyle[prop]; }); // Ensure color values are strings if (cssStyle.backgroundColor && typeof cssStyle.backgroundColor === 'object') { cssStyle.backgroundColor = String(cssStyle.backgroundColor); } if (cssStyle.color && typeof cssStyle.color === 'object') { cssStyle.color = String(cssStyle.color); } // Ensure numeric values that should be strings with units if (typeof cssStyle.fontSize === 'number') { cssStyle.fontSize = `${cssStyle.fontSize}px`; } if (typeof cssStyle.lineHeight === 'number') { cssStyle.lineHeight = cssStyle.lineHeight; } // Final cleanup - remove any remaining invalid properties const cleanedStyle = {}; Object.keys(cssStyle).forEach(key => { const value = cssStyle[key]; // Debug logging for problematic values if (process.env.NODE_ENV === 'development' && (Array.isArray(value) || (typeof value === 'object' && value !== null && typeof value.valueOf === 'function'))) { console.warn(`Skipping invalid CSS property ${key}:`, value); } // Only keep primitive values and valid objects if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' || value === null) { cleanedStyle[key] = value; } else if (typeof value === 'object' && value !== null && !Array.isArray(value) && typeof value.valueOf !== 'function' // Skip Animated.Value objects ) { // Allow plain objects (like shadowOffset) cleanedStyle[key] = value; } // Skip everything else (arrays, functions, Animated.Value, etc.) }); return cleanedStyle; }; // Style processing helper const processStyle = (style) => { if (!style) return {}; if (Array.isArray(style)) { // Filter out falsy values and process each style return style .filter(s => s) // Remove null, undefined, false values .reduce((acc, s) => ({ ...acc, ...convertRNStyleToCSS(s) }), {}); } return convertRNStyleToCSS(style); }; // Mock Animated for web const MockAnimated = { View: ({ style, children, ...props }) => { const processedStyle = processStyle(style); return React.createElement('div', { style: processedStyle, ...props, }, children); }, Text: ({ style, children, ...props }) => { const processedStyle = processStyle(style); return React.createElement('span', { style: processedStyle, ...props, }, children); }, Value: class MockValue { constructor(value) { this.value = value; } setValue(value) { this.value = value; } addListener() { return 'mock-listener'; } removeListener() { } interpolate(config) { // Simple mock interpolation for web return this.value; } // Add valueOf to make it work with CSS transforms valueOf() { return this.value; } toString() { return this.value.toString(); } }, timing: (animatedValue, config) => ({ start: (callback) => { if (animatedValue && animatedValue.setValue) { animatedValue.setValue(config.toValue); } callback === null || callback === void 0 ? void 0 : callback(); }, }), spring: (animatedValue, config) => ({ start: (callback) => { if (animatedValue && animatedValue.setValue) { animatedValue.setValue(config.toValue); } callback === null || callback === void 0 ? void 0 : callback(); }, }), loop: (animation) => ({ start: () => { } }), sequence: (animations) => ({ start: (callback) => callback === null || callback === void 0 ? void 0 : callback() }), decay: () => ({ start: (callback) => callback === null || callback === void 0 ? void 0 : callback() }), parallel: (animations) => ({ start: (callback) => callback === null || callback === void 0 ? void 0 : callback() }), stagger: (time, animations) => ({ start: (callback) => callback === null || callback === void 0 ? void 0 : callback(), }), delay: (time) => ({ start: (callback) => callback === null || callback === void 0 ? void 0 : callback() }), }; // Web-only components (no React Native dependencies) const WebComponents = { View: ({ style, children, onPress, ...props }) => { const processedStyle = processStyle(style); const webProps = { style: processedStyle, ...props }; if (onPress) { webProps.onClick = onPress; webProps.style = { ...processedStyle, cursor: 'pointer', userSelect: 'none', }; return React.createElement('button', webProps, children); } return React.createElement('div', webProps, children); }, Text: ({ style, children, numberOfLines, adjustsFontSizeToFit, minimumFontScale, ...props }) => { const processedStyle = processStyle(style); // Filter out React Native-specific props const webProps = { ...props }; delete webProps.numberOfLines; delete webProps.adjustsFontSizeToFit; delete webProps.minimumFontScale; return React.createElement('span', { style: processedStyle, ...webProps }, children); }, TextInput: ({ style, value, onChangeText, placeholder, multiline, numberOfLines, ...props }) => { const processedStyle = processStyle(style); const handleChange = (e) => { onChangeText === null || onChangeText === void 0 ? void 0 : onChangeText(e.target.value); }; if (multiline) { return React.createElement('textarea', { style: { ...processedStyle, resize: 'vertical' }, value, onChange: handleChange, placeholder, rows: numberOfLines || 4, ...props, }); } return React.createElement('input', { style: processedStyle, type: 'text', value, onChange: handleChange, placeholder, ...props, }); }, TouchableOpacity: ({ style, children, onPress, disabled, onPressIn, onPressOut, activeOpacity, ...props }) => { const processedStyle = processStyle(style); // Filter out React Native-specific props const webProps = { ...props }; delete webProps.onPressIn; delete webProps.onPressOut; delete webProps.activeOpacity; return React.createElement('button', { style: { // Base button reset styles - be more specific about what we reset outline: 'none', background: 'transparent', padding: 0, userSelect: 'none', cursor: disabled ? 'not-allowed' : 'pointer', opacity: disabled ? 0.6 : 1, // Component styles override base styles ...processedStyle, }, onClick: disabled ? undefined : onPress, disabled, ...webProps, }, children); }, Pressable: ({ style, onPress, children, disabled, onPressIn, onPressOut, ...props }) => { const processedStyle = processStyle(style); // Filter out React Native-specific props const webProps = { ...props }; delete webProps.onPressIn; delete webProps.onPressOut; return React.createElement('button', { style: { ...processedStyle, cursor: disabled ? 'not-allowed' : 'pointer', opacity: disabled ? 0.6 : 1, background: 'transparent', border: 'none', }, onClick: disabled ? undefined : onPress, disabled, ...webProps, }, children); }, ScrollView: ({ style, children, horizontal, ...props }) => { const processedStyle = processStyle(style); return React.createElement('div', { style: { ...processedStyle, overflow: 'auto', display: horizontal ? 'flex' : 'block', flexDirection: horizontal ? 'row' : 'column', }, ...props, }, children); }, SafeAreaView: ({ style, children, ...props }) => { const processedStyle = processStyle(style); return React.createElement('div', { style: { ...processedStyle, paddingTop: 'env(safe-area-inset-top)', paddingBottom: 'env(safe-area-inset-bottom)', paddingLeft: 'env(safe-area-inset-left)', paddingRight: 'env(safe-area-inset-right)', }, ...props, }, children); }, Image: ({ style, source, ...props }) => { const processedStyle = processStyle(style); const src = typeof source === 'object' ? source.uri : source; return React.createElement('img', { style: processedStyle, src, ...props, }); }, ActivityIndicator: ({ color = '#000', size = 'medium', style }) => { const processedStyle = processStyle(style); const sizeValue = size === 'small' ? '16px' : size === 'large' ? '32px' : '24px'; return React.createElement('div', { style: { ...processedStyle, width: sizeValue, height: sizeValue, border: `2px solid ${color}30`, borderTop: `2px solid ${color}`, borderRadius: '50%', animation: 'baap-spin 1s linear infinite', }, }); }, Modal: ({ visible, transparent, animationType, onRequestClose, children, ...props }) => { // For React Native, this will be replaced by the actual RN Modal at runtime // For web, we use our custom modal implementation if (!visible) return null; return React.createElement('div', { style: { position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, backgroundColor: transparent ? 'transparent' : 'rgba(0,0,0,0.5)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1000, }, onClick: (e) => { if (e.target === e.currentTarget) { onRequestClose === null || onRequestClose === void 0 ? void 0 : onRequestClose(); } }, ...props, }, children); }, Animated: MockAnimated, }; // Web-only APIs const WebAPIs = { StyleSheet: { create: (styles) => { // For React Native, this will use the actual StyleSheet.create at runtime // For web, we just return the styles as-is return styles; }, flatten: (style) => { if (Array.isArray(style)) { return style.reduce((acc, s) => ({ ...acc, ...s }), {}); } return style || {}; }, }, Platform: { OS: (() => { // For React Native, this will be replaced with actual Platform.OS at runtime // For web, we always return 'web' return 'web'; })(), select: (options) => { const os = WebAPIs.Platform.OS; return options[os] || options.default; }, }, Dimensions: { get: (dimension) => { // For React Native, this will be replaced with actual Dimensions.get at runtime // For web, we use window dimensions if (typeof window !== 'undefined') { return { width: window.innerWidth, height: window.innerHeight, }; } return { width: 375, height: 667 }; }, }, }; // Export platform components and APIs (always web components) const { View, Text, TextInput, TouchableOpacity, Pressable, ScrollView, SafeAreaView, Image, ActivityIndicator, Modal: Modal$1, Animated, } = WebComponents; const { StyleSheet, Platform, Dimensions } = WebAPIs; // Add CSS animation for spinner if (typeof document !== 'undefined') { const style = document.createElement('style'); style.textContent = ` @keyframes baap-spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } `; document.head.appendChild(style); } const flatLightTheme = { mode: 'light', design: 'flat', colors: { primary: '#007AFF', secondary: '#5856D6', background: '#FFFFFF', surface: '#F2F2F7', text: '#000000', textSecondary: '#8E8E93', border: '#C7C7CC', error: '#FF3B30', success: '#34C759', warning: '#FF9500', info: '#5856D6', lightShadow: '#FFFFFF', darkShadow: '#000000', }, shadows: { small: { shadowColor: '#000000', shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.18, shadowRadius: 1.0, elevation: 1, }, medium: { shadowColor: '#000000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.25, shadowRadius: 3.84, elevation: 3, }, large: { shadowColor: '#000000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.3, shadowRadius: 4.65, elevation: 5, }, }, spacing: { xs: 4, sm: 8, md: 16, lg: 24, xl: 32, }, typography: { h1: { fontSize: 34, fontWeight: 'bold', letterSpacing: 0.37, }, h2: { fontSize: 28, fontWeight: '600', letterSpacing: 0.35, }, h3: { fontSize: 22, fontWeight: '600', letterSpacing: 0.35, }, body: { fontSize: 17, fontWeight: 'normal', letterSpacing: -0.41, }, button: { fontSize: 17, fontWeight: '600', letterSpacing: -0.41, }, caption: { fontSize: 12, fontWeight: 'normal', letterSpacing: 0, }, }, shape: { borderRadius: { sm: 4, md: 8, lg: 12, full: 9999, }, }, }; const neumorphicLightTheme = { mode: 'light', design: 'neumorphic', colors: { primary: '#4A90E2', secondary: '#5856D6', background: '#E0E5EC', surface: '#E0E5EC', text: '#2D3436', textSecondary: '#636E72', border: '#D1D9E6', error: '#FF3B30', success: '#34C759', warning: '#FF9500', info: '#5856D6', lightShadow: '#FFFFFF', darkShadow: '#A3B1C6', }, shadows: { small: { shadowColor: '#000000', shadowOffset: { width: -3, height: -3 }, shadowOpacity: 0.2, shadowRadius: 3, elevation: 3, }, medium: { shadowColor: '#000000', shadowOffset: { width: -5, height: -5 }, shadowOpacity: 0.2, shadowRadius: 6, elevation: 5, }, large: { shadowColor: '#000000', shadowOffset: { width: -8, height: -8 }, shadowOpacity: 0.2, shadowRadius: 10, elevation: 8, }, }, spacing: { xs: 4, sm: 8, md: 16, lg: 24, xl: 32, }, typography: { h1: { fontSize: 34, fontWeight: 'bold', letterSpacing: 0.37, }, h2: { fontSize: 28, fontWeight: '600', letterSpacing: 0.35, }, h3: { fontSize: 22, fontWeight: '600', letterSpacing: 0.35, }, body: { fontSize: 17, fontWeight: 'normal', letterSpacing: -0.41, }, button: { fontSize: 17, fontWeight: '600', letterSpacing: -0.41, }, caption: { fontSize: 12, fontWeight: 'normal', letterSpacing: 0, }, }, shape: { borderRadius: { sm: 8, md: 12, lg: 16, full: 9999, }, }, }; // Skeuomorphic Color Palette - Rich, realistic colors const SKEUOMORPHIC_COLORS = { // Primary Colors - Rich blues with depth primary: '#2c5aa0', primaryLight: '#4a7bc8', primaryDark: '#1e3f73', // Secondary Colors - Warm oranges secondary: '#d4731b', secondaryLight: '#e89142', secondaryDark: '#a85a15', // Surface Colors - Paper-like textures surface: '#f8f6f2', surfaceVariant: '#ede9e4', // Background Colors - Natural tones background: '#fafaf8', // Text Colors - Rich blacks and whites onPrimary: '#ffffff', onSecondary: '#ffffff', onSurface: '#2d2c2a', // State Colors - Natural variations success: '#2d7d32', successDark: '#1b5e20', warning: '#f57c00', warningDark: '#e65100', error: '#d32f2f', errorDark: '#b71c1c', info: '#1976d2', infoDark: '#0d47a1', // Neutral Colors - Paper and metal tones neutral: '#6d6d6d', // Border Colors - Realistic edges border: '#d0ccc4', borderLight: '#e0ddd6', borderDark: '#a8a39a', // Shadow Colors - Multiple layers shadowLight: 'rgba(0, 0, 0, 0.08)', shadowMedium: 'rgba(0, 0, 0, 0.16)', shadowDark: 'rgba(0, 0, 0, 0.24)', shadowInner: 'rgba(0, 0, 0, 0.12)', // Highlight Colors - Realistic light reflections highlight: 'rgba(255, 255, 255, 0.6)', // Disabled Colors disabled: '#bdbdbd', disabledText: '#9e9e9e', }; // Complex Shadow System - Multiple layers for depth const SKEUOMORPHIC_SHADOWS = { // Button Shadows - Realistic button depth button: { default: [ { x: 0, y: 2, blur: 4, color: SKEUOMORPHIC_COLORS.shadowLight }, { x: 0, y: 4, blur: 8, color: SKEUOMORPHIC_COLORS.shadowMedium }, { x: 0, y: 1, blur: 0, color: SKEUOMORPHIC_COLORS.border }, ], pressed: [ { x: 0, y: 1, blur: 2, color: SKEUOMORPHIC_COLORS.shadowInner }, { x: 0, y: 0, blur: 0, color: SKEUOMORPHIC_COLORS.borderDark }, ]}, // Card Shadows - Paper-like depth card: [ { x: 0, y: 2, blur: 8, color: SKEUOMORPHIC_COLORS.shadowLight }, { x: 0, y: 4, blur: 16, color: SKEUOMORPHIC_COLORS.shadowMedium }, { x: 0, y: 1, blur: 0, color: SKEUOMORPHIC_COLORS.borderLight }, ], // Input Shadows - Inset depth input: { default: [ { x: 0, y: 0, blur: 0, color: SKEUOMORPHIC_COLORS.border }, { x: 0, y: 1, blur: 2, color: SKEUOMORPHIC_COLORS.shadowInner, inset: true }, ], focused: [ { x: 0, y: 0, blur: 0, color: SKEUOMORPHIC_COLORS.primary }, { x: 0, y: 1, blur: 3, color: SKEUOMORPHIC_COLORS.shadowInner, inset: true }, { x: 0, y: 0, blur: 4, color: `${SKEUOMORPHIC_COLORS.primary}40` }, ], }, // Modal Shadows - Deep overlay modal: [ { x: 0, y: 8, blur: 32, color: SKEUOMORPHIC_COLORS.shadowDark }, { x: 0, y: 4, blur: 16, color: SKEUOMORPHIC_COLORS.shadowMedium }, { x: 0, y: 2, blur: 8, color: SKEUOMORPHIC_COLORS.shadowLight }, ], // Text Shadows - Realistic text depth text: { default: [{ x: 0, y: 1, blur: 2, color: SKEUOMORPHIC_COLORS.shadowLight }], strong: [{ x: 0, y: 2, blur: 4, color: SKEUOMORPHIC_COLORS.shadowMedium }]}, }; // Gradients - Rich, multi-stop gradients const SKEUOMORPHIC_GRADIENTS = { button: { primary: [ { offset: 0, color: SKEUOMORPHIC_COLORS.primaryLight }, { offset: 0.5, color: SKEUOMORPHIC_COLORS.primary }, { offset: 1, color: SKEUOMORPHIC_COLORS.primaryDark }, ], secondary: [ { offset: 0, color: SKEUOMORPHIC_COLORS.secondaryLight }, { offset: 0.5, color: SKEUOMORPHIC_COLORS.secondary }, { offset: 1, color: SKEUOMORPHIC_COLORS.secondaryDark }, ], pressed: [ { offset: 0, color: SKEUOMORPHIC_COLORS.primaryDark }, { offset: 0.5, color: SKEUOMORPHIC_COLORS.primary }, { offset: 1, color: SKEUOMORPHIC_COLORS.primaryLight }, ], }, surface: [ { offset: 0, color: SKEUOMORPHIC_COLORS.surface }, { offset: 0.5, color: SKEUOMORPHIC_COLORS.surfaceVariant }, { offset: 1, color: SKEUOMORPHIC_COLORS.surface }, ], input: [ { offset: 0, color: SKEUOMORPHIC_COLORS.surfaceVariant }, { offset: 1, color: SKEUOMORPHIC_COLORS.surface }, ], card: [ { offset: 0, color: SKEUOMORPHIC_COLORS.surface }, { offset: 0.3, color: SKEUOMORPHIC_COLORS.surfaceVariant }, { offset: 0.7, color: SKEUOMORPHIC_COLORS.surfaceVariant }, { offset: 1, color: SKEUOMORPHIC_COLORS.surface }, ], }; // Typography - Enhanced text styles with realistic effects const SKEUOMORPHIC_TYPOGRAPHY = { h1: { fontSize: 32, fontWeight: 'bold', lineHeight: 40, letterSpacing: -0.5, textShadow: SKEUOMORPHIC_SHADOWS.text.strong, }, h2: { fontSize: 28, fontWeight: 'bold', lineHeight: 36, letterSpacing: -0.25, textShadow: SKEUOMORPHIC_SHADOWS.text.strong, }, h3: { fontSize: 24, fontWeight: '600', lineHeight: 32, letterSpacing: 0, textShadow: SKEUOMORPHIC_SHADOWS.text.default, }, body: { fontSize: 16, fontWeight: 'normal', lineHeight: 24, letterSpacing: 0.15, }, caption: { fontSize: 12, fontWeight: 'normal', lineHeight: 16, letterSpacing: 0.4, }, button: { fontSize: 14, fontWeight: '600', lineHeight: 20, letterSpacing: 0.75, textTransform: 'uppercase', textShadow: SKEUOMORPHIC_SHADOWS.text.default, }}; // Spacing - Consistent spacing system const SKEUOMORPHIC_SPACING = { xs: 4, sm: 8, md: 16, lg: 24, xl: 32, xxl: 48, xxxl: 64, }; // Border Radius - Realistic rounded corners const SKEUOMORPHIC_BORDER_RADIUS = { none: 0, sm: 4, md: 8, lg: 12, xl: 16, xxl: 24, full: 9999, }; // Border Widths - Multiple border styles const SKEUOMORPHIC_BORDER_WIDTHS = { thin: 1, medium: 2}; // Light Theme const skeuomorphicLightTheme = { mode: 'light', design: 'skeuomorphic', colors: { primary: SKEUOMORPHIC_COLORS.primary, secondary: SKEUOMORPHIC_COLORS.secondary, background: SKEUOMORPHIC_COLORS.background, surface: SKEUOMORPHIC_COLORS.surface, error: SKEUOMORPHIC_COLORS.error, warning: SKEUOMORPHIC_COLORS.warning, success: SKEUOMORPHIC_COLORS.success, info: SKEUOMORPHIC_COLORS.info, text: SKEUOMORPHIC_COLORS.onSurface, textSecondary: SKEUOMORPHIC_COLORS.neutral, border: SKEUOMORPHIC_COLORS.border, lightShadow: SKEUOMORPHIC_COLORS.shadowLight, darkShadow: SKEUOMORPHIC_COLORS.shadowDark, }, shadows: { small: { shadowColor: SKEUOMORPHIC_COLORS.shadowLight, shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.08, shadowRadius: 4 }, medium: { shadowColor: SKEUOMORPHIC_COLORS.shadowMedium, shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.16, shadowRadius: 8 }, large: { shadowColor: SKEUOMORPHIC_COLORS.shadowDark, shadowOffset: { width: 0, height: 8 }, shadowOpacity: 0.24, shadowRadius: 16 }, }, spacing: SKEUOMORPHIC_SPACING, typography: { h1: SKEUOMORPHIC_TYPOGRAPHY.h1, h2: SKEUOMORPHIC_TYPOGRAPHY.h2, h3: SKEUOMORPHIC_TYPOGRAPHY.h3, body: SKEUOMORPHIC_TYPOGRAPHY.body, button: SKEUOMORPHIC_TYPOGRAPHY.button, caption: SKEUOMORPHIC_TYPOGRAPHY.caption, }, shape: { borderRadius: SKEUOMORPHIC_BORDER_RADIUS, }, }; const glassmorphicLightTheme = { mode: 'light', design: 'glassmorphic', colors: { primary: '#007AFF', secondary: '#5856D6', background: 'rgba(255, 255, 255, 0.1)', surface: 'rgba(255, 255, 255, 0.2)', text: '#1D1D1F', textSecondary: 'rgba(29, 29, 31, 0.7)', border: 'rgba(255, 255, 255, 0.3)', error: '#FF3B30', success: '#34C759', warning: '#FF9500', info: '#5856D6', lightShadow: 'rgba(255, 255, 255, 0.8)', darkShadow: 'rgba(0, 0, 0, 0.1)', }, shadows: { small: { shadowColor: 'rgba(0, 0, 0, 0.1)', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 8, elevation: 2, }, medium: { shadowColor: 'rgba(0, 0, 0, 0.15)', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.15, shadowRadius: 16, elevation: 4, }, large: { shadowColor: 'rgba(0, 0, 0, 0.2)', shadowOffset: { width: 0, height: 8 }, shadowOpacity: 0.2, shadowRadius: 24, elevation: 8, }, }, spacing: { xs: 4, sm: 8, md: 16, lg: 24, xl: 32, }, typography: { h1: { fontSize: 34, fontWeight: 'bold', letterSpacing: 0.37, color: 'rgba(29, 29, 31, 0.9)', }, h2: { fontSize: 28, fontWeight: '600', letterSpacing: 0.35, color: 'rgba(29, 29, 31, 0.9)', }, h3: { fontSize: 22, fontWeight: '600', letterSpacing: 0.35, color: 'rgba(29, 29, 31, 0.9)', }, body: { fontSize: 17, fontWeight: 'normal', letterSpacing: -0.41, color: 'rgba(29, 29, 31, 0.8)', }, button: { fontSize: 17, fontWeight: '600', letterSpacing: -0.41, color: 'rgba(29, 29, 31, 0.9)', }, caption: { fontSize: 12, fontWeight: 'normal', letterSpacing: 0, color: 'rgba(29, 29, 31, 0.6)', }, }, shape: { borderRadius: { sm: 8, md: 12, lg: 16, full: 9999, }, }, }; const glassmorphicDarkTheme = { mode: 'dark', design: 'glassmorphic', colors: { primary: '#0A84FF', secondary: '#5E5CE6', background: 'rgba(0, 0, 0, 0.1)', surface: 'rgba(255, 255, 255, 0.1)', text: '#FFFFFF', textSecondary: 'rgba(255, 255, 255, 0.7)', border: 'rgba(255, 255, 255, 0.2)', error: '#FF453A', success: '#32D74B', warning: '#FF9F0A', info: '#5E5CE6', lightShadow: 'rgba(255, 255, 255, 0.1)', darkShadow: 'rgba(0, 0, 0, 0.3)', }, shadows: { small: { shadowColor: 'rgba(0, 0, 0, 0.3)', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.3, shadowRadius: 8, elevation: 2, }, medium: { shadowColor: 'rgba(0, 0, 0, 0.4)', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.4, shadowRadius: 16, elevation: 4, }, large: { shadowColor: 'rgba(0, 0, 0, 0.5)', shadowOffset: { width: 0, height: 8 }, shadowOpacity: 0.5, shadowRadius: 24, elevation: 8, }, }, spacing: { xs: 4, sm: 8, md: 16, lg: 24, xl: 32, }, typography: { h1: { fontSize: 34, fontWeight: 'bold', letterSpacing: 0.37, color: 'rgba(255, 255, 255, 0.95)', }, h2: { fontSize: 28, fontWeight: '600', letterSpacing: 0.35, color: 'rgba(255, 255, 255, 0.95)', }, h3: { fontSize: 22, fontWeight: '600', letterSpacing: 0.35, color: 'rgba(255, 255, 255, 0.95)', }, body: { fontSize: 17, fontWeight: 'normal', letterSpacing: -0.41, color: 'rgba(255, 255, 255, 0.85)', }, button: { fontSize: 17, fontWeight: '600', letterSpacing: -0.41, color: 'rgba(255, 255, 255, 0.95)', }, caption: { fontSize: 12, fontWeight: 'normal', letterSpacing: 0, color: 'rgba(255, 255, 255, 0.6)', }, }, shape: { borderRadius: { sm: 8, md: 12, lg: 16, full: 9999, }, }, }; /** * Accessibility utilities for color contrast validation and WCAG compliance */ /** * Convert hex color to RGB values */ function hexToRgb(hex) { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16), } : null; } /** * Calculate relative luminance of a color * Based on WCAG 2.1 specification */ function getRelativeLuminance(r, g, b) { const [rs, gs, bs] = [r, g, b].map((c) => { const sRGB = c / 255; return sRGB <= 0.03928 ? sRGB / 12.92 : Math.pow((sRGB + 0.055) / 1.055, 2.4); }); return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs; } /** * Calculate contrast ratio between two colors * Returns a value between 1 and 21 */ function getContrastRatio(color1, color2) { const rgb1 = hexToRgb(color1); const rgb2 = hexToRgb(color2); if (!rgb1 || !rgb2) { throw new Error('Invalid color format. Please use hex colors.'); } const lum1 = getRelativeLuminance(rgb1.r, rgb1.g, rgb1.b); const lum2 = getRelativeLuminance(rgb2.r, rgb2.g, rgb2.b); const brightest = Math.max(lum1, lum2); const darkest = Math.min(lum1, lum2); return (brightest + 0.05) / (darkest + 0.05); } /** * Check if color combination meets WCAG accessibility standards */ function checkColorContrast(foreground, background, fontSize = 'normal') { const ratio = getContrastRatio(foreground, background); // WCAG AA requirements: 4.5:1 for normal text, 3:1 for large text // WCAG AAA requirements: 7:1 for normal text, 4.5:1 for large text const aaThreshold = fontSize === 'large' ? 3 : 4.5; const aaaThreshold = fontSize === 'large' ? 4.5 : 7; const wcagAA = ratio >= aaThreshold; const wcagAAA = ratio >= aaaThreshold; let level; if (wcagAAA) { level = 'aaa'; } else if (wcagAA) { level = 'aa'; } else { level = 'fail'; } return { ratio: Math.round(ratio * 100) / 100, wcagAA, wcagAAA, level, }; } /** * Validate all color combinations in a theme */ function validateThemeAccessibility(colors, backgroundColors = ['#FFFFFF', '#000000']) { const results = {}; Object.entries(colors).forEach(([colorName, colorValue]) => { results[colorName] = backgroundColors.map((bg) => checkColorContrast(colorValue, bg)); }); return results; } /** * Theme validation utilities and schemas for compile-time and runtime validation */ /** * Theme validation errors */ class ThemeValidationError extends Error { constructor(message, field, value) { super(`Theme validation failed for '${field}': ${message}`); this.field = field; this.value = value; this.name = 'ThemeValidationError'; } } /** * Validation rules for theme properties */ const THEME_VALIDATION_RULES = { colors: { required: ['primary', 'secondary', 'background', 'surface', 'text', 'error'], hexPattern: /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/, contrastRequirements: { 'text-background': 4.5, 'text-surface': 4.5, 'primary-background': 3.0, 'secondary-background': 3.0, }, }, shadows: { required: ['small', 'medium', 'large']}, spacing: { required: ['xs', 'sm', 'md', 'lg', 'xl']}, typography: { required: ['h1', 'h2', 'h3', 'body', 'button', 'caption']}, shape: { required: ['borderRadius'], borderRadius: { required: ['sm', 'md', 'lg', 'full'], min: 0, }, }, }; /** * Validate theme colors structure and format */ function validateThemeColors(colors) { const { required, hexPattern } = THEME_VALIDATION_RULES.colors; // Check required colors for (const requiredColor of required) { if (!(requiredColor in colors)) { throw new ThemeValidationError(`Missing required color '${requiredColor}'`, 'colors', colors); } } // Validate hex format for all colors Object.entries(colors).forEach(([colorName, colorValue]) => { if (!hexPattern.test(colorValue)) { throw new ThemeValidationError(`Invalid hex color format '${colorValue}'`, `colors.${colorName}`, colorValue); } }); } /** * Validate theme shadows structure */ function validateThemeShadows(shadows) { const { required } = THEME_VALIDATION_RULES.shadows; for (const requiredShadow of required) { if (!(requiredShadow in shadows)) { throw new ThemeValidationError(`Missing required shadow '${requiredShadow}'`, 'shadows', shadows); } } } /** * Validate theme spacing structure */ function validateThemeSpacing(spacing) { const { required } = THEME_VALIDATION_RULES.spacing; for (const requiredSpacing of required) { if (!(requiredSpacing in spacing)) { throw new ThemeValidationError(`Missing required spacing '${requiredSpacing}'`, 'spacing', spacing); } if (typeof spacing[requiredSpacing] !== 'number' || spacing[requiredSpacing] < 0) { throw new ThemeValidationError(`Invalid spacing value '${spacing[requiredSpacing]}'. Must be a positive number.`, `spacing.${requiredSpacing}`, spacing[requiredSpacing]); } } } /** * Validate theme typography structure */ function validateThemeTypography(typography) { const { required } = THEME_VALIDATION_RULES.typography; for (const requiredTypo of required) { if (!(requiredTypo in typography)) { throw new ThemeValidationError(`Missing required typography property '${requiredTypo}'`, 'typography', typography); } } } /** * Validate theme shape structure */ function validateThemeShape(shape) { const { required, borderRadius } = THEME_VALIDATION_RULES.shape; for (const requiredProp of required) { if (!(requiredProp in shape)) { throw new ThemeValidationError(`Missing required shape property '${requiredProp}'`, 'shape', shape); } } // Validate borderRadius structure if ('borderRadius' in shape) { const borderRadiusObj = shape.borderRadius; if (!borderRadiusObj || typeof borderRadiusObj !== 'object') { throw new ThemeValidationError('borderRadius must be an object', 'shape.borderRadius', borderRadiusObj); } const borderRadiusRecord = borderRadiusObj; for (const requiredSize of borderRadius.required) { if (!(requiredSize in borderRadiusRecord)) { throw new ThemeValidationError(`Missing required borderRadius size '${requiredSize}'`, 'shape.borderRadius', borderRadiusRecord); } const value = borderRadiusRecord[requiredSize]; if (typeof value !== 'number' || value < borderRadius.min) { throw new ThemeValidationError(`Invalid borderRadius value '${value}'. Must be a non-negative number.`, `shape.borderRadius.${requiredSize}`, value); } } } } /** * Validate accessibility requirements for theme colors */ function validateThemeColorAccessibility(colors) { const { contrastRequirements } = THEME_VALIDATION_RULES.colors; const colorsRecord = colors; Object.entries(contrastRequirements).forEach(([pair, minRatio]) => { var _a; const [foreground, background] = pair.split('-'); if (colorsRecord[foreground] && colorsRecord[background]) { const accessibilityResults = validateThemeAccessibility({ [foreground]: colorsRecord[foreground], }, [colorsRecord[background]]); const result = (_a = accessibilityResults[foreground]) === null || _a === void 0 ? void 0 : _a[0]; if (result && result.ratio < minRatio) { throw new ThemeValidationError(`Insufficient color contrast between ${foreground} and ${background}. ` + `Got ${result.ratio}:1, required ${minRatio}:1`, `colors.${pair}`, { foreground: colorsRecord[foreground], background: colorsRecord[background] }); } } }); } /** * Comprehensive theme validation */ function validateTheme(theme) { try { // Validate structure validateThemeColors(theme.colors); validateThemeShadows(theme.shadows); validateThemeSpacing(theme.spacing); validateThemeTypography(theme.typography); validateThemeShape(theme.shape); // Validate accessibility (optional - can be disabled for development) if (process.env.NODE_ENV !== 'development' || process.env.VALIDATE_ACCESSIBILITY === 'true') { validateThemeColorAccessibility(theme.colors); } return theme; } catch (error) { if (error instanceof ThemeValidationError) { console.error('Theme Validation Error:', error.message); throw error; } throw new ThemeValidationError('Unknown validation error', 'theme', theme); } } // Retro Light Theme - 80s Neon Inspired const retroLightTheme = { mode: 'light', design: 'retro', colors: { primary: '#FF6B9D', // Hot pink secondary: '#00F5FF', // Electric cyan background: '#1A1A2E', // Dark navy surface: '#16213E', // Darker blue text: '#EEEEFF', // Off-white textSecondary: '#B8B8FF', // Light purple border: '#FF6B9D', // Hot pink border error: '#FF073A', // Neon red success: '#39FF14', // Electric green warning: '#FFFF00', // Electric yellow info: '#00F5FF', // Electric cyan lightShadow: '#FF6B9D40', // Pink glow darkShadow: '#00000080', // Dark shadow }, shadows: { small: { shadowColor: '#FF6B9D', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.6, shadowRadius: 4.0, elevation: 3, }, medium: { shadowColor: '#00F5FF', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.7, shadowRadius: 8.0, elevation: 6, }, large: { shadowColor: '#FF6B9D', shadowOffset: { width: 0, height: 8 }, shadowOpacity: 0.8, shadowRadius: 12.0, elevation: 10, }, }, spacing: { xs: 6, sm: 12, md: 20, lg: 28, xl: 36, }, typography: { h1: { fontSize: 36, fontWeight: 'bold', letterSpacing: 2.0, textTransform: 'uppercase', }, h2: { fontSize: 30, fontWeight: 'bold', letterSpacing: 1.5, textTransform: 'uppercase', }, h3: { fontSize: 24, fontWeight: '700', letterSpacing: 1.2, textTransform: 'uppercase', }, body: { fontSize: 16, fontWeight: 'normal', letterSpacing: 0.5, }, button: { fontSize: 18, fontWeight: 'bold', letterSpacing: 1.0, textTransform: 'uppercase', }, caption: { fontSize: 12, fontWeight: 'normal', letterSpacing: 0.8, textTransform: 'uppercase', }, }, shape: { borderRadius: { sm: 2, md: 4, lg: 8, full: 0, // Sharp corners for retro feel }, }, }; // Validate retro themes for accessibility and consistency function validateRetroTheme(theme, themeName) { try { validateTheme(theme); console.log(`✅ ${themeName} passed validation`); return theme; } catch (error) { if (error instanceof ThemeValidationError) { console.warn(`⚠️ ${themeName} validation issues:`, error.message); // Return theme with warnings but don't throw return theme; } throw error; } } // Export validated themes validateRetroTheme(retroLightTheme, 'Retro Light Theme'); // Accessibility-improved color palettes const ACCESSIBLE_RETRO_PALETTES = { neon80s: { primary: '#FF1493', // Deep pink - better contrast secondary: '#00CED1', // Dark turquoise - better contrast accent: '#32CD32', // Lime green - good contrast background: '#0A0A0A', // Darker for better contrast surface: '#1A1A1A', // Darker surface text: '#FFFFFF', // Pure white for maximum contrast textSecondary: '#CCCCCC', // Light gray with good contrast }, pastel90s: { primary: '#8B4B8B', // Darker plum for better contrast secondary: '#4B8B4B', // Darker green for better contrast accent: '#8B4B8B', // Consistent with primary background: '#F8F8F8', // Slightly darker cream surface: '#F0F0F0', // Light gray text: '#2F2F2F', // Dark gray for good contrast textSecondary: '#5F5F5F', // Medium gray }, grunge90s: { primary: '#9932CC', // Darker orchid for better contrast secondary: '#FF6347', // Tomato - better contrast than orange red accent: '#228B22', // Forest green - better contrast background: '#000000', // Pure black surface: '#1C1C1C', // Dark gray text: '#FFFFFF', // Pure white textSecondary: '#CCCCCC', // Light gray }, vintage70s: { primary: '#8B4513', // Saddle brown - good contrast secondary: '#A0522D', // Sienna - complementary accent: '#B8860B', // Dark goldenrod - better contrast background: '#FFF8DC', // Cornsilk - light background surface: '#F5DEB3', // Wheat - slightly darker text: '#2F2F2F', // Dark gray textSecondary: '#5F5F5F', // Medium gray }, terminal: { primary: '#00FF00', // Bright green - classic terminal secondary: '#FFFF00', // Bright yellow accent: '#00FFFF', // Bright cyan background: '#000000', // Pure black surface: '#0A0A0A', // Very dark gray text: '#00FF00', // Green text textSecondary: '#00CC00', // Darker green