{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "countdown",
  "title": "Countdown",
  "description": "A live countdown to a timestamp (vendor/weekly/event resets).",
  "registryDependencies": [
    "@engram/cn",
    "@engram/tokens"
  ],
  "files": [
    {
      "path": "src/components/countdown.tsx",
      "content": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { cn } from \"../lib/cn.js\";\n\nexport interface CountdownProps {\n  /** Target time — a Date or epoch milliseconds. */\n  until: Date | number;\n  /** Format remaining ms → string. Default: compact \"2d 4h\" / \"12m 30s\". */\n  format?: (msRemaining: number) => string;\n  /** Shown once the target has passed. Default \"—\". */\n  expiredLabel?: string;\n  /** Fired once when the timer crosses zero. */\n  onElapsed?: () => void;\n  className?: string;\n}\n\n/** Compact remaining-time formatter: \"2d 4h\", \"4h 12m\", \"12m 30s\", \"30s\". */\nexport function formatDuration(ms: number): string {\n  if (ms <= 0) return \"0s\";\n  const total = Math.floor(ms / 1000);\n  const days = Math.floor(total / 86400);\n  const hours = Math.floor((total % 86400) / 3600);\n  const minutes = Math.floor((total % 3600) / 60);\n  const seconds = total % 60;\n  if (days > 0) return `${days}d ${hours}h`;\n  if (hours > 0) return `${hours}h ${minutes}m`;\n  if (minutes > 0) return `${minutes}m ${seconds}s`;\n  return `${seconds}s`;\n}\n\n/**\n * A live countdown to a timestamp (vendor/weekly/event resets). Ticks each\n * second client-side; `format` controls the readout. Presentational — the\n * target time is supplied by the consumer.\n */\nexport function Countdown({\n  until,\n  format = formatDuration,\n  expiredLabel = \"—\",\n  onElapsed,\n  className,\n}: CountdownProps) {\n  const target = until instanceof Date ? until.getTime() : until;\n  const [now, setNow] = useState(() => Date.now());\n\n  useEffect(() => {\n    const id = setInterval(() => setNow(Date.now()), 1000);\n    return () => clearInterval(id);\n  }, []);\n\n  const remaining = target - now;\n  const expired = remaining <= 0;\n\n  useEffect(() => {\n    if (expired) onElapsed?.();\n  }, [expired, onElapsed]);\n\n  return (\n    <span\n      className={cn(\"font-engram-display tabular-nums\", className)}\n      suppressHydrationWarning\n    >\n      {expired ? expiredLabel : format(remaining)}\n    </span>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/engram/countdown.tsx"
    }
  ],
  "meta": {
    "level": "atom"
  },
  "docs": "Extend without forking: edit the copied source, use `asChild` (Radix Slot) to change the rendered element, pass the typed `annotations` prop for curated data (verdict/tags/per-plug), or use slot / render-prop props for arbitrary content. Requires the @engram/tokens theme (--engram-* CSS variables).",
  "categories": [
    "primitive"
  ],
  "type": "registry:component"
}