UNPKG

@nexim/tailwind-material-colors

Version:

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

8 lines (7 loc) 16.7 kB
{ "version": 3, "sources": ["../src/main.ts", "../src/lib/constants.ts", "../src/lib/utils.ts"], "sourcesContent": ["import { packageTracer } from '@alwatr/package-tracer';\nimport { argbFromHex, customColor, Hct } from '@material/material-color-utilities';\nimport { kebabCase } from 'change-case';\nimport plugin from 'tailwindcss/plugin';\n\nimport { AllMaterialDynamicColors, SchemeObjects } from './lib/constants.js';\nimport { type CssInJs, type MaterialThemePalettes, type MaterialThemePluginOptions } from './lib/type.js';\nimport { colorToRgbString } from './lib/utils.js';\n\nimport type { DictionaryReq } from '@alwatr/type-helper';\nimport type { Config, PluginAPI } from 'tailwindcss/plugin';\n\nexport type { MaterialThemePalettes, MaterialThemePluginOptions } from './lib/type.js';\n\n__dev_mode__: packageTracer.add(__package_name__, __package_version__);\n\n/**\n * Generates light and dark material design 3 color palettes based on the provided colors and scheme.\n *\n * @param options - The options for generating the color palettes.\n *\n * @example\n * ```ts\n * import { materialThemeBuilder } from '@nexim/material-design-color-tailwind-plugin';\n *\n * const options = {\n * primaryColor: '#6200EE',\n * };\n *\n * const { lightColors, darkColors } = materialThemeBuilder(options);\n *\n * console.log(lightColors.primary);\n * ```\n *\n * @returns An object containing light and dark color palettes.\n */\nexport function materialThemeBuilder(options: MaterialThemePluginOptions): MaterialThemePalettes {\n const source = argbFromHex(options.primaryColor);\n\n const SchemeObject = SchemeObjects[options.scheme ?? 'content'];\n\n const lightScheme = new SchemeObject(Hct.fromInt(source), false, options.contrast ?? 0);\n const darkScheme = new SchemeObject(Hct.fromInt(source), true, options.contrast ?? 0);\n\n const lightColors: DictionaryReq = {};\n const darkColors: DictionaryReq = {};\n\n // Generate colors for the core colors\n Object.entries(AllMaterialDynamicColors).forEach(([ name, DynamicColor ]) => {\n name = kebabCase(name);\n lightColors[name] = colorToRgbString(DynamicColor.getArgb(lightScheme));\n darkColors[name] = colorToRgbString(DynamicColor.getArgb(darkScheme));\n });\n\n // Generate colors for the secondary and tertiary and extra colors\n if (options.extraColors) {\n for (let colorName of Object.keys(options.extraColors)) {\n colorName = kebabCase(colorName);\n const { hex, blend } = options.extraColors[colorName];\n\n const { light, dark } = customColor(source, {\n name: colorName,\n value: argbFromHex(hex),\n blend: blend ?? true,\n });\n\n lightColors[colorName] = colorToRgbString(light.color);\n lightColors[`on-${colorName}`] = colorToRgbString(light.onColor);\n lightColors[`${colorName}-container`] = colorToRgbString(light.colorContainer);\n lightColors[`on-${colorName}-container`] = colorToRgbString(light.onColorContainer);\n\n darkColors[colorName] = colorToRgbString(dark.color);\n darkColors[`on-${colorName}`] = colorToRgbString(dark.onColor);\n darkColors[`${colorName}-container`] = colorToRgbString(dark.colorContainer);\n darkColors[`on-${colorName}-container`] = colorToRgbString(dark.onColorContainer);\n }\n }\n\n return { lightColors, darkColors };\n}\n\n/**\n * Generates a mapping of Material Design color tokens to their CSS variable references\n * @returns A Record mapping color names to their CSS rgb variable references\n */\nfunction generateColorList(options: MaterialThemePluginOptions): string[] {\n const allColorNames: string[] = [];\n\n Object.keys(AllMaterialDynamicColors).forEach((name) => allColorNames.push(kebabCase(name)));\n\n if (options.extraColors) {\n Object.keys(options.extraColors).forEach((colorName) => {\n const kebabName = kebabCase(colorName);\n allColorNames.push(kebabName);\n allColorNames.push(`on-${kebabName}`);\n allColorNames.push(`${kebabName}-container`);\n allColorNames.push(`on-${kebabName}-container`);\n });\n }\n\n return allColorNames;\n}\n\n/**\n * Creates a Tailwind CSS plugin that implements Material Design 3 theming.\n * This plugin generates CSS variables for light and dark themes, and provides utility classes for state interactions.\n *\n * The plugin provides:\n * - CSS variables for all Material Design 3 colors\n * - Dark mode support via media query and .dark class\n * - State utility classes (.state-*, .stateHover-*, .stateActive-*)\n * - Automatic color palette generation based on input colors\n *\n * @example\n * ```ts\n * // tailwind-plugin/material-colors.mjs\n * import { materialThemeBuilderTailwindPlugin } from '@nexim/material-design-color-tailwind-plugin';\n *\n * export default materialThemeBuilderTailwindPlugin({\n * colors: {\n * primary: '#6200EE',\n * extraColors: {\n * success: {\n * hex: '#00FF00',\n * },\n * },\n * },\n * });\n * ```\n *\n *\n * And then use it in your CSS:\n * ```css\n * // main.css\n * @import 'tailwindcss';\n *\n * @plugin \"./tailwind-plugin/material-colors.mjs\";\n * ```\n *\n * @throws {Error} When options parameter is undefined\n */\nexport const materialThemeBuilderTailwindPlugin: (\n options: MaterialThemePluginOptions,\n configFunction?: (options?: MaterialThemePluginOptions) => Partial<Config>,\n) => { handler: (api: PluginAPI) => void } = plugin.withOptions<MaterialThemePluginOptions>(\n (options) => {\n return (pluginApi) => {\n if (options == undefined) {\n throw new Error('Material Theme Plugin: options is undefined');\n }\n\n // Generate and add the CSS variables for light and dark themes\n const { lightColors, darkColors } = materialThemeBuilder(options);\n pluginApi.addBase({\n ':root': Object.fromEntries(Object.entries(lightColors).map(([ name, value ]) => [ `--ref-color-${name}`, value ])),\n '@media (prefers-color-scheme: dark)': {\n ':root': Object.fromEntries(Object.entries(darkColors).map(([ name, value ]) => [ `--ref-color-${name}`, value ])),\n },\n '.dark': Object.fromEntries(Object.entries(darkColors).map(([ name, value ]) => [ `--ref-color-${name}`, value ])),\n });\n\n const stateUtils: DictionaryReq<CssInJs> = {};\n\n for (const name of generateColorList(options)) {\n const varName = `--ref-color-${name}`;\n const colorVar = `rgb(var(${varName}))`;\n const hoverBg = `linear-gradient(rgba(var(${varName}), 0.08), rgba(var(${varName}), 0.08))`;\n const focusVisibleBg = `linear-gradient(rgba(var(${varName}), 0.12), rgba(var(${varName}), 0.12))`;\n const activeBg = `linear-gradient(rgba(var(${varName}), 0.12), rgba(var(${varName}), 0.12))`;\n\n stateUtils[`.state-${name}`] = {\n color: colorVar,\n '&:hover': {\n backgroundImage: hoverBg,\n },\n '&:focus-visible': {\n backgroundImage: focusVisibleBg,\n },\n '&:active': {\n backgroundImage: activeBg,\n },\n };\n\n stateUtils[`.stateHover-${name}`] = {\n color: colorVar,\n '&:hover': {\n backgroundImage: hoverBg,\n },\n };\n\n stateUtils[`.stateActive-${name}`] = {\n color: colorVar,\n '&:active': {\n backgroundImage: activeBg,\n },\n };\n }\n\n pluginApi.addUtilities(stateUtils);\n };\n },\n (options) => ({\n theme: {\n extend: {\n colors: () => {\n if (options == undefined) {\n throw new Error('Material Theme Plugin: options is undefined');\n }\n\n return Object.fromEntries(generateColorList(options).map((name) => [ name, `rgb(var(--ref-color-${name}))` ]));\n },\n },\n },\n }),\n);\n", "import {\n SchemeContent,\n SchemeExpressive,\n SchemeFidelity,\n SchemeMonochrome,\n SchemeNeutral,\n SchemeTonalSpot,\n SchemeVibrant,\n SchemeRainbow,\n SchemeFruitSalad,\n MaterialDynamicColors,\n} from '@material/material-color-utilities';\n\n/**\n * Maps scheme names to their corresponding Material Color Utilities scheme classes.\n */\nexport const SchemeObjects = {\n content: SchemeContent,\n expressive: SchemeExpressive,\n fidelity: SchemeFidelity,\n monochrome: SchemeMonochrome,\n neutral: SchemeNeutral,\n tonalSpot: SchemeTonalSpot,\n vibrant: SchemeVibrant,\n rainbow: SchemeRainbow,\n fruitsalad: SchemeFruitSalad,\n};\n\n/**\n * Collection of all standard Material Design dynamic color roles.\n */\nexport const AllMaterialDynamicColors = {\n background: MaterialDynamicColors.background,\n onBackground: MaterialDynamicColors.onBackground,\n surface: MaterialDynamicColors.surface,\n surfaceDim: MaterialDynamicColors.surfaceDim,\n surfaceBright: MaterialDynamicColors.surfaceBright,\n surfaceContainerLowest: MaterialDynamicColors.surfaceContainerLowest,\n surfaceContainerLow: MaterialDynamicColors.surfaceContainerLow,\n surfaceContainer: MaterialDynamicColors.surfaceContainer,\n surfaceContainerHigh: MaterialDynamicColors.surfaceContainerHigh,\n surfaceContainerHighest: MaterialDynamicColors.surfaceContainerHighest,\n onSurface: MaterialDynamicColors.onSurface,\n surfaceVariant: MaterialDynamicColors.surfaceVariant,\n onSurfaceVariant: MaterialDynamicColors.onSurfaceVariant,\n inverseSurface: MaterialDynamicColors.inverseSurface,\n inverseOnSurface: MaterialDynamicColors.inverseOnSurface,\n outline: MaterialDynamicColors.outline,\n outlineVariant: MaterialDynamicColors.outlineVariant,\n shadow: MaterialDynamicColors.shadow,\n scrim: MaterialDynamicColors.scrim,\n surfaceTint: MaterialDynamicColors.surfaceTint,\n primary: MaterialDynamicColors.primary,\n onPrimary: MaterialDynamicColors.onPrimary,\n primaryContainer: MaterialDynamicColors.primaryContainer,\n onPrimaryContainer: MaterialDynamicColors.onPrimaryContainer,\n inversePrimary: MaterialDynamicColors.inversePrimary,\n secondary: MaterialDynamicColors.secondary,\n onSecondary: MaterialDynamicColors.onSecondary,\n secondaryContainer: MaterialDynamicColors.secondaryContainer,\n onSecondaryContainer: MaterialDynamicColors.onSecondaryContainer,\n tertiary: MaterialDynamicColors.tertiary,\n onTertiary: MaterialDynamicColors.onTertiary,\n tertiaryContainer: MaterialDynamicColors.tertiaryContainer,\n onTertiaryContainer: MaterialDynamicColors.onTertiaryContainer,\n error: MaterialDynamicColors.error,\n onError: MaterialDynamicColors.onError,\n errorContainer: MaterialDynamicColors.errorContainer,\n onErrorContainer: MaterialDynamicColors.onErrorContainer,\n primaryFixed: MaterialDynamicColors.primaryFixed,\n primaryFixedDim: MaterialDynamicColors.primaryFixedDim,\n onPrimaryFixed: MaterialDynamicColors.onPrimaryFixed,\n onPrimaryFixedVariant: MaterialDynamicColors.onPrimaryFixedVariant,\n secondaryFixed: MaterialDynamicColors.secondaryFixed,\n secondaryFixedDim: MaterialDynamicColors.secondaryFixedDim,\n onSecondaryFixed: MaterialDynamicColors.onSecondaryFixed,\n onSecondaryFixedVariant: MaterialDynamicColors.onSecondaryFixedVariant,\n tertiaryFixed: MaterialDynamicColors.tertiaryFixed,\n tertiaryFixedDim: MaterialDynamicColors.tertiaryFixedDim,\n onTertiaryFixed: MaterialDynamicColors.onTertiaryFixed,\n onTertiaryFixedVariant: MaterialDynamicColors.onTertiaryFixedVariant,\n} as const;\n", "import { redFromArgb, greenFromArgb, blueFromArgb } from '@material/material-color-utilities';\n\n/**\n * Converts a Material Color Utilities ARGB number to an \"R, G, B\" string.\n *\n * @param color - The ARGB color number.\n * @returns A string representation of the RGB values.\n */\nexport const colorToRgbString = (color: number): string => `${redFromArgb(color)}, ${greenFromArgb(color)}, ${blueFromArgb(color)}`;\n"], "mappings": "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", "names": [] }