From 1ab5a1699e8340601ca82cf008c028b8d7a8ff9a Mon Sep 17 00:00:00 2001
From: RYGRIT
Date: Tue, 10 Feb 2026 15:58:36 +0800
Subject: [PATCH 1/3] refactor(link-base): update size and tests
---
app/components/Link/Base.vue | 94 ++++++++++++++++-----------
test/nuxt/a11y.spec.ts | 29 +++++++--
test/nuxt/components/LinkBase.spec.ts | 53 +++++++++++++++
3 files changed, 131 insertions(+), 45 deletions(-)
create mode 100644 test/nuxt/components/LinkBase.spec.ts
diff --git a/app/components/Link/Base.vue b/app/components/Link/Base.vue
index eeb1b9bf9..fc0cda230 100644
--- a/app/components/Link/Base.vue
+++ b/app/components/Link/Base.vue
@@ -7,12 +7,11 @@ const props = withDefaults(
/** Disabled links will be displayed as plain text */
disabled?: boolean
/**
- * `type` should never be used, because this will always be a link.
- * */
- type?: never
- variant?: 'button-primary' | 'button-secondary' | 'link'
- size?: 'small' | 'medium'
- block?: boolean
+ * Controls whether the link is styled as text or as a button.
+ */
+ type?: 'button' | 'link'
+ size?: 'xs' | 'sm' | 'md' | 'lg'
+ inline?: boolean
ariaKeyshortcuts?: string
@@ -37,7 +36,7 @@ const props = withDefaults(
noUnderline?: boolean
} & NuxtLinkProps
>(),
- { variant: 'link', size: 'medium' },
+ { type: 'link', size: 'md', inline: true },
)
const isLinkExternal = computed(
@@ -50,47 +49,66 @@ const isLinkAnchor = computed(
() => !!props.to && typeof props.to === 'string' && props.to.startsWith('#'),
)
-/** size is only applicable for button like links */
-const isLink = computed(() => props.variant === 'link')
-const isButton = computed(() => !isLink.value)
-const isButtonSmall = computed(() => props.size === 'small' && !isLink.value)
-const isButtonMedium = computed(() => props.size === 'medium' && !isLink.value)
+const isLink = computed(() => props.type === 'link')
+const isButton = computed(() => props.type === 'button')
+const sizeClass = computed(() => {
+ if (isButton.value) {
+ switch (props.size) {
+ case 'xs':
+ return 'text-xs px-2 py-0.5'
+ case 'sm':
+ return 'text-sm px-4 py-2'
+ case 'md':
+ return 'text-base px-5 py-2.5'
+ case 'lg':
+ return 'text-lg px-6 py-3'
+ }
+ }
+
+ switch (props.size) {
+ case 'xs':
+ return 'text-xs'
+ case 'sm':
+ return 'text-sm'
+ case 'md':
+ return 'text-base'
+ case 'lg':
+ return 'text-lg'
+ }
+})
{
it("should have no accessibility violations when it's the current link", async () => {
const component = await mountSuspended(LinkBase, {
- props: { to: 'http://example.com', current: true },
+ props: { to: 'http://example.com' },
+ attrs: { 'aria-current': 'page' },
slots: { default: 'Button link content' },
})
const results = await runAxe(component)
@@ -396,18 +397,18 @@ describe('component accessibility audits', () => {
expect(results.violations).toEqual([])
})
- it('should have no accessibility violations as secondary button', async () => {
+ it('should have no accessibility violations as button link', async () => {
const component = await mountSuspended(LinkBase, {
- props: { to: 'http://example.com', disabled: true, variant: 'button-secondary' },
+ props: { to: 'http://example.com', disabled: true, type: 'button' },
slots: { default: 'Button link content' },
})
const results = await runAxe(component)
expect(results.violations).toEqual([])
})
- it('should have no accessibility violations as primary button', async () => {
+ it('should have no accessibility violations as button link (large)', async () => {
const component = await mountSuspended(LinkBase, {
- props: { to: 'http://example.com', disabled: true, variant: 'button-primary' },
+ props: { to: 'http://example.com', disabled: true, type: 'button', size: 'lg' },
slots: { default: 'Button link content' },
})
const results = await runAxe(component)
@@ -419,8 +420,22 @@ describe('component accessibility audits', () => {
props: {
to: 'http://example.com',
disabled: true,
- variant: 'button-secondary',
- size: 'small',
+ type: 'button',
+ size: 'sm',
+ },
+ slots: { default: 'Button link content' },
+ })
+ const results = await runAxe(component)
+ expect(results.violations).toEqual([])
+ })
+
+ it('should have no accessibility violations as extra-small button', async () => {
+ const component = await mountSuspended(LinkBase, {
+ props: {
+ to: 'http://example.com',
+ disabled: true,
+ type: 'button',
+ size: 'xs',
},
slots: { default: 'Button link content' },
})
diff --git a/test/nuxt/components/LinkBase.spec.ts b/test/nuxt/components/LinkBase.spec.ts
new file mode 100644
index 000000000..a90e0d6bb
--- /dev/null
+++ b/test/nuxt/components/LinkBase.spec.ts
@@ -0,0 +1,53 @@
+import { describe, expect, it } from 'vitest'
+import { mountSuspended } from '@nuxt/test-utils/runtime'
+import LinkBase from '~/components/Link/Base.vue'
+
+describe('LinkBase', () => {
+ it('renders a default text link with inline layout and medium size', async () => {
+ const wrapper = await mountSuspended(LinkBase, {
+ props: { to: '/about' },
+ slots: { default: 'About' },
+ })
+
+ const link = wrapper.get('a')
+ expect(link.classes()).toContain('inline-flex')
+ expect(link.classes()).toContain('text-base')
+ expect(link.classes()).toContain('underline')
+ })
+
+ it('uses flex layout and small text size when inline is false', async () => {
+ const wrapper = await mountSuspended(LinkBase, {
+ props: { to: '/settings', size: 'sm', inline: false },
+ slots: { default: 'Settings' },
+ })
+
+ const link = wrapper.get('a')
+ expect(link.classes()).toContain('flex')
+ expect(link.classes()).toContain('text-sm')
+ })
+
+ it('applies extra-small text size for links', async () => {
+ const wrapper = await mountSuspended(LinkBase, {
+ props: { to: '/compare', size: 'xs' },
+ slots: { default: 'Compare' },
+ })
+
+ const link = wrapper.get('a')
+ expect(link.classes()).toContain('text-xs')
+ })
+
+ it('styles button links with size classes and no underline', async () => {
+ const wrapper = await mountSuspended(LinkBase, {
+ props: { to: '/compare', type: 'button', size: 'lg' },
+ slots: { default: 'Compare' },
+ })
+
+ const link = wrapper.get('a')
+ expect(link.classes()).toContain('border')
+ expect(link.classes()).toContain('rounded-md')
+ expect(link.classes()).toContain('text-lg')
+ expect(link.classes()).toContain('px-6')
+ expect(link.classes()).toContain('py-3')
+ expect(link.classes()).not.toContain('underline')
+ })
+})
From 6d3840d9a7ae51bc5e8f6456e57c8298106b3d3e Mon Sep 17 00:00:00 2001
From: RYGRIT
Date: Tue, 10 Feb 2026 16:00:15 +0800
Subject: [PATCH 2/3] fix: update LinkBase usage across app
---
app/app.vue | 6 +--
app/components/AppFooter.vue | 13 ++---
app/components/AppHeader.vue | 3 +-
app/components/BuildEnvironment.vue | 3 +-
app/components/Code/FileTree.vue | 5 +-
app/components/CollapsibleSection.vue | 2 +-
app/components/Compare/ComparisonGrid.vue | 11 ++--
.../Compare/ReplacementSuggestion.vue | 2 +-
app/components/Package/Dependencies.vue | 23 ++++++--
app/components/Package/InstallScripts.vue | 8 +--
app/components/Package/Keywords.vue | 4 +-
app/components/Package/MetricsBadges.vue | 4 +-
app/components/Package/Versions.vue | 25 ++++-----
app/components/PackageProvenanceSection.vue | 2 +-
app/pages/about.vue | 36 ++++++-------
app/pages/org/[org].vue | 6 +--
app/pages/package-code/[...path].vue | 16 +++---
app/pages/package/[[org]]/[name].vue | 54 +++++++++++--------
app/pages/~[username]/index.vue | 6 +--
app/pages/~[username]/orgs.vue | 5 +-
20 files changed, 132 insertions(+), 102 deletions(-)
diff --git a/app/app.vue b/app/app.vue
index 2b0a58740..11f4c13d4 100644
--- a/app/app.vue
+++ b/app/app.vue
@@ -121,9 +121,9 @@ if (import.meta.client) {
-
{{
- $t('common.skip_link')
- }}
+
+ {{ $t('common.skip_link') }}
+
diff --git a/app/components/AppFooter.vue b/app/components/AppFooter.vue
index cf7961ecb..e2f549ba9 100644
--- a/app/components/AppFooter.vue
+++ b/app/components/AppFooter.vue
@@ -17,10 +17,10 @@ const showModal = () => modalRef.value?.showModal?.()
-
+
{{ $t('footer.about') }}
-
+
{{ $t('privacy_policy.title') }}
+
modalRef.value?.showModal?.()
-
+
{{ $t('footer.docs') }}
-
+
{{ $t('footer.source') }}
-
+
{{ $t('footer.social') }}
-
+
{{ $t('footer.chat') }}
diff --git a/app/components/AppHeader.vue b/app/components/AppHeader.vue
index a55ea3217..b555e2aca 100644
--- a/app/components/AppHeader.vue
+++ b/app/components/AppHeader.vue
@@ -257,9 +257,10 @@ onKeyStroke(
v-for="link in desktopLinks"
:key="link.name"
class="border-none"
- variant="button-secondary"
+ type="button"
:to="link.to"
:aria-keyshortcuts="link.keyshortcut"
+ size="sm"
>
{{ link.label }}
diff --git a/app/components/BuildEnvironment.vue b/app/components/BuildEnvironment.vue
index 53eed13cf..7a7241a49 100644
--- a/app/components/BuildEnvironment.vue
+++ b/app/components/BuildEnvironment.vue
@@ -24,6 +24,7 @@ const buildTime = computed(() => new Date(buildInfo.value.time))
v{{ buildInfo.version }}
@@ -31,7 +32,7 @@ const buildTime = computed(() => new Date(buildInfo.value.time))
·
-
+
{{ buildInfo.shortCommit }}
diff --git a/app/components/Code/FileTree.vue b/app/components/Code/FileTree.vue
index c7b7434d9..f3dd735f2 100644
--- a/app/components/Code/FileTree.vue
+++ b/app/components/Code/FileTree.vue
@@ -80,11 +80,12 @@ watch(
diff --git a/app/components/CollapsibleSection.vue b/app/components/CollapsibleSection.vue
index 65a2a45cc..c4bf77c8a 100644
--- a/app/components/CollapsibleSection.vue
+++ b/app/components/CollapsibleSection.vue
@@ -103,7 +103,7 @@ useHead({
/>
-
+
{{ title }}
diff --git a/app/components/Compare/ComparisonGrid.vue b/app/components/Compare/ComparisonGrid.vue
index 3bf212957..e3a699db7 100644
--- a/app/components/Compare/ComparisonGrid.vue
+++ b/app/components/Compare/ComparisonGrid.vue
@@ -42,8 +42,9 @@ function getReplacementTooltip(col: ComparisonGridColumn): string {
{{ col.name }}@{{ col.version }}
@@ -80,9 +81,9 @@ function getReplacementTooltip(col: ComparisonGridColumn): string {
- {{
- $t('compare.no_dependency.e18e_community')
- }}
+
+ {{ $t('compare.no_dependency.e18e_community') }}
+
diff --git a/app/components/Compare/ReplacementSuggestion.vue b/app/components/Compare/ReplacementSuggestion.vue
index 1fd54f514..34d21a28e 100644
--- a/app/components/Compare/ReplacementSuggestion.vue
+++ b/app/components/Compare/ReplacementSuggestion.vue
@@ -73,7 +73,7 @@ const docUrl = computed(() => {
-
+
{{ $t('package.replacement.learn_more') }}
diff --git a/app/components/Package/Dependencies.vue b/app/components/Package/Dependencies.vue
index bab0a08fc..e8a34a6dc 100644
--- a/app/components/Package/Dependencies.vue
+++ b/app/components/Package/Dependencies.vue
@@ -91,7 +91,7 @@ const numberFormatter = useNumberFormatter()
:key="dep"
class="flex items-center justify-between py-1 text-sm gap-2"
>
-
+
{{ dep }}
@@ -111,6 +111,7 @@ const numberFormatter = useNumberFormatter()
:class="SEVERITY_TEXT_COLORS[getHighestSeverity(getVulnerableDepInfo(dep)!.counts)]"
:title="`${getVulnerableDepInfo(dep)!.counts.total} vulnerabilities`"
classicon="i-carbon:security"
+ size="sm"
>
{{ $t('package.dependencies.view_vulnerabilities') }}
@@ -120,6 +121,7 @@ const numberFormatter = useNumberFormatter()
class="shrink-0 text-purple-500"
:title="getDeprecatedDepInfo(dep)!.message"
classicon="i-carbon:warning-hex"
+ size="sm"
>
{{ $t('package.deprecated.label') }}
@@ -128,6 +130,7 @@ const numberFormatter = useNumberFormatter()
class="block truncate"
:class="getVersionClass(outdatedDeps[dep])"
:title="outdatedDeps[dep] ? getOutdatedTooltip(outdatedDeps[dep], $t) : version"
+ size="sm"
>
{{ version }}
@@ -178,7 +181,13 @@ const numberFormatter = useNumberFormatter()
class="flex items-center justify-between py-1 text-sm gap-1 min-w-0"
>
-
+
{{ peer.name }}
@@ -187,9 +196,11 @@ const numberFormatter = useNumberFormatter()
{{ peer.version }}
@@ -239,14 +250,16 @@ const numberFormatter = useNumberFormatter()
:key="dep"
class="flex items-center justify-between py-1 text-sm gap-2"
>
-
+
{{ dep }}
{{ version }}
diff --git a/app/components/Package/InstallScripts.vue b/app/components/Package/InstallScripts.vue
index b7747042d..07b1b173f 100644
--- a/app/components/Package/InstallScripts.vue
+++ b/app/components/Package/InstallScripts.vue
@@ -59,10 +59,10 @@ const isExpanded = shallowRef(false)
>
- {{ scriptParts[scriptName].prefix
- }}{{
- scriptParts[scriptName].filePath
- }}
+ {{ scriptParts[scriptName].prefix }}
+
+ {{ scriptParts[scriptName].filePath }}
+
{{ installScripts.content[scriptName] }}
diff --git a/app/components/Package/Keywords.vue b/app/components/Package/Keywords.vue
index 2328d0edb..e01f24983 100644
--- a/app/components/Package/Keywords.vue
+++ b/app/components/Package/Keywords.vue
@@ -8,8 +8,8 @@ defineProps<{
-
{{ keyword }}
diff --git a/app/components/Package/MetricsBadges.vue b/app/components/Package/MetricsBadges.vue
index 356047315..5697fb8a6 100644
--- a/app/components/Package/MetricsBadges.vue
+++ b/app/components/Package/MetricsBadges.vue
@@ -60,8 +60,8 @@ const typesHref = computed(() => {
diff --git a/app/components/Package/Versions.vue b/app/components/Package/Versions.vue
index 5f9fd0221..2927acb94 100644
--- a/app/components/Package/Versions.vue
+++ b/app/components/Package/Versions.vue
@@ -305,7 +305,8 @@ function getTagVersions(tag: string): VersionDisplay[] {
>
-
+
{{ $t('package.provenance_section.title') }}
diff --git a/app/pages/about.vue b/app/pages/about.vue
index be344b577..e8742a818 100644
--- a/app/pages/about.vue
+++ b/app/pages/about.vue
@@ -95,34 +95,34 @@ const { data: contributors, status: contributorsStatus } = useLazyFetch('/api/co
>
{{ $t('about.what_we_are_not.words.already') }}
- {{
- $t('about.what_we_are_not.words.people')
- }}
+
+ {{ $t('about.what_we_are_not.words.people') }}
+
- {{
- $t('about.what_we_are_not.words.building')
- }}
+
+ {{ $t('about.what_we_are_not.words.building') }}
+
- {{
- $t('about.what_we_are_not.words.really')
- }}
+
+ {{ $t('about.what_we_are_not.words.really') }}
+
- {{
- $t('about.what_we_are_not.words.cool')
- }}
+
+ {{ $t('about.what_we_are_not.words.cool') }}
+
- {{
- $t('about.what_we_are_not.words.package')
- }}
+
+ {{ $t('about.what_we_are_not.words.package') }}
+
- {{
- $t('about.what_we_are_not.words.managers')
- }}
+
+ {{ $t('about.what_we_are_not.words.managers') }}
+
diff --git a/app/pages/org/[org].vue b/app/pages/org/[org].vue
index 08739fd43..338566d34 100644
--- a/app/pages/org/[org].vue
+++ b/app/pages/org/[org].vue
@@ -244,9 +244,9 @@ defineOgImageComponent('Default', {
{{ error?.message ?? $t('org.page.failed_to_load') }}
- {{
- $t('common.go_back_home')
- }}
+
+ {{ $t('common.go_back_home') }}
+
diff --git a/app/pages/package-code/[...path].vue b/app/pages/package-code/[...path].vue
index b3b4a02bd..f9a805b86 100644
--- a/app/pages/package-code/[...path].vue
+++ b/app/pages/package-code/[...path].vue
@@ -374,9 +374,9 @@ defineOgImageComponent('Default', {
{{ $t('code.version_required') }}
-
{{
- $t('code.go_to_package')
- }}
+
+ {{ $t('code.go_to_package') }}
+
@@ -388,9 +388,9 @@ defineOgImageComponent('Default', {
{{ $t('code.failed_to_load_tree') }}
-
{{
- $t('code.back_to_package')
- }}
+
+ {{ $t('code.back_to_package') }}
+
@@ -495,7 +495,7 @@ defineOgImageComponent('Default', {
}}
{{ $t('code.view_raw') }}
@@ -546,7 +546,7 @@ defineOgImageComponent('Default', {
{{ $t('code.failed_to_load') }}
{{ $t('code.unavailable_hint') }}
{{ $t('code.view_raw') }}
diff --git a/app/pages/package/[[org]]/[name].vue b/app/pages/package/[[org]]/[name].vue
index 2fac440f4..dc6dd5f30 100644
--- a/app/pages/package/[[org]]/[name].vue
+++ b/app/pages/package/[[org]]/[name].vue
@@ -566,7 +566,11 @@ onKeyStroke(
:title="pkg.name"
dir="ltr"
>
-
+
@{{ orgName }}
/
@@ -609,8 +613,9 @@ onKeyStroke(
:to="packageRoute(pkg.name, resolvedVersion)"
:title="$t('package.view_permalink')"
dir="ltr"
- >{{ resolvedVersion }}
+ {{ resolvedVersion }}
+
v{{ resolvedVersion }}
@@ -625,8 +630,8 @@ onKeyStroke(
position="bottom"
>
{{ $t('package.links.docs') }}
{{ $t('package.links.code') }}
{{ $t('package.links.compare') }}
@@ -745,7 +753,7 @@ onKeyStroke(
class="flex flex-wrap items-center gap-x-3 gap-y-1.5 sm:gap-4 list-none m-0 p-0 mt-3 text-sm"
>
-
+
{{ repoRef.owner }}/{{ repoRef.repo }}
@@ -753,23 +761,23 @@ onKeyStroke(
-
+
{{ compactNumberFormatter.format(stars) }}
-
+
{{ compactNumberFormatter.format(forks) }}
-
+
{{ $t('package.links.homepage') }}
-
+
{{ $t('package.links.issues') }}
@@ -778,6 +786,7 @@ onKeyStroke(
:to="`https://www.npmjs.com/package/${pkg.name}`"
:title="$t('common.view_on_npm')"
classicon="i-carbon:logo-npm"
+ size="sm"
>
npm
@@ -787,12 +796,13 @@ onKeyStroke(
:to="jsrInfo.url"
:title="$t('badges.jsr.title')"
classicon="i-simple-icons:jsr"
+ size="sm"
>
{{ $t('package.links.jsr') }}
-
+
{{ $t('package.links.fund') }}
@@ -872,8 +882,8 @@ onKeyStroke(
-
+
{{ $t('package.get_started.title') }}
@@ -1157,7 +1167,7 @@ onKeyStroke(