Skip to content

Commit abf5521

Browse files
committed
Merge remote-tracking branch 'upstream/main' into vt/nullvoxpopuli
2 parents 79ef365 + 8c8f2fa commit abf5521

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+2388
-1009
lines changed

app/components/AppFooter.vue

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
<script setup lang="ts">
22
const route = useRoute()
33
const isHome = computed(() => route.name === 'index')
4+
5+
const modalRef = useTemplateRef('modalRef')
6+
const showModal = () => modalRef.value?.showModal?.()
47
</script>
58

69
<template>
@@ -11,7 +14,6 @@ const isHome = computed(() => route.name === 'index')
1114
>
1215
<div>
1316
<p class="font-mono text-balance m-0 hidden sm:block">{{ $t('tagline') }}</p>
14-
<BuildEnvironment v-if="!isHome" footer />
1517
</div>
1618
<!-- Desktop: Show all links. Mobile: Links are in MobileMenu -->
1719
<div class="hidden sm:flex items-center gap-6 min-h-11 text-xs">
@@ -33,12 +35,86 @@ const isHome = computed(() => route.name === 'index')
3335
<LinkBase to="https://chat.npmx.dev">
3436
{{ $t('footer.chat') }}
3537
</LinkBase>
38+
39+
<button
40+
type="button"
41+
class="group inline-flex gap-x-1 items-center justify-center underline-offset-[0.2rem] underline decoration-1 decoration-fg/30 font-mono text-fg hover:(decoration-accent text-accent) focus-visible:(decoration-accent text-accent) transition-colors duration-200"
42+
@click.prevent="showModal"
43+
aria-haspopup="dialog"
44+
>
45+
{{ $t('footer.keyboard_shortcuts') }}
46+
</button>
47+
48+
<Modal
49+
ref="modalRef"
50+
:modalTitle="$t('footer.keyboard_shortcuts')"
51+
class="w-auto max-w-lg"
52+
>
53+
<p class="mb-2 font-mono text-fg-subtle">
54+
{{ $t('shortcuts.section.global') }}
55+
</p>
56+
<ul class="mb-6 flex flex-col gap-2">
57+
<li class="flex gap-2 items-center">
58+
<kbd class="kbd">/</kbd>
59+
<span>{{ $t('shortcuts.focus_search') }}</span>
60+
</li>
61+
<li class="flex gap-2 items-center">
62+
<kbd class="kbd">?</kbd>
63+
<span>{{ $t('shortcuts.show_kbd_hints') }}</span>
64+
</li>
65+
<li class="flex gap-2 items-center">
66+
<kbd class="kbd">,</kbd>
67+
<span>{{ $t('shortcuts.settings') }}</span>
68+
</li>
69+
<li class="flex gap-2 items-center">
70+
<kbd class="kbd">c</kbd>
71+
<span>{{ $t('shortcuts.compare') }}</span>
72+
</li>
73+
</ul>
74+
<p class="mb-2 font-mono text-fg-subtle">
75+
{{ $t('shortcuts.section.search') }}
76+
</p>
77+
<ul class="mb-6 flex flex-col gap-2">
78+
<li class="flex gap-2 items-center">
79+
<kbd class="kbd">↑</kbd>/<kbd class="kbd">↓</kbd>
80+
<span>{{ $t('shortcuts.navigate_results') }}</span>
81+
</li>
82+
<li class="flex gap-2 items-center">
83+
<kbd class="kbd">Enter</kbd>
84+
<span>{{ $t('shortcuts.go_to_result') }}</span>
85+
</li>
86+
</ul>
87+
<p class="mb-2 font-mono text-fg-subtle">
88+
{{ $t('shortcuts.section.package') }}
89+
</p>
90+
<ul class="mb-6 flex flex-col gap-2">
91+
<li class="flex gap-2 items-center">
92+
<kbd class="kbd">.</kbd>
93+
<span>{{ $t('shortcuts.open_code_view') }}</span>
94+
</li>
95+
<li class="flex gap-2 items-center">
96+
<kbd class="kbd">d</kbd>
97+
<span>{{ $t('shortcuts.open_docs') }}</span>
98+
</li>
99+
<li class="flex gap-2 items-center">
100+
<kbd class="kbd">c</kbd>
101+
<span>{{ $t('shortcuts.compare_from_package') }}</span>
102+
</li>
103+
</ul>
104+
</Modal>
36105
</div>
37106
</div>
107+
<BuildEnvironment v-if="!isHome" footer />
38108
<p class="text-xs text-fg-muted text-center sm:text-start m-0">
39109
<span class="sm:hidden">{{ $t('non_affiliation_disclaimer') }}</span>
40110
<span class="hidden sm:inline">{{ $t('trademark_disclaimer') }}</span>
41111
</p>
42112
</div>
43113
</footer>
44114
</template>
115+
116+
<style scoped>
117+
.kbd {
118+
@apply items-center justify-center text-sm text-fg bg-bg-muted border border-border rounded px-2;
119+
}
120+
</style>

app/components/AppHeader.vue

