@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
JavaScript
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