@typeweave/plugin
Version:
For in-depth documentation, visit: [typeweave docs](https://typeweave.vercel.app)
142 lines (141 loc) • 5.61 kB
JavaScript
import plugin from 'tailwindcss/plugin';
import deepmerge from 'deepmerge';
import { flatten } from 'flat';
import Color from 'color';
import kebabcase from 'lodash.kebabcase';
import { darkThemeColors, lightThemeColors } from './semantics';
import { defaultLayout } from './layout';
import kebabCase from 'lodash.kebabcase';
const semanticThemes = {
light: {
base: 'light',
colors: lightThemeColors,
layout: defaultLayout,
},
dark: {
base: 'dark',
colors: darkThemeColors,
layout: defaultLayout,
},
};
export const typeweave = (config = {}) => {
const { themes: userThemes, defaultTheme = 'light', colorMode = 'rgb', defaultColors: userDefaultColors, defaultLayout: userDefaultLayout, } = config;
if (colorMode !== 'hsl' && colorMode !== 'rgb')
throw new Error('createTheme, `colorMode` must be either `hsl` or `rgb`');
const defaultThemes = {
light: deepmerge(semanticThemes.light, {
colors: userDefaultColors?.light ?? {},
layout: userDefaultLayout?.light ?? {},
}),
dark: deepmerge(semanticThemes.dark, {
colors: userDefaultColors?.dark ?? {},
layout: userDefaultLayout?.dark ?? {},
}),
};
const themes = {
...(userThemes ?? {}),
light: deepmerge(defaultThemes.light, userThemes?.light || {}),
dark: deepmerge(defaultThemes.dark, userThemes?.dark || {}),
};
Object.entries(themes).forEach(([themeName, theme]) => {
if (themeName === 'light' || themeName === 'dark')
return;
// It serves only as a guard, as every theme will have a base property.
if (!('base' in theme))
return;
const baseTheme = theme.base === 'dark' ? 'dark' : 'light';
themes[themeName] = deepmerge(defaultThemes[baseTheme], theme);
});
const pluginColors = {};
const pluginLayout = {};
const utilities = {};
const variants = [];
Object.entries(themes).forEach(([themeName, theme]) => {
// It serves only as a guard, as every theme will have a base property.
if (!('base' in theme))
return;
const { base, colors, layout } = theme;
let cssSelector = `:root.${themeName}, :root[data-theme="${themeName}"]`;
const scheme = base === 'dark' ? 'dark' : 'light';
if (themeName === defaultTheme) {
cssSelector = `:root,${cssSelector}`;
}
variants.push({ name: themeName, definition: `:is(.${themeName} &)` });
utilities[cssSelector] = { 'color-scheme': scheme };
const flatColors = flatten(colors, { delimiter: '-' });
// generate colors css variables
Object.entries(flatColors).forEach(([_colorName, colorValue]) => {
const colorName = kebabCase(_colorName);
const rgb = Color(colorValue).rgb().round().array();
const hsl = Color(colorValue).hsl().round().array();
const color = colorMode === 'hsl' ? hsl : rgb;
pluginColors[colorName] =
`${colorMode}(var(--${colorName}) / ${color[3] ?? '<alpha-value>'})`;
if (utilities[cssSelector])
utilities[cssSelector][`--${colorName}`] =
`${color[0]} ${color[1]} ${color[2]}`;
});
// generate layout css variables
Object.entries(layout || {}).forEach(([key, value]) => {
const kebabcaseKey = kebabcase(key);
if (!pluginLayout[key])
pluginLayout[key] = {};
if (typeof value === 'object') {
Object.entries(value).forEach(([nestedKey, nestedValue]) => {
const kebabcaseNestedKey = kebabcase(nestedKey);
const variable = `--${kebabcaseKey}-${kebabcaseNestedKey}`;
if (utilities[cssSelector])
utilities[cssSelector][variable] = nestedValue;
if (pluginLayout[key])
pluginLayout[key][kebabcaseNestedKey] = `var(${variable})`;
});
return;
}
const variable = `--${kebabcaseKey}`;
if (utilities[cssSelector])
utilities[cssSelector][variable] = value;
pluginLayout[key].DEFAULT = `var(${variable})`;
});
});
return plugin(({ addUtilities, addVariant }) => {
addUtilities(utilities);
variants.forEach((variant) => addVariant(variant.name, variant.definition));
addUtilities([
{
'.dynamic-icon > svg': {
width: '1em',
height: '1em',
},
'.disabled': {
opacity: '0.5',
pointerEvents: 'none',
},
'.border-test': {
border: '1px solid red',
},
},
]);
}, {
darkMode: 'class',
theme: {
extend: {
colors: pluginColors,
...pluginLayout,
fontFamily: { sans: 'Segoe UI' },
keyframes: {
skeletonWave: {
'100%': {
transform: 'translateX(100%)',
},
},
},
animation: {
skeletonWave: 'skeletonWave 2s linear 0.5s infinite',
},
},
},
future: {
hoverOnlyWhenSupported: true,
},
});
};