@@ -13,6 +13,9 @@ definePageMeta({
1313 name: ' package-versions' ,
1414})
1515
16+ /** Number of flat items (headers + version rows) to render statically during SSR */
17+ const SSR_COUNT = 20
18+
1619const route = useRoute ()
1720const router = useRouter ()
1821
@@ -334,7 +337,7 @@ watch(jumpVersion, () => {
334337 </section >
335338
336339 <!-- ── Version History ───────────────────────────────────────────────── -->
337- <section >
340+ <section v-if = " versionGroups.length > 0 " >
338341 <h2 class =" text-xs text-fg-subtle uppercase tracking-wider mb-3 px-4 sm:px-6 ps-1" >
339342 Version History
340343 <span class =" ms-1 normal-case font-normal tracking-normal" >
@@ -348,147 +351,184 @@ watch(jumpVersion, () => {
348351 <div
349352 class =" flex-1 min-w-0 border-y sm:border border-border sm:rounded-lg sm:overflow-hidden"
350353 >
351- <WindowVirtualizer :data =" flatItems" >
352- <template #default =" { item , index } " >
353- <!-- ── Group header ── -->
354- <button
355- v-if =" item.type === 'header'"
356- type =" button"
357- class =" flex items-center gap-3 px-4 py-2.5 w-full text-start hover:bg-bg-subtle transition-colors"
358- :class =" index < flatItems.length - 1 ? 'border-b border-border' : ''"
359- :aria-expanded =" expandedGroups.has(item.groupKey)"
360- :aria-label =" `${expandedGroups.has(item.groupKey) ? 'Collapse' : 'Expand'} ${item.label}`"
361- @click =" toggleGroup(item.groupKey)"
362- >
363- <span class =" w-4 h-4 flex items-center justify-center text-fg-subtle shrink-0" >
364- <span
365- v-if =" loadingGroup === item.groupKey"
366- class =" i-svg-spinners:ring-resize w-3 h-3"
367- aria-hidden =" true"
368- />
369- <span
370- v-else
371- class =" i-lucide:chevron-right w-3 h-3 transition-transform duration-200 rtl-flip"
372- :class =" expandedGroups.has(item.groupKey) ? 'rotate-90' : ''"
373- aria-hidden =" true"
374- />
375- </span >
376- <span class =" font-mono text-sm font-medium" >{{ item.label }}</span >
377- <span class =" text-xs text-fg-subtle" >({{ item.versions.length }})</span >
378- <span class =" ms-auto flex items-center gap-3 shrink-0" >
379- <span class =" font-mono text-xs text-fg-muted" dir =" ltr" >{{
380- item.versions[0]
381- }}</span >
382- <DateTime
383- v-if =" getVersionTime(item.versions[0])"
384- :datetime =" getVersionTime(item.versions[0])!"
385- class =" text-xs text-fg-subtle hidden sm:block"
386- year =" numeric"
387- month =" short"
388- day =" numeric"
389- />
390- </span >
391- </button >
354+ <ClientOnly >
355+ <WindowVirtualizer :data =" flatItems" >
356+ <template #default =" { item , index } " >
357+ <!-- ── Group header ── -->
358+ <button
359+ v-if =" item.type === 'header'"
360+ type =" button"
361+ class =" flex items-center gap-3 px-4 py-2.5 w-full text-start hover:bg-bg-subtle transition-colors"
362+ :class =" index < flatItems.length - 1 ? 'border-b border-border' : ''"
363+ :aria-expanded =" expandedGroups.has(item.groupKey)"
364+ :aria-label =" `${expandedGroups.has(item.groupKey) ? 'Collapse' : 'Expand'} ${item.label}`"
365+ @click =" toggleGroup(item.groupKey)"
366+ >
367+ <span class =" w-4 h-4 flex items-center justify-center text-fg-subtle shrink-0" >
368+ <span
369+ v-if =" loadingGroup === item.groupKey"
370+ class =" i-svg-spinners:ring-resize w-3 h-3"
371+ aria-hidden =" true"
372+ />
373+ <span
374+ v-else
375+ class =" i-lucide:chevron-right w-3 h-3 transition-transform duration-200 rtl-flip"
376+ :class =" expandedGroups.has(item.groupKey) ? 'rotate-90' : ''"
377+ aria-hidden =" true"
378+ />
379+ </span >
380+ <span class =" font-mono text-sm font-medium" >{{ item.label }}</span >
381+ <span class =" text-xs text-fg-subtle" >({{ item.versions.length }})</span >
382+ <span class =" ms-auto flex items-center gap-3 shrink-0" >
383+ <span class =" font-mono text-xs text-fg-muted" dir =" ltr" >{{
384+ item.versions[0]
385+ }}</span >
386+ <DateTime
387+ v-if =" getVersionTime(item.versions[0])"
388+ :datetime =" getVersionTime(item.versions[0])!"
389+ class =" text-xs text-fg-subtle hidden sm:block"
390+ year =" numeric"
391+ month =" short"
392+ day =" numeric"
393+ />
394+ </span >
395+ </button >
392396
393- <!-- ── Version row ── -->
394- <div
395- v-else
396- class =" transition-colors"
397- :class =" [
398- index < flatItems.length - 1 ? 'border-b border-border' : '',
399- selectedChangelogVersion === item.version ? 'bg-bg-subtle' : '',
400- ]"
401- >
397+ <!-- ── Version row ── -->
402398 <div
403- class =" flex items-center gap-3 px-4 ps-11 py-2.5 group relative"
404- :class =" selectedChangelogVersion === item.version ? '' : 'hover:bg-bg-subtle'"
399+ v-else
400+ class =" transition-colors"
401+ :class =" [
402+ index < flatItems.length - 1 ? 'border-b border-border' : '',
403+ selectedChangelogVersion === item.version ? 'bg-bg-subtle' : '',
404+ ]"
405405 >
406- <!-- Version + badges -->
407- <div class =" flex-1 min-w-0 flex items-center gap-2 flex-wrap" >
408- <LinkBase
409- :to =" packageRoute(packageName, item.version)"
410- :prefetch =" false"
411- class =" font-mono text-sm after:absolute after:inset-0 after:content-['']"
412- :class ="
413- fullVersionMap?.get(item.version)?.deprecated
414- ? 'text-red-700 dark:text-red-400'
415- : ''
416- "
417- :classicon ="
418- fullVersionMap?.get(item.version)?.deprecated
419- ? 'i-lucide:octagon-alert'
420- : undefined
421- "
422- dir =" ltr"
423- >
424- {{ item.version }}
425- </LinkBase >
426- <div
427- v-if =" versionToTagsMap.get(item.version)?.length"
428- class =" flex items-center gap-1 flex-wrap relative z-10"
429- >
406+ <div
407+ class =" flex items-center gap-3 px-4 ps-11 py-2.5 group relative"
408+ :class =" selectedChangelogVersion === item.version ? '' : 'hover:bg-bg-subtle'"
409+ >
410+ <!-- Version + badges -->
411+ <div class =" flex-1 min-w-0 flex items-center gap-2 flex-wrap" >
412+ <LinkBase
413+ :to =" packageRoute(packageName, item.version)"
414+ :prefetch =" false"
415+ class =" font-mono text-sm after:absolute after:inset-0 after:content-['']"
416+ :class ="
417+ fullVersionMap?.get(item.version)?.deprecated
418+ ? 'text-red-700 dark:text-red-400'
419+ : ''
420+ "
421+ :classicon ="
422+ fullVersionMap?.get(item.version)?.deprecated
423+ ? 'i-lucide:octagon-alert'
424+ : undefined
425+ "
426+ dir =" ltr"
427+ >
428+ {{ item.version }}
429+ </LinkBase >
430+ <div
431+ v-if =" versionToTagsMap.get(item.version)?.length"
432+ class =" flex items-center gap-1 flex-wrap relative z-10"
433+ >
434+ <span
435+ v-for =" tag in versionToTagsMap.get(item.version)"
436+ :key =" tag"
437+ class =" text-4xs font-semibold uppercase tracking-wide"
438+ :class =" tag === 'latest' ? 'text-accent' : 'text-fg-subtle'"
439+ >
440+ {{ tag }}
441+ </span >
442+ </div >
430443 <span
431- v-for =" tag in versionToTagsMap.get(item.version)"
432- :key =" tag"
433- class =" text-4xs font-semibold uppercase tracking-wide"
434- :class =" tag === 'latest' ? 'text-accent' : 'text-fg-subtle'"
444+ v-if =" fullVersionMap?.get(item.version)?.deprecated"
445+ class =" text-3xs font-medium text-red-700 dark:text-red-400 bg-red-100 dark:bg-red-900/30 px-1.5 py-0.5 rounded relative z-10"
446+ :title =" fullVersionMap.get(item.version)!.deprecated"
435447 >
436- {{ tag }}
448+ deprecated
437449 </span >
438450 </div >
439- <span
440- v-if =" fullVersionMap?.get(item.version)?.deprecated"
441- class =" text-3xs font-medium text-red-700 dark:text-red-400 bg-red-100 dark:bg-red-900/30 px-1.5 py-0.5 rounded relative z-10"
442- :title =" fullVersionMap.get(item.version)!.deprecated"
443- >
444- deprecated
445- </span >
451+
452+ <!-- Right side -->
453+ <div class =" flex items-center gap-2 shrink-0 relative z-10" >
454+ <!-- TODO(atriiy): changelog would be implemented later -->
455+
456+ <!-- Metadata: date + provenance -->
457+ <DateTime
458+ v-if =" getVersionTime(item.version)"
459+ :datetime =" getVersionTime(item.version)!"
460+ class =" text-xs text-fg-subtle hidden sm:block"
461+ year =" numeric"
462+ month =" short"
463+ day =" numeric"
464+ />
465+ <ProvenanceBadge
466+ v-if =" fullVersionMap?.get(item.version)?.hasProvenance"
467+ :package-name =" packageName"
468+ :version =" item.version"
469+ compact
470+ :linked =" false"
471+ />
472+ </div >
446473 </div >
447474
448- <!-- Right side -->
449- <div class =" flex items-center gap-2 shrink-0 relative z-10" >
450- <!-- TODO(atriiy): changelog would be implemented later -->
475+ <!-- Mobile inline changelog (below the row, sm and up uses side panel) -->
476+ <div
477+ v-if =" item.version in mockChangelogs"
478+ class =" grid sm:hidden transition-[grid-template-rows] duration-200 ease-out motion-reduce:transition-none"
479+ :class ="
480+ selectedChangelogVersion === item.version
481+ ? 'grid-rows-[1fr]'
482+ : 'grid-rows-[0fr]'
483+ "
484+ >
485+ <div class =" overflow-hidden" >
486+ <div class =" changelog-body border-t border-border px-4 py-3 text-sm" >
487+ {{
488+ selectedChangelogVersion === item.version
489+ ? selectedChangelogContent
490+ : ''
491+ }}
492+ </div >
493+ </div >
494+ </div >
495+ </div >
496+ </template >
497+ </WindowVirtualizer >
451498
452- <!-- Metadata: date + provenance -->
499+ <!-- SSR fallback: static list of first group headers -->
500+ <template #fallback >
501+ <div >
502+ <button
503+ v-for =" item in versionGroups.slice(0, SSR_COUNT)"
504+ :key =" item.groupKey"
505+ type =" button"
506+ class =" flex items-center gap-3 px-4 py-2.5 w-full text-start border-b border-border"
507+ :aria-expanded =" false"
508+ :aria-label =" `Expand ${item.label}`"
509+ >
510+ <span class =" w-4 h-4 flex items-center justify-center text-fg-subtle shrink-0" >
511+ <span class =" i-lucide:chevron-right w-3 h-3 rtl-flip" aria-hidden =" true" />
512+ </span >
513+ <span class =" font-mono text-sm font-medium" >{{ item.label }}</span >
514+ <span class =" text-xs text-fg-subtle" >({{ item.versions.length }})</span >
515+ <span class =" ms-auto flex items-center gap-3 shrink-0" >
516+ <span class =" font-mono text-xs text-fg-muted" dir =" ltr" >{{
517+ item.versions[0]
518+ }}</span >
453519 <DateTime
454- v-if =" getVersionTime(item.version )"
455- :datetime =" getVersionTime(item.version )!"
520+ v-if =" getVersionTime(item.versions[0] ?? '' )"
521+ :datetime =" getVersionTime(item.versions[0] ?? '' )!"
456522 class =" text-xs text-fg-subtle hidden sm:block"
457523 year =" numeric"
458524 month =" short"
459525 day =" numeric"
460526 />
461- <ProvenanceBadge
462- v-if =" fullVersionMap?.get(item.version)?.hasProvenance"
463- :package-name =" packageName"
464- :version =" item.version"
465- compact
466- :linked =" false"
467- />
468- </div >
469- </div >
470-
471- <!-- Mobile inline changelog (below the row, sm and up uses side panel) -->
472- <div
473- v-if =" item.version in mockChangelogs"
474- class =" grid sm:hidden transition-[grid-template-rows] duration-200 ease-out motion-reduce:transition-none"
475- :class ="
476- selectedChangelogVersion === item.version
477- ? 'grid-rows-[1fr]'
478- : 'grid-rows-[0fr]'
479- "
480- >
481- <div class =" overflow-hidden" >
482- <div class =" changelog-body border-t border-border px-4 py-3 text-sm" >
483- {{
484- selectedChangelogVersion === item.version ? selectedChangelogContent : ''
485- }}
486- </div >
487- </div >
488- </div >
527+ </span >
528+ </button >
489529 </div >
490530 </template >
491- </WindowVirtualizer >
531+ </ClientOnly >
492532 </div >
493533
494534 <!-- Changelog side panel (desktop only) -->
0 commit comments