Files
fusion/packages/dashboard/app/components/LoadingSpinner.tsx
gsxdsm 3b61ac3d23 fix: make loading spinners actually spin across the dashboard
Many loading states rendered bare "Loading…" text with no spinner
element, and a couple rendered an unstyled `loading-spinner` div with no
matching CSS (invisible). The global spin animation was never broken —
it rotates in Chromium and WebKit — so prior transform-box edits chased
a non-bug, validated only by a string-matching CSS test.

Add a shared <LoadingSpinner> (self-contained animated SVG, no
lucide-react import so it survives partial test mocks) and adopt it
across ~47 loading placeholders so every loading state shows a
consistent animated spinner. Includes a component test that asserts the
svg carries the animate-spin utility.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 03:53:43 -07:00

48 lines
1.6 KiB
TypeScript

import "./LoadingSpinner.css";
interface LoadingSpinnerProps {
/** Already-translated text shown beside the spinner (omit for icon-only). */
label?: string;
/** Icon size in px. Defaults to 14 to match inline loading text. */
size?: number;
/** Extra class on the wrapper, e.g. to slot into a section's layout. */
className?: string;
}
/**
* Shared loading indicator: an animated spinner plus optional label.
*
* Most loading states across the dashboard historically rendered bare
* "Loading…" text with no spinner, so nothing actually spun. Drop this inside
* the existing loading container (keeping its class for layout) and pass the
* translated label to get a consistent animated spinner everywhere.
*
* The icon is a self-contained inline SVG (visually identical to lucide's
* Loader2) rather than a `lucide-react` import, so it renders in component
* tests that stub `lucide-react` with a partial mock. It spins via the global
* `.animate-spin` keyframe in styles.css.
*/
export const LoadingSpinner = ({ label, size = 14, className }: LoadingSpinnerProps) => (
<span
className={className ? `loading-spinner ${className}` : "loading-spinner"}
role="status"
aria-live="polite"
>
<svg
className="animate-spin"
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<path d="M21 12a9 9 0 1 1-6.219-8.56" />
</svg>
{label ? <span className="loading-spinner__label">{label}</span> : null}
</span>
);