Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ebc0cb3
feat: add word wrap toggle to code viewer (#2027)
thealxlabs Mar 22, 2026
41d2a34
fix(ui): fix dropdown z-index and search layout stability on focus (#…
thealxlabs Mar 22, 2026
8e619f1
fix: show version-specific license, no-deps empty state, layout stabi…
thealxlabs Mar 22, 2026
b23fd4b
fix: exclude CodePen profile pages from playground links (#2110)
thealxlabs Mar 22, 2026
516ee88
feat: add package dependents list page (#2036)
thealxlabs Mar 22, 2026
662c37f
fix: show copy buttons on touch devices (#1771)
thealxlabs Mar 22, 2026
ca144b1
fix: improve compare bar chart skeleton loaders with varying widths (…
thealxlabs Mar 22, 2026
a657927
fix: collapsible section title now toggles instead of scrolling (#151…
thealxlabs Mar 22, 2026
13eacb0
feat: reorganize footer links into product/legal/community columns (#…
thealxlabs Mar 22, 2026
6e345ad
fix: add pointer cursor to ButtonBase (#1760)
thealxlabs Mar 22, 2026
bd8fa38
fix: cap pagination page count to max fetchable results (#1923)
thealxlabs Mar 22, 2026
727f305
fix: add back button to blog page (#2022)
thealxlabs Mar 22, 2026
a7d3613
fix: link aliased dependencies to their real package (#2010)
thealxlabs Mar 22, 2026
71785d6
fix: resolve CI failures (unused imports, type errors, i18n schema, a…
thealxlabs Mar 22, 2026
d704602
[autofix.ci] apply automated fixes
autofix-ci[bot] Mar 22, 2026
29974e5
fix(a11y): add aria-expanded and aria-controls to collapsible title b…
thealxlabs Mar 22, 2026
72baa25
fix(a11y): remove redundant aria-label from collapsible title button
thealxlabs Mar 22, 2026
ece3b66
fix(a11y): match copy button aria-label to its visible text
thealxlabs Mar 22, 2026
4fe4145
[autofix.ci] apply automated fixes
autofix-ci[bot] Mar 22, 2026
d2f86d2
Update Base.vue
thealxlabs Mar 22, 2026
0f63f0e
[autofix.ci] apply automated fixes
autofix-ci[bot] Mar 22, 2026
99322d5
fix(a11y): increase provenance link touch target size to meet 24px mi…
thealxlabs Mar 22, 2026
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
64 changes: 32 additions & 32 deletions app/components/AppFooter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,19 @@ const closeModal = () => modalRef.value?.close?.()
</p>
<BuildEnvironment v-if="!isHome" footer />
</div>
<!-- Desktop: Show all links. Mobile: Links are in MobileMenu -->
<div class="hidden sm:flex flex-col lg:items-end gap-3 min-h-11 text-xs">
<div class="flex items-center gap-5">
<LinkBase :to="{ name: 'about' }">
{{ $t('footer.about') }}
</LinkBase>
<LinkBase :to="{ name: 'blog' }">
{{ $t('footer.blog') }}
</LinkBase>
<LinkBase :to="{ name: 'privacy' }">
{{ $t('privacy_policy.title') }}
</LinkBase>
<LinkBase :to="{ name: 'accessibility' }">
{{ $t('a11y.footer_title') }}
</LinkBase>
<LinkBase :to="{ name: 'translation-status' }">
{{ $t('translation_status.title') }}
</LinkBase>
<!-- Desktop: Show all links in columns. Mobile: Links are in MobileMenu -->
<div class="hidden sm:grid sm:grid-cols-2 lg:grid-cols-3 gap-x-10 gap-y-1 min-h-11 text-xs">
<!-- Column 1: Product -->
<div class="flex flex-col gap-1">
<span class="text-fg-subtle uppercase tracking-wide text-2xs mb-1">{{
$t('footer.product')
}}</span>
<LinkBase :to="{ name: 'about' }">{{ $t('footer.about') }}</LinkBase>
<LinkBase :to="{ name: 'blog' }">{{ $t('footer.blog') }}</LinkBase>
<LinkBase :to="NPMX_DOCS_SITE">{{ $t('footer.docs') }}</LinkBase>
<button
type="button"
class="cursor-pointer group inline-flex gap-x-1 items-center justify-center underline-offset-[0.2rem] underline decoration-1 decoration-fg/30 font-mono text-fg hover:(decoration-accent text-accent) focus-visible:(decoration-accent text-accent) transition-colors duration-200"
class="text-start cursor-pointer inline-flex gap-x-1 items-center underline-offset-[0.2rem] underline decoration-1 decoration-fg/30 font-mono text-fg hover:(decoration-accent text-accent) focus-visible:(decoration-accent text-accent) transition-colors duration-200"
@click.prevent="showModal"
aria-haspopup="dialog"
>
Expand Down Expand Up @@ -126,19 +118,27 @@ const closeModal = () => modalRef.value?.close?.()
</p>
</Modal>
</div>
<div class="flex items-center gap-5">
<LinkBase :to="NPMX_DOCS_SITE">
{{ $t('footer.docs') }}
</LinkBase>
<LinkBase to="https://repo.npmx.dev">
{{ $t('footer.source') }}
</LinkBase>
<LinkBase to="https://social.npmx.dev">
{{ $t('footer.social') }}
</LinkBase>
<LinkBase :to="discord.url">
{{ discord.label }}
</LinkBase>

<!-- Column 2: Legal & Info -->
<div class="flex flex-col gap-1">
<span class="text-fg-subtle uppercase tracking-wide text-2xs mb-1">{{
$t('footer.legal')
}}</span>
<LinkBase :to="{ name: 'privacy' }">{{ $t('privacy_policy.title') }}</LinkBase>
<LinkBase :to="{ name: 'accessibility' }">{{ $t('a11y.footer_title') }}</LinkBase>
<LinkBase :to="{ name: 'translation-status' }">{{
$t('translation_status.title')
}}</LinkBase>
</div>

<!-- Column 3: Community -->
<div class="flex flex-col gap-1">
<span class="text-fg-subtle uppercase tracking-wide text-2xs mb-1">{{
$t('footer.community')
}}</span>
<LinkBase to="https://repo.npmx.dev">{{ $t('footer.source') }}</LinkBase>
<LinkBase to="https://social.npmx.dev">{{ $t('footer.social') }}</LinkBase>
<LinkBase :to="discord.url">{{ discord.label }}</LinkBase>
</div>
</div>
</div>
Expand Down
12 changes: 10 additions & 2 deletions app/components/AppHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ const route = useRoute()
const isMobile = useIsMobile()
const isSearchExpandedManually = shallowRef(false)
const searchBoxRef = useTemplateRef('searchBoxRef')
const searchContainerRef = useTemplateRef('searchContainerRef')

// On search page, always show search expanded on mobile
const isOnHomePage = computed(() => route.name === 'index')
Expand Down Expand Up @@ -188,6 +189,12 @@ function handleSearchBlur() {
}
}

onClickOutside(searchContainerRef, () => {
if (isMobile.value && !isOnSearchPage.value) {
isSearchExpandedManually.value = false
}
})

function handleSearchFocus() {
showFullSearch.value = true
}
Expand Down Expand Up @@ -215,7 +222,7 @@ onKeyStroke(
<div class="absolute inset-0 bg-bg/80 backdrop-blur-md" />
<nav
:aria-label="$t('nav.main_navigation')"
class="relative container min-h-14 flex items-center gap-2 z-1 justify-end"
class="relative container min-h-14 flex items-center gap-2 justify-end"
>
<!-- Mobile: Logo (navigates home) -->
<NuxtLink
Expand Down Expand Up @@ -260,6 +267,7 @@ onKeyStroke(

<!-- Center: Search bar + nav items -->
<div
ref="searchContainerRef"
class="flex-1 flex items-center md:gap-6"
:class="{
'hidden sm:flex': !isSearchExpanded,
Expand All @@ -277,7 +285,7 @@ onKeyStroke(
/>
<ul
v-if="!isSearchExpanded && isConnected && npmUser"
:class="{ hidden: showFullSearch }"
:class="{ 'invisible pointer-events-none': showFullSearch }"
class="hidden sm:flex items-center gap-4 sm:gap-6 list-none m-0 p-0"
>
<!-- Packages dropdown (when connected) -->
Expand Down
2 changes: 1 addition & 1 deletion app/components/Button/Base.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ defineExpose({
<template>
<button
ref="el"
class="group gap-x-1 items-center justify-center font-mono border border-border rounded-md transition-all duration-200 disabled:(opacity-40 cursor-not-allowed border-transparent)"
class="group gap-x-1 items-center justify-center font-mono border border-border rounded-md transition-all duration-200 cursor-pointer disabled:(opacity-40 cursor-not-allowed border-transparent)"
:class="{
'inline-flex': !block,
'flex': block,
Expand Down
28 changes: 26 additions & 2 deletions app/components/Code/Viewer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const props = defineProps<{
html: string
lines: number
selectedLines: { start: number; end: number } | null
wordWrap?: boolean
}>()

const emit = defineEmits<{
Expand Down Expand Up @@ -113,9 +114,17 @@ watch(
</div>

<!-- Code content -->
<div class="code-content flex-1 overflow-x-auto min-w-0">
<div
class="code-content flex-1 min-w-0"
:class="wordWrap ? 'overflow-x-hidden' : 'overflow-x-auto'"
>
<!-- eslint-disable vue/no-v-html -- HTML is generated server-side by Shiki -->
<div ref="codeRef" class="code-lines min-w-full w-fit" v-html="html" />
<div
ref="codeRef"
class="code-lines min-w-full w-fit"
:class="{ 'word-wrap': wordWrap }"
v-html="html"
/>
<!-- eslint-enable vue/no-v-html -->
</div>
</div>
Expand Down Expand Up @@ -155,6 +164,21 @@ watch(
transition: background-color 0.1s;
}

.code-content.word-wrap-active :deep(.line),
.code-content:has(.word-wrap) :deep(.line) {
white-space: pre-wrap;
overflow-wrap: break-word;
max-height: none;
overflow: visible;
}

.code-lines.word-wrap :deep(.line) {
white-space: pre-wrap;
overflow-wrap: break-word;
max-height: none;
overflow: visible;
}

/* Highlighted lines in code content - extend full width with negative margin */
.code-content :deep(.line.highlighted) {
@apply bg-yellow-500/20;
Expand Down
15 changes: 9 additions & 6 deletions app/components/CollapsibleSection.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<script setup lang="ts">
import { shallowRef, computed } from 'vue'
import { LinkBase } from '#components'

interface Props {
title: string
Expand Down Expand Up @@ -104,14 +103,18 @@ useHead({
/>
</button>

<span>
<LinkBase :to="`#${id}`">
{{ title }}
</LinkBase>
<button
type="button"
class="text-start focus-visible:outline-accent/70 rounded"
:aria-expanded="isOpen"
:aria-controls="contentId"
@click="toggle"
Comment on lines +106 to +111
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor

Expose disclosure semantics on the title toggle button.

This button also controls the collapsible content, but it currently omits the standard aria-expanded and aria-controls relationship/state on this control.

Suggested fix
         <button
           type="button"
           class="text-start focus-visible:outline-accent/70 rounded"
-          :aria-label="`${isOpen ? 'Collapse' : 'Expand'} ${title}`"
+          :aria-expanded="isOpen"
+          :aria-controls="contentId"
+          :aria-label="ariaLabel"
           `@click`="toggle"
         >
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button
type="button"
class="text-start focus-visible:outline-accent/70 rounded"
:aria-label="`${isOpen ? 'Collapse' : 'Expand'} ${title}`"
@click="toggle"
<button
type="button"
class="text-start focus-visible:outline-accent/70 rounded"
:aria-expanded="isOpen"
:aria-controls="contentId"
:aria-label="ariaLabel"
`@click`="toggle"

>
{{ title }}
<span v-if="subtitle" class="block text-2xs normal-case tracking-normal">{{
subtitle
}}</span>
</span>
</button>
</component>

<!-- Actions slot for buttons or other elements -->
Expand Down
28 changes: 18 additions & 10 deletions app/components/Compare/FacetBarChart.vue
Original file line number Diff line number Diff line change
Expand Up @@ -327,23 +327,31 @@ const config = computed<VueUiHorizontalBarConfig>(() => {
</VueUiHorizontalBar>

<template #fallback>
<div class="flex flex-col gap-2 justify-center items-center mb-2">
<SkeletonInline class="h-4 w-16" />
<SkeletonInline class="h-4 w-28" />
<div class="flex flex-col gap-2 justify-center items-center mb-4" aria-hidden="true">
<SkeletonInline class="h-4 w-20 rounded" />
<SkeletonInline class="h-3 w-36 rounded" />
</div>
<div class="flex flex-col gap-1">
<SkeletonInline class="h-7 w-full" v-for="pkg in packages" :key="pkg" />
<div class="flex flex-col gap-2 px-2" aria-hidden="true">
<div v-for="(pkg, i) in packages" :key="pkg" class="flex items-center gap-3">
<SkeletonInline class="h-3 shrink-0" :style="{ width: `${40 + ((i * 17) % 40)}%` }" />
<SkeletonInline class="h-7 flex-1 rounded" />
</div>
</div>
</template>
</ClientOnly>

<template v-else>
<div class="flex flex-col gap-2 justify-center items-center mb-2">
<SkeletonInline class="h-4 w-16" />
<SkeletonInline class="h-4 w-28" />
<!-- Title skeleton -->
<div class="flex flex-col gap-2 justify-center items-center mb-4" aria-hidden="true">
<SkeletonInline class="h-4 w-20 rounded" />
<SkeletonInline class="h-3 w-36 rounded" />
</div>
<div class="flex flex-col gap-1">
<SkeletonInline class="h-7 w-full" v-for="pkg in packages" :key="pkg" />
<!-- Bar skeletons with varying widths for visual realism -->
<div class="flex flex-col gap-2 px-2" aria-hidden="true">
<div v-for="(pkg, i) in packages" :key="pkg" class="flex items-center gap-3">
<SkeletonInline class="h-3 shrink-0" :style="{ width: `${40 + ((i * 17) % 40)}%` }" />
<SkeletonInline class="h-7 flex-1 rounded" />
</div>
</div>
</template>
</div>
Expand Down
19 changes: 16 additions & 3 deletions app/components/CopyToClipboardButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@ const props = defineProps<{

const buttonCopyText = computed(() => props.copyText || $t('common.copy'))
const buttonCopiedText = computed(() => props.copiedText || $t('common.copied'))
const buttonAriaLabelCopy = computed(() => props.ariaLabelCopy || $t('common.copy'))
const buttonAriaLabelCopied = computed(() => props.ariaLabelCopied || $t('common.copied'))
const buttonAriaLabelCopy = computed(
() => props.ariaLabelCopy || props.copyText || $t('common.copy'),
)
const buttonAriaLabelCopied = computed(
() => props.ariaLabelCopied || props.copiedText || $t('common.copied'),
)

const emit = defineEmits<{
click: []
Expand Down Expand Up @@ -85,8 +89,17 @@ function handleClick() {
}

@media (hover: none) {
/* On touch devices show the button statically (no hover possible) */
.copyButton {
display: none;
clip: auto;
clip-path: none;
height: auto;
overflow: visible;
width: auto;
opacity: 1;
translate: none;
pointer-events: auto;
transition: none;
}
}
</style>
Loading
Loading