UNPKG

@reusable-ui/themable

Version:
143 lines (142 loc) 6.54 kB
// cssfn: import { // writes css in javascript: rule, variants, style, vars, startsCapitalized, cssVars, // writes complex stylesheets in simpler way: memoizeResult, memoizeStyle, memoizeStyleWithVariants, } from '@cssfn/core'; // writes css in javascript // reusable-ui configs: import { // configs: colors, themes, cssColorConfig, } from '@reusable-ui/colors'; // a color management system // defaults: const _defaultTheme = 'inherit'; const [themableVars] = cssVars({ prefix: 'th', minify: false }); // shared variables: ensures the server-side & client-side have the same generated css variable names //#region caches const themeClassesCache = new Map(); export const createThemeClass = (themeName) => { const cached = themeClassesCache.get(themeName); if (cached !== undefined) return cached; // null is allowed if (themeName === 'inherit') { themeClassesCache.set(themeName, null); return null; } // if const themeClass = `th${startsCapitalized(themeName)}`; themeClassesCache.set(themeName, themeClass); return themeClass; }; const themeSelectorsCache = new Map(); export const createThemeSelector = (themeName) => { const cached = themeSelectorsCache.get(themeName); if (cached) return cached; const themeClass = createThemeClass(themeName); if (themeClass === null) return null; const themeRule = `.${themeClass}`; themeSelectorsCache.set(themeName, themeRule); return themeRule; }; let hasThemeSelectorsCache = undefined; let noThemeSelectorsCache = undefined; cssColorConfig.onChange.subscribe(() => { themeClassesCache.clear(); themeSelectorsCache.clear(); hasThemeSelectorsCache = undefined; noThemeSelectorsCache = undefined; }); //#endregion caches export const ifTheme = (themeName, styles) => rule(createThemeSelector(themeName), styles); export const ifHasTheme = (styles) => { return rule(hasThemeSelectorsCache ?? (hasThemeSelectorsCache = (Object.keys(themes) .map((themeName) => createThemeSelector(themeName)) .filter((selector) => (selector !== null)))), styles); }; export const ifNoTheme = (styles) => { return rule(noThemeSelectorsCache ?? (noThemeSelectorsCache = (`:not(:is(${Object.keys(themes) .map((themeName) => createThemeSelector(themeName)) .filter((selector) => (selector !== null)) .join(', ')}))`)), styles); }; const createThemableRule = (themeDefinition = defineThemeRule, options = themeOptions()) => { return style({ ...variants([ options.map((themeName) => ifTheme(themeName, themeDefinition(themeName))), ]), }); }; const getDefaultThemableRule = memoizeStyle(() => createThemableRule(), cssColorConfig.onChange); /** * Uses theme (color) options. * For example: `primary`, `success`, `danger`. * @param themeDefinition A callback to create a theme rules for each theme color in `options`. * @param options Defines all available theme color options. * @returns A `ThemableStuff` represents the theme rules for each theme color in `options`. */ export const usesThemable = (themeDefinition = defineThemeRule, options = themeOptions()) => { return { themableRule: (((themeDefinition === defineThemeRule) && (options === themeOptions())) ? getDefaultThemableRule : () => createThemableRule(themeDefinition, options)), themableVars, }; }; /** * Defines a theme rules for the given `themeName`. * @param themeName The theme name. * @returns A `CssRule` represents a theme rules for the given `themeName`. */ export const defineThemeRule = memoizeStyleWithVariants((themeName) => { return style({ ...vars({ [themableVars.backg]: colors[themeName], [themableVars.foreg]: colors[`${themeName}Text`], [themableVars.border]: colors[`${themeName}Bold`], [themableVars.altBackg]: themableVars.backgMild, [themableVars.altForeg]: themableVars.foregMild, [themableVars.foregOutlined]: themableVars.backg, [themableVars.altBackgOutlined]: themableVars.backg, [themableVars.altForegOutlined]: themableVars.foreg, [themableVars.backgMild]: colors[`${themeName}Mild`], [themableVars.foregMild]: themableVars.border, [themableVars.altBackgMild]: themableVars.backg, [themableVars.altForegMild]: themableVars.foreg, [themableVars.ring]: colors[`${themeName}Thin`], // 50% transparency of base color }), }); }, cssColorConfig.onChange); /** * Gets all available theme color options. * @returns A `ThemeName[]` represents all available theme color options. */ export const themeOptions = memoizeResult(() => { return Object.keys(themes); }, cssColorConfig.onChange); /** * Creates an conditional theme color rules for the given `themeName`. * @param themeName The theme name as the conditional theme color -or- `null` for undefining the conditional. * @returns A `CssRule` represents an conditional theme color rules for the given `themeName`. */ export const usesThemeConditional = (themeName) => style({ ...vars({ [themableVars.backgCond]: !themeName ? null : colors[themeName], [themableVars.foregCond]: !themeName ? null : colors[`${themeName}Text`], [themableVars.borderCond]: !themeName ? null : colors[`${themeName}Bold`], [themableVars.altBackgCond]: themableVars.backgMildCond, [themableVars.altForegCond]: themableVars.foregMildCond, [themableVars.foregOutlinedCond]: !themeName ? null : themableVars.backgCond, [themableVars.altBackgOutlinedCond]: themableVars.backgCond, [themableVars.altForegOutlinedCond]: themableVars.foregCond, [themableVars.backgMildCond]: !themeName ? null : colors[`${themeName}Mild`], [themableVars.foregMildCond]: !themeName ? null : themableVars.borderCond, [themableVars.altBackgMildCond]: themableVars.backgCond, [themableVars.altForegMildCond]: themableVars.foregCond, [themableVars.ringCond]: !themeName ? null : colors[`${themeName}Thin`], // 50% transparency of base color }), }); export const useThemable = ({ theme = _defaultTheme }) => ({ class: (theme === 'inherit') ? null : createThemeClass(theme), }); //#endregion themable