بازگشت به وبلاگ

Recharts در Next.js و Tailwind: تم‌دهی و استایل‌دهی سفارشی

۳ مرداد ۱۴۰۵3 دقیقه مطالعه
Recharts در Next.js و Tailwind: تم‌دهی و استایل‌دهی سفارشی

Recharts از همون اول چارت‌های کارکردنی می‌ده، ولی ظاهر پیش‌فرضش هیچ شباهتی به بقیه‌ی سایت طراحی‌شده با Tailwind نداره — فونت‌های متفاوت، رنگ‌های hex هاردکد، یه tooltip که با هرچی دورشه در تضاده. طبیعی‌کردنش با سیستم طراحی عمدتاً سه‌تا کار می‌خواد: درست Client Component کردنش، وصل‌کردن رنگ‌هاش به تم Tailwind/CSS variable به‌جای hex خام، و جایگزین‌کردن tooltip و legend پیش‌فرض با markup خودت.

۱. مجبوره Client Component باشه

Recharts سایز container رو اندازه می‌گیره و SVG رو با API های مرورگر رندر می‌کنه، پس هر کامپوننتی که یه چارت رندر می‌کنه به "use client" نیاز داره — ولی معنیش این نیست که کل صفحه هم باید همین‌طور باشه. داده رو توی یه Server Component والد fetch کن و به‌شکل prop پاسش بده.

// Server Component — داده رو fetch می‌کنه، سمت سرور می‌مونه
import { SkillsChart } from "./skills-chart";

export default async function SkillsSection() {
  const skills = await skillRepository.findAllWithLevels();
  return <SkillsChart data={skills} />; // فقط همین برگ به "use client" نیاز داره
}

۲. تم‌دهی با CSS variable به‌جای hex خام

کامپوننت‌های Recharts هر مقدار CSS معتبری رو به‌عنوان stroke/fill قبول می‌کنن — از جمله var(--some-token). خوندن رنگ‌ها مستقیم از تم Tailwind سایت یعنی چارت خودش توی dark mode دوباره تم می‌گیره، بدون نیاز به یه نگاشت رنگ جدا برای روشن/تاریک که دستی نگه‌داری بشه.

// globals.css (همون توکن‌های طراحی که همه‌جای دیگه استفاده می‌شن رو تعریف می‌کنه)
:root {
  --chart-1: oklch(0.65 0.22 260);
  --chart-2: oklch(0.7 0.18 160);
  --chart-grid: oklch(0.9 0 0);
  --chart-text: oklch(0.45 0 0);
}
.dark {
  --chart-1: oklch(0.75 0.2 260);
  --chart-2: oklch(0.78 0.16 160);
  --chart-grid: oklch(0.3 0 0);
  --chart-text: oklch(0.7 0 0);
}
"use client";
import {
  ResponsiveContainer,
  BarChart,
  Bar,
  XAxis,
  YAxis,
  CartesianGrid,
  Tooltip,
} from "recharts";

export function SkillsChart({ data }: { data: { name: string; level: number }[] }) {
  return (
    <ResponsiveContainer width="100%" height={320}>
      <BarChart data={data}>
        <CartesianGrid strokeDasharray="3 3" stroke="var(--chart-grid)" />
        <XAxis dataKey="name" stroke="var(--chart-text)" fontSize={12} tickLine={false} />
        <YAxis stroke="var(--chart-text)" fontSize={12} tickLine={false} />
        <Tooltip content={<ChartTooltip />} cursor={{ fill: "var(--chart-grid)", opacity: 0.3 }} />
        <Bar dataKey="level" fill="var(--chart-1)" radius={[6, 6, 0, 0]} />
      </BarChart>
    </ResponsiveContainer>
  );
}

به ResponsiveContainer یه height صریح پیکسلی بده (یه height درصدی تنها ممکنه داخل یه والد flex/grid به‌شکل ۰px رندر بشه، چون Recharts نسبت به ارتفاع resolved-شده‌ی والد اندازه می‌گیره) — این رایج‌ترین باگ «چارتم نمایش داده نمی‌شه»ست.

۳. یه tooltip که واقعاً با طراحی هماهنگه

tooltip پیش‌فرض یه جعبه‌ی سفید ساده با inline style‌ه. پاس‌دادن کامپوننت خودت به content یه المان معمولیِ استایل‌داده‌شده با Tailwind بهت می‌ده به‌جاش.

function ChartTooltip({ active, payload, label }: any) {
  if (!active || !payload?.length) return null;

  return (
    <div className="rounded-lg border bg-popover px-3 py-2 text-sm shadow-md">
      <p className="font-medium text-popover-foreground">{label}</p>
      {payload.map((entry: any) => (
        <p key={entry.dataKey} className="text-muted-foreground">
          {entry.name}: <span className="text-foreground">{entry.value}</span>
        </p>
      ))}
    </div>
  );
}

۴. چارت چندسری و یه legend سفارشی

<LineChart data={monthlyViews}>
  <Line type="monotone" dataKey="en" stroke="var(--chart-1)" strokeWidth={2} dot={false} />
  <Line type="monotone" dataKey="fa" stroke="var(--chart-2)" strokeWidth={2} dot={false} />
  <Legend content={<ChartLegend />} />
</LineChart>
function ChartLegend({ payload }: any) {
  return (
    <div className="mt-3 flex justify-center gap-4 text-sm">
      {payload.map((entry: any) => (
        <span key={entry.value} className="flex items-center gap-1.5">
          <span className="size-2.5 rounded-full" style={{ background: entry.color }} />
          {entry.value}
        </span>
      ))}
    </div>
  );
}

۵. RTL و لوکیل فارسی این سایت

Recharts برای چیدمان RTL خودکار flip نمی‌شه — ترتیب محوری که توی انگلیسی طبیعی خونده می‌شه، ممکنه توی فارسی برعکس خونده بشه. به‌جای اینکه بخوای خودِ SVG رو آینه کنی، وقتی لوکیل فعال فارسیه ترتیب داده رو برعکس کن (یا روی محور reversed ست کن).

const locale = useLocale(); // next-intl
const chartData = locale === "fa" ? [...data].reverse() : data;

چک‌لیست

  • کامپوننت چارت یه برگ کوچیک Client Component‌ه؛ گرفتن داده توی یه Server Component والد می‌مونه.

  • رنگ‌ها از CSS variable های وصل‌شده به تم Tailwind خونده می‌شن، نه hex هاردکد — پس dark mode به هیچ منطق اضافه‌ی مخصوص چارت نیاز نداره.

  • ResponsiveContainer یه height پیکسلی صریح داره، نه فقط width="100%".

  • Tooltip و legend با markup واقعی Tailwind جایگزین شدن، نه نسخه‌های پیش‌فرض inline-styled خودِ Recharts.

  • ترتیب داده برای لوکیل‌های RTL که جهت محور واقعاً اهمیت داره برعکس شده.