tw-colors
Version:
Tailwind plugin for switching color theme with just one className
205 lines • 6.44 kB
JavaScript
// 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