Music streaming stats share card with a primary-color gradient header, total stream count, monthly listeners, multi-segment genre allocation bar, genre rows with colored dots and week-over-week change percentages, an interactive recharts area sparkline with a custom dark tooltip, and three action buttons (Share now, Preview, more). Uses shadcn/ui Button and recharts. Perfect for music platforms, artist dashboards, and streaming analytics tools.
Files
"use client"
import { ArrowRight, Headphones, MoreVertical } from "lucide-react"
import {
Area,
AreaChart,
CartesianGrid,
ResponsiveContainer,
Tooltip,
XAxis,
} from "recharts"
import { Button } from "@/components/ui/button"
const stats = {
totalStreams: 2847392,
user: "@sofi_sounds",
monthlyListeners: 94210,
genres: [
{
name: "Pop",
color: "#fda4af",
allocation: 58.3,
change: 12.4,
plays: 1659820,
},
{
name: "R&B",
color: "#a78bfa",
allocation: 23.9,
change: 8.71,
plays: 681643,
},
{
name: "Electronic",
color: "#64748b",
allocation: 17.8,
change: -3.18,
plays: 505929,
},
],
chartData: [
{ month: "J", value: 142000 },
{ month: "F", value: 178000 },
{ month: "M", value: 163000 },
{ month: "A", value: 205000 },
{ month: "M", value: 241000 },
{ month: "J", value: 228000 },
{ month: "J", value: 259000 },
{ month: "A", value: 294000 },
{ month: "S", value: 271000 },
{ month: "O", value: 318000 },
{ month: "N", value: 341000 },
{ month: "D", value: 354000 },
],
}
function formatTotalStreams(n: number) {
if (n >= 1_000_000)
return (n / 1_000_000).toFixed(2).replace(/\.?0+$/, "") + "M"
if (n >= 1_000) return (n / 1_000).toFixed(1).replace(/\.?0+$/, "") + "K"
return n.toString()
}
function formatPlays(n: number) {
if (n >= 1_000_000)
return (n / 1_000_000).toFixed(2).replace(/\.?0+$/, "") + "M"
if (n >= 1_000) return Math.round(n / 1_000) + "K"
return n.toString()
}
function formatListeners(n: number) {
return new Intl.NumberFormat("en-US").format(n)
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function ChartTooltip({ active, payload }: any) {
if (active && payload && payload.length) {
return (
<div className="bg-foreground rounded-xl px-3.5 py-2.5 shadow-lg">
<p className="text-background text-sm font-semibold">
{formatPlays(payload[0].value)} plays
</p>
<p className="text-background/60 text-xs">Nov 2024</p>
</div>
)
}
return null
}
export default function MusicStatsCard() {
return (
<div className="bg-card border-border w-full max-w-sm overflow-hidden rounded-3xl border shadow-xl">
{/* Header with gradient blobs */}
<div className="relative px-6 pt-6 pb-8">
<div className="pointer-events-none absolute inset-0 overflow-hidden">
<div className="bg-primary/70 absolute -top-10 -right-6 h-44 w-44 rounded-full blur-3xl" />
<div className="bg-primary/50 absolute -top-6 left-4 h-36 w-36 rounded-full blur-3xl" />
<div className="bg-primary/30 absolute top-2 right-16 h-28 w-28 rounded-full blur-2xl" />
</div>
{/* Logo + label */}
<div className="relative mb-8 flex items-center gap-3">
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-white shadow-sm">
<Headphones className="h-6 w-6 text-neutral-900" />
</div>
<div>
<p className="text-muted-foreground text-sm leading-none">Share</p>
<p className="text-foreground mt-0.5 text-base leading-none font-bold">
Music Stats
</p>
</div>
</div>
{/* Total streams */}
<p className="text-foreground relative text-4xl font-bold tracking-tight">
{formatTotalStreams(stats.totalStreams)}
</p>
<p className="text-muted-foreground relative mt-1 text-sm">
streams this year
</p>
{/* User */}
<div className="relative mt-4 space-y-0.5">
<p className="text-muted-foreground text-sm">{stats.user}</p>
<p className="text-foreground text-sm">
<span className="font-bold">
{formatListeners(stats.monthlyListeners)}
</span>{" "}
monthly listeners
</p>
</div>
</div>
{/* Genre allocation bar */}
<div className="mb-6 px-6">
<div className="flex h-3 w-full overflow-hidden rounded-full">
{stats.genres.map((genre) => (
<div
key={genre.name}
className="h-full"
style={{
width: `${genre.allocation}%`,
backgroundColor: genre.color,
}}
/>
))}
</div>
</div>
{/* Genre list */}
<div className="mb-4 space-y-4 px-6">
{stats.genres.map((genre) => (
<div key={genre.name} className="flex items-center gap-3">
<div
className="h-2.5 w-2.5 shrink-0 rounded-full"
style={{ backgroundColor: genre.color }}
/>
<span className="text-foreground flex-1 text-sm">{genre.name}</span>
<span
className={`w-16 text-right text-sm font-medium tabular-nums ${
genre.change >= 0 ? "text-emerald-500" : "text-red-500"
}`}
>
{genre.change >= 0 ? "+" : ""}
{genre.change.toFixed(2)}%
</span>
<span className="text-foreground w-16 text-right text-sm font-semibold tabular-nums">
{formatPlays(genre.plays)}
</span>
</div>
))}
</div>
{/* Chart */}
<div className="h-36 px-1">
<ResponsiveContainer width="100%" height="100%">
<AreaChart
data={stats.chartData}
margin={{ top: 12, right: 8, bottom: 0, left: 8 }}
>
<defs>
<linearGradient id="streamGradient" x1="0" y1="0" x2="0" y2="1">
<stop
offset="0%"
stopColor="hsl(var(--muted-foreground))"
stopOpacity={0.12}
/>
<stop
offset="100%"
stopColor="hsl(var(--muted-foreground))"
stopOpacity={0}
/>
</linearGradient>
</defs>
<CartesianGrid
vertical={false}
strokeDasharray="2 6"
stroke="hsl(var(--border))"
strokeOpacity={0.7}
/>
<XAxis
dataKey="month"
axisLine={false}
tickLine={false}
tick={{ fontSize: 11, fill: "hsl(var(--muted-foreground))" }}
interval={0}
/>
<Tooltip
content={<ChartTooltip />}
cursor={{
stroke: "hsl(var(--foreground))",
strokeWidth: 1,
strokeDasharray: "3 3",
}}
/>
<Area
type="monotone"
dataKey="value"
stroke="hsl(var(--muted-foreground))"
strokeWidth={1.5}
fill="url(#streamGradient)"
dot={false}
activeDot={{
r: 4,
fill: "hsl(var(--foreground))",
strokeWidth: 0,
}}
/>
</AreaChart>
</ResponsiveContainer>
</div>
{/* Actions */}
<div className="flex items-center gap-2 px-6 py-5">
<Button className="flex-1 rounded-full" size="lg">
Share now
<ArrowRight className="ml-1 h-4 w-4" />
</Button>
<Button variant="secondary" className="rounded-full px-6" size="lg">
Preview
</Button>
<Button
variant="secondary"
size="icon"
className="h-11 w-11 shrink-0 rounded-full"
>
<MoreVertical className="h-4 w-4" />
</Button>
</div>
</div>
)
}
Music streaming stats share card with genre breakdown, play counts, and sparkline chart
music-stats-card-01
Share
Music Stats
2.85M
streams this year
@sofi_sounds
94,210 monthly listeners
Pop+12.40%1.66M
R&B+8.71%682K
Electronic-3.18%506K
"use client"
import { ArrowRight, Headphones, MoreVertical } from "lucide-react"
import {
Area,
AreaChart,
CartesianGrid,
ResponsiveContainer,
Tooltip,
XAxis,
} from "recharts"
import { Button } from "@/components/ui/button"
const stats = {
totalStreams: 2847392,
user: "@sofi_sounds",
monthlyListeners: 94210,
genres: [
{
name: "Pop",
color: "#fda4af",
allocation: 58.3,
change: 12.4,
plays: 1659820,
},
{
name: "R&B",
color: "#a78bfa",
allocation: 23.9,
change: 8.71,
plays: 681643,
},
{
name: "Electronic",
color: "#64748b",
allocation: 17.8,
change: -3.18,
plays: 505929,
},
],
chartData: [
{ month: "J", value: 142000 },
{ month: "F", value: 178000 },
{ month: "M", value: 163000 },
{ month: "A", value: 205000 },
{ month: "M", value: 241000 },
{ month: "J", value: 228000 },
{ month: "J", value: 259000 },
{ month: "A", value: 294000 },
{ month: "S", value: 271000 },
{ month: "O", value: 318000 },
{ month: "N", value: 341000 },
{ month: "D", value: 354000 },
],
}
function formatTotalStreams(n: number) {
if (n >= 1_000_000)
return (n / 1_000_000).toFixed(2).replace(/\.?0+$/, "") + "M"
if (n >= 1_000) return (n / 1_000).toFixed(1).replace(/\.?0+$/, "") + "K"
return n.toString()
}
function formatPlays(n: number) {
if (n >= 1_000_000)
return (n / 1_000_000).toFixed(2).replace(/\.?0+$/, "") + "M"
if (n >= 1_000) return Math.round(n / 1_000) + "K"
return n.toString()
}
function formatListeners(n: number) {
return new Intl.NumberFormat("en-US").format(n)
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function ChartTooltip({ active, payload }: any) {
if (active && payload && payload.length) {
return (
<div className="bg-foreground rounded-xl px-3.5 py-2.5 shadow-lg">
<p className="text-background text-sm font-semibold">
{formatPlays(payload[0].value)} plays
</p>
<p className="text-background/60 text-xs">Nov 2024</p>
</div>
)
}
return null
}
export default function MusicStatsCard() {
return (
<div className="bg-card border-border w-full max-w-sm overflow-hidden rounded-3xl border shadow-xl">
{/* Header with gradient blobs */}
<div className="relative px-6 pt-6 pb-8">
<div className="pointer-events-none absolute inset-0 overflow-hidden">
<div className="bg-primary/70 absolute -top-10 -right-6 h-44 w-44 rounded-full blur-3xl" />
<div className="bg-primary/50 absolute -top-6 left-4 h-36 w-36 rounded-full blur-3xl" />
<div className="bg-primary/30 absolute top-2 right-16 h-28 w-28 rounded-full blur-2xl" />
</div>
{/* Logo + label */}
<div className="relative mb-8 flex items-center gap-3">
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-white shadow-sm">
<Headphones className="h-6 w-6 text-neutral-900" />
</div>
<div>
<p className="text-muted-foreground text-sm leading-none">Share</p>
<p className="text-foreground mt-0.5 text-base leading-none font-bold">
Music Stats
</p>
</div>
</div>
{/* Total streams */}
<p className="text-foreground relative text-4xl font-bold tracking-tight">
{formatTotalStreams(stats.totalStreams)}
</p>
<p className="text-muted-foreground relative mt-1 text-sm">
streams this year
</p>
{/* User */}
<div className="relative mt-4 space-y-0.5">
<p className="text-muted-foreground text-sm">{stats.user}</p>
<p className="text-foreground text-sm">
<span className="font-bold">
{formatListeners(stats.monthlyListeners)}
</span>{" "}
monthly listeners
</p>
</div>
</div>
{/* Genre allocation bar */}
<div className="mb-6 px-6">
<div className="flex h-3 w-full overflow-hidden rounded-full">
{stats.genres.map((genre) => (
<div
key={genre.name}
className="h-full"
style={{
width: `${genre.allocation}%`,
backgroundColor: genre.color,
}}
/>
))}
</div>
</div>
{/* Genre list */}
<div className="mb-4 space-y-4 px-6">
{stats.genres.map((genre) => (
<div key={genre.name} className="flex items-center gap-3">
<div
className="h-2.5 w-2.5 shrink-0 rounded-full"
style={{ backgroundColor: genre.color }}
/>
<span className="text-foreground flex-1 text-sm">{genre.name}</span>
<span
className={`w-16 text-right text-sm font-medium tabular-nums ${
genre.change >= 0 ? "text-emerald-500" : "text-red-500"
}`}
>
{genre.change >= 0 ? "+" : ""}
{genre.change.toFixed(2)}%
</span>
<span className="text-foreground w-16 text-right text-sm font-semibold tabular-nums">
{formatPlays(genre.plays)}
</span>
</div>
))}
</div>
{/* Chart */}
<div className="h-36 px-1">
<ResponsiveContainer width="100%" height="100%">
<AreaChart
data={stats.chartData}
margin={{ top: 12, right: 8, bottom: 0, left: 8 }}
>
<defs>
<linearGradient id="streamGradient" x1="0" y1="0" x2="0" y2="1">
<stop
offset="0%"
stopColor="hsl(var(--muted-foreground))"
stopOpacity={0.12}
/>
<stop
offset="100%"
stopColor="hsl(var(--muted-foreground))"
stopOpacity={0}
/>
</linearGradient>
</defs>
<CartesianGrid
vertical={false}
strokeDasharray="2 6"
stroke="hsl(var(--border))"
strokeOpacity={0.7}
/>
<XAxis
dataKey="month"
axisLine={false}
tickLine={false}
tick={{ fontSize: 11, fill: "hsl(var(--muted-foreground))" }}
interval={0}
/>
<Tooltip
content={<ChartTooltip />}
cursor={{
stroke: "hsl(var(--foreground))",
strokeWidth: 1,
strokeDasharray: "3 3",
}}
/>
<Area
type="monotone"
dataKey="value"
stroke="hsl(var(--muted-foreground))"
strokeWidth={1.5}
fill="url(#streamGradient)"
dot={false}
activeDot={{
r: 4,
fill: "hsl(var(--foreground))",
strokeWidth: 0,
}}
/>
</AreaChart>
</ResponsiveContainer>
</div>
{/* Actions */}
<div className="flex items-center gap-2 px-6 py-5">
<Button className="flex-1 rounded-full" size="lg">
Share now
<ArrowRight className="ml-1 h-4 w-4" />
</Button>
<Button variant="secondary" className="rounded-full px-6" size="lg">
Preview
</Button>
<Button
variant="secondary"
size="icon"
className="h-11 w-11 shrink-0 rounded-full"
>
<MoreVertical className="h-4 w-4" />
</Button>
</div>
</div>
)
}