UNPKG

@nexim/tailwind-material-colors

Version:

A Tailwind CSS plugin to automatically generate and apply Material Design 3 color palettes.

180 lines 7.45 kB
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