@@ -4,8 +4,10 @@ import {
44 Typography
55} from "@/components/ui" ;
66import { ChartConfig , ChartContainer , ChartTooltip , ChartTooltipContent } from "@/components/ui/chart" ;
7+ import { DesignCardTint , DesignCategoryTabs , DesignPillToggle } from "@/components/design-components" ;
78import { UserAvatar } from '@stackframe/stack' ;
89import { fromNow , isWeekend } from '@stackframe/stack-shared/dist/utils/dates' ;
10+ import { urlString } from "@stackframe/stack-shared/dist/utils/urls" ;
911import { useState } from "react" ;
1012import { Bar , BarChart , CartesianGrid , Cell , Pie , PieChart , TooltipProps , XAxis , YAxis } from "recharts" ;
1113
@@ -181,13 +183,13 @@ export function ChartCard({
181183 className ?: string ,
182184 gradientColor ?: GradientColor ,
183185} ) {
184- const hoverTints : Record < GradientColor , string > = {
185- blue : "group-hover:bg-slate-500/[0.02] " ,
186- purple : "group-hover:bg-slate-500/[0.02] " ,
187- green : "group-hover:bg-slate-500/[0.02] " ,
188- orange : "group-hover:bg-slate-500/[0.02] " ,
189- slate : "group-hover:bg-slate-500/[0.02] " ,
190- cyan : "group-hover:bg-slate-500/[0.02] " ,
186+ const designCardGradients : Record < GradientColor , "blue" | "cyan" | "purple" | "green" | "orange" | "default" > = {
187+ blue : "blue " ,
188+ purple : "purple " ,
189+ green : "green " ,
190+ orange : "orange " ,
191+ slate : "default " ,
192+ cyan : "cyan " ,
191193 } ;
192194
193195 return (
@@ -203,23 +205,17 @@ export function ChartCard({
203205 }
204206 ` }
205207 </ style >
206- < div className = { cn (
207- "group relative rounded-2xl bg-white/90 dark:bg-background/60 backdrop-blur-xl transition-all duration-150 hover:transition-none chart-card-tooltip-escape" ,
208- "ring-1 ring-black/[0.06] hover:ring-black/[0.1] dark:ring-white/[0.06] dark:hover:ring-white/[0.1]" ,
209- "shadow-sm hover:shadow-md hover:z-10" ,
210- className
211- ) } >
212- { /* Subtle glassmorphic background */ }
213- < div className = "absolute inset-0 bg-gradient-to-br from-foreground/[0.06] via-foreground/[0.02] dark:from-foreground/[0.03] dark:via-foreground/[0.01] to-transparent pointer-events-none rounded-2xl overflow-hidden" />
214- { /* Accent hover tint */ }
215- < div className = { cn (
216- "absolute inset-0 transition-colors duration-150 group-hover:transition-none pointer-events-none rounded-2xl overflow-hidden" ,
217- hoverTints [ gradientColor ]
218- ) } />
219- < div className = "relative h-full flex flex-col" >
208+ < DesignCardTint
209+ gradient = { designCardGradients [ gradientColor ] }
210+ className = { cn (
211+ "chart-card-tooltip-escape h-full overflow-visible hover:z-10 [&>div]:h-full [&>div]:min-h-0 [&>div]:flex [&>div]:flex-col [&>div]:overflow-visible" ,
212+ className
213+ ) }
214+ >
215+ < div className = "h-full flex flex-col" >
220216 { children }
221217 </ div >
222- </ div >
218+ </ DesignCardTint >
223219 </ >
224220 ) ;
225221}
@@ -231,30 +227,26 @@ export function TimeRangeToggle({
231227 timeRange : TimeRange ,
232228 onTimeRangeChange : ( range : TimeRange ) => void ,
233229} ) {
234- const options : { value : TimeRange , label : string } [ ] = [
235- { value : '7d' , label : '7d' } ,
236- { value : '30d' , label : '30d' } ,
237- { value : 'all' , label : 'All' } ,
230+ const options : { id : TimeRange , label : string } [ ] = [
231+ { id : '7d' , label : '7d' } ,
232+ { id : '30d' , label : '30d' } ,
233+ { id : 'all' , label : 'All' } ,
238234 ] ;
239235
240236 return (
241- < div className = "inline-flex items-center gap-1 rounded-xl bg-foreground/[0.04] p-1 backdrop-blur-sm" >
242- { options . map ( ( option ) => (
243- < button
244- key = { option . value }
245- type = "button"
246- onClick = { ( ) => onTimeRangeChange ( option . value ) }
247- className = { cn (
248- "px-3 py-1.5 text-xs font-medium rounded-lg transition-all duration-150 hover:transition-none" ,
249- timeRange === option . value
250- ? "bg-background text-foreground shadow-sm ring-1 ring-foreground/[0.06] dark:bg-[hsl(240,71%,70%)]/10 dark:text-[hsl(240,71%,90%)] dark:ring-[hsl(240,71%,70%)]/20"
251- : "text-muted-foreground hover:text-foreground hover:bg-background/50"
252- ) }
253- >
254- { option . label }
255- </ button >
256- ) ) }
257- </ div >
237+ < DesignPillToggle
238+ options = { options }
239+ selected = { timeRange }
240+ size = "sm"
241+ glassmorphic
242+ onSelect = { ( selectedId ) => {
243+ if ( selectedId === '7d' || selectedId === '30d' || selectedId === 'all' ) {
244+ onTimeRangeChange ( selectedId ) ;
245+ return ;
246+ }
247+ throw new Error ( `Unsupported time range selected: ${ selectedId } ` ) ;
248+ } }
249+ />
258250 ) ;
259251}
260252
@@ -295,15 +287,6 @@ export function TabbedMetricsCard({
295287 // For "all" time range, use totalAllTime if provided (which includes data beyond 30 days)
296288 const displayTotal = timeRange === 'all' && totalAllTime !== undefined ? totalAllTime : total ;
297289
298- const activeTabColors : Record < GradientColor , string > = {
299- blue : "bg-blue-500 dark:bg-[hsl(240,71%,70%)]" ,
300- purple : "bg-purple-500 dark:bg-[hsl(200,91%,70%)]" ,
301- green : "bg-emerald-500 dark:bg-[hsl(200,91%,70%)]" ,
302- orange : "bg-orange-500 dark:bg-[hsl(240,71%,70%)]" ,
303- slate : "bg-slate-500 dark:bg-[hsl(240,71%,70%)]" ,
304- cyan : "bg-cyan-500 dark:bg-[hsl(200,91%,70%)]" ,
305- } ;
306-
307290 const hoverAccentColors : Record < GradientColor , string > = {
308291 blue : "hover:bg-blue-500/[0.06]" ,
309292 purple : "hover:bg-purple-500/[0.06]" ,
@@ -313,40 +296,31 @@ export function TabbedMetricsCard({
313296 cyan : "hover:bg-cyan-500/[0.06]" ,
314297 } ;
315298
316- const activeColorClass = activeTabColors [ gradientColor ] ;
317299 const hoverAccentClass = hoverAccentColors [ gradientColor ] ;
300+ const tabsGradient : "blue" | "cyan" | "purple" | "green" | "orange" | "default" = gradientColor === "slate" ? "default" : gradientColor ;
318301
319302 return (
320303 < ChartCard className = "h-full flex flex-col" gradientColor = { gradientColor } >
321304 < div className = { cn ( "flex items-center justify-between border-b border-foreground/[0.05]" , compact ? "px-4" : "px-5" ) } >
322- < div className = "flex items-center gap-1" >
323- < button
324- type = "button"
325- onClick = { ( ) => setView ( 'chart' ) }
326- className = { cn (
327- "relative px-3 py-3.5 text-xs font-medium transition-all duration-150 hover:transition-none rounded-t-lg" ,
328- view === 'chart' ? "text-foreground" : "text-muted-foreground hover:text-foreground"
329- ) }
330- >
331- { config . name }
332- { view === 'chart' && (
333- < div className = { cn ( "absolute bottom-0 left-3 right-3 h-0.5 rounded-full" , activeColorClass ) } />
334- ) }
335- </ button >
336- < button
337- type = "button"
338- onClick = { ( ) => setView ( 'list' ) }
339- className = { cn (
340- "relative px-3 py-3.5 text-xs font-medium transition-all duration-150 hover:transition-none rounded-t-lg" ,
341- view === 'list' ? "text-foreground" : "text-muted-foreground hover:text-foreground"
342- ) }
343- >
344- { listTitle }
345- { view === 'list' && (
346- < div className = { cn ( "absolute bottom-0 left-3 right-3 h-0.5 rounded-full" , activeColorClass ) } />
347- ) }
348- </ button >
349- </ div >
305+ < DesignCategoryTabs
306+ categories = { [
307+ { id : "chart" , label : config . name } ,
308+ { id : "list" , label : listTitle } ,
309+ ] }
310+ selectedCategory = { view }
311+ onSelect = { ( selectedId ) => {
312+ if ( selectedId === "chart" || selectedId === "list" ) {
313+ setView ( selectedId ) ;
314+ return ;
315+ }
316+ throw new Error ( `Unsupported metrics tab selected: ${ selectedId } ` ) ;
317+ } }
318+ showBadge = { false }
319+ size = "sm"
320+ glassmorphic = { false }
321+ gradient = { tabsGradient }
322+ className = "flex-1 min-w-0 border-0 [&>button]:rounded-none [&>button]:px-3 [&>button]:py-3.5 [&>button]:text-xs"
323+ />
350324
351325 { view === 'chart' && showTotal && (
352326 < span className = "text-lg font-semibold text-foreground tabular-nums" >
@@ -390,7 +364,7 @@ export function TabbedMetricsCard({
390364 { listData . map ( ( user ) => (
391365 < button
392366 key = { user . id }
393- onClick = { ( ) => router . push ( `/projects/${ projectId } /users/${ user . id } ` ) }
367+ onClick = { ( ) => router . push ( urlString `/projects/${ projectId } /users/${ user . id } ` ) }
394368 className = { cn (
395369 "w-full flex items-center gap-3 p-2.5 rounded-xl transition-all duration-150 hover:transition-none text-left group" ,
396370 hoverAccentClass
0 commit comments