[READ-ONLY] a fast, modern browser for the npm registry
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: pin package header ๐Ÿ“Œ (#566)

authored by

Thomas Deinhamer and committed by
GitHub
3ae83752 5e933536

+167 -136
+167 -136
app/pages/[...package].vue
··· 19 19 20 20 const router = useRouter() 21 21 22 + const header = useTemplateRef('header') 23 + const isHeaderPinned = shallowRef(false) 24 + 25 + function checkHeaderPosition() { 26 + const el = header.value 27 + if (!el) return 28 + 29 + const style = getComputedStyle(el) 30 + const top = parseFloat(style.top) || 0 31 + const rect = el.getBoundingClientRect() 32 + 33 + isHeaderPinned.value = Math.abs(rect.top - top) < 1 34 + } 35 + 36 + useEventListener('scroll', checkHeaderPosition, { passive: true }) 37 + useEventListener('resize', checkHeaderPosition) 38 + 39 + onMounted(() => { 40 + checkHeaderPosition() 41 + }) 42 + 22 43 const { packageName, requestedVersion, orgName } = usePackageRoute() 23 44 const selectedPM = useSelectedPackageManager() 24 45 const activePmId = computed(() => selectedPM.value ?? 'npm') ··· 389 410 </script> 390 411 391 412 <template> 392 - <main class="container flex-1 w-full py-8 xl:py-12"> 413 + <main class="container flex-1 w-full py-8"> 393 414 <PackageSkeleton v-if="status === 'pending'" /> 394 415 395 416 <article v-else-if="status === 'success' && pkg" class="package-page"> 396 417 <!-- Package header --> 397 - <header class="area-header border-b border-border"> 398 - <div class="mb-4"> 399 - <!-- Package name and version --> 400 - <div class="flex items-baseline gap-2 mb-1.5 sm:gap-3 sm:mb-2 flex-wrap min-w-0"> 401 - <h1 402 - class="font-mono text-2xl sm:text-3xl font-medium min-w-0 break-words" 403 - :title="pkg.name" 418 + <header 419 + class="area-header sticky top-14 z-1 bg-[--bg] py-2 border-border" 420 + ref="header" 421 + :class="{ 'border-b': isHeaderPinned }" 422 + > 423 + <!-- Package name and version --> 424 + <div class="flex items-baseline gap-2 sm:gap-3 flex-wrap min-w-0"> 425 + <h1 426 + class="font-mono text-2xl sm:text-3xl font-medium min-w-0 break-words" 427 + :title="pkg.name" 428 + > 429 + <NuxtLink 430 + v-if="orgName" 431 + :to="{ name: 'org', params: { org: orgName } }" 432 + class="text-fg-muted hover:text-fg transition-colors duration-200" 433 + >@{{ orgName }}</NuxtLink 434 + ><span v-if="orgName">/</span> 435 + <TooltipAnnounce :text="$t('common.copied')" :isVisible="copiedPkgName"> 436 + <button 437 + @click="copyPkgName()" 438 + aria-describedby="copy-pkg-name" 439 + class="cursor-copy active:scale-95 transition-transform" 440 + > 441 + {{ orgName ? pkg.name.replace(`@${orgName}/`, '') : pkg.name }} 442 + </button> 443 + </TooltipAnnounce> 444 + </h1> 445 + 446 + <span id="copy-pkg-name" class="sr-only">{{ $t('package.copy_name') }}</span> 447 + <span 448 + v-if="displayVersion" 449 + class="inline-flex items-baseline gap-1.5 font-mono text-base sm:text-lg text-fg-muted shrink-0" 450 + > 451 + <!-- Version resolution indicator (e.g., "latest โ†’ 4.2.0") --> 452 + <template v-if="resolvedVersion !== requestedVersion"> 453 + <span class="font-mono text-fg-muted text-sm">{{ requestedVersion }}</span> 454 + <span class="i-carbon:arrow-right rtl-flip w-3 h-3" aria-hidden="true" /> 455 + </template> 456 + 457 + <NuxtLink 458 + v-if="resolvedVersion !== requestedVersion" 459 + :to="`/${pkg.name}/v/${displayVersion.version}`" 460 + :title="$t('package.view_permalink')" 461 + >{{ displayVersion.version }}</NuxtLink 404 462 > 405 - <NuxtLink 406 - v-if="orgName" 407 - :to="{ name: 'org', params: { org: orgName } }" 408 - class="text-fg-muted hover:text-fg transition-colors duration-200" 409 - >@{{ orgName }}</NuxtLink 410 - ><span v-if="orgName">/</span> 411 - <TooltipAnnounce :text="$t('common.copied')" :isVisible="copiedPkgName"> 412 - <button 413 - @click="copyPkgName()" 414 - aria-describedby="copy-pkg-name" 415 - class="cursor-copy ms-1 mt-1 active:scale-95 transition-transform" 416 - > 417 - {{ orgName ? pkg.name.replace(`@${orgName}/`, '') : pkg.name }} 418 - </button> 419 - </TooltipAnnounce> 420 - </h1> 463 + <span v-else>v{{ displayVersion.version }}</span> 421 464 422 - <span id="copy-pkg-name" class="sr-only">{{ $t('package.copy_name') }}</span> 465 + <a 466 + v-if="hasProvenance(displayVersion)" 467 + :href="`https://www.npmjs.com/package/${pkg.name}/v/${displayVersion.version}#provenance`" 468 + target="_blank" 469 + rel="noopener noreferrer" 470 + class="inline-flex items-center justify-center gap-1.5 text-fg-muted hover:text-fg transition-colors duration-200 min-w-6 min-h-6" 471 + :title="$t('package.verified_provenance')" 472 + > 473 + <span class="i-solar:shield-check-outline w-3.5 h-3.5 shrink-0" aria-hidden="true" /> 474 + </a> 423 475 <span 424 - v-if="displayVersion" 425 - class="inline-flex items-baseline gap-1.5 font-mono text-base sm:text-lg text-fg-muted shrink-0" 476 + v-if=" 477 + requestedVersion && 478 + latestVersion && 479 + displayVersion.version !== latestVersion.version 480 + " 481 + class="text-fg-subtle text-sm shrink-0" 482 + >{{ $t('package.not_latest') }}</span 426 483 > 427 - <!-- Version resolution indicator (e.g., "latest โ†’ 4.2.0") --> 428 - <template v-if="resolvedVersion !== requestedVersion"> 429 - <span class="font-mono text-fg-muted text-sm">{{ requestedVersion }}</span> 430 - <span class="i-carbon:arrow-right rtl-flip w-3 h-3" aria-hidden="true" /> 431 - </template> 484 + </span> 432 485 433 - <NuxtLink 434 - v-if="resolvedVersion !== requestedVersion" 435 - :to="`/${pkg.name}/v/${displayVersion.version}`" 436 - :title="$t('package.view_permalink')" 437 - >{{ displayVersion.version }}</NuxtLink 438 - > 439 - <span v-else>v{{ displayVersion.version }}</span> 486 + <!-- Package metrics (module format, types) --> 487 + <ClientOnly> 488 + <PackageMetricsBadges 489 + v-if="displayVersion" 490 + :package-name="pkg.name" 491 + :version="displayVersion.version" 492 + :is-binary="isBinaryOnly" 493 + class="self-baseline ms-1 sm:ms-2" 494 + /> 495 + <template #fallback> 496 + <ul class="flex items-center gap-1.5 self-baseline ms-1 sm:ms-2"> 497 + <li class="skeleton w-8 h-5 rounded" /> 498 + <li class="skeleton w-12 h-5 rounded" /> 499 + </ul> 500 + </template> 501 + </ClientOnly> 440 502 441 - <a 442 - v-if="hasProvenance(displayVersion)" 443 - :href="`https://www.npmjs.com/package/${pkg.name}/v/${displayVersion.version}#provenance`" 444 - target="_blank" 445 - rel="noopener noreferrer" 446 - class="inline-flex items-center justify-center gap-1.5 text-fg-muted hover:text-fg transition-colors duration-200 min-w-6 min-h-6" 447 - :title="$t('package.verified_provenance')" 503 + <!-- Internal navigation: Docs + Code + Compare (hidden on mobile, shown in external links instead) --> 504 + <nav 505 + v-if="displayVersion" 506 + :aria-label="$t('package.navigation')" 507 + class="hidden sm:flex items-center gap-0.5 p-0.5 bg-bg-subtle border border-border-subtle rounded-md shrink-0 ms-auto self-center" 508 + > 509 + <NuxtLink 510 + v-if="docsLink" 511 + :to="docsLink" 512 + class="px-2 py-1.5 font-mono text-xs rounded transition-colors duration-150 border border-transparent text-fg-subtle hover:text-fg hover:bg-bg hover:shadow hover:border-border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 inline-flex items-center gap-1.5" 513 + aria-keyshortcuts="d" 514 + > 515 + <span class="i-carbon:document w-3 h-3" aria-hidden="true" /> 516 + {{ $t('package.links.docs') }} 517 + <kbd 518 + class="inline-flex items-center justify-center w-4 h-4 text-xs bg-bg-muted border border-border rounded" 519 + aria-hidden="true" 448 520 > 449 - <span 450 - class="i-solar:shield-check-outline w-3.5 h-3.5 shrink-0" 451 - aria-hidden="true" 452 - /> 453 - </a> 454 - <span 455 - v-if=" 456 - requestedVersion && 457 - latestVersion && 458 - displayVersion.version !== latestVersion.version 459 - " 460 - class="text-fg-subtle text-sm shrink-0" 461 - >{{ $t('package.not_latest') }}</span 521 + d 522 + </kbd> 523 + </NuxtLink> 524 + <NuxtLink 525 + :to="{ 526 + name: 'code', 527 + params: { 528 + path: [...pkg.name.split('/'), 'v', displayVersion.version], 529 + }, 530 + }" 531 + class="px-2 py-1.5 font-mono text-xs rounded transition-colors duration-150 border border-transparent text-fg-subtle hover:text-fg hover:bg-bg hover:shadow hover:border-border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 inline-flex items-center gap-1.5" 532 + aria-keyshortcuts="." 533 + > 534 + <span class="i-carbon:code w-3 h-3" aria-hidden="true" /> 535 + {{ $t('package.links.code') }} 536 + <kbd 537 + class="inline-flex items-center justify-center w-4 h-4 text-xs bg-bg-muted border border-border rounded" 538 + aria-hidden="true" 462 539 > 463 - </span> 464 - 465 - <!-- Package metrics (module format, types) --> 466 - <ClientOnly> 467 - <PackageMetricsBadges 468 - v-if="displayVersion" 469 - :package-name="pkg.name" 470 - :version="displayVersion.version" 471 - :is-binary="isBinaryOnly" 472 - class="self-baseline ms-1 sm:ms-2" 473 - /> 474 - <template #fallback> 475 - <ul class="flex items-center gap-1.5 self-baseline ms-1 sm:ms-2"> 476 - <li class="skeleton w-8 h-5 rounded" /> 477 - <li class="skeleton w-12 h-5 rounded" /> 478 - </ul> 479 - </template> 480 - </ClientOnly> 481 - 482 - <!-- Internal navigation: Docs + Code + Compare (hidden on mobile, shown in external links instead) --> 483 - <nav 484 - v-if="displayVersion" 485 - :aria-label="$t('package.navigation')" 486 - class="hidden sm:flex items-center gap-0.5 p-0.5 bg-bg-subtle border border-border-subtle rounded-md shrink-0 ms-auto self-center" 540 + . 541 + </kbd> 542 + </NuxtLink> 543 + <NuxtLink 544 + :to="{ path: '/compare', query: { packages: pkg.name } }" 545 + class="px-2 py-1.5 font-mono text-xs rounded transition-colors duration-150 border border-transparent text-fg-subtle hover:text-fg hover:bg-bg hover:shadow hover:border-border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 inline-flex items-center gap-1.5" 546 + aria-keyshortcuts="c" 487 547 > 488 - <NuxtLink 489 - v-if="docsLink" 490 - :to="docsLink" 491 - class="px-2 py-1.5 font-mono text-xs rounded transition-colors duration-150 border border-transparent text-fg-subtle hover:text-fg hover:bg-bg hover:shadow hover:border-border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 inline-flex items-center gap-1.5" 492 - aria-keyshortcuts="d" 493 - > 494 - <span class="i-carbon:document w-3 h-3" aria-hidden="true" /> 495 - {{ $t('package.links.docs') }} 496 - <kbd 497 - class="inline-flex items-center justify-center w-4 h-4 text-xs bg-bg-muted border border-border rounded" 498 - aria-hidden="true" 499 - > 500 - d 501 - </kbd> 502 - </NuxtLink> 503 - <NuxtLink 504 - :to="{ 505 - name: 'code', 506 - params: { 507 - path: [...pkg.name.split('/'), 'v', displayVersion.version], 508 - }, 509 - }" 510 - class="px-2 py-1.5 font-mono text-xs rounded transition-colors duration-150 border border-transparent text-fg-subtle hover:text-fg hover:bg-bg hover:shadow hover:border-border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 inline-flex items-center gap-1.5" 511 - aria-keyshortcuts="." 548 + <span class="i-carbon:compare w-3 h-3" aria-hidden="true" /> 549 + {{ $t('package.links.compare') }} 550 + <kbd 551 + class="inline-flex items-center justify-center w-4 h-4 text-xs bg-bg-muted border border-border rounded" 552 + aria-hidden="true" 512 553 > 513 - <span class="i-carbon:code w-3 h-3" aria-hidden="true" /> 514 - {{ $t('package.links.code') }} 515 - <kbd 516 - class="inline-flex items-center justify-center w-4 h-4 text-xs bg-bg-muted border border-border rounded" 517 - aria-hidden="true" 518 - > 519 - . 520 - </kbd> 521 - </NuxtLink> 522 - <NuxtLink 523 - :to="{ path: '/compare', query: { packages: pkg.name } }" 524 - class="px-2 py-1.5 font-mono text-xs rounded transition-colors duration-150 border border-transparent text-fg-subtle hover:text-fg hover:bg-bg hover:shadow hover:border-border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 inline-flex items-center gap-1.5" 525 - aria-keyshortcuts="c" 526 - > 527 - <span class="i-carbon:compare w-3 h-3" aria-hidden="true" /> 528 - {{ $t('package.links.compare') }} 529 - <kbd 530 - class="inline-flex items-center justify-center w-4 h-4 text-xs bg-bg-muted border border-border rounded" 531 - aria-hidden="true" 532 - > 533 - c 534 - </kbd> 535 - </NuxtLink> 536 - </nav> 537 - </div> 554 + c 555 + </kbd> 556 + </NuxtLink> 557 + </nav> 558 + </div> 559 + </header> 538 560 561 + <!-- Package details --> 562 + <section class="area-details"> 563 + <div class="mb-4"> 539 564 <!-- Description container with min-height to prevent CLS --> 540 565 <div class="max-w-2xl min-h-[4.5rem]"> 541 566 <p v-if="pkg.description" class="text-fg-muted text-base m-0"> ··· 698 723 699 724 <!-- Stats grid --> 700 725 <dl 701 - class="grid grid-cols-2 sm:grid-cols-11 gap-3 sm:gap-4 py-4 sm:py-6 mt-4 sm:mt-6 border-t border-border" 726 + class="grid grid-cols-2 sm:grid-cols-11 gap-3 sm:gap-4 py-4 sm:py-6 mt-4 sm:mt-6 border-t border-b border-border" 702 727 > 703 728 <div v-if="pkg.license" class="space-y-1 sm:col-span-2"> 704 729 <dt class="text-xs text-fg-subtle uppercase tracking-wider"> ··· 859 884 :version="displayVersion?.version" 860 885 /> 861 886 </ClientOnly> 862 - </header> 887 + </section> 863 888 864 889 <!-- Binary-only packages: Show only execute command (no install) --> 865 890 <section v-if="isBinaryOnly" class="area-install scroll-mt-20"> ··· 965 990 966 991 <div class="area-sidebar"> 967 992 <!-- Sidebar --> 968 - <div class="sticky top-20 space-y-6 sm:space-y-8 min-w-0 overflow-hidden"> 993 + <div class="sticky top-34 space-y-6 sm:space-y-8 min-w-0 overflow-hidden xl:(top-22 pt-2)"> 969 994 <!-- Maintainers (with admin actions when connected) --> 970 995 <PackageMaintainers :package-name="pkg.name" :maintainers="pkg.maintainers" /> 971 996 ··· 1113 1138 grid-template-columns: minmax(0, 1fr); 1114 1139 grid-template-areas: 1115 1140 'header' 1141 + 'details' 1116 1142 'install' 1117 1143 'vulns' 1118 1144 'sidebar' ··· 1125 1151 grid-template-columns: 2fr 1fr; 1126 1152 grid-template-areas: 1127 1153 'header header' 1154 + 'details details' 1128 1155 'install install' 1129 1156 'vulns vulns' 1130 1157 'readme sidebar'; ··· 1138 1165 grid-template-columns: 1fr 20rem; 1139 1166 grid-template-areas: 1140 1167 'header sidebar' 1168 + 'details sidebar' 1141 1169 'install sidebar' 1142 1170 'vulns sidebar' 1143 1171 'readme sidebar'; ··· 1146 1174 1147 1175 .area-header { 1148 1176 grid-area: header; 1149 - overflow-x: hidden; 1177 + } 1178 + 1179 + .area-details { 1180 + grid-area: details; 1150 1181 } 1151 1182 1152 1183 .area-install {