Spaces:
Sleeping
Sleeping
Download src/utils/motion.ts from Chunte/lerobot-visualizer-test: direct link, hf CLI and curl.
- Browser
- Download file 2.64 kB
-
https://huggingface.co/spaces/Chunte/lerobot-visualizer-test/resolve/main/src/utils/motion.ts
- Command line
-
hf download hf://spaces/Chunte/lerobot-visualizer-test/src/utils/motion.ts
-
curl -L -o motion.ts https://huggingface.co/spaces/Chunte/lerobot-visualizer-test/resolve/main/src/utils/motion.ts
2.64 kB
| import { useEffect, useState } from "react"; | |
| /** | |
| * Motion helpers shared by every surface that animates out: menus, popovers, the phone drawer, | |
| * overlays. Durations come from the CSS tokens in globals.css so JS timers and CSS transitions | |
| * can't drift apart, and collapse to 0 under reduced motion. | |
| */ | |
| export function prefersReducedMotion(): boolean { | |
| return ( | |
| typeof window !== "undefined" && | |
| typeof window.matchMedia === "function" && | |
| window.matchMedia("(prefers-reduced-motion: reduce)").matches | |
| ); | |
| } | |
| /** "150ms" → 150, "0.25s" → 250; anything else → fallback. */ | |
| export function parseDurationMs(raw: string, fallback: number): number { | |
| const m = /^(-?\d*\.?\d+)(ms|s)$/.exec(raw.trim()); | |
| if (!m) return fallback; | |
| const n = parseFloat(m[1]); | |
| return m[2] === "s" ? n * 1000 : n; | |
| } | |
| /** A duration token (e.g. "--duration-quick") from :root, in ms. */ | |
| export function readDurationMs(token: string, fallback: number): number { | |
| if (typeof window === "undefined") return fallback; | |
| const raw = getComputedStyle(document.documentElement).getPropertyValue( | |
| token, | |
| ); | |
| return parseDurationMs(raw, fallback); | |
| } | |
| /** How long to keep a closing surface mounted: its exit token, or 0 under reduced motion. */ | |
| export function exitMs(token = "--duration-quick", fallback = 150): number { | |
| return prefersReducedMotion() ? 0 : readDurationMs(token, fallback); | |
| } | |
| export type PresenceState = "open" | "closing" | "closed"; | |
| /** | |
| * Keeps a surface mounted long enough to play its exit. Render it while the state isn't | |
| * "closed", put `data-state={state}` on it, and make it inert while "closing". Reopening during | |
| * the exit goes straight back to "open", so CSS transitions reverse from the live on-screen value | |
| * instead of jumping (interruptible, unlike keyframes). | |
| */ | |
| export function usePresence( | |
| open: boolean, | |
| token = "--duration-quick", | |
| fallback = 150, | |
| ): PresenceState { | |
| const [phase, setPhase] = useState<PresenceState>(open ? "open" : "closed"); | |
| // Derived during render (React's sanctioned pattern), so there is never a frame where an | |
| // open surface is unmounted or a closing one has already vanished. | |
| if (open && phase !== "open") setPhase("open"); | |
| if (!open && phase === "open") setPhase("closing"); | |
| useEffect(() => { | |
| if (phase !== "closing") return; | |
| const t = window.setTimeout( | |
| () => setPhase("closed"), | |
| exitMs(token, fallback), | |
| ); | |
| return () => window.clearTimeout(t); | |
| }, [phase, token, fallback]); | |
| if (open) return "open"; | |
| return phase === "open" ? "closing" : phase; | |
| } | |