Skip to content

Commit f8fee4f

Browse files
committed
chore: convert chart modal to modal route
1 parent 8583ba8 commit f8fee4f

2 files changed

Lines changed: 131 additions & 22 deletions

File tree

app/components/PackageWeeklyDownloadStats.vue

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,6 @@ const props = defineProps<{
77
packageName: string
88
}>()
99
10-
const chartModal = useModal('chart-modal')
11-
12-
const isChartModalOpen = shallowRef(false)
13-
function openChartModal() {
14-
isChartModalOpen.value = true
15-
// ensure the component renders before opening the dialog
16-
nextTick(() => chartModal.open())
17-
}
18-
1910
const { data: packument } = usePackage(() => props.packageName)
2011
const createdIso = computed(() => packument.value?.time?.created ?? null)
2112
@@ -199,15 +190,14 @@ const config = computed(() => {
199190
<div class="space-y-8">
200191
<CollapsibleSection id="downloads" :title="$t('package.downloads.title')">
201192
<template #actions>
202-
<button
203-
type="button"
204-
@click="openChartModal"
193+
<NuxtLink
194+
:to="{ name: 'analytics', params: { package: props.packageName.split('/') } }"
205195
class="link-subtle font-mono text-sm inline-flex items-center gap-1.5 ms-auto shrink-0 self-center focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 rounded"
206196
:title="$t('package.downloads.analyze')"
207197
>
208198
<span class="i-carbon:data-analytics w-4 h-4" aria-hidden="true" />
209199
<span class="sr-only">{{ $t('package.downloads.analyze') }}</span>
210-
</button>
200+
</NuxtLink>
211201
</template>
212202

213203
<div class="w-full overflow-hidden">
@@ -247,15 +237,6 @@ const config = computed(() => {
247237
</div>
248238
</CollapsibleSection>
249239
</div>
250-
251-
<ChartModal v-if="isChartModalOpen" @close="isChartModalOpen = false">
252-
<PackageDownloadAnalytics
253-
:weeklyDownloads="weeklyDownloads"
254-
:inModal="true"
255-
:packageName="props.packageName"
256-
:createdIso="createdIso"
257-
/>
258-
</ChartModal>
259240
</template>
260241

261242
<style>
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
<script setup lang="ts">
2+
definePageMeta({
3+
name: 'analytics',
4+
})
5+
6+
const route = useRoute('analytics')
7+
const router = useRouter()
8+
9+
// Extract package name from route params (handles scoped packages like @org/pkg)
10+
const packageName = computed(() => {
11+
const params = route.params.package || []
12+
return params.join('/')
13+
})
14+
15+
// Fetch package data to get creation date and check if package exists
16+
const { data: packument, status: packageStatus } = usePackage(packageName)
17+
const createdIso = computed(() => packument.value?.time?.created ?? null)
18+
19+
// Determine if package exists (has data after loading)
20+
const packageExists = computed(() => packageStatus.value === 'success' && !!packument.value)
21+
22+
// Close destination: package page if exists, home otherwise
23+
const closeDestination = computed(() => (packageExists.value ? `/${packageName.value}` : '/'))
24+
25+
// Fetch weekly downloads for initial display
26+
const { fetchPackageDownloadEvolution } = useCharts()
27+
const weeklyDownloads = shallowRef<WeeklyDownloadPoint[]>([])
28+
29+
async function loadWeeklyDownloads() {
30+
if (!import.meta.client) return
31+
if (!packageName.value) return
32+
33+
try {
34+
const result = await fetchPackageDownloadEvolution(
35+
() => packageName.value,
36+
() => createdIso.value,
37+
() => ({ granularity: 'week' as const, weeks: 52 }),
38+
)
39+
weeklyDownloads.value = (result as WeeklyDownloadPoint[]) ?? []
40+
} catch {
41+
weeklyDownloads.value = []
42+
}
43+
}
44+
45+
onMounted(() => {
46+
loadWeeklyDownloads()
47+
})
48+
49+
watch(packageName, () => loadWeeklyDownloads())
50+
51+
function handleKeydown(event: KeyboardEvent) {
52+
if (event.key === 'Escape') {
53+
router.push(closeDestination.value)
54+
}
55+
}
56+
57+
// SEO
58+
useSeoMeta({
59+
title: () => (packageName.value ? `${packageName.value} Downloads - npmx` : 'Downloads - npmx'),
60+
description: () => `Download statistics and trends for ${packageName.value}`,
61+
})
62+
</script>
63+
64+
<template>
65+
<Teleport to="body">
66+
<div
67+
class="fixed inset-0 z-50 flex items-center justify-center p-0 sm:p-4"
68+
@keydown="handleKeydown"
69+
>
70+
<!-- Backdrop - clicking navigates back -->
71+
<NuxtLink
72+
:to="closeDestination"
73+
class="absolute inset-0 bg-black/60 cursor-default"
74+
:aria-label="$t('common.close_modal')"
75+
/>
76+
77+
<div
78+
class="relative w-full h-full sm:h-auto bg-bg sm:border sm:border-border sm:rounded-lg shadow-xl sm:max-h-[90vh] overflow-y-auto overscroll-contain sm:max-w-3xl"
79+
role="dialog"
80+
aria-modal="true"
81+
aria-labelledby="analytics-modal-title"
82+
>
83+
<div class="p-4 sm:p-6">
84+
<div class="flex items-center justify-between mb-4 sm:mb-6">
85+
<h1 id="analytics-modal-title" class="font-mono text-lg font-medium">
86+
{{ $t('package.downloads.modal_title') }}
87+
</h1>
88+
<NuxtLink
89+
:to="closeDestination"
90+
class="text-fg-subtle hover:text-fg transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 rounded"
91+
:aria-label="$t('common.close')"
92+
>
93+
<span class="i-carbon-close block w-5 h-5" aria-hidden="true" />
94+
</NuxtLink>
95+
</div>
96+
<div class="font-mono text-sm">
97+
<ClientOnly>
98+
<PackageDownloadAnalytics
99+
:weeklyDownloads="weeklyDownloads"
100+
:inModal="true"
101+
:packageName="packageName"
102+
:createdIso="createdIso"
103+
/>
104+
<template #fallback>
105+
<div class="min-h-[260px] flex items-center justify-center">
106+
<span
107+
class="i-carbon:circle-dash w-6 h-6 motion-safe:animate-spin text-fg-subtle"
108+
/>
109+
</div>
110+
</template>
111+
</ClientOnly>
112+
</div>
113+
</div>
114+
115+
<!-- Mobile close button -->
116+
<div class="sm:hidden flex justify-center pb-4">
117+
<NuxtLink
118+
:to="closeDestination"
119+
class="w-12 h-12 bg-bg-elevated border border-border rounded-full shadow-lg flex items-center justify-center text-fg-muted hover:text-fg transition-colors"
120+
:aria-label="$t('common.close')"
121+
>
122+
<span class="w-5 h-5 i-carbon:close" aria-hidden="true" />
123+
</NuxtLink>
124+
</div>
125+
</div>
126+
</div>
127+
</Teleport>
128+
</template>

0 commit comments

Comments
 (0)