From fd640bbc5bce7e074faa5881264529868946dfbc Mon Sep 17 00:00:00 2001 From: John Leider Date: Thu, 16 Jul 2026 21:16:40 -0500 Subject: [PATCH 1/2] docs: fix palette explorer export and frame the palettes page The palette explorer's Copy Config button emitted a fully commented-out colors block, so pasting its output produced a theme with no colors; the click path emitted keyless token lines that were invalid once uncommented. onExport now emits real, uncommented config: clicked swatches map to theme roles in click order, and the no-click default seeds primary/secondary from hues that actually exist in the selected palette so the reference resolves. Adds framing prose above the explorer (static vs generator, token-reference syntax, axis-flip note) and an in-widget hint explaining click-to-copy. --- .../src/components/docs/DocsPaletteBrowse.vue | 35 ++++++++++++++----- .../docs/src/pages/guide/features/palettes.md | 6 ++++ 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/apps/docs/src/components/docs/DocsPaletteBrowse.vue b/apps/docs/src/components/docs/DocsPaletteBrowse.vue index 6e07456f9..bb1930ffd 100644 --- a/apps/docs/src/components/docs/DocsPaletteBrowse.vue +++ b/apps/docs/src/components/docs/DocsPaletteBrowse.vue @@ -29,6 +29,8 @@ ant: { label: 'Ant Design', data: ant as Record>, namespace: 'ant' }, } + const ROLES = ['primary', 'secondary', 'accent', 'error', 'info', 'success', 'warning'] + const selected = shallowRef('tailwind') const clicks = ref(new Map()) const { copied, copy } = useClipboard() @@ -76,23 +78,33 @@ } function onExport () { - const p = palette.value - const ns = p.namespace + const ns = palette.value.namespace const name = selected.value - let code = `import { ${name} } from '@vuetify/v0/palettes/${name}'\n\n` - code += `app.use(\n createThemePlugin({\n palette: { ${ns}: ${name} },\n` - code += ` themes: {\n light: {\n colors: {\n` + + const lines: string[] = [] if (clicks.value.size > 0) { + // Map each clicked swatch to a theme role, in click order + let i = 0 for (const [path] of clicks.value) { - code += ` // '{palette.${ns}.${path}}',\n` + const role = ROLES[i] ?? `color${i + 1}` + lines.push(` ${role}: '{palette.${ns}.${path}}',`) + i++ } } else { - code += ` // primary: '{palette.${ns}.blue.500}',\n` - code += ` // secondary: '{palette.${ns}.slate.600}',\n` + // No clicks: seed primary/secondary from real hues in this palette so the config resolves + const shade = columns.value[Math.floor(columns.value.length / 2)] + for (const [index, [hue, collection]] of rows.value.slice(0, 2).entries()) { + const key = shade && collection[shade] ? shade : Object.keys(collection)[0]! + lines.push(` ${ROLES[index]}: '{palette.${ns}.${hue}.${key}}',`) + } } - code += ` }\n }\n }\n })\n)` + let code = `import { ${name} } from '@vuetify/v0/palettes/${name}'\n\n` + code += `app.use(\n createThemePlugin({\n palette: { ${ns}: ${name} },\n` + code += ` themes: {\n light: {\n colors: {\n` + code += `${lines.join('\n')}\n` + code += ` },\n },\n },\n })\n)` copy(code) } @@ -141,6 +153,11 @@ + +
+ Click a swatch to copy its token path and add it to the config. Then use Copy Config for a ready-to-paste theme. +
+
diff --git a/apps/docs/src/pages/guide/features/palettes.md b/apps/docs/src/pages/guide/features/palettes.md index b1a060f0e..1fa4aa639 100644 --- a/apps/docs/src/pages/guide/features/palettes.md +++ b/apps/docs/src/pages/guide/features/palettes.md @@ -24,6 +24,12 @@ v0 ships pre-built color data from popular design systems and generator adapters +Palettes come in two forms. **Static palettes** are raw color data — a map of hues to shades — that you namespace under the `palette` option and reference from your themes. **Generator adapters** take a single seed color and hand back a ready-made `{ palette, themes }` pair by wrapping a third-party color algorithm. + +Colors are wired up with token references: the `'{palette.tw.blue.500}'` string in the examples below points at a value in the `palette` map instead of hardcoding a hex, so one palette can back many theme roles. See [Theming](/guide/features/theming) for how references resolve. + +Use the explorer to browse the static palettes. Click any swatch to copy its token path, then **Copy Config** for a ready-to-paste `createThemePlugin` setup seeded with the swatches you clicked. Palettes with far more shades than hues, like Material, render with their axes flipped so the grid stays readable. + ## Static Palettes From d6099d28d9e0ba7b1bd51cf023e7768cb2dee051 Mon Sep 17 00:00:00 2001 From: John Leider Date: Fri, 17 Jul 2026 19:49:53 -0500 Subject: [PATCH 2/2] docs: show palette explorer selection state and relabel Material Clicking swatches now drives the export, but nothing showed what was selected. Adds a summary chip row (swatch color, mapped role, token path) with per-chip remove and a Clear button, plus a selected outline on each picked swatch so the grid and the export stay in sync. Relabels the static Material entry to 'Material 3' to disambiguate it from the Material generator section further down the page. --- .../src/components/docs/DocsPaletteBrowse.vue | 66 +++++++++++++++---- 1 file changed, 55 insertions(+), 11 deletions(-) diff --git a/apps/docs/src/components/docs/DocsPaletteBrowse.vue b/apps/docs/src/components/docs/DocsPaletteBrowse.vue index bb1930ffd..69da3e706 100644 --- a/apps/docs/src/components/docs/DocsPaletteBrowse.vue +++ b/apps/docs/src/components/docs/DocsPaletteBrowse.vue @@ -24,7 +24,7 @@ tailwind: { label: 'Tailwind', data: tailwind as Record>, namespace: 'tw' }, md1: { label: 'MD1', data: md1 as Record>, namespace: 'md1' }, md2: { label: 'MD2', data: md2 as Record>, namespace: 'md2' }, - material: { label: 'Material', data: material as Record>, namespace: 'md' }, + material: { label: 'Material 3', data: material as Record>, namespace: 'md' }, radix: { label: 'Radix', data: radix as Record>, namespace: 'radix' }, ant: { label: 'Ant Design', data: ant as Record>, namespace: 'ant' }, } @@ -67,6 +67,18 @@ // This swaps axes so groups become columns and tones become rows const transposed = computed(() => columns.value.length > rows.value.length * 2) + // Clicked swatches mapped to theme roles, in click order — drives the summary chips and the export + const selection = computed(() => + [...clicks.value].map(([path, hex], index) => ({ + path, + hex, + role: ROLES[index] ?? `color${index + 1}`, + })), + ) + + // Fast path -> role lookup so each swatch can show whether it is picked + const picked = computed(() => new Map(selection.value.map(entry => [entry.path, entry.role]))) + watch(selected, () => { clicks.value.clear() }) @@ -77,19 +89,24 @@ clicks.value.set(path, hex) } + function onRemove (path: string) { + clicks.value.delete(path) + } + + function onClear () { + clicks.value.clear() + } + function onExport () { const ns = palette.value.namespace const name = selected.value const lines: string[] = [] - if (clicks.value.size > 0) { - // Map each clicked swatch to a theme role, in click order - let i = 0 - for (const [path] of clicks.value) { - const role = ROLES[i] ?? `color${i + 1}` + if (selection.value.length > 0) { + // Each clicked swatch is already mapped to a role + for (const { path, role } of selection.value) { lines.push(` ${role}: '{palette.${ns}.${path}}',`) - i++ } } else { // No clicks: seed primary/secondary from real hues in this palette so the config resolves @@ -189,9 +206,12 @@ v-for="([hue, collection]) in rows" :key="hue" class="h-7 cursor-pointer border-0 transition-opacity" - :class="collection[tone] ? 'hover:opacity-80' : 'opacity-0 pointer-events-none'" + :class="[ + collection[tone] ? 'hover:opacity-80' : 'opacity-0 pointer-events-none', + picked.has(`${hue}.${tone}`) && 'outline outline-2 -outline-offset-2 outline-on-surface', + ]" :style="collection[tone] ? { backgroundColor: collection[tone] } : {}" - :title="collection[tone] ? `${hue}.${tone} · ${collection[tone]}` : ''" + :title="collection[tone] ? `${hue}.${tone} · ${collection[tone]}${picked.has(`${hue}.${tone}`) ? ` · ${picked.get(`${hue}.${tone}`)}` : ''}` : ''" @click="collection[tone] && onSwatch(hue, tone, collection[tone]!)" /> @@ -228,15 +248,39 @@ v-for="col in columns" :key="col" class="h-7 cursor-pointer border-0 transition-opacity" - :class="collection[col] ? 'hover:opacity-80' : 'opacity-0 pointer-events-none'" + :class="[ + collection[col] ? 'hover:opacity-80' : 'opacity-0 pointer-events-none', + picked.has(`${hue}.${col}`) && 'outline outline-2 -outline-offset-2 outline-on-surface', + ]" :style="collection[col] ? { backgroundColor: collection[col] } : {}" - :title="collection[col] ? `${hue}.${col} · ${collection[col]}` : ''" + :title="collection[col] ? `${hue}.${col} · ${collection[col]}${picked.has(`${hue}.${col}`) ? ` · ${picked.get(`${hue}.${col}`)}` : ''}` : ''" @click="collection[col] && onSwatch(hue, col, collection[col]!)" />
+ +
+
+ + {{ item.role }} + {{ item.path }} + +
+ + +
+