@@ -14,13 +14,15 @@ import { Button } from "./ui/button";
1414import { Tooltip , TooltipPopup , TooltipTrigger } from "./ui/tooltip" ;
1515
1616type TokenActivityMode = "daily" | "weekly" | "cumulative" ;
17+ type TokenActivityRange = "13-weeks" | "1-year" ;
1718
1819const DAY_MS = 86_400_000 ;
1920const ACTIVITY_MODE_LABELS : Record < TokenActivityMode , string > = {
2021 daily : "Daily" ,
2122 weekly : "Weekly" ,
2223 cumulative : "Cumulative" ,
2324} ;
25+ const TOKEN_ACTIVITY_LEGEND_LEVELS = [ 0 , 20 , 40 , 65 , 90 ] as const ;
2426
2527interface TokenActivityCell {
2628 readonly dateKey : string ;
@@ -109,13 +111,13 @@ function formatTokenActivityWeekTooltip(
109111}
110112
111113function tokenActivityColorClass ( intensityPercent : number ) : string {
112- if ( intensityPercent <= 0 ) return "bg-muted/35 " ;
113- if ( intensityPercent < 15 ) return "bg-primary/10 " ;
114- if ( intensityPercent < 30 ) return "bg-primary/25 " ;
115- if ( intensityPercent < 45 ) return "bg-primary/45 " ;
116- if ( intensityPercent < 65 ) return "bg-primary/65 " ;
117- if ( intensityPercent < 85 ) return "bg-primary/85 " ;
118- return "bg-primary" ;
114+ if ( intensityPercent <= 0 ) return "bg-muted/70 " ;
115+ if ( intensityPercent < 15 ) return "bg-primary-graph/30 " ;
116+ if ( intensityPercent < 30 ) return "bg-primary-graph/45 " ;
117+ if ( intensityPercent < 45 ) return "bg-primary-graph/60 " ;
118+ if ( intensityPercent < 65 ) return "bg-primary-graph/75 " ;
119+ if ( intensityPercent < 85 ) return "bg-primary-graph/90 " ;
120+ return "bg-primary-graph " ;
119121}
120122
121123function scaleTokenActivityIntensity ( value : number , maxValue : number ) : number {
@@ -259,8 +261,8 @@ function TokenActivitySquare(props: { readonly cell: TokenActivityCell }) {
259261 role = "img"
260262 aria-label = { props . cell . tooltip }
261263 className = { cn (
262- "block aspect-square w-full min-w-0 cursor-default rounded-[var(--app-radius-tiny)] transition-[transform, filter,box-shadow ] duration-150 ease-out" ,
263- "hover:z-10 hover:-translate-y-px hover:scale-110 hover: brightness-125 hover:ring-1 hover:ring-primary/45 " ,
264+ "block aspect-square w-full min-w-0 cursor-default rounded-[var(--app-radius-tiny)] transition-[filter] duration-150 ease-out" ,
265+ "hover:brightness-125" ,
264266 tokenActivityColorClass ( props . cell . intensityPercent ) ,
265267 ) }
266268 />
@@ -283,7 +285,7 @@ function TokenActivityWeekColumn(props: {
283285} ) {
284286 if ( props . mode === "daily" ) {
285287 return (
286- < div className = "grid min-w-0 grid-rows-7 gap-1 " >
288+ < div className = "grid min-w-0 grid-rows-7 gap-0.5 " >
287289 { props . week . cells . map ( ( cell ) => (
288290 < TokenActivitySquare key = { cell . dateKey } cell = { cell } />
289291 ) ) }
@@ -300,15 +302,15 @@ function TokenActivityWeekColumn(props: {
300302 < div
301303 role = "img"
302304 aria-label = { props . week . tooltip }
303- className = "group grid min-w-0 cursor-default grid-rows-7 gap-1 "
305+ className = "group grid min-w-0 cursor-default grid-rows-7 gap-0.5 "
304306 >
305307 { props . week . cells . map ( ( cell ) => (
306308 < span
307309 key = { cell . dateKey }
308310 aria-hidden = "true"
309311 className = { cn (
310- "block aspect-square w-full min-w-0 rounded-[var(--app-radius-tiny)] transition-[transform, filter,box-shadow ] duration-150 ease-out" ,
311- "group-hover:z-10 group-hover:scale-105 group-hover: brightness-125 group-hover:ring-1 group-hover:ring-primary/40 " ,
312+ "block aspect-square w-full min-w-0 rounded-[var(--app-radius-tiny)] transition-[filter] duration-150 ease-out" ,
313+ "group-hover:brightness-125" ,
312314 tokenActivityColorClass ( cell . intensityPercent ) ,
313315 ) }
314316 />
@@ -327,6 +329,63 @@ function TokenActivityWeekColumn(props: {
327329 ) ;
328330}
329331
332+ function TokenActivityGrid ( props : {
333+ readonly weeks : ReadonlyArray < TokenActivityWeek > ;
334+ readonly mode : TokenActivityMode ;
335+ readonly usageLabel : string ;
336+ readonly className ?: string | undefined ;
337+ } ) {
338+ return (
339+ < div className = { cn ( "min-w-0 pb-1" , props . className ) } >
340+ < div
341+ className = "grid min-w-0 gap-0.5 overflow-visible"
342+ style = { {
343+ gridTemplateColumns : `repeat(${ props . weeks . length } , minmax(0, 1fr))` ,
344+ } }
345+ aria-label = { `${ props . usageLabel } ${ ACTIVITY_MODE_LABELS [ props . mode ] . toLowerCase ( ) } token activity` }
346+ >
347+ { props . weeks . map ( ( week ) => (
348+ < TokenActivityWeekColumn key = { week . weekKey } week = { week } mode = { props . mode } />
349+ ) ) }
350+ </ div >
351+ < div
352+ className = "mt-2 grid min-w-0 gap-0.5"
353+ style = { {
354+ gridTemplateColumns : `repeat(${ props . weeks . length } , minmax(0, 1fr))` ,
355+ } }
356+ aria-hidden
357+ >
358+ { props . weeks . map ( ( week ) => (
359+ < span
360+ key = { week . weekKey }
361+ className = "h-3 overflow-visible text-[10px] leading-none text-muted-foreground"
362+ >
363+ { week . monthLabel }
364+ </ span >
365+ ) ) }
366+ </ div >
367+ < div
368+ className = "mt-1.5 flex items-center justify-end gap-1 text-[10px] leading-none text-muted-foreground"
369+ aria-label = "Token activity intensity from less to more"
370+ >
371+ < span > Less</ span >
372+ < span className = "flex items-center gap-0.5" aria-hidden >
373+ { TOKEN_ACTIVITY_LEGEND_LEVELS . map ( ( level ) => (
374+ < span
375+ key = { level }
376+ className = { cn (
377+ "size-2.5 rounded-[var(--app-radius-tiny)]" ,
378+ tokenActivityColorClass ( level ) ,
379+ ) }
380+ />
381+ ) ) }
382+ </ span >
383+ < span > More</ span >
384+ </ div >
385+ </ div >
386+ ) ;
387+ }
388+
330389function UsageLimitBar ( props : {
331390 readonly usageLabel : string ;
332391 readonly window : ProviderAccountUsageWindowPresentation ;
@@ -365,11 +424,14 @@ export function ProviderUsageDashboard(props: {
365424 readonly accountUsageResetInFlight ?: boolean | undefined ;
366425} ) {
367426 const [ activityMode , setActivityMode ] = useState < TokenActivityMode > ( "daily" ) ;
427+ const [ activityRange , setActivityRange ] = useState < TokenActivityRange > ( "13-weeks" ) ;
368428 const tokenUsage = props . usage . tokenUsage ?? null ;
369429 const activityWeeks = useMemo (
370430 ( ) => buildTokenActivityWeeks ( tokenUsage ?. buckets ?? [ ] , activityMode ) ,
371431 [ activityMode , tokenUsage ?. buckets ] ,
372432 ) ;
433+ const mobileActivityWeeks =
434+ activityRange === "13-weeks" ? activityWeeks . slice ( - 13 ) : activityWeeks ;
373435 const showLimits = props . showLimits ?? true ;
374436 const hasLimits =
375437 showLimits &&
@@ -464,16 +526,31 @@ export function ProviderUsageDashboard(props: {
464526 { tokenUsage ? (
465527 < section className = "grid gap-4" aria-label = { `${ tokenUsage . label } activity` } >
466528 { tokenUsage . summary . length > 0 ? (
467- < div className = "grid overflow-hidden rounded-lg border border-border/70 sm:grid-cols-5" >
468- { tokenUsage . summary . map ( ( entry ) => (
469- < div
470- key = { entry . key }
471- className = "min-w-0 border-border/60 border-t px-4 py-3 first:border-t-0 sm:border-t-0 sm:border-l sm:first:border-l-0"
472- >
473- < div className = "truncate text-sm font-medium text-foreground" > { entry . value } </ div >
474- < div className = "mt-0.5 truncate text-xs text-muted-foreground" > { entry . label } </ div >
475- </ div >
476- ) ) }
529+ < div className = "grid grid-cols-2 overflow-hidden rounded-lg border border-border/70 sm:grid-cols-5" >
530+ { tokenUsage . summary . map ( ( entry , index ) => {
531+ const isFirstMobileRow = index < 2 ;
532+ const isRightMobileColumn = index % 2 === 1 ;
533+ const isLastUnpairedEntry =
534+ tokenUsage . summary . length % 2 === 1 && index === tokenUsage . summary . length - 1 ;
535+ return (
536+ < div
537+ key = { entry . key }
538+ className = { cn (
539+ "min-w-0 border-border/60 px-3 py-3 sm:col-span-1 sm:border-t-0 sm:border-l sm:px-4 sm:first:border-l-0" ,
540+ ! isFirstMobileRow && "border-t" ,
541+ isRightMobileColumn && "border-l" ,
542+ isLastUnpairedEntry && "col-span-2" ,
543+ ) }
544+ >
545+ < div className = "truncate text-sm font-medium text-foreground" >
546+ { entry . value }
547+ </ div >
548+ < div className = "mt-0.5 truncate text-xs text-muted-foreground" >
549+ { entry . label }
550+ </ div >
551+ </ div >
552+ ) ;
553+ } ) }
477554 </ div >
478555 ) : null }
479556
@@ -499,36 +576,46 @@ export function ProviderUsageDashboard(props: {
499576 ) ) }
500577 </ div >
501578 </ div >
502- { activityWeeks . length > 0 ? (
503- < div className = "min-w-0 pb-1" >
504- < div
505- className = "grid min-w-0 gap-1 overflow-visible"
506- style = { {
507- gridTemplateColumns : `repeat(${ activityWeeks . length } , minmax(0, 1fr))` ,
508- } }
509- aria-label = { `${ tokenUsage . label } ${ ACTIVITY_MODE_LABELS [ activityMode ] . toLowerCase ( ) } token activity` }
579+ < div
580+ role = "group"
581+ className = "inline-flex w-fit rounded-md border border-border/70 bg-muted/20 p-0.5 sm:hidden"
582+ aria-label = "Token activity range"
583+ >
584+ { ( [ "13-weeks" , "1-year" ] as const ) . map ( ( range ) => (
585+ < button
586+ key = { range }
587+ type = "button"
588+ className = { cn (
589+ "h-7 cursor-pointer rounded px-2.5 text-xs transition-colors" ,
590+ activityRange === range
591+ ? "bg-background text-foreground shadow-xs"
592+ : "text-muted-foreground hover:text-foreground" ,
593+ ) }
594+ onClick = { ( ) => setActivityRange ( range ) }
595+ aria-pressed = { activityRange === range }
510596 >
511- { activityWeeks . map ( ( week ) => (
512- < TokenActivityWeekColumn key = { week . weekKey } week = { week } mode = { activityMode } />
513- ) ) }
597+ { range === "13-weeks" ? "13 weeks" : "1 year" }
598+ </ button >
599+ ) ) }
600+ </ div >
601+ { activityWeeks . length > 0 ? (
602+ < >
603+ < div className = "overflow-x-auto sm:hidden" >
604+ < TokenActivityGrid
605+ weeks = { mobileActivityWeeks }
606+ mode = { activityMode }
607+ usageLabel = { tokenUsage . label }
608+ className = { activityRange === "1-year" ? "min-w-[40rem]" : undefined }
609+ />
514610 </ div >
515- < div
516- className = "mt-2 grid min-w-0 gap-1"
517- style = { {
518- gridTemplateColumns : `repeat(${ activityWeeks . length } , minmax(0, 1fr))` ,
519- } }
520- aria-hidden
521- >
522- { activityWeeks . map ( ( week ) => (
523- < span
524- key = { week . weekKey }
525- className = "h-3 overflow-visible text-[10px] leading-none text-muted-foreground/75"
526- >
527- { week . monthLabel }
528- </ span >
529- ) ) }
611+ < div className = "hidden sm:block" >
612+ < TokenActivityGrid
613+ weeks = { activityWeeks }
614+ mode = { activityMode }
615+ usageLabel = { tokenUsage . label }
616+ />
530617 </ div >
531- </ div >
618+ </ >
532619 ) : (
533620 < p className = "text-xs text-muted-foreground" > No token activity has been reported.</ p >
534621 ) }
0 commit comments