Skip to content

Commit d0795eb

Browse files
committed
docs(discovery): add useNavigation composable and step ID debug display
- Extract drawer state from AppStore into useNavigation composable - Add SSR guard to prevent state pollution across server renders - Add navigation context to Discovery tour handlers - Display current step ID in DiscoveryContent (DEV only)
1 parent 33aed5c commit d0795eb

File tree

9 files changed

+120
-39
lines changed

9 files changed

+120
-39
lines changed

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

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import { Discovery } from '@/components/discovery'
77
88
// Composables
9+
import { useNavigation } from '@/composables/useNavigation'
910
import { useSearch } from '@/composables/useSearch'
1011
import { useSettings } from '@/composables/useSettings'
1112
@@ -16,12 +17,9 @@
1617
// Types
1718
import type { AtomProps } from '@vuetify/v0'
1819
19-
// Stores
20-
import { useAppStore } from '@/stores/app'
21-
2220
const { as = 'header' } = defineProps<AtomProps>()
2321
24-
const app = useAppStore()
22+
const navigation = useNavigation()
2523
const storage = useStorage()
2624
const route = useRoute()
2725
@@ -64,13 +62,13 @@
6462

6563
<button
6664
v-if="!isHomePage"
67-
:aria-expanded="app.drawer"
68-
:aria-label="app.drawer ? 'Close navigation' : 'Open navigation'"
65+
:aria-expanded="navigation.isOpen.value"
66+
:aria-label="navigation.isOpen.value ? 'Close navigation' : 'Open navigation'"
6967
class="pa-1 cursor-pointer md:hidden bg-transparent border-0 inline-flex align-center"
7068
type="button"
71-
@click="app.drawer = !app.drawer"
69+
@click="navigation.toggle()"
7270
>
73-
<AppIcon :icon="app.drawer ? 'close' : 'menu'" />
71+
<AppIcon :icon="navigation.isOpen.value ? 'close' : 'menu'" />
7472
</button>
7573

7674
<Discovery.Activator class="rounded-2xl" step="search">

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

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
// Composables
99
import { useLevelFilterContext } from '@/composables/useLevelFilter'
1010
import { useNavConfigContext } from '@/composables/useNavConfig'
11+
import { useNavigation } from '@/composables/useNavigation'
1112
import { createNavNested } from '@/composables/useNavNested'
1213
import { useSettings } from '@/composables/useSettings'
1314
@@ -26,6 +27,7 @@
2627
const devmode = useFeatures().get('devmode')!
2728
2829
const app = useAppStore()
30+
const navigation = useNavigation()
2931
const { selectedLevels } = useLevelFilterContext()
3032
const { configuredNav, activeFeatures, clearFilter } = useNavConfigContext()
3133
const route = useRoute()
@@ -116,16 +118,16 @@
116118
useClickOutside(
117119
() => navRef.value,
118120
() => {
119-
if (app.drawer && isMobile.value) {
120-
app.drawer = false
121+
if (navigation.isOpen.value && isMobile.value) {
122+
navigation.close()
121123
}
122124
},
123125
{ ignore: ['[data-app-bar]'] },
124126
)
125127
126128
watch(route, () => {
127-
if (app.drawer && isMobile.value) {
128-
app.drawer = false
129+
if (navigation.isOpen.value && isMobile.value) {
130+
navigation.close()
129131
}
130132
}, { immediate: true })
131133
</script>
@@ -141,10 +143,10 @@
141143
:class="[
142144
'flex flex-col fixed w-[230px] overflow-y-auto py-4 top-0 md:top-[72px] bottom-0 translate-x-[-100%] md:translate-x-0 border-r border-solid border-divider z-10',
143145
showBgGlass ? 'bg-glass-surface' : 'bg-surface',
144-
app.drawer && '!translate-x-0',
146+
navigation.isOpen.value && '!translate-x-0',
145147
!prefersReducedMotion && 'transition-transform duration-200 ease-in-out',
146148
]"
147-
:inert="!app.drawer && isMobile ? true : undefined"
149+
:inert="!navigation.isOpen.value && isMobile ? true : undefined"
148150
step="navigation"
149151
>
150152

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
const root = useDiscoveryRootContext('v0:discovery')
3636
const discovery = useDiscovery()
3737
const isReady = shallowRef(false)
38+
const isDev = import.meta.env.DEV
3839
3940
// Check for CSS Anchor Positioning support
4041
// Safari/iOS don't support anchor positioning - check for the specific property
@@ -153,6 +154,10 @@
153154
zIndex: 9999,
154155
}"
155156
>
157+
<div v-if="isDev" class="font-mono text-xs text-on-surface-variant/50 mb-1">
158+
{{ root.step }}
159+
</div>
160+
156161
<slot />
157162
</div>
158163
</Teleport>

