Skip to content

Commit 1648246

Browse files
committed
docs: add AppCloseButton component and fix fullscreen layout
- Create reusable AppCloseButton with stroke-based X icon - Add close button headers to theme and skill filter popovers - Add mobile header to AppNav with close button - Add AppNav and DocsSearch to fullscreen layout - Fix nav toggle and search not working on fullscreen pages
1 parent e38b4e4 commit 1648246

File tree

9 files changed

+128
-54
lines changed

9 files changed

+128
-54
lines changed

apps/docs/src/components.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ declare module 'vue' {
1616
AppBar: typeof import('./components/app/AppBar.vue')['default']
1717
AppBrowserIcon: typeof import('./components/app/AppBrowserIcon.vue')['default']
1818
AppChip: typeof import('./components/app/AppChip.vue')['default']
19+
AppCloseButton: typeof import('./components/app/AppCloseButton.vue')['default']
1920
AppCopyright: typeof import('./components/app/AppCopyright.vue')['default']
2021
AppDivider: typeof import('./components/app/AppDivider.vue')['default']
2122
AppFooter: typeof import('./components/app/AppFooter.vue')['default']
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<script setup lang="ts">
2+
export interface AppCloseButtonProps {
3+
size?: 'sm' | 'md'
4+
label?: string
5+
}
6+
7+
const {
8+
size = 'md',
9+
label = 'Close',
10+
} = defineProps<AppCloseButtonProps>()
11+
12+
defineEmits<{
13+
click: [e: MouseEvent]
14+
}>()
15+
16+
const sizes = {
17+
sm: { button: 20, icon: 12 },
18+
md: { button: 24, icon: 14 },
19+
}
20+
</script>
21+
22+
<template>
23+
<button
24+
:aria-label="label"
25+
class="close-button"
26+
:style="{
27+
width: `${sizes[size].button}px`,
28+
height: `${sizes[size].button}px`,
29+
}"
30+
:title="label"
31+
type="button"
32+
@click="$emit('click', $event)"
33+
>
34+
<svg
35+
aria-hidden="true"
36+
fill="none"
37+
:height="sizes[size].icon"
38+
stroke="currentColor"
39+
stroke-width="2"
40+
viewBox="0 0 24 24"
41+
:width="sizes[size].icon"
42+
>
43+
<path d="M18 6L6 18M6 6l12 12" />
44+
</svg>
45+
</button>
46+
</template>
47+
48+
<style scoped>
49+
.close-button {
50+
display: flex;
51+
align-items: center;
52+
justify-content: center;
53+
padding: 0;
54+
background: none;
55+
border: none;
56+
border-radius: 4px;
57+
color: var(--v0-text-secondary);
58+
cursor: pointer;
59+
opacity: 0.6;
60+
transition: opacity 0.15s, background 0.15s;
61+
}
62+
63+
.close-button:hover {
64+
opacity: 1;
65+
background: var(--v0-surface-variant);
66+
}
67+
68+
.close-button:focus-visible {
69+
opacity: 1;
70+
outline: 2px solid var(--v0-primary);
71+
outline-offset: 1px;
72+
}
73+
</style>

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,15 @@
150150
:padding="-4"
151151
step="navigation"
152152
>
153+
<!-- Mobile header -->
154+
<header class="md:hidden shrink-0 px-4 py-3 -mt-4 mb-4 border-b border-divider flex items-center justify-between bg-surface">
155+
<div class="flex items-center gap-2">
156+
<AppIcon class="text-primary" icon="menu" />
157+
<span class="font-medium">Navigation</span>
158+
</div>
159+
160+
<AppCloseButton label="Close navigation" @click="navigation.close" />
161+
</header>
153162

154163
<!-- URL filter banner -->
155164
<div v-if="navConfig.activeFeatures.value" class="-mt-4 px-4 py-3 mb-4 bg-surface-variant/50 border-b border-divider">

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

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -69,15 +69,7 @@
6969
<span id="settings-title" class="font-medium">Settings</span>
7070
</div>
7171

