کارت متریک (MetricCard)
کامپوننت نمایش معیارها و آمار به صورت خلاصه با پشتیبانی از نمودار و درصد تغییر
معرفی
کامپوننت Metric Card برای نمایش معیارها و آمارهای مهم به صورت خلاصه استفاده میشود. این کامپوننت شامل عنوان، مقدار، درصد تغییر و نمودار کوچک (Sparkline) است.
چه زمانی استفاده کنیم:
- برای نمایش KPIهای اصلی در داشبورد (بازدید، کاربران فعال، درآمد)
- وقتی نیاز به نمایش روند تغییرات با sparkline دارید
- برای متریکهایی که درصد تغییر نسبت به دوره قبل مهم است
چه زمانی استفاده نکنیم:
- برای نمایش یک عدد ساده بدون sparkline — از
StatDisplayاستفاده کنید - برای محتوای عمومی غیرعددی — از
Cardاستفاده کنید
استفاده پایه
کاربران فعال
- 4 دی: 4,000
- 4 دی: 4,150
- 4 دی: 4,100
- 4 دی: 4,250
- 4 دی: 4,300
- 4 دی: 4,400
- 4 دی: 4,350
- 4 دی: 4,500
- 4 دی: 4,550
- 4 دی: 4,600
- 4 دی: 4,580
- 4 دی: 4,605
زمین بازی
با تغییر تنظیمات زیر، کارت متریک را به صورت زنده مشاهده کنید.
بازدید کل
import {
MetricCard,
MetricCardHeader,
MetricCardLabel,
MetricCardContent,
MetricCardValue,
MetricCardDifferential,
MetricCardSparkline,
} from '@partodata/ui'
import { Users } from 'lucide-react'
export default function MyComponent() {
const data = [
{ value: 4000, timestamp: 1735689600 },
{ value: 4200, timestamp: 1735776000 },
{ value: 4100, timestamp: 1735862400 },
// ...
]
return (
<MetricCard>
<MetricCardHeader>
<MetricCardLabel icon={<Users className="h-3.5 w-3.5" />}>کاربران فعال</MetricCardLabel>
</MetricCardHeader>
<MetricCardContent>
<MetricCardValue>4,605</MetricCardValue>
<MetricCardDifferential comparisonLabel="نسبت به 1 تا 30 شهریور" direction="down" tone="negative">
-0.1%
</MetricCardDifferential>
</MetricCardContent>
<MetricCardSparkline data={data} dataKey="value" />
</MetricCard>
)
}حالتهای مختلف
با آیکونِ رنگی (MetricCardIcon)
MetricCardIcon یک باکسِ رنگی برای آیکون است — برای زمانی که آیکون فقط یک نشانهٔ کوچکِ کنارِ عنوان نیست، بلکه خودش بخشی از هویتِ بصریِ کارت است (مثلاً یک ردیف از KPIها که هرکدام با یک رنگِ معنایی جدا میشوند). رنگ از یک مجموعهٔ ثابتِ معنایی میآید — همان مجموعهای که Callout استفاده میکند، بهعلاوهٔ brand — نه هر رنگِ دلخواه:
کاربران فعال
import {
MetricCard,
MetricCardIcon,
MetricCardLabel,
MetricCardContent,
MetricCardValue,
MetricCardDifferential,
formatNumber,
} from '@partodata/ui'
import { Users } from 'lucide-react'
;<MetricCard className="flex flex-row items-center gap-3 px-4 py-3">
<MetricCardIcon variant="brand">
<Users />
</MetricCardIcon>
<div className="min-w-0 flex-1">
<MetricCardLabel>کاربران فعال</MetricCardLabel>
<MetricCardContent className="px-0">
<MetricCardValue>{formatNumber(4605)}</MetricCardValue>
<MetricCardDifferential comparisonLabel="نسبت به 1 تا 30 شهریور" direction="up" tone="positive">
+2.5%
</MetricCardDifferential>
</MetricCardContent>
</div>
</MetricCard>شش مقدارِ variant در دسترس است: brand، success، warning، destructive، info، neutral (پیشفرض). سه اندازه هم دارد: sm / md (پیشفرض) / lg. آیکونِ فرزند خودکار با اندازهٔ باکس همخوان میشود (نیازی به تنظیمِ دستیِ className روی خودِ آیکون نیست).
اگر رنگِ خارج از این مجموعه لازم دارید
MetricCardIcon عمداً بسته است — یک رنگِ اختصاصیِ محصول (مثلاً همرنگکردن با یک پالتِ status-dot مختص به یک پنل خاص)
به این معنا نیست که باید یک مقدارِ جدید به variant اضافه شود. className را مستقیماً روی MetricCardIcon بدهید تا
رنگِ پسزمینه/متن را override کند.
با آیکون و Tooltip
- 4 دی: 4,200
- 4 دی: 4,280
- 4 دی: 4,250
- 4 دی: 4,320
- 4 دی: 4,300
- 4 دی: 4,380
- 4 دی: 4,350
- 4 دی: 4,400
- 4 دی: 4,320
- 4 دی: 4,360
- 4 دی: 4,340
- 4 دی: 4,360
import { Users } from 'lucide-react'
;<MetricCard>
<MetricCardHeader>
<MetricCardLabel icon={<Users className="h-3.5 w-3.5" />} tooltip="تعداد کاربران فعال در 24 ساعت گذشته">
کاربران فعال
</MetricCardLabel>
</MetricCardHeader>
<MetricCardContent>
<MetricCardValue>4,605</MetricCardValue>
<MetricCardDifferential comparisonLabel="نسبت به 1 تا 30 شهریور" direction="up" tone="positive">
+2.5%
</MetricCardDifferential>
</MetricCardContent>
<MetricCardSparkline data={data} dataKey="value" />
</MetricCard>با لینک خارجی
درآمد ماهانه
باز کردن لینک- 4 دی: 150,000
- 4 دی: 155,000
- 4 دی: 158,000
- 4 دی: 165,000
- 4 دی: 170,000
- 4 دی: 180,000
- 4 دی: 185,000
- 4 دی: 195,000
- 4 دی: 210,000
- 4 دی: 225,000
- 4 دی: 240,000
- 4 دی: 250,000
<MetricCard>
<MetricCardHeader href="https://example.com/analytics">
<MetricCardLabel>درآمد ماهانه</MetricCardLabel>
</MetricCardHeader>
<MetricCardContent>
<MetricCardValue>250 میلیون</MetricCardValue>
<MetricCardDifferential comparisonLabel="نسبت به 1 تا 30 شهریور" direction="up" tone="positive">
+12.3%
</MetricCardDifferential>
</MetricCardContent>
<MetricCardSparkline data={revenueData} dataKey="value" />
</MetricCard>کارت پیوندی (href)
شاخصی که مقصد دارد («21 مسئلهٔ نیازمند توجه» ← فهرست آن مسائل) href میگیرد و کل کارت یک پیوند میشود: پیوند کشیده، حلقهٔ فوکوس دور کارت، hover:border-strong. پیوند با linkComponent قاب ساخته میشود (پیوند روتر، با کلیک وسط در تب تازه)، target ندارد، و tooltip برچسب، پیوند header و نمودار روی آن قابلاستفاده میمانند.
<MetricCard href="/issues?level=urgent,high" aria-label="مسائل نیازمند توجه: 21، باز کردن فهرست">
<MetricCardHeader>
<MetricCardLabel tooltip="مسائلی که شدت بالا یا فوری دارند">نیازمند توجه</MetricCardLabel>
</MetricCardHeader>
<MetricCardContent>
<MetricCardValue>21</MetricCardValue>
</MetricCardContent>
</MetricCard>قاعده: شمار روی کارت با شمار فهرست مقصد برابر باشد (کارتی که 21 میگوید فهرستی با 21 مورد باز میکند). نام پیوند aria-label کارت است (مقصد را در آن بنویسید) و بدون آن برچسب و مقدار؛ بدون هر دو در توسعه هشدار میدهد. aria-label روی div کارت نمیماند، چون نام روی عنصر بینقش ممنوع است و به پیوند میرود.
بدون نمودار
کاربران جدید
<MetricCard>
<MetricCardHeader>
<MetricCardLabel>کاربران جدید</MetricCardLabel>
</MetricCardHeader>
<MetricCardContent>
<MetricCardValue>1,234</MetricCardValue>
<MetricCardDifferential comparisonLabel="نسبت به 1 تا 30 شهریور" direction="up" tone="positive">
+5.2%
</MetricCardDifferential>
</MetricCardContent>
</MetricCard>حالت بارگذاری
<MetricCard isLoading={true} />حالت انگلیسی (LTR)
- Dec 24: 4,500
- Dec 24: 4,590
- Dec 24: 4,680
- Dec 24: 4,720
- Dec 24: 4,760
- Dec 24: 4,800
- Dec 24: 4,820
- Dec 24: 4,850
- Dec 24: 4,870
- Dec 24: 4,900
- Dec 24: 4,920
- Dec 24: 4,870
<MetricCard dir="ltr">
<MetricCardHeader href="https://example.com">
<MetricCardLabel tooltip="Number of active users in the last 24 hours">Active Users</MetricCardLabel>
</MetricCardHeader>
<MetricCardContent>
<MetricCardValue>4,605</MetricCardValue>
<MetricCardDifferential comparisonLabel="نسبت به 1 تا 30 شهریور" direction="down" tone="negative">
-0.1%
</MetricCardDifferential>
</MetricCardContent>
<MetricCardSparkline data={data} dataKey="value" />
</MetricCard>مقایسه با دورهٔ قبل
previous روی MetricCardValue مقدار دورهٔ قبل را کمرنگ کنار مقدار فعلی میگذارد، هر کدام با برچسبش. تغییر را با
TrendIndicator در ردیف بعد نشان دهید.
<MetricCard>
<MetricCardHeader>
<MetricCardLabel>بازدید</MetricCardLabel>
</MetricCardHeader>
<MetricCardContent>
<MetricCardValue currentLabel="این ماه" previous={{ value: '980', label: 'ماه قبل' }}>
1,240
</MetricCardValue>
</MetricCardContent>
<MetricCardContent>
<TrendIndicator comparisonLabel="نسبت به 7 روز قبل" value={26.5} showPercent size="sm" />
</MetricCardContent>
</MetricCard>Props
MetricCard
MetricCardHeader
MetricCardIcon
MetricCardLabel
MetricCardDifferential
MetricCardSparkline
مثالهای کاربردی
داشبورد آنالیتیکس
const metrics = [
{
label: 'کاربران فعال',
value: '4,605',
differential: '-0.1%',
direction: 'down',
tone: 'negative',
data: activeUsersData,
},
{
label: 'فروش امروز',
value: '123 میلیون',
differential: '+5.2%',
direction: 'up',
tone: 'positive',
data: salesData,
},
{
label: 'نرخ تبدیل',
value: '3.24%',
differential: '+0.8%',
direction: 'up',
tone: 'positive',
data: conversionData,
},
]
;<div className="grid grid-cols-1 md:grid-cols-3 gap-4" dir="rtl">
{metrics.map((metric) => (
<MetricCard key={metric.label}>
<MetricCardHeader>
<MetricCardLabel>{metric.label}</MetricCardLabel>
</MetricCardHeader>
<MetricCardContent>
<MetricCardValue>{metric.value}</MetricCardValue>
<MetricCardDifferential
comparisonLabel="نسبت به 1 تا 30 شهریور"
direction={metric.direction}
tone={metric.tone}
>
{metric.differential}
</MetricCardDifferential>
</MetricCardContent>
<MetricCardSparkline data={metric.data} dataKey="value" />
</MetricCard>
))}
</div>با React Query
پاسخ fetchMetrics باید نام دورهٔ واقعی مقایسه را در comparisonLabel برگرداند؛ بدون درصدِ قابل محاسبه و این برچسب، تغییر نمایش داده نمیشود.
import { useQuery } from '@tanstack/react-query'
function MetricCardWithQuery() {
const { data, isLoading } = useQuery({
queryKey: ['metrics'],
queryFn: fetchMetrics,
})
return (
<MetricCard isLoading={isLoading}>
<MetricCardHeader>
<MetricCardLabel>کاربران فعال</MetricCardLabel>
</MetricCardHeader>
<MetricCardContent>
<MetricCardValue>{data?.value.toLocaleString('en-US')}</MetricCardValue>
{typeof data?.trend === 'number' && data.comparisonLabel?.trim() && (
<MetricCardDifferential
comparisonLabel={data.comparisonLabel}
direction={data.trend > 0 ? 'up' : data.trend < 0 ? 'down' : 'neutral'}
tone={data.trend > 0 ? 'positive' : data.trend < 0 ? 'negative' : 'neutral'}
>
{data.trend > 0 ? '+' : ''}
{data.trend}%
</MetricCardDifferential>
)}
</MetricCardContent>
<MetricCardSparkline data={data?.history || []} dataKey="value" />
</MetricCard>
)
}نکات مهم
ساختار داده Sparkline
نمودار Sparkline نیاز به آرایهای از داده دارد که هر آیتم شامل:
type SparklineData = {
value: number // مقدار عددی
timestamp: number // Unix seconds؛ مثلا 1735689600
}جهت و رنگ Differential: دو محور
از 7.9 پیکان و رنگ دو محور جدا هستند، همان قرارداد TrendIndicator:
direction(up·down·neutral): پیکان و واژهٔ صفحهخوان. از علامت عدد میآید.tone(positive·negative·neutral): رنگ. از قطبیت شاخص میآید: افزایش نرخ تعاملpositive، افزایش نرخ خطاnegative، و افزایش حجم منشنهاneutral(حجم قطبیت ندارد: بیشتر بودن آن نه خبر خوب است نه بد).
<MetricCardDifferential direction="up" tone="neutral" comparisonLabel="نسبت به 7 روز قبل">24٪</MetricCardDifferential>
<MetricCardDifferential direction="down" tone="positive" comparisonLabel="نسبت به ماه قبل">8٪</MetricCardDifferential>variant="positive" / "negative" که هر دو را یکی میکرد منسوخ است. بدون هیچکدام از سه prop، همان رفتار قبل (یک افزایش مثبت) میماند.
رنگ هرگز تنها حاملِ معنا نیست: پیکان جهتدار هم رندر میشود و واژهٔ جهت («افزایش» / «کاهش») بهصورت متن پنهان برای صفحهخوانها منتشر میشود. اگر متنِ مقدار خودش جهت را میگوید و فلش را اضافی میدانید، showIcon={false} بدهید — ولی بدانید که در آن حالت تنها تفاوت دیداریِ مثبت و منفی رنگ است.
نمایش علامت مثبت/منفی
محتوای MetricCardDifferential داخل یک span با dir="ltr" رندر میشود؛ بنابراین علامتی که در ابتدای مقدار تایپ میکنید (مثل + یا -) در همان ترتیب و سمت درستِ عدد باقی میماند، حتی داخل کارت RTL. علامت را همانجا که میخواهید در ابتدای children بگذارید:
<MetricCardDifferential comparisonLabel="نسبت به 1 تا 30 شهریور" direction="down" tone="negative">-0.1٪</MetricCardDifferential>
<MetricCardDifferential comparisonLabel="نسبت به 1 تا 30 شهریور" direction="up" tone="positive">+12٪</MetricCardDifferential>اگر میخواهید علامت بهصورت جدا و بعد از مقدار افزوده شود، از prop sign استفاده کنید (کامپوننت علامت را بهطور خودکار درج یا جابهجا نمیکند؛ children دقیقاً همانگونه که پاس میدهید نمایش داده میشود):
<MetricCardDifferential comparisonLabel="نسبت به 1 تا 30 شهریور" direction="down" tone="negative" sign="-">
0.1٪
</MetricCardDifferential>RTL Support
کامپوننت به طور کامل از RTL پشتیبانی میکند. برای استفاده در محیط انگلیسی، dir="ltr" را به MetricCard اضافه کنید:
<MetricCard dir="ltr">{/* ... */}</MetricCard>وزن مقدار
مقدار (MetricCardValue) نقش عدد شاخص را دارد: .text-stat، 24 / 32 با وزن 400 (tabular-nums داخل آن در این فونت بیاثر است)، نه پررنگ و نه بلندتر از عنوان صفحه (22) بیش از یک پله. این کامپوننت همچنان KPIِ تیتر است، همراه sparkline و differential اختیاری. برای عدد آرام و بدون قاب از InlineStat استفاده کنید.
راهنمای استفاده
بکنید
- همیشه
MetricCardLabelبا tooltip توضیحی همراه کنید تا کاربر متریک را درک کند - برای دادههای async از propisLoadingاستفاده کنید تا layout shift نداشته باشید - در grid layout از تعداد یکسان MetricCard در هر ردیف استفاده کنید
نکنید
- بیش از 4 MetricCard در یک ردیف قرار ندهید — خوانایی کاهش مییابد - مقادیر را بدون واحد نمایش ندهید — کاربر باید
بداند عدد چه معنایی دارد - از
variant="negative"برای مقادیر مثبت استفاده نکنید — رنگ باید با معنا همخوانی داشته باشد
دسترسیپذیری
MetricCardLabelباtooltipیک دکمهٔ واقعی رندر میکند؛ بنابراین تعریف متریک با Tab قابل رسیدن است و با فوکوس (بدون ماوس) باز میشود. تیترh3و ترتیب عنوانها دستنخورده میماند- حالت بارگذاری از Skeleton استفاده میکند و layout shift را کاهش میدهد
- جهت differential با دو کانال منتقل میشود: فلش جهتدار (شکل) و رنگ. واژهٔ جهت («افزایش» / «کاهش») هم بهصورت
sr-onlyخوانده میشود. اگر باshowIcon={false}فلش را خاموش کنید، مسئولیت کانال دوم — مثلاً علامت+/-در children یا propsign— با شماست MetricCardSparklineیک ایستگاه Tab است: با فوکوس، اولین نقطه انتخاب میشود و کلیدهای ← و → مکاننما را روی نقطهها میبرند (Home/Endبرای ابتدا و انتها،Escapeبرای بستن). نمودار در RTL آینه نمیشود، پس → همیشه نقطهٔ سمت راست (متأخرتر) است- همان تاریخ و مقداری که در tooltipِ نمودار دیده میشود، بهصورت فهرست پنهان (
sr-only) هم منتشر میشود؛ برچسب نمودار ازlocaleساخته میشود («نمودار روند از … تا …») و دیگر در همهٔ زبانها فارسیِ ثابت نیست - کارت پیوندی یک پیوند واقعی (
a) با حلقهٔ فوکوس دور کل کارت است؛ نامشaria-labelکارت یا برچسب و مقدار است. tooltip برچسب و نمودار بالای پیوند میمانند و با صفحهکلید در دسترساند (ترتیب Tab: دکمهٔ tooltip، سپس پیوند) - لینک header نام دسترسپذیر خود را از یک متن پنهان (
sr-only) با محتوای «باز کردن لینک» میگیرد
کامپوننتهای مرتبط
- Card — اگر محتوای شما عمومی است و نیاز به sparkline ندارید، از Card استفاده کنید
- TrendIndicator — اگر عدد رشد بیرون از MetricCard میآید (جدول، ردیف)؛ همان دو محور
directionوtone - StatDisplay — اگر فقط یک عدد با label نیاز دارید بدون sparkline و differential، از StatDisplay استفاده کنید
دورهٔ رشد
comparisonLabel?: string دورهٔ واقعی مقایسه را مستقیم کنار عدد نشان میدهد. در مثالهای این صفحه، دادهٔ نمایشی نسبت به 1 تا 30 شهریور تعریف شده است. در محصول، برچسب را از داده یا درخواست مقایسه بگیرید؛ دورهٔ نامعلوم را با «دورهٔ قبل» جایگزین نکنید. نبود برچسب در توسعه هشدار میدهد و مقدار قدیمی را پنهان نمیکند.