Skip to content

Commit bdffaf3

Browse files
committed
feat: fallback for ssr
1 parent 0361273 commit bdffaf3

1 file changed

Lines changed: 164 additions & 124 deletions

File tree

app/pages/package/[[org]]/[name]/versions.vue

Lines changed: 164 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
1619
const route = useRoute()
1720
const 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

Comments
 (0)