Data Refresh State
motiq/data-refresh-stateFreeComponent
A rich, accessible refresh indicator your app drives: idle, checking, refreshing (determinate or honestly-indeterminate), partially updated, success, stale, offline, error, paused, and cancelled — with manual refresh, cancel, retry, pause/resume, and interval controls in compact, inline, and panel modes. Never spins on idle, never fakes progress, and never signals status by color alone. Presentation-only: the host owns the fetch and progress.
Live preview
live · rendered by the registry
Rendered by motiq on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://www.motiq.dev/r/data-refresh-state.jsonSource
1"use client";23import * as React from "react";4import { motion, AnimatePresence } from "motion/react";56import { cn } from "@/lib/utils";7import {8 useReducedMotion,9 useAnimatedNumber,10 useDisclosure,11 useAnchoredPortal,12 getStatusMeta,13 statusVars,14 formatTimestamp as sharedFormatTimestamp,15 type StatusMeta,16 type StatusTone,17} from "@/lib/motiq";1819/* -------------------------------------------------------------------------- */20/* Custom interval dropdown — a real library listbox popover (not a native21 <select>): keyboard-navigable, dismiss-on-outside-click, animated, themed. */22/* -------------------------------------------------------------------------- */2324const formatInterval = (ms: number) => (ms >= 60000 ? `${Math.round(ms / 60000)}m` : `${Math.round(ms / 1000)}s`);2526function IntervalSelect({27 value,28 options,29 onChange,30}: {31 value: number;32 options: number[];33 onChange: (ms: number) => void;34}) {35 const reduce = useReducedMotion();36 const menu = useDisclosure({ idPrefix: "mk-interval", dismissable: true });37 const anchor = useAnchoredPortal(menu.open, { align: "end" });38 const current = options.includes(value) ? value : options[0];39 const [activeIdx, setActiveIdx] = React.useState(() => Math.max(0, options.indexOf(current)));4041 React.useEffect(() => {42 if (menu.open) setActiveIdx(Math.max(0, options.indexOf(current)));43 }, [menu.open, current, options]);4445 const commit = (ms: number) => {46 onChange(ms);47 menu.setOpen(false);48 };4950 const onKeyDown = (e: React.KeyboardEvent) => {51 if (!menu.open) {52 if (e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") {53 e.preventDefault();54 menu.setOpen(true);55 }56 return;57 }58 if (e.key === "ArrowDown") { e.preventDefault(); setActiveIdx((a) => Math.min(options.length - 1, a + 1)); }59 else if (e.key === "ArrowUp") { e.preventDefault(); setActiveIdx((a) => Math.max(0, a - 1)); }60 else if (e.key === "Home") { e.preventDefault(); setActiveIdx(0); }61 else if (e.key === "End") { e.preventDefault(); setActiveIdx(options.length - 1); }62 else if (e.key === "Enter" || e.key === " ") { e.preventDefault(); commit(options[activeIdx]); }63 };6465 return (66 <div ref={menu.rootRef as React.RefObject<HTMLDivElement>} className="relative" onKeyDown={onKeyDown}>67 <button68 type="button"69 {...menu.triggerProps}70 ref={anchor.triggerRef as React.RefObject<HTMLButtonElement>}71 aria-haspopup="listbox"72// … truncated
What it pulls in
npm packages
Other registry items
Files it writes
More from motiq
All 100 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Activity Streamactivity-stream | motiq | Components | Free | 1 dep | |
| Adaptive Safe-Zone Gridadaptive-safe-zone-grid | motiq | Components | Free | no deps | |
| Agent Run Timelineagent-run-timeline | motiq | Components | Free | 1 dep | |
| AI Response Streamai-response-stream | motiq | Components | Free | 1 dep | |
| Animated Accordionanimated-accordion | motiq | Components | Free | 2 deps | |
| Animated Buttonanimated-button | motiq | Components | Free | 1 dep | |
| Animated Dialoganimated-dialog | motiq | Components | Free | 2 deps | |
| Animated Gridanimated-grid | motiq | Components | Free | 2 deps |
