Skip to content

Commit 70011cd

Browse files
authored
docs: fix palette explorer export and frame the palettes page (#629)
* 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. * 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.
1 parent b99ed48 commit 70011cd

2 files changed

Lines changed: 83 additions & 16 deletions

File tree

apps/docs/src/components/docs/DocsPaletteBrowse.vue

Lines changed: 77 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@
2424
tailwind: { label: 'Tailwind', data: tailwind as Record<string, Record<string, string>>, namespace: 'tw' },
2525
md1: { label: 'MD1', data: md1 as Record<string, Record<string, string>>, namespace: 'md1' },
2626
md2: { label: 'MD2', data: md2 as Record<string, Record<string, string>>, namespace: 'md2' },
27-
material: { label: 'Material', data: material as Record<string, Record<string, string>>, namespace: 'md' },
27+
material: { label: 'Material 3', data: material as Record<string, Record<string, string>>, namespace: 'md' },
2828
radix: { label: 'Radix', data: radix as Record<string, Record<string, string>>, namespace: 'radix' },
2929
ant: { label: 'Ant Design', data: ant as Record<string, Record<string, string>>, namespace: 'ant' },
3030
}
3131
32+
const ROLES = ['primary', 'secondary', 'accent', 'error', 'info', 'success', 'warning']
33+
3234
const selected = shallowRef('tailwind')
3335
const clicks = ref(new Map<string, string>())
3436
const { copied, copy } = useClipboard()
@@ -65,6 +67,18 @@
6567
// This swaps axes so groups become columns and tones become rows
6668
const transposed = computed(() => columns.value.length > rows.value.length * 2)
6769
70+
// Clicked swatches mapped to theme roles, in click order — drives the summary chips and the export
71+
const selection = computed(() =>
72+
[...clicks.value].map(([path, hex], index) => ({
73+
path,
74+
hex,
75+
role: ROLES[index] ?? `color${index + 1}`,
76+
})),
77+
)
78+
79+
// Fast path -> role lookup so each swatch can show whether it is picked
80+
const picked = computed(() => new Map(selection.value.map(entry => [entry.path, entry.role])))
81+
6882
watch(selected, () => {
6983
clicks.value.clear()
7084
})
@@ -75,24 +89,39 @@
7589
clicks.value.set(path, hex)
7690
}
7791
92+
function onRemove (path: string) {
93+
clicks.value.delete(path)
94+
}
95+
96+
function onClear () {
97+
clicks.value.clear()
98+
}
99+
78100
function onExport () {
79-
const p = palette.value
80-
const ns = p.namespace
101+
const ns = palette.value.namespace
81102
const name = selected.value
82-
let code = `import { ${name} } from '@vuetify/v0/palettes/${name}'\n\n`
83-
code += `app.use(\n createThemePlugin({\n palette: { ${ns}: ${name} },\n`
84-
code += ` themes: {\n light: {\n colors: {\n`
85103
86-
if (clicks.value.size > 0) {
87-
for (const [path] of clicks.value) {
88-
code += ` // '{palette.${ns}.${path}}',\n`
104+
const lines: string[] = []
105+
106+
if (selection.value.length > 0) {
107+
// Each clicked swatch is already mapped to a role
108+
for (const { path, role } of selection.value) {
109+
lines.push(` ${role}: '{palette.${ns}.${path}}',`)
89110
}
90111
} else {
91-
code += ` // primary: '{palette.${ns}.blue.500}',\n`
92-
code += ` // secondary: '{palette.${ns}.slate.600}',\n`
112+
// No clicks: seed primary/secondary from real hues in this palette so the config resolves
113+
const shade = columns.value[Math.floor(columns.value.length / 2)]
114+
for (const [index, [hue, collection]] of rows.value.slice(0, 2).entries()) {
115+
const key = shade && collection[shade] ? shade : Object.keys(collection)[0]!
116+
lines.push(` ${ROLES[index]}: '{palette.${ns}.${hue}.${key}}',`)
117+
}
93118
}
94119
95-
code += ` }\n }\n }\n })\n)`
120+
let code = `import { ${name} } from '@vuetify/v0/palettes/${name}'\n\n`
121+
code += `app.use(\n createThemePlugin({\n palette: { ${ns}: ${name} },\n`
122+
code += ` themes: {\n light: {\n colors: {\n`
123+
code += `${lines.join('\n')}\n`
124+
code += ` },\n },\n },\n })\n)`
96125
copy(code)
97126
}
98127
</script>
@@ -141,6 +170,11 @@
141170
</Select.Root>
142171
</div>
143172

173+
<!-- Hint -->
174+
<div class="text-xs op-60">
175+
Click a swatch to copy its token path and add it to the config. Then use <span class="font-medium">Copy Config</span> for a ready-to-paste theme.
176+
</div>
177+
144178
<!-- Swatch grid — fixed height container, uniform cell sizes -->
145179
<div class="h-[420px] overflow-auto border border-divider rounded-lg">
146180
<!-- Transposed: groups as columns, tones as rows (for Material) -->
@@ -172,9 +206,12 @@
172206
v-for="([hue, collection]) in rows"
173207
:key="hue"
174208
class="h-7 cursor-pointer border-0 transition-opacity"
175-
:class="collection[tone] ? 'hover:opacity-80' : 'opacity-0 pointer-events-none'"
209+
:class="[
210+
collection[tone] ? 'hover:opacity-80' : 'opacity-0 pointer-events-none',
211+
picked.has(`${hue}.${tone}`) && 'outline outline-2 -outline-offset-2 outline-on-surface',
212+
]"
176213
:style="collection[tone] ? { backgroundColor: collection[tone] } : {}"
177-
:title="collection[tone] ? `${hue}.${tone} · ${collection[tone]}` : ''"
214+
:title="collection[tone] ? `${hue}.${tone} · ${collection[tone]}${picked.has(`${hue}.${tone}`) ? ` · ${picked.get(`${hue}.${tone}`)}` : ''}` : ''"
178215
@click="collection[tone] && onSwatch(hue, tone, collection[tone]!)"
179216
/>
180217
</template>
@@ -211,15 +248,39 @@
211248
v-for="col in columns"
212249
:key="col"
213250
class="h-7 cursor-pointer border-0 transition-opacity"
214-
:class="collection[col] ? 'hover:opacity-80' : 'opacity-0 pointer-events-none'"
251+
:class="[
252+
collection[col] ? 'hover:opacity-80' : 'opacity-0 pointer-events-none',
253+
picked.has(`${hue}.${col}`) && 'outline outline-2 -outline-offset-2 outline-on-surface',
254+
]"
215255
:style="collection[col] ? { backgroundColor: collection[col] } : {}"
216-
:title="collection[col] ? `${hue}.${col} · ${collection[col]}` : ''"
256+
:title="collection[col] ? `${hue}.${col} · ${collection[col]}${picked.has(`${hue}.${col}`) ? ` · ${picked.get(`${hue}.${col}`)}` : ''}` : ''"
217257
@click="collection[col] && onSwatch(hue, col, collection[col]!)"
218258
/>
219259
</template>
220260
</div>
221261
</div>
222262