apps/docs/src/components/docs/DocsAskInput.vue

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,7 @@
99
import { useAsk } from '@/composables/useAsk'
1010
1111
// Utilities
12-
import { computed, onMounted, shallowRef, useTemplateRef, nextTick, watch } from 'vue'
13-
14-
// Stores
15-
import { useAppStore } from '@/stores/app'
12+
import { onMounted, shallowRef, useTemplateRef, nextTick, watch } from 'vue'
1613
1714
const props = defineProps<{
1815
hasMessages?: boolean
@@ -23,7 +20,6 @@
2320
reopen: []
2421
}>()
2522
26-
const app = useAppStore()
2723
const ask = useAsk()
2824
2925
watch(ask.focusTrigger, () => {
@@ -34,10 +30,6 @@
3430
3531
const formRef = useTemplateRef<{ focus: () => void }>('form')
3632
const isNearBottom = shallowRef(false)
37-
const isMobile = shallowRef(true)
38-
39-
// Hide when near bottom, or when drawer is open on mobile (< 768px)
40-
const isHidden = computed(() => isNearBottom.value || (app.drawer && isMobile.value))
4133
4234
function onSubmit (question: string) {
4335
emit('submit', question)
@@ -61,17 +53,10 @@
6153
isNearBottom.value = distanceFromBottom < 200
6254
}
6355
64-
function updateMobile () {
65-
if (!IN_BROWSER) return
66-
isMobile.value = window.innerWidth < 768
67-
}
68-
6956
useWindowEventListener('scroll', onScroll, { passive: true })
70-
useWindowEventListener('resize', updateMobile, { passive: true })
7157
7258
onMounted(() => {
7359
onScroll()
74-
updateMobile()
7560
})
7661
7762
defineExpose({ focus })
@@ -80,7 +65,7 @@
8065
<template>
8166
<Transition name="fade">
8267
<div
83-
v-show="!isHidden"
68+
v-show="!isNearBottom"
8469
class="fixed bottom-4 inset-x-0 mx-auto z-40 w-full max-w-sm px-4"
8570
>
8671
<Discovery.Activator class="rounded-2xl" step="ask-ai">
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/**
2+
* @module useNavigation
3+
*
4+
* @remarks
5+
* Controls the main navigation drawer open/close state.
6+
* Module-level singleton state shared across all useNavigation calls.
7+
*/
8+
9+
// Framework
10+
import { IN_BROWSER } from '@vuetify/v0'
11+
12+
// Utilities
13+
import { readonly, shallowRef } from 'vue'
14+
15+
// Types
16+
import type { ShallowRef } from 'vue'
17+
18+
export interface NavigationContext {
19+
/** Whether the drawer is open */
20+
isOpen: Readonly<ShallowRef<boolean>>
21+
/** Open the drawer */
22+
open: () => void
23+
/** Close the drawer */
24+
close: () => void
25+
/** Toggle the drawer */
26+
toggle: () => void
27+
}
28+
29+
let context: NavigationContext | null = null
30+
31+
/**
32+
* Controls the navigation drawer state.
33+
*
34+
* @returns Navigation state and controls
35+
*
36+
* @example
37+
* ```vue
38+
* <script setup lang="ts">
39+
* import { useNavigation } from '@/composables/useNavigation'
40+
*
41+
* const navigation = useNavigation()
42+
*
43+
* // Open the drawer
44+
* navigation.open()
45+
* </script>
46+
* ```
47+
*/
48+
export function useNavigation (): NavigationContext {
49+
// SSR: return fresh noop context for each server render
50+
if (!IN_BROWSER) {
51+
return {
52+
isOpen: readonly(shallowRef(false)),
53+
open: () => {},
54+
close: () => {},
55+
toggle: () => {},
56+
}
57+
}
58+
59+
if (context) return context
60+
61+
const isOpen = shallowRef(false)
62+
63+
function open () {
64+
isOpen.value = true
65+
}
66+
67+
function close () {
68+
isOpen.value = false
69+
}
70+
71+
function toggle () {
72+
isOpen.value = !isOpen.value
73+
}
74+
75+
context = {
76+
isOpen: readonly(isOpen),
77+
open,
78+
close,
79+
toggle,
80+
}
81+
82+
return context
83+
}

apps/docs/src/layouts/default.vue

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import { useDiscovery } from '@/composables/useDiscovery'
88
import { createLevelFilter } from '@/composables/useLevelFilter'
99
import { createNavConfig } from '@/composables/useNavConfig'
10+
import { useNavigation } from '@/composables/useNavigation'
1011
import { useScrollLock } from '@/composables/useScrollLock'
1112
import { useSearch } from '@/composables/useSearch'
1213
import { useSettings } from '@/composables/useSettings'
@@ -32,6 +33,7 @@
3233
const breakpoints = useBreakpoints()
3334
const discovery = useDiscovery()
3435
const ask = useAsk()
36+
const navigation = useNavigation()
3537
const { isOpen: isSearchOpen } = useSearch()
3638
const { isOpen: isSettingsOpen, close: closeSettings, prefersReducedMotion, forceReducedMotion } = useSettings()
3739
@@ -50,7 +52,7 @@
5052
return false
5153
})
5254
53-
const isMobileNavOpen = toRef(() => app.drawer && !breakpoints.mdAndUp.value)
55+
const isMobileNavOpen = toRef(() => navigation.isOpen.value && !breakpoints.mdAndUp.value)
5456
5557
useScrollLock(isSettingsOpen)
5658
useScrollLock(isMobileNavOpen)
@@ -82,7 +84,7 @@
8284
<div
8385
v-if="isMobileNavOpen"
8486
class="fixed inset-0 bg-black/30 z-9"
85-
@click="app.drawer = false"
87+
@click="navigation.close()"
8688
/>
8789
</Transition>
8890

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
// Composables
99
import { useAsk } from '@/composables/useAsk'
1010
import { useDiscovery } from '@/composables/useDiscovery'
11+
import { useNavigation } from '@/composables/useNavigation'
1112
import { useParams } from '@/composables/useRoute'
1213
import { useSettings } from '@/composables/useSettings'
1314
@@ -25,6 +26,7 @@
2526
const params = useParams<{ id: string }>()
2627
const discovery = useDiscovery()
2728
const ask = useAsk()
29+
const navigation = useNavigation()
2830
const router = useRouter()
2931
const tour = discovery.tours.get(params.value.id)
3032
@@ -41,7 +43,7 @@
4143
if (!tour) return
4244
4345
await router.push(tour.startRoute)
44-
discovery.start(tour.id, { stepId, context: { ask, settings } })
46+
discovery.start(tour.id, { stepId, context: { ask, navigation, settings } })
4547
}
4648
</script>
4749

