For the complete documentation index, see llms.txt. Markdown variants are available by appending .md to any URL or sending an Accept: text/markdown header. An agent skill is available at /.well-known/agent-skills/site-skill.md.
Home

Featured

A dashboard block with stat cards, an area chart, and a data table with tabs and pagination.

Registered Athletes+12.5%
1,284

Trending up this season

Across all disciplines

Race Entries-8%
3,842

Down 8% this period

Review entry requirements

Sessions Completed+18.3%
24,610

Strong training retention

Targets exceeded this month

Podium Rate+4.5%
38.2%

Steady performance increase

Meets season projections

Training Sessions
Total sessions completed across the season
Athlete Overview
Track athlete progress, session targets, and coach assignments.
Discipline
Goal
Sessions
Coach
MOMarco Odermatt
Giant SlalomIn Process
24
20
Helmut Krug
MSMikaela Shiffrin
SlalomDone
28
26
Mike Day
SGSofia Goggia
DownhillCancelled
18
12
M. Blardone
APAlexis Pinturault
All-roundPending
22
18
LGLara Gut-Behrami
Super-GDone
20
17
M. Blardone
HKHenrik Kristoffersen
SlalomIn Process
26
22
Helmut Krug
PVPetra Vlhová
SlalomPending
24
20
CSCorinne Suter
DownhillDone
16
14
M. Blardone
MMMatthias Mayer
Super-GCancelled
14
10
Christian Pravda
WHWendy Holdener
All-roundDone
22
20

0 of 10 row(s) selected.

Rows per page
Page 1 of 1
"use client";

import { TrendingDown, TrendingUp } from "lucide-react";
import * as React from "react";
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts";

import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card";
import {
  ChartContainer,
  ChartTooltip,
  ChartTooltipContent,
} from "@/components/ui/chart";
import type { ChartConfig } from "@/components/ui/chart";
import { DataTable } from "@/components/ui/e-data-table";
import type { DataTableRow } from "@/components/ui/e-data-table";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { cn } from "@/lib/utils";

// ─── Types ───────────────────────────────────────────────────────────────────

export interface FeaturedStat {
  label: string;
  trend: string;
  trendUp: boolean;
  value: string;
  subtitle: string;
  description: string;
}

export interface FeaturedChartPoint {
  date: string;
  visitors: number;
}

export interface FeaturedBlockProps {
  stats?: FeaturedStat[];
  chartTitle?: string;
  chartDescription?: string;
  chartData?: FeaturedChartPoint[];
  chartRanges?: string[];
  tableTitle?: string;
  tableDescription?: string;
  tableRows?: DataTableRow[];
  rowsPerPageOptions?: number[];
  className?: string;
}

// ─── Defaults ────────────────────────────────────────────────────────────────

const DEFAULT_STATS: FeaturedStat[] = [
  {
    description: "Across all disciplines",
    label: "Registered Athletes",
    subtitle: "Trending up this season",
    trend: "+12.5%",
    trendUp: true,
    value: "1,284",
  },
  {
    description: "Review entry requirements",
    label: "Race Entries",
    subtitle: "Down 8% this period",
    trend: "-8%",
    trendUp: false,
    value: "3,842",
  },
  {
    description: "Targets exceeded this month",
    label: "Sessions Completed",
    subtitle: "Strong training retention",
    trend: "+18.3%",
    trendUp: true,
    value: "24,610",
  },
  {
    description: "Meets season projections",
    label: "Podium Rate",
    subtitle: "Steady performance increase",
    trend: "+4.5%",
    trendUp: true,
    value: "38.2%",
  },
];

const DEFAULT_CHART_DATA: FeaturedChartPoint[] = [
  { date: "Nov 4", visitors: 1200 },
  { date: "Nov 11", visitors: 2800 },
  { date: "Nov 18", visitors: 4100 },
  { date: "Nov 25", visitors: 5600 },
  { date: "Dec 2", visitors: 7400 },
  { date: "Dec 9", visitors: 9800 },
  { date: "Dec 16", visitors: 12_300 },
  { date: "Dec 23", visitors: 15_700 },
  { date: "Jan 6", visitors: 18_200 },
  { date: "Jan 13", visitors: 14_900 },
  { date: "Jan 20", visitors: 17_300 },
  { date: "Jan 27", visitors: 20_100 },
  { date: "Feb 3", visitors: 22_800 },
  { date: "Feb 10", visitors: 24_610 },
];

