diff --git a/packages/ui/src/components/device-pairing.tsx b/packages/ui/src/components/device-pairing.tsx index dc5ab960..89dc79af 100644 --- a/packages/ui/src/components/device-pairing.tsx +++ b/packages/ui/src/components/device-pairing.tsx @@ -3,7 +3,7 @@ import { useState, useCallback, useRef, useEffect } from "react"; import { Button } from "@multica/ui/components/ui/button"; import { Textarea } from "@multica/ui/components/ui/textarea"; -import { Loading } from "@multica/ui/components/ui/loading"; +import { Spinner } from "@multica/ui/components/spinner"; import { useIsMobile } from "@multica/ui/hooks/use-mobile"; import { HugeiconsIcon } from "@hugeicons/react"; import { @@ -50,7 +50,7 @@ function ConnectionStatus({ return (
- +

{isVerifying ? "Waiting for approval" : "Connecting..."} diff --git a/packages/ui/src/components/spinner.tsx b/packages/ui/src/components/spinner.tsx index 10d802ab..9328784e 100644 --- a/packages/ui/src/components/spinner.tsx +++ b/packages/ui/src/components/spinner.tsx @@ -1,16 +1,12 @@ /** - * Spinner - 3x3 grid spinner based on SpinKit Grid + * Spinner — 3x3 grid pulse for **active processing / execution** states. * - * Features: - * - Uses currentColor (inherits text color from parent, typically theme primary) - * - Uses em sizing (scales with font-size) - * - 3x3 grid of cubes with staggered scale animation - * - Pure CSS animation (no JS state) + * Use when the system is actively doing work or waiting for human action + * (streaming content, generating responses, awaiting approval). + * For passive content-loading states, use `` instead. * - * Usage: - * // Uses primary theme color - * // Uses muted color - * // Controls size via Tailwind font-size + * Inherits color from `currentColor` (use Tailwind `text-*`). + * Scales with font-size (use Tailwind `text-*` for size). */ import { cn } from "@multica/ui/lib/utils" @@ -19,18 +15,33 @@ export interface SpinnerProps { className?: string } +const DELAYS = [0.2, 0.3, 0.4, 0.1, 0.2, 0.3, 0, 0.1, 0.2] + +const cubeStyle: React.CSSProperties = { + backgroundColor: "currentColor", + animation: "spinner-grid 1.3s infinite ease-in-out", + transform: "scale3d(0.5, 0.5, 1)", +} + export function Spinner({ className }: SpinnerProps) { return ( - - - - - - - - - - + + {DELAYS.map((delay, i) => ( + + ))} + + ) } diff --git a/packages/ui/src/components/ui/loading.tsx b/packages/ui/src/components/ui/loading.tsx index 6577fa97..8243b197 100644 --- a/packages/ui/src/components/ui/loading.tsx +++ b/packages/ui/src/components/ui/loading.tsx @@ -1,11 +1,52 @@ import { cn } from "@multica/ui/lib/utils" +const BAR_COUNT = 8 +const DURATION = 1.2 + +const bars = Array.from({ length: BAR_COUNT }, (_, i) => ({ + rotate: `${i * 45}deg`, + delay: `${-DURATION + (i * DURATION) / BAR_COUNT}s`, +})) + +/** + * Loading — Apple-style radiating-line spinner for **passive waiting** states. + * + * Use when the user is waiting for content to arrive (page init, data fetching). + * For active processing / execution states, use `` instead. + * + * Inherits color from `currentColor` (use Tailwind `text-*`). + * Scales with font-size (use Tailwind `text-*` for size). + */ function Loading({ className, ...props }: React.ComponentProps<"span">) { return ( - - {Array.from({ length: 9 }, (_, i) => ( - + + {bars.map((bar, i) => ( + ))} + + {/* keyframes injected once via ) } diff --git a/packages/ui/src/styles/globals.css b/packages/ui/src/styles/globals.css index f5adb57b..9598d9d0 100644 --- a/packages/ui/src/styles/globals.css +++ b/packages/ui/src/styles/globals.css @@ -183,46 +183,6 @@ } } -/* SPINNER - SpinKit Grid (3x3) */ -.spinner { - display: inline-grid; - grid-template-columns: repeat(3, 1fr); - width: 1em; - height: 1em; - gap: 0.08em; -} - -.spinner-cube { - background-color: currentColor; - animation: spinner-grid 1.3s infinite ease-in-out; - transform: scale3d(0.5, 0.5, 1); -} - -.spinner-cube:nth-child(1) { animation-delay: 0.2s; } -.spinner-cube:nth-child(2) { animation-delay: 0.3s; } -.spinner-cube:nth-child(3) { animation-delay: 0.4s; } -.spinner-cube:nth-child(4) { animation-delay: 0.1s; } -.spinner-cube:nth-child(5) { animation-delay: 0.2s; } -.spinner-cube:nth-child(6) { animation-delay: 0.3s; } -.spinner-cube:nth-child(7) { animation-delay: 0s; } -.spinner-cube:nth-child(8) { animation-delay: 0.1s; } -.spinner-cube:nth-child(9) { animation-delay: 0.2s; } - -@keyframes spinner-grid { - 0%, 70%, 100% { - transform: scale3d(0.5, 0.5, 1); - } - 35% { - transform: scale3d(0, 0, 1); - } -} - -@media (prefers-reduced-motion: reduce) { - .spinner-cube { - animation: none; - opacity: 0.7; - } -} /* Tool status: running glow pulse */ @keyframes glow-pulse {