Skip to content

Commit 669b90c

Browse files
Adebesin-Cellclaude
andcommitted
feat: mount mobile bottom bar globally; hide AppHeader on mobile
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 4e97065 commit 669b90c

3 files changed

Lines changed: 152 additions & 197 deletions

File tree

app/app.vue

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { isEditableElement } from '~/utils/input'
55
66
const route = useRoute()
77
const router = useRouter()
8+
const { mobileLinks } = useGlobalNavLinks()
89
const { locale, locales } = useI18n()
910
1011
// Initialize user preferences (accent color, package manager) before hydration to prevent flash/CLS
@@ -155,6 +156,9 @@ defineOgImage('Page.takumi', {}, { alt: 'npmx — a fast, modern browser for the
155156
<AppFooter />
156157

157158
<ScrollToTop />
159+
160+
<HeaderMobileBottomBar />
161+
<HeaderMobileMenuSheet :links="mobileLinks" />
158162
</div>
159163
</template>
160164

app/components/AppHeader.vue

Lines changed: 7 additions & 197 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
<script setup lang="ts">
22
import { LinkBase } from '#components'
3-
import type { NavigationConfig, NavigationConfigWithGroups } from '~/types'
4-
import { NPMX_DOCS_SITE } from '#shared/utils/constants'
53
6-
const discord = useDiscordLink()
74
const { open: openCommandPalette } = useCommandPalette()
85
const { commandPaletteShortcutLabel } = usePlatformModifierKey()
96
@@ -18,159 +15,16 @@ withDefaults(
1815
1916
const { isConnected, npmUser } = useConnector()
2017
21-
const desktopLinks = computed<NavigationConfig>(() => [
22-
{
23-
name: 'Compare',
24-
label: $t('nav.compare'),
25-
to: { name: 'compare' },
26-
keyshortcut: 'c',
27-
type: 'link',
28-
external: false,
29-
iconClass: 'i-lucide:git-compare',
30-
},
31-
{
32-
name: 'Settings',
33-
label: $t('nav.settings'),
34-
to: { name: 'settings' },
35-
keyshortcut: ',',
36-
type: 'link',
37-
external: false,
38-
iconClass: 'i-lucide:settings',
39-
},
40-
])
41-
42-
const mobileLinks = computed<NavigationConfigWithGroups>(() => [
43-
{
44-
name: 'Desktop Links',
45-
type: 'group',
46-
items: [...desktopLinks.value],
47-
},
48-
{
49-
type: 'separator',
50-
},
51-
{
52-
name: 'About & Policies',
53-
type: 'group',
54-
items: [
55-
{
56-
name: 'About',
57-
label: $t('footer.about'),
58-
to: { name: 'about' },
59-
type: 'link',
60-
external: false,
61-
iconClass: 'i-lucide:info',
62-
},
63-
{
64-
name: 'Blog',
65-
label: $t('footer.blog'),
66-
to: { name: 'blog' },
67-
type: 'link',
68-
external: false,
69-
iconClass: 'i-lucide:notebook-pen',
70-
},
71-
{
72-
name: 'Privacy Policy',
73-
label: $t('privacy_policy.title'),
74-
to: { name: 'privacy' },
75-
type: 'link',
76-
external: false,
77-
iconClass: 'i-lucide:shield-check',
78-
},
79-
{
80-
name: 'Accessibility',
81-
label: $t('a11y.title'),
82-
to: { name: 'accessibility' },
83-
type: 'link',
84-
external: false,
85-
iconClass: 'i-custom:a11y',
86-
},
87-
{
88-
name: 'Translation Status',
89-
label: $t('translation_status.title'),
90-
to: { name: 'translation-status' },
91-
type: 'link',
92-
external: false,
93-
iconClass: 'i-lucide:languages',
94-
},
95-
{
96-
name: 'Brand',
97-
label: $t('footer.brand'),
98-
to: { name: 'brand' },
99-
type: 'link',
100-
external: false,
101-
iconClass: 'i-lucide:palette',
102-
},
103-
],
104-
},
105-
{
106-
type: 'separator',
107-
},
108-
{
109-
name: 'External Links',
110-
type: 'group',
111-
label: $t('nav.links'),
112-
items: [
113-
{
114-
name: 'Docs',
115-
label: $t('footer.docs'),
116-
href: NPMX_DOCS_SITE,
117-
target: '_blank',
118-
type: 'link',
119-
external: true,
120-
iconClass: 'i-lucide:file-text',
121-
},
122-
{
123-
name: 'Source',
124-
label: $t('footer.source'),
125-
href: 'https://repo.npmx.dev',
126-
target: '_blank',
127-
type: 'link',
128-
external: true,
129-
iconClass: 'i-simple-icons:github',
130-
},
131-
{
132-
name: 'Social',
133-
label: $t('footer.social'),
134-
href: 'https://social.npmx.dev',
135-
target: '_blank',
136-
type: 'link',
137-
external: true,
138-
iconClass: 'i-simple-icons:bluesky',
139-
},
140-
{
141-
name: 'Chat',
142-
label: discord.value.label,
143-
href: discord.value.url,
144-
target: '_blank',
145-
type: 'link',
146-
external: true,
147-
iconClass: 'i-lucide:message-circle',
148-
},
149-
],
150-
},
151-
])
18+
const { desktopLinks } = useGlobalNavLinks()
15219
15320
const showFullSearch = shallowRef(false)
154-
const showMobileMenu = shallowRef(false)
15521
const { env, prNumber } = useAppConfig().buildInfo
15622
157-
// On mobile, clicking logo+search button expands search
15823
const route = useRoute()
159-
const isMobile = useIsMobile()
160-
const isSearchExpandedManually = shallowRef(false)
16124
const searchBoxRef = useTemplateRef('searchBoxRef')
16225
163-
// On search page, always show search expanded on mobile
16426
const isOnHomePage = computed(() => route.name === 'index')
16527
const isOnSearchPage = computed(() => route.name === 'search')
166-
const isSearchExpanded = computed(() => isOnSearchPage.value || isSearchExpandedManually.value)
167-
168-
function expandMobileSearch() {
169-
isSearchExpandedManually.value = true
170-
nextTick(() => {
171-
searchBoxRef.value?.focus()
172-
})
173-
}
17428
17529
watch(
17630
isOnSearchPage,
@@ -187,13 +41,6 @@ watch(
18741
18842
function handleSearchBlur() {
18943
showFullSearch.value = false
190-
// Collapse expanded search on mobile after blur (with delay for click handling)
191-
// But don't collapse if we're on the search page
192-
if (isMobile.value && !isOnSearchPage.value) {
193-
setTimeout(() => {
194-
isSearchExpandedManually.value = false
195-
}, 150)
196-
}
19744
}
19845
19946
function handleSearchFocus() {
@@ -207,23 +54,12 @@ useShortcuts({
20754
</script>
20855

20956
<template>
210-
<header class="sticky top-0 z-50 border-b border-border">
57+
<header class="hidden sm:block sticky top-0 z-50 border-b border-border">
21158
<div class="absolute inset-0 bg-bg/80 backdrop-blur-md" />
21259
<nav
21360
:aria-label="$t('nav.main_navigation')"
21461
class="relative container min-h-14 flex items-center gap-2 z-1 justify-end"
21562
>
216-
<!-- Mobile: Logo (navigates home) -->
217-
<LogoContextMenu v-if="!isSearchExpanded && !isOnHomePage" class="sm:hidden flex-shrink-0">
218-
<NuxtLink
219-
to="/"
220-
:aria-label="$t('header.home')"
221-
class="font-mono text-lg font-medium text-fg hover:text-fg transition-colors duration-200 focus-ring me-4"
222-
>
223-
<AppMark class="w-6 h-auto" />
224-
</NuxtLink>
225-
</LogoContextMenu>
226-
22763
<!-- Desktop: Logo (navigates home) -->
22864
<LogoContextMenu v-if="showLogo" class="hidden sm:flex flex-shrink-0 items-center">
22965
<NuxtLink
@@ -243,7 +79,7 @@ useShortcuts({
24379
</LogoContextMenu>
24480

24581
<NuxtLink
246-
v-if="showLogo && !isSearchExpanded && prNumber"
82+
v-if="showLogo && prNumber"
24783
:to="`https://github.com/npmx-dev/npmx.dev/pull/${prNumber}`"
24884
:aria-label="$t('header.pr', { prNumber })"
24985
>
@@ -277,21 +113,19 @@ useShortcuts({
277113
<div
278114
class="flex-1 flex items-center md:gap-6"
279115
:class="{
280-
'hidden sm:flex': !isSearchExpanded,
281116
'justify-end': isOnHomePage,
282117
'justify-center': !isOnHomePage,
283118
}"
284119
>
285-
<!-- Search bar (hidden on mobile unless expanded) -->
120+
<!-- Search bar -->
286121
<HeaderSearchBox
287122
ref="searchBoxRef"
288-
:inputClass="isSearchExpanded ? 'w-full' : ''"
289-
:class="{ 'max-w-md': !isSearchExpanded }"
123+
:class="{ 'max-w-md': !showFullSearch }"
290124
@focus="handleSearchFocus"
291125
@blur="handleSearchBlur"
292126
/>
293127
<ul
294-
v-if="!isSearchExpanded && isConnected && npmUser"
128+
v-if="isConnected && npmUser"
295129
:class="{ hidden: showFullSearch }"
296130
class="hidden sm:flex items-center gap-4 sm:gap-6 list-none m-0 p-0"
297131
>
@@ -307,7 +141,7 @@ useShortcuts({
307141
</ul>
308142
</div>
309143

310-
<!-- End: Desktop nav items + Mobile menu button -->
144+
<!-- End: Desktop nav items -->
311145
<div class="hidden sm:flex flex-shrink-0 items-center gap-2">
312146
<!-- Desktop: Explore link -->
313147
<LinkBase
@@ -323,30 +157,6 @@ useShortcuts({
323157

324158
<HeaderAccountMenu />
325159
</div>
326-
327-
<!-- Mobile: Search button (expands search) -->
328-
<ButtonBase
329-
type="button"
330-
class="sm:hidden ms-auto"
331-
:aria-label="$t('nav.tap_to_search')"
332-
:aria-expanded="showMobileMenu"
333-
@click="expandMobileSearch"
334-
v-if="!isSearchExpanded && !isOnHomePage"
335-
classicon="i-lucide:search"
336-
/>
337-
338-
<!-- Mobile: Menu button (always visible, click to open menu) -->
339-
<ButtonBase
340-
type="button"
341-
class="sm:hidden"
342-
:aria-label="$t('nav.open_menu')"
343-
:aria-expanded="showMobileMenu"
344-
@click="showMobileMenu = !showMobileMenu"
345-
classicon="i-lucide:menu"
346-
/>
347160
</nav>
348-
349-
<!-- Mobile menu -->
350-
<HeaderMobileMenu :links="mobileLinks" v-model:open="showMobileMenu" />
351161
</header>
352162
</template>

0 commit comments

Comments
 (0)