72-
<button
73-
aria-label="Close settings"
74-
class="inline-flex p-2 rounded-lg hover:bg-surface-variant transition-colors text-on-surface/60 hover:text-on-surface-variant"
75-
title="Close"
76-
type="button"
77-
@click="settings.close"
78-
>
79-
<AppIcon icon="close" size="18" />
80-
</button>
72+
<AppCloseButton label="Close settings" @click="settings.close" />
8173
</header>
8274

8375
<!-- Content -->

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55
// Composables
66
import { useLevelFilterContext } from '@/composables/useLevelFilter'
77
8+
// Utilities
9+
import { ref } from 'vue'
10+
811
const levelFilter = useLevelFilterContext()
12+
const isOpen = ref(false)
913
1014
const levelConfig: Record<number, { label: string, bg: string, text: string }> = {
1115
1: { label: 'Beginner', bg: 'bg-success border-success', text: 'text-on-success' },
@@ -15,7 +19,7 @@
1519
</script>
1620

1721
<template>
18-
<Popover.Root class="hidden md:block">
22+
<Popover.Root v-model="isOpen" class="hidden md:block">
1923
<Popover.Activator
2024
aria-label="Filter by skill level"
2125
class="relative bg-surface-tint text-on-surface-tint pa-1 inline-flex rounded hover:bg-surface-variant transition-all cursor-pointer"
@@ -32,6 +36,12 @@
3236
</Popover.Activator>
3337

3438
<Popover.Content class="p-2 rounded-lg bg-surface border border-divider shadow-lg min-w-[160px] !mt-1" position-area="bottom span-left">
39+
<!-- Header -->
40+
<div class="flex items-center justify-between mb-2 ps-1">
41+
<span class="text-xs font-semibold text-on-surface">Skill Level</span>
42+
<AppCloseButton size="sm" @click="isOpen = false" />
43+
</div>
44+
3545
<button
3646
v-for="level in levelFilter.levels"
3747
:key="level"

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@
6060
position-area="bottom span-left"
6161
position-try="bottom span-left, bottom span-right, top span-left, top span-right"
6262
>
63+
<!-- Header -->
64+
<div class="flex items-center justify-between mb-3 ps-1">
65+
<span class="text-xs font-semibold text-on-surface">Theme</span>
66+
<AppCloseButton size="sm" @click="isOpen = false" />
67+
</div>
68+
6369
<!-- Mode -->
6470
<div class="mb-3">
6571
<div class="text-xs font-medium text-on-surface-variant mb-2 px-1">Mode</div>

apps/docs/src/components/docs/DocsApiHover.vue

Lines changed: 1 addition & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -336,18 +336,7 @@
336336
<span class="popover-name">{{ displayName }}</span>
337337
<span v-if="vueApi" class="popover-kind popover-kind-vue">{{ vueApi.category }}</span>
338338
<span v-else class="popover-kind" :class="`popover-kind-${activeApiType}`">{{ (activeApi as Api).kind }}</span>
339-
<button aria-label="Close" class="popover-close" type="button" @click.stop="hidePopover">
340-
<svg
341-
fill="none"
342-
height="14"
343-
stroke="currentColor"
344-
stroke-width="2"
345-
viewBox="0 0 24 24"
346-
width="14"
347-
>
348-
<path d="M18 6L6 18M6 6l12 12" />
349-
</svg>
350-
</button>
339+
<AppCloseButton class="ml-auto" @click.stop="hidePopover" />
351340
</div>
352341

353342
<!-- Vue API content -->
@@ -552,28 +541,6 @@
552541
opacity: 0.7;
553542
}
554543
555-
.popover-close {
556-
display: flex;
557-
align-items: center;
558-
justify-content: center;
559-
width: 24px;
560-
height: 24px;
561-
margin-left: auto;
562-
padding: 0;
563-
background: none;
564-
border: none;
565-
border-radius: 4px;
566-
color: var(--v0-text-secondary);
567-
cursor: pointer;
568-
opacity: 0.6;
569-
transition: opacity 0.15s, background 0.15s;
570-
}
571-
572-
.popover-close:hover {
573-
opacity: 1;
574-
background: var(--v0-surface-variant);
575-
}
576-
577544
.popover-description {
578545
flex-shrink: 0;
579546
margin: 12px 0;

apps/docs/src/components/docs/DocsAskPanel.vue

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -164,14 +164,7 @@
164164
<AppIcon :icon="fullscreen ? 'fullscreen-exit' : 'fullscreen'" size="16" />
165165
</button>
166166

167-
<button
168-
class="inline-flex p-1.5 rounded-lg hover:bg-surface-variant transition-colors text-on-surface/60 hover:text-on-surface-variant"
169-
title="Close"
170-
type="button"
171-
@click="emit('close')"
172-
>
173-
<AppIcon icon="close" size="16" />
174-
</button>
167+
<AppCloseButton @click="emit('close')" />
175168
</div>
176169
</header>
177170
</Discovery.Activator>

apps/docs/src/layouts/fullscreen.vue

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
<script setup lang="ts">
2+
// Framework
3+
import { useBreakpoints } from '@vuetify/v0'
4+
25
// Composables
36
import { createLevelFilter } from '@/composables/useLevelFilter'
47
import { createNavConfig } from '@/composables/useNavConfig'
8+
import { useNavigation } from '@/composables/useNavigation'
59
import { useScrollLock } from '@/composables/useScrollLock'
610
import { useSettings } from '@/composables/useSettings'
711
812
// Utilities
9-
import { defineAsyncComponent, toRef } from 'vue'
13+
import { computed, defineAsyncComponent, toRef } from 'vue'
1014
1115
// Stores
1216
import { useAppStore } from '@/stores/app'
1317
1418
const AppSettingsSheet = defineAsyncComponent(() => import('@/components/app/AppSettingsSheet.vue'))
19+
const DocsSearch = defineAsyncComponent(() => import('@/components/docs/DocsSearch.vue'))
1520
1621
const app = useAppStore()
1722
const levelFilter = createLevelFilter(() => app.nav)
@@ -21,12 +26,18 @@
2126
const navConfig = createNavConfig(levelFilter.filteredNav)
2227
navConfig.provide()
2328
29+
const breakpoints = useBreakpoints()
30+
const navigation = useNavigation()
2431
const settings = useSettings()
2532
2633
const fadeTransition = toRef(() => settings.prefersReducedMotion.value ? undefined : 'fade')
2734
const slideTransition = toRef(() => settings.prefersReducedMotion.value ? undefined : 'slide')
2835
36+
const isModalOpen = computed(() => settings.isOpen.value)
37+
const isMobileNavOpen = toRef(() => navigation.isOpen.value && !breakpoints.mdAndUp.value)
38+
2939
useScrollLock(settings.isOpen)
40+
useScrollLock(isMobileNavOpen)
3041
</script>
3142

3243
<template>
@@ -38,14 +49,24 @@
3849
Skip to main content
3950
</a>
4051

41-
<div class="flex flex-col" :inert="settings.isOpen.value || undefined" style="min-height: calc(100vh - 72px)">
52+
<div class="flex flex-col min-h-[calc(100vh-72px)]" :inert="isModalOpen || undefined">
4253
<AppBanner />
54+
<AppNav />
4355
<AppBar />
4456
<main id="main-content" class="flex-1 flex flex-col">
4557
<router-view />
4658
</main>
4759
</div>
4860

61+
<!-- Mobile nav backdrop -->
62+
<Transition :name="fadeTransition">
63+
<div
64+
v-if="isMobileNavOpen"
65+
class="fixed inset-0 bg-black/30 z-9"
66+
@click="navigation.close()"
67+
/>
68+
</Transition>
69+
4970
<!-- Settings backdrop -->
5071
<Transition :name="fadeTransition">
5172
<div
@@ -59,6 +80,8 @@
5980
<Transition :name="slideTransition">
6081
<AppSettingsSheet v-if="settings.isOpen.value" />
6182
</Transition>
83+
84+
<DocsSearch />
6285
</div>
6386
</template>
6487

0 commit comments

Comments
 (0)