From a4bcd0bfffd5a732af04d1f71358230398b3e5b4 Mon Sep 17 00:00:00 2001 From: Muhammad Sammy Date: Sat, 2 Jan 2021 19:57:35 +0200 Subject: [PATCH] feat: support dark mode classes (#67) --- src/cli/core/ClassesGenerator.ts | 249 +++++++++++++++++---------- src/cli/core/ConfigScanner.ts | 42 +++-- src/cli/lib/baseTemplateString.ts | 5 + src/cli/lib/defaultTailwindConfig.ts | 18 +- src/cli/lib/types/config.ts | 2 + 5 files changed, 193 insertions(+), 123 deletions(-) diff --git a/src/cli/core/ClassesGenerator.ts b/src/cli/core/ClassesGenerator.ts index 1583619a..0d3895ba 100644 --- a/src/cli/core/ClassesGenerator.ts +++ b/src/cli/core/ClassesGenerator.ts @@ -8,25 +8,32 @@ import {nonConfigurableClassNames} from '../lib/non-configurable'; import {AllClasses, Backgrounds, Layout, Borders, Tables, Effects, Interactivity, TransitionsAndAnimations, Transforms, Accessibility, SVG, FlexBox, Grid, Spacing, Sizing, Typography} from '../lib/types/classes'; -import {TConfigTheme, TTailwindCSSConfig} from '../lib/types/config'; +import {TConfigTheme, TTailwindCSSConfig, TConfigDarkMode} from '../lib/types/config'; +/** + * The class responsible for generating the types from a parsed config by ConfigScanner. + */ export class ClassesGenerator implements IGenerator { - private readonly prefix: string; - private readonly separator: string; - private readonly theme: Omit; - private readonly configScanner: ConfigScanner; - private readonly generatedRegularClasses: AllClasses; - private readonly generatedPseudoClasses: string[]; + private readonly _prefix: string; + private readonly _separator: string; + private readonly _darkMode: TConfigDarkMode; + private readonly _theme: Omit; + private readonly _configScanner: ConfigScanner; + /** The array of generated types for regular classes */ + private readonly _generatedRegularClasses: AllClasses; + /** The array of generated types for pseudo classes */ + private readonly _generatedPseudoClasses: string[]; constructor(tailwindConfig: TTailwindCSSConfig) { const configScanner = new ConfigScanner(tailwindConfig); - this.prefix = configScanner.getPrefix(); - this.separator = configScanner.getSeparator(); - this.theme = configScanner.getTheme(); - this.configScanner = configScanner; + this._prefix = configScanner.getPrefix(); + this._separator = configScanner.getSeparator(); + this._darkMode = configScanner.getDarkMode(); + this._theme = configScanner.getTheme(); + this._configScanner = configScanner; - this.generatedRegularClasses = { + this._generatedRegularClasses = { Accessibility: this.accessibility(), Backgrounds: this.backgrounds(), Borders: this.borders(), @@ -44,22 +51,22 @@ export class ClassesGenerator implements IGenerator { Typography: this.typography(), }; - this.generatedPseudoClasses = this.pseudoClasses(); + this._generatedPseudoClasses = this.pseudoClasses(); } public generate = (): string => { - const regularClassesTemplate = Object.keys(this.generatedRegularClasses) + const regularClassesTemplate = Object.keys(this._generatedRegularClasses) .map(classGroup => { return new ClassesGroupTemplateGenerator( - this.generatedRegularClasses[classGroup as keyof AllClasses], + this._generatedRegularClasses[classGroup as keyof AllClasses], classGroup, - this.prefix, + this._prefix, ).generate(); }) .join('\n'); const pseudoClassesTemplate = - 'export type TPseudoClasses =' + generateTypes(this.generatedPseudoClasses); + 'export type TPseudoClasses =' + generateTypes(this._generatedPseudoClasses); return regularClassesTemplate + '\n\n' + pseudoClassesTemplate; }; @@ -67,15 +74,15 @@ export class ClassesGenerator implements IGenerator { private layout = (): Layout => { return { ...nonConfigurableClassNames.layout, - objectPosition: Object.keys(this.theme.objectPosition).map(x => 'object-' + x), - inset: Object.keys(this.theme.inset).flatMap(insetValue => { + objectPosition: Object.keys(this._theme.objectPosition).map(x => 'object-' + x), + inset: Object.keys(this._theme.inset).flatMap(insetValue => { return ['inset', 'inset-x', 'inset-y', 'top', 'right', 'bottom', 'left'].map(side => insetValue.startsWith('-') ? `-${side}-${insetValue.substring(1)}` : `${side}-${insetValue}`, ); }), - zIndex: Object.keys(this.theme.zIndex).flatMap(zIndexValue => + zIndex: Object.keys(this._theme.zIndex).flatMap(zIndexValue => zIndexValue.startsWith('-') ? `-z-${zIndexValue.substring(1)}` : `z-${zIndexValue}`, ), }; @@ -86,9 +93,9 @@ export class ClassesGenerator implements IGenerator { ...nonConfigurableClassNames.backgrounds, backgroundOpacity: this.getGeneratedClassesWithOpacities().backgroundOpacities, backgroundColor: this.generateClassesWithColors('backgroundColor'), - backgroundPosition: Object.keys(this.theme.backgroundPosition).map(x => 'bg-' + x), - backgroundSize: Object.keys(this.theme.backgroundSize).map(x => 'bg-' + x), - backgroundImage: Object.keys(this.theme.backgroundImage).map(x => 'bg-' + x), + backgroundPosition: Object.keys(this._theme.backgroundPosition).map(x => 'bg-' + x), + backgroundSize: Object.keys(this._theme.backgroundSize).map(x => 'bg-' + x), + backgroundImage: Object.keys(this._theme.backgroundImage).map(x => 'bg-' + x), gradientColorStops: this.generateClassesWithColors('gradientColorStops').flatMap(val => ['from', 'via', 'to'].map(x => x + val.replace('gradient', '')), ), @@ -100,11 +107,11 @@ export class ClassesGenerator implements IGenerator { ...nonConfigurableClassNames.borders, borderColor: this.generateClassesWithColors('borderColor'), borderOpacity: this.getGeneratedClassesWithOpacities().borderOpacities, - borderRadius: Object.keys(this.theme.borderRadius).flatMap(radius => { + borderRadius: Object.keys(this._theme.borderRadius).flatMap(radius => { const sides = ['t', 'r', 'b', 'l', 'tr', 'tl', 'br', 'bl']; return sides.map(side => `rounded-${side}-${radius}`).concat(`rounded-${radius}`); }), - borderWidth: Object.keys(this.theme.borderWidth).flatMap(width => { + borderWidth: Object.keys(this._theme.borderWidth).flatMap(width => { const sides = ['t', 'r', 'b', 'l']; return sides.map(side => `border-${side}-${width}`).concat(`border-${width}`); }), @@ -113,7 +120,7 @@ export class ClassesGenerator implements IGenerator { // divide width inherits its values from theme.borderWidth by default // but theme.divideWidth overrides it. divideWidth: Object.keys( - _.isEmpty(this.theme.divideWidth) ? this.theme.borderWidth : this.theme.divideWidth, + _.isEmpty(this._theme.divideWidth) ? this._theme.borderWidth : this._theme.divideWidth, ) .concat('reverse') .flatMap(width => ['x', 'y'].map(axis => `divide-${axis}-${width}`)), @@ -127,7 +134,7 @@ export class ClassesGenerator implements IGenerator { private effects = (): Effects => { return { ...nonConfigurableClassNames.effects, - boxShadow: Object.keys(this.theme.boxShadow).map(key => `shadow-${key}`), + boxShadow: Object.keys(this._theme.boxShadow).map(key => `shadow-${key}`), opacity: this.getGeneratedClassesWithOpacities().opacities, }; }; @@ -135,17 +142,17 @@ export class ClassesGenerator implements IGenerator { private transitionsAndAnimations = (): TransitionsAndAnimations => { return { ...nonConfigurableClassNames.transitionsAndAnimations, - transitionProperty: Object.keys(this.theme.transitionProperty).map( + transitionProperty: Object.keys(this._theme.transitionProperty).map( property => 'transition-' + property, ), - transitionDuration: Object.keys(this.theme.transitionDuration).map( + transitionDuration: Object.keys(this._theme.transitionDuration).map( value => 'duration-' + value, ), - transitionTimingFunction: Object.keys(this.theme.transitionTimingFunction).map( + transitionTimingFunction: Object.keys(this._theme.transitionTimingFunction).map( value => 'ease-' + value, ), - transitionDelay: Object.keys(this.theme.transitionDelay).map(value => 'delay-' + value), - animation: Object.keys(this.theme.animation).map(val => 'animate-' + val), + transitionDelay: Object.keys(this._theme.transitionDelay).map(value => 'delay-' + value), + animation: Object.keys(this._theme.animation).map(val => 'animate-' + val), }; }; @@ -153,16 +160,16 @@ export class ClassesGenerator implements IGenerator { return { ...nonConfigurableClassNames.transforms, scale: ['', 'x-', 'y-'].flatMap(x => - Object.keys(this.theme.scale).map(value => 'scale-' + x + value), + Object.keys(this._theme.scale).map(value => 'scale-' + x + value), ), - rotate: Object.keys(this.theme.rotate).map(value => + rotate: Object.keys(this._theme.rotate).map(value => value.startsWith('-') ? '-rotate-' + value.slice(1) : `rotate-${value}`, ), // translate gets values from theme.spacing in addition to 50% and 100% variations // by default and theme.translate overrides this behaviour. translate: ['x', 'y'].flatMap(side => { return Object.keys( - _.isEmpty(this.theme.translate) ? this.theme.spacing : this.theme.translate, + _.isEmpty(this._theme.translate) ? this._theme.spacing : this._theme.translate, ).map(value => value.startsWith('-') ? `-translate-${side}-${value.slice(1)}` @@ -170,28 +177,28 @@ export class ClassesGenerator implements IGenerator { ); }), skew: ['x', 'y'].flatMap(side => - Object.keys(this.theme.skew).map(value => + Object.keys(this._theme.skew).map(value => value.startsWith('-') ? `-skew-${side}-${value.substring(1)}` : `skew-${side}-${value}`, ), ), - transformOrigin: Object.keys(this.theme.transformOrigin).map(value => 'origin-' + value), + transformOrigin: Object.keys(this._theme.transformOrigin).map(value => 'origin-' + value), }; }; private interactivity = (): Interactivity => { return { ...nonConfigurableClassNames.interactivity, - cursor: Object.keys(this.theme.cursor).map(x => 'cursor-' + x), - outline: Object.keys(this.theme.outline).map(x => 'outline-' + x), + cursor: Object.keys(this._theme.cursor).map(x => 'cursor-' + x), + outline: Object.keys(this._theme.outline).map(x => 'outline-' + x), }; }; private SVG = (): SVG => { return { ...nonConfigurableClassNames.svg, - fill: Object.keys(this.theme.fill).map(value => 'fill-' + value), - stroke: Object.keys(this.theme.stroke).map(value => 'stroke-' + value), - strokeWidth: Object.keys(this.theme.strokeWidth).map(value => 'stroke-' + value), + fill: Object.keys(this._theme.fill).map(value => 'fill-' + value), + stroke: Object.keys(this._theme.stroke).map(value => 'stroke-' + value), + strokeWidth: Object.keys(this._theme.strokeWidth).map(value => 'stroke-' + value), }; }; @@ -204,30 +211,30 @@ export class ClassesGenerator implements IGenerator { private flexBox = (): FlexBox => { return { ...nonConfigurableClassNames.flexBox, - flexGrow: Object.keys(this.theme.flexGrow).map(key => `flex-grow-${key}`), - flexShrink: Object.keys(this.theme.flexShrink).map(key => `flex-shrink-${key}`), - order: Object.keys(this.theme.order).map(key => `order-${key}`), + flexGrow: Object.keys(this._theme.flexGrow).map(key => `flex-grow-${key}`), + flexShrink: Object.keys(this._theme.flexShrink).map(key => `flex-shrink-${key}`), + order: Object.keys(this._theme.order).map(key => `order-${key}`), }; }; private grid = (): Grid => { return { ...nonConfigurableClassNames.grid, - gridTemplateColumns: Object.keys(this.theme.gridTemplateColumns).map( + gridTemplateColumns: Object.keys(this._theme.gridTemplateColumns).map( key => `grid-cols-${key}`, ), - gridAutoColumns: Object.keys(this.theme.gridAutoColumns).map(key => `auto-cols-${key}`), - gridColumn: Object.keys(this.theme.gridColumn).map(key => `col-${key}`), - gridColumnStart: Object.keys(this.theme.gridColumnStart).map(key => `col-start-${key}`), - gridColumnEnd: Object.keys(this.theme.gridColumnEnd).map(key => `col-end-${key}`), - gridTemplateRows: Object.keys(this.theme.gridTemplateRows).map(key => `grid-rows-${key}`), - gridAutoRows: Object.keys(this.theme.gridAutoRows).map(key => `auto-rows-${key}`), - gridRow: Object.keys(this.theme.gridRow).map(key => `row-${key}`), - gridRowStart: Object.keys(this.theme.gridRowStart).map(key => `row-start-${key}`), - gridRowEnd: Object.keys(this.theme.gridRowEnd).map(key => `row-end-${key}`), + gridAutoColumns: Object.keys(this._theme.gridAutoColumns).map(key => `auto-cols-${key}`), + gridColumn: Object.keys(this._theme.gridColumn).map(key => `col-${key}`), + gridColumnStart: Object.keys(this._theme.gridColumnStart).map(key => `col-start-${key}`), + gridColumnEnd: Object.keys(this._theme.gridColumnEnd).map(key => `col-end-${key}`), + gridTemplateRows: Object.keys(this._theme.gridTemplateRows).map(key => `grid-rows-${key}`), + gridAutoRows: Object.keys(this._theme.gridAutoRows).map(key => `auto-rows-${key}`), + gridRow: Object.keys(this._theme.gridRow).map(key => `row-${key}`), + gridRowStart: Object.keys(this._theme.gridRowStart).map(key => `row-start-${key}`), + gridRowEnd: Object.keys(this._theme.gridRowEnd).map(key => `row-end-${key}`), gap: ['gap-', 'gap-y-', 'gap-x-'].flatMap(x => { // grid gap inherits its values from theme.spacing by default, but theme.gap overrides it. - return Object.keys(_.isEmpty(this.theme.gap) ? this.theme.spacing : this.theme.gap).map( + return Object.keys(_.isEmpty(this._theme.gap) ? this._theme.spacing : this._theme.gap).map( gapValue => x + gapValue, ); }), @@ -239,20 +246,20 @@ export class ClassesGenerator implements IGenerator { return { padding: sides.flatMap(side => { return Object.keys( - _.isEmpty(this.theme.padding) ? this.theme.spacing : this.theme.padding, + _.isEmpty(this._theme.padding) ? this._theme.spacing : this._theme.padding, ).map(value => value.startsWith('-') ? `-p${side}-${value.slice(1)}` : `p${side}-${value}`, ); }), margin: sides.flatMap(side => { return Object.keys( - _.isEmpty(this.theme.margin) ? this.theme.spacing : this.theme.margin, + _.isEmpty(this._theme.margin) ? this._theme.spacing : this._theme.margin, ).map(value => value.startsWith('-') ? `-m${side}-${value.slice(1)}` : `m${side}-${value}`, ); }), space: ['x', 'y'].flatMap(axis => { - return Object.keys(_.isEmpty(this.theme.space) ? this.theme.spacing : this.theme.space) + return Object.keys(_.isEmpty(this._theme.space) ? this._theme.spacing : this._theme.space) .concat('reverse') .map(key => { if (key.startsWith('-')) { @@ -278,31 +285,31 @@ export class ClassesGenerator implements IGenerator { // width values come from theme.spacing + `extraWidthSizing` by default // and theme.width overrides this default behaviour. // prettier-ignore - width: (_.isEmpty(this.theme.width) - ? Object.keys(this.theme.spacing).concat(extraWidthSizing) - : Object.keys(this.theme.width)).map(x => 'w-' + x), - minWidth: Object.keys(this.theme.minWidth).map(x => 'min-w-' + x), - maxWidth: Object.keys(this.theme.maxWidth).map(x => 'max-w-' + x), + width: (_.isEmpty(this._theme.width) + ? Object.keys(this._theme.spacing).concat(extraWidthSizing) + : Object.keys(this._theme.width)).map(x => 'w-' + x), + minWidth: Object.keys(this._theme.minWidth).map(x => 'min-w-' + x), + maxWidth: Object.keys(this._theme.maxWidth).map(x => 'max-w-' + x), // height values come from theme.spacing + `extraHeightSizing` by default // and overridden by theme.height. // prettier-ignore - height: (_.isEmpty(this.theme.height) - ? Object.keys(this.theme.spacing).concat(extraHeightSizing) - : Object.keys(this.theme.height)).map(x => 'h-' + x), - minHeight: Object.keys(this.theme.minHeight).map(x => 'min-h-' + x), - maxHeight: Object.keys(this.theme.maxHeight).map(x => 'max-h-' + x), + height: (_.isEmpty(this._theme.height) + ? Object.keys(this._theme.spacing).concat(extraHeightSizing) + : Object.keys(this._theme.height)).map(x => 'h-' + x), + minHeight: Object.keys(this._theme.minHeight).map(x => 'min-h-' + x), + maxHeight: Object.keys(this._theme.maxHeight).map(x => 'max-h-' + x), }; }; private typography = (): Typography => { return { ...nonConfigurableClassNames.typography, - fontFamily: Object.keys(this.theme.fontFamily).map(value => 'font-' + value), - fontSize: Object.keys(this.theme.fontSize).map(size => 'text-' + size), - fontWeight: Object.keys(this.theme.fontWeight).map(weight => 'font-' + weight), - letterSpacing: Object.keys(this.theme.letterSpacing).map(value => 'tracking-' + value), - lineHeight: Object.keys(this.theme.lineHeight).map(value => 'leading-' + value), - listStyleType: Object.keys(this.theme.listStyleType).map(value => 'list-' + value), + fontFamily: Object.keys(this._theme.fontFamily).map(value => 'font-' + value), + fontSize: Object.keys(this._theme.fontSize).map(size => 'text-' + size), + fontWeight: Object.keys(this._theme.fontWeight).map(weight => 'font-' + weight), + letterSpacing: Object.keys(this._theme.letterSpacing).map(value => 'tracking-' + value), + lineHeight: Object.keys(this._theme.lineHeight).map(value => 'leading-' + value), + listStyleType: Object.keys(this._theme.listStyleType).map(value => 'list-' + value), placeholderColor: this.generateClassesWithColors('placeholderColor'), placeholderOpacity: this.getGeneratedClassesWithOpacities().placeholderOpacities, textColor: this.generateClassesWithColors('textColor'), @@ -310,46 +317,98 @@ export class ClassesGenerator implements IGenerator { }; }; + // Generate the types for pseudo classes as hover:, focus: etc. + // and return them in a string array to be parsed and coverted into a template string that + // will be a part of the final generated file. See ClassesGroupTemplateGenerator class. private pseudoClasses = (): string[] => { + // Initialise a pseudoclasses variable with empty array value. const pseudoClasses: string[] = []; - // HACK + // HACK: This block is just to make accessibility object align with other types object shape const variantsConfig = Object.entries( - _.merge(this.configScanner.getVariants(), { - screenReaders: this.configScanner.getVariants().accessibility, + _.merge(this._configScanner.getVariants(), { + screenReaders: this._configScanner.getVariants().accessibility, }), ); - for (const [variantsKey, variantsForKey] of variantsConfig) { - Object.keys(this.generatedRegularClasses).map(key => { - if (_.has(this.generatedRegularClasses[key as keyof AllClasses], variantsKey)) { - const generatedClass = _.get( - this.generatedRegularClasses, - `${key}.${variantsKey}`, + // For every key-value pair in the variants section in tailwind config... + for (const [regularClassGroupKey, pseudoClassesVariantsForKey] of variantsConfig) { + // Find all matching names from the generated regular classes with the key of the variants config + Object.keys(this._generatedRegularClasses).map(key => { + // If the current key is found to be a member of the generated regular classes group... + if (_.has(this._generatedRegularClasses[key as keyof AllClasses], regularClassGroupKey)) { + // Get the value of the found generated class group + const generatedClassGroup = _.get( + this._generatedRegularClasses, + `${key}.${regularClassGroupKey}`, ) as string[]; - generatedClass.map(classname => { - variantsForKey.map(variant => { + + // For every member of the found regular classes group... + generatedClassGroup.map(classname => { + const isDarkModeEnabled = this._darkMode !== false; + + // Generate the classname of each variant... + pseudoClassesVariantsForKey.map(variant => { + // Get the breakpoints from config + + // If the variant is the responsive variant (md:, lg:)... if (variant === 'responsive') { - const [breakpoints] = this.configScanner.getThemeProperty('screens'); + // Create the classname for each breakpoint + const [breakpoints] = this._configScanner.getThemeProperty('screens'); breakpoints.map((breakpointVariant: string) => { - pseudoClasses.push(breakpointVariant + this.separator + this.prefix + classname); + // Push the created classes to the pseudoClasses array + pseudoClasses.push( + breakpointVariant + this._separator + this._prefix + classname, + ); + + // Add stackable dark and responsive pseudoclasses if the key has both as variants + if (pseudoClassesVariantsForKey.includes('dark') && isDarkModeEnabled) { + pseudoClasses.push( + breakpointVariant + + this._separator + + 'dark' + + this._separator + + this._prefix + + classname, + ); + } }); - } else { - pseudoClasses.push(variant + this.separator + this.prefix + classname); - if (variant.startsWith('group') && !pseudoClasses.includes('group')) - pseudoClasses.push(this.prefix + 'group'); + } + // Otherwise if the variant is 'dark' + else if (variant === 'dark') { + // If the dark mode is enabled... + if (isDarkModeEnabled) { + // Add the 'dark' prefix to the classname to create its pseudoclass + pseudoClasses.push(variant + this._separator + this._prefix + classname); + } + // Otherwise, do nothing. + } + // Otherwise... + else { + // Append the variant to the classname and push to the pseudoClasses array. + pseudoClasses.push(variant + this._separator + this._prefix + classname); + + // Add 'group' class if a the variant is group-hover, group-focus etc. + if (variant.startsWith(this._prefix + 'group') && !pseudoClasses.includes('group')) + pseudoClasses.push(this._prefix + 'group'); + + // Add 'dark' class if dark mode stategy is set to "class" + if (this._darkMode === 'class' && !pseudoClasses.includes('dark')) + pseudoClasses.push(this._prefix + 'dark'); } }); }); } + // Otherwise, skip and do nothing }); } + // After all is done, return the generated pseudo classes types array return pseudoClasses; }; private generateClassesWithColors = (property: ClassesWithColors): string[] => { - const [propertyKeys, propertyValues] = this.configScanner.getThemeProperty(property); + const [propertyKeys, propertyValues] = this._configScanner.getThemeProperty(property); return propertyKeys .filter(k => k !== 'default') // exclude `default` keys @@ -370,13 +429,13 @@ export class ClassesGenerator implements IGenerator { }; private getGeneratedClassesWithOpacities = (): ClassesWithOpacities => { - const allOpacities = this.configScanner.getTheme().opacity; + const allOpacities = this._configScanner.getTheme().opacity; // prettier-ignore type TOpacityProp = | 'divideOpacity' | 'textOpacity' | 'backgroundOpacity' | 'borderOpacity' | 'placeholderOpacity' const getOpacity = (themePropertyName: TOpacityProp, outputNamePrefix: string): string[] => { - const generatedOpacities = generateOpacities(allOpacities, this.theme, themePropertyName); + const generatedOpacities = generateOpacities(allOpacities, this._theme, themePropertyName); return Object.keys(generatedOpacities).map( opacity => `${outputNamePrefix}-opacity-${opacity}`, diff --git a/src/cli/core/ConfigScanner.ts b/src/cli/core/ConfigScanner.ts index f5704aee..3fce3873 100644 --- a/src/cli/core/ConfigScanner.ts +++ b/src/cli/core/ConfigScanner.ts @@ -1,42 +1,48 @@ import _ from 'lodash'; import {defaultTailwindConfig} from '../lib/defaultTailwindConfig'; -import {TTailwindCSSConfig, TConfigVariants} from '../lib/types/config'; +import {TTailwindCSSConfig, TConfigVariants, TConfigDarkMode} from '../lib/types/config'; import {TConfigTheme, TThemeItems} from '../lib/types/config'; /* eslint-disable @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-return */ export class ConfigScanner { - private readonly prefix: string; - private readonly separator: string; - private themeConfig: TConfigTheme; - private readonly variantsConfig: TConfigVariants; + private readonly _prefix: string; + private readonly _separator: string; + private readonly _darkMode: TConfigDarkMode; + private _themeConfig: TConfigTheme; + private readonly _variantsConfig: TConfigVariants; constructor(tailwindConfig: TTailwindCSSConfig) { - this.prefix = _.isEmpty(tailwindConfig?.prefix) ? '' : (tailwindConfig.prefix as string); - this.separator = _.isEmpty(tailwindConfig.separator) + this._prefix = _.isEmpty(tailwindConfig?.prefix) ? '' : (tailwindConfig.prefix as string); + this._darkMode = _.isEmpty(tailwindConfig?.darkMode) + ? false + : (tailwindConfig.darkMode as TConfigDarkMode); + this._separator = _.isEmpty(tailwindConfig.separator) ? ':' : (tailwindConfig.separator as string); - this.variantsConfig = _.isEmpty(tailwindConfig.variants) + this._variantsConfig = _.isEmpty(tailwindConfig.variants) ? defaultTailwindConfig.variants // Order does matter, defaultVariants will be overridden by themeVariants. : {...defaultTailwindConfig.variants, ...tailwindConfig.variants}; - this.themeConfig = {...defaultTailwindConfig.theme, ...tailwindConfig.theme}; + this._themeConfig = {...defaultTailwindConfig.theme, ...tailwindConfig.theme}; } - public getPrefix = (): string => this.prefix; + public getPrefix = (): string => this._prefix; - public getSeparator = (): string => this.separator; + public getDarkMode = (): TConfigDarkMode => this._darkMode; + + public getSeparator = (): string => this._separator; public getTheme = (): TThemeItems => { const evaluateCoreTheme = (): TThemeItems => { - const coreTheme = _.omit(this.themeConfig, 'extend'); + const coreTheme = _.omit(this._themeConfig, 'extend'); const valueEvaluator = new ThemeClosuresEvaluator(coreTheme); - for (const [key, value] of Object.entries(this.themeConfig)) { + for (const [key, value] of Object.entries(this._themeConfig)) { coreTheme[key as keyof TThemeItems] = valueEvaluator.evaluate(value); } return coreTheme; }; const evaluateThemeExtend = (): Partial => { - const themeExtend = this.themeConfig.extend; + const themeExtend = this._themeConfig.extend; if (themeExtend) { const valueEvaluator = new ThemeClosuresEvaluator(themeExtend); for (const [key, value] of Object.entries(themeExtend)) @@ -45,13 +51,13 @@ export class ConfigScanner { return themeExtend; }; - this.themeConfig = _.merge(evaluateCoreTheme(), evaluateThemeExtend()); - delete this.themeConfig?.extend; + this._themeConfig = _.merge(evaluateCoreTheme(), evaluateThemeExtend()); + delete this._themeConfig?.extend; - return this.themeConfig; + return this._themeConfig; }; - public getVariants = (): TConfigVariants => this.variantsConfig; + public getVariants = (): TConfigVariants => this._variantsConfig; public getThemeProperty = ( themeProperty: keyof TThemeItems, diff --git a/src/cli/lib/baseTemplateString.ts b/src/cli/lib/baseTemplateString.ts index ce929d82..7b708b9b 100644 --- a/src/cli/lib/baseTemplateString.ts +++ b/src/cli/lib/baseTemplateString.ts @@ -1,5 +1,10 @@ export const baseTemplateString = `/* eslint-disable */ /* tslint:disable */ + +////////////////////////////////////////////////////////////////////////////////////////////////// +// Autogenerated by 'tailwindcss-classnames' CLI. Do not edit this file directly. +////////////////////////////////////////////////////////////////////////////////////////////////// + import classnamesLib from 'classnames'; T_CUSTOM_CLASSES_IMPORT_STATEMENT ___ALL_CLASSES___ diff --git a/src/cli/lib/defaultTailwindConfig.ts b/src/cli/lib/defaultTailwindConfig.ts index 3f019248..f4942d1b 100644 --- a/src/cli/lib/defaultTailwindConfig.ts +++ b/src/cli/lib/defaultTailwindConfig.ts @@ -715,7 +715,7 @@ export const defaultTailwindConfig = { appearance: ['responsive'], backgroundAttachment: ['responsive'], backgroundClip: ['responsive'], - backgroundColor: ['responsive', 'hover', 'focus'], + backgroundColor: ['responsive', 'dark', 'group-hover', 'focus-within', 'hover', 'focus'], backgroundImage: ['responsive'], gradientColorStops: ['responsive', 'hover', 'focus'], backgroundOpacity: ['responsive', 'hover', 'focus'], @@ -723,8 +723,7 @@ export const defaultTailwindConfig = { backgroundRepeat: ['responsive'], backgroundSize: ['responsive'], borderCollapse: ['responsive'], - borderColor: ['responsive', 'hover', 'focus'], - borderOpacity: ['responsive', 'hover', 'focus'], + borderColor: ['responsive', 'dark', 'group-hover', 'focus-within', 'hover', 'focus'], borderRadius: ['responsive'], borderStyle: ['responsive'], borderWidth: ['responsive'], @@ -733,7 +732,7 @@ export const defaultTailwindConfig = { container: ['responsive'], cursor: ['responsive'], display: ['responsive'], - divideColor: ['responsive'], + divideColor: ['responsive', 'dark'], divideOpacity: ['responsive'], divideStyle: ['responsive'], divideWidth: ['responsive'], @@ -749,8 +748,7 @@ export const defaultTailwindConfig = { fontSize: ['responsive'], fontSmoothing: ['responsive'], fontVariantNumeric: ['responsive'], - fontStyle: ['responsive'], - fontWeight: ['responsive', 'hover', 'focus'], + gradientColorStops: ['responsive', 'dark', 'hover', 'focus'], height: ['responsive'], inset: ['responsive'], justifyContent: ['responsive'], @@ -776,19 +774,19 @@ export const defaultTailwindConfig = { placeContent: ['responsive'], placeItems: ['responsive'], placeSelf: ['responsive'], - placeholderColor: ['responsive', 'focus'], + placeholderColor: ['responsive', 'dark', 'focus'], placeholderOpacity: ['responsive', 'focus'], pointerEvents: ['responsive'], position: ['responsive'], resize: ['responsive'], + ringColor: ['responsive', 'dark', 'focus-within', 'focus'], + ringOffsetColor: ['responsive', 'dark', 'focus-within', 'focus'], space: ['responsive'], stroke: ['responsive'], strokeWidth: ['responsive'], tableLayout: ['responsive'], textAlign: ['responsive'], - textColor: ['responsive', 'hover', 'focus'], - textOpacity: ['responsive', 'hover', 'focus'], - textDecoration: ['responsive', 'hover', 'focus'], + textColor: ['responsive', 'dark', 'group-hover', 'focus-within', 'hover', 'focus'], textTransform: ['responsive'], userSelect: ['responsive'], verticalAlign: ['responsive'], diff --git a/src/cli/lib/types/config.ts b/src/cli/lib/types/config.ts index 16b23ccc..9d7adaf7 100644 --- a/src/cli/lib/types/config.ts +++ b/src/cli/lib/types/config.ts @@ -4,6 +4,8 @@ export type TTailwindCSSConfig = Partial< typeof defaultTailwindConfig & Record<'separator' | 'prefix', string> >; +export type TConfigDarkMode = false | 'media' | 'class'; + export type TConfigTheme = TThemeItems & {extend?: TThemeItems}; export type TConfigVariants = typeof defaultTailwindConfig.variants;