"use client"; /** * Web-adapted Spinner. * * The terminal Spinner (src/components/Spinner.tsx) drives animation via * useAnimationFrame and renders Unicode braille/block characters with ANSI * colour via Ink's . In the browser we replace that with a pure-CSS * spinning ring, preserving the same optional `tip` text and `mode` prop * surface so callers can swap in this component without changing props. */ import * as React from "react"; import { cn } from "@/lib/utils"; // ─── Types ──────────────────────────────────────────────────────────────────── /** Mirrors the SpinnerMode type from src/components/Spinner/index.ts */ export type SpinnerMode = | "queued" | "loading" | "thinking" | "auto" | "disabled"; export interface SpinnerProps { /** Visual mode — controls colour/appearance. */ mode?: SpinnerMode; /** Optional tip text shown next to the spinner. */ spinnerTip?: string; /** Override message replaces the default verb. */ overrideMessage?: string | null; /** Additional suffix appended after the main label. */ spinnerSuffix?: string | null; /** When true the spinner renders inline instead of as a block row. */ inline?: boolean; /** Extra class names for the wrapper element. */ className?: string; } // ─── Colour map ─────────────────────────────────────────────────────────────── const MODE_RING_CLASS: Record = { queued: "border-surface-500", loading: "border-brand-400", thinking: "border-brand-500", auto: "border-brand-400", disabled: "border-surface-600", }; const MODE_TEXT_CLASS: Record = { queued: "text-surface-400", loading: "text-brand-300", thinking: "text-brand-300", auto: "text-brand-300", disabled: "text-surface-500", }; const MODE_LABEL: Record = { queued: "Queued…", loading: "Loading…", thinking: "Thinking…", auto: "Working…", disabled: "", }; // ─── Component ──────────────────────────────────────────────────────────────── export function Spinner({ mode = "loading", spinnerTip, overrideMessage, spinnerSuffix, inline = false, className, }: SpinnerProps) { if (mode === "disabled") return null; const label = overrideMessage ?? spinnerTip ?? MODE_LABEL[mode]; const ringClass = MODE_RING_CLASS[mode]; const textClass = MODE_TEXT_CLASS[mode]; return ( {/* CSS spinning ring */} {/* Inner ring for the visible arc — achieved via box-shadow trick */} {(label || spinnerSuffix) && ( {label} {spinnerSuffix && ( {spinnerSuffix} )} )} ); } // ─── Shimmer / glimmer variant ──────────────────────────────────────────────── /** Pulsing shimmer bar — web replacement for GlimmerMessage / ShimmerChar. */ export function ShimmerBar({ className }: { className?: string }) { return (
); } /** Inline flashing cursor dot — web replacement for FlashingChar. */ export function FlashingCursor({ className }: { className?: string }) { return ( ); }