@jonmatum/react-mfe-shell
Version:
Production-ready React micro frontend shell with atomic design system, shared components, and utilities for building scalable MFE applications
1 lines • 310 kB
Source Map (JSON)
{"version":3,"sources":["../src/index.ts","../src/utils/tokens.ts","../src/utils/theme.ts","../src/utils/form.ts","../src/utils/index.ts","../src/utils/typography.ts","../src/types/form.ts","../src/types/index.ts","../src/contexts/SettingsContext.tsx","../src/contexts/TypographyContext.tsx","../src/components/atoms/Button.tsx","../src/utils/componentUtils.ts","../src/components/atoms/LoadingSpinner.tsx","../src/components/atoms/Input.tsx","../src/components/atoms/Label.tsx","../src/components/atoms/Icon.tsx","../src/components/atoms/Badge.tsx","../src/components/atoms/Avatar.tsx","../src/components/atoms/Divider.tsx","../src/components/atoms/FeatureChip.tsx","../src/components/atoms/Text.tsx","../src/components/atoms/Heading.tsx","../src/components/atoms/Paragraph.tsx","../src/components/atoms/Code.tsx","../src/components/atoms/Switch.tsx","../src/components/molecules/Modal.tsx","../src/components/molecules/Card.tsx","../src/components/molecules/FormField.tsx","../src/components/molecules/SearchBox.tsx","../src/components/molecules/Select.tsx","../src/components/molecules/Checkbox.tsx","../src/components/molecules/Radio.tsx","../src/components/molecules/SwitchField.tsx","../src/components/molecules/Textarea.tsx","../src/components/molecules/FileUpload.tsx"],"sourcesContent":["/**\n * React MFE Shell - Main Entry Point\n * Exports all components, utilities, types, and design tokens\n */\n\n// =============================================================================\n// DESIGN TOKENS & THEME SYSTEM\n// =============================================================================\n\n// Complete design token system\nexport { default as tokens } from './utils/tokens';\nexport * from './utils/tokens';\n\n// Theme management utilities\nexport * from './utils/theme';\n\n// Typography utilities\nexport * from './utils/typography';\n\n// Legacy token exports (for backward compatibility)\nexport {\n colors,\n spacing,\n typography,\n borderRadius,\n shadows,\n breakpoints,\n transitions,\n zIndex,\n} from './utils/tokens';\n\n// =============================================================================\n// TYPES\n// =============================================================================\n\nexport * from './types';\nexport * from './types/polymorphic';\n\n// =============================================================================\n// UTILITIES\n// =============================================================================\n\nexport * from './utils';\n\n// =============================================================================\n// CONTEXTS & PROVIDERS\n// =============================================================================\n\nexport { SettingsProvider, useSettings } from './contexts/SettingsContext';\nexport {\n TypographyProvider,\n useTypography,\n useScaledTypography,\n} from './contexts/TypographyContext';\n\n// =============================================================================\n// COMPONENTS\n// =============================================================================\n\n// Atoms\nexport { default as Button } from './components/atoms/Button';\nexport { default as Input } from './components/atoms/Input';\nexport { default as Label } from './components/atoms/Label';\nexport { default as Icon } from './components/atoms/Icon';\nexport { default as Badge } from './components/atoms/Badge';\nexport { default as Avatar } from './components/atoms/Avatar';\nexport { default as Divider } from './components/atoms/Divider';\nexport { default as FeatureChip } from './components/atoms/FeatureChip';\nexport { default as Text } from './components/atoms/Text';\nexport { default as Heading } from './components/atoms/Heading';\nexport { default as Paragraph } from './components/atoms/Paragraph';\nexport { default as Code } from './components/atoms/Code';\nexport { default as LoadingSpinner } from './components/atoms/LoadingSpinner';\nexport { default as Switch } from './components/atoms/Switch';\n\n// Molecules\nexport { default as Modal } from './components/molecules/Modal';\nexport { default as Card } from './components/molecules/Card';\n\n// Form Molecules\nexport { default as FormField } from './components/molecules/FormField';\nexport { default as SearchBox } from './components/molecules/SearchBox';\nexport { default as Select } from './components/molecules/Select';\nexport { default as Checkbox } from './components/molecules/Checkbox';\nexport { default as Radio } from './components/molecules/Radio';\nexport { default as SwitchField } from './components/molecules/SwitchField';\nexport { default as Textarea } from './components/molecules/Textarea';\nexport { default as FileUpload } from './components/molecules/FileUpload';\n","/**\n * Design Token System\n * Single source of truth for all design values\n * Supports theming, accessibility, and Tailwind CSS integration\n */\n\n// =============================================================================\n// COLOR TOKENS\n// =============================================================================\n\n/**\n * Base color palette - semantic color definitions\n * All colors meet WCAG AA contrast requirements\n */\nexport const baseColors = {\n // Neutral colors\n white: '#ffffff',\n black: '#000000',\n\n // Gray scale (optimized for accessibility)\n gray: {\n 50: '#f9fafb',\n 100: '#f3f4f6',\n 200: '#e5e7eb',\n 300: '#d1d5db',\n 400: '#9ca3af',\n 500: '#6b7280',\n 600: '#4b5563',\n 700: '#374151',\n 800: '#1f2937',\n 900: '#111827',\n 950: '#030712',\n },\n\n // Primary brand colors\n blue: {\n 50: '#eff6ff',\n 100: '#dbeafe',\n 200: '#bfdbfe',\n 300: '#93c5fd',\n 400: '#60a5fa',\n 500: '#3b82f6',\n 600: '#2563eb',\n 700: '#1d4ed8',\n 800: '#1e40af',\n 900: '#1e3a8a',\n 950: '#172554',\n },\n\n // Success colors\n green: {\n 50: '#f0fdf4',\n 100: '#dcfce7',\n 200: '#bbf7d0',\n 300: '#86efac',\n 400: '#4ade80',\n 500: '#22c55e',\n 600: '#16a34a',\n 700: '#15803d',\n 800: '#166534',\n 900: '#14532d',\n 950: '#052e16',\n },\n\n // Warning colors\n yellow: {\n 50: '#fefce8',\n 100: '#fef3c7',\n 200: '#fde68a',\n 300: '#fcd34d',\n 400: '#fbbf24',\n 500: '#f59e0b',\n 600: '#d97706',\n 700: '#b45309',\n 800: '#92400e',\n 900: '#78350f',\n 950: '#451a03',\n },\n\n // Error colors\n red: {\n 50: '#fef2f2',\n 100: '#fee2e2',\n 200: '#fecaca',\n 300: '#fca5a5',\n 400: '#f87171',\n 500: '#ef4444',\n 600: '#dc2626',\n 700: '#b91c1c',\n 800: '#991b1b',\n 900: '#7f1d1d',\n 950: '#450a0a',\n },\n\n // Info colors\n cyan: {\n 50: '#ecfeff',\n 100: '#cffafe',\n 200: '#a5f3fc',\n 300: '#67e8f9',\n 400: '#22d3ee',\n 500: '#06b6d4',\n 600: '#0891b2',\n 700: '#0e7490',\n 800: '#155e75',\n 900: '#164e63',\n 950: '#083344',\n },\n} as const;\n\n/**\n * Semantic color tokens - mapped to base colors\n * These provide meaning and context to colors\n */\nexport const semanticColors = {\n primary: baseColors.blue,\n secondary: baseColors.gray,\n success: baseColors.green,\n warning: baseColors.yellow,\n error: baseColors.red,\n info: baseColors.cyan,\n} as const;\n\n/**\n * Theme-aware color system\n * Defines colors for light and dark themes\n */\nexport const themeColors = {\n light: {\n // Background colors\n background: {\n primary: baseColors.white,\n secondary: baseColors.gray[50],\n tertiary: baseColors.gray[100],\n },\n\n // Surface colors (cards, modals, etc.)\n surface: {\n primary: baseColors.white,\n secondary: baseColors.gray[50],\n tertiary: baseColors.gray[100],\n elevated: baseColors.white,\n },\n\n // Text colors\n text: {\n primary: baseColors.gray[900],\n secondary: baseColors.gray[600],\n tertiary: baseColors.gray[500],\n inverse: baseColors.white,\n disabled: baseColors.gray[400],\n },\n\n // Border colors\n border: {\n primary: baseColors.gray[200],\n secondary: baseColors.gray[300],\n tertiary: baseColors.gray[400],\n focus: semanticColors.primary[500],\n },\n\n // Interactive colors\n interactive: {\n primary: semanticColors.primary[600],\n 'primary-hover': semanticColors.primary[700],\n 'primary-active': semanticColors.primary[800],\n secondary: baseColors.gray[100],\n 'secondary-hover': baseColors.gray[200],\n 'secondary-active': baseColors.gray[300],\n },\n\n // Status colors\n status: {\n success: semanticColors.success[600],\n warning: semanticColors.warning[600],\n error: semanticColors.error[600],\n info: semanticColors.info[600],\n },\n },\n\n dark: {\n // Background colors\n background: {\n primary: baseColors.gray[900],\n secondary: baseColors.gray[800],\n tertiary: baseColors.gray[700],\n },\n\n // Surface colors\n surface: {\n primary: baseColors.gray[800],\n secondary: baseColors.gray[700],\n tertiary: baseColors.gray[600],\n elevated: baseColors.gray[700],\n },\n\n // Text colors\n text: {\n primary: baseColors.gray[100],\n secondary: baseColors.gray[300],\n tertiary: baseColors.gray[400],\n inverse: baseColors.gray[900],\n disabled: baseColors.gray[500],\n },\n\n // Border colors\n border: {\n primary: baseColors.gray[700],\n secondary: baseColors.gray[600],\n tertiary: baseColors.gray[500],\n focus: semanticColors.primary[400],\n },\n\n // Interactive colors\n interactive: {\n primary: semanticColors.primary[500],\n 'primary-hover': semanticColors.primary[400],\n 'primary-active': semanticColors.primary[300],\n secondary: baseColors.gray[700],\n 'secondary-hover': baseColors.gray[600],\n 'secondary-active': baseColors.gray[500],\n },\n\n // Status colors\n status: {\n success: semanticColors.success[500],\n warning: semanticColors.warning[500],\n error: semanticColors.error[500],\n info: semanticColors.info[500],\n },\n },\n} as const;\n\n// =============================================================================\n// TYPOGRAPHY TOKENS\n// =============================================================================\n\n/**\n * Font family tokens\n */\nexport const fontFamily = {\n sans: [\n 'Inter',\n '-apple-system',\n 'BlinkMacSystemFont',\n '\"Segoe UI\"',\n 'Roboto',\n '\"Helvetica Neue\"',\n 'Arial',\n 'sans-serif',\n ],\n mono: [\n '\"JetBrains Mono\"',\n 'Menlo',\n 'Monaco',\n 'Consolas',\n '\"Liberation Mono\"',\n '\"Courier New\"',\n 'monospace',\n ],\n} as const;\n\n/**\n * Font size tokens with line heights\n * Following a modular scale for consistency\n */\nexport const fontSize = {\n xs: ['0.75rem', { lineHeight: '1rem' }], // 12px\n sm: ['0.875rem', { lineHeight: '1.25rem' }], // 14px\n base: ['1rem', { lineHeight: '1.5rem' }], // 16px\n lg: ['1.125rem', { lineHeight: '1.75rem' }], // 18px\n xl: ['1.25rem', { lineHeight: '1.75rem' }], // 20px\n '2xl': ['1.5rem', { lineHeight: '2rem' }], // 24px\n '3xl': ['1.875rem', { lineHeight: '2.25rem' }], // 30px\n '4xl': ['2.25rem', { lineHeight: '2.5rem' }], // 36px\n '5xl': ['3rem', { lineHeight: '1' }], // 48px\n '6xl': ['3.75rem', { lineHeight: '1' }], // 60px\n '7xl': ['4.5rem', { lineHeight: '1' }], // 72px\n '8xl': ['6rem', { lineHeight: '1' }], // 96px\n '9xl': ['8rem', { lineHeight: '1' }], // 128px\n} as const;\n\n/**\n * Font weight tokens\n */\nexport const fontWeight = {\n thin: '100',\n extralight: '200',\n light: '300',\n normal: '400',\n medium: '500',\n semibold: '600',\n bold: '700',\n extrabold: '800',\n black: '900',\n} as const;\n\n/**\n * Letter spacing tokens\n */\nexport const letterSpacing = {\n tighter: '-0.05em',\n tight: '-0.025em',\n normal: '0em',\n wide: '0.025em',\n wider: '0.05em',\n widest: '0.1em',\n} as const;\n\n// =============================================================================\n// SPACING TOKENS\n// =============================================================================\n\n/**\n * Spacing scale based on 0.25rem (4px) increments\n * Provides consistent spacing throughout the design system\n */\nexport const spacing = {\n 0: '0px',\n px: '1px',\n 0.5: '0.125rem', // 2px\n 1: '0.25rem', // 4px\n 1.5: '0.375rem', // 6px\n 2: '0.5rem', // 8px\n 2.5: '0.625rem', // 10px\n 3: '0.75rem', // 12px\n 3.5: '0.875rem', // 14px\n 4: '1rem', // 16px\n 5: '1.25rem', // 20px\n 6: '1.5rem', // 24px\n 7: '1.75rem', // 28px\n 8: '2rem', // 32px\n 9: '2.25rem', // 36px\n 10: '2.5rem', // 40px\n 11: '2.75rem', // 44px\n 12: '3rem', // 48px\n 14: '3.5rem', // 56px\n 16: '4rem', // 64px\n 20: '5rem', // 80px\n 24: '6rem', // 96px\n 28: '7rem', // 112px\n 32: '8rem', // 128px\n 36: '9rem', // 144px\n 40: '10rem', // 160px\n 44: '11rem', // 176px\n 48: '12rem', // 192px\n 52: '13rem', // 208px\n 56: '14rem', // 224px\n 60: '15rem', // 240px\n 64: '16rem', // 256px\n 72: '18rem', // 288px\n 80: '20rem', // 320px\n 96: '24rem', // 384px\n} as const;\n\n// =============================================================================\n// SHADOW TOKENS\n// =============================================================================\n\n/**\n * Box shadow tokens for elevation\n * Provides depth and hierarchy to components\n */\nexport const boxShadow = {\n none: 'none',\n sm: '0 1px 2px 0 rgb(0 0 0 / 0.05)',\n base: '0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)',\n md: '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)',\n lg: '0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)',\n xl: '0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)',\n '2xl': '0 25px 50px -12px rgb(0 0 0 / 0.25)',\n inner: 'inset 0 2px 4px 0 rgb(0 0 0 / 0.05)',\n} as const;\n\n/**\n * Drop shadow tokens for filters\n */\nexport const dropShadow = {\n none: 'none',\n sm: '0 1px 1px rgb(0 0 0 / 0.05)',\n base: '0 1px 2px rgb(0 0 0 / 0.1)',\n md: '0 4px 3px rgb(0 0 0 / 0.07)',\n lg: '0 10px 8px rgb(0 0 0 / 0.04)',\n xl: '0 20px 13px rgb(0 0 0 / 0.03)',\n '2xl': '0 25px 25px rgb(0 0 0 / 0.15)',\n} as const;\n\n// =============================================================================\n// BORDER RADIUS TOKENS\n// =============================================================================\n\n/**\n * Border radius tokens for consistent rounded corners\n */\nexport const borderRadius = {\n none: '0px',\n sm: '0.125rem', // 2px\n base: '0.25rem', // 4px\n md: '0.375rem', // 6px\n lg: '0.5rem', // 8px\n xl: '0.75rem', // 12px\n '2xl': '1rem', // 16px\n '3xl': '1.5rem', // 24px\n full: '9999px',\n} as const;\n\n// =============================================================================\n// BREAKPOINT TOKENS\n// =============================================================================\n\n/**\n * Responsive breakpoints for mobile-first design\n */\nexport const breakpoints = {\n xs: '475px',\n sm: '640px',\n md: '768px',\n lg: '1024px',\n xl: '1280px',\n '2xl': '1536px',\n} as const;\n\n// =============================================================================\n// ANIMATION TOKENS\n// =============================================================================\n\n/**\n * Animation duration tokens\n */\nexport const animationDuration = {\n 75: '75ms',\n 100: '100ms',\n 150: '150ms',\n 200: '200ms',\n 300: '300ms',\n 500: '500ms',\n 700: '700ms',\n 1000: '1000ms',\n} as const;\n\n/**\n * Animation timing function tokens\n */\nexport const animationTimingFunction = {\n linear: 'linear',\n in: 'cubic-bezier(0.4, 0, 1, 1)',\n out: 'cubic-bezier(0, 0, 0.2, 1)',\n 'in-out': 'cubic-bezier(0.4, 0, 0.2, 1)',\n} as const;\n\n// =============================================================================\n// Z-INDEX TOKENS\n// =============================================================================\n\n/**\n * Z-index tokens for layering\n */\nexport const zIndex = {\n auto: 'auto',\n 0: '0',\n 10: '10',\n 20: '20',\n 30: '30',\n 40: '40',\n 50: '50',\n dropdown: '1000',\n sticky: '1020',\n fixed: '1030',\n modal: '1040',\n popover: '1050',\n tooltip: '1060',\n toast: '1070',\n} as const;\n\n// =============================================================================\n// COMPONENT-SPECIFIC TOKENS\n// =============================================================================\n\n/**\n * Component size tokens\n */\nexport const componentSizes = {\n xs: {\n height: spacing[6],\n padding: `${spacing[1]} ${spacing[2]}`,\n fontSize: fontSize.xs[0],\n },\n sm: {\n height: spacing[8],\n padding: `${spacing[1.5]} ${spacing[3]}`,\n fontSize: fontSize.sm[0],\n },\n md: {\n height: spacing[10],\n padding: `${spacing[2]} ${spacing[4]}`,\n fontSize: fontSize.base[0],\n },\n lg: {\n height: spacing[12],\n padding: `${spacing[2.5]} ${spacing[6]}`,\n fontSize: fontSize.lg[0],\n },\n xl: {\n height: spacing[14],\n padding: `${spacing[3]} ${spacing[8]}`,\n fontSize: fontSize.xl[0],\n },\n} as const;\n\n// =============================================================================\n// EXPORTED TOKEN COLLECTIONS\n// =============================================================================\n\n/**\n * Complete design token system\n */\nexport const tokens = {\n colors: {\n base: baseColors,\n semantic: semanticColors,\n theme: themeColors,\n },\n typography: {\n fontFamily,\n fontSize,\n fontWeight,\n letterSpacing,\n },\n spacing,\n shadows: {\n box: boxShadow,\n drop: dropShadow,\n },\n borderRadius,\n breakpoints,\n animation: {\n duration: animationDuration,\n timingFunction: animationTimingFunction,\n },\n zIndex,\n components: {\n sizes: componentSizes,\n },\n} as const;\n\n// =============================================================================\n// LEGACY EXPORTS (for backward compatibility)\n// =============================================================================\n\nexport const colors = baseColors;\nexport const typography = { fontFamily, fontSize, fontWeight, letterSpacing };\nexport const shadows = boxShadow;\nexport const transitions = animationTimingFunction;\n\n// Default export\nexport default tokens;\n","/**\n * Theme Management Utilities\n * Provides comprehensive theme switching and management functionality\n */\n\nimport { themeColors } from './tokens';\nimport type { ThemeMode, ThemeConfig, ThemeColors } from '../types/tokens';\n\n// =============================================================================\n// CONSTANTS\n// =============================================================================\n\n/**\n * Available theme modes\n */\nexport const THEME_MODES: ThemeMode[] = ['light', 'dark', 'system'] as const;\n\n/**\n * Storage key for theme preference\n */\nexport const THEME_STORAGE_KEY = 'mfe-shell-theme' as const;\n\n/**\n * CSS class names for themes\n */\nexport const THEME_CLASS_NAMES = {\n light: '',\n dark: 'dark',\n} as const;\n\n/**\n * Media query for system dark mode preference\n */\nexport const DARK_MODE_MEDIA_QUERY = '(prefers-color-scheme: dark)' as const;\n\n// =============================================================================\n// THEME DETECTION\n// =============================================================================\n\n/**\n * Detects the system's preferred color scheme\n */\nexport function getSystemTheme(): 'light' | 'dark' {\n if (typeof window === 'undefined') {\n return 'light'; // Default for SSR\n }\n\n try {\n const mediaQuery = window.matchMedia(DARK_MODE_MEDIA_QUERY);\n return mediaQuery.matches ? 'dark' : 'light';\n } catch {\n return 'light'; // Fallback if matchMedia is not supported\n }\n}\n\n/**\n * Resolves the actual theme based on the theme mode\n */\nexport function resolveTheme(mode: ThemeMode): 'light' | 'dark' {\n switch (mode) {\n case 'light':\n return 'light';\n case 'dark':\n return 'dark';\n case 'system':\n return getSystemTheme();\n default:\n return 'light';\n }\n}\n\n// =============================================================================\n// THEME PERSISTENCE\n// =============================================================================\n\n/**\n * Saves theme preference to localStorage\n */\nexport function saveThemePreference(mode: ThemeMode): void {\n try {\n localStorage.setItem(THEME_STORAGE_KEY, mode);\n } catch (error) {\n console.warn('Failed to save theme preference:', error);\n }\n}\n\n/**\n * Loads theme preference from localStorage\n */\nexport function loadThemePreference(): ThemeMode {\n try {\n const stored = localStorage.getItem(THEME_STORAGE_KEY);\n if (stored && THEME_MODES.includes(stored as ThemeMode)) {\n return stored as ThemeMode;\n }\n } catch (error) {\n console.warn('Failed to load theme preference:', error);\n }\n\n return 'system'; // Default to system preference\n}\n\n/**\n * Clears theme preference from localStorage\n */\nexport function clearThemePreference(): void {\n try {\n localStorage.removeItem(THEME_STORAGE_KEY);\n } catch (error) {\n console.warn('Failed to clear theme preference:', error);\n }\n}\n\n// =============================================================================\n// DOM MANIPULATION\n// =============================================================================\n\n/**\n * Applies theme classes to the document element\n */\nexport function applyThemeToDOM(mode: ThemeMode): void {\n if (typeof document === 'undefined') {\n return; // Skip in SSR environment\n }\n\n const resolvedTheme = resolveTheme(mode);\n const { classList } = document.documentElement;\n\n // Remove existing theme classes\n classList.remove('light', 'dark');\n\n // Add the resolved theme class\n if (resolvedTheme === 'dark') {\n classList.add('dark');\n }\n\n // Set data attribute for CSS targeting\n document.documentElement.setAttribute('data-theme', resolvedTheme);\n}\n\n/**\n * Gets the current theme from the DOM\n */\nexport function getCurrentThemeFromDOM(): 'light' | 'dark' {\n if (typeof document === 'undefined') {\n return 'light';\n }\n\n return document.documentElement.classList.contains('dark') ? 'dark' : 'light';\n}\n\n// =============================================================================\n// CSS CUSTOM PROPERTIES\n// =============================================================================\n\n/**\n * Generates CSS custom properties for a theme\n */\nexport function generateThemeCustomProperties(\n theme: ThemeColors\n): Record<string, string> {\n return {\n // Background colors\n '--color-background-primary': theme.background.primary,\n '--color-background-secondary': theme.background.secondary,\n '--color-background-tertiary': theme.background.tertiary,\n\n // Surface colors\n '--color-surface-primary': theme.surface.primary,\n '--color-surface-secondary': theme.surface.secondary,\n '--color-surface-tertiary': theme.surface.tertiary,\n '--color-surface-elevated': theme.surface.elevated || theme.surface.primary,\n\n // Text colors\n '--color-text-primary': theme.text.primary,\n '--color-text-secondary': theme.text.secondary,\n '--color-text-tertiary': theme.text.tertiary,\n '--color-text-inverse': theme.text.inverse,\n '--color-text-disabled': theme.text.disabled,\n\n // Border colors\n '--color-border-primary': theme.border.primary,\n '--color-border-secondary': theme.border.secondary,\n '--color-border-tertiary': theme.border.tertiary,\n '--color-border-focus': theme.border.focus,\n\n // Interactive colors\n '--color-interactive-primary': theme.interactive.primary,\n '--color-interactive-primary-hover': theme.interactive['primary-hover'],\n '--color-interactive-primary-active': theme.interactive['primary-active'],\n '--color-interactive-secondary': theme.interactive.secondary,\n '--color-interactive-secondary-hover': theme.interactive['secondary-hover'],\n '--color-interactive-secondary-active':\n theme.interactive['secondary-active'],\n\n // Status colors\n '--color-status-success': theme.status.success,\n '--color-status-warning': theme.status.warning,\n '--color-status-error': theme.status.error,\n '--color-status-info': theme.status.info,\n };\n}\n\n/**\n * Applies CSS custom properties to the document root\n */\nexport function applyThemeCustomProperties(theme: ThemeColors): void {\n if (typeof document === 'undefined') {\n return;\n }\n\n const properties = generateThemeCustomProperties(theme);\n const { style } = document.documentElement;\n\n Object.entries(properties).forEach(([property, value]) => {\n style.setProperty(property, value);\n });\n}\n\n// =============================================================================\n// SYSTEM THEME MONITORING\n// =============================================================================\n\n/**\n * Creates a media query listener for system theme changes\n */\nexport function createSystemThemeListener(\n callback: (isDark: boolean) => void\n): (() => void) | null {\n if (typeof window === 'undefined') {\n return null;\n }\n\n try {\n const mediaQuery = window.matchMedia(DARK_MODE_MEDIA_QUERY);\n\n const listener = (event: MediaQueryListEvent) => {\n callback(event.matches);\n };\n\n // Add listener\n if (mediaQuery.addEventListener) {\n mediaQuery.addEventListener('change', listener);\n\n // Return cleanup function\n return () => {\n mediaQuery.removeEventListener('change', listener);\n };\n } else if (mediaQuery.addListener) {\n // Fallback for older browsers\n mediaQuery.addListener(listener);\n\n return () => {\n mediaQuery.removeListener(listener);\n };\n }\n } catch (error) {\n console.warn('Failed to create system theme listener:', error);\n }\n\n return null;\n}\n\n// =============================================================================\n// THEME CONFIGURATION\n// =============================================================================\n\n/**\n * Creates a complete theme configuration\n */\nexport function createThemeConfig(mode: ThemeMode): ThemeConfig {\n const resolvedTheme = resolveTheme(mode);\n const colors = themeColors[resolvedTheme];\n\n return {\n mode,\n colors,\n };\n}\n\n/**\n * Validates if a theme mode is valid\n */\nexport function isValidThemeMode(mode: string): mode is ThemeMode {\n return THEME_MODES.includes(mode as ThemeMode);\n}\n\n// =============================================================================\n// THEME UTILITIES\n// =============================================================================\n\n/**\n * Gets the opposite theme\n */\nexport function getOppositeTheme(theme: 'light' | 'dark'): 'light' | 'dark' {\n return theme === 'light' ? 'dark' : 'light';\n}\n\n/**\n * Checks if the current theme is dark\n */\nexport function isDarkTheme(mode: ThemeMode): boolean {\n return resolveTheme(mode) === 'dark';\n}\n\n/**\n * Checks if the current theme is light\n */\nexport function isLightTheme(mode: ThemeMode): boolean {\n return resolveTheme(mode) === 'light';\n}\n\n/**\n * Gets theme-appropriate color value\n */\nexport function getThemeColor(\n colorPath: string,\n mode: ThemeMode = 'system'\n): string {\n const resolvedTheme = resolveTheme(mode);\n const colors = themeColors[resolvedTheme];\n\n // Simple path resolution (e.g., 'text.primary' -> colors.text.primary)\n const pathParts = colorPath.split('.');\n let value: Record<string, unknown> | string = colors;\n\n for (const part of pathParts) {\n if (value && typeof value === 'object' && part in value) {\n value = (value as Record<string, unknown>)[part] as\n | Record<string, unknown>\n | string;\n } else {\n return ''; // Return empty string if path is invalid\n }\n }\n\n return typeof value === 'string' ? value : '';\n}\n\n// =============================================================================\n// INITIALIZATION\n// =============================================================================\n\n/**\n * Initializes the theme system\n */\nexport function initializeTheme(): ThemeMode {\n const preferredMode = loadThemePreference();\n applyThemeToDOM(preferredMode);\n\n // Apply CSS custom properties for the resolved theme\n const resolvedTheme = resolveTheme(preferredMode);\n applyThemeCustomProperties(themeColors[resolvedTheme]);\n\n return preferredMode;\n}\n\n/**\n * Sets up complete theme management\n */\nexport function setupThemeManagement(\n onThemeChange?: (mode: ThemeMode, resolvedTheme: 'light' | 'dark') => void\n): {\n currentMode: ThemeMode;\n setTheme: (mode: ThemeMode) => void;\n cleanup: () => void;\n} {\n // Initialize theme\n const currentMode = initializeTheme();\n\n // Set up system theme listener\n const cleanup = createSystemThemeListener(isDark => {\n const storedMode = loadThemePreference();\n if (storedMode === 'system') {\n const newTheme = isDark ? 'dark' : 'light';\n applyThemeToDOM('system');\n applyThemeCustomProperties(themeColors[newTheme]);\n onThemeChange?.('system', newTheme);\n }\n });\n\n // Theme setter function\n const setTheme = (mode: ThemeMode) => {\n saveThemePreference(mode);\n applyThemeToDOM(mode);\n\n const resolvedTheme = resolveTheme(mode);\n applyThemeCustomProperties(themeColors[resolvedTheme]);\n\n onThemeChange?.(mode, resolvedTheme);\n };\n\n return {\n currentMode,\n setTheme,\n cleanup: cleanup || (() => {}),\n };\n}\n\n// =============================================================================\n// EXPORTS\n// =============================================================================\n\nexport default {\n // Theme detection\n getSystemTheme,\n resolveTheme,\n\n // Persistence\n saveThemePreference,\n loadThemePreference,\n clearThemePreference,\n\n // DOM manipulation\n applyThemeToDOM,\n getCurrentThemeFromDOM,\n\n // CSS custom properties\n generateThemeCustomProperties,\n applyThemeCustomProperties,\n\n // System monitoring\n createSystemThemeListener,\n\n // Configuration\n createThemeConfig,\n isValidThemeMode,\n\n // Utilities\n getOppositeTheme,\n isDarkTheme,\n isLightTheme,\n getThemeColor,\n\n // Initialization\n initializeTheme,\n setupThemeManagement,\n\n // Constants\n THEME_MODES,\n THEME_STORAGE_KEY,\n THEME_CLASS_NAMES,\n DARK_MODE_MEDIA_QUERY,\n};\n","import { useId, useState, useCallback } from 'react';\n\n// Form validation types\nexport interface ValidationRule<T = unknown> {\n required?: boolean | string;\n minLength?: number | { value: number; message: string };\n maxLength?: number | { value: number; message: string };\n pattern?: RegExp | { value: RegExp; message: string };\n custom?: (value: T) => string | undefined;\n}\n\nexport interface FormFieldState<T = unknown> {\n value: T;\n error?: string;\n touched: boolean;\n dirty: boolean;\n}\n\nexport interface UseFormFieldOptions<T = unknown> {\n initialValue?: T;\n validation?: ValidationRule<T>;\n validateOnChange?: boolean;\n validateOnBlur?: boolean;\n}\n\n// Validation utility functions\nexport const validateField = <T = unknown>(\n value: T,\n rules?: ValidationRule<T>\n): string | undefined => {\n if (!rules) return undefined;\n\n // Required validation\n if (rules.required) {\n const isEmpty =\n value === undefined ||\n value === null ||\n value === '' ||\n (Array.isArray(value) && value.length === 0);\n if (isEmpty) {\n return typeof rules.required === 'string'\n ? rules.required\n : 'This field is required';\n }\n }\n\n // Skip other validations if value is empty and not required\n if (!value && !rules.required) return undefined;\n\n // String-based validations\n if (typeof value === 'string') {\n // Min length validation\n if (rules.minLength) {\n const minLength =\n typeof rules.minLength === 'number'\n ? rules.minLength\n : rules.minLength.value;\n const message =\n typeof rules.minLength === 'object'\n ? rules.minLength.message\n : `Must be at least ${minLength} characters`;\n if (value.length < minLength) return message;\n }\n\n // Max length validation\n if (rules.maxLength) {\n const maxLength =\n typeof rules.maxLength === 'number'\n ? rules.maxLength\n : rules.maxLength.value;\n const message =\n typeof rules.maxLength === 'object'\n ? rules.maxLength.message\n : `Must be no more than ${maxLength} characters`;\n if (value.length > maxLength) return message;\n }\n\n // Pattern validation\n if (rules.pattern) {\n const pattern =\n rules.pattern instanceof RegExp ? rules.pattern : rules.pattern.value;\n const message =\n rules.pattern instanceof RegExp\n ? 'Invalid format'\n : rules.pattern.message;\n if (!pattern.test(value)) return message;\n }\n }\n\n // Custom validation\n if (rules.custom) {\n return rules.custom(value);\n }\n\n return undefined;\n};\n\n// Form field hook for consistent state management\nexport const useFormField = <T = string>(\n {\n initialValue = '' as T,\n validation,\n validateOnChange = false,\n validateOnBlur = true,\n }: UseFormFieldOptions<T> = {} as UseFormFieldOptions<T>\n) => {\n const [state, setState] = useState<FormFieldState<T>>({\n value: initialValue,\n error: undefined,\n touched: false,\n dirty: false,\n });\n\n const validate = useCallback(\n (value: T) => {\n return validateField(value, validation);\n },\n [validation]\n );\n\n const setValue = useCallback(\n (newValue: T) => {\n setState(prev => {\n const error = validateOnChange ? validate(newValue) : prev.error;\n return {\n ...prev,\n value: newValue,\n dirty: newValue !== initialValue,\n error,\n };\n });\n },\n [initialValue, validate, validateOnChange]\n );\n\n const setTouched = useCallback(() => {\n setState(prev => {\n if (prev.touched) return prev;\n const error = validateOnBlur ? validate(prev.value) : prev.error;\n return {\n ...prev,\n touched: true,\n error,\n };\n });\n }, [validate, validateOnBlur]);\n\n const setError = useCallback((error?: string) => {\n setState(prev => ({ ...prev, error }));\n }, []);\n\n const reset = useCallback(() => {\n setState({\n value: initialValue,\n error: undefined,\n touched: false,\n dirty: false,\n });\n }, [initialValue]);\n\n const validateNow = useCallback(() => {\n const error = validate(state.value);\n setState(prev => ({ ...prev, error, touched: true }));\n return !error;\n }, [validate, state.value]);\n\n return {\n ...state,\n setValue,\n setTouched,\n setError,\n reset,\n validate: validateNow,\n isValid: !state.error,\n };\n};\n\n// Common validation patterns\nexport const validationPatterns = {\n email: {\n value: /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/,\n message: 'Please enter a valid email address',\n },\n phone: {\n value: /^[+]?[1-9][\\d]{0,15}$/,\n message: 'Please enter a valid phone number',\n },\n url: {\n value: /^https?:\\/\\/.+/,\n message: 'Please enter a valid URL',\n },\n alphanumeric: {\n value: /^[a-zA-Z0-9]+$/,\n message: 'Only letters and numbers are allowed',\n },\n noSpecialChars: {\n value: /^[a-zA-Z0-9\\s]+$/,\n message: 'Special characters are not allowed',\n },\n};\n\n// Generate consistent IDs for form fields\nexport const useFormFieldId = (prefix = 'field') => {\n return useId() || `${prefix}-${Math.random().toString(36).substr(2, 9)}`;\n};\n\n// Form field error state utilities\nexport const getFieldErrorProps = (error?: string, fieldId?: string) => ({\n 'aria-invalid': error ? ('true' as const) : ('false' as const),\n 'aria-describedby': error && fieldId ? `${fieldId}-error` : undefined,\n});\n\n// Form accessibility helpers\nexport const getFormFieldAccessibility = (\n fieldId: string,\n error?: string,\n description?: string,\n required?: boolean\n) => {\n const describedBy = [];\n if (description) describedBy.push(`${fieldId}-description`);\n if (error) describedBy.push(`${fieldId}-error`);\n\n return {\n id: fieldId,\n 'aria-invalid': error ? ('true' as const) : ('false' as const),\n 'aria-describedby':\n describedBy.length > 0 ? describedBy.join(' ') : undefined,\n 'aria-required': required ? ('true' as const) : undefined,\n };\n};\n","// Utility functions for MFE Shell\n\n/**\n * Combines CSS class names, filtering out falsy values\n * Optimized for performance with early returns\n */\nexport function classNames(\n ...classes: (string | undefined | null | false)[]\n): string {\n if (classes.length === 0) return '';\n if (classes.length === 1) return classes[0] || '';\n\n return classes.filter(Boolean).join(' ');\n}\n\n/**\n * Local storage utilities with error handling\n * Note: Caching removed to avoid test interference\n */\nexport const storage = {\n get: <T>(key: string, defaultValue: T): T => {\n try {\n const item = localStorage.getItem(key);\n return item ? JSON.parse(item) : defaultValue;\n } catch {\n return defaultValue;\n }\n },\n\n set: <T>(key: string, value: T): void => {\n try {\n localStorage.setItem(key, JSON.stringify(value));\n } catch {\n // Silently fail if localStorage is not available\n }\n },\n\n remove: (key: string): void => {\n try {\n localStorage.removeItem(key);\n } catch {\n // Silently fail if localStorage is not available\n }\n },\n\n clear: (): void => {\n try {\n localStorage.clear();\n } catch {\n // Silently fail if localStorage is not available\n }\n },\n};\n\n/**\n * Theme utilities with caching for performance\n */\nlet systemThemeCache: 'light' | 'dark' | null = null;\nlet mediaQueryListener: ((e: MediaQueryListEvent) => void) | null = null;\n\nexport const theme = {\n /**\n * Gets the system theme preference with caching\n */\n getSystemTheme: (): 'light' | 'dark' => {\n if (typeof window === 'undefined') return 'light';\n\n if (systemThemeCache === null) {\n const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');\n systemThemeCache = mediaQuery.matches ? 'dark' : 'light';\n\n // Set up listener to update cache when system theme changes\n if (!mediaQueryListener && mediaQuery.addEventListener) {\n mediaQueryListener = e => {\n systemThemeCache = e.matches ? 'dark' : 'light';\n };\n mediaQuery.addEventListener('change', mediaQueryListener);\n }\n }\n\n return systemThemeCache;\n },\n\n /**\n * Applies theme to document with performance optimization\n */\n applyTheme: (themeName: 'light' | 'dark' | 'system'): void => {\n if (typeof document === 'undefined') return;\n\n const actualTheme =\n themeName === 'system' ? theme.getSystemTheme() : themeName;\n const { classList } = document.documentElement;\n\n // Only update if theme actually changed\n if (classList && classList.contains) {\n const currentTheme = classList.contains('dark') ? 'dark' : 'light';\n if (currentTheme !== actualTheme) {\n classList.remove('light', 'dark');\n classList.add(actualTheme);\n }\n } else {\n // Fallback for test environments\n classList.remove('light', 'dark');\n classList.add(actualTheme);\n }\n },\n\n /**\n * Clear theme cache (useful for testing)\n */\n clearCache: (): void => {\n systemThemeCache = null;\n if (mediaQueryListener && typeof window !== 'undefined') {\n const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');\n if (mediaQuery.removeEventListener) {\n mediaQuery.removeEventListener('change', mediaQueryListener);\n }\n mediaQueryListener = null;\n }\n },\n};\n\n/**\n * Debounce function for performance optimization\n * Uses WeakMap for cleanup to prevent memory leaks\n */\nconst debounceTimers = new WeakMap<\n (...args: unknown[]) => unknown,\n NodeJS.Timeout\n>();\n\nexport function debounce<T extends (...args: unknown[]) => unknown>(\n func: T,\n wait: number\n): (...args: Parameters<T>) => void {\n return (...args: Parameters<T>) => {\n const existingTimer = debounceTimers.get(func);\n if (existingTimer) {\n clearTimeout(existingTimer);\n }\n\n const timer = setTimeout(() => {\n debounceTimers.delete(func);\n func(...args);\n }, wait);\n\n debounceTimers.set(func, timer);\n };\n}\n\n/**\n * Throttle function for performance optimization\n */\nconst throttleTimers = new WeakMap<\n (...args: unknown[]) => unknown,\n { timer: NodeJS.Timeout | null; lastRun: number }\n>();\n\nexport function throttle<T extends (...args: unknown[]) => unknown>(\n func: T,\n limit: number\n): (...args: Parameters<T>) => void {\n return (...args: Parameters<T>) => {\n const now = Date.now();\n const throttleData = throttleTimers.get(func) || {\n timer: null,\n lastRun: 0,\n };\n\n if (now - throttleData.lastRun >= limit) {\n func(...args);\n throttleData.lastRun = now;\n } else if (!throttleData.timer) {\n throttleData.timer = setTimeout(\n () => {\n func(...args);\n throttleData.lastRun = Date.now();\n throttleData.timer = null;\n },\n limit - (now - throttleData.lastRun)\n );\n }\n\n throttleTimers.set(func, throttleData);\n };\n}\n\n/**\n * Generate unique IDs with better entropy\n */\nlet idCounter = 0;\nexport function generateId(prefix = 'id'): string {\n return `${prefix}-${++idCounter}-${Math.random().toString(36).substr(2, 9)}`;\n}\n\n/**\n * Format numbers with proper locale and caching\n */\nconst numberFormatters = new Map<string, Intl.NumberFormat>();\n\nexport function formatNumber(num: number, locale = 'en-US'): string {\n if (!numberFormatters.has(locale)) {\n numberFormatters.set(locale, new Intl.NumberFormat(locale));\n }\n return numberFormatters.get(locale)!.format(num);\n}\n\n/**\n * Truncate text with ellipsis (optimized)\n */\nexport function truncate(text: string, maxLength: number): string {\n if (text.length <= maxLength) return text;\n return text.slice(0, maxLength - 3) + '...';\n}\n\n/**\n * Deep merge objects (useful for settings)\n */\nexport function deepMerge<T extends Record<string, unknown>>(\n target: T,\n ...sources: Partial<T>[]\n): T {\n if (!sources.length) return target;\n const source = sources.shift();\n\n if (isObject(target) && isObject(source)) {\n for (const key in source) {\n if (isObject(source[key])) {\n if (!target[key]) Object.assign(target, { [key]: {} });\n deepMerge(\n target[key] as Record<string, unknown>,\n source[key] as Record<string, unknown>\n );\n } else {\n Object.assign(target, { [key]: source[key] });\n }\n }\n }\n\n return deepMerge(target, ...sources);\n}\n\n/**\n * Check if value is an object\n */\nfunction isObject(item: unknown): item is Record<string, unknown> {\n return item !== null && typeof item === 'object' && !Array.isArray(item);\n}\n\n/**\n * Clamp a number between min and max values\n */\nexport function clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max);\n}\n\n/**\n * Check if code is running in browser environment\n */\nexport const isBrowser = typeof window !== 'undefined';\n\n/**\n * Check if user prefers reduced motion\n */\nexport function prefersReducedMotion(): boolean {\n if (!isBrowser) return false;\n return window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n}\n\n// Export form utilities\nexport * from './form';\n","/**\n * Typography Utilities\n * DRY utilities for typography system\n */\n\nimport {\n TextVariant,\n TextSize,\n TextWeight,\n TextAlignment,\n TextTransform,\n TextDecoration,\n TextWhitespace,\n TextOverflow,\n LineClamp,\n ResponsiveTypographyValue,\n TypographyScale,\n} from '../types';\nimport { classNames } from './index';\n\n// =============================================================================\n// TYPOGRAPHY VARIANT DEFINITIONS\n// =============================================================================\n\n/**\n * Typography variant configurations\n * Each variant defines semantic meaning and default styling\n */\nexport const typographyVariants: Record<\n TextVariant,\n {\n defaultSize: TextSize;\n defaultWeight: TextWeight;\n defaultColor: string;\n semanticElement?: string;\n description: string;\n }\n> = {\n // Content variants\n body: {\n defaultSize: 'base',\n defaultWeight: 'normal',\n defaultColor: 'text-primary',\n semanticElement: 'p',\n description: 'Standard body text for content',\n },\n 'body-large': {\n defaultSize: 'lg',\n defaultWeight: 'normal',\n defaultColor: 'text-primary',\n semanticElement: 'p',\n description: 'Larger body text for emphasis',\n },\n 'body-small': {\n defaultSize: 'sm',\n defaultWeight: 'normal',\n defaultColor: 'text-primary',\n semanticElement: 'p',\n description: 'Smaller body text for secondary content',\n },\n caption: {\n defaultSize: 'sm',\n defaultWeight: 'normal',\n defaultColor: 'text-secondary',\n semanticElement: 'span',\n description: 'Captions and secondary information',\n },\n overline: {\n defaultSize: 'xs',\n defaultWeight: 'medium',\n defaultColor: 'text-secondary',\n semanticElement: 'span',\n description: 'Overline text with uppercase styling',\n },\n label: {\n defaultSize: 'sm',\n defaultWeight: 'medium',\n defaultColor: 'text-primary',\n semanticElement: 'label',\n description: 'Form labels and UI labels',\n },\n helper: {\n defaultSize: 'xs',\n defaultWeight: 'normal',\n defaultColor: 'text-secondary',\n semanticElement: 'span',\n description: 'Helper text and descriptions',\n },\n\n // Display variants\n display: {\n defaultSize: '6xl',\n defaultWeight: 'bold',\n defaultColor: 'text-primary',\n semanticElement: 'h1',\n description: 'Large display text for hero sections',\n },\n headline: {\n defaultSize: '4xl',\n defaultWeight: 'bold',\n defaultColor: 'text-primary',\n semanticElement: 'h1',\n description: 'Headlines and page titles',\n },\n title: {\n defaultSize: '2xl',\n defaultWeight: 'semibold',\n defaultColor: 'text-primary',\n semanticElement: 'h2',\n description: 'Section titles and headings',\n },\n subtitle: {\n defaultSize: 'lg',\n defaultWeight: 'medium',\n defaultColor: 'text-secondary',\n semanticElement: 'h3',\n description: 'Subtitles and subheadings',\n },\n\n // Specialized variants\n code: {\n defaultSize: 'sm',\n defaultWeight: 'normal',\n defaultColor: 'text-primary',\n semanticElement: 'code',\n description: 'Inline code and monospace text',\n },\n kbd: {\n defaultSize: 'xs',\n defaultWeight: 'medium',\n defaultColor: 'text-primary',\n semanticElement: 'kbd',\n description: 'Keyboard shortcuts and keys',\n },\n quote: {\n defaultSize: 'lg',\n defaultWeight: 'normal',\n defaultColor: 'text-secondary',\n semanticElement: 'blockquote',\n description: 'Quotes and testimonials',\n },\n lead: {\n defaultSize: 'xl',\n defaultWeight: 'normal',\n defaultColor: 'text-primary',\n semanticElement: 'p',\n description: 'Lead paragraphs and introductions',\n },\n muted: {\n defaultSize: 'base',\n defaultWeight: 'normal',\n defaultColor: 'text-tertiary',\n semanticElement: 'span',\n description: 'Muted text with reduced emphasis',\n },\n};\n\n// =============================================================================\n// SIZE AND WEIGHT MAPPINGS\n// =============================================================================\n\n/**\n * Size class mappings\n */\nexport const sizeClasses: Record<TextSize, string> = {\n xs: 'text-xs',\n sm: 'text-sm',\n base: 'text-base',\n lg: 'text-lg',\n xl: 'text-xl',\n '2xl': 'text-2xl',\n '3xl': 'text-3xl',\n '4xl': 'text-4xl',\n '5xl': 'text-5xl',\n '6xl': 'text-6xl',\n '7xl': 'text-7xl',\n '8xl': 'text-8xl',\n '9xl': 'text-9xl',\n};\n\n/**\n * Weight class mappings\n */\nexport const weightClasses: Record<TextWeight, string> = {\n thin: 'font-thin',\n extralight: 'font-extralight',\n light: 'font-light',\n normal: 'font-normal',\n medium: 'font-medium',\n semibold: 'font-semibold',\n bold: 'font-bold',\n extrabold: 'font-extrabold',\n black: 'font-black',\n};\n\n/**\n * Alignment class mappings\n */\nexport const alignmentClasses: Record<TextAlignment, string> = {\n left: 'text-left',\n center: 'text-center',\n right: 'text-right',\n justify: 'text-justify',\n start: 'text-start',\n end: 'text-end',\n};\n\n/**\n * Transform class mappings\n */\nexport const transformClasses: Record<TextTransform, string> = {\n none: '',\n uppercase: 'uppercase',\n lowercase: 'lowercase',\n capitalize: 'capitalize',\n};\n\n/**\n * Decoration class mappings\n */\nexport const decorationClasses: Record<TextDecoration, string> = {\n none: 'no-underline',\n unde