UNPKG

@pastel-palette/tailwindcss

Version:

A comprehensive kawaii-inspired color system with OKLCH color space support, TypeScript definitions, and TailwindCSS v4 integration. Features a cute & kawaii aesthetic with soft, pastel tones.

555 lines (501 loc) 16.9 kB
import type { ColorFormat, ColorSystem, ColorVariants, GeneratorConfig, MaterialColor, SemanticColor, } from '@pastel-palette/colors' function camelToKebabCase(str: string): string { return str.replaceAll(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`) } export function generateColorVariable(name: string, value: string): string { const kebabName = camelToKebabCase(name) return `--color-${kebabName}: ${value}` } export function generateThemeVariable(name: string, value: string): string { const kebabName = camelToKebabCase(name) return `--color-${kebabName}: ${value}` } function extractColorValues( colorString: string, colorSpace: ColorFormat = 'srgb', ): string { if (colorSpace === 'oklch') { // Extract OKLCH values from "oklch(0.85 0.12 237)" format to "0.85 0.12 237" const match = colorString.match(/oklch\(([^)]+)\)/) if (match) { return match[1] } } else if (colorSpace === 'p3') { // Extract P3 values from "color(display-p3 0.678 0.812 1.0)" format to "0.678 0.812 1.0" // eslint-disable-next-line regexp/no-super-linear-backtracking const match = colorString.match(/color\(display-p3\s+([^)]+)\)/) if (match) { return match[1] } } else { // Extract RGB values from "rgb(255 0 0)" format to "255 0 0" const match = colorString.match(/rgb\(([^)]+)\)/) if (match) { return match[1] } // Extract RGBA values from "rgba(255 0 0 / 0.5)" format to "255 0 0 / 0.5" const alphaMatch = colorString.match(/rgba?\(([^)]+)\)/) if (alphaMatch) { return alphaMatch[1] } } return colorString } // Deprecated: keeping for compatibility export function generateColorVariables( colors: ColorSystem, mode: 'light' | 'dark', colorSpace: ColorFormat = 'srgb', ): string { const lines: string[] = [] const suffix = mode === 'light' ? '-light' : '-dark' function getColorString(colorValue: any): string { if (colorSpace === 'oklch') { return colorValue.oklch || colorValue.srgb || '' } else if (colorSpace === 'p3') { return colorValue.p3 || colorValue.srgb || '' } else { return colorValue.srgb || '' } } // Regular colors for (const [colorName, variants] of Object.entries(colors.regular.colors)) { const colorValue = (variants as ColorVariants)[mode] const colorString = getColorString(colorValue) lines.push( generateColorVariable( `${colorName}${suffix}`, extractColorValues(colorString, colorSpace), ), ) } // Regular grayScale colors for (const [grayScaleName, variants] of Object.entries( colors.regular.grayScale, )) { const colorValue = (variants as ColorVariants)[mode] const colorString = getColorString(colorValue) lines.push( generateColorVariable( `${grayScaleName}${suffix}`, extractColorValues(colorString, colorSpace), ), ) } // High contrast regular colors for (const [colorName, variants] of Object.entries( colors['high-contrast'].colors, )) { const colorValue = (variants as ColorVariants)[mode] const colorString = getColorString(colorValue) lines.push( generateColorVariable( `${colorName}-hc${suffix}`, extractColorValues(colorString, colorSpace), ), ) } // High contrast grayScale colors for (const [grayScaleName, variants] of Object.entries( colors['high-contrast'].grayScale, )) { const colorValue = (variants as ColorVariants)[mode] const colorString = getColorString(colorValue) lines.push( generateColorVariable( `${grayScaleName}-hc${suffix}`, extractColorValues(colorString, colorSpace), ), ) } // Kawaii regular colors if (colors.kawaii) { for (const [colorName, variants] of Object.entries(colors.kawaii.colors)) { const colorValue = (variants as ColorVariants)[mode] const colorString = getColorString(colorValue) lines.push( generateColorVariable( `${colorName}-kawaii${suffix}`, extractColorValues(colorString, colorSpace), ), ) } // Kawaii grayScale colors for (const [grayScaleName, variants] of Object.entries( colors.kawaii.grayScale, )) { const colorValue = (variants as ColorVariants)[mode] const colorString = getColorString(colorValue) lines.push( generateColorVariable( `${grayScaleName}-kawaii${suffix}`, extractColorValues(colorString, colorSpace), ), ) } } // Use regular theme for semantic colors (element, background, fill, material, application) const regularTheme = colors.regular for (const [colorName, depthColors] of Object.entries(regularTheme.element)) { for (const [depth, variants] of Object.entries( depthColors as SemanticColor, )) { const colorValue = (variants as ColorVariants)[mode] const varName = depth === 'primary' ? colorName : `${colorName}-${depth}` const colorString = getColorString(colorValue) lines.push( generateColorVariable( `${varName}${suffix}`, extractColorValues(colorString, colorSpace), ), ) } } for (const [depth, variants] of Object.entries(regularTheme.background)) { const colorValue = (variants as ColorVariants)[mode] const varName = depth === 'primary' ? 'background' : `background-${depth}` const colorString = getColorString(colorValue) lines.push( generateColorVariable( `${varName}${suffix}`, extractColorValues(colorString, colorSpace), ), ) } for (const [depth, variants] of Object.entries(regularTheme.fill)) { const colorValue = (variants as ColorVariants)[mode] const varName = depth === 'primary' ? 'fill' : `fill-${depth}` const colorString = getColorString(colorValue) lines.push( generateColorVariable( `${varName}${suffix}`, extractColorValues(colorString, colorSpace), ), ) } for (const [opacity, materialColor] of Object.entries( regularTheme.material, )) { const colorValue = (materialColor as MaterialColor)[mode] const varName = `material-${opacity.toLowerCase()}` const colorString = getColorString(colorValue) lines.push( generateColorVariable( `${varName}${suffix}`, extractColorValues(colorString, colorSpace), ), ) } for (const [colorName, variants] of Object.entries( regularTheme.application, )) { const colorValue = (variants as ColorVariants)[mode] const colorString = getColorString(colorValue) lines.push( generateColorVariable( `${colorName}${suffix}`, extractColorValues(colorString, colorSpace), ), ) } return lines.join(';\n ') } export function generateUnifiedThemeColorVariables( colors: ColorSystem, colorSpace: ColorFormat = 'srgb', ): { lightModeDefaults: string allVariants: string darkOverrides: string kawaiiOverrides: string kawaiiDarkOverrides: string hcOverrides: string hcDarkOverrides: string } { const lightModeLines: string[] = [] const allVariantLines: string[] = [] const darkOverrideLines: string[] = [] const kawaiiOverrideLines: string[] = [] const kawaiiDarkOverrideLines: string[] = [] const hcOverrideLines: string[] = [] const hcDarkOverrideLines: string[] = [] function getColorString(colorValue: any): string { if (colorSpace === 'oklch') { return colorValue.oklch || colorValue.srgb || '' } else if (colorSpace === 'p3') { return colorValue.p3 || colorValue.srgb || '' } else { return colorValue.srgb || '' } } function processColorGroup( regularGroup: any, hcGroup: any, kawaiiGroup: any, baseName: string, ) { const regularVariants = regularGroup[baseName] as ColorVariants const hcVariants = hcGroup[baseName] as ColorVariants const kawaiiVariants = kawaiiGroup ? (kawaiiGroup[baseName] as ColorVariants) : null if (!regularVariants) return const regularLight = getColorString(regularVariants.light) const regularDark = getColorString(regularVariants.dark) const hcLight = getColorString(hcVariants.light) const hcDark = getColorString(hcVariants.dark) const kawaiiLight = kawaiiVariants ? getColorString(kawaiiVariants.light) : null const kawaiiDark = kawaiiVariants ? getColorString(kawaiiVariants.dark) : null // Light mode default (regular light) lightModeLines.push(generateThemeVariable(baseName, regularLight)) // All variants with suffixes allVariantLines.push( generateThemeVariable(`${baseName}-light`, regularLight), ) allVariantLines.push(generateThemeVariable(`${baseName}-dark`, regularDark)) allVariantLines.push(generateThemeVariable(`${baseName}-hc`, hcLight)) if (kawaiiLight) { allVariantLines.push( generateThemeVariable(`${baseName}-kawaii`, kawaiiLight), ) } // Dark mode overrides darkOverrideLines.push(generateThemeVariable(baseName, regularDark)) // Kawaii overrides if (kawaiiLight && kawaiiDark) { kawaiiOverrideLines.push(generateThemeVariable(baseName, kawaiiLight)) kawaiiDarkOverrideLines.push(generateThemeVariable(baseName, kawaiiDark)) } // High contrast overrides hcOverrideLines.push(generateThemeVariable(baseName, hcLight)) hcDarkOverrideLines.push(generateThemeVariable(baseName, hcDark)) } // Process regular colors for (const colorName of Object.keys(colors.regular.colors)) { processColorGroup( colors.regular.colors, colors['high-contrast'].colors, colors.kawaii.colors, colorName, ) } // Process grayScale colors for (const grayScaleName of Object.keys(colors.regular.grayScale)) { processColorGroup( colors.regular.grayScale, colors['high-contrast'].grayScale, colors.kawaii.grayScale, grayScaleName, ) } // Process semantic colors (element, background, fill, material, application) function processSemanticColors( regularSemantic: any, kawaiisemantic: any, hcSemantic: any, groupName: string, ) { for (const [key, variants] of Object.entries(regularSemantic)) { let varName: string switch (groupName) { case 'element': { varName = key === 'primary' ? key : `${camelToKebabCase(key)}` break } case 'background': { varName = key === 'primary' ? 'background' : `background-${camelToKebabCase(key)}` break } case 'fill': { varName = key === 'primary' ? 'fill' : `fill-${camelToKebabCase(key)}` break } case 'material': { varName = `material-${camelToKebabCase(key)}` break } default: { varName = camelToKebabCase(key) } } if (groupName === 'element') { // Element colors have depth structure for (const [depth, depthVariants] of Object.entries( variants as SemanticColor, )) { const depthVarName = depth === 'primary' ? varName : `${varName}-${camelToKebabCase(depth)}` const regularLight = getColorString( (depthVariants as ColorVariants).light, ) const regularDark = getColorString( (depthVariants as ColorVariants).dark, ) // For semantic colors, we use the same values across themes for now // but you could extend this to use theme-specific semantic colors const kawaiiLight = kawaiisemantic?.[key]?.[depth] ? getColorString(kawaiisemantic[key][depth].light) : regularLight const kawaiiDark = kawaiisemantic?.[key]?.[depth] ? getColorString(kawaiisemantic[key][depth].dark) : regularDark const hcLight = hcSemantic?.[key]?.[depth] ? getColorString(hcSemantic[key][depth].light) : regularLight const hcDark = hcSemantic?.[key]?.[depth] ? getColorString(hcSemantic[key][depth].dark) : regularDark lightModeLines.push(generateThemeVariable(depthVarName, regularLight)) allVariantLines.push( generateThemeVariable(`${depthVarName}-light`, regularLight), ) allVariantLines.push( generateThemeVariable(`${depthVarName}-dark`, regularDark), ) darkOverrideLines.push( generateThemeVariable(depthVarName, regularDark), ) kawaiiOverrideLines.push( generateThemeVariable(depthVarName, kawaiiLight), ) kawaiiDarkOverrideLines.push( generateThemeVariable(depthVarName, kawaiiDark), ) hcOverrideLines.push(generateThemeVariable(depthVarName, hcLight)) hcDarkOverrideLines.push(generateThemeVariable(depthVarName, hcDark)) } } else { // Other semantic colors have direct variants const regularLight = getColorString((variants as ColorVariants).light) const regularDark = getColorString((variants as ColorVariants).dark) const kawaiiLight = kawaiisemantic?.[key] ? getColorString(kawaiisemantic[key].light) : regularLight const kawaiiDark = kawaiisemantic?.[key] ? getColorString(kawaiisemantic[key].dark) : regularDark const hcLight = hcSemantic?.[key] ? getColorString(hcSemantic[key].light) : regularLight const hcDark = hcSemantic?.[key] ? getColorString(hcSemantic[key].dark) : regularDark lightModeLines.push(generateThemeVariable(varName, regularLight)) allVariantLines.push( generateThemeVariable(`${varName}-light`, regularLight), ) allVariantLines.push( generateThemeVariable(`${varName}-dark`, regularDark), ) darkOverrideLines.push(generateThemeVariable(varName, regularDark)) kawaiiOverrideLines.push(generateThemeVariable(varName, kawaiiLight)) kawaiiDarkOverrideLines.push(generateThemeVariable(varName, kawaiiDark)) hcOverrideLines.push(generateThemeVariable(varName, hcLight)) hcDarkOverrideLines.push(generateThemeVariable(varName, hcDark)) } } } processSemanticColors( colors.regular.element, colors.kawaii.element, colors['high-contrast'].element, 'element', ) processSemanticColors( colors.regular.background, colors.kawaii.background, colors['high-contrast'].background, 'background', ) processSemanticColors( colors.regular.fill, colors.kawaii.fill, colors['high-contrast'].fill, 'fill', ) processSemanticColors( colors.regular.material, colors.kawaii.material, colors['high-contrast'].material, 'material', ) processSemanticColors( colors.regular.application, colors.kawaii.application, colors['high-contrast'].application, 'application', ) return { lightModeDefaults: lightModeLines.join(';\n '), allVariants: allVariantLines.join(';\n '), darkOverrides: darkOverrideLines.join(';\n '), kawaiiOverrides: kawaiiOverrideLines.join(';\n '), kawaiiDarkOverrides: kawaiiDarkOverrideLines.join(';\n '), hcOverrides: hcOverrideLines.join(';\n '), hcDarkOverrides: hcDarkOverrideLines.join(';\n '), } } export function generateTailwindTheme( colors: ColorSystem, colorSpace: ColorFormat = 'srgb', ): string { const colorVars = generateUnifiedThemeColorVariables(colors, colorSpace) return `/* This file is auto-generated by Pastel Palette for Tailwind v4 */ @import "tailwindcss"; /* Light mode colors (default) */ @theme { ${colorVars.lightModeDefaults}; } /* All color variants with suffixes */ @theme { ${colorVars.allVariants}; } @layer theme { :root { /* Dark mode overrides */ @variant dark { ${colorVars.darkOverrides}; } } } @layer theme { [data-contrast=low], [data-contrast=low] * { /* Kawaii color overrides */ ${colorVars.kawaiiOverrides}; /* Kawaii dark mode overrides */ @variant dark { ${colorVars.kawaiiDarkOverrides}; } } } @layer theme { [data-contrast=high], [data-contrast=high] * { /* High contrast color overrides */ ${colorVars.hcOverrides}; /* High contrast dark mode overrides */ @variant dark { ${colorVars.hcDarkOverrides}; } } }` } export function generateCSS(config: GeneratorConfig): string { const colorSpace = config.formatOptions?.colorSpace || 'srgb' return generateTailwindTheme(config.colors, colorSpace) }