UNPKG

tw-colors

Version:

Tailwind plugin for switching color theme with just one className

1 lines 15.1 kB
{"version":3,"sources":["../../lib/index.ts"],"sourcesContent":["import Color from 'color';\nimport plugin from 'tailwindcss/plugin';\nimport forEach from 'lodash.foreach';\nimport flatten from 'flat';\n\nconst SCHEME = Symbol('color-scheme');\nconst emptyConfig: TwcConfig = {};\n\ntype NestedColors = { [SCHEME]?: 'light' | 'dark' } & MaybeNested<string, string>;\ntype FlatColors = { [SCHEME]?: 'light' | 'dark' } & Record<string, string>;\ntype TwcObjectConfig<ThemeName extends string> = Record<ThemeName, NestedColors>;\ntype TwcFunctionConfig<ThemeName extends string> = (scheme: {\n light: typeof light;\n dark: typeof dark;\n}) => TwcObjectConfig<ThemeName>;\n\ntype DefaultThemeObject<ThemeName = any> = {\n light: NoInfer<ThemeName> | (string & {});\n dark: NoInfer<ThemeName> | (string & {});\n};\n\ntype ResolvedVariants = Array<{ name: string; definition: string[] }>;\ntype ResolvedUtilities = { [selector: string]: Record<string, any> };\ntype ResolvedColors = {\n [colorName: string]: ({\n opacityValue,\n opacityVariable,\n }: {\n opacityValue: string;\n opacityVariable: string;\n }) => string;\n};\ntype Resolved = {\n variants: ResolvedVariants;\n utilities: ResolvedUtilities;\n colors: ResolvedColors;\n};\n\nexport type TwcConfig<ThemeName extends string = string> =\n | TwcObjectConfig<ThemeName>\n | TwcFunctionConfig<ThemeName>;\n\nexport interface TwcOptions<ThemeName extends string = string> {\n produceCssVariable?: (colorName: string) => string;\n produceThemeClass?: (themeName: ThemeName) => string;\n produceThemeVariant?: (themeName: ThemeName) => string;\n defaultTheme?: NoInfer<ThemeName> | (string & {}) | DefaultThemeObject<ThemeName>;\n strict?: boolean;\n}\n\n/**\n * Resolves the variants, base and colors to inject in the plugin\n * Library authors might use this function instead of the createThemes function\n */\nexport const resolveTwcConfig = <ThemeName extends string>(\n config: TwcConfig<ThemeName> = emptyConfig,\n {\n produceCssVariable = defaultProduceCssVariable,\n produceThemeClass = defaultProduceThemeClass,\n produceThemeVariant = produceThemeClass,\n defaultTheme,\n strict = false,\n }: TwcOptions<ThemeName> = {},\n) => {\n const resolved: Resolved = {\n variants: [],\n utilities: {},\n colors: {},\n };\n const configObject = typeof config === 'function' ? config({ dark, light }) : config;\n // @ts-ignore forEach types fail to assign themeName\n forEach(configObject, (colors: NestedColors, themeName: ThemeName) => {\n const themeClassName = produceThemeClass(themeName);\n const themeVariant = produceThemeVariant(themeName);\n\n const flatColors = flattenColors(colors);\n // set the resolved.variants\n resolved.variants.push({\n name: themeVariant,\n // tailwind will generate only the first matched definition\n definition: [\n generateVariantDefinitions(`.${themeClassName}`),\n generateVariantDefinitions(`[data-theme='${themeName}']`),\n generateRootVariantDefinitions(themeName, defaultTheme),\n ].flat(),\n });\n\n const cssSelector = `.${themeClassName},[data-theme=\"${themeName}\"]`;\n // set the color-scheme css property\n resolved.utilities[cssSelector] = colors[SCHEME] ? { 'color-scheme': colors[SCHEME] } : {};\n\n forEach(flatColors, (colorValue, colorName) => {\n // this case was handled above\n if ((colorName as any) === SCHEME) return;\n const safeColorName = escapeChars(colorName, '/');\n let [h, s, l, defaultAlphaValue]: HslaArray = [0, 0, 0, 1];\n try {\n [h, s, l, defaultAlphaValue] = toHslaArray(colorValue);\n } catch (error: any) {\n const message = `\\r\\nWarning - In theme \"${themeName}\" color \"${colorName}\". ${error.message}`;\n\n if (strict) {\n throw new Error(message);\n }\n return console.error(message);\n }\n const twcColorVariable = produceCssVariable(safeColorName);\n const twcOpacityVariable = `${produceCssVariable(safeColorName)}-opacity`;\n // add the css variables in \"@layer utilities\" for the hsl values\n const hslValues = `${h} ${s}% ${l}%`;\n resolved.utilities[cssSelector][twcColorVariable] = hslValues;\n addRootUtilities(resolved.utilities, {\n key: twcColorVariable,\n value: hslValues,\n defaultTheme,\n themeName,\n });\n if (typeof defaultAlphaValue === 'number') {\n // add the css variables in \"@layer utilities\" for the alpha\n const alphaValue = defaultAlphaValue.toFixed(2);\n resolved.utilities[cssSelector][twcOpacityVariable] = alphaValue;\n addRootUtilities(resolved.utilities, {\n key: twcOpacityVariable,\n value: alphaValue,\n defaultTheme,\n themeName,\n });\n }\n // set the dynamic color in tailwind config theme.colors\n resolved.colors[colorName] = ({ opacityVariable, opacityValue }) => {\n // if the opacity is set with a slash (e.g. bg-primary/90), use the provided value\n if (!isNaN(+opacityValue)) {\n return `hsl(var(${twcColorVariable}) / ${opacityValue})`;\n }\n // if no opacityValue was provided (=it is not parsable to a number),\n // the twcOpacityVariable (opacity defined in the color definition rgb(0, 0, 0, 0.5))\n // should have the priority over the tw class based opacity(e.g. \"bg-opacity-90\")\n // This is how tailwind behaves as for v3.2.4\n if (opacityVariable) {\n return `hsl(var(${twcColorVariable}) / var(${twcOpacityVariable}, var(${opacityVariable})))`;\n }\n return `hsl(var(${twcColorVariable}) / var(${twcOpacityVariable}, 1))`;\n };\n });\n });\n\n return resolved;\n};\n\nexport const createThemes = <ThemeName extends string>(\n config: TwcConfig<ThemeName> = emptyConfig,\n options: TwcOptions<ThemeName> = {},\n) => {\n const resolved = resolveTwcConfig(config, options);\n\n return plugin(\n ({ addUtilities, addVariant }) => {\n // add the css variables to \"@layer utilities\" because:\n // - The Base layer does not provide intellisense\n // - The Components layer might get overriden by tailwind default colors in case of name clash\n addUtilities(resolved.utilities);\n // add the theme as variant e.g. \"theme-[name]:text-2xl\"\n resolved.variants.forEach(({ name, definition }) => addVariant(name, definition));\n },\n // extend the colors config\n {\n theme: {\n extend: {\n // @ts-ignore tailwind types are broken\n colors: resolved.colors,\n },\n },\n },\n );\n};\n\nfunction escapeChars(str: string, ...chars: string[]) {\n let result = str;\n for (let char of chars) {\n const regexp = new RegExp(char, 'g');\n result = str.replace(regexp, '\\\\' + char);\n }\n return result;\n}\n\nfunction flattenColors(colors: NestedColors) {\n const flatColorsWithDEFAULT: FlatColors = flatten(colors, {\n safe: true,\n delimiter: '-',\n });\n\n return Object.entries(flatColorsWithDEFAULT).reduce((acc, [key, value]) => {\n acc[key.replace(/\\-DEFAULT$/, '')] = value;\n return acc;\n }, {} as FlatColors);\n}\n\nfunction toHslaArray(colorValue?: string) {\n return Color(colorValue).hsl().round(1).array() as HslaArray;\n}\n\nfunction defaultProduceCssVariable(themeName: string) {\n return `--twc-${themeName}`;\n}\n\nfunction defaultProduceThemeClass(themeName: string) {\n return themeName;\n}\n\nfunction dark(colors: NestedColors): { [SCHEME]: 'dark' } & MaybeNested<string, string> {\n return {\n ...colors,\n [SCHEME]: 'dark',\n };\n}\n\nfunction light(colors: NestedColors): { [SCHEME]: 'light' } & MaybeNested<string, string> {\n return {\n ...colors,\n [SCHEME]: 'light',\n };\n}\n\nfunction generateVariantDefinitions(selector: string) {\n return [\n `${selector}&`,\n `:is(${selector} > &:not([data-theme]))`,\n `:is(${selector} &:not(${selector} [data-theme]:not(${selector}) * ))`,\n `:is(${selector}:not(:has([data-theme])) &:not([data-theme]))`,\n ];\n}\n\nfunction generateRootVariantDefinitions<ThemeName extends string>(\n themeName: ThemeName,\n defaultTheme: TwcOptions<ThemeName>['defaultTheme'],\n) {\n const baseDefinitions = [\n `:root&`,\n `:is(:root > &:not([data-theme]))`,\n `:is(:root &:not([data-theme] *):not([data-theme]))`,\n ];\n\n if (typeof defaultTheme === 'string' && themeName === defaultTheme) {\n return baseDefinitions;\n }\n\n if (typeof defaultTheme === 'object' && themeName === defaultTheme.light) {\n return baseDefinitions.map(\n (definition) => `@media (prefers-color-scheme: light){${definition}}`,\n );\n }\n\n if (typeof defaultTheme === 'object' && themeName === defaultTheme.dark) {\n return baseDefinitions.map(\n (definition) => `@media (prefers-color-scheme: dark){${definition}}`,\n );\n }\n return [];\n}\n\n// hande the defaultTheme utils\nfunction addRootUtilities<ThemeName extends string>(\n utilities: ResolvedUtilities,\n {\n key,\n value,\n defaultTheme,\n themeName,\n }: {\n key: string;\n value: string;\n defaultTheme: TwcOptions<ThemeName>['defaultTheme'];\n themeName: ThemeName;\n },\n) {\n if (!defaultTheme) return;\n if (typeof defaultTheme === 'string') {\n if (themeName === defaultTheme) {\n // initialize\n if (!utilities[':root']) {\n utilities[':root'] = {};\n }\n // set\n utilities[':root'][key] = value;\n }\n } else if (themeName === defaultTheme.light) {\n // initialize\n if (!utilities['@media (prefers-color-scheme: light)']) {\n utilities['@media (prefers-color-scheme: light)'] = {\n ':root': {},\n };\n }\n // set\n utilities['@media (prefers-color-scheme: light)'][':root'][key] = value;\n } else if (themeName === defaultTheme.dark) {\n // initialize\n if (!utilities['@media (prefers-color-scheme: dark)']) {\n utilities['@media (prefers-color-scheme: dark)'] = {\n ':root': {},\n };\n }\n // set\n utilities['@media (prefers-color-scheme: dark)'][':root'][key] = value;\n }\n}\n\ninterface MaybeNested<K extends keyof any = string, V extends string = string> {\n [key: string]: V | MaybeNested<K, V>;\n}\n\ntype NoInfer<T> = [T][T extends any ? 0 : never];\n\ntype HslaArray = [number, number, number, number | undefined];\n"],"mappings":";AAAA,OAAO,WAAW;AAClB,OAAO,YAAY;AACnB,OAAO,aAAa;AACpB,OAAO,aAAa;AAEpB,IAAM,SAAS,OAAO,cAAc;AACpC,IAAM,cAAyB,CAAC;AAgDzB,IAAM,mBAAmB,CAC7B,SAA+B,aAC/B;AAAA,EACG,qBAAqB;AAAA,EACrB,oBAAoB;AAAA,EACpB,sBAAsB;AAAA,EACtB;AAAA,EACA,SAAS;AACZ,IAA2B,CAAC,MAC1B;AACF,QAAM,WAAqB;AAAA,IACxB,UAAU,CAAC;AAAA,IACX,WAAW,CAAC;AAAA,IACZ,QAAQ,CAAC;AAAA,EACZ;AACA,QAAM,eAAe,OAAO,WAAW,aAAa,OAAO,EAAE,MAAM,MAAM,CAAC,IAAI;AAE9E,UAAQ,cAAc,CAAC,QAAsB,cAAyB;AACnE,UAAM,iBAAiB,kBAAkB,SAAS;AAClD,UAAM,eAAe,oBAAoB,SAAS;AAElD,UAAM,aAAa,cAAc,MAAM;AAEvC,aAAS,SAAS,KAAK;AAAA,MACpB,MAAM;AAAA;AAAA,MAEN,YAAY;AAAA,QACT,2BAA2B,IAAI,gBAAgB;AAAA,QAC/C,2BAA2B,gBAAgB,aAAa;AAAA,QACxD,+BAA+B,WAAW,YAAY;AAAA,MACzD,EAAE,KAAK;AAAA,IACV,CAAC;AAED,UAAM,cAAc,IAAI,+BAA+B;AAEvD,aAAS,UAAU,WAAW,IAAI,OAAO,MAAM,IAAI,EAAE,gBAAgB,OAAO,MAAM,EAAE,IAAI,CAAC;AAEzF,YAAQ,YAAY,CAAC,YAAY,cAAc;AAE5C,UAAK,cAAsB;AAAQ;AACnC,YAAM,gBAAgB,YAAY,WAAW,GAAG;AAChD,UAAI,CAAC,GAAG,GAAG,GAAG,iBAAiB,IAAe,CAAC,GAAG,GAAG,GAAG,CAAC;AACzD,UAAI;AACD,SAAC,GAAG,GAAG,GAAG,iBAAiB,IAAI,YAAY,UAAU;AAAA,MACxD,SAAS,OAAP;AACC,cAAM,UAAU;AAAA,sBAA2B,qBAAqB,eAAe,MAAM;AAErF,YAAI,QAAQ;AACT,gBAAM,IAAI,MAAM,OAAO;AAAA,QAC1B;AACA,eAAO,QAAQ,MAAM,OAAO;AAAA,MAC/B;AACA,YAAM,mBAAmB,mBAAmB,aAAa;AACzD,YAAM,qBAAqB,GAAG,mBAAmB,aAAa;AAE9D,YAAM,YAAY,GAAG,KAAK,MAAM;AAChC,eAAS,UAAU,WAAW,EAAE,gBAAgB,IAAI;AACpD,uBAAiB,SAAS,WAAW;AAAA,QAClC,KAAK;AAAA,QACL,OAAO;AAAA,QACP;AAAA,QACA;AAAA,MACH,CAAC;AACD,UAAI,OAAO,sBAAsB,UAAU;AAExC,cAAM,aAAa,kBAAkB,QAAQ,CAAC;AAC9C,iBAAS,UAAU,WAAW,EAAE,kBAAkB,IAAI;AACtD,yBAAiB,SAAS,WAAW;AAAA,UAClC,KAAK;AAAA,UACL,OAAO;AAAA,UACP;AAAA,UACA;AAAA,QACH,CAAC;AAAA,MACJ;AAEA,eAAS,OAAO,SAAS,IAAI,CAAC,EAAE,iBAAiB,aAAa,MAAM;AAEjE,YAAI,CAAC,MAAM,CAAC,YAAY,GAAG;AACxB,iBAAO,WAAW,uBAAuB;AAAA,QAC5C;AAKA,YAAI,iBAAiB;AAClB,iBAAO,WAAW,2BAA2B,2BAA2B;AAAA,QAC3E;AACA,eAAO,WAAW,2BAA2B;AAAA,MAChD;AAAA,IACH,CAAC;AAAA,EACJ,CAAC;AAED,SAAO;AACV;AAEO,IAAM,eAAe,CACzB,SAA+B,aAC/B,UAAiC,CAAC,MAChC;AACF,QAAM,WAAW,iBAAiB,QAAQ,OAAO;AAEjD,SAAO;AAAA,IACJ,CAAC,EAAE,cAAc,WAAW,MAAM;AAI/B,mBAAa,SAAS,SAAS;AAE/B,eAAS,SAAS,QAAQ,CAAC,EAAE,MAAM,WAAW,MAAM,WAAW,MAAM,UAAU,CAAC;AAAA,IACnF;AAAA;AAAA,IAEA;AAAA,MACG,OAAO;AAAA,QACJ,QAAQ;AAAA;AAAA,UAEL,QAAQ,SAAS;AAAA,QACpB;AAAA,MACH;AAAA,IACH;AAAA,EACH;AACH;AAEA,SAAS,YAAY,QAAgB,OAAiB;AACnD,MAAI,SAAS;AACb,WAAS,QAAQ,OAAO;AACrB,UAAM,SAAS,IAAI,OAAO,MAAM,GAAG;AACnC,aAAS,IAAI,QAAQ,QAAQ,OAAO,IAAI;AAAA,EAC3C;AACA,SAAO;AACV;AAEA,SAAS,cAAc,QAAsB;AAC1C,QAAM,wBAAoC,QAAQ,QAAQ;AAAA,IACvD,MAAM;AAAA,IACN,WAAW;AAAA,EACd,CAAC;AAED,SAAO,OAAO,QAAQ,qBAAqB,EAAE,OAAO,CAAC,KAAK,CAAC,KAAK,KAAK,MAAM;AACxE,QAAI,IAAI,QAAQ,cAAc,EAAE,CAAC,IAAI;AACrC,WAAO;AAAA,EACV,GAAG,CAAC,CAAe;AACtB;AAEA,SAAS,YAAY,YAAqB;AACvC,SAAO,MAAM,UAAU,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,MAAM;AACjD;AAEA,SAAS,0BAA0B,WAAmB;AACnD,SAAO,SAAS;AACnB;AAEA,SAAS,yBAAyB,WAAmB;AAClD,SAAO;AACV;AAEA,SAAS,KAAK,QAA0E;AACrF,SAAO;AAAA,IACJ,GAAG;AAAA,IACH,CAAC,MAAM,GAAG;AAAA,EACb;AACH;AAEA,SAAS,MAAM,QAA2E;AACvF,SAAO;AAAA,IACJ,GAAG;AAAA,IACH,CAAC,MAAM,GAAG;AAAA,EACb;AACH;AAEA,SAAS,2BAA2B,UAAkB;AACnD,SAAO;AAAA,IACJ,GAAG;AAAA,IACH,OAAO;AAAA,IACP,OAAO,kBAAkB,6BAA6B;AAAA,IACtD,OAAO;AAAA,EACV;AACH;AAEA,SAAS,+BACN,WACA,cACD;AACC,QAAM,kBAAkB;AAAA,IACrB;AAAA,IACA;AAAA,IACA;AAAA,EACH;AAEA,MAAI,OAAO,iBAAiB,YAAY,cAAc,cAAc;AACjE,WAAO;AAAA,EACV;AAEA,MAAI,OAAO,iBAAiB,YAAY,cAAc,aAAa,OAAO;AACvE,WAAO,gBAAgB;AAAA,MACpB,CAAC,eAAe,wCAAwC;AAAA,IAC3D;AAAA,EACH;AAEA,MAAI,OAAO,iBAAiB,YAAY,cAAc,aAAa,MAAM;AACtE,WAAO,gBAAgB;AAAA,MACpB,CAAC,eAAe,uCAAuC;AAAA,IAC1D;AAAA,EACH;AACA,SAAO,CAAC;AACX;AAGA,SAAS,iBACN,WACA;AAAA,EACG;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACH,GAMD;AACC,MAAI,CAAC;AAAc;AACnB,MAAI,OAAO,iBAAiB,UAAU;AACnC,QAAI,cAAc,cAAc;AAE7B,UAAI,CAAC,UAAU,OAAO,GAAG;AACtB,kBAAU,OAAO,IAAI,CAAC;AAAA,MACzB;AAEA,gBAAU,OAAO,EAAE,GAAG,IAAI;AAAA,IAC7B;AAAA,EACH,WAAW,cAAc,aAAa,OAAO;AAE1C,QAAI,CAAC,UAAU,sCAAsC,GAAG;AACrD,gBAAU,sCAAsC,IAAI;AAAA,QACjD,SAAS,CAAC;AAAA,MACb;AAAA,IACH;AAEA,cAAU,sCAAsC,EAAE,OAAO,EAAE,GAAG,IAAI;AAAA,EACrE,WAAW,cAAc,aAAa,MAAM;AAEzC,QAAI,CAAC,UAAU,qCAAqC,GAAG;AACpD,gBAAU,qCAAqC,IAAI;AAAA,QAChD,SAAS,CAAC;AAAA,MACb;AAAA,IACH;AAEA,cAAU,qCAAqC,EAAE,OAAO,EAAE,GAAG,IAAI;AAAA,EACpE;AACH;","names":[]}