const DEFAULT_TABLE_ROWS: DataTableRow[] = [
  {
    avatarSrc: "https://i.pravatar.cc/40?img=3",
    header: "Marco Odermatt",
    id: "1",
    limit: 20,
    reviewer: "Helmut Krug",
    sectionType: "Giant Slalom",
    status: "in-process",
    target: 24,
  },
  {
    avatarSrc: "https://i.pravatar.cc/40?img=5",
    header: "Mikaela Shiffrin",
    id: "2",
    limit: 26,
    reviewer: "Mike Day",
    sectionType: "Slalom",
    status: "done",
    target: 28,
  },
  {
    avatarSrc: "https://i.pravatar.cc/40?img=10",
    header: "Sofia Goggia",
    id: "3",
    limit: 12,
    reviewer: "M. Blardone",
    sectionType: "Downhill",
    status: "cancelled",
    target: 18,
  },
  {
    avatarSrc: "https://i.pravatar.cc/40?img=12",
    header: "Alexis Pinturault",
    id: "4",
    limit: 18,
    reviewer: null,
    sectionType: "All-round",
    status: "pending",
    target: 22,
  },
  {
    avatarSrc: "https://i.pravatar.cc/40?img=9",
    header: "Lara Gut-Behrami",
    id: "5",
    limit: 17,
    reviewer: "M. Blardone",
    sectionType: "Super-G",
    status: "done",
    target: 20,
  },
  {
    avatarSrc: "https://i.pravatar.cc/40?img=15",
    header: "Henrik Kristoffersen",
    id: "6",
    limit: 22,
    reviewer: "Helmut Krug",
    sectionType: "Slalom",
    status: "in-process",
    target: 26,
  },
  {
    avatarSrc: "https://i.pravatar.cc/40?img=11",
    header: "Petra Vlhová",
    id: "7",
    limit: 20,
    reviewer: null,
    sectionType: "Slalom",
    status: "pending",
    target: 24,
  },
  {
    avatarSrc: "https://i.pravatar.cc/40?img=8",
    header: "Corinne Suter",
    id: "8",
    limit: 14,
    reviewer: "M. Blardone",
    sectionType: "Downhill",
    status: "done",
    target: 16,
  },
  {
    avatarSrc: "https://i.pravatar.cc/40?img=20",
    header: "Matthias Mayer",
    id: "9",
    limit: 10,
    reviewer: "Christian Pravda",
    sectionType: "Super-G",
    status: "cancelled",
    target: 14,
  },
  {
    avatarSrc: "https://i.pravatar.cc/40?img=13",
    header: "Wendy Holdener",
    id: "10",
    limit: 20,
    reviewer: null,
    sectionType: "All-round",
    status: "done",
    target: 22,
  },
];

// ─── Stat Cards ──────────────────────────────────────────────────────────────

const StatCards = ({ stats }: { stats: FeaturedStat[] }) => (
  <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4">
    {stats.map((stat, i) => (
      <Card
        key={i}
        className="rounded-3xl border-white/40 bg-linear-to-br from-white/50 to-primary/10 shadow-xl shadow-primary/20 backdrop-blur-xl dark:border-white/10 dark:from-white/5 dark:to-primary/10"
      >
        <CardHeader className="pb-2">
          <div className="flex items-center justify-between">
            <span className="text-sm text-muted-foreground">{stat.label}</span>
            <span
              className={cn(
                "inline-flex items-center gap-1 text-xs font-medium",
                stat.trendUp
                  ? "text-emerald-600 dark:text-emerald-400"
                  : "text-destructive"
              )}
            >
              {stat.trendUp ? (
                <TrendingUp className="size-3" />
              ) : (
                <TrendingDown className="size-3" />
              )}
              {stat.trend}
            </span>
          </div>
          <CardTitle className="text-2xl font-bold">{stat.value}</CardTitle>
        </CardHeader>
        <CardContent className="pt-0">
          <p className="flex items-center gap-1 text-sm font-medium">
            {stat.subtitle}
            {stat.trendUp ? (
              <TrendingUp className="size-3.5" />
            ) : (
              <TrendingDown className="size-3.5" />
            )}
          </p>
          <p className="mt-0.5 text-xs text-muted-foreground">
            {stat.description}
          </p>
        </CardContent>
      </Card>
    ))}
  </div>
);

// ─── Chart ───────────────────────────────────────────────────────────────────

const chartConfig = {
  visitors: {
    color: "var(--foreground)",
    label: "Visitors",
  },
} satisfies ChartConfig;

