Skip to content

Commit f636f6f

Browse files
authored
Merge branch 'main' into perf/ofetch-timeout
2 parents 82711f7 + 485cc9c commit f636f6f

18 files changed

Lines changed: 429 additions & 80 deletions

.github/workflows/ci.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ on:
1111
branches:
1212
- main
1313

14+
# cancel in-progress runs on new commits to same PR (gitub.event.number)
15+
concurrency:
16+
group: ${{ github.workflow }}-${{ github.event.number || github.sha }}
17+
cancel-in-progress: true
18+
1419
permissions:
1520
contents: read
1621

app/components/AppFooter.vue

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,16 @@ const isHome = computed(() => route.name === 'index')
1313
<p class="font-mono text-balance m-0 hidden sm:block">{{ $t('tagline') }}</p>
1414
<BuildEnvironment v-if="!isHome" footer />
1515
</div>
16-
<div class="flex flex-wrap items-center gap-x-3 sm:gap-6">
17-
<NuxtLink
18-
to="/about"
19-
class="link-subtle font-mono text-xs min-h-8 sm:min-h-11 flex items-center"
20-
>
16+
<!-- Desktop: Show all links. Mobile: Links are in MobileMenu -->
17+
<div class="hidden sm:flex items-center gap-6">
18+
<NuxtLink to="/about" class="link-subtle font-mono text-xs min-h-11 flex items-center">
2119
{{ $t('footer.about') }}
2220
</NuxtLink>
2321
<a
2422
href="https://docs.npmx.dev"
2523
target="_blank"
2624
rel="noopener noreferrer"
27-
class="link-subtle font-mono text-xs min-h-8 sm:min-h-11 flex items-center gap-1"
25+
class="link-subtle font-mono text-xs min-h-11 flex items-center gap-1"
2826
>
2927
{{ $t('footer.docs') }}
3028
<span class="i-carbon:launch rtl-flip w-3 h-3" aria-hidden="true" />
@@ -33,7 +31,7 @@ const isHome = computed(() => route.name === 'index')
3331
href="https://repo.npmx.dev"
3432
target="_blank"
3533
rel="noopener noreferrer"
36-
class="link-subtle font-mono text-xs min-h-8 sm:min-h-11 flex items-center gap-1"
34+
class="link-subtle font-mono text-xs min-h-11 flex items-center gap-1"
3735
>
3836
{{ $t('footer.source') }}
3937
<span class="i-carbon:launch rtl-flip w-3 h-3" aria-hidden="true" />
@@ -42,7 +40,7 @@ const isHome = computed(() => route.name === 'index')
4240
href="https://social.npmx.dev"
4341
target="_blank"
4442
rel="noopener noreferrer"
45-
class="link-subtle font-mono text-xs min-h-8 sm:min-h-11 flex items-center gap-1"
43+
class="link-subtle font-mono text-xs min-h-11 flex items-center gap-1"
4644
>
4745
{{ $t('footer.social') }}
4846
<span class="i-carbon:launch rtl-flip w-3 h-3" aria-hidden="true" />
@@ -51,7 +49,7 @@ const isHome = computed(() => route.name === 'index')
5149
href="https://chat.npmx.dev"
5250
target="_blank"
5351
rel="noopener noreferrer"
54-
class="link-subtle font-mono text-xs min-h-8 sm:min-h-11 flex items-center gap-1"
52+
class="link-subtle font-mono text-xs min-h-11 flex items-center gap-1"
5553
>
5654
{{ $t('footer.chat') }}
5755
<span class="i-carbon:launch rtl-flip w-3 h-3" aria-hidden="true" />

app/components/AppHeader.vue

