Skip to content

Commit a181def

Browse files
committed
fix: truncate long package versions
1 parent e63676d commit a181def

5 files changed

Lines changed: 121 additions & 60 deletions

File tree

app/components/PackageCard.vue

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,12 @@ function formatDate(dateStr: string): string {
3434
>
3535
{{ result.package.name }}
3636
</component>
37-
<div class="flex items-center gap-1.5 shrink-0">
38-
<span v-if="result.package.version" class="font-mono text-xs text-fg-subtle">
37+
<div class="flex items-center gap-1.5 shrink-0 max-w-32">
38+
<span
39+
v-if="result.package.version"
40+
class="font-mono text-xs text-fg-subtle truncate"
41+
:title="result.package.version"
42+
>
3943
v{{ result.package.version }}
4044
</span>
4145
<ProvenanceBadge

app/components/PackageDependencies.vue

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,6 @@ const sortedPeerDependencies = computed(() => {
3232
return a.name.localeCompare(b.name)
3333
})
3434
})
35-
36-
// Check if a version string is "long" (multiple alternatives)
37-
function isLongVersion(version: string): boolean {
38-
return version.length > 20 || version.includes('||')
39-
}
40-
41-
// Truncate long version strings for display
42-
function truncateVersion(version: string, maxLength = 20): string {
43-
if (version.length <= maxLength) return version
44-
return `${version.slice(0, maxLength)}…`
45-
}
4635
</script>
4736

