Skip to content

Commit b8fbc2d

Browse files
committed
docs(discovery): add new tour
Add "Using Search" 9-step guided tour teaching search features: - Search button and input - Results organization - Favorite, Ask AI, and dismiss actions - Search history - Live favoriting demonstration Also includes: - Lazy load GitHub data in footer with IntersectionObserver - Auto-focus discovery content on ready - Show action buttons during active tours
1 parent 0d4a2d0 commit b8fbc2d

File tree

6 files changed

+337
-13
lines changed

6 files changed

+337
-13
lines changed

apps/docs/src/components/app/AppFooter.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
}
5656
}
5757
58+
// Only fetch GitHub data when footer becomes visible
5859
useIntersectionObserver(
5960
footerRef,
6061
entries => {
@@ -67,7 +68,7 @@
6768
</script>
6869

6970
<template>
70-
<footer ref="footerRef" class="app-footer py-4 border-t border-divider/50" :class="[inset && 'md:ml-[230px]', settings.showBgGlass.value ? 'bg-glass-surface' : 'bg-surface']">
71+
<footer class="app-footer py-4 border-t border-divider/50" :class="[inset && 'md:ml-[230px]', settings.showBgGlass.value ? 'bg-glass-surface' : 'bg-surface']">
7172
<div class="max-w-[1200px] mx-auto px-6 flex flex-col md:flex-row items-center justify-between gap-4">
7273
<div class="flex flex-col md:flex-row items-center gap-4 text-sm opacity-60">
7374
<AppCopyright />

apps/docs/src/components/discovery/DiscoveryContent.vue

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import { useDiscovery } from '@/composables/useDiscovery'
1010
1111
// Utilities
12-
import { shallowRef, toRef, useAttrs, watch } from 'vue'
12+
import { nextTick, shallowRef, toRef, useAttrs, useTemplateRef, watch } from 'vue'
1313
1414
defineOptions({ name: 'DiscoveryContent', inheritAttrs: false })
1515
@@ -36,6 +36,7 @@
3636
const discovery = useDiscovery()
3737
const isReady = shallowRef(false)
3838
const isDev = import.meta.env.DEV
39+
const contentRef = useTemplateRef<HTMLElement>('content')
3940
4041
// Check for CSS Anchor Positioning support
4142
// Safari/iOS don't support anchor positioning - check for the specific property
@@ -143,16 +144,25 @@
143144
},
144145
{ immediate: true },
145146
)
147+
148+
watch(isReady, ready => {
149+
if (ready) {
150+
nextTick(() => contentRef.value?.focus())
151+
}
152+
})
146153
</script>
147154

148155
<template>
149156
<Teleport v-if="isReady" to="body">
150157
<div
158+
ref="content"
151159
v-bind="attrs"
160+
class="outline-none"
152161
:style="{
153162
...style,
154163
zIndex: 9999,
155164
}"
165+
tabindex="-1"
156166
>
157167
<div v-if="isDev" class="font-mono text-xs text-on-surface-variant/50 mb-1">
158168
{{ root.step }}

apps/docs/src/components/docs/DocsSearch.vue

Lines changed: 132 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
88
// Composables
99
import { useAsk } from '@/composables/useAsk'
10+
import { useDiscovery } from '@/composables/useDiscovery'
1011
import { useSearch } from '@/composables/useSearch'
1112
import { useSettings } from '@/composables/useSettings'
1213
@@ -18,6 +19,7 @@
1819
import type { SavedResult, SearchResult } from '@/composables/useSearch'
1920
2021
const search = useSearch()
22+
const discovery = useDiscovery()
2123
2224
const router = useRouter()
2325
const inputRef = useTemplateRef<HTMLInputElement>('input')
@@ -155,7 +157,7 @@
155157
<div :class="['rounded-lg shadow-xl border border-divider overflow-hidden', settings.showBgGlass.value ? 'bg-glass-surface' : 'bg-surface']">
156158
<Discovery.Activator
157159
class="flex-1 bg-transparent flex border-b border-divider outline-none text-on-surface rounded-lg rounded-b-0 items-center gap-3 px-4 py-3"
158-
step="search-tabs"
160+
step="search-input"
159161
>
160162
<AppIcon
161163
aria-hidden="true"
@@ -221,7 +223,14 @@
221223
<!-- Empty query state: show favorites and recents -->
222224
<template v-else-if="!search.text.value.trim()">
223225
<!-- Favorites section -->
224-
<div v-if="search.favorites.value.length > 0" aria-label="Favorites" role="group">
226+
<Discovery.Activator
227+
v-if="search.favorites.value.length > 0"
228+
aria-label="Favorites"
229+
as="div"
230+
class="rounded-lg"
231+
role="group"
232+
step="search-favorited"
233+
>
225234
<div class="px-4 py-2 text-xs font-medium text-on-surface-variant uppercase tracking-wide bg-surface-variant/50 flex items-center justify-between">
226235
<span>Favorites</span>
227236
</div>
@@ -264,10 +273,17 @@
264273
<AppIcon aria-hidden="true" icon="close" size="16" />
265274
</span>
266275
</div>
267-
</div>
276+
</Discovery.Activator>
268277

