-
-
Notifications
You must be signed in to change notification settings - Fork 424
Expand file tree
/
Copy pathAppHeader.vue
More file actions
150 lines (134 loc) · 5 KB
/
AppHeader.vue
File metadata and controls
150 lines (134 loc) · 5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
<script setup lang="ts">
withDefaults(
defineProps<{
showLogo?: boolean
showConnector?: boolean
}>(),
{
showLogo: true,
showConnector: true,
},
)
const { isConnected, npmUser } = useConnector()
const router = useRouter()
const route = useRoute()
const searchQuery = ref('')
const isSearchFocused = ref(false)
const showSearchBar = computed(() => {
return route.name !== 'search' && route.name !== 'index'
})
async function handleSearchInput() {
const query = searchQuery.value.trim()
await router.push({
name: 'search',
query: query ? { q: query } : undefined,
})
searchQuery.value = ''
}
onKeyStroke(',', e => {
// Don't trigger if user is typing in an input
const target = e.target as HTMLElement
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
return
}
e.preventDefault()
router.push('/settings')
})
</script>
<template>
<header
aria-label="Site header"
class="sticky top-0 z-50 bg-bg/80 backdrop-blur-md border-b border-border"
>
<nav aria-label="Main navigation" class="container h-14 flex items-center">
<!-- Left: Logo -->
<div class="flex-shrink-0">
<NuxtLink
v-if="showLogo"
to="/"
:aria-label="$t('header.home')"
class="header-logo font-mono text-lg font-medium text-fg hover:text-fg transition-colors duration-200 focus-ring rounded"
>
<span class="text-accent"><span class="-tracking-0.2em">.</span>/</span>npmx
</NuxtLink>
<!-- Spacer when logo is hidden -->
<span v-else class="w-1" />
</div>
<!-- Center: Search bar + nav items -->
<div class="flex-1 flex items-center justify-center gap-4 sm:gap-6">
<!-- Search bar (shown on all pages except home and search) -->
<search v-if="showSearchBar" class="hidden sm:block flex-1 max-w-md">
<form
role="search"
method="GET"
action="/search"
class="relative"
@submit.prevent="handleSearchInput"
>
<label for="header-search" class="sr-only">
{{ $t('search.label') }}
</label>
<div class="relative group" :class="{ 'is-focused': isSearchFocused }">
<div class="search-box relative flex items-center">
<span
class="absolute left-3 text-fg-subtle font-mono text-sm pointer-events-none transition-colors duration-200 motion-reduce:transition-none group-focus-within:text-accent z-1"
>
/
</span>
<input
id="header-search"
v-model="searchQuery"
type="search"
name="q"
:placeholder="$t('search.placeholder')"
v-bind="noCorrect"
class="w-full bg-bg-subtle border border-border rounded-md pl-7 pr-3 py-1.5 font-mono text-sm text-fg placeholder:text-fg-subtle transition-border-color duration-300 motion-reduce:transition-none focus:border-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/50"
autocomplete="off"
@input="handleSearchInput"
@focus="isSearchFocused = true"
@blur="isSearchFocused = false"
/>
<button type="submit" class="sr-only">{{ $t('search.button') }}</button>
</div>
</div>
</form>
</search>
<ul class="flex items-center gap-4 sm:gap-6 list-none m-0 p-0">
<!-- Packages dropdown (when connected) -->
<li v-if="isConnected && npmUser" class="flex items-center">
<HeaderPackagesDropdown :username="npmUser" />
</li>
<!-- Orgs dropdown (when connected) -->
<li v-if="isConnected && npmUser" class="flex items-center">
<HeaderOrgsDropdown :username="npmUser" />
</li>
</ul>
</div>
<!-- Right: User status + GitHub -->
<div class="flex-shrink-0 flex items-center gap-4 sm:gap-6 ml-auto sm:ml-0">
<NuxtLink
to="/about"
class="sm:hidden link-subtle font-mono text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/50 rounded"
>
{{ $t('footer.about') }}
</NuxtLink>
<NuxtLink
to="/settings"
class="link-subtle font-mono text-sm inline-flex items-center gap-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/50 rounded"
aria-keyshortcuts=","
>
{{ $t('nav.settings') }}
<kbd
class="hidden sm:inline-flex items-center justify-center w-5 h-5 text-xs bg-bg-muted border border-border rounded"
aria-hidden="true"
>
,
</kbd>
</NuxtLink>
<div v-if="showConnector" class="hidden sm:block">
<ConnectorStatus />
</div>
</div>
</nav>
</header>
</template>