4837
<template>
@@ -77,11 +66,10 @@ function truncateVersion(version: string, maxLength = 20): string {
7766
{{ dep }}
7867
</NuxtLink>
7968
<span
80-
class="font-mono text-xs text-fg-subtle max-w-[50%] text-right"
81-
:class="isLongVersion(version) ? 'truncate' : 'shrink-0'"
82-
:title="isLongVersion(version) ? version : undefined"
69+
class="font-mono text-xs text-fg-subtle max-w-[50%] text-right truncate"
70+
:title="version"
8371
>
84-
{{ isLongVersion(version) ? truncateVersion(version) : version }}
72+
{{ version }}
8573
</span>
8674
</li>
8775
</ul>
@@ -125,11 +113,10 @@ function truncateVersion(version: string, maxLength = 20): string {
125113
</span>
126114
</div>
127115
<span
128-
class="font-mono text-xs text-fg-subtle max-w-[40%] text-right"
129-
:class="isLongVersion(peer.version) ? 'truncate' : 'shrink-0'"
130-
:title="isLongVersion(peer.version) ? peer.version : undefined"
116+
class="font-mono text-xs text-fg-subtle max-w-[40%] text-right truncate"
117+
:title="peer.version"
131118
>
132-
{{ isLongVersion(peer.version) ? truncateVersion(peer.version) : peer.version }}
119+
{{ peer.version }}
133120
</span>
134121
</li>
135122
</ul>

app/components/PackageVersions.vue

Lines changed: 77 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ const props = defineProps<{
1616
time: Record<string, string>
1717
}>()
1818
19+
/** Maximum number of dist-tag rows to show before collapsing into "Other versions" */
20+
const MAX_VISIBLE_TAGS = 10
21+
1922
/** A version with its metadata */
2023
interface VersionDisplay {
2124
version: string
@@ -42,9 +45,9 @@ function versionRoute(version: string): RouteLocationRaw {
4245
// Version to tags lookup (supports multiple tags per version)
4346
const versionToTags = computed(() => buildVersionToTagsMap(props.distTags))
4447
45-
// Initial tag rows derived from props (SSR-safe)
48+
// All tag rows derived from props (SSR-safe)
4649
// Deduplicates so each version appears only once, with all its tags
47-
const initialTagRows = computed(() => {
50+
const allTagRows = computed(() => {
4851
// Group tags by version with their metadata
4952
const versionMap = new Map<
5053
string,
@@ -87,6 +90,12 @@ const initialTagRows = computed(() => {
8790
.sort((a, b) => compareVersions(b.primaryVersion.version, a.primaryVersion.version))
8891
})
8992
93+
// Visible tag rows (limited to MAX_VISIBLE_TAGS)
94+
const visibleTagRows = computed(() => allTagRows.value.slice(0, MAX_VISIBLE_TAGS))
95+
96+
// Hidden tag rows (overflow beyond MAX_VISIBLE_TAGS) - shown in "Other versions"
97+
const hiddenTagRows = computed(() => allTagRows.value.slice(MAX_VISIBLE_TAGS))
98+
9099
// Client-side state for expansion and loaded versions
91100
const expandedTags = ref<Set<string>>(new Set())
92101
const tagVersions = ref<Map<string, VersionDisplay[]>>(new Map())
@@ -159,7 +168,7 @@ function processLoadedVersions(allVersions: PackageVersionInfo[]) {
159168
// For each tag, find versions in its channel (same major + same prerelease channel)
160169
const claimedVersions = new Set<string>()
161170
162-
for (const row of initialTagRows.value) {
171+
for (const row of allTagRows.value) {
163172
const tagVersion = distTags[row.tag]
164173
if (!tagVersion) continue
165174
@@ -292,14 +301,14 @@ function formatDate(dateStr: string): string {
292301
</script>
293302

294303
<template>
295-
<section v-if="initialTagRows.length > 0" aria-labelledby="versions-heading">
304+
<section v-if="allTagRows.length > 0" aria-labelledby="versions-heading" class="overflow-hidden">
296305
<h2 id="versions-heading" class="text-xs text-fg-subtle uppercase tracking-wider mb-3">
297306
Versions
298307
</h2>
299308

300-
<div class="space-y-0.5">
301-
<!-- Dist-tag rows -->
302-
<div v-for="row in initialTagRows" :key="row.id">
309+
<div class="space-y-0.5 min-w-0">
310+
<!-- Dist-tag rows (limited to MAX_VISIBLE_TAGS) -->
311+
<div v-for="row in visibleTagRows" :key="row.id">
303312
<div class="flex items-center gap-2">
304313
<!-- Expand button (only if there are more versions to show) -->
305314
<button
@@ -327,6 +336,7 @@ function formatDate(dateStr: string): string {
327336
<NuxtLink
328337
:to="versionRoute(row.primaryVersion.version)"
329338
class="font-mono text-sm text-fg-muted hover:text-fg transition-colors duration-200 truncate"
339+
:title="row.primaryVersion.version"
330340
>
331341
{{ row.primaryVersion.version }}
332342
</NuxtLink>
@@ -346,11 +356,12 @@ function formatDate(dateStr: string): string {
346356
/>
347357
</div>
348358
</div>
349-
<div v-if="row.tags.length" class="flex items-center gap-1 mt-0.5">
359+
<div v-if="row.tags.length" class="flex items-center gap-1 mt-0.5 flex-wrap">
350360
<span
351361
v-for="tag in row.tags"
352362
:key="tag"
353-
class="text-[9px] font-semibold text-fg-subtle uppercase tracking-wide"
363+
class="text-[9px] font-semibold text-fg-subtle uppercase tracking-wide truncate max-w-[150px]"
364+
:title="tag"
354365
>
355366
{{ tag }}
356367
</span>
@@ -368,6 +379,7 @@ function formatDate(dateStr: string): string {
368379
<NuxtLink
369380
:to="versionRoute(v.version)"
370381
class="font-mono text-xs text-fg-subtle hover:text-fg-muted transition-colors duration-200 truncate"
382+
:title="v.version"
371383
>
372384
{{ v.version }}
373385
</NuxtLink>
@@ -390,7 +402,8 @@ function formatDate(dateStr: string): string {
390402
<span
391403
v-for="tag in filterExcludedTags(v.tags, row.tags)"
392404
:key="tag"
393-
class="text-[8px] font-semibold text-fg-subtle uppercase tracking-wide"
405+
class="text-[8px] font-semibold text-fg-subtle uppercase tracking-wide truncate max-w-[120px]"
406+
:title="tag"
394407
>
395408
{{ tag }}
396409
</span>
@@ -417,11 +430,49 @@ function formatDate(dateStr: string): string {
417430
:class="otherVersionsExpanded ? 'i-carbon-chevron-down' : 'i-carbon-chevron-right'"
418431
/>
419432
</span>
420-
<span class="text-xs text-fg-muted py-1.5"> Other versions </span>
433+
<span class="text-xs text-fg-muted py-1.5">
434+
Other versions
435+
<span v-if="hiddenTagRows.length > 0" class="text-fg-subtle">
436+
({{ hiddenTagRows.length }} more tagged)
437+
</span>
438+
</span>
421439
</button>
422440

423441
<!-- Expanded other versions -->
424442
<div v-if="otherVersionsExpanded" class="ml-4 pl-2 border-l border-border space-y-0.5">
443+
<!-- Hidden tag rows (overflow from visible tags) -->
444+
<div v-for="row in hiddenTagRows" :key="row.id" class="py-1">
445+
<div class="flex items-center justify-between gap-2">
446+
<NuxtLink
447+
:to="versionRoute(row.primaryVersion.version)"
448+
class="font-mono text-xs text-fg-muted hover:text-fg transition-colors duration-200 truncate"
449+
:title="row.primaryVersion.version"
450+
>
451+
{{ row.primaryVersion.version }}
452+
</NuxtLink>
453+
<div class="flex items-center gap-2 shrink-0">
454+
<time
455+
v-if="row.primaryVersion.time"
456+
:datetime="row.primaryVersion.time"
457+
class="text-[10px] text-fg-subtle"
458+
>
459+
{{ formatDate(row.primaryVersion.time) }}
460+
</time>
461+
</div>
462+
</div>
463+
<div v-if="row.tags.length" class="flex items-center gap-1 mt-0.5 flex-wrap">
464+
<span
465+
v-for="tag in row.tags"
466+
:key="tag"
467+
class="text-[8px] font-semibold text-fg-subtle uppercase tracking-wide truncate max-w-[120px]"
468+
:title="tag"
469+
>
470+
{{ tag }}
471+
</span>
472+
</div>
473+
</div>
474+
475+
<!-- Major version groups (untagged versions) -->
425476
<template v-if="otherMajorGroups.length > 0">
426477
<div v-for="(group, groupIndex) in otherMajorGroups" :key="group.major">
427478
<!-- Major group header -->
@@ -430,22 +481,27 @@ function formatDate(dateStr: string): string {
430481
type="button"
431482
class="w-full text-left py-1"
432483
:aria-expanded="group.expanded"
484+
:title="group.versions[0]?.version"
433485
@click="toggleMajorGroup(groupIndex)"
434486
>
435487
<div class="flex items-center gap-2">
436488
<span
437489
class="w-3 h-3 transition-transform duration-200 text-fg-subtle"
438490
:class="group.expanded ? 'i-carbon-chevron-down' : 'i-carbon-chevron-right'"
439491
/>
440-
<span class="font-mono text-xs text-fg-muted">
492+
<span class="font-mono text-xs text-fg-muted truncate">
441493
{{ group.versions[0]?.version }}
442494
</span>
443495
</div>
444-
<div v-if="group.versions[0]?.tags?.length" class="flex items-center gap-1 ml-5">
496+
<div
497+
v-if="group.versions[0]?.tags?.length"
498+
class="flex items-center gap-1 ml-5 flex-wrap"
499+
>
445500
<span
446501
v-for="tag in group.versions[0].tags"
447502
:key="tag"
448-
class="text-[8px] font-semibold text-fg-subtle uppercase tracking-wide"
503+
class="text-[8px] font-semibold text-fg-subtle uppercase tracking-wide truncate max-w-[120px]"
504+
:title="tag"
449505
>
450506
{{ tag }}
451507
</span>
@@ -458,7 +514,8 @@ function formatDate(dateStr: string): string {
458514
<NuxtLink
459515
v-if="group.versions[0]"
460516
:to="versionRoute(group.versions[0].version)"
461-
class="font-mono text-xs text-fg-muted hover:text-fg transition-colors duration-200"
517+
class="font-mono text-xs text-fg-muted hover:text-fg transition-colors duration-200 truncate"
518+
:title="group.versions[0].version"
462519
>
463520
{{ group.versions[0].version }}
464521
</NuxtLink>
@@ -481,6 +538,7 @@ function formatDate(dateStr: string): string {
481538
<NuxtLink
482539
:to="versionRoute(v.version)"
483540
class="font-mono text-xs text-fg-subtle hover:text-fg-muted transition-colors duration-200 truncate"
541+
:title="v.version"
484542
>
485543
{{ v.version }}
486544
</NuxtLink>
@@ -509,7 +567,10 @@ function formatDate(dateStr: string): string {
509567
</div>
510568
</div>
511569
</template>
512-
<div v-else-if="hasLoadedAll" class="py-1 text-xs text-fg-subtle">
570+
<div
571+
v-else-if="hasLoadedAll && hiddenTagRows.length === 0"
572+
class="py-1 text-xs text-fg-subtle"
573+
>
513574
All versions are covered by tags above
514575
</div>
515576
</div>

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

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -249,16 +249,19 @@ defineOgImageComponent('Package', {
249249
</script>
250250

251251
<template>
252-
<main class="container py-8 sm:py-12">
252+
<main class="container py-8 sm:py-12 overflow-hidden">
253253
<PackageSkeleton v-if="status === 'pending'" />
254254

255-
<article v-else-if="status === 'success' && pkg" class="animate-fade-in">
255+
<article v-else-if="status === 'success' && pkg" class="animate-fade-in min-w-0">
256256
<!-- Package header -->
257257
<header class="mb-8 pb-8 border-b border-border">
258258
<div class="mb-4">
259259
<!-- Package name and version -->
260-
<div class="flex items-center gap-3 mb-2 flex-wrap">
261-
<h1 class="font-mono text-2xl sm:text-3xl font-medium">
260+
<div class="flex items-start gap-3 mb-2 flex-wrap min-w-0">
261+
<h1
262+
class="font-mono text-2xl sm:text-3xl font-medium min-w-0 break-words"
263+
:title="pkg.name"
264+
>
262265
<NuxtLink
263266
v-if="orgName"
264267
:to="{ name: 'org', params: { org: orgName } }"
@@ -276,27 +279,27 @@ defineOgImageComponent('Package', {
276279
"
277280
:target="hasProvenance(displayVersion) ? '_blank' : undefined"
278281
:rel="hasProvenance(displayVersion) ? 'noopener noreferrer' : undefined"
279-
class="shrink-0 inline-flex items-center gap-1.5 px-3 py-1 font-mono text-sm bg-bg-muted border border-border rounded-md transition-colors duration-200"
282+
class="inline-flex items-center gap-1.5 px-3 py-1 font-mono text-sm bg-bg-muted border border-border rounded-md transition-colors duration-200 max-w-full shrink-0"
280283
:class="
281284
hasProvenance(displayVersion)
282285
? 'hover:border-border-hover cursor-pointer'
283286
: 'cursor-default'
284287
"
285-
:title="hasProvenance(displayVersion) ? 'Verified provenance' : undefined"
288+
:title="`v${displayVersion.version}`"
286289
>
287-
v{{ displayVersion.version }}
290+
<span class="truncate max-w-32 sm:max-w-48"> v{{ displayVersion.version }} </span>
288291
<span
289292
v-if="
290293
requestedVersion &&
291294
latestVersion &&
292295
displayVersion.version !== latestVersion.version
293296
"
294-
class="text-fg-subtle"
297+
class="text-fg-subtle shrink-0"
295298
>(not latest)</span
296299
>
297300
<span
298301
v-if="hasProvenance(displayVersion)"
299-
class="i-solar-shield-check-outline w-4 h-4 text-fg-muted"
302+
class="i-solar-shield-check-outline w-4 h-4 text-fg-muted shrink-0"
300303
aria-label="Verified provenance"
301304
/>
302305
</a>
@@ -562,7 +565,7 @@ defineOgImageComponent('Package', {
562565
</div>
563566

564567
<!-- Sidebar -->
565-
<aside class="order-1 lg:order-2 space-y-8">
568+
<aside class="order-1 lg:order-2 space-y-8 min-w-0 overflow-hidden">
566569
<!-- Maintainers (with admin actions when connected) -->
567570
<PackageMaintainers :package-name="pkg.name" :maintainers="pkg.maintainers" />
568571

@@ -603,16 +606,19 @@ defineOgImageComponent('Package', {
603606
<dl class="space-y-2">
604607
<div
605608
v-if="displayVersion.engines.node"
606-
class="flex items-center justify-between py-1"
609+
class="flex items-center justify-between gap-4 py-1"
607610
>
608-
<dt class="text-fg-muted text-sm">node</dt>
609-
<dd class="font-mono text-sm text-fg">
611+
<dt class="text-fg-muted text-sm shrink-0">node</dt>
612+
<dd class="font-mono text-sm text-fg truncate" :title="displayVersion.engines.node">
610613
{{ displayVersion.engines.node }}
611614
</dd>
612615
</div>
613-
<div v-if="displayVersion.engines.npm" class="flex items-center justify-between py-1">
614-
<dt class="text-fg-muted text-sm">npm</dt>
615-
<dd class="font-mono text-sm text-fg">
616+
<div
617+
v-if="displayVersion.engines.npm"
618+
class="flex items-center justify-between gap-4 py-1"
619+
>
620+
<dt class="text-fg-muted text-sm shrink-0">npm</dt>
621+
<dd class="font-mono text-sm text-fg truncate" :title="displayVersion.engines.npm">
616622
{{ displayVersion.engines.npm }}
617623
</dd>
618624
</div>

0 commit comments

Comments
 (0)