@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,458 lines (1,442 loc) • 126 kB
JavaScript
'use strict';
var React = require('react');
var jsxRuntime = require('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 = { ...style };
// 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;
}
// Ensure color values are strings
if (style.backgroundColor && typeof style.backgroundColor === 'object') {
cssStyle.backgroundColor = String(style.backgroundColor);
}
if (style.color && typeof style.color === 'object') {
cssStyle.color = String(style.color);
}
return cssStyle;
};
// Style processing helper
const processStyle = (style) => {
if (Array.isArray(style)) {
return style.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;
}
},
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: {
...processedStyle,
cursor: disabled ? 'not-allowed' : 'pointer',
opacity: disabled ? 0.6 : 1,
border: 'none',
background: 'transparent',
padding: 0,
userSelect: 'none',
},
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,
},
},
};
const ThemeContext = React.createContext(undefined);
const ThemeProvider = ({ children }) => {
const [currentTheme, setCurrentTheme] = React.useState(flatLightTheme);
const setMode = React.useCallback((mode) => {
setCurrentTheme(prevTheme => {
// Here we'll implement the logic to switch between light/dark variants
// of the current design theme
return prevTheme;
});
}, []);
const setDesign = React.useCallback((design) => {
setCurrentTheme(prevTheme => {
const mode = prevTheme.mode;
switch (design) {
case 'neumorphic':
return { ...neumorphicLightTheme, mode };
case 'skeuomorphic':
case 'material':
case 'simplistic':
// Temporarily fall back to flat theme for unimplemented designs
return { ...flatLightTheme, mode };
default:
return { ...flatLightTheme, mode };
}
});
}, []);
return (jsxRuntime.jsx(ThemeContext.Provider, { value: { theme: currentTheme, setMode, setDesign }, children: children }));
};
const useTheme = () => {
const context = React.useContext(ThemeContext);
if (context === undefined) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
};
const NEUMORPHIC_COLORS = {
background: '#fff',
darkShadow: '#cacaca',
lightShadow: '#f6f6f6',
text: '#2d2d2d',
primary: '#4A90E2',
secondary: '#6c757d',
success: '#28a745',
danger: '#dc3545',
warning: '#ffc107',
info: '#17a2b8',
};
const NEUMORPHIC_CONFIG = {
distance: 10,
blur: 41,
intensity: 1,
borderRadius: 22,
};
const getNeumorphicStyles = ({ isPressed = false, customBackground, customBorderRadius, } = {}) => {
const baseStyles = [
{
backgroundColor: customBackground || NEUMORPHIC_COLORS.background,
borderWidth: 0,
borderRadius: customBorderRadius || NEUMORPHIC_CONFIG.borderRadius,
padding: 16,
},
];
if (isPressed) {
// Inset shadow effect when pressed
baseStyles.push({
boxShadow: `inset ${NEUMORPHIC_CONFIG.distance}px ${NEUMORPHIC_CONFIG.distance}px ${NEUMORPHIC_CONFIG.blur}px ${NEUMORPHIC_COLORS.darkShadow}, inset -${NEUMORPHIC_CONFIG.distance}px -${NEUMORPHIC_CONFIG.distance}px ${NEUMORPHIC_CONFIG.blur}px ${NEUMORPHIC_COLORS.lightShadow}`,
});
}
else {
// Raised effect when not pressed
baseStyles.push({
boxShadow: `${NEUMORPHIC_CONFIG.distance}px ${NEUMORPHIC_CONFIG.distance}px ${NEUMORPHIC_CONFIG.blur}px ${NEUMORPHIC_COLORS.darkShadow}, -${NEUMORPHIC_CONFIG.distance}px -${NEUMORPHIC_CONFIG.distance}px ${NEUMORPHIC_CONFIG.blur}px ${NEUMORPHIC_COLORS.lightShadow}`,
});
}
return baseStyles;
};
const useNeumorphicShadow = (options = {}) => {
const { theme } = useTheme();
const { size = 'medium', intensity = 1, inset = false, distance: customDistance, blur: customBlur, } = options;
return React.useMemo(() => {
if (theme.design !== 'neumorphic') {
return theme.shadows[size];
}
const distances = {
small: 3,
medium: 5,
large: 8,
};
const blurs = {
small: 6,
medium: 10,
large: 16,
};
const distance = customDistance || distances[size];
const blur = customBlur || blurs[size];
const spread = distance / 2;
if (Platform.OS === 'web') {
const lightShadow = theme.colors.lightShadow;
const darkShadow = theme.colors.darkShadow;
const lightOpacity = 1 * intensity;
const darkOpacity = 0.7 * intensity;
const insetStr = inset ? 'inset ' : '';
return {
backgroundColor: theme.colors.background,
borderRadius: theme.shape.borderRadius.md,
boxShadow: `
${insetStr}${-distance}px ${-distance}px ${blur}px ${spread}px rgba(${lightShadow}, ${lightOpacity}),
${insetStr}${distance}px ${distance}px ${blur}px ${spread}px rgba(${darkShadow}, ${darkOpacity})
`,
};
}
// React Native - We need to simulate the effect with a single shadow
// since React Native doesn't support multiple shadows
return {
backgroundColor: theme.colors.background,
borderRadius: theme.shape.borderRadius.md,
shadowColor: inset ? theme.colors.darkShadow : theme.colors.lightShadow,
shadowOffset: {
width: inset ? distance : -distance,
height: inset ? distance : -distance,
},
shadowOpacity: intensity * (inset ? 0.7 : 1),
shadowRadius: blur / 2,
elevation: distance,
};
}, [theme, size, intensity, inset, customDistance, customBlur]);
};
const Button = ({ children, variant = 'primary', size = 'medium', design, disabled, loading, onPress, style, textStyle, backgroundColor, textColor, }) => {
const { theme } = useTheme();
const [isPressed, setIsPressed] = React.useState(false);
const activeDesign = design || theme.design;
const getVariantStyles = () => {
// If design is neumorphic, use neumorphic styles
if (activeDesign === 'neumorphic') {
return {
container: {
backgroundColor: backgroundColor || '#fff',
borderRadius: 22,
...(isPressed
? {
boxShadow: 'inset 10px 10px 41px #cacaca, inset -10px -10px 41px #f6f6f6',
}
: {
boxShadow: '10px 10px 41px #cacaca, -10px -10px 41px #f6f6f6',
}),
opacity: disabled ? 0.6 : 1,
},
text: {
color: disabled ? '#9e9e9e' : textColor || '#2196f3',
...theme.typography.button,
},
};
}
// For other designs, use the standard flat styles with custom colors
switch (variant) {
case 'secondary':
return {
container: {
...styles$u.secondaryButton,
backgroundColor: backgroundColor || '#f50057',
},
text: {
...styles$u.secondaryText,
color: textColor || 'white',
},
};
case 'outline':
return {
container: {
...styles$u.outlineButton,
borderColor: backgroundColor || '#2196f3',
},
text: {
...styles$u.outlineText,
color: textColor || backgroundColor || '#2196f3',
},
};
case 'text':
return {
container: styles$u.textButton,
text: {
...styles$u.textButtonText,
color: textColor || '#2196f3',
},
};
default:
return {
container: {
...styles$u.primaryButton,
backgroundColor: backgroundColor || '#2196f3',
},
text: {
...styles$u.primaryText,
color: textColor || 'white',
},
};
}
};
const getSizeStyles = () => {
const sizes = {
small: {
paddingVertical: 8,
paddingHorizontal: 16,
borderRadius: 12,
},
medium: {
paddingVertical: 12,
paddingHorizontal: 24,
borderRadius: 16,
},
large: {
paddingVertical: 16,
paddingHorizontal: 32,
borderRadius: 20,
},
};
return sizes[size];
};
const variantStyles = getVariantStyles();
const sizeStyles = getSizeStyles();
return (jsxRuntime.jsx(TouchableOpacity, { style: [
styles$u.button,
variantStyles.container,
sizeStyles,
disabled && styles$u.disabledButton,
style,
], onPress: onPress, onPressIn: () => setIsPressed(true), onPressOut: () => setIsPressed(false), disabled: disabled || loading, activeOpacity: activeDesign === 'neumorphic' ? 1 : 0.7, children: loading ? (jsxRuntime.jsx(ActivityIndicator, { color: variant === 'primary' ? 'white' : '#666666' })) : (jsxRuntime.jsx(Text, { style: [styles$u.text, variantStyles.text, disabled && styles$u.disabledText, textStyle], children: children })) }));
};
const styles$u = StyleSheet.create({
button: {
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
},
primaryButton: {
backgroundColor: '#2196f3',
},
primaryText: {
color: 'white',
fontWeight: '600',
},
secondaryButton: {
backgroundColor: '#f50057',
},
secondaryText: {
color: 'white',
fontWeight: '600',
},
outlineButton: {
backgroundColor: 'transparent',
borderWidth: 2,
borderColor: '#2196f3',
},
outlineText: {
color: '#2196f3',
fontWeight: '600',
},
textButton: {
backgroundColor: 'transparent',
},
textButtonText: {
color: '#2196f3',
fontWeight: '600',
},
disabledButton: {
opacity: 0.6,
},
disabledText: {
color: '#999999',
},
text: {
fontSize: 16,
fontWeight: '600',
},
});
const Input = ({ style, label, error, design = 'flat', backgroundColor = NEUMORPHIC_COLORS.background, textColor = NEUMORPHIC_COLORS.text, isFocused = false, ...props }) => {
const [focused, setFocused] = React.useState(isFocused);
const getInputStyles = () => {
const baseStyles = [styles$t.input];
if (error) {
baseStyles.push(styles$t.inputError);
}
if (design === 'neumorphic') {
const neumorphicStyles = getNeumorphicStyles({
isPressed: focused,
customBackground: backgroundColor,
customBorderRadius: 12,
});
baseStyles.push(...neumorphicStyles);
baseStyles.push({
height: 40,
paddingHorizontal: 10,
color: textColor,
borderWidth: 0,
});
}
if (style) {
baseStyles.push(style);
}
return baseStyles;
};
const getLabelStyles = () => {
const labelStyles = [styles$t.label];
if (design === 'neumorphic') {
labelStyles.push({
color: textColor,
fontSize: 14,
fontWeight: '500',
marginBottom: 8,
textShadowColor: NEUMORPHIC_COLORS.lightShadow,
textShadowOffset: { width: 1, height: 1 },
textShadowRadius: 1,
});
}
return labelStyles;
};
return (jsxRuntime.jsxs(View, { style: styles$t.container, children: [label && jsxRuntime.jsx(Text, { style: getLabelStyles(), children: label }), jsxRuntime.jsx(TextInput, { style: getInputStyles(), placeholderTextColor: design === 'neumorphic' ? `${textColor}80` : undefined, onFocus: () => setFocused(true), onBlur: () => setFocused(false), ...props }), error && jsxRuntime.jsx(Text, { style: styles$t.error, children: error })] }));
};
const styles$t = StyleSheet.create({
container: {
gap: 4,
},
input: {
height: 40,
borderColor: '#ccc',
borderWidth: 1,
paddingHorizontal: 10,
borderRadius: 5,
},
label: {
fontSize: 14,
fontWeight: '500',
},
error: {
color: 'red',
fontSize: 12,
},
inputError: {
borderColor: 'red',
},
});
const TextArea = ({ style, design = 'flat', backgroundColor = NEUMORPHIC_COLORS.background, textColor = NEUMORPHIC_COLORS.text, ...props }) => {
const [isFocused, setIsFocused] = React.useState(false);
const getTextAreaStyles = () => {
const baseStyles = [styles$s.textArea];
if (design === 'neumorphic') {
const neumorphicStyles = getNeumorphicStyles({
isPressed: isFocused,
customBackground: backgroundColor,
customBorderRadius: 8,
});
baseStyles.push(...neumorphicStyles);
baseStyles.push({
backgroundColor,
borderWidth: 0,
});
}
if (style) {
baseStyles.push(style);
}
return baseStyles;
};
return (jsxRuntime.jsx(TextInput, { style: getTextAreaStyles(), multiline: true, onFocus: () => setIsFocused(true), onBlur: () => setIsFocused(false), placeholderTextColor: design === 'neumorphic' ? textColor + '80' : undefined, defaultValue: "", ...props, ...(design === 'neumorphic' && { selectionColor: textColor }) }));
};
const styles$s = StyleSheet.create({
textArea: {
height: 100,
paddingHorizontal: 16,
paddingVertical: 12,
borderRadius: 8,
textAlignVertical: 'top',
marginVertical: 8,
},
});
const Checkbox = ({ checked = false, onChange, label = 'Checkbox', style, disabled = false, design = 'flat', backgroundColor = NEUMORPHIC_COLORS.background, textColor = NEUMORPHIC_COLORS.text, }) => {
const [isChecked, setIsChecked] = React.useState(checked);
const [isPressed, setIsPressed] = React.useState(false);
const toggleCheckbox = () => {
if (disabled)
return;
const newChecked = !isChecked;
setIsChecked(newChecked);
if (onChange) {
onChange(newChecked);
}
};
const getCheckboxStyles = () => {
const baseStyles = [styles$r.checkbox];
if (design === 'neumorphic') {
const neumorphicStyles = getNeumorphicStyles({
isPressed: isPressed || isChecked,
customBackground: backgroundColor,
customBorderRadius: 6,
});
baseStyles.push(...neumorphicStyles);
baseStyles.push({
width: 24,
height: 24,
borderWidth: 0,
padding: 0,
});
}
if (disabled) {
baseStyles.push(styles$r.disabled);
}
return baseStyles;
};
const getCheckMarkStyles = () => {
const baseStyles = [styles$r.checked];
if (design === 'neumorphic') {
baseStyles.push({
width: 14,
height: 14,
backgroundColor: textColor,
borderRadius: 3,
});
}
return baseStyles;
};
return (jsxRuntime.jsxs(View, { style: [styles$r.container, style], children: [jsxRuntime.jsx(TouchableOpacity, { style: getCheckboxStyles(), onPress: toggleCheckbox, disabled: disabled, onPressIn: () => setIsPressed(true), onPressOut: () => setIsPressed(false), children: isChecked && jsxRuntime.jsx(View, { style: getCheckMarkStyles() }) }), jsxRuntime.jsx(Text, { style: [
styles$r.label,
disabled && styles$r.disabledText,
design === 'neumorphic' && {
color: textColor,
textShadowColor: NEUMORPHIC_COLORS.lightShadow,
textShadowOffset: { width: 1, height: 1 },
textShadowRadius: 1,
},
], children: label })] }));
};
const styles$r = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
marginVertical: 5,
},
checkbox: {
width: 20,
height: 20,
borderWidth: 1,
borderColor: '#000',
justifyContent: 'center',
alignItems: 'center',
marginRight: 10,
},
checked: {
width: 12,
height: 12,
backgroundColor: '#000',
},
label: {
fontSize: 16,
},
disabled: {
backgroundColor: '#ccc',
},
disabledText: {
color: '#ccc',
},
});
const RadioButton = ({ initialSelected = false, onToggle, label = 'Radio Button', style, disabled = false, design = 'flat', backgroundColor = NEUMORPHIC_COLORS.background, textColor = NEUMORPHIC_COLORS.text, }) => {
const [selected, setSelected] = React.useState(initialSelected);
const [isPressed, setIsPressed] = React.useState(false);
const toggleSelection = () => {
if (disabled)
return;
const newSelected = !selected;
setSelected(newSelected);
if (onToggle) {
onToggle(newSelected);
}
};
const getRadioStyles = () => {
const baseStyles = [styles$q.radio];
if (design === 'neumorphic') {
const neumorphicStyles = getNeumorphicStyles({
isPressed: isPressed || selected,
customBackground: backgroundColor,
customBorderRadius: 15,
});
baseStyles.push(...neumorphicStyles);
baseStyles.push({
width: 28,
height: 28,
borderWidth: 0,
padding: 0,
});
}
if (disabled) {
baseStyles.push(styles$q.disabled);
}
return baseStyles;
};
const getSelectedStyles = () => {
const baseStyles = [styles$q.selected];
if (design === 'neumorphic') {
baseStyles.push({
width: 14,
height: 14,
backgroundColor: textColor,
borderRadius: 7,
});
}
return baseStyles;
};
return (jsxRuntime.jsxs(View, { style: [styles$q.container, style], children: [jsxRuntime.jsx(TouchableOpacity, { style: getRadioStyles(), onPress: toggleSelection, disabled: disabled, onPressIn: () => setIsPressed(true), onPressOut: () => setIsPressed(false), children: selected && jsxRuntime.jsx(View, { style: getSelectedStyles() }) }), jsxRuntime.jsx(Text, { style: [
styles$q.label,
disabled && styles$q.disabledText,
design === 'neumorphic' && {
color: textColor,
textShadowColor: NEUMORPHIC_COLORS.lightShadow,
textShadowOffset: { width: 1, height: 1 },
textShadowRadius: 1,
},
], children: label })] }));
};
const styles$q = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
marginVertical: 5,
},
radio: {
width: 20,
height: 20,
borderRadius: 10,
borderWidth: 1,
borderColor: '#000',
justifyContent: 'center',
alignItems: 'center',
marginRight: 10,
},
selected: {
width: 12,
height: 12,
borderRadius: 6,
backgroundColor: '#000',
},
label: {
fontSize: 16,
},
disabled: {
backgroundColor: '#ccc',
},
disabledText: {
color: '#ccc',
},
});
const ToggleSwitch = ({ initialValue = false, onToggle, label = 'Toggle', style, disabled = false, design = 'flat', backgroundColor = NEUMORPHIC_COLORS.background, textColor = NEUMORPHIC_COLORS.text, activeColor = '#4CAF50', }) => {
const [value, setValue] = React.useState(initialValue);
const [isPressed, setIsPressed] = React.useState(false);
const translateX = React.useState(new Animated.Value(value ? 28 : 0))[0];
const toggleSwitch = () => {
if (disabled)
return;
const newValue = !value;
setValue(newValue);
Animated.spring(translateX, {
toValue: newValue ? 28 : 0,
useNativeDriver: true,
bounciness: 8,
}).start();
if (onToggle) {
onToggle(newValue);
}
};
const getToggleContainerStyles = () => {
const baseStyles = [styles$p.toggleContainer];
if (design === 'neumorphic') {
const neumorphicStyles = getNeumorphicStyles({
isPressed,
customBackground: backgroundColor,
customBorderRadius: 20,
});
baseStyles.push(...neumorphicStyles);
baseStyles.push({
width: 56,
height: 28,
padding: 2,
});
}
if (disabled) {
baseStyles.push(styles$p.disabled);
}
return baseStyles;
};
const getKnobStyles = () => {
const baseStyles = [styles$p.knob];
if (design === 'neumorphic') {
const knobNeumorphicStyles = getNeumorphicStyles({
isPressed: value,
customBackground: value ? activeColor : backgroundColor,
customBorderRadius: 12,
});
baseStyles.push(...knobNeumorphicStyles);
baseStyles.push({
width: 24,
height: 24,
margin: 0,
});
}
return baseStyles;
};
return (jsxRuntime.jsxs(View, { style: [styles$p.container, style], children: [jsxRuntime.jsx(Text, { style: [
styles$p.label,
disabled && styles$p.disabledText,
design === 'neumorphic' && {
color: textColor,
textShadowColor: NEUMORPHIC_COLORS.lightShadow,
textShadowOffset: { width: 1, height: 1 },
textShadowRadius: 1,
},
], children: label }), jsxRuntime.jsx(TouchableOpacity, { style: getToggleContainerStyles(), onPress: toggleSwitch, disabled: disabled, onPressIn: () => setIsPressed(true), onPressOut: () => setIsPressed(false), activeOpacity: 1, children: jsxRuntime.jsx(Animated.View, { style: [
getKnobStyles(),
{
transform: [{ translateX }],
},
] }) })] }));
};
const styles$p = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
marginVertical: 5,
},
toggleContainer: {
width: 50,
height: 24,
borderRadius: 12,
backgroundColor: '#E0E0E0',
justifyContent: 'center',
},
knob: {
width: 20,
height: 20,
borderRadius: 10,
backgroundColor: '#FFFFFF',
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.25,
shadowRadius: 3.84,
elevation: 5,
},
label: {
marginRight: 10,
fontSize: 16,
},
disabled: {
opacity: 0.5,
},
disabledText: {
color: '#ccc',
},
});
const CustomSlider = ({ value, onValueChange, minimumValue = 0, maximumValue = 100, minimumTrackTintColor = NEUMORPHIC_COLORS.primary, maximumTrackTintColor = NEUMORPHIC_COLORS.lightShadow, thumbTintColor = NEUMORPHIC_COLORS.primary, style, design = 'flat', backgroundColor = NEUMORPHIC_COLORS.background, width = 200, step, ...props }) => {
const getContainerStyles = () => {
const baseStyles = [styles$o.container];
if (design === 'neumorphic') {
const neumorphicStyles = getNeumorphicStyles({
isPressed: false,
customBackground: backgroundColor,
customBorderRadius: 8,
});
baseStyles.push(...neumorphicStyles);
baseStyles.push({
backgroundColor,
padding: 12,
});
}
if (style) {
baseStyles.push(style);
}
return baseStyles;
};
// Cross-platform slider using HTML input range
const WebSlider = () => (jsxRuntime.jsx("input", { type: "range", min: minimumValue, max: maximumValue, value: value, step: step, onChange: e => onValueChange(Number(e.target.value)), style: {
width,
height: 20,
background: maximumTrackTintColor,
outline: 'none',
opacity: 0.7,
transition: 'opacity 0.2s',
cursor: 'pointer',
borderRadius: '10px',
appearance: 'none',
WebkitAppearance: 'none',
}, ...props }));
return (jsxRuntime.jsx(View, { style: getContainerStyles(), children: jsxRuntime.jsx(WebSlider, {}) }));
};
const styles$o = StyleSheet.create({
container: {
marginVertical: 10,
alignItems: 'center',
},
});
/**
* A component for displaying text with consistent styling across the app.
* Implements Material Design typography scale with responsive sizing.
*
* @example
* ```tsx
* // Basic usage
* <Typography variant="h1">Hello World</Typography>
*
* // With neumorphic styling
* <Typography
* variant="h1"
* design="neumorphic"
* backgroundColor="#E0E5EC"
* >
* Neumorphic Heading
* </Typography>
*
* // With custom styling
* <Typography
* variant="body1"
* color="#007AFF"
* align="center"
* >
* Centered blue text
* </Typography>
*
* // Auto-sizing text
* <Typography
* adjustsFontSizeToFit
* numberOfLines={1}
* >
* This text will shrink to fit
* </Typography>
* ```
*/
const Typography = ({ variant = 'body1', children, style, color = '#000000', align = 'left', numberOfLines, adjustsFontSizeToFit = true, minimumFontScale = 0.7, design = 'flat', backgroundColor = NEUMORPHIC_COLORS.background, ...props }) => {
const variantStyles = {
h1: { fontSize: 32, fontWeight: '700', lineHeight: 40 },
h2: { fontSize: 28, fontWeight: '700', lineHeight: 36 },
h3: { fontSize: 24, fontWeight: '600', lineHeight: 32 },
h4: { fontSize: 20, fontWeight: '600', lineHeight: 28 },
h5: { fontSize: 18, fontWeight: '500', lineHeight: 26 },
h6: { fontSize: 16, fontWeight: '500', lineHeight: 24 },
subtitle1: { fontSize: 16, fontWeight: '400', lineHeight: 24 },
subtitle2: { fontSize: 14, fontWeight: '400', lineHeight: 22 },
body1: { fontSize: 16, fontWeight: '400', lineHeight: 24 },
body2: { fontSize: 14, fontWeight: '400', lineHeight: 22 },
caption: { fontSize: 12, fontWeight: '400', lineHeight: 20 },
overline: { fontSize: 10, fontWeight: '400', lineHeight: 16, textTransform: 'uppercase' },
};
const getTypographyStyles = () => {
const baseStyles = [
variantStyles[variant],
{ color, textAlign: align },
];
if (design === 'neumorphic') {
const neumorphicStyles = getNeumorphicStyles({
customBackground: backgroundColor,
customBorderRadius: 8,
});
baseStyles.push(...neumorphicStyles);
baseStyles.push({ padding: 8 });
}
if (style) {
baseStyles.push(style);
}
return baseStyles;
};
return (jsxRuntime.jsx(Text, { ...props, style: getTypographyStyles(), numberOfLines: numberOfLines, adjustsFontSizeToFit: adjustsFontSizeToFit, minimumFontScale: minimumFontScale, children: children }));
};
StyleSheet.create({
base: {
fontFamily: Platform.select({
ios: 'System',
android: 'Roboto',
default: 'System',
}),
},
h1: {
fontSize: 32,
fontWeight: '700',
lineHeight: 40,
letterSpacing: -0.5,
marginBottom: 8,
},
h2: {
fontSize: 28,
fontWeight: '700',
lineHeight: 36,
letterSpacing: -0.5,
marginBottom: 8,
},
h3: {
fontSize: 24,
fontWeight: '600',
lineHeight: 32,
marginBottom: 8,
},
h4: {
fontSize: 20,
fontWeight: '600',
lineHeight: 28,
marginBottom: 8,
},
h5: {
fontSize: 18,
fontWeight: '600',
lineHeight: 26,
marginBottom: 8,
},
h6: {
fontSize: 16,
fontWeight: '600',
lineHeight: 24,
marginBottom: 8,
},
subtitle1: {
fontSize: 16,
fontWeight: '500',
lineHeight: 24,
letterSpacing: 0.15,
marginBottom: 4,
},
subtitle2: {
fontSize: 14,
fontWeight: '500',
lineHeight: 22,
letterSpacing: 0.1,
marginBottom: 4,
},
body1: {
fontSize: 16,
fontWeight: '400',
lineHeight: 24,
letterSpacing: 0.15,
},
body2: {
fontSize: 14,
fontWeight: '400',
lineHeight: 22,
letterSpacing: 0.15,
},
caption: {
fontSize: 12,
fontWeight: '400',
lineHeight: 18,
letterSpacing: 0.4,
},
overline: {
fontSize: 10,
fontWeight: '500',
lineHeight: 16,
letterSpacing: 1.5,
textTransform: 'uppercase',
},
});
const Dropdown = ({ options, onSelect, label = 'Select', value, placeholder = 'Choose an option', style, dropdownStyle, optionStyle, labelStyle, textStyle, design = 'flat', backgroundColor = NEUMORPHIC_COLORS.background, textColor = NEUMORPHIC_COLORS.text, }) => {
const [isOpen, setIsOpen] = React.useState(false);
const [animation] = React.useState(new Animated.Value(0));
const [pressedOption, setPressedOption] = React.useState(null);
const toggleDropdown = () => {
const toValue = isOpen ? 0 : 1;
setIsOpen(!isOpen);
Animated.timing(animation, {
toValue,
duration: 200,
useNativeDriver: false,
}).start();
};
const optionsHeight = animation.interpolate({
inputRange: [0, 1],
outputRange: [0, Math.min(options.length * 48 + 16, 250)],
});
const getContainerStyles = () => {
const baseStyles = [styles$n.container];
if (style) {
baseStyles.push(style);
}
return baseStyles;
};
const getDropdownStyles = () => {
const baseStyles = [styles$n.dropdown];
if (design === 'neumorphic') {
const neumorphicStyles = getNeumorphicStyles({
isPressed: isOpen,
customBackground: backgroundColor,
customBorderRadius: 8,
});
baseStyles.push(...neumorphicStyles);
baseStyles.push({
borderWidth: 0,
});
}
if (dropdownStyle) {
baseStyles.push(dropdownStyle);
}
return baseStyles;
};
const getOptionsContainerStyles = () => {
const baseStyles = [styles$n.optionsContainer];
if (design === 'neumorphic') {
const neumorphicStyles = getN