269278
<!-- Recents section -->
270-
<div v-if="search.recents.value.length > 0" aria-label="Recent searches" role="group">
279+
<Discovery.Activator
280+
v-if="search.recents.value.length > 0"
281+
aria-label="Recent searches"
282+
as="div"
283+
class="rounded-lg"
284+
role="group"
285+
step="search-history"
286+
>
271287
<div class="px-4 py-2 text-xs font-medium text-on-surface-variant uppercase tracking-wide bg-surface-variant/50">
272288
Recent
273289
</div>
@@ -326,7 +342,7 @@
326342
</span>
327343
</div>
328344
</div>
329-
</div>
345+
</Discovery.Activator>
330346

331347
<!-- No content placeholder -->
332348
<div
@@ -358,22 +374,127 @@
358374
<div class="px-4 py-2 text-xs font-medium text-on-surface-variant uppercase tracking-wide bg-surface-variant/50">
359375
{{ group.category }}
360376
</div>
377+
<!-- First result gets Discovery.Activator for tour -->
378+
<Discovery.Activator
379+
v-if="groupIndex === 0 && group.items.length > 0"
380+
class="rounded-lg"
381+
step="search-results"
382+
>
383+
<div
384+
:id="`search-result-0`"
385+
:aria-selected="0 === search.selection.index.value"
386+
:class="[
387+
'group w-full px-4 py-2 flex items-center gap-2 text-left transition-colors cursor-pointer',
388+
0 === search.selection.index.value
389+
? 'bg-primary/10 text-primary'
390+
: 'hover:bg-surface-variant text-on-surface',
391+
]"
392+
:data-selected="0 === search.selection.index.value"
393+
role="option"
394+
tabindex="-1"
395+
@click="navigate(group.items[0])"
396+
@mouseenter="onHover(0)"
397+
>
398+
<div class="flex-1 min-w-0 flex flex-col gap-0.5">
399+
<span class="font-medium">{{ group.items[0].title }}</span>
400+
<span
401+
v-if="group.items[0].headings.length > 0"
402+
class="text-xs text-on-surface-variant truncate"
403+
>
404+
{{ group.items[0].headings.slice(0, 3).join(' → ') }}
405+
</span>
406+
</div>
407+
<div class="flex items-center gap-1 shrink-0">
408+
<!-- Favorite toggle -->
409+
<Discovery.Activator
410+
class="rounded-lg"
411+
:padding="4"
412+
step="search-favorite"
413+
>
414+
<span
415+
:aria-label="search.isFavorite(group.items[0].id) ? 'Remove from favorites' : 'Add to favorites'"
416+
:class="[
417+
'inline-flex p-1.5 rounded-lg hover:bg-surface-variant focus-visible:bg-surface-variant transition-colors cursor-pointer',
418+
search.isFavorite(group.items[0].id) || discovery.isActive.value ? 'opacity-100 text-warning' : 'opacity-0 group-hover:opacity-100 focus-visible:opacity-100 text-on-surface/60 hover:text-warning focus-visible:text-warning',
419+
]"
420+
role="button"
421+
tabindex="0"
422+
:title="search.isFavorite(group.items[0].id) ? 'Remove from favorites' : 'Add to favorites'"
423+
@click="toggleFavorite($event, group.items[0].id)"
424+
@keydown.enter.stop="toggleFavorite($event, group.items[0].id)"
425+
@keydown.space.stop.prevent="toggleFavorite($event, group.items[0].id)"
426+
>
427+
<AppIcon
428+
aria-hidden="true"
429+
:icon="search.isFavorite(group.items[0].id) ? 'star' : 'star-outline'"
430+
size="16"
431+
/>
432+
</span>
433+
</Discovery.Activator>
434+
<!-- Ask AI button -->
435+
<Discovery.Activator
436+
class="rounded-lg"
437+
:padding="4"
438+
step="search-ask-ai"
439+
>
440+
<span
441+
aria-label="Ask AI about this page"
442+
:class="[
443+
'inline-flex p-1.5 rounded-lg hover:bg-surface-variant focus-visible:bg-surface-variant transition-colors text-on-surface/60 hover:text-primary focus-visible:text-primary cursor-pointer',
444+
discovery.isActive.value ? 'opacity-100' : 'opacity-0 group-hover:opacity-100 focus-visible:opacity-100',
445+
]"
446+
role="button"
447+
tabindex="0"
448+
title="Ask AI"
449+
@click="askAbout($event, group.items[0])"
450+
@keydown.enter.stop="askAbout($event, group.items[0])"
451+
@keydown.space.stop.prevent="askAbout($event, group.items[0])"
452+
>
453+
<AppIcon aria-hidden="true" icon="create" size="16" />
454+
</span>
455+
</Discovery.Activator>
456+
<!-- Dismiss button -->
457+
<Discovery.Activator
458+
class="rounded-lg"
459+
:padding="4"
460+
step="search-dismiss"
461+
>
462+
<span
463+
aria-label="Dismiss result"
464+
:class="[
465+
'inline-flex p-1.5 rounded-lg hover:bg-surface-variant focus-visible:bg-surface-variant transition-colors text-on-surface/60 hover:text-on-surface-variant focus-visible:text-on-surface-variant cursor-pointer',
466+
discovery.isActive.value ? 'opacity-100' : 'opacity-0 group-hover:opacity-100 focus-visible:opacity-100',
467+
]"
468+
role="button"
469+
tabindex="0"
470+
title="Dismiss result"
471+
@click="dismissResult($event, group.items[0].id)"
472+
@keydown.enter.stop="dismissResult($event, group.items[0].id)"
473+
@keydown.space.stop.prevent="dismissResult($event, group.items[0].id)"
474+
>
475+
<AppIcon aria-hidden="true" icon="close" size="16" />
476+
</span>
477+
</Discovery.Activator>
478+
</div>
479+
</div>
480+
</Discovery.Activator>
481+
<!-- Remaining results in first group (skip first) -->
361482
<div
362-
v-for="(result, itemIndex) in group.items"
363-
:id="`search-result-${getFlatIndex(groupIndex, itemIndex)}`"
483+
v-for="(result, itemIndex) in groupIndex === 0 ? group.items.slice(1) : group.items"
484+
:id="`search-result-${getFlatIndex(groupIndex, groupIndex === 0 ? itemIndex + 1 : itemIndex)}`"
364485
:key="result.id"
365-
:aria-selected="getFlatIndex(groupIndex, itemIndex) === search.selection.index.value"
486+
:aria-selected="getFlatIndex(groupIndex, groupIndex === 0 ? itemIndex + 1 : itemIndex) === search.selection.index.value"
366487
:class="[
367488
'group w-full px-4 py-2 flex items-center gap-2 text-left transition-colors cursor-pointer',
368-
getFlatIndex(groupIndex, itemIndex) === search.selection.index.value
489+
getFlatIndex(groupIndex, groupIndex === 0 ? itemIndex + 1 : itemIndex) === search.selection.index.value
369490
? 'bg-primary/10 text-primary'
370491
: 'hover:bg-surface-variant text-on-surface',
371492
]"
372-
:data-selected="getFlatIndex(groupIndex, itemIndex) === search.selection.index.value"
493+
:data-selected="getFlatIndex(groupIndex, groupIndex === 0 ? itemIndex + 1 : itemIndex) === search.selection.index.value"
373494
role="option"
374495
tabindex="-1"
375496
@click="navigate(result)"
376-
@mouseenter="onHover(getFlatIndex(groupIndex, itemIndex))"
497+
@mouseenter="onHover(getFlatIndex(groupIndex, groupIndex === 0 ? itemIndex + 1 : itemIndex))"
377498
>
378499
<div class="flex-1 min-w-0 flex flex-col gap-0.5">
379500
<span class="font-medium">{{ result.title }}</span>

