Skip to content

Commit f0a73b2

Browse files
committed
docs(createTokens): add Design Token Explorer example
1 parent 2ef3093 commit f0a73b2

File tree

3 files changed

+253
-1
lines changed

3 files changed

+253
-1
lines changed
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
<script setup lang="ts">
2+
import { computed, shallowRef, toRef } from 'vue'
3+
import { CATEGORIES, tokens } from './tokens'
4+
import type { Category } from './tokens'
5+
6+
const category = shallowRef<Category>('color')
7+
const search = shallowRef('')
8+
const resolveInput = shallowRef('{semantic.primary}')
9+
10+
const resolved = toRef(() => {
11+
const input = resolveInput.value.trim()
12+
if (!input) return null
13+
const result = tokens.resolve(input)
14+
return result === undefined ? null : String(result)
15+
})
16+
17+
const alias = toRef(() => tokens.isAlias(resolveInput.value.trim()))
18+
19+
const categories = computed(() => {
20+
const all = tokens.values()
21+
const groups: Record<string, Array<{ id: string | number, value: unknown, resolved: unknown }>> = {
22+
color: [],
23+
semantic: [],
24+
spacing: [],
25+
radius: [],
26+
}
27+
28+
for (const ticket of all) {
29+
const id = String(ticket.id)
30+
const prefix = id.split('.')[0]
31+
if (prefix in groups) {
32+
groups[prefix].push({
33+
id: ticket.id,
34+
value: ticket.value,
35+
resolved: tokens.resolve(String(ticket.value)) ?? ticket.value,
36+
})
37+
}
38+
}
39+
40+
return groups
41+
})
42+
43+
const filtered = computed(() => {
44+
const group = categories.value[category.value] ?? []
45+
if (!search.value) return group
46+
const q = search.value.toLowerCase()
47+
return group.filter(t => String(t.id).toLowerCase().includes(q) || String(t.value).toLowerCase().includes(q))
48+
})
49+
50+
function isColor (value: unknown): boolean {
51+
return typeof value === 'string' && value.startsWith('#')
52+
}
53+
54+
function onResolve (id: string) {
55+
resolveInput.value = `{${id}}`
56+
}
57+
</script>
58+
59+
<template>
60+
<div class="space-y-5">
61+
<!-- Header stats -->
62+
<div class="flex items-center justify-between">
63+
<div class="text-xs text-on-surface-variant">
64+
{{ tokens.size }} tokens registered
65+
</div>
66+
<div class="flex gap-1">
67+
<button
68+
v-for="cat in CATEGORIES"
69+
:key="cat"
70+
class="px-2 py-0.5 text-xs rounded-md border capitalize transition-all"
71+
:class="category === cat
72+
? 'border-primary bg-primary/10 text-primary font-medium'
73+
: 'border-divider text-on-surface-variant hover:border-primary/50'"
74+
@click="category = cat"
75+
>
76+
{{ cat }}
77+
</button>
78+
</div>
79+
</div>
80+
81+
<!-- Search -->
82+
<input
83+
v-model="search"
84+
class="w-full px-3 py-1.5 text-sm rounded-lg border border-divider bg-surface text-on-surface placeholder:text-on-surface-variant outline-none focus:border-primary"
85+
placeholder="Filter tokens..."
86+
>
87+
88+
<!-- Token grid -->
89+
<div class="grid grid-cols-1 gap-1 max-h-56 overflow-auto pr-1">
90+
<button
91+
v-for="token in filtered"
92+
:key="String(token.id)"
93+
class="flex items-center gap-3 px-3 py-2 rounded-lg border border-divider hover:border-primary/30 transition-all text-left group"
94+
@click="onResolve(String(token.id))"
95+
>
96+
<!-- Color swatch or value indicator -->
97+
<div
98+
v-if="isColor(token.resolved)"
99+
class="size-6 rounded shrink-0 border border-divider"
100+
:style="{ backgroundColor: String(token.resolved) }"
101+
/>
102+
<div
103+
v-else
104+
class="size-6 rounded shrink-0 bg-surface-variant flex items-center justify-center"
105+
>
106+
<span class="text-[10px] text-on-surface-variant font-mono">
107+
{{ String(token.resolved).slice(0, 3) }}
108+
</span>
109+
</div>
110+
111+
<!-- Token info -->
112+
<div class="flex-1 min-w-0">
113+
<div class="text-xs font-mono text-on-surface truncate">{{ token.id }}</div>
114+
<div class="text-[10px] text-on-surface-variant/60 font-mono truncate">
115+
{{ tokens.isAlias(String(token.value)) ? `${token.value} → ${token.resolved}` : token.value }}
116+
</div>
117+
</div>
118+
119+
<!-- Click hint -->
120+
<span class="text-on-surface-variant/40 opacity-0 group-hover:opacity-100 transition-opacity text-base">›</span>
121+
</button>
122+
123+
<div
124+
v-if="filtered.length === 0"
125+
class="text-center text-sm text-on-surface-variant py-4"
126+
>
127+
No tokens matching "{{ search }}"
128+
</div>
129+
</div>
130+
131+
<!-- Alias resolver -->
132+
<div class="rounded-lg border border-divider p-4 space-y-3">
133+
<div class="text-xs font-medium text-on-surface-variant">Alias Resolver</div>
134+
<div class="flex gap-2">
135+
<input
136+
v-model="resolveInput"
137+
class="flex-1 px-3 py-1.5 text-sm font-mono rounded-lg border border-divider bg-surface text-on-surface outline-none focus:border-primary"
138+
placeholder="{semantic.primary}"
139+
>
140+
<div
141+
v-if="resolved && isColor(resolved)"
142+
class="w-10 rounded-lg border border-divider shrink-0"
143+
:style="{ backgroundColor: resolved }"
144+
/>
145+
</div>
146+
<div class="flex items-center gap-3 text-xs">
147+
<span
148+
v-if="alias"
149+
class="px-1.5 py-0.5 rounded bg-primary/10 text-primary font-medium"
150+
>
151+
alias
152+
</span>
153+
<span
154+
v-else
155+
class="px-1.5 py-0.5 rounded bg-surface-variant text-on-surface-variant"
156+
>
157+
direct
158+
</span>
159+
<span class="font-mono text-on-surface">
160+
{{ resolved ?? 'unresolved' }}
161+
</span>
162+
</div>
163+
</div>
164+
</div>
165+
</template>
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { createTokens } from '@vuetify/v0'
2+
3+
export const tokens = createTokens({
4+
color: {
5+
blue: {
6+
50: '#eff6ff',
7+
100: '#dbeafe',
8+
500: '#3b82f6',
9+
600: '#2563eb',
10+
900: '#1e3a5f',
11+
},
12+
green: {
13+
50: '#f0fdf4',
14+
100: '#dcfce7',
15+
500: '#22c55e',
16+
600: '#16a34a',
17+
900: '#14532d',
18+
},
19+
amber: {
20+
50: '#fffbeb',
21+
100: '#fef3c7',
22+
500: '#f59e0b',
23+
600: '#d97706',
24+
900: '#78350f',
25+
},
26+
slate: {
27+
50: '#f8fafc',
28+
100: '#f1f5f9',
29+
200: '#e2e8f0',
30+
700: '#334155',
31+
800: '#1e293b',
32+
900: '#0f172a',
33+
},
34+
},
35+
semantic: {
36+
'primary': '{color.blue.500}',
37+
'primary-dark': '{color.blue.600}',
38+
'success': '{color.green.500}',
39+
'warning': '{color.amber.500}',
40+
'surface': '{color.slate.50}',
41+
'surface-dark': '{color.slate.900}',
42+
'text': '{color.slate.900}',
43+
'text-dark': '{color.slate.50}',
44+
'muted': '{color.slate.200}',
45+
},
46+
spacing: {
47+
xs: '4px',
48+
sm: '8px',
49+
md: '16px',
50+
lg: '24px',
51+
xl: '32px',
52+
},
53+
radius: {
54+
sm: '4px',
55+
md: '8px',
56+
lg: '12px',
57+
full: '9999px',
58+
},
59+
})
60+
61+
export type Category = 'color' | 'semantic' | 'spacing' | 'radius'
62+
63+
export const CATEGORIES: readonly Category[] = ['color', 'semantic', 'spacing', 'radius'] as const