Lines changed: 124 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script setup lang="ts">
22
import { LinkBase } from '#components'
3+
import type { NavigationConfig, NavigationConfigWithGroups } from '~/types'
34
import { isEditableElement } from '~/utils/input'
45
56
withDefaults(
@@ -13,6 +14,106 @@ withDefaults(
1314
1415
const { isConnected, npmUser } = useConnector()
1516
17+
const desktopLinks = computed<NavigationConfig>(() => [
18+
{
19+
name: 'Compare',
20+
label: $t('nav.compare'),
21+
to: { name: 'compare' },
22+
keyshortcut: 'c',
23+
type: 'link',
24+
external: false,
25+
iconClass: 'i-carbon:compare',
26+
},
27+
{
28+
name: 'Settings',
29+
label: $t('nav.settings'),
30+
to: { name: 'settings' },
31+
keyshortcut: ',',
32+
type: 'link',
33+
external: false,
34+
iconClass: 'i-carbon:settings',
35+
},
36+
])
37+
38+
const mobileLinks = computed<NavigationConfigWithGroups>(() => [
39+
{
40+
name: 'Desktop Links',
41+
type: 'group',
42+
items: [...desktopLinks.value],
43+
},
44+
{
45+
type: 'separator',
46+
},
47+
{
48+
name: 'About & Policies',
49+
type: 'group',
50+
items: [
51+
{
52+
name: 'About',
53+
label: $t('footer.about'),
54+
to: { name: 'about' },
55+
type: 'link',
56+
external: false,
57+
iconClass: 'i-carbon:information',
58+
},
59+
{
60+
name: 'Privacy Policy',
61+
label: $t('privacy_policy.title'),
62+
to: { name: 'privacy' },
63+
type: 'link',
64+
external: false,
65+
iconClass: 'i-carbon:security',
66+
},
67+
],
68+
},
69+
{
70+
type: 'separator',
71+
},
72+
{
73+
name: 'External Links',
74+
type: 'group',
75+
label: $t('nav.links'),
76+
items: [
77+
{
78+
name: 'Docs',
79+
label: $t('footer.docs'),
80+
href: 'https://docs.npmx.dev',
81+
target: '_blank',
82+
type: 'link',
83+
external: true,
84+
iconClass: 'i-carbon:document',
85+
},
86+
{
87+
name: 'Source',
88+
label: $t('footer.source'),
89+
href: 'https://repo.npmx.dev',
90+
target: '_blank',
91+
type: 'link',
92+
external: true,
93+
iconClass: 'i-carbon:logo-github',
94+
},
95+
{
96+
name: 'Social',
97+
label: $t('footer.social'),
98+
href: 'https://social.npmx.dev',
99+
target: '_blank',
100+
type: 'link',
101+
external: true,
102+
iconClass: 'i-simple-icons:bluesky',
103+
},
104+
{
105+
name: 'Chat',
106+
label: $t('footer.chat'),
107+
href: 'https://chat.npmx.dev',
108+
target: '_blank',
109+
type: 'link',
110+
external: true,
111+
iconClass: 'i-carbon:chat',
112+
},
113+
],
114+
},
115+
])
116+
16117
const showFullSearch = shallowRef(false)
17118
const showMobileMenu = shallowRef(false)
18119
@@ -63,23 +164,18 @@ function handleSearchFocus() {
63164
}
64165
65166
onKeyStroke(
66-
e => isKeyWithoutModifiers(e, ',') && !isEditableElement(e.target),
67167
e => {
68-
e.preventDefault()
69-
navigateTo({ name: 'settings' })
70-
},
71-
{ dedupe: true },
72-
)
168+
if (isEditableElement(e.target)) {
169+
return
170+
}
73171
74-
onKeyStroke(
75-
e =>
76-
isKeyWithoutModifiers(e, 'c') &&
77-
!isEditableElement(e.target) &&
78-
// Allow more specific handlers to take precedence
79-
!e.defaultPrevented,
80-
e => {
81-
e.preventDefault()
82-
navigateTo({ name: 'compare' })
172+
for (const link of desktopLinks.value) {
173+
if (link.to && link.keyshortcut && isKeyWithoutModifiers(e, link.keyshortcut)) {
174+
e.preventDefault()
175+
navigateTo(link.to.name)
176+
break
177+
}
178+
}
83179
},
84180
{ dedupe: true },
85181
)
@@ -122,8 +218,12 @@ onKeyStroke(
122218

123219
<!-- Center: Search bar + nav items -->
124220
<div
125-
class="flex-1 flex items-center justify-center md:gap-6"
126-
:class="{ 'hidden sm:flex': !isSearchExpanded }"
221+
class="flex-1 flex items-center md:gap-6"
222+
:class="{
223+
'hidden sm:flex': !isSearchExpanded,
224+
'justify-end': isOnHomePage,
225+
'justify-center': !isOnHomePage,
226+
}"
127227
>
128228
<!-- Search bar (hidden on mobile unless expanded) -->
129229
<HeaderSearchBox
@@ -152,24 +252,16 @@ onKeyStroke(
152252

153253
<!-- End: Desktop nav items + Mobile menu button -->
154254
<div class="hidden sm:flex flex-shrink-0">
155-
<!-- Desktop: Compare link -->
156-
<LinkBase
157-
class="border-none"
158-
variant="button-secondary"
159-
:to="{ name: 'compare' }"
160-
keyshortcut="c"
161-
>
162-
{{ $t('nav.compare') }}
163-
</LinkBase>
164-
165-
<!-- Desktop: Settings link -->
255+
<!-- Desktop: Explore link -->
166256
<LinkBase
257+
v-for="link in desktopLinks"
258+
:key="link.name"
167259
class="border-none"
168260
variant="button-secondary"
169-
:to="{ name: 'settings' }"
170-
keyshortcut=","
261+
:to="link.to"
262+
:keyshortcut="link.keyshortcut"
171263
>
172-
{{ $t('nav.settings') }}
264+
{{ link.label }}
173265
</LinkBase>
174266

175267
<HeaderAccountMenu />
@@ -187,6 +279,6 @@ onKeyStroke(
187279
</nav>
188280

189281
<!-- Mobile menu -->
190-
<HeaderMobileMenu v-model:open="showMobileMenu" />
282+
<HeaderMobileMenu :links="mobileLinks" v-model:open="showMobileMenu" />
191283
</header>
192284
</template>

app/components/BuildEnvironment.vue

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
<script setup lang="ts">
2-
defineProps<{
2+
import type { BuildInfo } from '#shared/types'
3+
4+
const { footer = false, buildInfo: buildInfoProp } = defineProps<{
35
footer?: boolean
6+
buildInfo?: BuildInfo
47
}>()
58
69
const { locale } = useI18n()
7-
const buildInfo = useAppConfig().buildInfo
10+
const appConfig = useAppConfig()
11+
const buildInfo = computed(() => buildInfoProp || appConfig.buildInfo)
812
</script>
913

1014
<template>
1115
<div
1216
class="font-mono text-xs text-fg-muted flex items-center gap-2 motion-safe:animate-fade-in motion-safe:animate-fill-both"
13-
:class="footer ? 'mt-4 justify-start' : 'mb-8 justify-center'"
17+
:class="footer ? 'my-1 justify-center sm:justify-start' : 'mb-8 justify-center'"
1418
style="animation-delay: 0.05s"
1519
>
1620
<i18n-t keypath="built_at" scope="global">

app/components/Compare/PackageSelector.vue

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ const maxPackages = computed(() => props.max ?? 4)
1414
const inputValue = shallowRef('')
1515
const isInputFocused = shallowRef(false)
1616
17-
// Use the shared npm search composable
18-
const { data: searchData, status } = useNpmSearch(inputValue, { size: 15 })
17+
// Use the shared search composable (supports both npm and Algolia providers)
18+
const { data: searchData, status } = useSearch(inputValue, { size: 15 })
1919
2020
const isSearching = computed(() => status.value === 'pending')
2121
@@ -139,17 +139,16 @@ function handleBlur() {
139139

140140
<!-- Add package input -->
141141
<div v-if="packages.length < maxPackages" class="relative">
142-
<div class="relative group">
142+
<div class="relative group flex items-center">
143143
<label for="package-search" class="sr-only">
144144
{{ $t('compare.selector.search_label') }}
145145
</label>
146146
<span
147-
class="absolute inset-y-0 start-3 flex items-center text-fg-subtle pointer-events-none group-focus-within:text-accent"
148-
aria-hidden="true"
147+
class="absolute inset-is-3 text-fg-subtle font-mono text-md pointer-events-none transition-colors duration-200 motion-reduce:transition-none [.group:hover:not(:focus-within)_&]:text-fg/80 group-focus-within:text-accent z-1"
149148
>
150-
<span class="i-carbon:search w-4 h-4" />
149+
/
151150
</span>
152-
<input
151+
<InputBase
153152
id="package-search"
154153
v-model="inputValue"
155154
type="text"
@@ -158,7 +157,9 @@ function handleBlur() {
158157
? $t('compare.selector.search_first')
159158
: $t('compare.selector.search_add')
160159
"
161-
class="w-full bg-bg-subtle border border-border rounded-lg ps-10 pe-4 py-2.5 font-mono text-sm text-fg placeholder:text-fg-subtle motion-reduce:transition-none duration-200 focus:border-accent focus-visible:(outline-2 outline-accent/70)"
160+
no-correct
161+
size="medium"
162+
class="w-full min-w-25 ps-7"
162163
aria-autocomplete="list"
163164
@focus="isInputFocused = true"
164165
@blur="handleBlur"

app/components/Compare/ReplacementSuggestion.vue

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@ const emit = defineEmits<{
1616
1717
const docUrl = computed(() => {
1818
if (props.replacement.type !== 'documented' || !props.replacement.docPath) return null
19-
// TODO(serhalp): Once the e18e docs site is complete, link there instead
20-
return `https://github.com/es-tooling/module-replacements/blob/main/docs/modules/${props.replacement.docPath}.md`
19+
return `https://e18e.dev/docs/replacements/${props.replacement.docPath}.html`
2120
})
2221
</script>
2322

0 commit comments

Comments
 (0)