Lines changed: 113 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,39 @@ withDefaults(
1111
const { isConnected, npmUser } = useConnector()
1212
1313
const showFullSearch = shallowRef(false)
14+
const showMobileMenu = shallowRef(false)
15+
16+
// On mobile, clicking logo+search button expands search
17+
const route = useRoute()
18+
const isMobile = useIsMobile()
19+
const isSearchExpandedManually = shallowRef(false)
20+
const searchBoxRef = shallowRef<{ focus: () => void } | null>(null)
21+
22+
// On search page, always show search expanded on mobile
23+
const isOnSearchPage = computed(() => route.name === 'search')
24+
const isSearchExpanded = computed(() => isOnSearchPage.value || isSearchExpandedManually.value)
25+
26+
function expandMobileSearch() {
27+
isSearchExpandedManually.value = true
28+
nextTick(() => {
29+
searchBoxRef.value?.focus()
30+
})
31+
}
32+
33+
function handleSearchBlur() {
34+
showFullSearch.value = false
35+
// Collapse expanded search on mobile after blur (with delay for click handling)
36+
// But don't collapse if we're on the search page
37+
if (isMobile.value && !isOnSearchPage.value) {
38+
setTimeout(() => {
39+
isSearchExpandedManually.value = false
40+
}, 150)
41+
}
42+
}
43+
44+
function handleSearchFocus() {
45+
showFullSearch.value = true
46+
}
1447
1548
onKeyStroke(
1649
',',
@@ -32,43 +65,66 @@ onKeyStroke(
3265
<header class="sticky top-0 z-50 bg-bg/80 backdrop-blur-md border-b border-border">
3366
<nav
3467
:aria-label="$t('nav.main_navigation')"
35-
class="container min-h-14 flex items-center justify-start"
68+
class="container min-h-14 flex items-center justify-between gap-2"
3669
>
37-
<!-- Start: Logo -->
38-
<div :class="{ 'hidden sm:block': showFullSearch }" class="flex-shrink-0">
39-
<div v-if="showLogo">
40-
<NuxtLink
41-
to="/"
42-
:aria-label="$t('header.home')"
43-
dir="ltr"
44-
class="inline-flex items-center gap-2 header-logo font-mono text-lg font-medium text-fg hover:text-fg transition-colors duration-200 focus-ring rounded"
45-
>
46-
<img
47-
aria-hidden="true"
48-
:alt="$t('alt_logo')"
49-
src="/logo.svg"
50-
width="96"
51-
height="96"
52-
class="w-8 h-8 rounded-lg"
53-
/>
54-
<span>npmx</span>
55-
</NuxtLink>
56-
</div>
57-
<!-- Spacer when logo is hidden -->
58-
<span v-else class="w-1" />
70+
<!-- Mobile: Logo + search button (expands search, doesn't navigate) -->
71+
<button
72+
v-if="!isSearchExpanded"
73+
type="button"
74+
class="sm:hidden flex-shrink-0 inline-flex items-center gap-2 font-mono text-lg font-medium text-fg hover:text-fg transition-colors duration-200 focus-ring rounded"
75+
:aria-label="$t('nav.tap_to_search')"
76+
@click="expandMobileSearch"
77+
>
78+
<img
79+
aria-hidden="true"
80+
:alt="$t('alt_logo')"
81+
src="/logo.svg"
82+
width="96"
83+
height="96"
84+
class="w-8 h-8 rounded-lg"
85+
/>
86+
<span class="i-carbon:search w-4 h-4 text-fg-subtle" aria-hidden="true" />
87+
</button>
88+
89+
<!-- Desktop: Logo (navigates home) -->
90+
<div v-if="showLogo" class="hidden sm:flex flex-shrink-0 items-center">
91+
<NuxtLink
92+
to="/"
93+
:aria-label="$t('header.home')"
94+
dir="ltr"
95+
class="inline-flex items-center gap-2 header-logo font-mono text-lg font-medium text-fg hover:text-fg transition-colors duration-200 focus-ring rounded"
96+
>
97+
<img
98+
aria-hidden="true"
99+
:alt="$t('alt_logo')"
100+
src="/logo.svg"
101+
width="96"
102+
height="96"
103+
class="w-8 h-8 rounded-lg"
104+
/>
105+
<span>npmx</span>
106+
</NuxtLink>
59107
</div>
108+
<!-- Spacer when logo is hidden on desktop -->
109+
<span v-else class="hidden sm:block w-1" />
60110

61111
<!-- Center: Search bar + nav items -->
62-
<div class="flex-1 flex items-center justify-center md:gap-6 mx-2">
63-
<!-- Search bar (shown on all pages except home) -->
112+
<div
113+
class="flex-1 flex items-center justify-center md:gap-6"
114+
:class="{ 'hidden sm:flex': !isSearchExpanded }"
115+
>
116+
<!-- Search bar (hidden on mobile unless expanded) -->
64117
<SearchBox
65-
:inputClass="showFullSearch ? '' : 'max-w[6rem]'"
66-
@focus="showFullSearch = true"
67-
@blur="showFullSearch = false"
118+
ref="searchBoxRef"
119+
:inputClass="isSearchExpanded ? 'w-full' : ''"
120+
:class="{ 'max-w-md': !isSearchExpanded }"
121+
@focus="handleSearchFocus"
122+
@blur="handleSearchBlur"
68123
/>
69124
<ul
70-
:class="{ 'hidden sm:flex': showFullSearch }"
71-
class="flex items-center gap-4 sm:gap-6 list-none m-0 p-0"
125+
v-if="!isSearchExpanded"
126+
:class="{ hidden: showFullSearch }"
127+
class="hidden sm:flex items-center gap-4 sm:gap-6 list-none m-0 p-0"
72128
>
73129
<!-- Packages dropdown (when connected) -->
74130
<li v-if="isConnected && npmUser" class="flex items-center">
@@ -82,34 +138,46 @@ onKeyStroke(
82138
</ul>
83139
</div>
84140

85-
<!-- End: User status + GitHub -->
86-
<div
87-
:class="{ 'hidden sm:flex': showFullSearch }"
88-
class="flex-1 flex flex-wrap items-center justify-end sm:gap-3 ms-auto sm:ms-0"
89-
>
90-
<NuxtLink
91-
to="/about"
92-
class="px-2 py-1.5 sm:hidden link-subtle font-mono text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/50 rounded"
93-
>
94-
{{ $t('footer.about') }}
95-
</NuxtLink>
96-
141+
<!-- End: Desktop nav items + Mobile menu button -->
142+
<div class="flex-shrink-0 flex items-center gap-4 sm:gap-6">
143+
<!-- Desktop: Settings link -->
97144
<NuxtLink
98145
to="/settings"
99-
class="link-subtle font-mono text-sm inline-flex items-center gap-2 px-2 py-1.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/50 rounded"
146+
class="hidden sm:inline-flex link-subtle font-mono text-sm items-center gap-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/50 rounded"
100147
aria-keyshortcuts=","
101148
>
102149
{{ $t('nav.settings') }}
103150
<kbd
104-
class="hidden sm:inline-flex items-center justify-center w-5 h-5 text-xs bg-bg-muted border border-border rounded"
151+
class="inline-flex items-center justify-center w-5 h-5 text-xs bg-bg-muted border border-border rounded"
105152
aria-hidden="true"
106153
>
107154
,
108155
</kbd>
109156
</NuxtLink>
110157

111-
<HeaderAccountMenu />
158+
<!-- Desktop: Account menu -->
159+
<div class="hidden sm:block">
160+
<HeaderAccountMenu />
161+
</div>
162+
163+
<!-- Mobile: Menu button (always visible, toggles menu) -->
164+
<button
165+
type="button"
166+
class="sm:hidden p-2 -m-2 text-fg-subtle hover:text-fg transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/50 rounded"
167+
:aria-label="showMobileMenu ? $t('common.close') : $t('nav.open_menu')"
168+
:aria-expanded="showMobileMenu"
169+
@click="showMobileMenu = !showMobileMenu"
170+
>
171+
<span
172+
class="w-6 h-6 inline-block"
173+
:class="showMobileMenu ? 'i-carbon:close' : 'i-carbon:menu'"
174+
aria-hidden="true"
175+
/>
176+
</button>
112177
</div>
113178
</nav>
179+
180+
<!-- Mobile menu -->
181+
<MobileMenu v-model:open="showMobileMenu" />
114182
</header>
115183
</template>

app/components/BuildEnvironment.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const buildInfo = useAppConfig().buildInfo
1313
:class="footer ? 'mt-4 justify-start' : 'mb-8 justify-center'"
1414
style="animation-delay: 0.05s"
1515
>
16-
<i18n-t keypath="built_at">
16+
<i18n-t keypath="built_at" scope="global">
1717
<NuxtTime :datetime="buildInfo.time" :locale="locale" relative />
1818
</i18n-t>
1919
<span>&middot;</span>

0 commit comments

Comments
 (0)