mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-02-01 04:37:57 +01:00
feat(module): handle variants with dynamic colors
This commit is contained in:
@@ -20,7 +20,8 @@ export default defineNuxtConfig({
|
|||||||
},
|
},
|
||||||
ui: {
|
ui: {
|
||||||
colors: {
|
colors: {
|
||||||
primary: 'blue'
|
primary: 'blue',
|
||||||
|
gray: 'zinc'
|
||||||
},
|
},
|
||||||
preset: {
|
preset: {
|
||||||
},
|
},
|
||||||
|
|||||||
126
src/module.ts
126
src/module.ts
@@ -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'))
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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('|')
|
||||||
|
|||||||
@@ -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))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user