Skip to content

Commit 5a685e1

Browse files
committed
Merge branch 'pr-146' into graphieros/main
2 parents ab4db68 + ac0b34e commit 5a685e1

5 files changed

Lines changed: 241 additions & 114 deletions

File tree

app/components/ChartModal.vue

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
<script setup lang="ts">
22
const open = defineModel<boolean>('open', { default: false })
3+
4+
function handleKeydown(event: KeyboardEvent) {
5+
if (event.key === 'Escape') {
6+
open.value = false
7+
}
8+
}
39
</script>
410

511
<template>
@@ -10,7 +16,11 @@ const open = defineModel<boolean>('open', { default: false })
1016
enter-from-class="opacity-0"
1117
leave-to-class="opacity-0"
1218
>
13-
<div v-if="open" class="fixed inset-0 z-50 flex items-center justify-center p-4">
19+
<div
20+
v-if="open"
21+
class="fixed inset-0 z-50 flex items-center justify-center p-0 sm:p-4"
22+
@keydown="handleKeydown"
23+
>
1424
<!-- Backdrop -->
1525
<button
1626
type="button"
@@ -20,14 +30,14 @@ const open = defineModel<boolean>('open', { default: false })
2030
/>
2131

2232
<div
23-
class="relative w-full bg-bg border border-border rounded-lg shadow-xl max-h-[90vh] overflow-y-auto overscroll-contain max-w-3xl"
33+
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"
2434
role="dialog"
2535
aria-modal="true"
26-
aria-labelledby="package-download-stats"
36+
aria-labelledby="chart-modal-title"
2737
>
28-
<div class="p-6">
29-
<div class="flex items-center justify-between mb-6">
30-
<h2 id="package-download-stats" class="font-mono text-lg font-medium">
38+
<div class="p-4 sm:p-6">
39+
<div class="flex items-center justify-between mb-4 sm:mb-6">
40+
<h2 id="chart-modal-title" class="font-mono text-lg font-medium">
3141
<slot name="title" />
3242
</h2>
3343
<button
@@ -39,7 +49,7 @@ const open = defineModel<boolean>('open', { default: false })
3949
<span class="i-carbon-close block w-5 h-5" aria-hidden="true" />
4050
</button>
4151
</div>
42-
<div class="flex items-center font-mono text-sm">
52+
<div class="font-mono text-sm">
4353
<slot />
4454
</div>
4555
</div>

app/components/PackageDownloadAnalytics.vue

