UNPKG

react-theme-system

Version:

A comprehensive React theme management system that enforces consistency, supports dark/light mode, and eliminates hardcoded styles

185 lines (184 loc) 7.62 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.themeValidator = exports.createThemeValidator = exports.validateThemeConfig = exports.validateTheme = void 0; // Required theme properties const REQUIRED_COLOR_PROPERTIES = [ 'primary', 'secondary', 'accent', 'background', 'surface', 'text.primary', 'text.secondary', 'text.disabled', 'border', 'error', 'warning', 'success', 'info' ]; const REQUIRED_SPACING_PROPERTIES = [ 'xs', 'sm', 'md', 'lg', 'xl', 'xxl', 'scale' ]; const REQUIRED_TYPOGRAPHY_PROPERTIES = [ 'fontFamily.primary', 'fontFamily.secondary', 'fontFamily.mono', 'fontSize.xs', 'fontSize.sm', 'fontSize.base', 'fontSize.lg', 'fontSize.xl', 'fontSize.2xl', 'fontSize.3xl', 'fontSize.4xl', 'fontWeight.light', 'fontWeight.normal', 'fontWeight.medium', 'fontWeight.semibold', 'fontWeight.bold', 'lineHeight.tight', 'lineHeight.normal', 'lineHeight.relaxed' ]; // Helper function to get nested object value const getNestedValue = (obj, path) => { return path.split('.').reduce((current, key) => current?.[key], obj); }; // Validate color values const validateColor = (color) => { if (!color || typeof color !== 'string') return false; // Check for valid CSS color formats const colorRegex = /^(#([0-9A-F]{3}){1,2}|rgb\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*\)|rgba\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*,\s*[\d.]+\s*\)|hsl\(\s*\d+\s*,\s*\d+%\s*,\s*\d+%\s*\)|hsla\(\s*\d+\s*,\s*\d+%\s*,\s*\d+%\s*,\s*[\d.]+\s*\)|transparent|currentColor|inherit|initial|unset)$/i; return colorRegex.test(color); }; // Validate spacing values const validateSpacing = (spacing) => { if (!spacing || typeof spacing !== 'string') return false; // Check for valid CSS length units const spacingRegex = /^(\d+(\.\d+)?(px|rem|em|vh|vw|%|ch|ex|in|cm|mm|pt|pc)|0)$/; return spacingRegex.test(spacing); }; // Validate typography values const validateTypography = (value) => { if (typeof value === 'number') { return value >= 100 && value <= 900; // Valid font-weight range } if (typeof value === 'string') { // Check for valid CSS font values const fontRegex = /^([a-zA-Z\s,]+|inherit|initial|unset)$/; const sizeRegex = /^(\d+(\.\d+)?(px|rem|em|vh|vw|%|ch|ex|in|cm|mm|pt|pc)|0)$/; const lineHeightRegex = /^(\d+(\.\d+)?|normal|inherit|initial|unset)$/; return fontRegex.test(value) || sizeRegex.test(value) || lineHeightRegex.test(value); } return false; }; // Validate individual theme const validateTheme = (theme, themeName) => { const errors = []; const warnings = []; // Validate colors REQUIRED_COLOR_PROPERTIES.forEach(prop => { const value = getNestedValue(theme, prop); if (!value) { errors.push(`Missing required color property: ${prop} in ${themeName} theme`); } else if (!validateColor(value)) { errors.push(`Invalid color value for ${prop} in ${themeName} theme: ${value}`); } }); // Validate spacing REQUIRED_SPACING_PROPERTIES.forEach(prop => { const value = getNestedValue(theme, prop); if (!value) { errors.push(`Missing required spacing property: ${prop} in ${themeName} theme`); } else if (prop !== 'scale' && !validateSpacing(value)) { errors.push(`Invalid spacing value for ${prop} in ${themeName} theme: ${value}`); } else if (prop === 'scale' && typeof value !== 'function') { errors.push(`Spacing scale must be a function in ${themeName} theme`); } }); // Validate typography REQUIRED_TYPOGRAPHY_PROPERTIES.forEach(prop => { const value = getNestedValue(theme, prop); if (!value) { errors.push(`Missing required typography property: ${prop} in ${themeName} theme`); } else if (!validateTypography(value)) { warnings.push(`Potentially invalid typography value for ${prop} in ${themeName} theme: ${value}`); } }); // Validate shadows if (!theme.shadows || typeof theme.shadows !== 'object') { errors.push(`Missing or invalid shadows object in ${themeName} theme`); } // Validate border radius if (!theme.borderRadius || typeof theme.borderRadius !== 'object') { errors.push(`Missing or invalid borderRadius object in ${themeName} theme`); } // Validate breakpoints if (!theme.breakpoints || typeof theme.breakpoints !== 'object') { errors.push(`Missing or invalid breakpoints object in ${themeName} theme`); } // Validate transitions if (!theme.transitions || typeof theme.transitions !== 'object') { errors.push(`Missing or invalid transitions object in ${themeName} theme`); } // Validate z-index if (!theme.zIndex || typeof theme.zIndex !== 'object') { errors.push(`Missing or invalid zIndex object in ${themeName} theme`); } return { isValid: errors.length === 0, errors, warnings }; }; exports.validateTheme = validateTheme; // Validate theme configuration const validateThemeConfig = (config) => { const errors = []; const warnings = []; // Validate light theme if (!config.light) { errors.push('Missing light theme configuration'); } else { const lightValidation = (0, exports.validateTheme)(config.light, 'light'); errors.push(...lightValidation.errors); warnings.push(...lightValidation.warnings); } // Validate dark theme if (!config.dark) { errors.push('Missing dark theme configuration'); } else { const darkValidation = (0, exports.validateTheme)(config.dark, 'dark'); errors.push(...darkValidation.errors); warnings.push(...darkValidation.warnings); } // Check for theme consistency if (config.light && config.dark) { const lightKeys = Object.keys(config.light); const darkKeys = Object.keys(config.dark); const missingInDark = lightKeys.filter(key => !darkKeys.includes(key)); const missingInLight = darkKeys.filter(key => !lightKeys.includes(key)); if (missingInDark.length > 0) { warnings.push(`Dark theme missing properties: ${missingInDark.join(', ')}`); } if (missingInLight.length > 0) { warnings.push(`Light theme missing properties: ${missingInLight.join(', ')}`); } } return { isValid: errors.length === 0, errors, warnings }; }; exports.validateThemeConfig = validateThemeConfig; // Create a theme schema validator const createThemeValidator = (strict = false) => { return { validate: (config) => { const result = (0, exports.validateThemeConfig)(config); if (strict && result.warnings.length > 0) { result.errors.push(...result.warnings); result.warnings = []; result.isValid = result.errors.length === 0; } return result; }, validateTheme: (theme, themeName) => { const result = (0, exports.validateTheme)(theme, themeName); if (strict && result.warnings.length > 0) { result.errors.push(...result.warnings); result.warnings = []; result.isValid = result.errors.length === 0; } return result; } }; }; exports.createThemeValidator = createThemeValidator; // Export default validator exports.themeValidator = (0, exports.createThemeValidator)();