11<script setup lang="ts">
22import { VueUiSparkline } from ' vue-data-ui/vue-ui-sparkline'
33import { useCssVariables } from ' ~/composables/useColors'
4- import { getPalette , type VueUiXyDatasetItem } from " vue-data-ui" ;
4+ import { getPalette , type VueUiXyDatasetItem } from ' vue-data-ui'
55import type { VueUiSparklineConfig , VueUiSparklineDatasetItem } from ' vue-data-ui'
66
77import (' vue-data-ui/style.css' )
88
99const props = defineProps <{
10- dataset? : Array <VueUiXyDatasetItem & {
11- color? : string
12- series: number []
13- dashIndices? : number []
14- }>
15- dates: number [],
10+ dataset? : Array <
11+ VueUiXyDatasetItem & {
12+ color? : string
13+ series: number []
14+ dashIndices? : number []
15+ }
16+ >
17+ dates: number []
1618 datetimeFormatterOptions: {
17- year: string ,
18- month: string ,
19+ year: string
20+ month: string
1921 day: string
2022 }
2123}>()
@@ -62,7 +64,7 @@ const { colors } = useCssVariables(
6264const isDarkMode = computed (() => resolvedMode .value === ' dark' )
6365
6466const datasets = computed <VueUiSparklineDatasetItem [][]>(() => {
65- return (props .dataset ?? []).map (( unit ) => {
67+ return (props .dataset ?? []).map (unit => {
6668 return props .dates .map ((period , i ) => {
6769 return {
6870 period ,
@@ -88,138 +90,145 @@ function resetHover() {
8890const configs = computed (() => {
8991 return (props .dataset || []).map <VueUiSparklineConfig >((unit , i ) => {
9092 return {
91- a11y: {
92- translations: {
93- keyboardNavigation: $t (
94- ' package.trends.chart_assistive_text.keyboard_navigation_horizontal' ,
95- ),
96- tableAvailable: $t (' package.trends.chart_assistive_text.table_available' ),
97- tableCaption: $t (' package.trends.chart_assistive_text.table_caption' ),
93+ a11y: {
94+ translations: {
95+ keyboardNavigation: $t (
96+ ' package.trends.chart_assistive_text.keyboard_navigation_horizontal' ,
97+ ),
98+ tableAvailable: $t (' package.trends.chart_assistive_text.table_available' ),
99+ tableCaption: $t (' package.trends.chart_assistive_text.table_caption' ),
100+ },
101+ },
102+ theme: isDarkMode .value ? ' dark' : ' ' ,
103+ skeletonConfig: {
104+ style: {
105+ backgroundColor: ' transparent' ,
106+ dataLabel: {
107+ show: true ,
108+ color: ' transparent' ,
109+ },
110+ area: {
111+ color: colors .value .borderHover ,
112+ useGradient: false ,
113+ opacity: 10 ,
114+ },
115+ line: {
116+ color: colors .value .borderHover ,
117+ },
118+ },
98119 },
99- },
100- theme: isDarkMode .value ? ' dark' : ' ' ,
101- skeletonConfig: {
120+ skeletonDataset: Array .from ({ length: unit .series .length }, () => 0 ),
102121 style: {
103122 backgroundColor: ' transparent' ,
104- dataLabel: {
105- show: true ,
106- color: ' transparent' ,
107- },
123+ animation: { show: false },
108124 area: {
109125 color: colors .value .borderHover ,
110126 useGradient: false ,
111127 opacity: 10 ,
112128 },
129+ dataLabel: {
130+ offsetX: - 12 ,
131+ fontSize: 24 ,
132+ bold: false ,
133+ color: colors .value .fg ,
134+ datetimeFormatter: {
135+ enable: true ,
136+ locale: locale .value ,
137+ useUTC: true ,
138+ options: props .datetimeFormatterOptions ,
139+ },
140+ },
113141 line: {
114- color: colors . value . borderHover ,
142+ color: unit . color ?? palette [ i ] ,
115143 },
116- },
117- },
118- skeletonDataset: Array .from ({ length: unit .series .length }, () => 0 ),
119- style: {
120- backgroundColor: ' transparent' ,
121- animation: { show: false },
122- area: {
123- color: colors .value .borderHover ,
124- useGradient: false ,
125- opacity: 10 ,
126- },
127- dataLabel: {
128- offsetX: - 12 ,
129- fontSize: 24 ,
130- bold: false ,
131- color: colors .value .fg ,
132- datetimeFormatter: {
133- enable: true ,
134- locale: locale .value ,
135- useUTC: true ,
136- options: props .datetimeFormatterOptions ,
144+ plot: {
145+ radius: 6 ,
146+ stroke: isDarkMode .value ? ' oklch(0.985 0 0)' : ' oklch(0.145 0 0)' ,
147+ },
148+ title: {
149+ fontSize: 12 ,
150+ color: colors .value .fgSubtle ,
151+ bold: false ,
137152 },
138- },
139- line: {
140- color: unit .color ?? palette [i ],
141- },
142- plot: {
143- radius: 6 ,
144- stroke: isDarkMode .value ? ' oklch(0.985 0 0)' : ' oklch(0.145 0 0)' ,
145- },
146- title: {
147- fontSize: 12 ,
148- color: colors .value .fgSubtle ,
149- bold: false ,
150- },
151153
152- verticalIndicator: {
153- strokeDasharray: 5 ,
154- color: colors .value .fgSubtle ,
155- },
156- padding: {
157- left: 0 ,
158- right: 0 ,
159- top: 0 ,
160- bottom: 0 ,
154+ verticalIndicator: {
155+ strokeDasharray: 5 ,
156+ color: colors .value .fgSubtle ,
157+ },
158+ padding: {
159+ left: 0 ,
160+ right: 0 ,
161+ top: 0 ,
162+ bottom: 0 ,
163+ },
161164 },
162- },
163- }
165+ }
164166 })
165167})
166168 </script >
167169
168170<template >
169- <div >
170- <div class =" grid gap-8 sm:grid-cols-2" >
171- <ClientOnly v-for =" (config, i) in configs" :key =" `config_${i}`" >
172- <div @mouseleave =" resetHover" class =" w-full max-w-[400px] mx-auto" >
173- <div class =" flex gap-2 place-items-center" >
174- <div class =" h-3 w-3" >
175- <svg viewBox =" 0 0 2 2" class =" w-full" >
176- <rect x =" 0" y =" 0" width =" 2" height =" 2" rx =" 0.3" :fill =" dataset?.[i]?.color ?? palette[i]" />
177- </svg >
178- </div >
179- {{ applyEllipsis(dataset?.[i]?.name ?? '', 28) }}
180- </div >
181- <VueUiSparkline
182- :key =" `${i}_${step}`"
183- :config
184- :dataset =" datasets?.[i]"
185- :selectedIndex
186- @hoverIndex =" hoverIndex"
187- >
188- <!-- Keyboard navigation hint -->
189- <template #hint =" { isVisible } " >
190- <p v-if =" isVisible" class =" text-accent text-xs text-center mt-2" aria-hidden =" true" >
191- {{ $t('package.downloads.sparkline_nav_hint') }}
192- </p >
193- </template >
194-
195- <template #skeleton >
196- <!-- This empty div overrides the default built-in scanning animation on load -->
197- <div />
198- </template >
199- </VueUiSparkline >
171+ <div >
172+ <div class =" grid gap-8 sm:grid-cols-2" >
173+ <ClientOnly v-for =" (config, i) in configs" :key =" `config_${i}`" >
174+ <div @mouseleave =" resetHover" class =" w-full max-w-[400px] mx-auto" >
175+ <div class =" flex gap-2 place-items-center" >
176+ <div class =" h-3 w-3" >
177+ <svg viewBox =" 0 0 2 2" class =" w-full" >
178+ <rect
179+ x =" 0"
180+ y =" 0"
181+ width =" 2"
182+ height =" 2"
183+ rx =" 0.3"
184+ :fill =" dataset?.[i]?.color ?? palette[i]"
185+ />
186+ </svg >
200187 </div >
188+ {{ applyEllipsis(dataset?.[i]?.name ?? '', 28) }}
189+ </div >
190+ <VueUiSparkline
191+ :key =" `${i}_${step}`"
192+ :config
193+ :dataset =" datasets?.[i]"
194+ :selectedIndex
195+ @hoverIndex =" hoverIndex"
196+ >
197+ <!-- Keyboard navigation hint -->
198+ <template #hint =" { isVisible } " >
199+ <p v-if =" isVisible" class =" text-accent text-xs text-center mt-2" aria-hidden =" true" >
200+ {{ $t('package.downloads.sparkline_nav_hint') }}
201+ </p >
202+ </template >
201203
202- <template #fallback >
203- <!-- Skeleton matching VueUiSparkline layout (title 24px + SVG aspect 500:80) -->
204- <div class =" max-w-xs" >
205- <!-- Title row: fontSize * 2 = 24px -->
206- <div class =" h-6 flex items-center ps-3" >
207- <SkeletonInline class =" h-3 w-36" />
208- </div >
209- <!-- Chart area: matches SVG viewBox 500:80 -->
210- <div class =" aspect-[500/80] flex items-center" >
211- <!-- Data label (covers ~42% width, matching dataLabel.offsetX) -->
212- <div class =" w-[42%] flex items-center ps-0.5" >
213- <SkeletonInline class =" h-7 w-24" />
214- </div >
215- <!-- Sparkline line placeholder -->
216- <div class =" flex-1 flex items-end pe-3" >
217- <SkeletonInline class =" h-px w-full" />
218- </div >
219- </div >
220- </div >
204+ <template #skeleton >
205+ <!-- This empty div overrides the default built-in scanning animation on load -->
206+ <div />
221207 </template >
222- </ClientOnly >
208+ </VueUiSparkline >
223209 </div >
210+
211+ <template #fallback >
212+ <!-- Skeleton matching VueUiSparkline layout (title 24px + SVG aspect 500:80) -->
213+ <div class =" max-w-xs" >
214+ <!-- Title row: fontSize * 2 = 24px -->
215+ <div class =" h-6 flex items-center ps-3" >
216+ <SkeletonInline class =" h-3 w-36" />
217+ </div >
218+ <!-- Chart area: matches SVG viewBox 500:80 -->
219+ <div class =" aspect-[500/80] flex items-center" >
220+ <!-- Data label (covers ~42% width, matching dataLabel.offsetX) -->
221+ <div class =" w-[42%] flex items-center ps-0.5" >
222+ <SkeletonInline class =" h-7 w-24" />
223+ </div >
224+ <!-- Sparkline line placeholder -->
225+ <div class =" flex-1 flex items-end pe-3" >
226+ <SkeletonInline class =" h-px w-full" />
227+ </div >
228+ </div >
229+ </div >
230+ </template >
231+ </ClientOnly >
224232 </div >
225- </template >
233+ </div >
234+ </template >
0 commit comments