Lines changed: 143 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import type { VueUiXyDatasetItem } from 'vue-data-ui'
44
import { VueUiXy } from 'vue-data-ui/vue-ui-xy'
55
import { useDebounceFn } from '@vueuse/core'
66
7+
const { t } = useI18n()
8+
79
const {
810
weeklyDownloads,
911
inModal = false,
@@ -195,7 +197,7 @@ function initDateRangeFallbackClient() {
195197
}
196198
197199
watch(
198-
() => weeklyDownloads,
200+
() => weeklyDownloads?.length,
199201
() => {
200202
initDateRangeFromWeekly()
201203
initDateRangeFallbackClient()
@@ -323,6 +325,9 @@ async function load() {
323325
324326
evolution.value = (result as EvolutionData) ?? []
325327
displayedGranularity.value = selectedGranularity.value
328+
} catch {
329+
if (currentToken !== requestToken) return
330+
evolution.value = []
326331
} finally {
327332
if (currentToken === requestToken) {
328333
pending.value = false
@@ -393,7 +398,7 @@ const config = computed(() => ({
393398
grid: {
394399
labels: {
395400
axis: {
396-
yLabel: `${selectedGranularity.value} downloads`,
401+
yLabel: t('package.downloads.y_axis_label', { granularity: selectedGranularity.value }),
397402
xLabel: packageName,
398403
yLabelOffsetX: 12,
399404
fontSize: 24,
@@ -453,153 +458,192 @@ const config = computed(() => ({
453458

454459
<template>
455460
<div class="w-full relative">
456-
<div class="w-full mb-2 flex flex-col gap-2">
457-
<div class="w-full grid grid-cols-1 sm:grid-cols-[1fr_auto] gap-2">
458-
<div class="flex gap-2">
459-
<!-- Granularity -->
461+
<div class="w-full mb-4 flex flex-col gap-3">
462+
<!-- Mobile: stack vertically, Desktop: horizontal -->
463+
<div class="flex flex-col sm:flex-row gap-3 sm:gap-2 sm:items-end">
464+
<!-- Granularity -->
465+
<div class="flex flex-col gap-1 sm:shrink-0">
466+
<label
467+
for="granularity"
468+
class="text-[10px] font-mono text-fg-subtle tracking-wide uppercase"
469+
>
470+
{{ t('package.downloads.granularity') }}
471+
</label>
472+
473+
<div
474+
class="flex items-center px-2.5 py-1.75 bg-bg-subtle border border-border rounded-md focus-within:(border-border-hover ring-2 ring-fg/50)"
475+
>
476+
<select
477+
id="granularity"
478+
v-model="selectedGranularity"
479+
class="w-full bg-transparent font-mono text-sm text-fg outline-none"
480+
>
481+
<option value="daily">{{ t('package.downloads.granularity_daily') }}</option>
482+
<option value="weekly">{{ t('package.downloads.granularity_weekly') }}</option>
483+
<option value="monthly">{{ t('package.downloads.granularity_monthly') }}</option>
484+
<option value="yearly">{{ t('package.downloads.granularity_yearly') }}</option>
485+
</select>
486+
</div>
487+
</div>
488+
489+
<!-- Date range inputs -->
490+
<div class="grid grid-cols-2 gap-2 flex-1">
460491
<div class="flex flex-col gap-1">
461492
<label
462-
for="granularity"
493+
for="startDate"
463494
class="text-[10px] font-mono text-fg-subtle tracking-wide uppercase"
464495
>
465-
Granularity
496+
{{ t('package.downloads.start_date') }}
466497
</label>
467-
468498
<div
469-
class="flex items-center px-2.5 py-1.75 bg-bg-subtle border border-border rounded-md focus-within:(border-border-hover ring-2 ring-fg/50)"
499+
class="flex items-center gap-2 px-2.5 py-1.75 bg-bg-subtle border border-border rounded-md focus-within:(border-border-hover ring-2 ring-fg/50)"
470500
>
471-
<select
472-
id="granularity"
473-
v-model="selectedGranularity"
474-
class="w-full bg-transparent font-mono text-sm text-fg outline-none"
475-
>
476-
<option value="daily">Daily</option>
477-
<option value="weekly">Weekly</option>
478-
<option value="monthly">Monthly</option>
479-
<option value="yearly">Yearly</option>
480-
</select>
501+
<span class="i-carbon-calendar w-4 h-4 text-fg-subtle shrink-0" aria-hidden="true" />
502+
<input
503+
id="startDate"
504+
v-model="startDate"
505+
type="date"
506+
class="w-full min-w-0 bg-transparent font-mono text-sm text-fg outline-none [color-scheme:dark]"
507+
/>
481508
</div>
482509
</div>
483510

484-
<!-- Date range inputs -->
485-
<div class="grid grid-cols-2 gap-2">
486-
<div class="flex flex-col gap-1">
487-
<label
488-
for="startDate"
489-
class="text-[10px] font-mono text-fg-subtle tracking-wide uppercase"
490-
>
491-
Start
492-
</label>
493-
<div
494-
class="flex items-center gap-2 px-2.5 py-1.75 bg-bg-subtle border border-border rounded-md focus-within:(border-border-hover ring-2 ring-fg/50)"
495-
>
496-
<span class="i-carbon-calendar w-4 h-4 text-fg-subtle" aria-hidden="true" />
497-
<input
498-
id="startDate"
499-
v-model="startDate"
500-
type="date"
501-
class="w-full bg-transparent font-mono text-sm text-fg outline-none [color-scheme:dark]"
502-
/>
503-
</div>
504-
</div>
505-
506-
<div class="flex flex-col gap-1">
507-
<label
508-
for="endDate"
509-
class="text-[10px] font-mono text-fg-subtle tracking-wide uppercase"
510-
>
511-
End
512-
</label>
513-
<div
514-
class="flex items-center gap-2 px-2.5 py-1.75 bg-bg-subtle border border-border rounded-md focus-within:(border-border-hover ring-2 ring-fg/50)"
515-
>
516-
<span class="i-carbon-calendar w-4 h-4 text-fg-subtle" aria-hidden="true" />
517-
<input
518-
id="endDate"
519-
v-model="endDate"
520-
type="date"
521-
class="w-full bg-transparent font-mono text-sm text-fg outline-none [color-scheme:dark]"
522-
/>
523-
</div>
524-
</div>
525-
</div>
526-
<!-- Reset -->
527511
<div class="flex flex-col gap-1">
528-
<!-- spacer label to align with others -->
529-
<div class="font-mono tracking-wide uppercase invisible">Reset</div>
530-
531-
<button
532-
v-if="showResetButton"
533-
type="button"
534-
title="Reset date range"
535-
class="flex items-center justify-center px-2.5 py-1.75 border border-transparent rounded-md text-fg-subtle hover:text-fg transition-colors hover:border-border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50"
536-
@click="
537-
() => {
538-
hasUserEditedDates = false
539-
startDate = ''
540-
endDate = ''
541-
initDateRangeFromWeekly()
542-
initDateRangeFallbackClient()
543-
}
544-
"
512+
<label
513+
for="endDate"
514+
class="text-[10px] font-mono text-fg-subtle tracking-wide uppercase"
515+
>
516+
{{ t('package.downloads.end_date') }}
517+
</label>
518+
<div
519+
class="flex items-center gap-2 px-2.5 py-1.75 bg-bg-subtle border border-border rounded-md focus-within:(border-border-hover ring-2 ring-fg/50)"
545520
>
546-
<span class="i-carbon-reset w-5 h-5 inline-block" />
547-
</button>
521+
<span class="i-carbon-calendar w-4 h-4 text-fg-subtle shrink-0" aria-hidden="true" />
522+
<input
523+
id="endDate"
524+
v-model="endDate"
525+
type="date"
526+
class="w-full min-w-0 bg-transparent font-mono text-sm text-fg outline-none [color-scheme:dark]"
527+
/>
528+
</div>
548529
</div>
549530
</div>
531+
532+
<!-- Reset button -->
533+
<button
534+
v-if="showResetButton"
535+
type="button"
536+
aria-label="Reset date range"
537+
class="self-end flex items-center justify-center px-2.5 py-1.75 border border-transparent rounded-md text-fg-subtle hover:text-fg transition-colors hover:border-border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 sm:mb-0"
538+
@click="
539+
() => {
540+
hasUserEditedDates = false
541+
startDate = ''
542+
endDate = ''
543+
initDateRangeFromWeekly()
544+
initDateRangeFallbackClient()
545+
}
546+
"
547+
>
548+
<span class="i-carbon-reset w-5 h-5 inline-block" aria-hidden="true" />
549+
</button>
550550
</div>
551551
</div>
552552

553-
<ClientOnly v-if="inModal">
553+
<ClientOnly v-if="inModal && chartData.dataset">
554554
<VueUiXy :dataset="chartData.dataset" :config="config">
555555
<template #menuIcon="{ isOpen }">
556-
<span class="i-carbon-close w-6 h-6" v-if="isOpen" />
557-
<span class="i-carbon-overflow-menu-vertical w-6 h-6" v-else />
556+
<span v-if="isOpen" class="i-carbon-close w-6 h-6" aria-hidden="true" />
557+
<span v-else class="i-carbon-overflow-menu-vertical w-6 h-6" aria-hidden="true" />
558558
</template>
559559
<template #optionCsv>
560-
<span class="i-carbon-csv w-6 h-6 text-fg-subtle" style="pointer-events: none" />
560+
<span
561+
class="i-carbon-csv w-6 h-6 text-fg-subtle"
562+
style="pointer-events: none"
563+
aria-hidden="true"
564+
/>
561565
</template>
562566
<template #optionImg>
563-
<span class="i-carbon-png w-6 h-6 text-fg-subtle" style="pointer-events: none" />
567+
<span
568+
class="i-carbon-png w-6 h-6 text-fg-subtle"
569+
style="pointer-events: none"
570+
aria-hidden="true"
571+
/>
564572
</template>
565573
<template #optionSvg>
566-
<span class="i-carbon-svg w-6 h-6 text-fg-subtle" style="pointer-events: none" />
574+
<span
575+
class="i-carbon-svg w-6 h-6 text-fg-subtle"
576+
style="pointer-events: none"
577+
aria-hidden="true"
578+
/>
567579
</template>
568580

569581
<template #annotator-action-close>
570-
<span class="i-carbon-close w-6 h-6 text-fg-subtle" style="pointer-events: none" />
582+
<span
583+
class="i-carbon-close w-6 h-6 text-fg-subtle"
584+
style="pointer-events: none"
585+
aria-hidden="true"
586+
/>
571587
</template>
572588
<template #annotator-action-color="{ color }">
573-
<span class="i-carbon-color-palette w-6 h-6" :style="{ color }" />
589+
<span class="i-carbon-color-palette w-6 h-6" :style="{ color }" aria-hidden="true" />
574590
</template>
575-
<template #annotator-action-undo="{ disabled }">
576-
<span class="i-carbon-undo w-6 h-6 text-fg-subtle" style="pointer-events: none" />
591+
<template #annotator-action-undo>
592+
<span
593+
class="i-carbon-undo w-6 h-6 text-fg-subtle"
594+
style="pointer-events: none"
595+
aria-hidden="true"
596+
/>
577597
</template>
578-
<template #annotator-action-redo="{ disabled }">
579-
<span class="i-carbon-redo w-6 h-6 text-fg-subtle" style="pointer-events: none" />
598+
<template #annotator-action-redo>
599+
<span
600+
class="i-carbon-redo w-6 h-6 text-fg-subtle"
601+
style="pointer-events: none"
602+
aria-hidden="true"
603+
/>
580604
</template>
581-
<template #annotator-action-delete="{ disabled }">
582-
<span class="i-carbon-trash-can w-6 h-6 text-fg-subtle" style="pointer-events: none" />
605+
<template #annotator-action-delete>
606+
<span
607+
class="i-carbon-trash-can w-6 h-6 text-fg-subtle"
608+
style="pointer-events: none"
609+
aria-hidden="true"
610+
/>
583611
</template>
584612
<template #optionAnnotator="{ isAnnotator }">
585613
<span
614+
v-if="isAnnotator"
586615
class="i-carbon-edit-off w-6 h-6 text-fg-subtle"
587616
style="pointer-events: none"
588-
v-if="isAnnotator"
617+
aria-hidden="true"
618+
/>
619+
<span
620+
v-else
621+
class="i-carbon-edit w-6 h-6 text-fg-subtle"
622+
style="pointer-events: none"
623+
aria-hidden="true"
589624
/>
590-
<span class="i-carbon-edit w-6 h-6 text-fg-subtle" style="pointer-events: none" v-else />
591625
</template>
592626
</VueUiXy>
593627
<template #fallback>
594628
<div class="min-h-[260px]" />
595629
</template>
596630
</ClientOnly>
597631

632+
<!-- Empty state when no chart data -->
633+
<div
634+
v-if="inModal && !chartData.dataset && !pending"
635+
class="min-h-[260px] flex items-center justify-center text-fg-subtle font-mono text-sm"
636+
>
637+
{{ t('package.downloads.no_data') }}
638+
</div>
639+
598640
<div
599641
v-if="pending"
642+
role="status"
643+
aria-live="polite"
600644
class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-xs text-fg-subtle font-mono bg-bg/70 backdrop-blur px-3 py-2 rounded-md border border-border"
601645
>
602-
Loading…
646+
{{ t('package.downloads.loading') }}
603647
</div>
604648
</div>
605649
</template>

0 commit comments

Comments
 (0)