Skip to content

Commit 99b7d07

Browse files
committed
docs: redesign the getting-started wizard as icon cards
Replace the plain checkbox list with a grid of selectable cards - each with an icon badge, title, and description - matching the card-picker pattern used elsewhere for this kind of preference UI. Selected cards get a highlighted border, tinted icon badge, and a check mark.
1 parent d765770 commit 99b7d07

1 file changed

Lines changed: 105 additions & 52 deletions

File tree

‎docs/app/components/global/GettingStartedWizard.vue‎

Lines changed: 105 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,25 @@
22
/**
33
* Interactive "what should I read" wizard for the Getting Started guide.
44
*
5-
* Every question is a checkbox group (multiple answers allowed per question,
6-
* since a real devtool usually spans more than one answer — e.g. it reads
7-
* from both the node side and the user's web app). Selections persist to
8-
* `localStorage` so a reader can leave the page and pick up where they left
9-
* off; the recommended reading list at the bottom recomputes from whatever
10-
* is currently checked.
5+
* Every question is a grid of selectable cards (multiple answers allowed per
6+
* question, since a real devtool usually spans more than one answer — e.g.
7+
* it reads from both the node side and the user's web app). Selections
8+
* persist to `localStorage` so a reader can leave the page and pick up where
9+
* they left off; the recommended reading list at the bottom recomputes from
10+
* whatever is currently checked.
1111
*/
1212
1313
interface WizardItem {
1414
value: string
1515
label: string
16+
icon: string
1617
description?: string
1718
}
1819
1920
interface WizardSection {
2021
key: string
21-
legend: string
22+
title: string
23+
hint: string
2224
items: WizardItem[]
2325
}
2426
@@ -33,59 +35,65 @@ const STORAGE_KEY = 'devframe-docs:getting-started'
3335
const sections: WizardSection[] = [
3436
{
3537
key: 'dataSource',
36-
legend: 'Where do you want to visualize data from?',
38+
title: 'Data source',
39+
hint: 'Where do you want to visualize data from?',
3740
items: [
38-
{ value: 'node', label: 'The node side', description: 'Server state, build output, the filesystem, child processes' },
39-
{ value: 'browser', label: 'The user\'s web app', description: 'State living in the page you\'re developing' },
41+
{ value: 'node', label: 'The node side', icon: 'i-lucide-server', description: 'Server state, build output, the filesystem, child processes' },
42+
{ value: 'browser', label: 'The user\'s web app', icon: 'i-lucide-app-window', description: 'State living in the page you\'re developing' },
4043
],
4144
},
4245
{
4346
key: 'environments',
44-
legend: 'What do you expect your tool to work with?',
47+
title: 'Target environments',
48+
hint: 'What do you expect your tool to work with?',
4549
items: [
46-
{ value: 'standalone', label: 'Standalone', description: 'A CLI or dev server with no host framework' },
47-
{ value: 'vite', label: 'Vite' },
48-
{ value: 'next', label: 'Next.js' },
49-
{ value: 'framework', label: 'A specific framework', description: 'Nuxt, or a host framework the kits don\'t cover yet' },
50-
{ value: 'all', label: 'All frameworks', description: 'Anything that speaks a Web Standard Request/Response' },
50+
{ value: 'standalone', label: 'Standalone', icon: 'i-lucide-terminal', description: 'A CLI or dev server with no host framework' },
51+
{ value: 'vite', label: 'Vite', icon: 'i-simple-icons-vite' },
52+
{ value: 'next', label: 'Next.js', icon: 'i-simple-icons-nextdotjs' },
53+
{ value: 'framework', label: 'A specific framework', icon: 'i-lucide-puzzle', description: 'Nuxt, or a host framework the kits don\'t cover yet' },
54+
{ value: 'all', label: 'All frameworks', icon: 'i-lucide-infinity', description: 'Anything that speaks a Web Standard Request/Response' },
5155
],
5256
},
5357
{
5458
key: 'availability',
55-
legend: 'When is the data available?',
59+
title: 'Data availability',
60+
hint: 'When is the data available?',
5661
items: [
57-
{ value: 'dev', label: 'Development time', description: 'Live, over a running dev server' },
58-
{ value: 'build', label: 'Production build time' },
59-
{ value: 'static', label: 'Statically available', description: 'Local filesystem, a static dump, etc.' },
60-
{ value: 'remote', label: 'Remotely', description: 'Over the web, not on localhost' },
62+
{ value: 'dev', label: 'Development time', icon: 'i-lucide-code', description: 'Live, over a running dev server' },
63+
{ value: 'build', label: 'Production build time', icon: 'i-lucide-hammer' },
64+
{ value: 'static', label: 'Statically available', icon: 'i-lucide-hard-drive', description: 'Local filesystem, a static dump, etc.' },
65+
{ value: 'remote', label: 'Remotely', icon: 'i-lucide-cloud', description: 'Over the web, not on localhost' },
6166
],
6267
},
6368
{
6469
key: 'frontend',
65-
legend: 'How do you want to build the frontend view?',
70+
title: 'Frontend approach',
71+
hint: 'How do you want to build the frontend view?',
6672
items: [
67-
{ value: 'framework', label: 'A preferred framework', description: 'Vue, React, Svelte, Solid...' },
68-
{ value: 'webcomponents', label: 'Web Components' },
69-
{ value: 'nodeside', label: 'Build the frontend on the node side', description: 'Describe the UI as data instead of shipping a bundle' },
73+
{ value: 'framework', label: 'A preferred framework', icon: 'i-lucide-component', description: 'Vue, React, Svelte, Solid...' },
74+
{ value: 'webcomponents', label: 'Web Components', icon: 'i-lucide-box' },
75+
{ value: 'nodeside', label: 'Build it on the node side', icon: 'i-lucide-braces', description: 'Describe the UI as data instead of shipping a bundle' },
7076
],
7177
},
7278
{
7379
key: 'agent',
74-
legend: 'Should it also work with a coding agent?',
80+
title: 'Agent support',
81+
hint: 'Should it also work with a coding agent?',
7582
items: [
76-
{ value: 'agent', label: 'Yes, expose it to a coding agent', description: 'Same RPC functions, resources, and state, over MCP' },
83+
{ value: 'agent', label: 'Yes, expose it to a coding agent', icon: 'i-lucide-bot', description: 'Same RPC functions, resources, and state, over MCP' },
7784
],
7885
},
7986
{
8087
key: 'requirements',
81-
legend: 'Any specific requirements?',
88+
title: 'Other requirements',
89+
hint: 'Any specific requirements?',
8290
items: [
83-
{ value: 'streaming', label: 'Streaming data' },
84-
{ value: 'overlay', label: 'An overlay on the user\'s web app' },
85-
{ value: 'hub', label: 'Composing with other devtools', description: 'One UI, many devframes' },
86-
{ value: 'security', label: 'Authentication' },
87-
{ value: 'deep-linking', label: 'Deep linking', description: 'Shareable URLs into a specific view' },
88-
{ value: 'terminal', label: 'Terminal / process access' },
91+
{ value: 'streaming', label: 'Streaming data', icon: 'i-lucide-radio' },
92+
{ value: 'overlay', label: 'An overlay on the user\'s web app', icon: 'i-lucide-layers' },
93+
{ value: 'hub', label: 'Composing with other devtools', icon: 'i-lucide-layout-dashboard', description: 'One UI, many devframes' },
94+
{ value: 'security', label: 'Authentication', icon: 'i-lucide-shield-check' },
95+
{ value: 'deep-linking', label: 'Deep linking', icon: 'i-lucide-link', description: 'Shareable URLs into a specific view' },
96+
{ value: 'terminal', label: 'Terminal / process access', icon: 'i-lucide-square-terminal' },
8997
],
9098
},
9199
]
@@ -104,7 +112,7 @@ const DOC_CATALOG: Record<string, DocEntry> = {
104112
'/guide/security': { title: 'Security', description: 'Localhost binding and a trust handshake before a browser can call RPC.', icon: 'i-lucide-shield-check' },
105113
'/guide/agent-native': { title: 'Agent-Native Devframe', description: 'Expose RPC functions, resources, and shared state to coding agents over MCP.', icon: 'i-lucide-bot' },
106114
'/guide/hub': { title: 'Hub', description: 'Orchestrate many devtools sharing one UI — docks, terminals, messages, commands.', icon: 'i-lucide-layout-dashboard' },
107-
'/guide/client-context': { title: 'Client Scripts & Client Context', description: 'How a dock client script runs a devframe\'s code inside the host page.', icon: 'i-lucide-code-2' },
115+
'/guide/client-context': { title: 'Client Scripts & Client Context', description: 'How a dock client script runs a devframe\'s code inside the host page.', icon: 'i-lucide-code' },
108116
'/guide/hub-initiate': { title: 'Serve a Hub Anywhere', description: 'initHub() serves a whole multi-devframe install from one handler.', icon: 'i-lucide-server-cog' },
109117
'/guide/services': { title: 'Cross-Devframe Services', description: 'Expose a typed, namespaced capability to every devframe in a hub.', icon: 'i-lucide-share-2' },
110118
'/guide/deep-linking': { title: 'Deep Linking', description: 'Send a user to a specific view inside a devframe from a URL or an agent.', icon: 'i-lucide-link' },
@@ -193,12 +201,25 @@ watch(selections, (value) => {
193201
localStorage.setItem(STORAGE_KEY, JSON.stringify(value))
194202
}, { deep: true })
195203
196-
const hasSelections = computed(() => sections.some(section => selections[section.key].length > 0))
204+
function isChecked(sectionKey: string, value: string): boolean {
205+
return selections[sectionKey]!.includes(value)
206+
}
207+
208+
function toggle(sectionKey: string, value: string): void {
209+
const current = selections[sectionKey]!
210+
const index = current.indexOf(value)
211+
if (index === -1)
212+
current.push(value)
213+
else
214+
current.splice(index, 1)
215+
}
216+
217+
const hasSelections = computed(() => sections.some(section => selections[section.key]!.length > 0))
197218
198219
const recommendedDocs = computed(() => {
199220
const paths = new Set(BASE_DOCS)
200221
for (const section of sections) {
201-
for (const value of selections[section.key]) {
222+
for (const value of selections[section.key]!) {
202223
for (const path of RECOMMENDATIONS[`${section.key}:${value}`] ?? [])
203224
paths.add(path)
204225
}
@@ -208,14 +229,14 @@ const recommendedDocs = computed(() => {
208229
.map(path => ({ path, ...DOC_CATALOG[path]! }))
209230
})
210231
211-
function reset() {
232+
function reset(): void {
212233
for (const section of sections) selections[section.key] = []
213234
}
214235
</script>
215236

216237
<template>
217238
<div class="not-prose rounded-xl border border-default divide-y divide-default overflow-hidden">
218-
<div class="flex items-center justify-between gap-4 px-5 py-4 bg-muted">
239+
<div class="flex items-center justify-between gap-4 px-5 py-4 sm:px-6 bg-muted">
219240
<div>
220241
<p class="font-medium text-highlighted">
221242
What kind of devtool do you want to build?
@@ -236,22 +257,54 @@ function reset() {
236257
/>
237258
</div>
238259

239-
<div class="grid grid-cols-1 md:grid-cols-2 gap-px bg-default">
240-
<div
241-
v-for="section in sections"
242-
:key="section.key"
243-
class="bg-default p-5"
244-
>
245-
<UCheckboxGroup
246-
v-model="selections[section.key]"
247-
:legend="section.legend"
248-
:items="section.items"
249-
:ui="{ legend: 'text-base font-medium text-highlighted mb-3' }"
250-
/>
260+
<div
261+
v-for="section in sections"
262+
:key="section.key"
263+
class="p-5 sm:p-6"
264+
>
265+
<p class="font-medium text-highlighted">
266+
{{ section.title }}
267+
</p>
268+
<p class="text-sm text-muted mt-0.5 mb-4">
269+
{{ section.hint }}
270+
</p>
271+
272+
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
273+
<button
274+
v-for="item in section.items"
275+
:key="item.value"
276+
type="button"
277+
role="checkbox"
278+
:aria-checked="isChecked(section.key, item.value)"
279+
class="relative flex flex-col items-start gap-3 rounded-lg border p-4 text-left transition-colors cursor-pointer"
280+
:class="isChecked(section.key, item.value)
281+
? 'border-primary bg-primary/5 ring-1 ring-primary/30'
282+
: 'border-default hover:border-accented hover:bg-elevated/50'"
283+
@click="toggle(section.key, item.value)"
284+
>
285+
<UIcon
286+
v-if="isChecked(section.key, item.value)"
287+
name="i-lucide-circle-check"
288+
class="absolute top-3 right-3 size-4 text-primary"
289+
/>
290+
<span
291+
class="inline-flex items-center justify-center size-9 rounded-full transition-colors"
292+
:class="isChecked(section.key, item.value) ? 'bg-primary/10 text-primary' : 'bg-elevated text-muted'"
293+
>
294+
<UIcon :name="item.icon" class="size-4.5" />
295+
</span>
296+
<span>
297+
<span class="block text-sm font-medium text-highlighted pe-4">{{ item.label }}</span>
298+
<span
299+
v-if="item.description"
300+
class="block text-xs text-muted mt-0.5"
301+
>{{ item.description }}</span>
302+
</span>
303+
</button>
251304
</div>
252305
</div>
253306

254-
<div class="p-5 bg-muted">
307+
<div class="p-5 sm:p-6 bg-muted">
255308
<p class="font-medium text-highlighted mb-3">
256309
{{ hasSelections ? 'Recommended docs, based on your answers' : 'Start here' }}
257310
</p>

0 commit comments

Comments
 (0)