shadimage
roiui
itemcard-trafficsource: ok · hit

Traffic Card

A website traffic card component with line chart and statistics.

npx shadcn add "https://roiui.com/r/card-traffic.json"
free

Where it goes

stats

Interaction

animated-counter

Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.

Dependencies

Bundle weight · medium
chartsrecharts

Registry dependencies

https://roiui.com/r/badge.jsonhttps://roiui.com/r/card.jsonhttps://roiui.com/r/chart.json

Files3 · source cached 10/14/2026, 2:04:50 PM

registry/brook/blocks/card-traffic/components/card-traffic.tsx
"use client";

import { CartesianGrid, Line, LineChart, XAxis, YAxis } from "recharts";
import { Card, CardContent, CardHeader, CardTitle } from "../../ui/card/card";
import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from "../../ui/chart/chart";
import styles from "./card-traffic.module.css";

export type TrafficDataPoint = {
  /** ISO date (YYYY-MM-DD), or a numeric day label. */
  date: string | number;
  desktop: number;
  mobile: number;
};

export type CardTrafficProps = {
  data: TrafficDataPoint[];
  bounceRate?: number;
  conversionRate?: number;
};

const formatDate = (value: unknown) => {
  if (typeof value === "number") {
    return `Day ${value}`;
  }
  const date = new Date(String(value));
  return Number.isNaN(date.getTime())
    ? String(value)
    : date.toLocaleDateString("en-US", { month: "short", day: "numeric", timeZone: "UTC" });
};

const chartConfig = {
  mobile: {
    label: "Mobile",
    color: "var(--chart-1)",
  },
  desktop: {
    label: "Desktop",
    color: "var(--chart-2)",
  },
} satisfies ChartConfig;

export function CardTraffic({ data, bounceRate, conversionRate }: CardTrafficProps) {
  const mobile = data.reduce((sum, point) => sum + point.mobile, 0);
  const desktop = data.reduce((sum, point) => sum + point.desktop, 0);
  return (
    <Card
      className={styles.cardContainer}
      style={{
        maxWidth: "600px",
        width: "100%",
      }}
    >
      <CardHeader>
        <CardTitle>Website Traffic</CardTitle>
      </CardHeader>
      <CardContent className={styles.chartContainer}>
        <div style={{ width: "100%", height: "100%" }}>
          <ChartContainer config={chartConfig}>
            <LineChart data={data}>
              <CartesianGrid strokeDasharray="3 3" vertical={false} />
              <XAxis dataKey="date" hide={true} tickFormatter={formatDate} />
              <YAxis hide={true} />
              <ChartTooltip content={<ChartTooltipContent labelFormatter={formatDate} />} />
              <Line
                animationDuration={800}
                dataKey="mobile"
                dot={false}
                stroke="var(--color-mobile)"
                strokeWidth={2}
                type="monotone"
              />
              <Line
                animationBegin={200}
                animationDuration={800}
                dataKey="desktop"
                dot={false}
                stroke="var(--color-desktop)"
                strokeWidth={2}
                type="monotone"
              />
            </LineChart>
          </ChartContainer>
        </div>
      </CardContent>

      <div className={styles.statsGrid}>
        <div className={styles.statCard}>
          <div className={styles.statHeader}>
            <span className={styles.statLabel}>Mobile</span>
          </div>
          <span className={styles.statNumber}>{mobile.toLocaleString("en-US")}</span>
        </div>

        <div className={styles.statCard}>
          <div className={styles.statHeader}>
            <span className={styles.statLabel}>Desktop</span>
          </div>
          <span className={styles.statNumber}>{desktop.toLocaleString("en-US")}</span>
        </div>

        <div className={styles.statCard}>
          <div className={styles.statHeader}>
            <span className={styles.statLabel}>Bounce Rate</span>
          </div>
          <span className={styles.statNumber}>{bounceRate === undefined ? "Not provided" : `${bounceRate}%`}</span>
        </div>

        <div className={styles.statCard}>
          <div className={styles.statHeader}>
            <span className={styles.statLabel}>Conversion</span>
          </div>
          <span className={styles.statNumber}>
            {conversionRate === undefined ? "Not provided" : `${conversionRate}%`}
          </span>
        </div>
      </div>
    </Card>
  );
}
registry/brook/blocks/card-traffic/components/card-traffic.module.css
.cardContainer {
  width: 100%;
  max-width: 600px;
}

.chartContainer {
  height: 200px;
  padding: 0;
}

.statsGrid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
  border-top: 0.5px solid oklch(from var(--border) l c h / 0.7);
  margin: 0 -24px -24px -24px;
}

.statCard {
  padding: 1rem;
  display: flex;
  flex-direction: column;
  gap: 8px;
  border-right: 0.5px solid oklch(from var(--border) l c h / 0.7);
  border-bottom: 0.5px solid oklch(from var(--border) l c h / 0.7);
}

