Components
- Accordion
- Alert
- Alert Dialog
- Aspect Ratio
- Attachment
- Avatar
- Badge
- Breadcrumb
- Bubble
- Button
- Button Group
- Calendar
- Card
- Carousel
- Chart
- Checkbox
- Collapsible
- Combobox
- Command
- Context Menu
- Date Picker
- Dialog
- Drawer
- Dropdown Menu
- Empty
- Field
- Hover Card
- Input
- Input OTP
- Input Group
- Item
- Kbd
- Label
- Marker
- Menubar
- Message
- Message Scroller
- Native Select
- Navigation Menu
- Pagination
- Popover
- Progress
- Radio Group
- Resizable
- Scroll Area
- Select
- Separator
- Sheet
- Sidebar
- Skeleton
- Slider
- Sonner
- Spinner
- Switch
- Table
- Tabs
- Textarea
- Toggle
- Toggle Group
- Tooltip
- New Component
Elements
Charts
Icons
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 Slalom | In Process | 24 | 20 | Helmut Krug | |||
MSMikaela Shiffrin | Slalom | Done | 28 | 26 | Mike Day | |||
SGSofia Goggia | Downhill | Cancelled | 18 | 12 | M. Blardone | |||
APAlexis Pinturault | All-round | Pending | 22 | 18 | ||||
LGLara Gut-Behrami | Super-G | Done | 20 | 17 | M. Blardone | |||
HKHenrik Kristoffersen | Slalom | In Process | 26 | 22 | Helmut Krug | |||
PVPetra Vlhová | Slalom | Pending | 24 | 20 | ||||
CSCorinne Suter | Downhill | Done | 16 | 14 | M. Blardone | |||
MMMatthias Mayer | Super-G | Cancelled | 14 | 10 | Christian Pravda | |||
WHWendy Holdener | All-round | Done | 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
| Prop | Type | Default | Description |
|---|---|---|---|
stats | FeaturedStat[] | See defaults | Four stat cards at the top |
chartTitle | string | "Total Visitors" | Chart card heading |
chartDescription | string | "Total for the last 3 months" | Chart card subheading |
chartData | FeaturedChartPoint[] | Sample data | Area chart data points |
chartRanges | string[] | ["Last 3 months", ...] | Time range tabs |
tableTabs | FeaturedTableTab[] | See defaults | Table section tabs |
tableRows | FeaturedTableRow[] | See defaults | Table row data |
totalRows | number | 68 | Total row count for pagination |
rowsPerPageOptions | number[] | [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 loadingInstallation
$ npx shadcn add \https://gin-design.vercel.app/b-featured