const ChartSection = ({
  title,
  description,
  data,
  ranges,
}: {
  title: string;
  description: string;
  data: FeaturedChartPoint[];
  ranges: string[];
}) => {
  const [activeRange, setActiveRange] = React.useState(ranges[0]);

  return (
    <Card className="rounded-3xl border-white/40 bg-linear-to-br from-white/50 to-primary/10 shadow-xl shadow-primary/20 backdrop-blur-xl dark:border-white/10 dark:from-white/5 dark:to-primary/10">
      <CardHeader>
        <div className="flex flex-col gap-1 sm:flex-row sm:items-start sm:justify-between">
          <div>
            <CardTitle>{title}</CardTitle>
            <CardDescription>{description}</CardDescription>
          </div>
          <Tabs value={activeRange} onValueChange={setActiveRange}>
            <TabsList>
              {ranges.map((r) => (
                <TabsTrigger key={r} value={r}>
                  {r}
                </TabsTrigger>
              ))}
            </TabsList>
          </Tabs>
        </div>
      </CardHeader>
      <CardContent>
        <ChartContainer config={chartConfig} className="h-55 w-full">
          <AreaChart data={data} margin={{ left: 0, right: 0 }}>
            <defs>
              <linearGradient id="fillVisitors" x1="0" y1="0" x2="0" y2="1">
                <stop
                  offset="5%"
                  stopColor="var(--foreground)"
                  stopOpacity={0.25}
                />
                <stop
                  offset="95%"
                  stopColor="var(--foreground)"
                  stopOpacity={0.02}
                />
              </linearGradient>
            </defs>
            <CartesianGrid vertical={false} stroke="var(--border)" />
            <XAxis
              dataKey="date"
              tickLine={false}
              axisLine={false}
              tickMargin={8}
              tick={{ fontSize: 11 }}
            />
            <ChartTooltip content={<ChartTooltipContent />} />
            <Area
              dataKey="visitors"
              type="monotone"
              fill="url(#fillVisitors)"
              stroke="var(--foreground)"
              strokeWidth={1.5}
            />
          </AreaChart>
        </ChartContainer>
      </CardContent>
    </Card>
  );
};

// ─── FeaturedBlock ────────────────────────────────────────────────────────────

const FeaturedBlock = ({
  stats = DEFAULT_STATS,
  chartTitle = "Training Sessions",
  chartDescription = "Total sessions completed across the season",
  chartData = DEFAULT_CHART_DATA,
  chartRanges = ["Last 3 months", "Last 30 days", "Last 7 days"],
  tableTitle = "Athlete Overview",
  tableDescription = "Track athlete progress, session targets, and coach assignments.",
  tableRows = DEFAULT_TABLE_ROWS,
  rowsPerPageOptions = [10, 20, 50],
  className,
}: FeaturedBlockProps) => (
  <div
    data-slot="featured-block"
    className={cn("flex flex-col gap-6 p-6", className)}
  >
    <StatCards stats={stats} />
    <ChartSection
      title={chartTitle}
      description={chartDescription}
      data={chartData}
      ranges={chartRanges}
    />
    <DataTable
      rows={tableRows}
      title={tableTitle}
      description={tableDescription}
      rowsPerPageOptions={rowsPerPageOptions}
      columnLabels={{
        assignPlaceholder: "Assign coach",
        filterPlaceholder: "Filter athletes...",
        header: "Athlete",
        limit: "Sessions",
        reviewer: "Coach",
        sectionType: "Discipline",
        target: "Goal",
      }}
    />
  </div>
);

export { FeaturedBlock };
export { FeaturedBlock as BFeatured };
export FeaturedBlock;

Custom stats

Pass your own metrics to the stats row:

<FeaturedBlock
  stats={[
    {
      label: "Monthly Revenue",
      trend: "+8.2%",
      trendUp: true,
      value: "$42,500",
      subtitle: "Up from last month",
      description: "Based on completed transactions",
    },
    // ...
  ]}
/>

Custom chart data

<FeaturedBlock
  chartTitle="Page Views"
  chartDescription="Last 90 days"
  chartData={[
    { date: "Jan 1", visitors: 1200 },
    { date: "Jan 8", visitors: 2400 },
    // ...
  ]}
  chartRanges={["Last 90 days", "Last 30 days", "Last 7 days"]}
/>

Custom table data

<FeaturedBlock
  tableTabs={[
    { label: "All" },
    { label: "Active", count: 5 },
    { label: "Archived" },
  ]}
  tableRows={[
    {
      id: "1",
      header: "Introduction",
      sectionType: "Narrative",
      status: "done",
      target: 10,
      limit: 8,
      reviewer: "Alice Johnson",
    },
  ]}
  totalRows={42}
/>

Props

PropTypeDefaultDescription
statsFeaturedStat[]See defaultsFour stat cards at the top
chartTitlestring"Total Visitors"Chart card heading
chartDescriptionstring"Total for the last 3 months"Chart card subheading
chartDataFeaturedChartPoint[]Sample dataArea chart data points
chartRangesstring[]["Last 3 months", ...]Time range tabs
tableTabsFeaturedTableTab[]See defaultsTable section tabs
tableRowsFeaturedTableRow[]See defaultsTable row data
totalRowsnumber68Total row count for pagination
rowsPerPageOptionsnumber[][10, 20, 50]Rows per page options

