{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "countdown",
  "title": "Countdown",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "An SSR-safe timer with controlled or local seconds, pause support, and a completion announcement.",
  "registryDependencies": [
    "utils",
    "https://ui.ballmac.com/r/i18n.json"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/countdown.tsx",
      "content": "// Ballmac UI: Countdown. https://ui.ballmac.com/components/countdown\n\"use client\"\n\nimport * as React from \"react\"\nimport { cn } from \"@/lib/utils\"\nimport { useLocale, useMessages } from \"@/lib/ballmac/i18n\"\n\ntype CountdownProps = Omit<React.ComponentProps<\"div\">, \"defaultValue\"> & {\n  /** Controlled seconds remaining. */\n  value?: number\n  /** Initial seconds remaining when uncontrolled. */\n  defaultValue?: number\n  /** Called after a one-second tick. */\n  onValueChange?: (seconds: number) => void\n  /** Called once when an uncontrolled timer reaches zero. */\n  onComplete?: () => void\n  /** Pause or resume ticking. */\n  running?: boolean\n  /** Accessible purpose of the timer. */\n  label?: string\n}\n\nfunction Countdown({\n  className,\n  value,\n  defaultValue = 0,\n  onValueChange,\n  onComplete,\n  running = true,\n  label,\n  ...props\n}: CountdownProps) {\n  const msg = useMessages()\n  const locale = useLocale()\n  label ??= msg(\"countdown.label\", \"Time remaining\")\n  const [internal, setInternal] = React.useState(\n    Math.max(0, Math.floor(defaultValue)),\n  )\n  const remaining = Math.max(0, Math.floor(value ?? internal))\n  const onValueChangeRef = React.useRef(onValueChange)\n  const onCompleteRef = React.useRef(onComplete)\n  React.useEffect(() => {\n    onValueChangeRef.current = onValueChange\n    onCompleteRef.current = onComplete\n  }, [onValueChange, onComplete])\n  React.useEffect(() => {\n    if (!running || remaining === 0) return\n    const timer = window.setTimeout(() => {\n      const next = Math.max(0, remaining - 1)\n      if (value === undefined) setInternal(next)\n      onValueChangeRef.current?.(next)\n      if (next === 0) onCompleteRef.current?.()\n    }, 1000)\n    return () => window.clearTimeout(timer)\n  }, [running, remaining, value])\n  const hours = Math.floor(remaining / 3600)\n  const minutes = Math.floor((remaining % 3600) / 60)\n  const seconds = remaining % 60\n  const units =\n    hours > 0\n      ? ([\n          [hours, \"hours\"],\n          [minutes, \"minutes\"],\n          [seconds, \"seconds\"],\n        ] as const)\n      : ([\n          [minutes, \"minutes\"],\n          [seconds, \"seconds\"],\n        ] as const)\n  const unitFormat = (n: number, unit: \"hour\" | \"minute\" | \"second\") => new Intl.NumberFormat(locale, { style: \"unit\", unit, unitDisplay: \"long\" }).format(n)\n  const spoken = new Intl.ListFormat(locale, { type: \"unit\", style: \"short\" }).format([...(hours ? [unitFormat(hours, \"hour\")] : []), unitFormat(minutes, \"minute\"), unitFormat(seconds, \"second\")])\n  return (\n    <div\n      data-slot=\"countdown\"\n      role=\"timer\"\n      aria-label={`${label}: ${spoken}`}\n      className={cn(\n        \"inline-flex min-w-0 items-center gap-1.5 font-mono tabular-nums\",\n        className,\n      )}\n      {...props}\n    >\n      {units.map(([number, unit], index) => (\n        <React.Fragment key={unit}>\n          {index > 0 && (\n            <span aria-hidden=\"true\" className=\"text-muted-foreground text-lg\">\n              :\n            </span>\n          )}\n          <span\n            aria-hidden=\"true\"\n            data-slot=\"countdown-unit\"\n            className=\"bg-card flex min-w-11 items-center justify-center rounded-lg border border-border px-2 py-1.5 text-lg font-semibold shadow-sm\"\n          >\n            {String(number).padStart(2, \"0\")}\n          </span>\n        </React.Fragment>\n      ))}\n      <span className=\"sr-only\" aria-live=\"polite\">\n        {remaining === 0 ? msg(\"countdown.complete\", \"{label} complete\", { label }) : \"\"}\n      </span>\n    </div>\n  )\n}\n\nexport { Countdown, type CountdownProps }\n",
      "type": "registry:ui",
      "target": "@components/ballmac/countdown.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "timer",
      "countdown",
      "time"
    ],
    "version": "1.0.0",
    "updated": "2026-09-30",
    "ai": {
      "summary": "An SSR-safe timer with controlled or local seconds, pause support, and a completion announcement.",
      "whenToUse": [
        "Show time until a short event",
        "Display a temporary offer or session limit"
      ],
      "whenNotToUse": [
        "Use progress for work completion"
      ],
      "composesWith": [
        "progress-ring"
      ],
      "a11y": [
        {
          "keys": "None",
          "action": "Timer is labelled; completion is announced"
        }
      ],
      "customization": [
        "Controlled or uncontrolled seconds",
        "Pause and completion callback"
      ]
    },
    "examples": [
      "countdown-demo",
      "countdown-states"
    ],
    "url": "https://ui.ballmac.com/components/countdown"
  },
  "categories": [
    "feedback"
  ],
  "type": "registry:ui"
}