Stat Card
WhereUIการ์ดตัวเลข KPI พร้อมป้ายกำกับ ไอคอน และการเปลี่ยนแปลงขึ้น/ลง/คงที่ ตัวเลขใช้ tabular-nums ให้ต่อกันเป็นคอลัมน์สวยงาม
ติดตั้ง
Terminal
pnpm dlx shadcn@latest add @whereui/stat-cardต้องผูก registry และติดตั้ง @whereui/theme นำหน้าเสมอเมื่อติดตั้งครั้งแรก
การใช้งาน
import { StatCard } from "@/components/ui/stat-card"<StatCard label="พนักงานทั้งหมด" value={248} change={{ trend: "up", value: "+2.5% จากไตรมาสก่อน" }}/>ตัวอย่างเพิ่มเติม
แนวโน้มลง คงที่ และไอคอน
Props
| prop | type | ค่าเริ่มต้น | คำอธิบาย |
|---|---|---|---|
| label | React.ReactNode | — | ป้ายตัวชี้วัด เช่น "พนักงานทั้งหมด" |
| value | React.ReactNode | — | ตัวเลขหรือข้อความหลัก ขนาดใหญ่ — ตัวเลขแนะนำ <Money> เมื่อเป็นเงิน |
| icon | React.ReactNode | — | ไอคอนมุมขวาบน เช่น <UsersIcon /> |
| change | { trend: "up" | "down" | "flat"; value: string } | — | การเปลี่ยนแปลงเทียบก่อนหน้า — up สีเขียว down สีแดง flat สีเทา พร้อมไอคอนลูกศร |
ข้อควรรู้
- วางเรียงเป็นตารางด้วย grid เช่น grid gap-4 sm:grid-cols-3 — การ์ดยืดเต็มคอลัมน์เอง
ซอร์สโค้ด
components/ui/stat-card.tsx
import * as React from "react"import { MinusIcon, TrendingDownIcon, TrendingUpIcon,} from "lucide-react"import { cn } from "cn"import { Card } from "@/components/ui/card"export type StatCardChange = { /** ทิศทางการเปลี่ยนแปลง — up สีเขียว, down สีแดง, flat สีเทา */ trend: "up" | "down" | "flat" /** ข้อความการเปลี่ยนแปลง เช่น "+2.5%" หรือ "อดทนานี้ 12 รายการ" */ value: string}const changeTrendClasses = { up: "text-success", down: "text-destructive", flat: "text-muted-foreground",} as constconst changeTrendIcons = { up: TrendingUpIcon, down: TrendingDownIcon, flat: MinusIcon,} as constfunction StatCard({ className, label, value, icon, change, ...props}: React.ComponentProps<"div"> & { /** ป้ายตัวชี้วัด เช่น "พนักงานทั้งหมด" */ label: React.ReactNode /** ตัวเลข KPI */ value: React.ReactNode /** ไอคอนมุมขวาบน */ icon?: React.ReactNode /** การเปลี่ยนแปลงเทียบก่อนหน้า */ change?: StatCardChange}) { const TrendIcon = change ? changeTrendIcons[change.trend] : null return ( <Card data-slot="stat-card" className={cn("gap-0 px-4", className)} {...props}> <div className="flex items-start justify-between gap-2"> <div className="text-sm text-muted-foreground">{label}</div> {icon ? ( <div data-slot="stat-card-icon" aria-hidden="true" className="mt-0.5 shrink-0 text-muted-foreground [&_svg]:size-4" > {icon} </div> ) : null} </div> <div data-slot="stat-card-value" className="mt-2 font-heading text-2xl leading-tight font-semibold tracking-tight tabular-nums" > {value} </div> {change && TrendIcon ? ( <div data-slot="stat-card-change" className={cn( "mt-1.5 flex items-center gap-1 text-xs font-medium", changeTrendClasses[change.trend] )} > <TrendIcon aria-hidden="true" className="size-3.5" /> {change.value} </div> ) : null} </Card> )}export { StatCard }