/** * Tailwind CSS Plugin for KodexaLabs Design Tokens * Integrates design tokens into Tailwind configuration * * @module @kodexalabs/design-tokens/tailwind */ import plugin from 'tailwindcss/plugin'; import { designTokens, darkThemeTokens } from './tokens'; import type { DesignTokens } from './tokens'; /** * Converts HSL color string to Tailwind-compatible format * Removes 'hsl()' wrapper for use with opacity modifiers */ function hslToTailwind(hsl: string): string { return hsl.replace(/^hsl\(/, '').replace(/\)$/, ''); } /** * Extracts color values from design tokens for Tailwind */ function extractColors(tokens: DesignTokens | Partial) { const colors: Record = {}; // Brand colors if (tokens.color?.brand) { colors.brand = { background: hslToTailwind(tokens.color.brand.background.hsl), foreground: hslToTailwind(tokens.color.brand.foreground.hsl), white: hslToTailwind(tokens.color.brand.white.hsl), whiteStrong: hslToTailwind(tokens.color.brand.whiteStrong.hsl), success: hslToTailwind(tokens.color.brand.success.hsl), muted: hslToTailwind(tokens.color.brand.muted.hsl), logo: { primary: hslToTailwind(tokens.color.brand.logoPrimary.hsl), secondary: hslToTailwind(tokens.color.brand.logoSecondary.hsl), }, }; colors.white = colors.brand.white; colors['white-strong'] = colors.brand.whiteStrong; colors['logo-primary'] = colors.brand.logo.primary; colors['logo-secondary'] = colors.brand.logo.secondary; } // Semantic colors if (tokens.color?.semantic) { colors.background = hslToTailwind(tokens.color.semantic.background); colors.foreground = hslToTailwind(tokens.color.semantic.foreground); colors.surface = hslToTailwind(tokens.color.semantic.surface); colors.border = hslToTailwind(tokens.color.semantic.border); } // UI colors if (tokens.color?.ui) { colors.card = { DEFAULT: hslToTailwind(tokens.color.ui.card), foreground: hslToTailwind(tokens.color.ui.cardForeground), }; colors.popover = { DEFAULT: hslToTailwind(tokens.color.ui.popover), foreground: hslToTailwind(tokens.color.ui.popoverForeground), }; colors.primary = { DEFAULT: hslToTailwind(tokens.color.ui.primary), foreground: hslToTailwind(tokens.color.ui.primaryForeground), }; colors.secondary = { DEFAULT: hslToTailwind(tokens.color.ui.secondary), foreground: hslToTailwind(tokens.color.ui.secondaryForeground), }; colors.muted = { DEFAULT: hslToTailwind(tokens.color.ui.muted), foreground: hslToTailwind(tokens.color.ui.mutedForeground), }; colors.accent = { DEFAULT: hslToTailwind(tokens.color.ui.accent), foreground: hslToTailwind(tokens.color.ui.accentForeground), }; colors.destructive = { DEFAULT: hslToTailwind(tokens.color.ui.destructive), foreground: hslToTailwind(tokens.color.ui.destructiveForeground), }; colors.success = { DEFAULT: hslToTailwind(tokens.color.ui.success), foreground: hslToTailwind(tokens.color.ui.successForeground), }; colors.warning = { DEFAULT: hslToTailwind(tokens.color.ui.warning), foreground: hslToTailwind(tokens.color.ui.warningForeground), }; colors.info = { DEFAULT: hslToTailwind(tokens.color.ui.info), foreground: hslToTailwind(tokens.color.ui.infoForeground), }; colors.input = hslToTailwind(tokens.color.ui.input); colors.ring = hslToTailwind(tokens.color.ui.ring); } // Gray scale if (tokens.color?.gray) { colors.gray = Object.entries(tokens.color.gray).reduce((acc, [key, value]) => { acc[key] = hslToTailwind(value); return acc; }, {} as Record); } // Chart colors if (tokens.color?.chart) { colors.chart = Object.entries(tokens.color.chart).reduce((acc, [key, value]) => { acc[key] = hslToTailwind(value); return acc; }, {} as Record); } return colors; } /** * KodexaLabs Design Tokens Tailwind Plugin * * @example * ```javascript * // tailwind.config.js * import kodexaTokens from '@kodexalabs/design-tokens/tailwind'; * * export default { * plugins: [kodexaTokens], * }; * ``` */ export const kodexaDesignTokensPlugin = plugin( function ({ addBase, addUtilities, theme }) { // Add CSS variables as base styles addBase({ ':root': { '--color-brand-background': designTokens.color.brand.background.hsl, '--color-brand-foreground': designTokens.color.brand.foreground.hsl, '--color-brand-white': designTokens.color.brand.white.hsl, '--color-brand-white-strong': designTokens.color.brand.whiteStrong.hsl, '--color-brand-success': designTokens.color.brand.success.hsl, '--color-brand-muted': designTokens.color.brand.muted.hsl, '--color-brand-logo-primary': designTokens.color.brand.logoPrimary.hsl, '--color-brand-logo-secondary': designTokens.color.brand.logoSecondary.hsl, // Spacing '--spacing-0': designTokens.spacing[0], '--spacing-1': designTokens.spacing[1], '--spacing-2': designTokens.spacing[2], '--spacing-3': designTokens.spacing[3], '--spacing-4': designTokens.spacing[4], '--spacing-5': designTokens.spacing[5], '--spacing-6': designTokens.spacing[6], '--spacing-7': designTokens.spacing[7], '--spacing-8': designTokens.spacing[8], // Z-index '--z-bg': String(designTokens.zIndex.bg), '--z-content': String(designTokens.zIndex.content), '--z-modal': String(designTokens.zIndex.modal), '--z-tooltip': String(designTokens.zIndex.tooltip), }, }); // Add custom utilities addUtilities({ '.text-balance': { 'text-wrap': 'balance', }, '.text-pretty': { 'text-wrap': 'pretty', }, '.glow': { 'box-shadow': designTokens.shadow.glow, }, '.glow-strong': { 'box-shadow': designTokens.shadow.glowStrong, }, }); }, { theme: { extend: { colors: extractColors(designTokens), fontFamily: { sans: designTokens.typography.fontFamily.sans.split(',').map(f => f.trim()), mono: designTokens.typography.fontFamily.mono.split(',').map(f => f.trim()), display: designTokens.typography.fontFamily.display.split(',').map(f => f.trim()), heading: designTokens.typography.fontFamily.heading.split(',').map(f => f.trim()), }, fontSize: { micro: designTokens.typography.fontSize.micro, caption: designTokens.typography.fontSize.caption, body: designTokens.typography.fontSize.body, subhead: designTokens.typography.fontSize.subhead, headline: designTokens.typography.fontSize.headline, title: designTokens.typography.fontSize.title, display: designTokens.typography.fontSize.display, hero: designTokens.typography.fontSize.hero, }, fontWeight: { regular: String(designTokens.typography.fontWeight.regular), medium: String(designTokens.typography.fontWeight.medium), semibold: String(designTokens.typography.fontWeight.semibold), bold: String(designTokens.typography.fontWeight.bold), black: String(designTokens.typography.fontWeight.black), }, lineHeight: { tight: String(designTokens.typography.lineHeight.tight), snug: String(designTokens.typography.lineHeight.snug), normal: String(designTokens.typography.lineHeight.normal), relaxed: String(designTokens.typography.lineHeight.relaxed), loose: String(designTokens.typography.lineHeight.loose), }, letterSpacing: { tighter: designTokens.typography.letterSpacing.tighter, tight: designTokens.typography.letterSpacing.tight, normal: designTokens.typography.letterSpacing.normal, wide: designTokens.typography.letterSpacing.wide, wider: designTokens.typography.letterSpacing.wider, }, spacing: { 0: designTokens.spacing[0], 1: designTokens.spacing[1], 2: designTokens.spacing[2], 3: designTokens.spacing[3], 4: designTokens.spacing[4], 5: designTokens.spacing[5], 6: designTokens.spacing[6], 7: designTokens.spacing[7], 8: designTokens.spacing[8], 9: designTokens.spacing[9], 10: designTokens.spacing[10], }, borderRadius: { none: designTokens.radius.none, xs: designTokens.radius.xs, sm: designTokens.radius.sm, md: designTokens.radius.md, lg: designTokens.radius.lg, xl: designTokens.radius.xl, '2xl': designTokens.radius['2xl'], '3xl': designTokens.radius['3xl'], round: designTokens.radius.round, circle: designTokens.radius.circle, }, boxShadow: { xs: designTokens.shadow.xs, sm: designTokens.shadow.sm, md: designTokens.shadow.md, lg: designTokens.shadow.lg, xl: designTokens.shadow.xl, '2xl': designTokens.shadow['2xl'], inner: designTokens.shadow.inner, glow: designTokens.shadow.glow, 'glow-strong': designTokens.shadow.glowStrong, }, transitionTimingFunction: { standard: designTokens.motion.easing.standard, emphasized: designTokens.motion.easing.emphasized, decelerate: designTokens.motion.easing.decelerate, accelerate: designTokens.motion.easing.accelerate, sharp: designTokens.motion.easing.sharp, }, transitionDuration: { instant: designTokens.motion.duration.instant, fast: designTokens.motion.duration.fast, base: designTokens.motion.duration.base, slow: designTokens.motion.duration.slow, slower: designTokens.motion.duration.slower, }, maxWidth: { container: designTokens.layout.container['2xl'], 'container-xs': designTokens.layout.container.xs, 'container-sm': designTokens.layout.container.sm, 'container-md': designTokens.layout.container.md, 'container-lg': designTokens.layout.container.lg, 'container-xl': designTokens.layout.container.xl, measure: designTokens.layout.measure, }, borderWidth: { thin: designTokens.borderWidth.thin, base: designTokens.borderWidth.base, thick: designTokens.borderWidth.thick, }, zIndex: { bg: String(designTokens.zIndex.bg), base: String(designTokens.zIndex.base), content: String(designTokens.zIndex.content), dropdown: String(designTokens.zIndex.dropdown), sticky: String(designTokens.zIndex.sticky), overlay: String(designTokens.zIndex.overlay), modal: String(designTokens.zIndex.modal), popover: String(designTokens.zIndex.popover), tooltip: String(designTokens.zIndex.tooltip), toast: String(designTokens.zIndex.toast), }, }, }, } ); export default kodexaDesignTokensPlugin;