{"$schema":"https:\/\/ui.particle.academy\/schema\/registry-item.json","name":"time-picker","type":"registry:ui","title":"TimePicker","description":"TimePicker from react-fancy","package":"react-fancy","dependencies":["lucide-react"],"registryDependencies":["inputs"],"files":[{"path":"components\/fancy\/time-picker\/TimePicker.tsx","content":"import { forwardRef, useCallback } from \"react\";\r\nimport { ChevronUp, ChevronDown } from \"lucide-react\";\r\nimport { cn } from \"..\/..\/utils\/cn\";\r\nimport { useControllableState } from \"..\/..\/hooks\/use-controllable-state\";\r\nimport { useFieldMode } from \"..\/inputs\/mode\/FieldMode.context\";\r\nimport { useInlineEdit } from \"..\/inputs\/mode\/useInlineEdit\";\r\nimport type { TimePickerProps } from \".\/TimePicker.types\";\r\n\r\nfunction pad(n: number): string {\r\n  return n.toString().padStart(2, \"0\");\r\n}\r\n\r\nfunction parseTime(val: string): { hours: number; minutes: number } {\r\n  const [h, m] = val.split(\":\").map(Number);\r\n  return { hours: h || 0, minutes: m || 0 };\r\n}\r\n\r\nexport const TimePicker = forwardRef<HTMLDivElement, TimePickerProps>(\r\n  function TimePicker(\r\n    {\r\n      value: controlledValue,\r\n      defaultValue = \"12:00\",\r\n      onChange,\r\n      format = \"12h\",\r\n      minuteStep = 1,\r\n      disabled = false,\r\n      className,\r\n      mode,\r\n    },\r\n    ref,\r\n  ) {\r\n    const [value, setValue] = useControllableState(\r\n      controlledValue,\r\n      defaultValue,\r\n      onChange,\r\n    );\r\n    const resolvedMode = useFieldMode(mode);\r\n    const { showControl, interactive, enterEdit, exitEdit } = useInlineEdit(resolvedMode, disabled);\r\n\r\n    const { hours: h24, minutes } = parseTime(value);\r\n    const isPM = h24 >= 12;\r\n    const displayHour = format === \"12h\" ? (h24 % 12 || 12) : h24;\r\n\r\n    if (!showControl) {\r\n      const formatted =\r\n        format === \"12h\"\r\n          ? `${pad(displayHour)}:${pad(minutes)} ${isPM ? \"PM\" : \"AM\"}`\r\n          : `${pad(displayHour)}:${pad(minutes)}`;\r\n      return (\r\n        <div\r\n          data-react-fancy-time-picker=\"\"\r\n          data-mode=\"view\"\r\n          ref={ref}\r\n          role={interactive ? \"button\" : undefined}\r\n          tabIndex={interactive ? 0 : undefined}\r\n          title={interactive ? \"Click to edit\" : undefined}\r\n          onClick={interactive ? enterEdit : undefined}\r\n          onKeyDown={\r\n            interactive\r\n              ? (e) => {\r\n                  if (e.key === \"Enter\" || e.key === \" \") {\r\n                    e.preventDefault();\r\n                    enterEdit();\r\n                  }\r\n                }\r\n              : undefined\r\n          }\r\n          className={cn(\r\n            \"text-sm font-medium tabular-nums text-zinc-900 dark:text-zinc-100\",\r\n            interactive &&\r\n              \"cursor-pointer rounded-md outline-none transition-colors hover:bg-zinc-100 focus-visible:ring-2 focus-visible:ring-blue-500\/40 dark:hover:bg-zinc-800\",\r\n            className,\r\n          )}\r\n        >\r\n          {formatted}\r\n        <\/div>\r\n      );\r\n    }\r\n\r\n    const updateTime = useCallback(\r\n      (hours: number, mins: number) => {\r\n        setValue(`${pad(hours)}:${pad(mins)}`);\r\n      },\r\n      [setValue],\r\n    );\r\n\r\n    const changeHour = useCallback(\r\n      (delta: number) => {\r\n        const newHour = (h24 + delta + 24) % 24;\r\n        updateTime(newHour, minutes);\r\n      },\r\n      [h24, minutes, updateTime],\r\n    );\r\n\r\n    const changeMinute = useCallback(\r\n      (delta: number) => {\r\n        const newMin = (minutes + delta + 60) % 60;\r\n        updateTime(h24, newMin);\r\n      },\r\n      [h24, minutes, updateTime],\r\n    );\r\n\r\n    const toggleAmPm = useCallback(() => {\r\n      updateTime((h24 + 12) % 24, minutes);\r\n    }, [h24, minutes, updateTime]);\r\n\r\n    const spinBtnClass =\r\n      \"flex h-7 w-full items-center justify-center rounded text-zinc-400 transition-colors hover:bg-zinc-100 hover:text-zinc-600 dark:hover:bg-zinc-800 dark:hover:text-zinc-300 disabled:opacity-50\";\r\n    const displayClass =\r\n      \"flex h-10 w-12 items-center justify-center text-lg font-medium tabular-nums\";\r\n\r\n    return (\r\n      <div\r\n        data-react-fancy-time-picker=\"\"\r\n        ref={ref}\r\n        onBlur={(e) => {\r\n          if (interactive && !e.currentTarget.contains(e.relatedTarget as Node)) exitEdit();\r\n        }}\r\n        className={cn(\r\n          \"inline-flex items-center gap-1 rounded-lg border border-zinc-200 bg-white p-2 dark:border-zinc-700 dark:bg-zinc-900\",\r\n          disabled && \"opacity-50\",\r\n          className,\r\n        )}\r\n      >\r\n        {\/* Hour column *\/}\r\n        <div className=\"flex flex-col items-center\">\r\n          <button type=\"button\" autoFocus={interactive} onClick={() => changeHour(1)} disabled={disabled} className={spinBtnClass} aria-label=\"Increase hour\">\r\n            <ChevronUp size={14} \/>\r\n          <\/button>\r\n          <div className={displayClass}>{pad(displayHour)}<\/div>\r\n          <button type=\"button\" onClick={() => changeHour(-1)} disabled={disabled} className={spinBtnClass} aria-label=\"Decrease hour\">\r\n            <ChevronDown size={14} \/>\r\n          <\/button>\r\n        <\/div>\r\n\r\n        <span className=\"text-lg text-zinc-400\">:<\/span>\r\n\r\n        {\/* Minute column *\/}\r\n        <div className=\"flex flex-col items-center\">\r\n          <button type=\"button\" onClick={() => changeMinute(minuteStep)} disabled={disabled} className={spinBtnClass} aria-label=\"Increase minute\">\r\n            <ChevronUp size={14} \/>\r\n          <\/button>\r\n          <div className={displayClass}>{pad(minutes)}<\/div>\r\n          <button type=\"button\" onClick={() => changeMinute(-minuteStep)} disabled={disabled} className={spinBtnClass} aria-label=\"Decrease minute\">\r\n            <ChevronDown size={14} \/>\r\n          <\/button>\r\n        <\/div>\r\n\r\n        {\/* AM\/PM toggle *\/}\r\n        {format === \"12h\" && (\r\n          <button\r\n            type=\"button\"\r\n            onClick={toggleAmPm}\r\n            disabled={disabled}\r\n            className=\"ml-1 rounded-lg bg-zinc-100 px-2 py-1 text-sm font-medium transition-colors hover:bg-zinc-200 dark:bg-zinc-800 dark:hover:bg-zinc-700\"\r\n          >\r\n            {isPM ? \"PM\" : \"AM\"}\r\n          <\/button>\r\n        )}\r\n      <\/div>\r\n    );\r\n  },\r\n);\r\n\r\nTimePicker.displayName = \"TimePicker\";\r\n","type":"registry:ui","target":"components\/fancy\/time-picker\/TimePicker.tsx"},{"path":"components\/fancy\/time-picker\/TimePicker.types.ts","content":"import type { FieldMode } from \"..\/inputs\/inputs.types\";\r\n\r\nexport interface TimePickerProps {\r\n  value?: string;\r\n  defaultValue?: string;\r\n  onChange?: (value: string) => void;\r\n  format?: \"12h\" | \"24h\";\r\n  minuteStep?: number;\r\n  disabled?: boolean;\r\n  className?: string;\r\n  \/** `\"edit\"` (default) renders the spinners; `\"view\"` renders the time as text. *\/\r\n  mode?: FieldMode;\r\n}\r\n","type":"registry:ui","target":"components\/fancy\/time-picker\/TimePicker.types.ts"},{"path":"components\/fancy\/time-picker\/index.ts","content":"export { TimePicker } from \".\/TimePicker\";\r\nexport type { TimePickerProps } from \".\/TimePicker.types\";\r\n","type":"registry:ui","target":"components\/fancy\/time-picker\/index.ts"}]}