Loading State

AI & Agents

Pixel-grid loader with shimmer and elapsed time.

Interactive Live Preview
components/ui/loading-state.tsxTypeScript · React 19
1"use client";
2
3import { useEffect, useState } from "react";
4
5/* ─────────────────────────────────────────────────────────
6 * LOADING STATE — pixel-grid loader for long-running work
7 *
8 * Variants:
9 * Drive — square cells, chevron wavefront driving right;
10 * the 650ms cycle is shorter than the sweep, so
11 * two fronts are always in flight
12 * Dots — same wavefront, circular cells
13 * Orbit — a comet lapping the grid perimeter
14 * Surfer — the Drive loader paired with a meme video below
15 *
16 * Paired with a shimmering label and a live elapsed timer
17 * in mono tabular figures. Reduced motion freezes the grid
18 * to its dim state; the timer still ticks.
19 * ───────────────────────────────────────────────────────── */
20
21const chevron = Array.from({ length: 9 }, (_, i) => {
22 const r = Math.floor(i / 3), c = i % 3;
23 return (c + Math.abs(r - 1)) * 90;
24});
25
26const ORBIT_ORDER = [0, 1, 2, 5, 8, 7, 6, 3];
27const orbit = Array.from({ length: 9 }, (_, i) => {
28 const k = ORBIT_ORDER.indexOf(i);
29 return k === -1 ? null : k * 110;
30});
31
32const PATTERNS: Record<string, { delays: (number | null)[]; dur: number; round: boolean }> = {
33 Drive: { delays: chevron, dur: 650, round: false },
34 Dots: { delays: chevron, dur: 650, round: true },
35 Orbit: { delays: orbit, dur: 950, round: false },
36};
37
38function LoaderGrid({
39 delays,
40 dur,
41 round,
42}: {
43 delays: (number | null)[];
44 dur: number;
45 round: boolean;
46}) {
47 return (
48 <span aria-hidden className="grid shrink-0 grid-cols-[repeat(3,4px)] gap-[1.5px]">
49 {delays.map((delay, index) => (
50 <span
51 key={index}
52 className={`size-[4px] bg-ink ${round ? "rounded-full" : "rounded-[1px]"}`}
53 style={{
54 opacity: delay === null ? 0.07 : 0.15,
55 animation: delay === null ? "none" : `pixel-on ${dur}ms ease-in-out ${delay}ms infinite`,
56 }}
57 />
58 ))}
59 </span>
60 );
61}
62
63function useElapsed() {
64 const [ds, setDs] = useState(0);
65 useEffect(() => {
66 const t = setInterval(() => setDs((d) => d + 1), 100);
67 return () => clearInterval(t);
68 }, []);
69 const total = ds / 10;
70 if (total < 60) return `${total.toFixed(1)}s`;
71 return `${Math.floor(total / 60)}m ${(total % 60).toFixed(1)}s`;
72}
73
74export default function LoadingState({
75 label,
76 variant = "Drive",
77 /** the meme feed for the Surfer variant; drop the file in /public to light it up */
78 videoSrc = "/subway-surfers.mp4",
79}: {
80 label?: string;
81 variant?: string;
82 videoSrc?: string;
83}) {
84 const elapsed = useElapsed();
85 const surfer = variant === "Surfer";
86 const resolvedLabel = label ?? (surfer ? "Subway surfing" : "Churning");
87 const [videoOk, setVideoOk] = useState(true);
88 const { delays, dur, round } = PATTERNS[variant] ?? PATTERNS.Drive;
89
90 const labelEl = (
91 <span
92 className="bg-clip-text text-[13px] font-medium text-transparent"
93 style={{
94 backgroundImage:
95 "linear-gradient(90deg, var(--ink-3) 35%, var(--ink) 50%, var(--ink-3) 65%)",
96 backgroundSize: "200% 100%",
97 animation: "shimmer-text 1.4s linear infinite",
98 }}
99 >
100 {resolvedLabel}
101 </span>
102 );
103 const elapsedEl = <span className="font-mono text-[12px] text-ink-3 tabular-nums">{elapsed}</span>;
104
105 if (surfer) {
106 return (
107 <div role="status" className="flex w-fit flex-col items-start">
108 <div className="flex items-center gap-2.5">
109 <LoaderGrid {...PATTERNS.Drive} />
110 {labelEl}
111 {elapsedEl}
112 </div>
113
114 {/* the context card follows the status text it is illustrating */}
115 <div
116 className="mt-2 w-56 overflow-hidden rounded-[10px] shadow-overlay"
117 style={{ animation: "pop-in 200ms cubic-bezier(0.16,1,0.3,1) both", transformOrigin: "top left" }}
118 >
119 <div className="relative aspect-video w-full" style={{ background: "var(--tooltip-bg)" }}>
120 {videoOk ? (
121 <video
122 src={videoSrc}
123 autoPlay
124 muted
125 loop
126 playsInline
127 onError={() => setVideoOk(false)}
128 className="h-full w-full object-cover"
129 />
130 ) : (
131 <div className="flex h-full w-full flex-col items-center justify-center gap-1.5">
132 <LoaderGrid {...PATTERNS.Drive} />
133 <span className="px-3 text-center font-mono text-[10px]" style={{ color: "var(--tooltip-muted)" }}>
134 Video unavailable
135 </span>
136 </div>
137 )}
138 </div>
139 </div>
140 </div>
141 );
142 }
143
144 return (
145 <div role="status" className="flex w-fit items-center gap-2.5">
146 <LoaderGrid delays={delays} dur={dur} round={round} />
147 {labelEl}
148 {elapsedEl}
149 </div>
150 );
151}
152

Props & Specification

PropTypeDefaultDescription
variantDrive | Dots | Orbit | SurferDriveVisual state and layout presentation mode.
classNamestring""Optional Tailwind CSS wrapper class overrides.

Related Components in AI & Agents