213 lines
7.4 KiB
TypeScript
213 lines
7.4 KiB
TypeScript
![]() |
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<ThemeConfig, 'fontFamily' | 'fontSize' | 'lineHeight' | 'letterSpacing' | 'fontWeight'> {
|
||
|
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
|
||
|
};
|
||
|
}
|