import { defineNuxtModule, installModule, addComponentsDir, addImportsDir, createResolver, addPlugin, resolvePath } from '@nuxt/kit' import defaultColors from 'tailwindcss/colors.js' import { defaultExtractor as createDefaultExtractor } from 'tailwindcss/lib/lib/defaultExtractor.js' import { iconsPlugin, getIconCollections } from '@egoist/tailwindcss-icons' import { name, version } from '../package.json' import { generateSafelist, excludeColors, customSafelistExtractor } from './colors' import appConfig from './runtime/app.config' type DeepPartial = Partial<{ [P in keyof T]: DeepPartial | { [key: string]: string } }> const defaultExtractor = createDefaultExtractor({ tailwindConfig: { separator: ':' } }) delete defaultColors.lightBlue delete defaultColors.warmGray delete defaultColors.trueGray delete defaultColors.coolGray delete defaultColors.blueGray declare module '@nuxt/schema' { interface AppConfigInput { ui?: { primary?: string gray?: string colors?: string[] } & DeepPartial } } export interface ModuleOptions { /** * @default 'u' */ prefix?: string /** * @default false */ global?: boolean icons: string[] | string safelistColors?: string[] } export default defineNuxtModule({ meta: { name, version, configKey: 'ui', compatibility: { nuxt: '^3.0.0-rc.8' } }, defaults: { prefix: 'U', icons: ['heroicons'], safelistColors: ['primary'] }, async setup (options, nuxt) { const { resolve } = createResolver(import.meta.url) // Transpile runtime const runtimeDir = resolve('./runtime') nuxt.options.build.transpile.push(runtimeDir) nuxt.options.build.transpile.push('@popperjs/core', '@headlessui/vue') nuxt.options.css.push(resolve(runtimeDir, 'ui.css')) const appConfigFile = await resolvePath(resolve(runtimeDir, 'app.config')) nuxt.hook('app:resolve', (app) => { app.configs.push(appConfigFile) }) nuxt.hook('tailwindcss:config', function (tailwindConfig) { const globalColors: any = { ...(tailwindConfig.theme.colors || defaultColors), ...tailwindConfig.theme.extend?.colors } tailwindConfig.theme.extend.colors = tailwindConfig.theme.extend.colors || {} // @ts-ignore globalColors.primary = tailwindConfig.theme.extend.colors.primary = { 50: 'rgb(var(--color-primary-50) / )', 100: 'rgb(var(--color-primary-100) / )', 200: 'rgb(var(--color-primary-200) / )', 300: 'rgb(var(--color-primary-300) / )', 400: 'rgb(var(--color-primary-400) / )', 500: 'rgb(var(--color-primary-500) / )', 600: 'rgb(var(--color-primary-600) / )', 700: 'rgb(var(--color-primary-700) / )', 800: 'rgb(var(--color-primary-800) / )', 900: 'rgb(var(--color-primary-900) / )', 950: 'rgb(var(--color-primary-950) / )', DEFAULT: 'rgb(var(--color-primary-DEFAULT) / )' } if (globalColors.gray) { // @ts-ignore globalColors.cool = tailwindConfig.theme.extend.colors.cool = defaultColors.gray } // @ts-ignore globalColors.gray = tailwindConfig.theme.extend.colors.gray = { 50: 'rgb(var(--color-gray-50) / )', 100: 'rgb(var(--color-gray-100) / )', 200: 'rgb(var(--color-gray-200) / )', 300: 'rgb(var(--color-gray-300) / )', 400: 'rgb(var(--color-gray-400) / )', 500: 'rgb(var(--color-gray-500) / )', 600: 'rgb(var(--color-gray-600) / )', 700: 'rgb(var(--color-gray-700) / )', 800: 'rgb(var(--color-gray-800) / )', 900: 'rgb(var(--color-gray-900) / )', 950: 'rgb(var(--color-gray-950) / )' } const colors = excludeColors(globalColors) nuxt.options.appConfig.ui = { ...nuxt.options.appConfig.ui, primary: 'green', gray: 'cool', colors } tailwindConfig.safelist = tailwindConfig.safelist || [] tailwindConfig.safelist.push(...generateSafelist(options.safelistColors, colors)) tailwindConfig.plugins = tailwindConfig.plugins || [] tailwindConfig.plugins.push(iconsPlugin({ collections: getIconCollections(options.icons as any[]) })) }) // Modules await installModule('@nuxtjs/color-mode', { classSuffix: '' }) await installModule('@nuxtjs/tailwindcss', { exposeConfig: true, config: { darkMode: 'class', plugins: [ require('@tailwindcss/forms')({ strategy: 'class' }), require('@tailwindcss/aspect-ratio'), require('@tailwindcss/typography'), require('@tailwindcss/container-queries') ], content: { files: [ resolve(runtimeDir, 'components/**/*.{vue,mjs,ts}'), resolve(runtimeDir, '*.{mjs,js,ts}') ], transform: { vue: (content) => { return content.replaceAll(/(?:\r\n|\r|\n)/g, ' ') } }, extract: { vue: (content) => { return [ ...defaultExtractor(content), ...customSafelistExtractor(options.prefix, content, nuxt.options.appConfig.ui.colors, options.safelistColors) ] } } } } }) // Plugins addPlugin({ src: resolve(runtimeDir, 'plugins', 'colors') }) // Components addComponentsDir({ path: resolve(runtimeDir, 'components', 'elements'), prefix: options.prefix, global: options.global, watch: false }) addComponentsDir({ path: resolve(runtimeDir, 'components', 'forms'), prefix: options.prefix, global: options.global, watch: false }) addComponentsDir({ path: resolve(runtimeDir, 'components', 'data'), prefix: options.prefix, global: options.global, watch: false }) addComponentsDir({ path: resolve(runtimeDir, 'components', 'layout'), prefix: options.prefix, global: options.global, watch: false }) addComponentsDir({ path: resolve(runtimeDir, 'components', 'navigation'), prefix: options.prefix, global: options.global, watch: false }) addComponentsDir({ path: resolve(runtimeDir, 'components', 'overlays'), prefix: options.prefix, global: options.global, watch: false }) // Composables addImportsDir(resolve(runtimeDir, 'composables')) } })