{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ink/stopwatch",
  "title": "Stopwatch",
  "description": "Count-up stopwatch with laps and reset",
  "dependencies": [
    "ink"
  ],
  "registryDependencies": [
    "https://termcn.dev/r/ink/interaction.json",
    "https://termcn.dev/r/ink/use-interval.json",
    "https://termcn.dev/r/ink/use-theme.json",
    "https://termcn.dev/r/ink/use-unicode.json"
  ],
  "files": [
    {
      "path": "registry/ui/stopwatch.tsx",
      "content": "import { Box, Text } from \"ink\";\nimport React, { useState, useCallback, useRef } from \"react\";\n\nimport { useInteraction } from \"@/hooks/use-interaction\";\nimport type { InteractionProps } from \"@/hooks/use-interaction\";\nimport { useInterval } from \"@/hooks/use-interval\";\nimport { useTheme } from \"@/hooks/use-theme\";\nimport { useUnicode } from \"@/hooks/use-unicode\";\n\nexport interface StopwatchProps extends InteractionProps {\n  autoStart?: boolean;\n  color?: string;\n  showLaps?: boolean;\n  \"aria-label\"?: string;\n}\n\nconst pad = (n: number, len = 2) => String(n).padStart(len, \"0\");\n\nconst formatElapsed = (ms: number): string => {\n  const totalSeconds = Math.floor(ms / 1000);\n  const h = Math.floor(totalSeconds / 3600);\n  const m = Math.floor((totalSeconds % 3600) / 60);\n  const s = totalSeconds % 60;\n  const centis = Math.floor((ms % 1000) / 10);\n\n  return `${pad(h)}:${pad(m)}:${pad(s)}.${pad(centis)}`;\n};\n\nconst getStatus = (running: boolean, elapsed: number): string => {\n  if (running) {\n    return \"Running\";\n  }\n  if (elapsed === 0) {\n    return \"Ready\";\n  }\n  return \"Stopped\";\n};\n\nconst getStatusColor = (\n  running: boolean,\n  elapsed: number,\n  resolvedColor: string,\n  theme: ReturnType<typeof useTheme>\n): string => {\n  if (running) {\n    return resolvedColor;\n  }\n  if (elapsed === 0) {\n    return theme.colors.mutedForeground;\n  }\n  return theme.colors.warning;\n};\n\nexport const Stopwatch = ({\n  autoStart = false,\n  color,\n  showLaps = true,\n  id,\n  autoFocus,\n  isActive = true,\n  disabled,\n  \"aria-label\": ariaLabel,\n}: StopwatchProps) => {\n  const theme = useTheme();\n  const unicode = useUnicode();\n  const resolvedColor = color ?? theme.colors.primary;\n\n  const [running, setRunning] = useState(autoStart);\n  const [elapsed, setElapsed] = useState(0);\n  const [laps, setLaps] = useState<number[]>([]);\n  const lastTickRef = useRef<number>(autoStart ? Date.now() : 0);\n  const elapsedRef = useRef(0);\n\n  const tick = useCallback(() => {\n    const now = Date.now();\n    const delta = now - lastTickRef.current;\n    lastTickRef.current = now;\n    elapsedRef.current += delta;\n    setElapsed(elapsedRef.current);\n  }, []);\n\n  useInterval(tick, running ? 50 : null, { isActive });\n\n  const { isFocused } = useInteraction(\n    (input) => {\n      if (input === \" \") {\n        if (!running) {\n          lastTickRef.current = Date.now();\n        }\n        setRunning((r) => !r);\n      } else if (input === \"l\" && running) {\n        setLaps((prev) => [...prev, elapsedRef.current]);\n      } else if (input === \"r\") {\n        setRunning(false);\n        setElapsed(0);\n        elapsedRef.current = 0;\n        setLaps([]);\n      }\n    },\n    { autoFocus, disabled, id, isActive }\n  );\n\n  const status = getStatus(running, elapsed);\n  const statusColor = getStatusColor(running, elapsed, resolvedColor, theme);\n\n  return (\n    <Box\n      flexDirection=\"column\"\n      gap={0}\n      aria-role=\"timer\"\n      aria-label={\n        ariaLabel ??\n        `Stopwatch: ${formatElapsed(elapsed)}. ${status}. ${laps.length} laps.`\n      }\n      aria-state={{ busy: running, disabled: disabled || undefined }}\n    >\n      <Box gap={2} alignItems=\"center\">\n        <Text color={resolvedColor} bold>\n          {formatElapsed(elapsed)}\n        </Text>\n        <Text color={statusColor}>[{status}]</Text>\n      </Box>\n      <Text aria-hidden color={theme.colors.mutedForeground} dimColor>\n        {unicode\n          ? \"space start/stop · l lap · r reset\"\n          : \"space start/stop - l lap - r reset\"}\n      </Text>\n      {isFocused && <Text aria-hidden>Focused</Text>}\n      {showLaps && laps.length > 0 && (\n        <Box flexDirection=\"column\" marginTop={1}>\n          <Text color={theme.colors.mutedForeground} bold>\n            Laps:\n          </Text>\n          {laps.map((lapTime, i) => {\n            const prevLap = laps[i - 1] ?? 0;\n            const split = i === 0 ? lapTime : lapTime - prevLap;\n            return (\n              <Box key={i} gap={2}>\n                <Text color={theme.colors.mutedForeground}>\n                  #{String(i + 1).padStart(2, \"0\")}\n                </Text>\n                <Text color={resolvedColor}>{formatElapsed(lapTime)}</Text>\n                <Text color={theme.colors.mutedForeground} dimColor>\n                  +{formatElapsed(split)}\n                </Text>\n              </Box>\n            );\n          })}\n        </Box>\n      )}\n    </Box>\n  );\n};\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "utility"
  ],
  "type": "registry:ui"
}