feat(module): handle variants with dynamic colors

This commit is contained in:
Benjamin Canac
2022-06-30 17:16:22 +02:00
parent f98253c255
commit 5a8b078d65
5 changed files with 474 additions and 457 deletions

View File

@@ -20,7 +20,8 @@ export default defineNuxtConfig({
}, },
ui: { ui: {
colors: { colors: {
primary: 'blue' primary: 'blue',
gray: 'zinc'
}, },
preset: { preset: {
}, },

View File

@@ -1,9 +1,21 @@
import { defineNuxtModule, installModule, addComponentsDir, addTemplate, addPlugin, resolveModule, createResolver } from '@nuxt/kit' import { defineNuxtModule, installModule, addComponentsDir, addTemplate, addPlugin, createResolver } from '@nuxt/kit'
import { defu, defuArrayFn } from 'defu' import { defu } from 'defu'
import type { TailwindConfig } from 'tailwindcss/tailwind-config'
import colors from 'tailwindcss/colors.js' import colors from 'tailwindcss/colors.js'
import type { TailwindConfig } from 'tailwindcss/tailwind-config'
import { name, version } from '../package.json' import { name, version } from '../package.json'
import { safeColorsAsRegex } from './runtime/utils/colors' import { colorsAsRegex, excludeColors } from './runtime/utils/colors'
import defaultPreset from './runtime/presets/default'
// @ts-ignore
delete colors.lightBlue
// @ts-ignore
delete colors.warmGray
// @ts-ignore
delete colors.trueGray
// @ts-ignore
delete colors.coolGray
// @ts-ignore
delete colors.blueGray
interface ColorsOptions { interface ColorsOptions {
/** /**
@@ -12,16 +24,13 @@ interface ColorsOptions {
primary?: string primary?: string
/** /**
* @default 'zinc' * @default 'gray'
*/ */
gray?: string gray?: string
} }
export interface ModuleOptions { export interface ModuleOptions {
/** preset?: object
* @default 'tailwindui'
*/
preset?: string | object
/** /**
* @default 'u' * @default 'u'
@@ -34,11 +43,11 @@ export interface ModuleOptions {
} }
const defaults = { const defaults = {
preset: 'default', preset: {},
prefix: 'u', prefix: 'u',
colors: { colors: {
primary: 'indigo', primary: 'indigo',
gray: 'zinc' gray: 'gray'
}, },
tailwindcss: { tailwindcss: {
theme: {} theme: {}
@@ -57,7 +66,7 @@ export default defineNuxtModule<ModuleOptions>({
}, },
defaults, defaults,
async setup (options, nuxt) { async setup (options, nuxt) {
const { preset, prefix, colors: { primary = 'indigo', gray = 'zinc' } = {}, tailwindcss: { theme = {} } = {} } = options const { preset = {}, prefix, colors: { primary = 'indigo', gray = 'gray' } = {}, tailwindcss: { theme = {} } = {} } = options
const { resolve } = createResolver(import.meta.url) const { resolve } = createResolver(import.meta.url)
@@ -66,35 +75,25 @@ export default defineNuxtModule<ModuleOptions>({
nuxt.options.build.transpile.push(runtimeDir) nuxt.options.build.transpile.push(runtimeDir)
nuxt.options.build.transpile.push('@popperjs/core', '@headlessui/vue', '@iconify/vue') nuxt.options.build.transpile.push('@popperjs/core', '@headlessui/vue', '@iconify/vue')
await installModule('@nuxtjs/color-mode', { classSuffix: '' })
await installModule('@nuxtjs/tailwindcss', {
viewer: false,
config: {
darkMode: 'class',
theme: defuArrayFn({
extend: {
colors: {
// @ts-ignore // @ts-ignore
gray: typeof gray === 'object' ? gray : (colors && colors[gray]), nuxt.hook('tailwindcss:config', function (tailwindConfig: TailwindConfig) {
const globalColors = {
...(tailwindConfig.theme.colors || colors),
...tailwindConfig.theme.extend?.colors
}
// @ts-ignore // @ts-ignore
primary: typeof primary === 'object' ? primary : (colors && colors[primary]) tailwindConfig.theme.extend.colors = tailwindConfig.theme.extend.colors || {}
} // @ts-ignore
} globalColors.primary = tailwindConfig.theme.extend.colors.primary = globalColors[primary]
}, theme), // @ts-ignore
plugins: [ globalColors.gray = tailwindConfig.theme.extend.colors.gray = globalColors[gray]
require('@tailwindcss/forms'),
require('@tailwindcss/line-clamp'), const variantColors = excludeColors(globalColors)
require('@tailwindcss/aspect-ratio'), const safeColorsAsRegex = colorsAsRegex(variantColors)
require('@tailwindcss/typography')
], tailwindConfig.safelist = tailwindConfig.safelist || []
content: [ tailwindConfig.safelist.push(...[{
resolve(runtimeDir, 'components/**/*.{vue,js,ts}'),
resolve(runtimeDir, 'presets/*.{mjs,js,ts}')
],
// Safelist dynamic colors used in preset
safelist: [
'dark',
{
pattern: new RegExp(`bg-(${safeColorsAsRegex})-400`) pattern: new RegExp(`bg-(${safeColorsAsRegex})-400`)
}, },
{ {
@@ -108,7 +107,34 @@ export default defineNuxtModule<ModuleOptions>({
{ {
pattern: new RegExp(`ring-(${safeColorsAsRegex})-(500)`), pattern: new RegExp(`ring-(${safeColorsAsRegex})-(500)`),
variants: ['focus'] variants: ['focus']
}, }])
const ui: object = defu(preset, defaultPreset(variantColors))
addTemplate({
filename: 'ui.mjs',
getContents: () => `export default ${JSON.stringify(ui)}`
})
})
await installModule('@nuxtjs/color-mode', { classSuffix: '' })
await installModule('@nuxtjs/tailwindcss', {
viewer: false,
config: {
darkMode: 'class',
theme,
plugins: [
require('@tailwindcss/forms'),
require('@tailwindcss/line-clamp'),
require('@tailwindcss/aspect-ratio'),
require('@tailwindcss/typography')
],
content: [
resolve(runtimeDir, 'components/**/*.{vue,js,ts}'),
resolve(runtimeDir, 'presets/*.{mjs,js,ts}')
],
safelist: [
'dark',
{ {
pattern: /rounded-(sm|md|lg|xl|2xl|3xl)/, pattern: /rounded-(sm|md|lg|xl|2xl|3xl)/,
variants: ['sm'] variants: ['sm']
@@ -118,26 +144,6 @@ export default defineNuxtModule<ModuleOptions>({
cssPath: resolve(runtimeDir, 'tailwind.css') cssPath: resolve(runtimeDir, 'tailwind.css')
}) })
const presetsDir = resolve(runtimeDir, './presets')
let ui: object = (await import(resolveModule(`./${defaults.preset}`, { paths: presetsDir }))).default
try {
if (typeof preset === 'object') {
ui = defu(preset, ui)
} else {
// @ts-ignore
ui = (await import(resolveModule(`./${preset}`, { paths: presetsDir }))).default
}
} catch (e) {
// eslint-disable-next-line no-console
console.warn('Could not load preset file.')
}
addTemplate({
filename: 'ui.mjs',
getContents: () => `export default ${JSON.stringify(ui)}`
})
addPlugin(resolve(runtimeDir, 'plugins', 'toast.client')) addPlugin(resolve(runtimeDir, 'plugins', 'toast.client'))
addPlugin(resolve(runtimeDir, 'plugins', 'clipboard.client')) addPlugin(resolve(runtimeDir, 'plugins', 'clipboard.client'))

View File

@@ -1,5 +1,4 @@
import { safeColors } from '../utils/colors' export default (variantColors: string[]) => {
const button = { const button = {
base: 'font-medium focus:outline-none disabled:cursor-not-allowed disabled:opacity-75 focus:ring-offset-white dark:focus:ring-offset-black', base: 'font-medium focus:outline-none disabled:cursor-not-allowed disabled:opacity-75 focus:ring-offset-white dark:focus:ring-offset-black',
rounded: 'rounded-md', rounded: 'rounded-md',
@@ -28,12 +27,13 @@ const button = {
xl: 'p-3' xl: 'p-3'
}, },
variant: { variant: {
...safeColors.reduce((acc: any, color) => { ...variantColors.reduce((acc: any, color: string) => {
acc[color] = `shadow-sm border border-transparent text-white bg-${color}-600 hover:bg-${color}-700 disabled:bg-${color}-600 focus:ring-2 focus:ring-offset-2 focus:ring-${color}-500` acc[color] = `shadow-sm border border-transparent text-white bg-${color}-600 hover:bg-${color}-700 disabled:bg-${color}-600 focus:ring-2 focus:ring-offset-2 focus:ring-${color}-500`
return acc return acc
}, {}), }, {}),
secondary: 'border border-transparent text-primary-700 bg-primary-100 hover:bg-primary-200 disabled:bg-primary-100 focus:ring-2 focus:ring-offset-2 focus:ring-primary-500', secondary: 'border border-transparent text-primary-700 bg-primary-100 hover:bg-primary-200 disabled:bg-primary-100 focus:ring-2 focus:ring-offset-2 focus:ring-primary-500',
white: 'shadow-sm border u-border-gray-300 u-text-gray-700 u-bg-white hover:u-bg-gray-50 disabled:u-bg-white focus:ring-2 focus:ring-offset-2 focus:ring-primary-500', white: 'shadow-sm border u-border-gray-300 u-text-gray-700 u-bg-white hover:u-bg-gray-50 disabled:u-bg-white focus:ring-2 focus:ring-offset-2 focus:ring-primary-500',
gray: 'shadow-sm border u-border-gray-300 u-text-gray-700 u-bg-gray-50 hover:u-bg-gray-100 disabled:u-bg-gray-50 focus:ring-2 focus:ring-offset-2 focus:ring-primary-500',
black: 'shadow-sm border border-transparent u-text-white u-bg-gray-800 hover:u-bg-gray-900 disabled:u-bg-gray-800 focus:ring-2 focus:ring-offset-2 focus:ring-primary-500', black: 'shadow-sm border border-transparent u-text-white u-bg-gray-800 hover:u-bg-gray-900 disabled:u-bg-gray-800 focus:ring-2 focus:ring-offset-2 focus:ring-primary-500',
transparent: 'border border-transparent u-text-gray-500 hover:u-text-gray-700 focus:u-text-gray-700 disabled:hover:u-text-gray-500', transparent: 'border border-transparent u-text-gray-500 hover:u-text-gray-700 focus:u-text-gray-700 disabled:hover:u-text-gray-500',
link: 'border border-transparent text-primary-500 hover:text-primary-700 focus:text-primary-700' link: 'border border-transparent text-primary-500 hover:text-primary-700 focus:text-primary-700'
@@ -81,7 +81,7 @@ const badge = {
xl: 'text-sm px-4 py-1' xl: 'text-sm px-4 py-1'
}, },
variant: { variant: {
...safeColors.reduce((acc: any, color) => { ...variantColors.reduce((acc: any, color: string) => {
acc[color] = `bg-${color}-100 dark:bg-${color}-700 text-${color}-800 dark:text-${color}-100` acc[color] = `bg-${color}-100 dark:bg-${color}-700 text-${color}-800 dark:text-${color}-100`
return acc return acc
}, {}) }, {})
@@ -348,7 +348,7 @@ const avatar = {
'bottom-left': 'bottom-0 left-0' 'bottom-left': 'bottom-0 left-0'
}, },
variant: { variant: {
...safeColors.reduce((acc: any, color) => { ...variantColors.reduce((acc: any, color: string) => {
acc[color] = `bg-${color}-400` acc[color] = `bg-${color}-400`
return acc return acc
}, {}) }, {})
@@ -367,7 +367,7 @@ const avatar = {
} }
} }
export default { return {
card, card,
modal, modal,
button, button,
@@ -388,3 +388,4 @@ export default {
pills, pills,
avatar avatar
} }
}

View File

@@ -1,22 +1,18 @@
export const safeColors = [ import { omit, kebabCase } from './index'
'primary',
'rose', export const colorsToExclude = [
'pink', 'inherit',
'fuchsia', 'transparent',
'purple', 'current',
'violet', 'white',
'indigo', 'black',
'blue', 'slate',
'sky', 'gray',
'cyan', 'zinc',
'teal', 'neutral',
'emerald', 'stone'
'green',
'lime',
'yellow',
'amber',
'orange',
'red'
] ]
export const safeColorsAsRegex = safeColors.join('|') export const excludeColors = (colors: object) => Object.keys(omit(colors, colorsToExclude)).map(color => kebabCase(color))
export const colorsAsRegex = (colors: string[]) => colors.join('|')

View File

@@ -3,3 +3,16 @@ export * from './popper'
export function classNames (...classes: any[string]) { export function classNames (...classes: any[string]) {
return classes.filter(Boolean).join(' ') return classes.filter(Boolean).join(' ')
} }
export const kebabCase = (str: string) => {
return str
?.match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
?.map(x => x.toLowerCase())
?.join('-')
}
export const omit = (obj: object, keys: string[]) => {
return Object.fromEntries(
Object.entries(obj).filter(([key]) => !keys.includes(key))
)
}