Dashboard Populate
An empty dashboard cascades to life with KPI count-ups, bar bounces, line traces, and a donut fill.
registry/bases/editframe/components/dashboard-populate/index.tsx
"use client";
import { Timegroup } from "@editframe/react";
import type { CSSProperties } from "react";
const FONT_FAMILY =
"var(--font-geist-sans), -apple-system, BlinkMacSystemFont, sans-serif";
const MONO_FAMILY =
"var(--font-geist-mono), ui-monospace, SFMono-Regular, Menlo, monospace";
const BAR_VALUES = [0.55, 0.78, 0.42, 0.92, 0.66];
const LINE_POINTS: [number, number][] = [
[0, 70],
[16, 55],
[32, 62],
[48, 38],
[64, 45],
[80, 22],
[100, 12],
];
const STRUCTURE_END = 15;
const buildLinePath = () =>
LINE_POINTS.map(([x, y], i) => `${i === 0 ? "M" : "L"}${x},${y}`).join(" ");
const formatNumber = (n: number) => n.toLocaleString("en-US");
export interface DashboardPopulateProps {
accentColor?: string;
kpiTarget?: number;
speed?: number;
fps?: number;
durationInFrames?: number;
width?: number;
height?: number;
className?: string;
}
const Tile = ({
title,
children,
style,
}: {
title: string;
children: React.ReactNode;
style?: CSSProperties;
}) => (
<div
style={{
background: "linear-gradient(180deg, #131316 0%, #0d0d10 100%)",
border: "1px solid rgba(255,255,255,0.08)",
borderRadius: 16,
boxShadow:
"inset 0 1px 0 rgba(255,255,255,0.04), 0 20px 40px rgba(0,0,0,0.3)",
display: "flex",
flexDirection: "column",
gap: 16,
padding: 24,
position: "relative",
...style,
}}
>
<div
style={{
color: "rgba(255,255,255,0.5)",
fontSize: 12,
fontWeight: 500,
letterSpacing: "0.08em",
textTransform: "uppercase",
}}
>
{title}
</div>
<div style={{ flex: 1, position: "relative" }}>{children}</div>
</div>
);
export const DashboardPopulate = ({
accentColor = "#22c55e",
kpiTarget = 128_400,
speed = 1,
fps = 30,
durationInFrames = 180,
width = 1280,
height = 720,
className,
}: DashboardPopulateProps) => {
const safeSpeed = Math.max(0.01, speed);
const durationMs = (durationInFrames / fps) * 1000;
const frameMs = 1000 / fps;
const structureEndMs = STRUCTURE_END * frameMs;
const kpiMs = ((60 / fps) * 1000) / safeSpeed;
const barStaggerMs = ((4 / fps) * 1000) / safeSpeed;
const lineMs = ((50 / fps) * 1000) / safeSpeed;
const containerStyle: CSSProperties = {
background: "#09090b",
color: "white",
display: "flex",
flexDirection: "column",
fontFamily: FONT_FAMILY,
gap: 24,
height,
overflow: "hidden",
padding: 64,
position: "relative",
width,
};
const linePath = buildLinePath();
const donutR = 70;
const donutCircum = 2 * Math.PI * donutR;
return (
<Timegroup
className={className}
duration={`${durationMs}ms`}
mode="fixed"
style={containerStyle}
>
<>
<style>{`
@keyframes framecn-dashboard-structure {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes framecn-dashboard-kpi {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes framecn-dashboard-bar {
from { height: 0; }
to { height: var(--bar-h); }
}
@keyframes framecn-dashboard-line {
from { stroke-dashoffset: 1; }
to { stroke-dashoffset: 0; }
}
@keyframes framecn-dashboard-donut {
from { stroke-dashoffset: ${donutCircum}; }
to { stroke-dashoffset: ${donutCircum * 0.28}; }
}
`}</style>
{/* Subtle grid bg */}
<div
style={{
backgroundImage:
"linear-gradient(rgba(255,255,255,0.022) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,0.022) 1px, transparent 1px)",
backgroundSize: "48px 48px",
inset: 0,
position: "absolute",
}}
/>
{/* Header */}
<div
style={{
alignItems: "center",
animation: `framecn-dashboard-structure ${structureEndMs}ms ease-out backwards`,
display: "flex",
justifyContent: "space-between",
position: "relative",
}}
>
<div>
<div
style={{
fontSize: 28,
fontWeight: 600,
letterSpacing: "-0.02em",
}}
>
Overview
</div>
<div
style={{
fontSize: 14,
marginTop: 4,
opacity: 0.5,
}}
>
Last 30 days
</div>
</div>
<div
style={{
display: "flex",
fontFamily: MONO_FAMILY,
fontSize: 12,
gap: 8,
}}
>
{["1D", "7D", "30D", "ALL"].map((l, i) => (
<div
key={l}
style={{
background:
i === 2
? "rgba(255,255,255,0.1)"
: "rgba(255,255,255,0.03)",
border: "1px solid rgba(255,255,255,0.06)",
borderRadius: 8,
padding: "6px 12px",
}}
>
{l}
</div>
))}
</div>
</div>
{/* Grid */}
<div
style={{
display: "grid",
flex: 1,
gap: 24,
gridTemplateColumns: "1fr 1fr",
gridTemplateRows: "1fr 1fr",
position: "relative",
}}
>
{/* KPI */}
<Tile
title="Revenue"
style={{
animation: `framecn-dashboard-structure ${structureEndMs}ms ease-out backwards`,
}}
>
<div
style={{
animation: `framecn-dashboard-kpi ${kpiMs}ms cubic-bezier(0.16, 1, 0.3, 1) ${structureEndMs}ms backwards`,
display: "flex",
flexDirection: "column",
gap: 12,
height: "100%",
justifyContent: "center",
}}
>
<div
style={{
fontFamily: MONO_FAMILY,
fontSize: 64,
fontVariantNumeric: "tabular-nums",
fontWeight: 700,
letterSpacing: "-0.04em",
}}
>
${formatNumber(kpiTarget)}
</div>
<div
style={{
alignItems: "center",
color: accentColor,
display: "flex",
fontSize: 14,
gap: 8,
}}
>
<span style={{ fontWeight: 600 }}>+12.4%</span>
<span style={{ color: "white", opacity: 0.5 }}>
vs last month
</span>
</div>
</div>
</Tile>
{/* Bar chart */}
<Tile
title="Sessions"
style={{
animation: `framecn-dashboard-structure ${structureEndMs}ms ease-out backwards`,
}}
>
<svg
viewBox="0 0 200 100"
preserveAspectRatio="none"
style={{ height: "100%", width: "100%" }}
>
<line
x1="0"
y1="100"
x2="200"
y2="100"
stroke="rgba(255,255,255,0.15)"
strokeWidth="0.5"
/>
{[25, 50, 75].map((y) => (
<line
key={y}
x1="0"
y1={y}
x2="200"
y2={y}
stroke="rgba(255,255,255,0.06)"
strokeWidth="0.5"
strokeDasharray="2 2"
/>
))}
{BAR_VALUES.map((v, i) => {
const barH = v * 90;
const barW = 24;
const x = 16 + i * 36;
const y = 100 - barH;
return (
<rect
key={i}
x={x}
y={y}
width={barW}
height={barH}
rx={3}
fill={accentColor}
opacity={0.85}
style={{
animation: `framecn-dashboard-bar ${(barH / 90) * kpiMs}ms cubic-bezier(0.16, 1, 0.3, 1) ${structureEndMs + i * barStaggerMs}ms backwards`,
transformBox: "fill-box",
transformOrigin: `0 ${100 - y}px`,
}}
/>
);
})}
</svg>
</Tile>
{/* Line chart */}
<Tile
title="Active users"
style={{
animation: `framecn-dashboard-structure ${structureEndMs}ms ease-out backwards`,
}}
>
<svg
viewBox="0 0 100 100"
preserveAspectRatio="none"
style={{ height: "100%", width: "100%" }}
>
<line
x1="0"
y1="100"
x2="100"
y2="100"
stroke="rgba(255,255,255,0.15)"
strokeWidth="0.4"
/>
{[20, 40, 60, 80].map((y) => (
<line
key={y}
x1="0"
y1={y}
x2="100"
y2={y}
stroke="rgba(255,255,255,0.06)"
strokeWidth="0.4"
strokeDasharray="1.5 1.5"
/>
))}
<path
d={linePath}
fill="none"
stroke={accentColor}
strokeWidth="1.6"
strokeLinecap="round"
strokeLinejoin="round"
pathLength={1}
style={{
animation: `framecn-dashboard-line ${lineMs}ms cubic-bezier(0.16, 1, 0.3, 1) ${structureEndMs + barStaggerMs * 5}ms backwards`,
strokeDasharray: 1,
}}
/>
</svg>
</Tile>
{/* Donut */}
<Tile
title="Conversion"
style={{
animation: `framecn-dashboard-structure ${structureEndMs}ms ease-out backwards`,
}}
>
<div
style={{
alignItems: "center",
display: "flex",
height: "100%",
justifyContent: "center",
position: "relative",
}}
>
<svg width="180" height="180" viewBox="0 0 180 180">
<circle
cx="90"
cy="90"
r={donutR}
fill="none"
stroke="rgba(255,255,255,0.08)"
strokeWidth="14"
/>
<circle
cx="90"
cy="90"
r={donutR}
fill="none"
stroke={accentColor}
strokeWidth="14"
strokeLinecap="round"
style={{
animation: `framecn-dashboard-donut ${lineMs}ms cubic-bezier(0.16, 1, 0.3, 1) ${structureEndMs + barStaggerMs * 6}ms backwards`,
strokeDasharray: donutCircum,
transform: "rotate(-90deg)",
transformOrigin: "center",
}}
/>
</svg>
<div
style={{
fontFamily: MONO_FAMILY,
fontSize: 36,
fontVariantNumeric: "tabular-nums",
fontWeight: 700,
letterSpacing: "-0.02em",
position: "absolute",
}}
>
72.0%
</div>
</div>
</Tile>
</div>
</>
</Timegroup>
);
};
- Where it goes
- statskanbandashboard-shell
- Interaction
- animated-counter
- Use cases
- dashboard
DependenciesBundle weight · light
remotion