Skip to content

Commit ab9b15e

Browse files
committed
feat: use package header on all package pages
1 parent dd49a5a commit ab9b15e

9 files changed

Lines changed: 100 additions & 162 deletions

File tree

app/components/Package/Header.vue

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@ import { togglePackageLike } from '~/utils/atproto/likes'
88
import { isEditableElement } from '~/utils/input'
99
1010
const props = defineProps<{
11-
pkg: Pick<SlimPackument, 'name' | 'versions' | 'dist-tags'> | null
11+
pkg?: Pick<SlimPackument, 'name' | 'versions' | 'dist-tags'> | null
1212
resolvedVersion?: string | null
13-
displayVersion: PackumentVersion | null
14-
latestVersion: SlimVersion | null
15-
provenanceData: ProvenanceDetails | null
16-
provenanceStatus: string
13+
displayVersion?: PackumentVersion | null
14+
latestVersion?: SlimVersion | null
15+
provenanceData?: ProvenanceDetails | null
16+
provenanceStatus?: string | null
1717
page: 'main' | 'docs' | 'code' | 'diff'
18+
versionUrlPattern: string
1819
}>()
1920
2021
const { requestedVersion, orgName } = usePackageRoute()
@@ -126,11 +127,6 @@ const diffLink = computed((): RouteLocationRaw | null => {
126127
return diffRoute(props.pkg.name, props.resolvedVersion, props.latestVersion.version)
127128
})
128129
129-
// URL pattern for version selector - includes file path if present
130-
const versionUrlPattern = computed(
131-
() => `/package/${props.pkg?.name || packageName.value}/v/{version}`,
132-
)
133-
134130
const keyboardShortcuts = useKeyboardShortcuts()
135131
136132
onKeyStroke(
@@ -341,7 +337,7 @@ const likeAction = async () => {
341337
:tabindex="showScrollToTop ? 0 : -1"
342338
/>
343339
<div class="flex-inline items-center flex-nowrap gap-1 font-mono text-fg-muted">
344-
<template v-if="hasProvenance(displayVersion)">
340+
<template v-if="displayVersion && hasProvenance(displayVersion)">
345341
<TooltipApp
346342
:text="
347343
provenanceData && provenanceStatus !== 'pending'

app/components/VersionSelector.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ const versionToTags = computed(() => buildVersionToTagsMap(props.distTags))
7474
7575
/** Get URL for a specific version */
7676
function getVersionUrl(version: string): string {
77+
console.log('props.urlPattern', props.urlPattern)
7778
return props.urlPattern.replace('{version}', version)
7879
}
7980

app/pages/diff/[[org]]/[packageName]/v/[versionRange].vue

Lines changed: 40 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script setup lang="ts">
22
import type { CompareResponse, FileChange } from '#shared/types'
3+
import type { RouteLocationRaw } from 'vue-router'
34
import { diffRoute, packageRoute } from '~/utils/router'
45
56
definePageMeta({
@@ -90,12 +91,24 @@ const groupedDeps = computed(() => {
9091
}
9192
return groups
9293
})
94+
// Keep latestVersion for comparison (to show "(latest)" badge)
95+
const latestVersionDetailed = computed(() => {
96+
if (!pkg.value) return null
97+
const latestTag = pkg.value['dist-tags']?.latest
98+
if (!latestTag) return null
99+
return pkg.value.versions[latestTag] ?? null
100+
})
101+
102+
const normalizeRoutePath = (route: RouteLocationRaw) => {
103+
const resolvedHref = router.resolve(route).href
104+
return resolvedHref.replace(/%7B/g, '{').replace(/%7D/g, '}')
105+
}
93106
94107
const fromVersionUrlPattern = computed(() => {
95-
return router.resolve(diffRoute(packageName.value, '{version}', toVersion.value)).href
108+
return normalizeRoutePath(diffRoute(packageName.value, '{version}', toVersion.value))
96109
})
97110
const toVersionUrlPattern = computed(() => {
98-
return router.resolve(diffRoute(packageName.value, fromVersion.value, '{version}')).href
111+
return normalizeRoutePath(diffRoute(packageName.value, fromVersion.value, '{version}'))
99112
})
100113
101114
useSeoMeta({
@@ -112,53 +125,17 @@ useSeoMeta({
112125

113126
<template>
114127
<main class="flex-1 flex flex-col min-h-0">
115-
<!-- Header -->
116-
<header class="border-b border-border bg-bg sticky top-14 z-20">
117-
<div class="container py-4">
118-
<!-- Package info -->
119-
<div class="flex items-center gap-2 mb-3 flex-wrap min-w-0">
120-
<NuxtLink
121-
:to="packageRoute(packageName)"
122-
class="font-mono text-lg font-medium hover:text-fg transition-colors min-w-0 truncate"
123-
>
124-
{{ packageName }}
125-
</NuxtLink>
126-
<span class="text-fg-subtle">/</span>
127-
<span class="font-mono text-sm text-fg-muted">compare</span>
128-
</div>
129-
130-
<!-- Version selectors -->
131-
<div class="flex items-center gap-3 flex-wrap">
132-
<div class="flex items-center gap-2">
133-
<span class="text-xs text-fg-subtle uppercase tracking-wide">From</span>
134-
<VersionSelector
135-
v-if="pkg?.versions && pkg?.['dist-tags']"
136-
:package-name="packageName"
137-
:current-version="fromVersion"
138-
:versions="pkg.versions"
139-
:dist-tags="pkg['dist-tags']"
140-
:url-pattern="fromVersionUrlPattern"
141-
/>
142-
<span v-else class="font-mono text-sm text-fg-muted">{{ fromVersion }}</span>
143-
</div>
144-
145-
<span class="i-lucide:arrow-right w-4 h-4 text-fg-subtle" />
146-
147-
<div class="flex items-center gap-2">
148-
<span class="text-xs text-fg-subtle uppercase tracking-wide">To</span>
149-
<VersionSelector
150-
v-if="pkg?.versions && pkg?.['dist-tags']"
151-
:package-name="packageName"
152-
:current-version="toVersion"
153-
:versions="pkg.versions"
154-
:dist-tags="pkg['dist-tags']"
155-
:url-pattern="toVersionUrlPattern"
156-
/>
157-
<span v-else class="font-mono text-sm text-fg-muted">{{ toVersion }}</span>
158-
</div>
159-
</div>
160-
</div>
161-
</header>
128+
<div class="w-full container -mb-px">
129+
<PackageHeader
130+
:pkg="pkg"
131+
:resolved-version="fromVersion"
132+
:display-version="pkg?.requestedVersion"
133+
:latest-version="latestVersionDetailed"
134+
:version-url-pattern="fromVersionUrlPattern"
135+
page="diff"
136+
/>
137+
</div>
138+
<span class="block h-px w-full bg-border" />
162139

163140
<!-- Error: invalid route -->
164141
<div v-if="!versionRange" class="container py-20 text-center">
@@ -186,6 +163,20 @@ useSeoMeta({
186163
<aside
187164
class="hidden md:flex w-80 border-ie border-border bg-bg-subtle flex-col shrink-0 min-h-0"
188165
>
166+
<div class="px-3 py-2 border-b border-border">
167+
<p class="text-xs font-medium text-fg mb-1 flex items-center gap-1.5">
168+
<span class="block i-lucide-git-compare-arrows w-3.5 h-3.5" />
169+
{{ $t('compare.version_selector_title') }}
170+
</p>
171+
<VersionSelector
172+
v-if="pkg?.versions && pkg?.['dist-tags']"
173+
:package-name="packageName"
174+
:current-version="toVersion"
175+
:versions="pkg.versions"
176+
:dist-tags="pkg['dist-tags']"
177+
:url-pattern="toVersionUrlPattern"
178+
/>
179+
</div>
189180
<DiffSidebarPanel
190181
:compare="compare"
191182
:grouped-deps="groupedDeps"

app/pages/package-code/[[org]]/[packageName]/v/[version]/[...filePath].vue

Lines changed: 19 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,14 @@ const markdownViewMode = shallowRef<(typeof markdownViewModes)[number]['key']>('
293293
294294
const bytesFormatter = useBytesFormatter()
295295
296+
// Keep latestVersion for comparison (to show "(latest)" badge)
297+
const latestVersion = computed(() => {
298+
if (!pkg.value) return null
299+
const latestTag = pkg.value['dist-tags']?.latest
300+
if (!latestTag) return null
301+
return pkg.value.versions[latestTag] ?? null
302+
})
303+
296304
useHead({
297305
link: [{ rel: 'canonical', href: canonicalUrl }],
298306
})
@@ -330,69 +338,17 @@ defineOgImageComponent('Default', {
330338

331339
<template>
332340
<main class="flex-1 flex flex-col">
333-
<!-- Header -->
334-
<header class="border-b border-border bg-bg sticky top-14 z-20">
335-
<div class="container py-4">
336-
<!-- Package info and navigation -->
337-
<div class="flex items-center gap-2 mb-3 flex-wrap min-w-0">
338-
<NuxtLink
339-
:to="packageRoute(packageName, version)"
340-
class="font-mono text-lg font-medium hover:text-fg transition-colors min-w-0 truncate max-w-[60vw] sm:max-w-none"
341-
:title="packageName"
342-
>
343-
<span v-if="orgName" class="text-fg-muted">@{{ orgName }}/</span
344-
>{{ orgName ? packageName.replace(`@${orgName}/`, '') : packageName }}
345-
</NuxtLink>
346-
<!-- Version selector -->
347-
<VersionSelector
348-
v-if="version && pkg?.versions && pkg?.['dist-tags']"
349-
:package-name="packageName"
350-
:current-version="version"
351-
:versions="pkg.versions"
352-
:dist-tags="pkg['dist-tags']"
353-
:url-pattern="versionUrlPattern"
354-
/>
355-
<span
356-
v-else-if="version"
357-
class="px-2 py-0.5 font-mono text-sm bg-bg-muted border border-border rounded truncate max-w-32 sm:max-w-48"
358-
:title="`v${version}`"
359-
>
360-
v{{ version }}
361-
</span>
362-
<span class="text-fg-subtle shrink-0">/</span>
363-
<span class="font-mono text-sm text-fg-muted shrink-0">{{
364-
$t('package.links.code')
365-
}}</span>
366-
</div>
367-
368-
<!-- Breadcrumb navigation -->
369-
<nav
370-
:aria-label="$t('code.file_path')"
371-
class="flex items-center gap-1 font-mono text-sm overflow-x-auto"
372-
dir="ltr"
373-
>
374-
<NuxtLink
375-
v-if="filePath"
376-
:to="getCurrentCodeUrlWithPath()"
377-
class="text-fg-muted hover:text-fg transition-colors shrink-0"
378-
>
379-
{{ $t('code.root') }}
380-
</NuxtLink>
381-
<span v-else class="text-fg shrink-0">{{ $t('code.root') }}</span>
382-
<template v-for="(crumb, i) in breadcrumbs" :key="crumb.path">
383-
<span class="text-fg-subtle">/</span>
384-
<NuxtLink
385-
v-if="i < breadcrumbs.length - 1"
386-
:to="getCurrentCodeUrlWithPath(crumb.path)"
387-
class="text-fg-muted hover:text-fg transition-colors"
388-
>
389-
{{ crumb.name }}
390-
</NuxtLink>
391-
<span v-else class="text-fg">{{ crumb.name }}</span>
392-
</template>
393-
</nav>
394-
</div>
395-
</header>
341+
<div class="w-full container -mb-px">
342+
<PackageHeader
343+
:pkg="pkg"
344+
:resolved-version="version"
345+
:display-version="pkg?.requestedVersion"
346+
:latest-version="latestVersion"
347+
:version-url-pattern="versionUrlPattern"
348+
page="code"
349+
/>
350+
</div>
351+
<span class="block h-px w-full bg-border" />
396352

397353
<!-- Error: no version -->
398354
<div v-if="!version" class="container py-20 text-center">

app/pages/package-docs/[...path].vue

Lines changed: 22 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,17 @@ const { data: docsData, status: docsStatus } = useLazyFetch<DocsResponse>(
9393
}),
9494
},
9595
)
96+
// Keep latestVersion for comparison (to show "(latest)" badge)
97+
const latestVersionDetailed = computed(() => {
98+
if (!pkg.value) return null
99+
const latestTag = pkg.value['dist-tags']?.latest
100+
if (!latestTag) return null
101+
return pkg.value.versions[latestTag] ?? null
102+
})
103+
104+
const versionUrlPattern = computed(
105+
() => `/package-docs/${pkg.value?.name || packageName.value}/v/{version}`,
106+
)
96107
97108
const pageTitle = computed(() => {
98109
if (!packageName.value) return 'API Docs - npmx'
@@ -123,45 +134,17 @@ const showEmptyState = computed(() => docsData.value?.status !== 'ok')
123134

124135
<template>
125136
<div class="docs-page flex-1 flex flex-col">
126-
<!-- Visually hidden h1 for accessibility -->
127-
<h1 class="sr-only">{{ packageName }} API Documentation</h1>
128-
129-
<!-- Sticky header - positioned below AppHeader -->
130-
<header
131-
aria-label="Package documentation header"
132-
class="docs-header sticky z-10 border-b border-border"
133-
>
134-
<div class="absolute inset-0 bg-bg/90 backdrop-blur" />
135-
<div class="relative px-4 sm:px-6 lg:px-8 py-4 z-1">
136-
<div class="flex items-center justify-between gap-4">
137-
<div class="flex items-center gap-3 min-w-0">
138-
<NuxtLink
139-
v-if="packageName"
140-
:to="packageRoute(packageName)"
141-
class="font-mono text-lg sm:text-xl font-semibold text-fg hover:text-fg-muted transition-colors truncate"
142-
>
143-
{{ packageName }}
144-
</NuxtLink>
145-
<VersionSelector
146-
v-if="resolvedVersion && pkg?.versions && pkg?.['dist-tags']"
147-
:package-name="packageName"
148-
:current-version="resolvedVersion"
149-
:versions="pkg.versions"
150-
:dist-tags="pkg['dist-tags']"
151-
:url-pattern="`/package-docs/${packageName}/v/{version}`"
152-
/>
153-
<span v-else-if="resolvedVersion" class="text-fg-subtle font-mono text-sm shrink-0">
154-
{{ resolvedVersion }}
155-
</span>
156-
</div>
157-
<div class="flex items-center gap-3 shrink-0">
158-
<span class="text-xs px-2 py-1 rounded badge-green border border-badge-green/50">
159-
API Docs
160-
</span>
161-
</div>
162-
</div>
163-
</div>
164-
</header>
137+
<div class="w-full container -mb-px">
138+
<PackageHeader
139+
:pkg="pkg"
140+
:resolved-version="resolvedVersion"
141+
:display-version="pkg?.requestedVersion"
142+
:latest-version="latestVersionDetailed"
143+
:version-url-pattern="versionUrlPattern"
144+
page="docs"
145+
/>
146+
</div>
147+
<span class="block h-px w-full bg-border" />
165148

166149
<div class="flex" dir="ltr">
167150
<!-- Sidebar TOC -->

app/pages/package/[[org]]/[name].vue

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,11 @@ const canonicalUrl = computed(() => {
517517
return requestedVersion.value ? `${base}/v/${requestedVersion.value}` : base
518518
})
519519
520+
// URL pattern for version selector - includes file path if present
521+
const versionUrlPattern = computed(
522+
() => `/package/${pkg.value?.name || packageName.value}/v/{version}`,
523+
)
524+
520525
const dependencyCount = computed(() => getDependencyCount(displayVersion.value))
521526
522527
const numberFormatter = useNumberFormatter()
@@ -585,6 +590,7 @@ const showSkeleton = shallowRef(false)
585590
:provenance-status="provenanceStatus"
586591
:class="$style.areaHeader"
587592
page="main"
593+
:version-url-pattern="versionUrlPattern"
588594
/>
589595
<article id="package-article" :class="$style.packagePage">
590596
<!-- Package details -->

i18n/locales/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1167,6 +1167,7 @@
11671167
"file_size_warning": "{size} exceeds the 250KB limit for comparison",
11681168
"compare_versions": "diff",
11691169
"compare_versions_title": "Compare with latest version",
1170+
"version_selector_title": "Compare with version",
11701171
"summary": "Summary",
11711172
"deps_count": "{count} dep | {count} deps",
11721173
"dependencies": "Dependencies",

i18n/schema.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3505,6 +3505,9 @@
35053505
"compare_versions_title": {
35063506
"type": "string"
35073507
},
3508+
"version_selector_title": {
3509+
"type": "string"
3510+
},
35083511
"summary": {
35093512
"type": "string"
35103513
},

test/nuxt/a11y.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -752,6 +752,7 @@ describe('component accessibility audits', () => {
752752
provenanceData: null,
753753
provenanceStatus: 'idle',
754754
page: 'docs',
755+
versionUrlPattern: '/package/vue/v/{version}',
755756
},
756757
})
757758
const results = await runAxe(component)

0 commit comments

Comments
 (0)