diff --git a/src/plugins/plugins.ts b/src/plugins/plugins.ts index 629e2e24..98d9186b 100644 --- a/src/plugins/plugins.ts +++ b/src/plugins/plugins.ts @@ -16,6 +16,7 @@ export default function PluginsPlugin(options: NuxtUIOptions) { const plugins = globSync(['**/*', '!*.d.ts'], { cwd: join(runtimeDir, 'plugins'), absolute: true }) plugins.unshift(resolvePathSync('../runtime/vue/plugins/head', { extensions: ['.ts', '.mjs', '.js'], url: import.meta.url })) + plugins.push(resolvePathSync('../runtime/vue/plugins/colors', { extensions: ['.ts', '.mjs', '.js'], url: import.meta.url })) if (options.colorMode) { plugins.push(resolvePathSync('../runtime/vue/plugins/color-mode', { extensions: ['.ts', '.mjs', '.js'], url: import.meta.url })) } diff --git a/src/runtime/vue/composables/useAppConfig.ts b/src/runtime/vue/composables/useAppConfig.ts index a06491cf..27199fc9 100644 --- a/src/runtime/vue/composables/useAppConfig.ts +++ b/src/runtime/vue/composables/useAppConfig.ts @@ -1,3 +1,6 @@ +import { reactive } from 'vue' import appConfig from '#build/app.config' -export const useAppConfig = () => appConfig +const _appConfig = reactive(appConfig) + +export const useAppConfig = () => _appConfig diff --git a/src/runtime/vue/plugins/colors.ts b/src/runtime/vue/plugins/colors.ts new file mode 100644 index 00000000..9bf46501 --- /dev/null +++ b/src/runtime/vue/plugins/colors.ts @@ -0,0 +1,69 @@ +import { computed, watch, watchEffect } from 'vue' +import colors from 'tailwindcss/colors' +import { useHead } from '@unhead/vue' +import type { Plugin } from 'vue' +import { useAppConfig } from '../composables/useAppConfig' + +const shades = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950] as const + +function getColor(color: keyof typeof colors, shade: typeof shades[number]): string { + if (color in colors && typeof colors[color] === 'object' && shade in colors[color]) { + return colors[color][shade] as string + } + return '' +} + +function generateShades(key: string, value: string) { + return `${shades.map(shade => `--ui-color-${key}-${shade}: var(--color-${value === 'neutral' ? 'old-neutral' : value}-${shade}, ${getColor(value as keyof typeof colors, shade)});`).join('\n ')}` +} + +function generateColor(key: string, shade: number) { + return `--ui-${key}: var(--ui-color-${key}-${shade});` +} + +export default { + install(app) { + app.runWithContext(() => { + const appConfig = useAppConfig() + + const root = computed(() => { + const { neutral, ...colors } = appConfig.ui.colors + + return `@layer base { + :root { + ${Object.entries(appConfig.ui.colors).map(([key, value]: [string, string]) => generateShades(key, value)).join('\n ')} + } + :root, .light { + ${Object.keys(colors).map(key => generateColor(key, 500)).join('\n ')} + } + .dark { + ${Object.keys(colors).map(key => generateColor(key, 400)).join('\n ')} + } +}` + }) + + useHead({ + style: [{ + innerHTML: root, + tagPriority: -2, + id: 'nuxt-ui-colors' + }] + }) + + if (typeof document !== 'undefined') { + watchEffect(() => { + console.log('Colors changed, updating style:', JSON.stringify(appConfig.ui.colors)) + + let styleEl = document.querySelector('#nuxt-ui-colors-vue') as HTMLStyleElement + if (!styleEl) { + styleEl = document.createElement('style') + styleEl.id = 'nuxt-ui-colors-vue' + document.head.appendChild(styleEl) + } + + styleEl.innerHTML = root.value + }) + } + }) + } +} satisfies Plugin