components/we-inc/stats-bento-grid-for-a-dashboard.tsx
// Dashboard stats bento: a wide revenue tile, a ring, a ranked list and two small numbers.
// Four columns from md, two on small tablets, one on phones.
const revenue = [18, 22, 21, 27, 25, 31, 34, 33, 38, 41, 39, 46];
const plans = [
{ name: "Studio", value: 412, share: 100 },
{ name: "Solo", value: 288, share: 70 },
{ name: "Team", value: 141, share: 34 },
];
function Spark() {
const max = Math.max(...revenue);
const pts = revenue.map((v, i) => `${(i / (revenue.length - 1)) * 300},${90 - (v / max) * 80}`).join(" ");
return (
<svg viewBox="0 0 300 100" preserveAspectRatio="none" className="mt-6 h-28 w-full" aria-hidden="true">
<polygon points={`0,100 ${pts} 300,100`} className="fill-emerald-100" />
<polyline points={pts} fill="none" className="stroke-emerald-600" strokeWidth="2.5" strokeLinejoin="round" vectorEffect="non-scaling-stroke" />
</svg>
);
}
export default function StatsBentoGridForADashboard() {
const retention = 92;
const c = 2 * Math.PI * 34;
return (
<section className="bg-white px-5 py-14 sm:py-20">
<div className="mx-auto max-w-6xl">
<div className="flex flex-wrap items-baseline justify-between gap-x-4 gap-y-1">
<h2 className="text-2xl font-semibold tracking-tight text-slate-900">October overview</h2>
<p className="text-sm text-slate-600">Fernway Yoga, all studios</p>
</div>
<div className="mt-8 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
<article className="rounded-2xl border border-slate-200 p-6 sm:col-span-2 lg:row-span-2">
<h3 className="text-sm font-medium text-slate-600">Monthly revenue</h3>
<p className="mt-2 text-4xl font-semibold tracking-tight text-slate-900 tabular-nums">$46,180</p>
<p className="mt-1 text-sm font-medium text-emerald-700">Up 17.9% on September</p>
<Spark />
<div className="mt-2 flex justify-between text-xs text-slate-500"><span>Nov 2025</span><span>Oct 2026</span></div>
</article>
<article className="flex items-center gap-5 rounded-2xl border border-slate-200 p-6">
<svg viewBox="0 0 80 80" className="h-20 w-20 shrink-0 -rotate-90" aria-hidden="true">
<circle cx="40" cy="40" r="34" fill="none" className="stroke-slate-100" strokeWidth="9" />
<circle cx="40" cy="40" r="34" fill="none" className="stroke-orange-500" strokeWidth="9" strokeLinecap="round" strokeDasharray={`${(retention / 100) * c} ${c}`} />
</svg>
<div>
<h3 className="text-sm font-medium text-slate-600">Members kept</h3>
<p className="text-3xl font-semibold text-slate-900 tabular-nums">{retention}%</p>
</div>
</article>
<article className="rounded-2xl border border-slate-200 p-6">
<h3 className="text-sm font-medium text-slate-600">Classes booked</h3>
<p className="mt-2 text-3xl font-semibold text-slate-900 tabular-nums">3,904</p>
<p className="mt-1 text-sm text-slate-600">87% of spaces filled</p>
</article>
<article className="rounded-2xl border border-slate-200 p-6 sm:col-span-2">
<h3 className="text-sm font-medium text-slate-600">Members by plan</h3>
<ul className="mt-4 space-y-3">
{plans.map((p) => (
<li key={p.name} className="grid grid-cols-[64px_1fr_44px] items-center gap-3 text-sm">
<span className="text-slate-700">{p.name}</span>
<span className="h-2.5 rounded-full bg-slate-100"><span className="block h-full rounded-full bg-emerald-500" style={{ width: `${p.share}%` }} /></span>
<span className="text-right font-medium text-slate-900 tabular-nums">{p.value}</span>
</li>
))}
</ul>
</article>
</div>
</div>
</section>
);
}