apps/docs/src/pages/skillz.[id].vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import { useDiscovery } from '@/composables/useDiscovery'
1414
import { useNavigation } from '@/composables/useNavigation'
1515
import { useParams } from '@/composables/useRoute'
16+
import { useSearch } from '@/composables/useSearch'
1617
import { useSettings } from '@/composables/useSettings'
1718
1819
// Utilities
@@ -30,6 +31,7 @@
3031
const discovery = useDiscovery()
3132
const ask = useAsk()
3233
const navigation = useNavigation()
34+
const search = useSearch()
3335
const breakpoints = useBreakpoints()
3436
const router = useRouter()
3537
const tour = discovery.tours.get(params.value.id)
@@ -54,6 +56,7 @@
5456
ask,
5557
breakpoints,
5658
navigation,
59+
search,
5760
settings,
5861
},
5962
})
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
{
2+
"mode": "guided",
3+
"id": "using-search",
4+
"name": "Using Search",
5+
"level": 1,
6+
"track": "essentials",
7+
"categories": ["meta"],
8+
"order": 1,
9+
"prerequisites": [],
10+
"description": "Master the documentation search to find components, composables, and guides quickly.",
11+
"minutes": 2,
12+
"startRoute": "/introduction/getting-started",
13+
"steps": [
14+
{
15+
"id": "search",
16+
"title": "Search Button",
17+
"task": "This is the search button. It opens the search panel where you can find components, composables, and guides.",
18+
"hint": "Keyboard shortcut: Ctrl+K (or Cmd+K on Mac)",
19+
"learn": "Where to find search"
20+
},
21+
{
22+
"id": "search-input",
23+
"title": "Search Input",
24+
"task": "This is where you type your search query. Results update instantly as you type.",
25+
"hint": "Arrow keys navigate results, Enter selects.",
26+
"learn": "Where to type search queries",
27+
"placement": "bottom"
28+
},
29+
{
30+
"id": "search-results",
31+
"title": "Search Results",
32+
"task": "Results are grouped by category: Components, Composables, Guides, and more. The best matches appear first.",
33+
"hint": "",
34+
"learn": "How search results are organized",
35+
"placement": "bottom"
36+
},
37+
{
38+
"id": "search-favorite",
39+
"title": "Favorite Results",
40+
"task": "The star icon adds a result to your favorites. Favorites appear at the top when search is empty for quick access.",
41+
"hint": "",
42+
"learn": "What the favorite button does",
43+
"placement": "left",
44+
"placementMobile": "bottom"
45+
},
46+
{
47+
"id": "search-ask-ai",
48+
"title": "Ask AI",
49+
"task": "This button navigates to the page and asks AI about the topic. Great for getting a quick overview.",
50+
"hint": "",
51+
"learn": "What the Ask AI button does",
52+
"placement": "left",
53+
"placementMobile": "bottom"
54+
},
55+
{
56+
"id": "search-dismiss",
57+
"title": "Dismiss Results",
58+
"task": "The X icon hides a result for the current session. Useful for filtering out irrelevant matches.",
59+
"hint": "",
60+
"learn": "What the dismiss button does",
61+
"placement": "left",
62+
"placementMobile": "bottom"
63+
},
64+
{
65+
"id": "search-history",
66+
"title": "Search History",
67+
"task": "Recent searches appear here when the search field is empty. History persists between sessions.",
68+
"hint": "",
69+
"learn": "Where to find search history",
70+
"placement": "bottom"
71+
},
72+
{
73+
"id": "search-favorited",
74+
"title": "Favorited!",
75+
"task": "We just added Tabs to your favorites. Notice it now appears at the top for quick access anytime you open search.",
76+
"hint": "",
77+
"learn": "How favorites work in practice",
78+
"placement": "bottom"
79+
},
80+
{
81+
"id": "conclusion",
82+
"title": "You're a Search Pro!",
83+
"task": "That covers the search features. Use Ctrl+K anytime to quickly find what you need.",
84+
"hint": "",
85+
"learn": "Ready to search like a pro",
86+
"noActivator": true
87+
}
88+
]
89+
}

0 commit comments

Comments
 (0)