Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 52 additions & 10 deletions app/components/PackageDependencies.vue
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,26 @@ const sortedOptionalDependencies = computed(() => {
<template>
<div class="space-y-8">
<!-- Dependencies -->
<section v-if="sortedDependencies.length > 0" aria-labelledby="dependencies-heading">
<h2 id="dependencies-heading" class="text-xs text-fg-subtle uppercase tracking-wider mb-3">
{{ $t('package.dependencies.title', { count: sortedDependencies.length }) }}
<section
id="dependencies"
v-if="sortedDependencies.length > 0"
aria-labelledby="dependencies-heading"
class="scroll-mt-20"
>
<h2
id="dependencies-heading"
class="group text-xs text-fg-subtle uppercase tracking-wider mb-3"
>
<a
href="#dependencies"
class="inline-flex items-center gap-1.5 text-fg-subtle hover:text-fg-muted transition-colors duration-200 no-underline"
>
{{ $t('package.dependencies.title', { count: sortedDependencies.length }) }}
<span
class="i-carbon-link w-3 h-3 block opacity-0 group-hover:opacity-100 transition-opacity duration-200"
aria-hidden="true"
/>
</a>
</h2>
<ul class="space-y-1 list-none m-0 p-0" :aria-label="$t('package.dependencies.list_label')">
<li
Expand Down Expand Up @@ -99,12 +116,26 @@ const sortedOptionalDependencies = computed(() => {
</section>

<!-- Peer Dependencies -->
<section v-if="sortedPeerDependencies.length > 0" aria-labelledby="peer-dependencies-heading">
<section
id="peer-dependencies"
v-if="sortedPeerDependencies.length > 0"
aria-labelledby="peer-dependencies-heading"
class="scroll-mt-20"
>
<h2
id="peer-dependencies-heading"
class="text-xs text-fg-subtle uppercase tracking-wider mb-3"
class="group text-xs text-fg-subtle uppercase tracking-wider mb-3"
>
{{ $t('package.peer_dependencies.title', { count: sortedPeerDependencies.length }) }}
<a
href="#peer-dependencies"
class="inline-flex items-center gap-1.5 text-fg-subtle hover:text-fg-muted transition-colors duration-200 no-underline"
>
{{ $t('package.peer_dependencies.title', { count: sortedPeerDependencies.length }) }}
<span
class="i-carbon-link w-3 h-3 block opacity-0 group-hover:opacity-100 transition-opacity duration-200"
aria-hidden="true"
/>
</a>
</h2>
<ul
class="space-y-1 list-none m-0 p-0"
Expand Down Expand Up @@ -154,16 +185,27 @@ const sortedOptionalDependencies = computed(() => {

<!-- Optional Dependencies -->
<section
id="optional-dependencies"
v-if="sortedOptionalDependencies.length > 0"
aria-labelledby="optional-dependencies-heading"
class="scroll-mt-20"
>
<h2
id="optional-dependencies-heading"
class="text-xs text-fg-subtle uppercase tracking-wider mb-3"
class="group text-xs text-fg-subtle uppercase tracking-wider mb-3"
>
{{
$t('package.optional_dependencies.title', { count: sortedOptionalDependencies.length })
}}
<a
href="#optional-dependencies"
class="inline-flex items-center gap-1.5 text-fg-subtle hover:text-fg-muted transition-colors duration-200 no-underline"
>
{{
$t('package.optional_dependencies.title', { count: sortedOptionalDependencies.length })
}}
<span
class="i-carbon-link w-3 h-3 block opacity-0 group-hover:opacity-100 transition-opacity duration-200"
aria-hidden="true"
/>
</a>
</h2>
<ul
class="space-y-1 list-none m-0 p-0"
Expand Down
20 changes: 17 additions & 3 deletions app/components/PackageMaintainers.vue
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,23 @@ watch(
</script>

<template>
<section v-if="maintainers?.length" aria-labelledby="maintainers-heading">
<h2 id="maintainers-heading" class="text-xs text-fg-subtle uppercase tracking-wider mb-3">
{{ $t('package.maintainers.title') }}
<section
id="maintainers"
v-if="maintainers?.length"
aria-labelledby="maintainers-heading"
class="scroll-mt-20"
>
<h2 id="maintainers-heading" class="group text-xs text-fg-subtle uppercase tracking-wider mb-3">
<a
href="#maintainers"
class="inline-flex items-center gap-1.5 text-fg-subtle hover:text-fg-muted transition-colors duration-200 no-underline"
>
{{ $t('package.maintainers.title') }}
<span
class="i-carbon-link w-3 h-3 block opacity-0 group-hover:opacity-100 transition-opacity duration-200"
aria-hidden="true"
/>
</a>
</h2>
<ul class="space-y-2 list-none m-0 p-0" :aria-label="$t('package.maintainers.list_label')">
<li
Expand Down
20 changes: 17 additions & 3 deletions app/components/PackageVersions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -305,9 +305,23 @@ function getTagVersions(tag: string): VersionDisplay[] {
</script>

<template>
<section v-if="allTagRows.length > 0" aria-labelledby="versions-heading" class="overflow-hidden">
<h2 id="versions-heading" class="text-xs text-fg-subtle uppercase tracking-wider mb-3">
{{ $t('package.versions.title') }}
<section
id="versions"
v-if="allTagRows.length > 0"
aria-labelledby="versions-heading"
class="overflow-hidden scroll-mt-20"
>
<h2 id="versions-heading" class="group text-xs text-fg-subtle uppercase tracking-wider mb-3">
<a
href="#versions"
class="inline-flex items-center gap-1.5 text-fg-subtle hover:text-fg-muted transition-colors duration-200 no-underline"
>
{{ $t('package.versions.title') }}
<span
class="i-carbon-link w-3 h-3 block opacity-0 group-hover:opacity-100 transition-opacity duration-200"
aria-hidden="true"
/>
</a>
</h2>

<div class="space-y-0.5 min-w-0">
Expand Down
17 changes: 13 additions & 4 deletions app/components/PackageWeeklyDownloadStats.vue
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,19 @@ const config = computed(() => {

<template>
<div class="space-y-8">
<section>
<section id="downloads" class="scroll-mt-20">
<div class="flex items-center justify-between mb-3">
<h2 class="text-xs text-fg-subtle uppercase tracking-wider">
{{ $t('package.downloads.title') }}
<h2 class="group text-xs text-fg-subtle uppercase tracking-wider">
<a
href="#downloads"
class="inline-flex items-center gap-1.5 text-fg-subtle hover:text-fg-muted transition-colors duration-200 no-underline"
>
{{ $t('package.downloads.title') }}
<span
class="i-carbon-link w-3 h-3 block opacity-0 group-hover:opacity-100 transition-opacity duration-200"
aria-hidden="true"
/>
</a>
</h2>
<button
type="button"
Expand Down Expand Up @@ -211,7 +220,7 @@ const config = computed(() => {
class="w-12 h-12 bg-bg-elevated border border-border rounded-full shadow-lg flex items-center justify-center text-fg-muted hover:text-fg transition-colors"
:aria-label="$t('common.close')"
>
<span class="w-5 h-5 i-carbon-close" />
<span class="w-5 h-5 i-carbon-close" aria-hidden="true" />
</button>
</div>
</template>
Expand Down
72 changes: 61 additions & 11 deletions app/pages/[...package].vue
Original file line number Diff line number Diff line change
Expand Up @@ -685,10 +685,19 @@ defineOgImageComponent('Package', {
/>

<!-- Install command with package manager selector -->
<section aria-labelledby="install-heading" class="area-install">
<section id="install" aria-labelledby="install-heading" class="area-install scroll-mt-20">
<div class="flex flex-wrap items-center justify-between mb-3">
<h2 id="install-heading" class="text-xs text-fg-subtle uppercase tracking-wider">
{{ $t('package.install.title') }}
<h2 id="install-heading" class="group text-xs text-fg-subtle uppercase tracking-wider">
<a
href="#install"
class="inline-flex items-center gap-1.5 text-fg-subtle hover:text-fg-muted transition-colors duration-200 no-underline"
>
{{ $t('package.install.title') }}
<span
class="i-carbon-link w-3 h-3 block opacity-0 group-hover:opacity-100 transition-opacity duration-200"
aria-hidden="true"
/>
</a>
</h2>
<!-- Package manager tabs -->
<div
Expand Down Expand Up @@ -787,9 +796,22 @@ defineOgImageComponent('Package', {
</section>

<!-- README -->
<section aria-labelledby="readme-heading" class="area-readme min-w-0">
<h2 id="readme-heading" class="text-xs text-fg-subtle uppercase tracking-wider mb-4">
{{ $t('package.readme.title') }}
<section
id="readme"
aria-labelledby="readme-heading"
class="area-readme min-w-0 scroll-mt-20"
>
<h2 id="readme-heading" class="group text-xs text-fg-subtle uppercase tracking-wider mb-4">
<a
href="#readme"
class="inline-flex items-center gap-1.5 text-fg-subtle hover:text-fg-muted transition-colors duration-200 no-underline"
>
{{ $t('package.readme.title') }}
<span
class="i-carbon-link w-3 h-3 block opacity-0 group-hover:opacity-100 transition-opacity duration-200"
aria-hidden="true"
/>
</a>
</h2>
<!-- eslint-disable vue/no-v-html -- HTML is sanitized server-side -->
<div
Expand Down Expand Up @@ -817,9 +839,26 @@ defineOgImageComponent('Package', {
</ClientOnly>

<!-- Keywords -->
<section v-if="displayVersion?.keywords?.length" aria-labelledby="keywords-heading">
<h2 id="keywords-heading" class="text-xs text-fg-subtle uppercase tracking-wider mb-3">
{{ $t('package.keywords_title') }}
<section
id="keywords"
v-if="displayVersion?.keywords?.length"
aria-labelledby="keywords-heading"
class="scroll-mt-20"
>
<h2
id="keywords-heading"
class="group text-xs text-fg-subtle uppercase tracking-wider mb-3"
>
<a
href="#keywords"
class="inline-flex items-center gap-1.5 text-fg-subtle hover:text-fg-muted transition-colors duration-200 no-underline"
>
{{ $t('package.keywords_title') }}
<span
class="i-carbon-link w-3 h-3 block opacity-0 group-hover:opacity-100 transition-opacity duration-200"
aria-hidden="true"
/>
</a>
</h2>
<ul class="flex flex-wrap gap-1.5 list-none m-0 p-0">
<li v-for="keyword in displayVersion.keywords.slice(0, 15)" :key="keyword">
Expand All @@ -840,16 +879,27 @@ defineOgImageComponent('Package', {
/>

<section
id="compatibility"
v-if="
displayVersion?.engines && (displayVersion.engines.node || displayVersion.engines.npm)
"
aria-labelledby="compatibility-heading"
class="scroll-mt-20"
>
<h2
id="compatibility-heading"
class="text-xs text-fg-subtle uppercase tracking-wider mb-3"
class="group text-xs text-fg-subtle uppercase tracking-wider mb-3"
>
{{ $t('package.compatibility') }}
<a
href="#compatibility"
class="inline-flex items-center gap-1.5 text-fg-subtle hover:text-fg-muted transition-colors duration-200 no-underline"
>
{{ $t('package.compatibility') }}
<span
class="i-carbon-link w-3 h-3 block opacity-0 group-hover:opacity-100 transition-opacity duration-200"
aria-hidden="true"
/>
</a>
</h2>
<dl class="space-y-2">
<div v-if="displayVersion.engines.node" class="flex justify-between gap-4 py-1">
Expand Down
48 changes: 33 additions & 15 deletions test/nuxt/components/PackageVersions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,12 @@ describe('PackageVersions', () => {
},
})

const link = component.find('a')
expect(link.exists()).toBe(true)
expect(link.text()).toBe('2.0.0')
// Find version links (exclude anchor links that start with #)
const versionLinks = component
.findAll('a')
.filter(a => !a.attributes('href')?.startsWith('#'))
expect(versionLinks.length).toBeGreaterThan(0)
expect(versionLinks[0]?.text()).toBe('2.0.0')
})

it('renders scoped package version links correctly', async () => {
Expand All @@ -99,9 +102,12 @@ describe('PackageVersions', () => {
},
})

const link = component.find('a')
expect(link.exists()).toBe(true)
expect(link.text()).toBe('1.0.0')
// Find version links (exclude anchor links that start with #)
const versionLinks = component
.findAll('a')
.filter(a => !a.attributes('href')?.startsWith('#'))
expect(versionLinks.length).toBeGreaterThan(0)
expect(versionLinks[0]?.text()).toBe('1.0.0')
})
})

Expand Down Expand Up @@ -190,8 +196,11 @@ describe('PackageVersions', () => {
},
})

const links = component.findAll('a')
const versions = links.map(l => l.text())
// Find version links (exclude anchor links that start with #)
const versionLinks = component
.findAll('a')
.filter(a => !a.attributes('href')?.startsWith('#'))
const versions = versionLinks.map(l => l.text())
// Should be sorted by version descending
expect(versions[0]).toBe('2.0.0')
})
Expand All @@ -210,8 +219,12 @@ describe('PackageVersions', () => {
},
})

const link = component.find('a')
expect(link.classes()).toContain('text-red-400')
// Find version links (exclude anchor links that start with #)
const versionLinks = component
.findAll('a')
.filter(a => !a.attributes('href')?.startsWith('#'))
expect(versionLinks.length).toBeGreaterThan(0)
expect(versionLinks[0]?.classes()).toContain('text-red-400')
})

it('shows deprecated version in title attribute', async () => {
Expand All @@ -226,8 +239,12 @@ describe('PackageVersions', () => {
},
})

const link = component.find('a')
expect(link.attributes('title')).toContain('deprecated')
// Find version links (exclude anchor links that start with #)
const versionLinks = component
.findAll('a')
.filter(a => !a.attributes('href')?.startsWith('#'))
expect(versionLinks.length).toBeGreaterThan(0)
expect(versionLinks[0]?.attributes('title')).toContain('deprecated')
})

it('filters deprecated tags from visible list when package is not deprecated', async () => {
Expand Down Expand Up @@ -552,9 +569,10 @@ describe('PackageVersions', () => {
},
})

// Count visible version links (excluding "Other versions" section)
// The first set of links before the "Other versions" button
const visibleLinks = component.findAll('a')
// Count visible version links (excluding anchor links that start with #)
const visibleLinks = component
.findAll('a')
.filter(a => !a.attributes('href')?.startsWith('#'))
// Should have max 10 visible links in the main section
expect(visibleLinks.length).toBeLessThanOrEqual(10)
})
Expand Down