apps/docs/src/pages/composables/registration/create-tokens.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,30 @@ const features = createTokens({
5656
features.resolve('rtl') // { value: true, variation: 'toggle' }
5757
```
5858

59+
## Examples
60+
61+
::: example
62+
/composables/create-tokens/tokens.ts 1
63+
/composables/create-tokens/design-system.vue 2
64+
65+
### Design Token Explorer
66+
67+
A design system with four token categories — color palettes, semantic aliases, spacing, and radius — split across two files:
68+
69+
| File | Role |
70+
|------|------|
71+
| `tokens.ts` | Defines the token collection with nested color scales and semantic aliases |
72+
| `design-system.vue` | Explorer UI with category tabs, search, and an alias resolver |
73+
74+
**Key patterns:**
75+
76+
- Nested objects flatten to dot-notation IDs: `color.blue.500`, `spacing.md`
77+
- Alias syntax `{color.blue.500}` references other tokens — `resolve()` follows the chain
78+
- `isAlias()` distinguishes alias references from direct values
79+
- Click any token to resolve it in the Alias Resolver panel
80+
81+
:::
82+
5983
## Reactivity
6084

6185
`createTokens` uses **minimal reactivity** like its parent `createRegistry`. Token resolution is cached but not reactive.
@@ -75,7 +99,7 @@ flowchart TD
7599
createTokens --> usePermissions
76100
```
77101

78-
<DocsApi name="createTokens" />
102+
<DocsApi />
79103

80104
## Frequently Asked Questions
81105

0 commit comments

Comments
 (0)