import { Theme, ThemeConfig, ThemeColors, ThemeSemantics, ThemeSeed, ThemeToken } from './types'; import { withAlpha, generateColorScale } from './colors'; import { darkMode } from './utils'; export function generateThemeColors(seed: ThemeSeed['colors']): ThemeColors { const defaultColors = { success: '#22c55e', warning: '#f59e0b', error: '#ef4444', info: '#3b82f6' }; const colors = { ...defaultColors, ...seed }; return Object.fromEntries( Object.entries(colors).map(([key, value]) => [ key, generateColorScale(value) ]) ) as ThemeColors; } export function generateSemantics(colors: ThemeColors, isDark: boolean): ThemeSemantics { const neutral = colors.neutral; const primary = colors.primary; const statusColors = { success: colors.success, warning: colors.warning, error: colors.error, info: colors.info }; return { colors, textColor: { DEFAULT: darkMode(isDark, neutral[100], neutral[900]), primary: darkMode(isDark, neutral[100], neutral[900]), secondary: darkMode(isDark, neutral[300], neutral[700]), tertiary: darkMode(isDark, neutral[400], neutral[600]), disabled: darkMode(isDark, neutral[500], neutral[400]), inverse: darkMode(isDark, neutral[900], neutral[100]), success: statusColors.success[darkMode(isDark, 400, 600)], warning: statusColors.warning[darkMode(isDark, 400, 600)], error: statusColors.error[darkMode(isDark, 400, 600)], info: statusColors.info[darkMode(isDark, 400, 600)], link: primary[darkMode(isDark, 400, 600)], linkHover: primary[darkMode(isDark, 300, 700)], placeholder: darkMode(isDark, neutral[500], neutral[400]), highlight: primary[darkMode(isDark, 300, 700)] }, backgroundColor: { DEFAULT: darkMode(isDark, neutral[900], neutral[50]), paper: darkMode(isDark, neutral[800], neutral[100]), subtle: darkMode(isDark, neutral[700], neutral[200]), inverse: darkMode(isDark, neutral[50], neutral[900]), success: withAlpha(statusColors.success[darkMode(isDark, 900, 50)], 0.12), warning: withAlpha(statusColors.warning[darkMode(isDark, 900, 50)], 0.12), error: withAlpha(statusColors.error[darkMode(isDark, 900, 50)], 0.12), info: withAlpha(statusColors.info[darkMode(isDark, 900, 50)], 0.12), primaryHover: withAlpha(primary[darkMode(isDark, 800, 100)], 0.08), primaryActive: withAlpha(primary[darkMode(isDark, 700, 200)], 0.12), primaryDisabled: withAlpha(neutral[darkMode(isDark, 700, 200)], 0.12), secondaryHover: withAlpha(neutral[darkMode(isDark, 800, 100)], 0.08), secondaryActive: withAlpha(neutral[darkMode(isDark, 700, 200)], 0.12), secondaryDisabled: withAlpha(neutral[darkMode(isDark, 700, 200)], 0.12), selected: withAlpha(primary[darkMode(isDark, 900, 50)], 0.16), hover: withAlpha(neutral[darkMode(isDark, 800, 100)], 0.08), focused: withAlpha(primary[darkMode(isDark, 900, 50)], 0.12), disabled: withAlpha(neutral[darkMode(isDark, 700, 200)], 0.12), overlay: withAlpha(neutral[900], 0.5) }, border: { DEFAULT: darkMode(isDark, neutral[600], neutral[300]), subtle: darkMode(isDark, neutral[700], neutral[200]), strong: darkMode(isDark, neutral[500], neutral[400]), focus: primary[500], inverse: darkMode(isDark, neutral[300], neutral[600]), success: statusColors.success[darkMode(isDark, 500, 500)], warning: statusColors.warning[darkMode(isDark, 500, 500)], error: statusColors.error[darkMode(isDark, 500, 500)], info: statusColors.info[darkMode(isDark, 500, 500)], disabled: darkMode(isDark, neutral[600], neutral[300]) } }; } export function generateSpacing(): ThemeConfig['spacing'] { return { 0: '0', xs: '0.25rem', sm: '0.5rem', DEFAULT: '1rem', lg: '1.5rem', xl: '2rem', '2xl': '3rem' }; } export function generateBorderRadius(): ThemeConfig['borderRadius'] { return { none: '0', xs: '0.125rem', sm: '0.25rem', DEFAULT: '0.375rem', lg: '0.5rem', xl: '0.75rem', full: '9999px' }; } export function generateTypography(): Pick { return { fontFamily: { sans: ['-apple-system', 'BlinkMacSystemFont', '"Segoe UI"', 'Roboto', '"Helvetica Neue"', 'Arial', 'sans-serif'], serif: ['Georgia', 'Cambria', '"Times New Roman"', 'Times', 'serif'], mono: ['Menlo', 'Monaco', 'Consolas', '"Liberation Mono"', '"Courier New"', 'monospace'], DEFAULT: ['-apple-system', 'BlinkMacSystemFont', '"Segoe UI"', 'Roboto', '"Helvetica Neue"', 'Arial', 'sans-serif'] }, fontSize: { xs: '0.75rem', sm: '0.875rem', base: '1rem', lg: '1.125rem', xl: '1.25rem', '2xl': '1.5rem', display: '2rem', DEFAULT: '1rem' }, lineHeight: { xs: '1rem', sm: '1.25rem', base: '1.5rem', lg: '1.75rem', xl: '1.75rem', '2xl': '2rem', display: '2.5rem', DEFAULT: '1.5rem' }, letterSpacing: { xs: '-0.05em', sm: '-0.025em', base: '0', lg: '0.025em', xl: '0.025em', '2xl': '0.025em', display: '0', DEFAULT: '0' }, fontWeight: { xs: 400, sm: 400, base: 400, lg: 500, xl: 500, '2xl': 600, display: 600, DEFAULT: 400 } }; } export function generateBoxShadow(): ThemeConfig['boxShadow'] { return { none: 'none', sm: '0 1px 2px 0 rgb(0 0 0 / 0.05)', DEFAULT: '0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)', lg: '0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)', xl: '0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)', inner: 'inset 0 2px 4px 0 rgb(0 0 0 / 0.05)' }; } export function generateZIndex(): ThemeConfig['zIndex'] { return { negative: '-1', 0: '0', 10: '10', 20: '20', 30: '30', 40: '40', 50: '50', modal: '1000', popover: '1100', tooltip: '1200', DEFAULT: '0' }; } export function generateThemeConfig(): ThemeConfig { return { borderRadius: generateBorderRadius(), spacing: generateSpacing(), ...generateTypography(), boxShadow: generateBoxShadow(), zIndex: generateZIndex() }; } export function generateTheme(seed: ThemeSeed): Theme { const isDark = seed.isDark ?? false; const colors = generateThemeColors(seed.colors); const semantics = generateSemantics(colors, isDark); const config = generateThemeConfig(); return { token: { ...colors, ...semantics, ...config }, isDark }; }