.statCard:nth-child(2n) {
  border-right: none;
}

.statCard:nth-child(n + 3) {
  border-bottom: none;
}

@media (max-width: 640px) {
  .cardContainer {
    max-width: 100%;
  }

  .chartContainer {
    height: 200px;
  }

  .statsGrid {
    margin: 0 -20px -20px -20px;
  }

  .statCard {
    padding: 1rem;
    gap: 0.5rem;
  }

  .statLabel {
    font-size: 0.875rem;
  }

  .statNumber {
    font-size: 1.75rem;
    font-weight: 700;
  }
}

@media (max-width: 400px) {
  .statsGrid {
    grid-template-columns: 1fr;
    grid-template-rows: auto;
    margin: 0 -20px -20px -20px;
  }

  .statCard {
    border-right: none;
    border-bottom: 1px solid var(--border);
    padding: 1rem;
  }

  .statCard:nth-child(2n) {
    border-right: none;
  }

  .statCard:nth-child(n + 3) {
    border-bottom: 1px solid var(--border);
  }

  .statCard:last-child {
    border-bottom: none;
  }

  .statNumber {
    font-size: 1.5rem;
  }
}

.statHeader {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.statLabel {
  font-size: 14px;
  line-height: 20px;
  color: var(--muted-foreground);
  font-weight: 500;
}

.statNumber {
  font-size: 32px;
  font-weight: 700;
  color: var(--foreground);
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
registry/brook/blocks/card-traffic/data.json
[
  {
    "date": "2025-07-14",
    "desktop": 12.3,
    "mobile": 28.7
  },
  {
    "date": "2025-07-15",
    "desktop": 11.8,
    "mobile": 29.2
  },
  {
    "date": "2025-07-16",
    "desktop": 13.1,
    "mobile": 31.4
  },
  {
    "date": "2025-07-17",
    "desktop": 12.9,
    "mobile": 30.8
  },
  {
    "date": "2025-07-18",
    "desktop": 14.2,
    "mobile": 33.1
  },
  {
    "date": "2025-07-19",
    "desktop": 13.7,
    "mobile": 32.3
  },
  {
    "date": "2025-07-20",
    "desktop": 11.5,
    "mobile": 27.9
  },
  {
    "date": "2025-07-21",
    "desktop": 10.8,
    "mobile": 26.4
  },
  {
    "date": "2025-07-22",
    "desktop": 12.6,
    "mobile": 29.8
  },
  {
    "date": "2025-07-23",
    "desktop": 13.9,
    "mobile": 32.7
  },
  {
    "date": "2025-07-24",
    "desktop": 14.1,
    "mobile": 33.9
  },
  {
    "date": "2025-07-25",
    "desktop": 13.3,
    "mobile": 31.8
  },
  {
    "date": "2025-07-26",
    "desktop": 12.7,
    "mobile": 30.2
  },
  {
    "date": "2025-07-27",
    "desktop": 11.9,
    "mobile": 28.6
  },
  {
    "date": "2025-07-28",
    "desktop": 13.8,
    "mobile": 32.4
  },
  {
    "date": "2025-07-29",
    "desktop": 14.5,
    "mobile": 34.2
  },
  {
    "date": "2025-07-30",
    "desktop": 13.2,
    "mobile": 31.1
  },
  {
    "date": "2025-07-31",
    "desktop": 12.4,
    "mobile": 29.7
  },
  {
    "date": "2025-08-01",
    "desktop": 14.8,
    "mobile": 35.1
  },
  {
    "date": "2025-08-02",
    "desktop": 15.2,
    "mobile": 36.3
  },
  {
    "date": "2025-08-03",
    "desktop": 11.6,
    "mobile": 28.2
  },
  {
    "date": "2025-08-04",
    "desktop": 10.9,
    "mobile": 26.8
  },
  {
    "date": "2025-08-05",
    "desktop": 13.4,
    "mobile": 32.1
  },
  {
    "date": "2025-08-06",
    "desktop": 14.7,
    "mobile": 34.8
  },
  {
    "date": "2025-08-07",
    "desktop": 13.1,
    "mobile": 31.7
  },
  {
    "date": "2025-08-08",
    "desktop": 12.8,
    "mobile": 30.4
  },
  {
    "date": "2025-08-09",
    "desktop": 14.3,
    "mobile": 33.6
  },
  {
    "date": "2025-08-10",
    "desktop": 15.1,
    "mobile": 35.7
  },
  {
    "date": "2025-08-11",
    "desktop": 11.7,
    "mobile": 28.9
  },
  {
    "date": "2025-08-12",
    "desktop": 12.2,
    "mobile": 29.4
  }
]

More from roiui

Browse all components in this library →