Loading State
AI & AgentsPixel-grid loader with shimmer and elapsed time.
Interactive Live Preview
components/ui/loading-state.tsxTypeScript · React 19
1"use client";23import { useEffect, useState } from "react";45/* ─────────────────────────────────────────────────────────6 * LOADING STATE — pixel-grid loader for long-running work7 *8 * Variants:9 * Drive — square cells, chevron wavefront driving right;10 * the 650ms cycle is shorter than the sweep, so11 * two fronts are always in flight12 * Dots — same wavefront, circular cells13 * Orbit — a comet lapping the grid perimeter14 * Surfer — the Drive loader paired with a meme video below15 *16 * Paired with a shimmering label and a live elapsed timer17 * in mono tabular figures. Reduced motion freezes the grid18 * to its dim state; the timer still ticks.19 * ───────────────────────────────────────────────────────── */2021const 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});2526const 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});3132const 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};3738function 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 <span51 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}6263function 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}7374export 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;8990 const labelEl = (91 <span92 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>;104105 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>113114 {/* the context card follows the status text it is illustrating */}115 <div116 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 <video122 src={videoSrc}123 autoPlay124 muted125 loop126 playsInline127 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 unavailable135 </span>136 </div>137 )}138 </div>139 </div>140 </div>141 );142 }143144 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
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | Drive | Dots | Orbit | Surfer | Drive | Visual state and layout presentation mode. |
| className | string | "" | Optional Tailwind CSS wrapper class overrides. |