@reusable-ui/themable
Version:
Color options of UI.
143 lines (142 loc) • 6.54 kB
JavaScript
// 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