Skip to content

Commit 205c937

Browse files
committed
refactor: replace links with component
1 parent c84a978 commit 205c937

8 files changed

Lines changed: 64 additions & 124 deletions

File tree

app/app.vue

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,9 @@ if (import.meta.client) {
117117
<template>
118118
<div class="min-h-screen flex flex-col bg-bg text-fg">
119119
<NuxtPwaAssets />
120-
<a href="#main-content" class="skip-link font-mono">{{ $t('common.skip_link') }}</a>
120+
<LinkBase href="#main-content" variant="button-primary" class="skip-link">{{
121+
$t('common.skip_link')
122+
}}</LinkBase>
121123

122124
<AppHeader :show-logo="!isHomepage" />
123125

@@ -136,19 +138,9 @@ if (import.meta.client) {
136138
.skip-link {
137139
position: fixed;
138140
top: -100%;
139-
inset-inline-start: 0;
140-
padding: 0.5rem 1rem;
141-
background: var(--fg);
142-
color: var(--bg);
143-
font-size: 0.875rem;
144141
z-index: 100;
145-
transition: top 0.2s ease;
146142
}
147143
148-
.skip-link:hover {
149-
color: var(--bg);
150-
text-decoration: underline;
151-
}
152144
.skip-link:focus {
153145
top: 0;
154146
}

app/components/AppFooter.vue

Lines changed: 10 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -15,51 +15,25 @@ const isHome = computed(() => route.name === 'index')
1515
</div>
1616
<!-- Desktop: Show all links. Mobile: Links are in MobileMenu -->
1717
<div class="hidden sm:flex items-center gap-6">
18-
<NuxtLink to="/about" class="link-subtle font-mono text-xs flex items-center">
18+
<LinkBase to="/about">
1919
{{ $t('footer.about') }}
20-
</NuxtLink>
21-
<NuxtLink
22-
to="/privacy"
23-
class="link-subtle font-mono text-xs min-h-11 flex items-center gap-1 lowercase"
24-
>
25-
{{ $t('privacy_policy.title') }}
26-
</NuxtLink>
27-
<a
28-
href="https://docs.npmx.dev"
29-
target="_blank"
30-
rel="noopener noreferrer"
31-
class="link-subtle font-mono text-xs flex items-center gap-1"
32-
>
20+
</LinkBase>
21+
<LinkBase href="https://docs.npmx.dev" target="_blank" rel="noopener noreferrer">
3322
{{ $t('footer.docs') }}
3423
<span class="i-carbon:launch rtl-flip w-3 h-3" aria-hidden="true" />
35-
</a>
36-
<a
37-
href="https://repo.npmx.dev"
38-
target="_blank"
39-
rel="noopener noreferrer"
40-
class="link-subtle font-mono text-xs flex items-center gap-1"
41-
>
24+
</LinkBase>
25+
<LinkBase href="https://repo.npmx.dev" target="_blank" rel="noopener noreferrer">
4226
{{ $t('footer.source') }}
4327
<span class="i-carbon:launch rtl-flip w-3 h-3" aria-hidden="true" />
44-
</a>
45-
<a
46-
href="https://social.npmx.dev"
47-
target="_blank"
48-
rel="noopener noreferrer"
49-
class="link-subtle font-mono text-xs flex items-center gap-1"
50-
>
28+
</LinkBase>
29+
<LinkBase href="https://social.npmx.dev" target="_blank" rel="noopener noreferrer">
5130
{{ $t('footer.social') }}
5231
<span class="i-carbon:launch rtl-flip w-3 h-3" aria-hidden="true" />
53-
</a>
54-
<a
55-
href="https://chat.npmx.dev"
56-
target="_blank"
57-
rel="noopener noreferrer"
58-
class="link-subtle font-mono text-xs flex items-center gap-1"
59-
>
32+
</LinkBase>
33+
<LinkBase href="https://chat.npmx.dev" target="_blank" rel="noopener noreferrer">
6034
{{ $t('footer.chat') }}
6135
<span class="i-carbon:launch rtl-flip w-3 h-3" aria-hidden="true" />
62-
</a>
36+
</LinkBase>
6337
</div>
6438
</div>
6539
<p class="text-xs text-fg-muted text-center sm:text-start m-0">

app/components/AppHeader.vue

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -151,49 +151,52 @@ onKeyStroke(
151151
<!-- End: Desktop nav items + Mobile menu button -->
152152
<div class="flex-shrink-0 flex items-center gap-0.5 sm:gap-2">
153153
<!-- Desktop: Compare link -->
154-
<NuxtLink
154+
<LinkBase
155+
variant="button-secondary"
155156
to="/compare"
156-
class="hidden sm:inline-flex link-subtle font-mono text-sm items-center gap-2 px-2 py-1.5 hover:bg-bg-subtle focus-visible:outline-accent/70 rounded"
157+
class="inline-flex items-center gap-2"
157158
aria-keyshortcuts="c"
158159
>
159160
{{ $t('nav.compare') }}
160161
<kbd
161-
class="inline-flex items-center justify-center w-5 h-5 text-xs bg-bg-muted border border-border rounded"
162+
class="inline-flex items-center justify-center w-5 h-5 text-xs bg-bg-muted border border-border rounded no-underline"
162163
aria-hidden="true"
163164
>
164165
c
165166
</kbd>
166-
</NuxtLink>
167+
</LinkBase>
167168

168169
<!-- Desktop: Settings link -->
169-
<NuxtLink
170+
<LinkBase
171+
variant="button-secondary"
170172
to="/settings"
171-
class="hidden sm:inline-flex link-subtle font-mono text-sm items-center gap-2 px-2 py-1.5 hover:bg-bg-subtle focus-visible:outline-accent/70 rounded"
173+
class="inline-flex items-center gap-2"
172174
aria-keyshortcuts=","
173175
>
174176
{{ $t('nav.settings') }}
175177
<kbd
176-
class="inline-flex items-center justify-center w-5 h-5 text-xs bg-bg-muted border border-border rounded"
178+
class="inline-flex items-center justify-center w-5 h-5 text-xs bg-bg-muted border border-border rounded no-underline"
177179
aria-hidden="true"
178180
>
179181
,
180182
</kbd>
181-
</NuxtLink>
183+
</LinkBase>
182184

183185
<!-- Desktop: Account menu -->
184186
<div class="hidden sm:block">
185187
<HeaderAccountMenu />
186188
</div>
187189

188190
<!-- Mobile: Menu button (always visible, click to open menu) -->
189-
<button
191+
<ButtonBase
190192
type="button"
191-
class="sm:hidden flex items-center p-2 -m-2 text-fg-subtle hover:text-fg transition-colors duration-200 focus-visible:outline-accent/70 rounded"
193+
class="sm:hidden flex"
192194
:aria-label="$t('nav.open_menu')"
193-
@click="showMobileMenu = true"
195+
:aria-expanded="showMobileMenu"
196+
@click="showMobileMenu = !showMobileMenu"
194197
>
195198
<span class="w-6 h-6 inline-block i-carbon:menu" aria-hidden="true" />
196-
</button>
199+
</ButtonBase>
197200
</div>
198201
</nav>
199202

app/components/Header/AccountMenu.client.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ function openAuthModal() {
5757

5858
<template>
5959
<div ref="accountMenuRef" class="relative flex min-w-24 justify-end">
60-
<button
60+
<ButtonBase
6161
type="button"
6262
class="relative flex items-center gap-2 px-2 py-1.5 rounded-md transition-colors duration-200 hover:bg-bg-subtle hover:text-accent focus-visible:outline-accent/70"
6363
:aria-expanded="isOpen"
@@ -126,7 +126,7 @@ function openAuthModal() {
126126
>
127127
{{ operationCount }}
128128
</span>
129-
</button>
129+
</ButtonBase>
130130

131131
<!-- Dropdown menu -->
132132
<Transition

app/components/Link/Base.vue

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,23 @@ const props = withDefaults(
1212
* If you want a button use `TagButton` instead.
1313
* */
1414
type?: never
15-
variant?: 'button-primary' | 'button-secondary' | 'tag'
15+
variant?: 'button-primary' | 'button-secondary' | 'tag' | 'link'
1616
} &
1717
/** This makes sure the link always has either `to` or `href` */
1818
(Required<Pick<NuxtLinkProps, 'to'>> | Required<Pick<NuxtLinkProps, 'href'>>) &
1919
NuxtLinkProps
2020
>(),
21-
{ variant: 'button-secondary' },
21+
{ variant: 'link' },
2222
)
2323
</script>
2424

2525
<template>
2626
<span
2727
v-if="disabled"
28-
class="opacity-50 inline-flex gap-x-1 items-center justify-center font-mono border border-transparent rounded-md"
2928
:class="{
30-
'text-sm px-4 py-2': variant !== 'tag',
29+
'opacity-50 inline-flex gap-x-1 items-center justify-center font-mono border border-transparent rounded-md':
30+
variant !== 'link',
31+
'text-sm px-4 py-2': variant !== 'tag' && variant !== 'link',
3132
'text-xs px-2 py-0.5': variant === 'tag',
3233
'bg-bg-muted text-fg-muted': variant === 'tag',
3334
'text-bg bg-fg': variant === 'button-primary',
@@ -37,9 +38,12 @@ const props = withDefaults(
3738
/></span>
3839
<NuxtLink
3940
v-else
40-
class="cursor-pointer inline-flex gap-x-1 items-center justify-center font-mono border border-border rounded-md transition-all duration-200 aria-current:(bg-fg text-bg border-fg hover:enabled:(text-bg/50))"
4141
:class="{
42-
'text-sm px-4 py-2': variant !== 'tag',
42+
'text-fg underline-offset-4 underline decoration-fg/50 hover:(no-underline text-fg/80) transition-colors duration-200':
43+
variant === 'link',
44+
'gap-x-1 items-center justify-center font-mono border border-border rounded-md transition-all duration-200 aria-current:(bg-fg text-bg border-fg hover:enabled:(text-bg/50))':
45+
variant !== 'link',
46+
'text-sm px-4 py-2': variant !== 'tag' && variant !== 'link',
4347
'text-xs px-2 py-0.5': variant === 'tag',
4448
'bg-bg-muted text-fg-muted hover:(text-fg border-border-hover)': variant === 'tag',
4549
'text-bg bg-fg hover:(bg-fg/90)': variant === 'button-primary',

app/pages/about.vue

Lines changed: 20 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,8 @@ const { data: contributors, status: contributorsStatus } = useFetch<GitHubContri
7474
<strong class="text-fg">{{ $t('about.what_we_are.better_ux_dx') }}</strong>
7575
</template>
7676
<template #jsr>
77-
<a
78-
href="https://jsr.io/"
79-
target="_blank"
80-
rel="noopener noreferrer"
81-
class="link text-fg"
82-
>JSR</a
77+
<LinkBase href="https://jsr.io/" target="_blank" rel="noopener noreferrer"
78+
>JSR</LinkBase
8379
>
8480
</template>
8581
</i18n-t>
@@ -112,58 +108,34 @@ const { data: contributors, status: contributorsStatus } = useFetch<GitHubContri
112108
>
113109
<template #already>{{ $t('about.what_we_are_not.words.already') }}</template>
114110
<template #people>
115-
<a
116-
:href="pmLinks.npm"
117-
target="_blank"
118-
rel="noopener noreferrer"
119-
class="text-fg-muted hover:text-fg underline decoration-fg-subtle/50 hover:decoration-fg"
120-
>{{ $t('about.what_we_are_not.words.people') }}</a
121-
>
111+
<LinkBase :href="pmLinks.npm" target="_blank" rel="noopener noreferrer">{{
112+
$t('about.what_we_are_not.words.people')
113+
}}</LinkBase>
122114
</template>
123115
<template #building>
124-
<a
125-
:href="pmLinks.pnpm"
126-
target="_blank"
127-
rel="noopener noreferrer"
128-
class="text-fg-muted hover:text-fg underline decoration-fg-subtle/50 hover:decoration-fg"
129-
>{{ $t('about.what_we_are_not.words.building') }}</a
130-
>
116+
<LinkBase :href="pmLinks.pnpm" target="_blank" rel="noopener noreferrer">{{
117+
$t('about.what_we_are_not.words.building')
118+
}}</LinkBase>
131119
</template>
132120
<template #really>
133-
<a
134-
:href="pmLinks.yarn"
135-
target="_blank"
136-
rel="noopener noreferrer"
137-
class="text-fg-muted hover:text-fg underline decoration-fg-subtle/50 hover:decoration-fg"
138-
>{{ $t('about.what_we_are_not.words.really') }}</a
139-
>
121+
<LinkBase :href="pmLinks.yarn" target="_blank" rel="noopener noreferrer">{{
122+
$t('about.what_we_are_not.words.really')
123+
}}</LinkBase>
140124
</template>
141125
<template #cool>
142-
<a
143-
:href="pmLinks.bun"
144-
target="_blank"
145-
rel="noopener noreferrer"
146-
class="text-fg-muted hover:text-fg underline decoration-fg-subtle/50 hover:decoration-fg"
147-
>{{ $t('about.what_we_are_not.words.cool') }}</a
148-
>
126+
<LinkBase :href="pmLinks.bun" target="_blank" rel="noopener noreferrer">{{
127+
$t('about.what_we_are_not.words.cool')
128+
}}</LinkBase>
149129
</template>
150130
<template #package>
151-
<a
152-
:href="pmLinks.deno"
153-
target="_blank"
154-
rel="noopener noreferrer"
155-
class="text-fg-muted hover:text-fg underline decoration-fg-subtle/50 hover:decoration-fg"
156-
>{{ $t('about.what_we_are_not.words.package') }}</a
157-
>
131+
<LinkBase :href="pmLinks.deno" target="_blank" rel="noopener noreferrer">{{
132+
$t('about.what_we_are_not.words.package')
133+
}}</LinkBase>
158134
</template>
159135
<template #managers>
160-
<a
161-
:href="pmLinks.vlt"
162-
target="_blank"
163-
rel="noopener noreferrer"
164-
class="text-fg-muted hover:text-fg underline decoration-fg-subtle/50 hover:decoration-fg"
165-
>{{ $t('about.what_we_are_not.words.managers') }}</a
166-
>
136+
<LinkBase :href="pmLinks.vlt" target="_blank" rel="noopener noreferrer">{{
137+
$t('about.what_we_are_not.words.managers')
138+
}}</LinkBase>
167139
</template>
168140
</i18n-t>
169141
</span>

app/pages/index.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ defineOgImageComponent('Default', {
9898

9999
<ButtonBase type="submit" variant="primary" class="absolute inset-ie-2">
100100
<span class="i-carbon:search align-middle w-4 h-4" aria-hidden="true"></span>
101-
101+
102102
<span class="sr-only sm:not-sr-only">
103103
{{ $t('search.button') }}
104104
</span>
@@ -118,15 +118,15 @@ defineOgImageComponent('Default', {
118118
>
119119
<ul class="flex flex-wrap items-center justify-center gap-x-6 gap-y-3 list-none m-0 p-0">
120120
<li v-for="framework in SHOWCASED_FRAMEWORKS" :key="framework.name">
121-
<NuxtLink
121+
<LinkBase
122122
:to="{ name: 'package', params: { package: [framework.package] } }"
123-
class="link-subtle font-mono text-sm inline-flex items-center gap-2 group"
123+
class="inline-flex items-center gap-2 group"
124124
>
125125
<span
126126
class="w-1 h-1 rounded-full bg-accent group-hover:bg-fg transition-colors duration-200"
127127
/>
128128
{{ framework.name }}
129-
</NuxtLink>
129+
</LinkBase>
130130
</li>
131131
</ul>
132132
</nav>

uno.config.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -126,11 +126,6 @@ export default defineConfig({
126126
// Focus states - subtle but accessible
127127
['focus-ring', 'outline-none focus-visible:(ring-2 ring-fg/50 ring-offset-2)'],
128128

129-
// Links
130-
[
131-
'link',
132-
'text-fg underline-offset-4 decoration-border hover:(decoration-fg underline) transition-colors duration-200 focus-ring',
133-
],
134129
['link-subtle', 'text-fg-muted hover:text-fg transition-colors duration-200 focus-ring'],
135130

136131
// badges

0 commit comments

Comments
 (0)