bug fixers 10/20

This commit is contained in:
2026-10-20 15:33:19 +05:30
parent 0e423b806c
commit 47479fb4f2
43 changed files with 13191 additions and 811 deletions
@@ -7,6 +7,9 @@ export interface KpiTileSpec {
label: string
value: number
icon?: LucideIcon
/** Makes the tile a filter toggle for the list below (see StatCard). */
onClick?: () => void
active?: boolean
}
/**
@@ -20,7 +23,7 @@ export function KpiRow({ tiles }: { tiles: KpiTileSpec[] | null }) {
<div className="mb-4 grid grid-cols-1 gap-3 sm:grid-cols-4">
{tiles
? tiles.map((tile) => (
<StatCard key={tile.label} label={tile.label} value={tile.value} icon={tile.icon} />
<StatCard key={tile.label} label={tile.label} value={tile.value} icon={tile.icon} onClick={tile.onClick} active={tile.active} />
))
: Array.from({ length: 2 }).map((_, i) => <Skeleton key={i} className="h-24 rounded-2xl" />)}
</div>
@@ -58,6 +58,10 @@ export interface StatCardProps {
valueClassName?: string
/** Extra classes for the icon tile, e.g. a warning tint instead of the primary one. */
iconClassName?: string
/** Makes the card a toggle button that filters the list below it (click again to clear). */
onClick?: () => void
/** The card's filter is the one currently applied — highlighted and announced as pressed. */
active?: boolean
}
/**
@@ -76,18 +80,29 @@ export function StatCard({
className,
valueClassName,
iconClassName,
onClick,
active = false,
}: StatCardProps) {
const hasDelta = typeof delta === "number" && delta !== 0
const isPositive = hasDelta && delta > 0
const isGood = hasDelta && (invert ? !isPositive : isPositive)
// Clickable cards are real buttons (keyboard + screen reader); plain cards stay divs.
const Root = onClick ? "button" : "div"
return (
// A size container: the card reads its *own* width, so it lays out the same whether a page puts
// it in a 2-up phone grid or a wide desktop row.
<div
<Root
{...(onClick
? { type: "button" as const, onClick, "aria-pressed": active, title: active ? "Click to clear this filter" : "Click to filter the list" }
: {})}
className={cn(
"@container min-w-0 rounded-2xl bg-card px-4 py-5 shadow-sm ring-1 ring-foreground/10 transition-all duration-200 hover:-translate-y-0.5 hover:shadow-lg hover:ring-primary/30",
className
"@container min-w-0 rounded-2xl bg-card px-4 py-5 text-left shadow-sm ring-1 ring-foreground/10 transition-all duration-200 hover:-translate-y-0.5 hover:shadow-lg hover:ring-primary/30",
onClick && "w-full cursor-pointer focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none",
className,
// After className so the "selected" look wins over a card's own tint.
active && "bg-primary/5 ring-2 ring-primary"
)}
>
{/* Wide enough: icon beside the text. Narrow (e.g. two per row on a phone): icon above, so the
@@ -122,10 +137,11 @@ export function StatCard({
{deltaLabel && <span className="text-muted-foreground">{deltaLabel}</span>}
</div>
) : (
hint && <p className="truncate text-xs text-muted-foreground">{hint}</p>
// Wraps rather than truncating, so a hint like "12 sales by card" is never cut off in a narrow card.
hint && <p className="text-xs leading-snug wrap-break-word text-muted-foreground">{hint}</p>
)}
</div>
</div>
</div>
</Root>
)
}