Specs

# Mint — Featured Block Specs
 
## Component
- name: b-featured
- file: registry/mint/b-featured.tsx
- installs to: components/ui/b-featured.tsx
- npm dependencies: recharts, lucide-react
- registry dependencies: card, chart, tabs, button, e-data-table
 
## What is it
A full-width dashboard block composed of three stacked sections: a row of
stat cards, an area chart with time-range tabs, and a paginated data table
with filterable tabs. Use it as the primary view of a dashboard or analytics
page. All three sections accept data via props so the block can be dropped in
with any domain — athletes, users, orders, documents.
 
## Visual identity
Clean and data-forward, now carried in Mint's fresh green palette. Each
section is a Card with a soft, glassy border (60% opacity) and generously
rounded corners. Stat cards use a two-row layout: the metric value at large
type, trend badge (green/red) top-right, subtitle and description below. The
chart uses a single area series with a soft green gradient fill and a
hairline stroke. The table uses minimal row dividers with no background fill
— emphasis is on the data, not the chrome.
 
## Structure
- StatCards row: 4 cards, 1 col → 2 col (sm) → 4 col (xl)
- ChartSection: full-width Card with Tabs for time range, ResponsiveContainer
  wrapping recharts AreaChart
- TableSection: full-width Card with Tabs for status filters, DataTable with
  column header sort, filter input, row selection, and pagination
 
## Design tokens
--background: oklch(1 0 0)               — page background behind the block
--card: oklch(1 0 0)                     — card surface
--card-foreground: oklch(0.145 0 0)      — card text
--border: oklch(0.88 0.04 155 / 60%)      — card borders, table row dividers
--foreground: oklch(0.145 0 0)           — stat values, headings
--muted: oklch(0.96 0.02 155)            — tab list background, hover rows
--muted-foreground: oklch(0.52 0.06 155) — stat labels, chart axis labels
--primary: oklch(0.5 0.14 155)           — active tab indicator, buttons
--primary-foreground: oklch(1 0 0)       — text on primary backgrounds
--ring: oklch(0.5 0.14 155)              — focus ring on interactive elements
--radius: 1.25rem                        — card and badge corner radius
 
## Dark theme
--background: oklch(0.145 0 0)
--card: oklch(0.205 0 0)
--card-foreground: oklch(0.985 0 0)
--border: oklch(0.32 0.05 155 / 40%)
--foreground: oklch(0.985 0 0)
--muted: oklch(0.2 0.03 155)
--muted-foreground: oklch(0.6 0.06 155)
 
## shadcn config (components.json)
- style: new-york
- rsc: false (uses "use client" — state for tabs, chart range, table filters)
- tsx: true
- baseColor: neutral
- cssVariables: true
- iconLibrary: lucide
- aliases:
  - components → @/components
  - ui → @/components/ui
  - utils → @/lib/utils
  - lib → @/lib
  - hooks → @/hooks
 
## Stat card anatomy
Each stat in FeaturedStat renders:
- label: text-sm text-muted-foreground (top-left)
- trend: text-xs font-medium, TrendingUp (emerald) or TrendingDown (red) icon
- value: text-2xl font-bold leading-none
- subtitle: text-sm font-medium with repeat icon
- description: text-xs text-muted-foreground
 
## Chart section
Built with recharts AreaChart inside ResponsiveContainer (height 220):
- Single Area series, type="monotone", gradient fill (5% → 2% opacity)
- CartesianGrid vertical={false} for horizontal-only grid lines
- XAxis: tickLine=false, axisLine=false for a clean baseline
- Tooltip: white card with border, 8px radius
 
## Table section
Uses the e-data-table element with tabs for status filters:
- Default columns: header, sectionType, status (badge), target, limit, reviewer
- Status badge colours: done (emerald), in-process (blue), pending (yellow), cancelled (red)
- Filter input filters across all string columns
- Pagination via DataTable's built-in rowsPerPage
 
## Conventions
- Pass all four sections as props — do not import sub-components separately
- chartData points must have { date: string, visitors: number } shape
- tableRows must satisfy FeaturedTableRow (id, header, sectionType, status,
  target, limit, reviewer)
- Use totalRows to reflect the full dataset size when rows are paginated server-side
- The block is "use client" — wrap in a Suspense boundary if lazy loading

Installation

GIN Design Plugin
$ npx shadcn add https://gin-design.vercel.app/b-featured