forked from
npmx.dev/npmx.dev
[READ-ONLY]
a fast, modern browser for the npm registry
1<script setup lang="ts"></script>
2
3<template>
4 <article
5 aria-busy="true"
6 :aria-label="$t('package.skeleton.loading')"
7 class="package-page motion-safe:animate-fade-in"
8 >
9 <!-- Package header — matches area-header in [...name].vue -->
10 <header class="area-header sticky top-14 z-1 bg-[--bg] py-2">
11 <div class="flex items-baseline gap-x-2 gap-y-1 sm:gap-x-3 flex-wrap min-w-0">
12 <!-- Package name -->
13 <div class="min-w-0">
14 <h1 class="font-mono text-2xl sm:text-3xl font-medium">
15 <SkeletonInline class="h-9 w-48" />
16 </h1>
17 </div>
18 <!-- Version -->
19 <span class="inline-flex items-baseline font-mono text-base sm:text-lg shrink-0">
20 <SkeletonInline class="h-6 w-20" />
21 </span>
22
23 <!-- Docs + Code + Compare nav placeholder (hidden on mobile) -->
24 <div
25 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"
26 >
27 <SkeletonInline class="h-7 w-22 rounded" />
28 <SkeletonInline class="h-7 w-20 rounded" />
29 <SkeletonInline class="h-7 w-26 rounded" />
30 </div>
31
32 <!-- Metrics badges + likes — matches basis-full flex row -->
33 <div class="basis-full flex gap-2 sm:gap-3 flex-wrap items-stretch">
34 <!-- Badge row (Types, ESM, CJS, CJS-ghost) -->
35 <div class="flex items-center gap-1.5 self-baseline">
36 <SkeletonBlock class="w-16 h-5.5 rounded" />
37 <SkeletonBlock class="w-13 h-5.5 rounded" />
38 <SkeletonBlock class="w-13 h-5.5 rounded" />
39 <SkeletonBlock class="w-13 h-5.5 rounded bg-bg-subtle" />
40 </div>
41 <!-- Likes button placeholder -->
42 <SkeletonBlock class="w-14 h-5.5 rounded self-baseline" />
43 </div>
44 </div>
45 </header>
46
47 <!-- Package details — matches area-details in [...name].vue -->
48 <section class="area-details">
49 <div class="mb-4">
50 <!-- Description -->
51 <div class="max-w-2xl">
52 <div class="space-y-2">
53 <SkeletonBlock class="h-5 w-full" />
54 <SkeletonBlock class="h-5 w-4/5" />
55 <SkeletonBlock class="h-5 w-3/5" />
56 </div>
57 </div>
58
59 <!-- External links -->
60 <ul
61 class="flex flex-wrap items-center gap-x-3 gap-y-1.5 sm:gap-4 list-none m-0 p-0 mt-3 text-sm"
62 >
63 <li><SkeletonInline class="h-5 w-28" /></li>
64 <li><SkeletonInline class="h-5 w-14" /></li>
65 <li><SkeletonInline class="h-5 w-16" /></li>
66 <li><SkeletonInline class="h-5 w-10" /></li>
67 </ul>
68 </div>
69
70 <!-- Stats grid — matches dl in [...name].vue -->
71 <dl
72 class="grid grid-cols-2 sm:grid-cols-7 md:grid-cols-11 gap-3 sm:gap-4 py-4 sm:py-6 mt-4 sm:mt-6 border-t border-b border-border"
73 >
74 <!-- License -->
75 <div class="space-y-1 sm:col-span-2">
76 <dt class="text-xs text-fg-subtle uppercase tracking-wider">
77 {{ $t('package.stats.license') }}
78 </dt>
79 <dd class="font-mono text-sm">
80 <SkeletonInline class="h-5 w-12" />
81 </dd>
82 </div>
83
84 <!-- Deps -->
85 <div class="space-y-1 sm:col-span-2">
86 <dt class="text-xs text-fg-subtle uppercase tracking-wider">
87 {{ $t('package.stats.deps') }}
88 </dt>
89 <dd class="font-mono text-sm">
90 <SkeletonInline class="h-5 w-12" />
91 </dd>
92 </div>
93
94 <!-- Install Size -->
95 <div class="space-y-1 sm:col-span-3">
96 <dt class="text-xs text-fg-subtle uppercase tracking-wider">
97 {{ $t('package.stats.install_size') }}
98 </dt>
99 <dd class="font-mono text-sm">
100 <SkeletonInline class="h-5 w-16" />
101 </dd>
102 </div>
103
104 <!-- Vulns -->
105 <div class="space-y-1 sm:col-span-2">
106 <dt class="text-xs text-fg-subtle uppercase tracking-wider">
107 {{ $t('package.stats.vulns') }}
108 </dt>
109 <dd class="font-mono text-sm text-fg-subtle">-</dd>
110 </div>
111
112 <!-- Published -->
113 <div class="space-y-1 sm:col-span-2">
114 <dt class="text-xs text-fg-subtle uppercase tracking-wider">
115 {{ $t('package.stats.published') }}
116 </dt>
117 <dd class="font-mono text-sm">
118 <SkeletonInline class="h-5 w-28" />
119 </dd>
120 </div>
121 </dl>
122 </section>
123
124 <!-- Install section — matches area-install in [...name].vue -->
125 <section class="area-install scroll-mt-20">
126 <div class="flex flex-wrap items-center justify-between mb-3">
127 <h2 class="text-xs font-mono text-fg-subtle uppercase tracking-wider">
128 {{ $t('package.get_started.title') }}
129 </h2>
130 <!-- Package manager select placeholder -->
131 <SkeletonInline class="h-7 w-24 rounded" />
132 </div>
133 <!-- Terminal-style install command — matches TerminalInstall.vue -->
134 <div class="bg-bg-subtle border border-border rounded-lg overflow-hidden">
135 <div class="flex gap-1.5 px-3 pt-2 sm:px-4 sm:pt-3">
136 <span class="w-2.5 h-2.5 rounded-full bg-fg-subtle" />
137 <span class="w-2.5 h-2.5 rounded-full bg-fg-subtle" />
138 <span class="w-2.5 h-2.5 rounded-full bg-fg-subtle" />
139 </div>
140 <div class="px-3 pt-2 pb-3 sm:px-4 sm:pt-3 sm:pb-4 space-y-1">
141 <!-- $ install command -->
142 <div class="flex items-center gap-2">
143 <span class="text-fg-subtle font-mono text-sm select-none shrink-0">$</span>
144 <SkeletonInline class="h-5 w-40" />
145 </div>
146 <!-- # Run locally -->
147 <div class="flex items-center gap-2 pt-1">
148 <SkeletonInline class="h-4 w-24" />
149 </div>
150 <!-- $ run command -->
151 <div class="flex items-center gap-2">
152 <span class="text-fg-subtle font-mono text-sm select-none shrink-0">$</span>
153 <SkeletonInline class="h-5 w-28" />
154 </div>
155 <!-- # Create new project -->
156 <div class="flex items-center gap-2 pt-1">
157 <SkeletonInline class="h-4 w-36" />
158 </div>
159 <!-- $ create command -->
160 <div class="flex items-center gap-2">
161 <span class="text-fg-subtle font-mono text-sm select-none shrink-0">$</span>
162 <SkeletonInline class="h-5 w-32" />
163 </div>
164 </div>
165 </div>
166 </section>
167
168 <!-- Vulns area (empty placeholder to hold grid space) -->
169 <div class="area-vulns" />
170
171 <!-- README — matches area-readme in [...name].vue -->
172 <section class="area-readme min-w-0 scroll-mt-20">
173 <div class="flex flex-wrap items-center justify-between mb-3 px-1">
174 <h2 class="text-xs font-mono text-fg-subtle uppercase tracking-wider">
175 {{ $t('package.readme.title') }}
176 </h2>
177 </div>
178 <!-- Simulated README content -->
179 <div class="space-y-4">
180 <!-- Heading -->
181 <SkeletonBlock class="h-7 w-2/3" />
182 <!-- Paragraphs -->
183 <SkeletonBlock class="h-4 w-full" />
184 <SkeletonBlock class="h-4 w-full" />
185 <SkeletonBlock class="h-4 w-4/5" />
186 <!-- Gap for section break -->
187 <SkeletonBlock class="h-6 w-1/2 mt-6" />
188 <SkeletonBlock class="h-4 w-full" />
189 <SkeletonBlock class="h-4 w-full" />
190 <SkeletonBlock class="h-4 w-3/4" />
191 <!-- Code block placeholder -->
192 <SkeletonBlock class="h-24 w-full rounded-lg mt-4" />
193 <SkeletonBlock class="h-4 w-full" />
194 <SkeletonBlock class="h-4 w-5/6" />
195 </div>
196 </section>
197
198 <!-- Sidebar — matches area-sidebar in [...name].vue -->
199 <div class="area-sidebar">
200 <div
201 class="sticky top-30 xl:top-14 space-y-6 sm:space-y-8 min-w-0 overflow-y-auto pe-2.5 lg:(max-h-[calc(100dvh-8.5rem)] overscroll-contain) xl:(pt-2 max-h-[calc(100dvh-6rem)])"
202 >
203 <div class="flex flex-col gap-4 sm:gap-6 xl:(pt-2)">
204 <!-- Download stats — matches CollapsibleSection + sparkline skeleton -->
205 <section>
206 <div class="flex items-center justify-between mb-3 px-1">
207 <h2
208 class="text-xs font-mono text-fg-subtle uppercase tracking-wider flex items-center gap-2"
209 >
210 <span class="w-4 h-4 flex items-center justify-center shrink-0">
211 <span class="i-lucide:chevron-down w-3 h-3" aria-hidden="true" />
212 </span>
213 {{ $t('package.downloads.title') }}
214 </h2>
215 </div>
216 <div class="ms-6 max-w-xs">
217 <!-- Title row: fontSize * 2 = 24px -->
218 <div class="h-6 flex items-center ps-3">
219 <SkeletonInline class="h-3 w-36" />
220 </div>
221 <!-- Chart area: matches SVG viewBox 500:80 -->
222 <div class="aspect-[500/80] flex items-center">
223 <div class="w-[42%] flex items-center ps-0.5">
224 <SkeletonInline class="h-7 w-24" />
225 </div>
226 <div class="flex-1 flex items-end pe-3">
227 <SkeletonInline class="h-px w-full" />
228 </div>
229 </div>
230 </div>
231 </section>
232
233 <!-- Playgrounds — matches PackagePlaygrounds (not CollapsibleSection) -->
234 <section class="px-1">
235 <h2 class="text-xs font-mono text-fg-subtle uppercase tracking-wider mb-3">
236 {{ $t('package.playgrounds.title') }}
237 </h2>
238 <SkeletonBlock class="w-full h-9 rounded-md" />
239 </section>
240
241 <!-- Compatibility — matches CollapsibleSection -->
242 <section>
243 <div class="flex items-center justify-between mb-3 px-1">
244 <h2
245 class="text-xs font-mono text-fg-subtle uppercase tracking-wider flex items-center gap-2"
246 >
247 <span class="w-4 h-4 flex items-center justify-center shrink-0">
248 <span class="i-lucide:chevron-down w-3 h-3" aria-hidden="true" />
249 </span>
250 {{ $t('package.compatibility') }}
251 </h2>
252 </div>
253 <div class="ms-6 space-y-2">
254 <div class="flex justify-between gap-4 py-1">
255 <SkeletonInline class="h-4 w-16" />
256 <SkeletonInline class="h-4 w-20" />
257 </div>
258 </div>
259 </section>
260
261 <!-- Versions — matches CollapsibleSection + PackageVersions -->
262 <section>
263 <div class="flex items-center justify-between mb-3 px-1">
264 <h2
265 class="text-xs font-mono text-fg-subtle uppercase tracking-wider flex items-center gap-2"
266 >
267 <span class="w-4 h-4 flex items-center justify-center shrink-0">
268 <span class="i-lucide:chevron-down w-3 h-3" aria-hidden="true" />
269 </span>
270 {{ $t('package.skeleton.versions') }}
271 </h2>
272 </div>
273 <div class="ms-6 space-y-0.5 min-w-0">
274 <!-- Version rows with expand chevron + version + tag + date -->
275 <div v-for="i in 4" :key="i" class="flex items-center gap-2 px-1">
276 <span class="w-4 h-4 flex items-center justify-center shrink-0">
277 <span class="i-lucide:chevron-right w-3 h-3 text-fg-subtle" aria-hidden="true" />
278 </span>
279 <div class="flex-1 py-1.5 min-w-0 flex gap-2 justify-between items-center">
280 <div>
281 <SkeletonInline
282 class="h-4"
283 :class="i === 1 ? 'w-12' : i === 2 ? 'w-22' : i === 3 ? 'w-26' : 'w-14'"
284 />
285 <SkeletonInline class="h-2.5 w-10 mt-0.5" />
286 </div>
287 <SkeletonInline class="h-3 w-20 shrink-0" />
288 </div>
289 </div>
290 <!-- Other versions row -->
291 <div class="flex items-center gap-2 p-1">
292 <span class="w-4 h-4 flex items-center justify-center shrink-0">
293 <span class="i-lucide:chevron-right w-3 h-3 text-fg-subtle" aria-hidden="true" />
294 </span>
295 <SkeletonInline class="h-3 w-28" />
296 </div>
297 </div>
298 </section>
299
300 <!-- Dependencies — matches CollapsibleSection -->
301 <section>
302 <div class="flex items-center justify-between mb-3 px-1">
303 <h2
304 class="text-xs font-mono text-fg-subtle uppercase tracking-wider flex items-center gap-2"
305 >
306 <span class="w-4 h-4 flex items-center justify-center shrink-0">
307 <span class="i-lucide:chevron-down w-3 h-3" aria-hidden="true" />
308 </span>
309 {{ $t('package.skeleton.dependencies') }}
310 </h2>
311 </div>
312 <ul class="ms-6 px-1 space-y-1 list-none m-0 p-0">
313 <li class="flex items-center justify-between py-1 text-sm">
314 <SkeletonInline class="h-4 w-24" />
315 <SkeletonInline class="h-4 w-12" />
316 </li>
317 <li class="flex items-center justify-between py-1 text-sm">
318 <SkeletonInline class="h-4 w-32" />
319 <SkeletonInline class="h-4 w-10" />
320 </li>
321 <li class="flex items-center justify-between py-1 text-sm">
322 <SkeletonInline class="h-4 w-20" />
323 <SkeletonInline class="h-4 w-14" />
324 </li>
325 <li class="flex items-center justify-between py-1 text-sm">
326 <SkeletonInline class="h-4 w-28" />
327 <SkeletonInline class="h-4 w-12" />
328 </li>
329 </ul>
330 </section>
331
332 <!-- Keywords — matches CollapsibleSection -->
333 <section>
334 <div class="flex items-center justify-between mb-3 px-1">
335 <h2
336 class="text-xs font-mono text-fg-subtle uppercase tracking-wider flex items-center gap-2"
337 >
338 <span class="w-4 h-4 flex items-center justify-center shrink-0">
339 <span class="i-lucide:chevron-down w-3 h-3" aria-hidden="true" />
340 </span>
341 {{ $t('package.skeleton.keywords') }}
342 </h2>
343 </div>
344 <ul class="ms-6 flex flex-wrap gap-1.5 list-none m-0 p-1">
345 <li><SkeletonInline class="h-6 w-16 rounded" /></li>
346 <li><SkeletonInline class="h-6 w-12 rounded" /></li>
347 <li><SkeletonInline class="h-6 w-20 rounded" /></li>
348 <li><SkeletonInline class="h-6 w-14 rounded" /></li>
349 <li><SkeletonInline class="h-6 w-18 rounded" /></li>
350 <li><SkeletonInline class="h-6 w-10 rounded" /></li>
351 </ul>
352 </section>
353
354 <!-- Maintainers — matches CollapsibleSection -->
355 <section>
356 <div class="flex items-center justify-between mb-3 px-1">
357 <h2
358 class="text-xs font-mono text-fg-subtle uppercase tracking-wider flex items-center gap-2"
359 >
360 <span class="w-4 h-4 flex items-center justify-center shrink-0">
361 <span class="i-lucide:chevron-down w-3 h-3" aria-hidden="true" />
362 </span>
363 {{ $t('package.skeleton.maintainers') }}
364 </h2>
365 </div>
366 <ul class="ms-6 space-y-2 list-none my-1 px-1">
367 <li><SkeletonInline class="h-5 w-28" /></li>
368 <li><SkeletonInline class="h-5 w-24" /></li>
369 </ul>
370 </section>
371 </div>
372 </div>
373 </div>
374 </article>
375</template>
376
377<style scoped>
378.package-page {
379 display: grid;
380 gap: 2rem;
381
382 /* Mobile: single column, sidebar above readme */
383 grid-template-columns: minmax(0, 1fr);
384 grid-template-areas:
385 'header'
386 'details'
387 'install'
388 'vulns'
389 'sidebar'
390 'readme';
391}
392
393/* Tablet/medium: header/install/vulns full width, readme+sidebar side by side */
394@media (min-width: 1024px) {
395 .package-page {
396 grid-template-columns: 2fr 1fr;
397 grid-template-areas:
398 'header header'
399 'details details'
400 'install install'
401 'vulns vulns'
402 'readme sidebar';
403 grid-template-rows: auto auto auto auto 1fr;
404 }
405}
406
407/* Desktop: floating sidebar alongside all content */
408@media (min-width: 1280px) {
409 .package-page {
410 grid-template-columns: 1fr 20rem;
411 grid-template-areas:
412 'header sidebar'
413 'details sidebar'
414 'install sidebar'
415 'vulns sidebar'
416 'readme sidebar';
417 }
418}
419
420.area-header {
421 grid-area: header;
422}
423
424.area-details {
425 grid-area: details;
426}
427
428.area-install {
429 grid-area: install;
430}
431
432.area-vulns {
433 grid-area: vulns;
434}
435
436.area-readme {
437 grid-area: readme;
438 overflow-x: hidden;
439}
440
441.area-sidebar {
442 grid-area: sidebar;
443}
444
445/* Sidebar scrollbar: hidden by default, shown on hover/focus */
446@media (min-width: 1024px) {
447 .sidebar-scroll {
448 scrollbar-gutter: stable;
449 scrollbar-width: 8px;
450 scrollbar-color: transparent transparent;
451 }
452
453 .sidebar-scroll::-webkit-scrollbar {
454 width: 8px;
455 height: 8px;
456 }
457
458 .sidebar-scroll::-webkit-scrollbar-track,
459 .sidebar-scroll::-webkit-scrollbar-thumb {
460 background: transparent;
461 }
462
463 .sidebar-scroll:hover,
464 .sidebar-scroll:focus-within {
465 scrollbar-color: var(--border) transparent;
466 }
467
468 .sidebar-scroll:hover::-webkit-scrollbar-thumb,
469 .sidebar-scroll:focus-within::-webkit-scrollbar-thumb {
470 background-color: var(--border);
471 border-radius: 9999px;
472 }
473
474 .sidebar-scroll:hover::-webkit-scrollbar-track,
475 .sidebar-scroll:focus-within::-webkit-scrollbar-track {
476 background: transparent;
477 }
478}
479</style>