@nexim/tailwind-material-colors
Version:
A Tailwind CSS plugin to automatically generate and apply Material Design 3 color palettes.
180 lines • 7.45 kB
JavaScript
import { argbFromHex, customColor, Hct } from '@material/material-color-utilities';
import { kebabCase } from 'change-case';
import plugin from 'tailwindcss/plugin';
import { AllMaterialDynamicColors, SchemeObjects } from './lib/constants.js';
import {} from './lib/type.js';
import { colorToRgbString } from './lib/utils.js';
/**
* Generates light and dark material design 3 color palettes based on the provided colors and scheme.
*
* @param options - The options for generating the color palettes.
*
* @example
* ```ts
* import { materialThemeBuilder } from '@nexim/material-design-color-tailwind-plugin';
*
* const options = {
* primaryColor: '#6200EE',
* };
*
* const { lightColors, darkColors } = materialThemeBuilder(options);
*
* console.log(lightColors.primary);
* ```
*
* @returns An object containing light and dark color palettes.
*/
export function materialThemeBuilder(options) {
const source = argbFromHex(options.primaryColor);
const SchemeObject = SchemeObjects[options.scheme ?? 'content'];
const lightScheme = new SchemeObject(Hct.fromInt(source), false, options.contrast ?? 0);
const darkScheme = new SchemeObject(Hct.fromInt(source), true, options.contrast ?? 0);
const lightColors = {};
const darkColors = {};
// Generate colors for the core colors
Object.entries(AllMaterialDynamicColors).forEach(([name, DynamicColor]) => {
name = kebabCase(name);
lightColors[name] = colorToRgbString(DynamicColor.getArgb(lightScheme));
darkColors[name] = colorToRgbString(DynamicColor.getArgb(darkScheme));
});
// Generate colors for the secondary and tertiary and extra colors
if (options.extraColors) {
for (let colorName of Object.keys(options.extraColors)) {
colorName = kebabCase(colorName);
const { hex, blend } = options.extraColors[colorName];
const { light, dark } = customColor(source, {
name: colorName,
value: argbFromHex(hex),
blend: blend ?? true,
});
lightColors[colorName] = colorToRgbString(light.color);
lightColors[`on-${colorName}`] = colorToRgbString(light.onColor);
lightColors[`${colorName}-container`] = colorToRgbString(light.colorContainer);
lightColors[`on-${colorName}-container`] = colorToRgbString(light.onColorContainer);
darkColors[colorName] = colorToRgbString(dark.color);
darkColors[`on-${colorName}`] = colorToRgbString(dark.onColor);
darkColors[`${colorName}-container`] = colorToRgbString(dark.colorContainer);
darkColors[`on-${colorName}-container`] = colorToRgbString(dark.onColorContainer);
}
}
return { lightColors, darkColors };
}
/**
* Generates a mapping of Material Design color tokens to their CSS variable references
* @returns A Record mapping color names to their CSS rgb variable references
*/
function generateColorList(options) {
const allColorNames = [];
Object.keys(AllMaterialDynamicColors).forEach((name) => allColorNames.push(kebabCase(name)));
if (options.extraColors) {
Object.keys(options.extraColors).forEach((colorName) => {
const kebabName = kebabCase(colorName);
allColorNames.push(kebabName);
allColorNames.push(`on-${kebabName}`);
allColorNames.push(`${kebabName}-container`);
allColorNames.push(`on-${kebabName}-container`);
});
}
return allColorNames;
}
/**
* Creates a Tailwind CSS plugin that implements Material Design 3 theming.
* This plugin generates CSS variables for light and dark themes, and provides utility classes for state interactions.
*
* The plugin provides:
* - CSS variables for all Material Design 3 colors
* - Dark mode support via media query and .dark-theme class
* - Light mode support via media query and .light-theme class
* - State utility classes (.state-*, .stateHover-*, .stateActive-*)
* - Automatic color palette generation based on input colors
*
* @example
* ```ts
* // tailwind-plugin/material-colors.mjs
* import { materialThemeBuilderTailwindPlugin } from '@nexim/material-design-color-tailwind-plugin';
*
* export default materialThemeBuilderTailwindPlugin({
* colors: {
* primary: '#6200EE',
* extraColors: {
* success: {
* hex: '#00FF00',
* },
* },
* },
* });
* ```
*
*
* And then use it in your CSS:
* ```css
* // main.css
* @import 'tailwindcss';
*
* @plugin "./tailwind-plugin/material-colors.mjs";
* ```
*
* @throws Error When options parameter is undefined
*/
export const materialThemeBuilderTailwindPlugin = plugin.withOptions((options) => {
return (pluginApi) => {
if (options == undefined) {
throw new Error('Material Theme Plugin: options is undefined');
}
// Generate and add the CSS variables for light and dark themes
const { lightColors, darkColors } = materialThemeBuilder(options);
pluginApi.addBase({
':root': Object.fromEntries(Object.entries(lightColors).map(([name, value]) => [`--ref-color-${name}`, value])),
'@media (prefers-color-scheme: dark)': {
':root': Object.fromEntries(Object.entries(darkColors).map(([name, value]) => [`--ref-color-${name}`, value])),
},
'.dark-theme': Object.fromEntries(Object.entries(darkColors).map(([name, value]) => [`--ref-color-${name}`, value])),
'.light-theme': Object.fromEntries(Object.entries(lightColors).map(([name, value]) => [`--ref-color-${name}`, value])),
});
const stateUtils = {};
for (const name of generateColorList(options)) {
const varName = `--ref-color-${name}`;
const colorVar = `rgb(var(${varName}))`;
const hoverBg = `linear-gradient(rgb(var(${varName}) / 0.08), rgb(var(${varName}) / 0.08))`;
const focusVisibleBg = `linear-gradient(rgb(var(${varName}) / 0.12), rgb(var(${varName}) / 0.12))`;
const activeBg = `linear-gradient(rgb(var(${varName}) / 0.12), rgb(var(${varName}) / 0.12))`;
stateUtils[`.state-${name}`] = {
color: colorVar,
'&:hover': {
backgroundImage: hoverBg,
},
'&:focus-visible': {
backgroundImage: focusVisibleBg,
},
'&:active': {
backgroundImage: activeBg,
},
};
stateUtils[`.stateHover-${name}`] = {
color: colorVar,
'&:hover': {
backgroundImage: hoverBg,
},
};
stateUtils[`.stateActive-${name}`] = {
color: colorVar,
'&:active': {
backgroundImage: activeBg,
},
};
}
pluginApi.addUtilities(stateUtils);
};
}, (options) => ({
theme: {
extend: {
colors: () => {
if (options == undefined) {
throw new Error('Material Theme Plugin: options is undefined');
}
return Object.fromEntries(generateColorList(options).map((name) => [name, `rgb(var(--ref-color-${name}) / <alpha-value>)`]));
},
},
},
}));
//# sourceMappingURL=main.js.map