{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "countdown-timer",
  "type": "registry:component",
  "title": "Countdown Timer",
  "description": "Countdown and SLA timer for deadlines, escalations, and response windows.",
  "dependencies": [
    "@vllnt/ui@^0.2.1"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/countdown-timer/countdown-timer.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { cn } from \"@vllnt/ui\";\nimport { Badge } from \"@vllnt/ui\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n} from \"@vllnt/ui\";\n\nexport type CountdownTimerProps = React.ComponentPropsWithoutRef<\"div\"> & {\n  deadline: Date | number | string;\n  description?: string;\n  now?: Date | number | string;\n  startedAt?: Date | number | string;\n  tickMs?: number;\n  title?: string;\n  warningThresholdMs?: number;\n};\n\ntype TimerSegment = {\n  label: string;\n  value: string;\n};\n\nfunction normalizeDate(input: Date | number | string): Date {\n  if (input instanceof Date) {\n    return new Date(input.getTime());\n  }\n\n  return new Date(input);\n}\n\nfunction useLiveDate(now: CountdownTimerProps[\"now\"], tickMs: number) {\n  const fixedNow = React.useMemo(\n    () => (now ? normalizeDate(now) : undefined),\n    [now],\n  );\n  const [liveNow, setLiveNow] = React.useState<Date>(fixedNow ?? new Date());\n\n  React.useEffect(() => {\n    if (fixedNow) {\n      setLiveNow(fixedNow);\n      return;\n    }\n\n    const interval = window.setInterval(() => {\n      setLiveNow(new Date());\n    }, tickMs);\n\n    return () => {\n      window.clearInterval(interval);\n    };\n  }, [fixedNow, tickMs]);\n\n  return liveNow;\n}\n\nfunction getRemainingMs(deadline: Date, now: Date): number {\n  return deadline.getTime() - now.getTime();\n}\n\nfunction getStatus(\n  remainingMs: number,\n  warningThresholdMs: number,\n): {\n  badgeVariant: \"default\" | \"destructive\" | \"secondary\";\n  label: string;\n  toneClassName: string;\n} {\n  if (remainingMs <= 0) {\n    return {\n      badgeVariant: \"destructive\",\n      label: \"Breached\",\n      toneClassName: \"bg-destructive\",\n    };\n  }\n\n  if (remainingMs <= warningThresholdMs) {\n    return {\n      badgeVariant: \"secondary\",\n      label: \"At risk\",\n      toneClassName: \"bg-amber-500\",\n    };\n  }\n\n  return {\n    badgeVariant: \"default\",\n    label: \"On track\",\n    toneClassName: \"bg-emerald-500\",\n  };\n}\n\nfunction formatSegments(milliseconds: number): TimerSegment[] {\n  const totalSeconds = Math.max(0, Math.floor(milliseconds / 1000));\n  const days = Math.floor(totalSeconds / 86_400);\n  const hours = Math.floor((totalSeconds % 86_400) / 3600);\n  const minutes = Math.floor((totalSeconds % 3600) / 60);\n  const seconds = totalSeconds % 60;\n\n  return [\n    { label: \"Days\", value: String(days).padStart(2, \"0\") },\n    { label: \"Hours\", value: String(hours).padStart(2, \"0\") },\n    { label: \"Minutes\", value: String(minutes).padStart(2, \"0\") },\n    { label: \"Seconds\", value: String(seconds).padStart(2, \"0\") },\n  ];\n}\n\nconst DEADLINE_FORMATTER = new Intl.DateTimeFormat(\"en-US\", {\n  day: \"numeric\",\n  hour: \"numeric\",\n  minute: \"2-digit\",\n  month: \"short\",\n  timeZoneName: \"short\",\n});\n\nfunction formatDeadline(date: Date): string {\n  return DEADLINE_FORMATTER.format(date);\n}\n\nfunction getProgress(deadline: Date, now: Date, startedAt?: Date): number {\n  if (!startedAt) {\n    return 0;\n  }\n\n  const total = deadline.getTime() - startedAt.getTime();\n\n  if (total <= 0) {\n    return 100;\n  }\n\n  const elapsed = now.getTime() - startedAt.getTime();\n\n  return Math.min(100, Math.max(0, (elapsed / total) * 100));\n}\n\nfunction TimerSegments({ segments }: { segments: TimerSegment[] }) {\n  return (\n    <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-4\">\n      {segments.map((segment) => (\n        <div\n          className=\"rounded-lg border bg-background/80 px-3 py-4 text-center\"\n          key={segment.label}\n        >\n          <div className=\"text-2xl font-semibold tracking-tight\">\n            {segment.value}\n          </div>\n          <div className=\"text-xs uppercase tracking-[0.18em] text-muted-foreground\">\n            {segment.label}\n          </div>\n        </div>\n      ))}\n    </div>\n  );\n}\n\nfunction TimerProgress({\n  progress,\n  remainingMs,\n  segments,\n  startedAt,\n  toneClassName,\n}: {\n  progress: number;\n  remainingMs: number;\n  segments: TimerSegment[];\n  startedAt?: Date;\n  toneClassName: string;\n}) {\n  const progressWidth = startedAt ? progress : remainingMs <= 0 ? 100 : 0;\n  const statusText = startedAt\n    ? `${Math.round(progress)}% used`\n    : segments.map((segment) => segment.value).join(\":\");\n\n  return (\n    <div className=\"space-y-2\">\n      <div className=\"flex items-center justify-between text-xs text-muted-foreground\">\n        <span>{startedAt ? \"SLA elapsed\" : \"Time remaining\"}</span>\n        <span>{statusText}</span>\n      </div>\n      <div className=\"h-2 overflow-hidden rounded-full bg-muted\">\n        <div\n          className={cn(\"h-full transition-all\", toneClassName)}\n          style={{ width: `${progressWidth}%` }}\n        />\n      </div>\n    </div>\n  );\n}\n\nexport const CountdownTimer = React.forwardRef<\n  HTMLDivElement,\n  CountdownTimerProps\n>(\n  (\n    {\n      className,\n      deadline,\n      description,\n      now,\n      startedAt,\n      tickMs = 1000,\n      title = \"Countdown timer\",\n      warningThresholdMs = 15 * 60 * 1000,\n      ...props\n    },\n    ref,\n  ) => {\n    const deadlineDate = React.useMemo(\n      () => normalizeDate(deadline),\n      [deadline],\n    );\n    const startedAtDate = React.useMemo(\n      () => (startedAt ? normalizeDate(startedAt) : undefined),\n      [startedAt],\n    );\n    const liveNow = useLiveDate(now, tickMs);\n    const remainingMs = getRemainingMs(deadlineDate, liveNow);\n    const status = getStatus(remainingMs, warningThresholdMs);\n    const segments = formatSegments(Math.abs(remainingMs));\n    const progress = getProgress(deadlineDate, liveNow, startedAtDate);\n\n    return (\n      <Card className={cn(\"shadow-sm\", className)} ref={ref} {...props}>\n        <CardHeader className=\"space-y-2 pb-3\">\n          <div className=\"flex items-start justify-between gap-3\">\n            <div>\n              <CardTitle className=\"text-base\">{title}</CardTitle>\n              <CardDescription>\n                {description ?? `Deadline ${formatDeadline(deadlineDate)}`}\n              </CardDescription>\n            </div>\n            <Badge variant={status.badgeVariant}>{status.label}</Badge>\n          </div>\n        </CardHeader>\n        <CardContent className=\"space-y-4\">\n          <TimerSegments segments={segments} />\n          <TimerProgress\n            progress={progress}\n            remainingMs={remainingMs}\n            segments={segments}\n            startedAt={startedAtDate}\n            toneClassName={status.toneClassName}\n          />\n        </CardContent>\n      </Card>\n    );\n  },\n);\n\nCountdownTimer.displayName = \"CountdownTimer\";\n",
      "type": "registry:component"
    }
  ],
  "version": "0.2.1",
  "stability": "stable"
}