263+
<!-- Selection summary — which swatches are picked and the role each maps to -->
264+
<div v-if="selection.length > 0" class="flex flex-wrap items-center gap-2">
265+
<div
266+
v-for="item in selection"
267+
:key="item.path"
268+
class="inline-flex items-center gap-1.5 h-7 pl-1.5 pr-0.5 text-xs border border-divider rounded-full"
269+
>
270+
<span class="w-3 h-3 rounded-full border border-divider" :style="{ backgroundColor: item.hex }" />
271+
<span class="font-medium">{{ item.role }}</span>
272+
<span class="op-50 font-mono">{{ item.path }}</span>
273+
<AppCloseButton :label="`Remove ${item.role}`" size="sm" @click="onRemove(item.path)" />
274+
</div>
275+
276+
<button
277+
class="text-xs op-60 hover:op-100 underline underline-offset-2 cursor-pointer bg-transparent border-0"
278+
@click="onClear"
279+
>
280+
Clear
281+
</button>
282+
</div>
283+
223284
<!-- Export button with inline copied indicator -->
224285
<div class="flex items-center gap-3">
225286
<button

apps/docs/src/pages/guide/features/palettes.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ v0 ships pre-built color data from popular design systems and generator adapters
2424

2525
<DocsPageFeatures :frontmatter />
2626

27+
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.
28+
29+
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.
30+
31+
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.
32+
2733
<DocsPaletteExplorer />
2834

2935
## Static Palettes

0 commit comments

Comments
 (0)