Skip to content

Commit ba2dfee

Browse files
authored
Merge branch 'main' into russian
2 parents 062a852 + f8f4373 commit ba2dfee

25 files changed

Lines changed: 2075 additions & 150 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

CONTRIBUTING.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,12 @@ The connector will check your npm authentication, generate a connection token, a
105105

106106
## Code style
107107

108+
When committing changes, try to keep an eye out for unintended formatting updates. These can make a pull request look noisier than it really is and slow down the review process. Sometimes IDEs automatically reformat files on save, which can unintentionally introduce extra changes.
109+
110+
To help with this, the project uses `oxfmt` to handle formatting via a pre-commit hook. The hook will automatically reformat files when needed. If something can’t be fixed automatically, it will let you know what needs to be updated before you can commit.
111+
112+
If you want to get ahead of any formatting issues, you can also run `pnpm lint:fix` before committing to fix formatting across the whole project.
113+
108114
### Typescript
109115

110116
- We care about good types – never cast things to `any` 💪

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: 126 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,52 @@ 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+
watch(
34+
isOnSearchPage,
35+
visible => {
36+
if (!visible) return
37+
38+
searchBoxRef.value?.focus()
39+
nextTick(() => {
40+
searchBoxRef.value?.focus()
41+
})
42+
},
43+
{ flush: 'sync' },
44+
)
45+
46+
function handleSearchBlur() {
47+
showFullSearch.value = false
48+
// Collapse expanded search on mobile after blur (with delay for click handling)
49+
// But don't collapse if we're on the search page
50+
if (isMobile.value && !isOnSearchPage.value) {
51+
setTimeout(() => {
52+
isSearchExpandedManually.value = false
53+
}, 150)
54+
}
55+
}
56+
57+
function handleSearchFocus() {
58+
showFullSearch.value = true
59+
}
1460
1561
onKeyStroke(
1662
',',
@@ -32,43 +78,66 @@ onKeyStroke(
3278
<header class="sticky top-0 z-50 bg-bg/80 backdrop-blur-md border-b border-border">
3379
<nav
3480
:aria-label="$t('nav.main_navigation')"
35-
class="container min-h-14 flex items-center justify-start"
81+
class="container min-h-14 flex items-center justify-between gap-2"
3682
>
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" />
83+
<!-- Mobile: Logo + search button (expands search, doesn't navigate) -->
84+
<button
85+
v-if="!isSearchExpanded"
86+
type="button"
87+
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"
88+
:aria-label="$t('nav.tap_to_search')"
89+
@click="expandMobileSearch"
90+
>
91+
<img
92+
aria-hidden="true"
93+
:alt="$t('alt_logo')"
94+
src="/logo.svg"
95+
width="96"
96+
height="96"
97+
class="w-8 h-8 rounded-lg"
98+
/>
99+
<span class="i-carbon:search w-4 h-4 text-fg-subtle" aria-hidden="true" />
100+
</button>
101+
102+
<!-- Desktop: Logo (navigates home) -->
103+
<div v-if="showLogo" class="hidden sm:flex flex-shrink-0 items-center">
104+
<NuxtLink
105+
to="/"
106+
:aria-label="$t('header.home')"
107+
dir="ltr"
108+
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"
109+
>
110+
<img
111+
aria-hidden="true"
112+
:alt="$t('alt_logo')"
113+
src="/logo.svg"
114+
width="96"
115+
height="96"
116+
class="w-8 h-8 rounded-lg"
117+
/>
118+
<span>npmx</span>
119+
</NuxtLink>
59120
</div>
121+
<!-- Spacer when logo is hidden on desktop -->
122+
<span v-else class="hidden sm:block w-1" />
60123

61124
<!-- 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) -->
125+
<div
126+
class="flex-1 flex items-center justify-center md:gap-6"
127+
:class="{ 'hidden sm:flex': !isSearchExpanded }"
128+
>
129+
<!-- Search bar (hidden on mobile unless expanded) -->
64130
<SearchBox
65-
:inputClass="showFullSearch ? '' : 'max-w[6rem]'"
66-
@focus="showFullSearch = true"
67-
@blur="showFullSearch = false"
131+
ref="searchBoxRef"
132+
:inputClass="isSearchExpanded ? 'w-full' : ''"
133+
:class="{ 'max-w-md': !isSearchExpanded }"
134+
@focus="handleSearchFocus"
135+
@blur="handleSearchBlur"
68136
/>
69137
<ul
70-
:class="{ 'hidden sm:flex': showFullSearch }"
71-
class="flex items-center gap-4 sm:gap-6 list-none m-0 p-0"
138+
v-if="!isSearchExpanded"
139+
:class="{ hidden: showFullSearch }"
140+
class="hidden sm:flex items-center gap-4 sm:gap-6 list-none m-0 p-0"
72141
>
73142
<!-- Packages dropdown (when connected) -->
74143
<li v-if="isConnected && npmUser" class="flex items-center">
@@ -82,34 +151,46 @@ onKeyStroke(
82151
</ul>
83152
</div>
84153

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-
154+
<!-- End: Desktop nav items + Mobile menu button -->
155+
<div class="flex-shrink-0 flex items-center gap-4 sm:gap-6">
156+
<!-- Desktop: Settings link -->
97157
<NuxtLink
98158
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"
159+
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"
100160
aria-keyshortcuts=","
101161
>
102162
{{ $t('nav.settings') }}
103163
<kbd
104-
class="hidden sm:inline-flex items-center justify-center w-5 h-5 text-xs bg-bg-muted border border-border rounded"
164+
class="inline-flex items-center justify-center w-5 h-5 text-xs bg-bg-muted border border-border rounded"
105165
aria-hidden="true"
106166
>
107167
,
108168
</kbd>
109169
</NuxtLink>
110170

111-
<HeaderAccountMenu />
171+
<!-- Desktop: Account menu -->
172+
<div class="hidden sm:block">
173+
<HeaderAccountMenu />
174+
</div>
175+
176+
<!-- Mobile: Menu button (always visible, toggles menu) -->
177+
<button
178+
type="button"
179+
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"
180+
:aria-label="showMobileMenu ? $t('common.close') : $t('nav.open_menu')"
181+
:aria-expanded="showMobileMenu"
182+
@click="showMobileMenu = !showMobileMenu"
183+
>
184+
<span
185+
class="w-6 h-6 inline-block"
186+
:class="showMobileMenu ? 'i-carbon:close' : 'i-carbon:menu'"
187+
aria-hidden="true"
188+
/>
189+
</button>
112190
</div>
113191
</nav>
192+
193+
<!-- Mobile menu -->
194+
<MobileMenu v-model:open="showMobileMenu" />
114195
</header>
115196
</template>

0 commit comments

Comments
 (0)