Skip to content

Commit cda7f6e

Browse files
authored
feat: add weekly downloads to <PackageCard> (#97)
1 parent d1cd5e0 commit cda7f6e

2 files changed

Lines changed: 108 additions & 56 deletions

File tree

app/components/PackageCard.vue

Lines changed: 105 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -20,82 +20,132 @@ const emit = defineEmits<{
2020

2121
<template>
2222
<article
23-
class="group card-interactive scroll-mt-48 scroll-mb-6"
23+
class="group card-interactive scroll-mt-48 scroll-mb-6 relative focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-bg focus-within:ring-offset-2 focus-within:ring-fg/50"
2424
:class="{ 'bg-bg-muted border-border-hover': selected }"
2525
>
26-
<NuxtLink
27-
:to="{ name: 'package', params: { package: result.package.name.split('/') } }"
28-
:prefetch-on="prefetch ? 'visibility' : 'interaction'"
29-
class="block focus:outline-none decoration-none scroll-mt-48 scroll-mb-6"
30-
:data-result-index="index"
31-
@focus="index != null && emit('focus', index)"
32-
@mouseenter="index != null && emit('focus', index)"
33-
>
34-
<header class="flex items-start justify-between gap-2 sm:gap-4 mb-2">
35-
<component
36-
:is="headingLevel ?? 'h3'"
37-
class="font-mono text-sm sm:text-base font-medium text-fg group-hover:text-fg transition-colors duration-200 min-w-0 break-words"
26+
<div class="mb-2 flex items-baseline justify-between gap-2">
27+
<component
28+
:is="headingLevel ?? 'h3'"
29+
class="font-mono text-sm sm:text-base font-medium text-fg group-hover:text-fg transition-colors duration-200 min-w-0 break-all"
30+
>
31+
<NuxtLink
32+
:to="{ name: 'package', params: { package: result.package.name.split('/') } }"
33+
:prefetch-on="prefetch ? 'visibility' : 'interaction'"
34+
class="focus-visible:outline-none decoration-none scroll-mt-48 scroll-mb-6 after:content-[''] after:absolute after:inset-0"
35+
:data-result-index="index"
36+
@focus="index != null && emit('focus', index)"
37+
@mouseenter="index != null && emit('focus', index)"
3838
>
3939
{{ result.package.name }}
40-
</component>
41-
<div class="flex items-center gap-1.5 shrink-0">
40+
</NuxtLink>
41+
</component>
42+
<!-- Mobile: version next to package name -->
43+
<div class="sm:hidden text-fg-subtle flex items-center gap-1.5 shrink-0">
44+
<span
45+
v-if="result.package.version"
46+
class="font-mono text-xs truncate max-w-20"
47+
:title="result.package.version"
48+
>
49+
v{{ result.package.version }}
50+
</span>
51+
<ProvenanceBadge
52+
v-if="result.package.publisher?.trustedPublisher"
53+
:provider="result.package.publisher.trustedPublisher.id"
54+
:package-name="result.package.name"
55+
:version="result.package.version"
56+
:linked="false"
57+
compact
58+
/>
59+
</div>
60+
</div>
61+
<div class="flex justify-between items-start gap-4 sm:gap-8">
62+
<div class="min-w-0">
63+
<p
64+
v-if="result.package.description"
65+
class="text-fg-muted text-xs sm:text-sm line-clamp-2 mb-2 sm:mb-3"
66+
>
67+
<MarkdownText :text="result.package.description" />
68+
</p>
69+
<div class="flex flex-wrap items-center gap-x-3 sm:gap-x-4 gap-y-2 text-xs text-fg-subtle">
70+
<dl v-if="showPublisher || result.package.date" class="flex items-center gap-4 m-0">
71+
<div
72+
v-if="showPublisher && result.package.publisher?.username"
73+
class="flex items-center gap-1.5"
74+
>
75+
<dt class="sr-only">Publisher</dt>
76+
<dd class="font-mono">@{{ result.package.publisher.username }}</dd>
77+
</div>
78+
<div v-if="result.package.date" class="flex items-center gap-1.5">
79+
<dt class="sr-only">Updated</dt>
80+
<dd>
81+
<NuxtTime
82+
:datetime="result.package.date"
83+
year="numeric"
84+
month="short"
85+
day="numeric"
86+
/>
87+
</dd>
88+
</div>
89+
</dl>
90+
</div>
91+
<!-- Mobile: downloads on separate row -->
92+
<dl
93+
v-if="result.downloads?.weekly"
94+
class="sm:hidden flex items-center gap-4 mt-2 text-xs text-fg-subtle m-0"
95+
>
96+
<div class="flex items-center gap-1.5">
97+
<dt class="sr-only">Weekly downloads</dt>
98+
<dd class="flex items-center gap-1.5">
99+
<span class="i-carbon-chart-line w-3.5 h-3.5 inline-block" aria-hidden="true" />
100+
<span class="font-mono">{{ formatNumber(result.downloads.weekly) }}/w</span>
101+
</dd>
102+
</div>
103+
</dl>
104+
</div>
105+
<!-- Desktop: version and downloads on right side -->
106+
<div class="hidden sm:flex flex-col gap-2 shrink-0">
107+
<div class="text-fg-subtle flex items-start gap-2 justify-end">
42108
<span
43109
v-if="result.package.version"
44-
class="font-mono text-xs text-fg-subtle truncate max-w-20 sm:max-w-32"
110+
class="font-mono text-xs truncate max-w-32"
45111
:title="result.package.version"
46112
>
47113
v{{ result.package.version }}
48114
</span>
49-
<ProvenanceBadge
50-
v-if="result.package.publisher?.trustedPublisher"
51-
:provider="result.package.publisher.trustedPublisher.id"
52-
:package-name="result.package.name"
53-
:version="result.package.version"
54-
compact
55-
/>
56-
</div>
57-
</header>
58-
59-
<p
60-
v-if="result.package.description"
61-
class="text-fg-muted text-xs sm:text-sm line-clamp-2 mb-2 sm:mb-3"
62-
>
63-
<MarkdownText :text="result.package.description" />
64-
</p>
65-
66-
<footer class="flex flex-wrap items-center gap-x-3 sm:gap-x-4 gap-y-2 text-xs text-fg-subtle">
67-
<dl v-if="showPublisher || result.package.date" class="flex items-center gap-4 m-0">
68115
<div
69-
v-if="showPublisher && result.package.publisher?.username"
70-
class="flex items-center gap-1.5"
116+
v-if="result.package.publisher?.trustedPublisher"
117+
class="flex items-center gap-1.5 shrink-0 max-w-32"
71118
>
72-
<dt class="sr-only">Publisher</dt>
73-
<dd class="font-mono">@{{ result.package.publisher.username }}</dd>
74-
</div>
75-
<div v-if="result.package.date" class="flex items-center gap-1.5">
76-
<dt class="sr-only">Updated</dt>
77-
<dd>
78-
<NuxtTime
79-
:datetime="result.package.date"
80-
year="numeric"
81-
month="short"
82-
day="numeric"
83-
/>
84-
</dd>
119+
<ProvenanceBadge
120+
:provider="result.package.publisher.trustedPublisher.id"
121+
:package-name="result.package.name"
122+
:version="result.package.version"
123+
:linked="false"
124+
compact
125+
/>
85126
</div>
86-
</dl>
87-
</footer>
88-
</NuxtLink>
127+
</div>
128+
<div
129+
v-if="result.downloads?.weekly"
130+
class="text-fg-subtle gap-2 flex items-center justify-end"
131+
>
132+
<span class="i-carbon-chart-line w-3.5 h-3.5 inline-block" aria-hidden="true" />
133+
<span class="font-mono text-xs">
134+
{{ formatNumber(result.downloads.weekly) }} / week
135+
</span>
136+
</div>
137+
</div>
138+
</div>
89139

90140
<ul
91141
v-if="result.package.keywords?.length"
92142
aria-label="Keywords"
93-
class="flex flex-wrap gap-1.5 mt-3 pt-3 border-t border-border list-none m-0 p-0"
143+
class="relative z-10 flex flex-wrap gap-1.5 mt-3 pt-3 border-t border-border list-none m-0 p-0"
94144
>
95145
<li v-for="keyword in result.package.keywords.slice(0, 5)" :key="keyword">
96146
<NuxtLink
97147
:to="{ name: 'search', query: { q: `keywords:${keyword}` } }"
98-
class="tag decoration-none"
148+
class="tag decoration-none focus-visible:ring-2 focus-visible:ring-fg/50 focus-visible:outline-none"
99149
>
100150
{{ keyword }}
101151
</NuxtLink>

app/components/ProvenanceBadge.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ defineProps<{
88
version?: string
99
/** Whether to show as compact (icon only) or full (with text) */
1010
compact?: boolean
11+
/** Whether to render as a link (defaults to true when packageName and version are provided) */
12+
linked?: boolean
1113
}>()
1214
1315
const providerLabels: Record<string, string> = {
@@ -18,7 +20,7 @@ const providerLabels: Record<string, string> = {
1820

1921
<template>
2022
<a
21-
v-if="packageName && version"
23+
v-if="packageName && version && linked !== false"
2224
:href="`https://www.npmjs.com/package/${packageName}/v/${version}#provenance`"
2325
target="_blank"
2426
rel="noopener noreferrer"

0 commit comments

Comments
 (0)