Skip to content

Commit b43cc0a

Browse files
refactor: create a backbutton component (#2024)
Co-authored-by: Willow (GHOST) <git@willow.sh>
1 parent 2501b3c commit b43cc0a

File tree

10 files changed

+46
-83
lines changed

10 files changed

+46
-83
lines changed

app/components/BackButton.vue

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<script setup lang="ts">
2+
const router = useRouter()
3+
const canGoBack = useCanGoBack()
4+
</script>
5+
6+
<template>
7+
<button
8+
v-if="canGoBack"
9+
type="button"
10+
class="inline-flex items-center gap-2 p-1.5 -mx-1.5 font-mono text-sm text-fg-muted hover:text-fg transition-colors duration-200 rounded focus-visible:outline-accent/70 shrink-0"
11+
@click="router.back()"
12+
>
13+
<span class="i-lucide:arrow-left rtl-flip w-4 h-4" aria-hidden="true" />
14+
<span class="sr-only sm:not-sr-only">{{ $t('nav.back') }}</span>
15+
</button>
16+
</template>

app/pages/about.vue

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@ import type { Role } from '#server/api/contributors.get'
33
import { SPONSORS } from '~/assets/logos/sponsors'
44
import { OSS_PARTNERS } from '~/assets/logos/oss-partners'
55
6-
const router = useRouter()
7-
const canGoBack = useCanGoBack()
8-
96
useSeoMeta({
107
title: () => `${$t('about.title')} - npmx`,
118
ogTitle: () => `${$t('about.title')} - npmx`,
@@ -57,15 +54,7 @@ const roleLabels = computed(
5754
<h1 class="font-mono text-3xl sm:text-4xl font-medium">
5855
{{ $t('about.heading') }}
5956
</h1>
60-
<button
61-
type="button"
62-
class="cursor-pointer inline-flex items-center gap-2 p-1.5 -mx-1.5 font-mono text-sm text-fg-muted hover:text-fg transition-colors duration-200 rounded focus-visible:outline-accent/70 shrink-0"
63-
@click="router.back()"
64-
v-if="canGoBack"
65-
>
66-
<span class="i-lucide:arrow-left rtl-flip w-4 h-4" aria-hidden="true" />
67-
<span class="hidden sm:inline">{{ $t('nav.back') }}</span>
68-
</button>
57+
<BackButton />
6958
</div>
7059
<p class="text-fg-muted text-lg">
7160
{{ $t('tagline') }}

app/pages/accessibility.vue

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@ defineOgImageComponent('Default', {
1212
title: () => $t('a11y.title'),
1313
description: () => $t('a11y.welcome', { app: 'npmx' }),
1414
})
15-
16-
const router = useRouter()
17-
const canGoBack = useCanGoBack()
1815
</script>
1916

2017
<template>
@@ -25,15 +22,7 @@ const canGoBack = useCanGoBack()
2522
<h1 class="font-mono text-3xl sm:text-4xl font-medium">
2623
{{ $t('a11y.title') }}
2724
</h1>
28-
<button
29-
type="button"
30-
class="cursor-pointer inline-flex items-center gap-2 p-1.5 -mx-1.5 font-mono text-sm text-fg-muted hover:text-fg transition-colors duration-200 rounded shrink-0"
31-
@click="router.back()"
32-
v-if="canGoBack"
33-
>
34-
<span class="i-lucide:arrow-left rtl-flip w-4 h-4" aria-hidden="true" />
35-
<span class="sr-only sm:not-sr-only">{{ $t('nav.back') }}</span>
36-
</button>
25+
<BackButton />
3726
</div>
3827
</header>
3928

app/pages/compare.vue

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ definePageMeta({
99
})
1010
1111
const { locale } = useI18n()
12-
const router = useRouter()
13-
const canGoBack = useCanGoBack()
1412
const { copied, copy } = useClipboard({ copiedDuring: 2000 })
1513
const maxPackages = 4
1614
@@ -173,15 +171,7 @@ useSeoMeta({
173171
<h1 class="font-mono text-3xl sm:text-4xl font-medium">
174172
{{ $t('compare.packages.title') }}
175173
</h1>
176-
<button
177-
type="button"
178-
class="cursor-pointer inline-flex items-center gap-2 p-1.5 -mx-1.5 font-mono text-sm text-fg-muted hover:text-fg transition-colors duration-200 rounded focus-visible:outline-accent/70 shrink-0"
179-
@click="router.back()"
180-
v-if="canGoBack"
181-
>
182-
<span class="i-lucide:arrow-left rtl-flip w-4 h-4" aria-hidden="true" />
183-
<span class="hidden sm:inline">{{ $t('nav.back') }}</span>
184-
</button>
174+
<BackButton />
185175
</div>
186176
<p class="text-fg-muted text-lg">
187177
{{ $t('compare.packages.tagline') }}

app/pages/pds.vue

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
<script setup lang="ts">
22
import type { AtprotoProfile } from '#shared/types/atproto'
33
4-
const router = useRouter()
5-
const canGoBack = useCanGoBack()
6-
74
useSeoMeta({
85
title: () => `${$t('pds.title')} - npmx`,
96
ogTitle: () => `${$t('pds.title')} - npmx`,
@@ -51,15 +48,7 @@ const totalAccounts = computed(() => pdsUsers.value.length)
5148
<h1 class="font-mono text-3xl sm:text-4xl font-medium">
5249
{{ $t('pds.title') }}
5350
</h1>
54-
<button
55-
type="button"
56-
class="cursor-pointer inline-flex items-center gap-2 p-1.5 -mx-1.5 font-mono text-sm text-fg-muted hover:text-fg transition-colors duration-200 rounded focus-visible:outline-accent/70 shrink-0"
57-
@click="router.back()"
58-
v-if="canGoBack"
59-
>
60-
<span class="i-lucide:arrow-left rtl-flip w-4 h-4" aria-hidden="true" />
61-
<span class="hidden sm:inline">{{ $t('nav.back') }}</span>
62-
</button>
51+
<BackButton />
6352
</div>
6453
<p class="text-fg-muted text-lg">
6554
{{ $t('pds.meta_description') }}

app/pages/privacy.vue

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ defineOgImageComponent('Default', {
1313
description: () => $t('privacy_policy.welcome', { app: 'npmx' }),
1414
})
1515
16-
const router = useRouter()
17-
const canGoBack = useCanGoBack()
1816
const buildInfo = useAppConfig().buildInfo
1917
const { locale } = useI18n()
2018
</script>
@@ -27,15 +25,7 @@ const { locale } = useI18n()
2725
<h1 class="font-mono text-3xl sm:text-4xl font-medium">
2826
{{ $t('privacy_policy.title') }}
2927
</h1>
30-
<button
31-
type="button"
32-
class="cursor-pointer inline-flex items-center gap-2 p-1.5 -mx-1.5 font-mono text-sm text-fg-muted hover:text-fg transition-colors duration-200 rounded focus-visible:outline-accent/70 shrink-0"
33-
@click="router.back()"
34-
v-if="canGoBack"
35-
>
36-
<span class="i-lucide:arrow-left rtl-flip w-4 h-4" aria-hidden="true" />
37-
<span class="sr-only sm:not-sr-only">{{ $t('nav.back') }}</span>
38-
</button>
28+
<BackButton />
3929
</div>
4030
<i18n-t
4131
keypath="privacy_policy.last_updated"

app/pages/recharging.vue

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@ defineOgImageComponent('Default', {
1717
description: () => $t('vacations.meta_description'),
1818
})
1919
20-
const router = useRouter()
21-
const canGoBack = useCanGoBack()
22-
2320
const { data: stats } = useFetch('/api/repo-stats')
2421
2522
/**
@@ -74,15 +71,7 @@ const icons = [
7471
<h1 class="font-mono text-3xl sm:text-4xl font-medium">
7572
{{ $t('vacations.heading') }}
7673
</h1>
77-
<button
78-
type="button"
79-
class="cursor-pointer inline-flex items-center gap-2 p-1.5 -mx-1.5 font-mono text-sm text-fg-muted hover:text-fg transition-colors duration-200 rounded focus-visible:outline-accent/70 shrink-0"
80-
@click="router.back()"
81-
v-if="canGoBack"
82-
>
83-
<span class="i-lucide:arrow-left rtl-flip w-4 h-4" aria-hidden="true" />
84-
<span class="sr-only sm:not-sr-only">{{ $t('nav.back') }}</span>
85-
</button>
74+
<BackButton />
8675
</div>
8776
<i18n-t
8877
keypath="vacations.subtitle"

app/pages/settings.vue

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
<script setup lang="ts">
22
const router = useRouter()
3-
const canGoBack = useCanGoBack()
43
const { settings } = useSettings()
54
const { locale: currentLocale, locales, setLocale: setNuxti18nLocale } = useI18n()
65
const colorMode = useColorMode()
@@ -51,15 +50,7 @@ const setLocale: typeof setNuxti18nLocale = newLocale => {
5150
<h1 class="font-mono text-3xl sm:text-4xl font-medium">
5251
{{ $t('settings.title') }}
5352
</h1>
54-
<button
55-
type="button"
56-
class="cursor-pointer inline-flex items-center gap-2 p-1.5 -mx-1.5 font-mono text-sm text-fg-muted hover:text-fg transition-colors duration-200 rounded focus-visible:outline-accent/70 shrink-0"
57-
@click="router.back()"
58-
v-if="canGoBack"
59-
>
60-
<span class="i-lucide:arrow-left rtl-flip w-4 h-4" aria-hidden="true" />
61-
<span class="sr-only sm:not-sr-only">{{ $t('nav.back') }}</span>
62-
</button>
53+
<BackButton />
6354
</div>
6455
<p class="text-fg-muted text-lg">
6556
{{ $t('settings.tagline') }}

test/nuxt/a11y.spec.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,12 @@ vi.mock('vue-data-ui/vue-ui-xy', () => {
113113
}
114114
})
115115

116+
vi.mock('~/composables/useCanGoBack', () => {
117+
return {
118+
useCanGoBack: () => shallowRef(true),
119+
}
120+
})
121+
116122
// Import components from #components where possible
117123
// For server/client variants, we need to import directly to test the specific variant
118124
import {
@@ -125,6 +131,7 @@ import {
125131
AboutLogoList,
126132
AuthorAvatar,
127133
AuthorList,
134+
BackButton,
128135
BlogPostFederatedArticles,
129136
BlogPostListCard,
130137
BlogPostWrapper,
@@ -457,6 +464,15 @@ describe('component accessibility audits', () => {
457464
})
458465
})
459466

467+
describe('BackButton', () => {
468+
it('should have no accessibility violations', async () => {
469+
const component = await mountSuspended(BackButton)
470+
expect(component.find('button').exists()).toBe(true)
471+
const results = await runAxe(component)
472+
expect(results.violations).toEqual([])
473+
})
474+
})
475+
460476
describe('TagStatic', () => {
461477
it('should have no accessibility violations', async () => {
462478
const component = await mountSuspended(TagStatic, {

test/unit/a11y-component-coverage.spec.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ const SKIPPED_COMPONENTS: Record<string, string> = {
5050
'Translation/StatusByFile.unused.vue': 'Unused component, might be needed in the future',
5151
}
5252

53+
function normalizeComponentPath(filePath: string): string {
54+
return filePath.replaceAll('\\', '/')
55+
}
56+
5357
/**
5458
* Recursively get all Vue component files in a directory.
5559
*/
@@ -63,7 +67,7 @@ function getVueFiles(dir: string, baseDir: string = dir): string[] {
6367
files.push(...getVueFiles(fullPath, baseDir))
6468
} else if (entry.isFile() && entry.name.endsWith('.vue')) {
6569
// Get relative path from base components directory
66-
files.push(path.relative(baseDir, fullPath))
70+
files.push(normalizeComponentPath(path.relative(baseDir, fullPath)))
6771
}
6872
}
6973

@@ -88,7 +92,7 @@ function parseComponentsDeclaration(dtsPath: string): Map<string, string[]> {
8892
let match
8993
while ((match = exportRegex.exec(content)) !== null) {
9094
const componentName = match[1]!
91-
const filePath = match[2]!
95+
const filePath = normalizeComponentPath(match[2]!)
9296

9397
const existing = componentMap.get(componentName) || []
9498
if (!existing.includes(filePath)) {
@@ -117,7 +121,7 @@ function getTestedComponents(
117121
let match
118122

119123
while ((match = directImportRegex.exec(testFileContent)) !== null) {
120-
tested.add(match[1]!)
124+
tested.add(normalizeComponentPath(match[1]!))
121125
}
122126

123127
// Match #components imports like:

0 commit comments

Comments
 (0)