UNPKG

tw-colors

Version:

Tailwind plugin for switching color theme with just one className

205 lines 6.44 kB
// lib/index.ts import Color from "color"; import plugin from "tailwindcss/plugin"; import forEach from "lodash.foreach"; import flatten from "flat"; var SCHEME = Symbol("color-scheme"); var emptyConfig = {}; var resolveTwcConfig = (config = emptyConfig, { produceCssVariable = defaultProduceCssVariable, produceThemeClass = defaultProduceThemeClass, produceThemeVariant = produceThemeClass, defaultTheme, strict = false } = {}) => { const resolved = { variants: [], utilities: {}, colors: {} }; const configObject = typeof config === "function" ? config({ dark, light }) : config; forEach(configObject, (colors, themeName) => { const themeClassName = produceThemeClass(themeName); const themeVariant = produceThemeVariant(themeName); const flatColors = flattenColors(colors); resolved.variants.push({ name: themeVariant, // tailwind will generate only the first matched definition definition: [ generateVariantDefinitions(`.${themeClassName}`), generateVariantDefinitions(`[data-theme='${themeName}']`), generateRootVariantDefinitions(themeName, defaultTheme) ].flat() }); const cssSelector = `.${themeClassName},[data-theme="${themeName}"]`; resolved.utilities[cssSelector] = colors[SCHEME] ? { "color-scheme": colors[SCHEME] } : {}; forEach(flatColors, (colorValue, colorName) => { if (colorName === SCHEME) return; const safeColorName = escapeChars(colorName, "/"); let [h, s, l, defaultAlphaValue] = [0, 0, 0, 1]; try { [h, s, l, defaultAlphaValue] = toHslaArray(colorValue); } catch (error) { const message = `\r Warning - In theme "${themeName}" color "${colorName}". ${error.message}`; if (strict) { throw new Error(message); } return console.error(message); } const twcColorVariable = produceCssVariable(safeColorName); const twcOpacityVariable = `${produceCssVariable(safeColorName)}-opacity`; const hslValues = `${h} ${s}% ${l}%`; resolved.utilities[cssSelector][twcColorVariable] = hslValues; addRootUtilities(resolved.utilities, { key: twcColorVariable, value: hslValues, defaultTheme, themeName }); if (typeof defaultAlphaValue === "number") { const alphaValue = defaultAlphaValue.toFixed(2); resolved.utilities[cssSelector][twcOpacityVariable] = alphaValue; addRootUtilities(resolved.utilities, { key: twcOpacityVariable, value: alphaValue, defaultTheme, themeName }); } resolved.colors[colorName] = ({ opacityVariable, opacityValue }) => { if (!isNaN(+opacityValue)) { return `hsl(var(${twcColorVariable}) / ${opacityValue})`; } if (opacityVariable) { return `hsl(var(${twcColorVariable}) / var(${twcOpacityVariable}, var(${opacityVariable})))`; } return `hsl(var(${twcColorVariable}) / var(${twcOpacityVariable}, 1))`; }; }); }); return resolved; }; var createThemes = (config = emptyConfig, options = {}) => { const resolved = resolveTwcConfig(config, options); return plugin( ({ addUtilities, addVariant }) => { addUtilities(resolved.utilities); resolved.variants.forEach(({ name, definition }) => addVariant(name, definition)); }, // extend the colors config { theme: { extend: { // @ts-ignore tailwind types are broken colors: resolved.colors } } } ); }; function escapeChars(str, ...chars) { let result = str; for (let char of chars) { const regexp = new RegExp(char, "g"); result = str.replace(regexp, "\\" + char); } return result; } function flattenColors(colors) { const flatColorsWithDEFAULT = flatten(colors, { safe: true, delimiter: "-" }); return Object.entries(flatColorsWithDEFAULT).reduce((acc, [key, value]) => { acc[key.replace(/\-DEFAULT$/, "")] = value; return acc; }, {}); } function toHslaArray(colorValue) { return Color(colorValue).hsl().round(1).array(); } function defaultProduceCssVariable(themeName) { return `--twc-${themeName}`; } function defaultProduceThemeClass(themeName) { return themeName; } function dark(colors) { return { ...colors, [SCHEME]: "dark" }; } function light(colors) { return { ...colors, [SCHEME]: "light" }; } function generateVariantDefinitions(selector) { return [ `${selector}&`, `:is(${selector} > &:not([data-theme]))`, `:is(${selector} &:not(${selector} [data-theme]:not(${selector}) * ))`, `:is(${selector}:not(:has([data-theme])) &:not([data-theme]))` ]; } function generateRootVariantDefinitions(themeName, defaultTheme) { const baseDefinitions = [ `:root&`, `:is(:root > &:not([data-theme]))`, `:is(:root &:not([data-theme] *):not([data-theme]))` ]; if (typeof defaultTheme === "string" && themeName === defaultTheme) { return baseDefinitions; } if (typeof defaultTheme === "object" && themeName === defaultTheme.light) { return baseDefinitions.map( (definition) => `@media (prefers-color-scheme: light){${definition}}` ); } if (typeof defaultTheme === "object" && themeName === defaultTheme.dark) { return baseDefinitions.map( (definition) => `@media (prefers-color-scheme: dark){${definition}}` ); } return []; } function addRootUtilities(utilities, { key, value, defaultTheme, themeName }) { if (!defaultTheme) return; if (typeof defaultTheme === "string") { if (themeName === defaultTheme) { if (!utilities[":root"]) { utilities[":root"] = {}; } utilities[":root"][key] = value; } } else if (themeName === defaultTheme.light) { if (!utilities["@media (prefers-color-scheme: light)"]) { utilities["@media (prefers-color-scheme: light)"] = { ":root": {} }; } utilities["@media (prefers-color-scheme: light)"][":root"][key] = value; } else if (themeName === defaultTheme.dark) { if (!utilities["@media (prefers-color-scheme: dark)"]) { utilities["@media (prefers-color-scheme: dark)"] = { ":root": {} }; } utilities["@media (prefers-color-scheme: dark)"][":root"][key] = value; } } export { createThemes, resolveTwcConfig }; //# sourceMappingURL=index.mjs.map