apps/docs/src/skillz/tours/beginner/using-the-docs/index.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,31 @@
11
// Types
22
import type { UseAskReturn } from '@/composables/useAsk'
3+
import type { NavigationContext } from '@/composables/useNavigation'
34
import type { SettingsContext } from '@/composables/useSettings'
45

56
interface TourContext {
67
ask: UseAskReturn
8+
navigation: NavigationContext
79
settings: SettingsContext
810
}
911

1012
/**
1113
* Returns tour definition for registration by the store.
1214
* Guided mode: handlers set up UI state to show features, users just click next.
1315
*/
14-
export function defineTour ({ ask, settings }: TourContext) {
16+
export function defineTour ({ ask, navigation, settings }: TourContext) {
1517
return {
1618
handlers: {
1719
'ask-ai': {
20+
enter: () => ask.close(),
1821
leave: () => ask.close(),
1922
back: () => ask.close(),
2023
},
2124
'ask-ai-panel': {
2225
enter: () => ask.open(),
2326
},
2427
'ask-ai-features': {
28+
enter: () => ask.open(),
2529
back: () => ask.open(),
2630
},
2731
'settings': {
@@ -30,15 +34,16 @@ export function defineTour ({ ask, settings }: TourContext) {
3034
},
3135
'skill-level': {
3236
enter: () => settings.open(),
37+
back: () => settings.close(),
3338
leave: () => settings.close(),
3439
},
3540
'navigation': {
3641
enter: () => {
3742
settings.close()
38-
// TODO: app().drawer = true - needs app store in context
43+
navigation.open()
3944
},
40-
leave: () => {
41-
// TODO: app().drawer = false - needs app store in context
45+
back: () => {
46+
navigation.close()
4247
},
4348
},
4449
},

apps/docs/src/stores/app.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ function flattenRoutes (nav: NavItem): string[] {
3535

3636
export const useAppStore = defineStore('app', {
3737
state: () => ({
38-
drawer: false,
3938
nav: navData as NavItem[],
4039
stats: {
4140
commit: null as Commit | null,

0 commit comments

Comments
 (0)