{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "time-picker",
  "title": "Time Picker",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-react/components/time-picker/time-picker.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport { Clock, X } from 'lucide-react'\nimport { Button } from '@/components/ui/button'\nimport { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'\nimport { ScrollArea } from '@/components/ui/scroll-area'\nimport { cn } from '@/lib/utils'\n\nexport type TimeFormat = 'HH:mm' | 'HH:mm:ss' | 'hh:mm A'\n\nexport interface TimeParts {\n  hour24: number\n  minute: number\n  second: number\n}\n\nexport interface TimePreset {\n  label: string\n  value: string\n}\n\nexport type TimePickerSize = 'small' | 'middle' | 'large'\nexport type TimePickerStatus = 'error' | 'warning'\n\nfunction parse(v: string): TimeParts | null {\n  if (!v) return null\n  const m = /^(\\d{1,2}):(\\d{2})(?::(\\d{2}))?$/.exec(v.trim())\n  if (!m) return null\n  const h = Number(m[1])\n  const min = Number(m[2])\n  const s = m[3] ? Number(m[3]) : 0\n  if (\n    Number.isNaN(h) ||\n    Number.isNaN(min) ||\n    Number.isNaN(s) ||\n    h < 0 ||\n    h > 23 ||\n    min < 0 ||\n    min > 59 ||\n    s < 0 ||\n    s > 59\n  )\n    return null\n  return { hour24: h, minute: min, second: s }\n}\n\nfunction toMinutes(p: TimeParts) {\n  return p.hour24 * 60 + p.minute + p.second / 60\n}\n\n// ---- TimeColumns ----\n\nexport interface TimeColumnsProps {\n  /** Time value. HH:mm or HH:mm:ss depending on format. Empty = no selection. */\n  value?: string\n  onValueChange?: (value: string) => void\n  use24Hour?: boolean\n  use12Hours?: boolean\n  minuteStep?: number\n  hourStep?: number\n  secondStep?: number\n  /** 24h HH:mm or HH:mm:ss. */\n  minTime?: string\n  /** 24h HH:mm or HH:mm:ss. */\n  maxTime?: string\n  format?: TimeFormat\n  disabledHours?: () => number[]\n  disabledMinutes?: (selectedHour: number) => number[]\n  disabledSeconds?: (selectedHour: number, selectedMinute: number) => number[]\n  hideDisabledOptions?: boolean\n  /** Auto-scroll active rows into view when this becomes true. */\n  visible?: boolean\n}\n\nfunction TimeColumns({\n  value = '',\n  onValueChange,\n  use24Hour = false,\n  use12Hours = false,\n  minuteStep = 5,\n  hourStep = 1,\n  secondStep = 1,\n  minTime,\n  maxTime,\n  format = 'HH:mm',\n  disabledHours,\n  disabledMinutes,\n  disabledSeconds,\n  hideDisabledOptions = false,\n  visible = true,\n}: TimeColumnsProps) {\n  const parts = parse(value)\n\n  function format24(p: TimeParts) {\n    if (format === 'HH:mm:ss') {\n      return `${String(p.hour24).padStart(2, '0')}:${String(p.minute).padStart(2, '0')}:${String(p.second).padStart(2, '0')}`\n    }\n    return `${String(p.hour24).padStart(2, '0')}:${String(p.minute).padStart(2, '0')}`\n  }\n\n  const showSeconds = format === 'HH:mm:ss'\n\n  const effective12Hour = format === 'hh:mm A' ? true : use12Hours ? true : !use24Hour\n\n  const hour12 = parts ? (parts.hour24 % 12 === 0 ? 12 : parts.hour24 % 12) : null\n  const period = parts && parts.hour24 >= 12 ? 'PM' : 'AM'\n\n  const minBound = parse(minTime ?? '') ?? null\n  const maxBound = parse(maxTime ?? '') ?? null\n\n  function withinBounds(p: TimeParts) {\n    const m = toMinutes(p)\n    if (minBound && m < toMinutes(minBound)) return false\n    if (maxBound && m > toMinutes(maxBound)) return false\n    return true\n  }\n\n  const disabledHoursSet = new Set(disabledHours ? disabledHours() : [])\n  const disabledMinutesSet = new Set(disabledMinutes ? disabledMinutes(parts?.hour24 ?? 0) : [])\n  const disabledSecondsSet = new Set(\n    disabledSeconds ? disabledSeconds(parts?.hour24 ?? 0, parts?.minute ?? 0) : [],\n  )\n\n  function isHourDisabledItem(h: number) {\n    if (disabledHoursSet.has(h)) return true\n    const cur = parts ?? { hour24: 0, minute: 0, second: 0 }\n    if (effective12Hour) {\n      const isPM = period === 'PM'\n      return !withinBounds({ hour24: (h % 12) + (isPM ? 12 : 0), minute: cur.minute, second: cur.second })\n    }\n    return !withinBounds({ hour24: h, minute: cur.minute, second: cur.second })\n  }\n\n  function isMinuteDisabledItem(m: number) {\n    if (disabledMinutesSet.has(m)) return true\n    const cur = parts ?? { hour24: 0, minute: 0, second: 0 }\n    return !withinBounds({ hour24: cur.hour24, minute: m, second: cur.second })\n  }\n\n  function isSecondDisabledItem(s: number) {\n    if (disabledSecondsSet.has(s)) return true\n    const cur = parts ?? { hour24: 0, minute: 0, second: 0 }\n    return !withinBounds({ hour24: cur.hour24, minute: cur.minute, second: s })\n  }\n\n  const hourStepN = Math.max(1, hourStep)\n  const hours12List = Array.from({ length: 12 }, (_, i) => i + 1)\n    .filter((h) => (h - 1) % hourStepN === 0)\n    .filter((h) => !hideDisabledOptions || !isHourDisabledItem(h))\n\n  const hours24List = Array.from({ length: 24 }, (_, i) => i)\n    .filter((h) => h % hourStepN === 0)\n    .filter((h) => !hideDisabledOptions || !isHourDisabledItem(h))\n\n  const minStepN = Math.max(1, Math.min(60, minuteStep))\n  const minutesList = Array.from({ length: Math.ceil(60 / minStepN) }, (_, i) => i * minStepN).filter(\n    (m) => !hideDisabledOptions || !isMinuteDisabledItem(m),\n  )\n\n  const secStepN = Math.max(1, Math.min(60, secondStep))\n  const secondsList = Array.from({ length: Math.ceil(60 / secStepN) }, (_, i) => i * secStepN).filter(\n    (s) => !hideDisabledOptions || !isSecondDisabledItem(s),\n  )\n\n  function commit(p: TimeParts) {\n    if (!withinBounds(p)) return\n    onValueChange?.(format24(p))\n  }\n\n  function pickHour12(h: number) {\n    const cur = parts ?? { hour24: 0, minute: 0, second: 0 }\n    const isPM = period === 'PM'\n    commit({ hour24: (h % 12) + (isPM ? 12 : 0), minute: cur.minute, second: cur.second })\n  }\n\n  function pickHour24(h: number) {\n    const cur = parts ?? { hour24: 0, minute: 0, second: 0 }\n    commit({ hour24: h, minute: cur.minute, second: cur.second })\n  }\n\n  function pickMinute(m: number) {\n    const cur = parts ?? { hour24: 0, minute: 0, second: 0 }\n    commit({ hour24: cur.hour24, minute: m, second: cur.second })\n  }\n\n  function pickSecond(s: number) {\n    const cur = parts ?? { hour24: 0, minute: 0, second: 0 }\n    commit({ hour24: cur.hour24, minute: cur.minute, second: s })\n  }\n\n  function pickPeriod(p: 'AM' | 'PM') {\n    const cur = parts ?? { hour24: 0, minute: 0, second: 0 }\n    const h12 = cur.hour24 % 12\n    commit({ hour24: h12 + (p === 'PM' ? 12 : 0), minute: cur.minute, second: cur.second })\n  }\n\n  const hourCol = React.useRef<HTMLDivElement | null>(null)\n  const minCol = React.useRef<HTMLDivElement | null>(null)\n  const secCol = React.useRef<HTMLDivElement | null>(null)\n\n  React.useEffect(() => {\n    if (!visible) return\n    const raf = requestAnimationFrame(() => {\n      hourCol.current?.querySelector('[data-active=\"true\"]')?.scrollIntoView({ block: 'center' })\n      minCol.current?.querySelector('[data-active=\"true\"]')?.scrollIntoView({ block: 'center' })\n      secCol.current?.querySelector('[data-active=\"true\"]')?.scrollIntoView({ block: 'center' })\n    })\n    return () => cancelAnimationFrame(raf)\n  }, [visible, value])\n\n  function isHourActive(h: number) {\n    if (!parts) return false\n    return effective12Hour ? hour12 === h : parts.hour24 === h\n  }\n\n  function isHourDisabled(h: number) {\n    if (!hideDisabledOptions) return isHourDisabledItem(h)\n    return false\n  }\n\n  function isMinuteDisabled(m: number) {\n    if (!hideDisabledOptions) return isMinuteDisabledItem(m)\n    return false\n  }\n\n  function isSecondDisabled(s: number) {\n    if (!hideDisabledOptions) return isSecondDisabledItem(s)\n    return false\n  }\n\n  return (\n    <div className=\"flex divide-x\" data-uipkge=\"\" data-slot=\"time-columns\">\n      <ScrollArea ref={hourCol} className=\"h-56\">\n        <div className=\"flex w-14 flex-col p-1\">\n          {(effective12Hour ? hours12List : hours24List).map((h) => (\n            <button\n              key={h}\n              type=\"button\"\n              data-active={isHourActive(h)}\n              disabled={isHourDisabled(h)}\n              className={cn(\n                'hover:bg-accent focus-visible:ring-ring rounded px-2 py-1 text-center text-sm tabular-nums transition-colors focus-visible:ring-2 focus-visible:outline-none disabled:opacity-30 disabled:hover:bg-transparent',\n                isHourActive(h) ? 'bg-primary text-primary-foreground hover:bg-primary' : '',\n              )}\n              onClick={() => (effective12Hour ? pickHour12(h) : pickHour24(h))}\n            >\n              {String(h).padStart(2, '0')}\n            </button>\n          ))}\n        </div>\n      </ScrollArea>\n\n      <ScrollArea ref={minCol} className=\"h-56\">\n        <div className=\"flex w-14 flex-col p-1\">\n          {minutesList.map((m) => (\n            <button\n              key={m}\n              type=\"button\"\n              data-active={parts?.minute === m}\n              disabled={isMinuteDisabled(m)}\n              className={cn(\n                'hover:bg-accent focus-visible:ring-ring rounded px-2 py-1 text-center text-sm tabular-nums transition-colors focus-visible:ring-2 focus-visible:outline-none disabled:opacity-30 disabled:hover:bg-transparent',\n                parts?.minute === m ? 'bg-primary text-primary-foreground hover:bg-primary' : '',\n              )}\n              onClick={() => pickMinute(m)}\n            >\n              {String(m).padStart(2, '0')}\n            </button>\n          ))}\n        </div>\n      </ScrollArea>\n\n      {showSeconds && (\n        <ScrollArea ref={secCol} className=\"h-56\">\n          <div className=\"flex w-14 flex-col p-1\">\n            {secondsList.map((s) => (\n              <button\n                key={s}\n                type=\"button\"\n                data-active={parts?.second === s}\n                disabled={isSecondDisabled(s)}\n                className={cn(\n                  'hover:bg-accent focus-visible:ring-ring rounded px-2 py-1 text-center text-sm tabular-nums transition-colors focus-visible:ring-2 focus-visible:outline-none disabled:opacity-30 disabled:hover:bg-transparent',\n                  parts?.second === s ? 'bg-primary text-primary-foreground hover:bg-primary' : '',\n                )}\n                onClick={() => pickSecond(s)}\n              >\n                {String(s).padStart(2, '0')}\n              </button>\n            ))}\n          </div>\n        </ScrollArea>\n      )}\n\n      {effective12Hour && (\n        <div className=\"flex w-12 flex-col p-1\">\n          {(['AM', 'PM'] as const).map((p) => (\n            <button\n              key={p}\n              type=\"button\"\n              className={cn(\n                'hover:bg-accent focus-visible:ring-ring rounded px-2 py-1 text-center text-sm transition-colors focus-visible:ring-2 focus-visible:outline-none',\n                period === p ? 'bg-primary text-primary-foreground hover:bg-primary' : '',\n              )}\n              onClick={() => pickPeriod(p)}\n            >\n              {p}\n            </button>\n          ))}\n        </div>\n      )}\n    </div>\n  )\n}\n\n// ---- TimePicker ----\n\nexport interface TimePickerProps {\n  /** Controlled value as 24h HH:mm or HH:mm:ss. Empty string = no selection. */\n  value?: string\n  /** Uncontrolled initial value. */\n  defaultValue?: string\n  onValueChange?: (value: string) => void\n  placeholder?: string\n  disabled?: boolean\n  readOnly?: boolean\n  clearable?: boolean\n  allowClear?: boolean\n  minuteStep?: number\n  hourStep?: number\n  secondStep?: number\n  /** Backward-compat: when true, render 24-hour selector. */\n  use24Hour?: boolean\n  /** When true, show AM/PM selector. */\n  use12Hours?: boolean\n  minTime?: string\n  maxTime?: string\n  format?: TimeFormat\n  disabledHours?: () => number[]\n  disabledMinutes?: (selectedHour: number) => number[]\n  disabledSeconds?: (selectedHour: number, selectedMinute: number) => number[]\n  hideDisabledOptions?: boolean\n  presets?: TimePreset[]\n  size?: TimePickerSize\n  status?: TimePickerStatus\n  triggerClassName?: string\n  className?: string\n}\n\nconst sizeClasses: Record<TimePickerSize, string> = {\n  small: 'h-8 text-xs px-2.5 py-1',\n  middle: 'h-9 text-sm px-3 py-1.5',\n  large: 'h-11 text-base px-4 py-2',\n}\n\nconst statusClasses: Record<string, string> = {\n  error: 'border-destructive focus-visible:ring-destructive',\n  warning: 'border-warning focus-visible:ring-warning',\n}\n\nconst TimePicker = React.forwardRef<HTMLButtonElement, TimePickerProps>(\n  (\n    {\n      value,\n      defaultValue = '',\n      onValueChange,\n      placeholder = 'Pick a time',\n      disabled = false,\n      readOnly = false,\n      clearable = true,\n      allowClear,\n      minuteStep = 5,\n      hourStep = 1,\n      secondStep = 1,\n      use24Hour = false,\n      use12Hours = false,\n      minTime,\n      maxTime,\n      format = 'HH:mm',\n      disabledHours,\n      disabledMinutes,\n      disabledSeconds,\n      hideDisabledOptions = false,\n      presets = [],\n      size = 'middle',\n      status,\n      triggerClassName,\n      className,\n    },\n    ref,\n  ) => {\n    const [open, setOpen] = React.useState(false)\n\n    const isControlled = value !== undefined\n    const [internal, setInternal] = React.useState<string>(defaultValue)\n    const currentValue = isControlled ? value : internal\n\n    const effectiveAllowClear = allowClear !== undefined ? allowClear : clearable\n\n    const parsed = parse(currentValue)\n\n    function formatDisplay(h: number, m: number, s: number) {\n      if (format === 'hh:mm A') {\n        const h12 = h % 12 === 0 ? 12 : h % 12\n        const p = h >= 12 ? 'PM' : 'AM'\n        return `${String(h12).padStart(2, '0')}:${String(m).padStart(2, '0')} ${p}`\n      }\n      if (format === 'HH:mm:ss') {\n        return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`\n      }\n      return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`\n    }\n\n    const display = parsed ? formatDisplay(parsed.hour24, parsed.minute, parsed.second) : ''\n\n    function emitTime(v: string) {\n      if (!isControlled) setInternal(v)\n      onValueChange?.(v)\n    }\n\n    function pickNow() {\n      if (readOnly) return\n      const d = new Date()\n      const sStep = Math.max(1, secondStep)\n      const rawS = d.getSeconds()\n      const snappedS = Math.round(rawS / sStep) * sStep\n      const secCarry = Math.floor(snappedS / 60)\n      const s = snappedS % 60\n\n      const mStep = Math.max(1, minuteStep)\n      const rawM = d.getMinutes() + secCarry\n      const snappedM = Math.round(rawM / mStep) * mStep\n      const minCarry = Math.floor(snappedM / 60)\n      const min = snappedM % 60\n\n      const h = d.getHours() + minCarry\n      const hourStr = String(h).padStart(2, '0')\n      const minStr = String(min).padStart(2, '0')\n      const secStr = String(s).padStart(2, '0')\n      emitTime(format === 'HH:mm:ss' ? `${hourStr}:${minStr}:${secStr}` : `${hourStr}:${minStr}`)\n    }\n\n    function applyPreset(preset: TimePreset) {\n      if (readOnly) return\n      emitTime(preset.value)\n      setOpen(false)\n    }\n\n    function clear(event: React.MouseEvent) {\n      event.stopPropagation()\n      if (disabled || readOnly) return\n      emitTime('')\n    }\n\n    const triggerClasses = cn(\n      'min-w-[160px] justify-start gap-2 text-left font-normal',\n      !parsed && 'text-muted-foreground',\n      sizeClasses[size],\n      status && statusClasses[status],\n      triggerClassName,\n      className,\n    )\n\n    return (\n      <Popover open={open} onOpenChange={setOpen}>\n        <PopoverTrigger asChild>\n          <Button\n            ref={ref}\n            type=\"button\"\n            variant=\"outline\"\n            disabled={disabled}\n            className={triggerClasses}\n            data-uipkge=\"\"\n            data-slot=\"time-picker\"\n          >\n            <Clock className=\"size-4 shrink-0\" />\n            <span className=\"flex-1 truncate\">{display || placeholder}</span>\n            {effectiveAllowClear && parsed && !disabled && !readOnly && (\n              <button\n                type=\"button\"\n                className=\"text-muted-foreground hover:text-foreground focus-visible:ring-ring -mr-1 inline-flex size-5 items-center justify-center rounded transition-colors focus-visible:ring-2 focus-visible:outline-none\"\n                aria-label=\"Clear time\"\n                onClick={clear}\n              >\n                <X className=\"size-3.5\" aria-hidden=\"true\" />\n              </button>\n            )}\n          </Button>\n        </PopoverTrigger>\n        <PopoverContent className=\"w-auto p-0\" align=\"start\">\n          {presets.length > 0 && (\n            <div className=\"flex flex-col gap-0.5 border-b p-2\">\n              {presets.map((p) => (\n                <button\n                  key={p.label}\n                  type=\"button\"\n                  className=\"hover:bg-accent focus-visible:ring-ring rounded-md px-2 py-1.5 text-left text-xs transition-colors focus-visible:ring-2 focus-visible:outline-none\"\n                  onClick={() => applyPreset(p)}\n                >\n                  {p.label}\n                </button>\n              ))}\n            </div>\n          )}\n          <div className=\"flex items-center justify-between border-b px-3 py-2\">\n            <span className=\"text-muted-foreground text-xs tracking-widest uppercase\">Time</span>\n            <button\n              type=\"button\"\n              className=\"text-muted-foreground hover:text-foreground focus-visible:ring-ring text-xs focus-visible:ring-2 focus-visible:outline-none\"\n              onClick={pickNow}\n            >\n              Now\n            </button>\n          </div>\n          <TimeColumns\n            value={currentValue}\n            use24Hour={use24Hour}\n            use12Hours={use12Hours}\n            minuteStep={minuteStep}\n            hourStep={hourStep}\n            secondStep={secondStep}\n            minTime={minTime}\n            maxTime={maxTime}\n            format={format}\n            disabledHours={disabledHours}\n            disabledMinutes={disabledMinutes}\n            disabledSeconds={disabledSeconds}\n            hideDisabledOptions={hideDisabledOptions}\n            visible={open}\n            onValueChange={emitTime}\n          />\n        </PopoverContent>\n      </Popover>\n    )\n  },\n)\nTimePicker.displayName = 'TimePicker'\n\n// ---- TimeRangePicker ----\n\nexport interface TimeRangePreset {\n  label: string\n  value: [string, string]\n}\n\nexport interface TimeRangePickerProps {\n  value?: [string, string] | null\n  onValueChange?: (value: [string, string] | null) => void\n  placeholder?: string\n  disabled?: boolean\n  readOnly?: boolean\n  clearable?: boolean\n  allowClear?: boolean\n  minuteStep?: number\n  hourStep?: number\n  secondStep?: number\n  use24Hour?: boolean\n  use12Hours?: boolean\n  minTime?: string\n  maxTime?: string\n  format?: TimeFormat\n  disabledHours?: () => number[]\n  disabledMinutes?: (selectedHour: number) => number[]\n  disabledSeconds?: (selectedHour: number, selectedMinute: number) => number[]\n  hideDisabledOptions?: boolean\n  presets?: TimeRangePreset[]\n  size?: TimePickerSize\n  status?: TimePickerStatus\n  triggerClassName?: string\n  className?: string\n}\n\nconst TimeRangePicker = React.forwardRef<HTMLButtonElement, TimeRangePickerProps>(\n  (\n    {\n      value: valueProp,\n      onValueChange,\n      placeholder = 'Pick a time range',\n      disabled = false,\n      readOnly = false,\n      clearable = true,\n      allowClear,\n      minuteStep = 5,\n      hourStep = 1,\n      secondStep = 1,\n      use24Hour = false,\n      use12Hours = false,\n      minTime,\n      maxTime,\n      format = 'HH:mm',\n      disabledHours,\n      disabledMinutes,\n      disabledSeconds,\n      hideDisabledOptions = false,\n      presets = [],\n      size = 'middle',\n      status,\n      triggerClassName,\n      className,\n    },\n    ref,\n  ) => {\n    const [open, setOpen] = React.useState(false)\n    const isControlled = valueProp !== undefined\n    const [internal, setInternal] = React.useState<[string, string] | null>(null)\n    const currentValue = isControlled ? valueProp : internal\n\n    const effectiveAllowClear = allowClear !== undefined ? allowClear : clearable\n\n    const startValue = currentValue?.[0] ?? ''\n    const endValue = currentValue?.[1] ?? ''\n    const startParsed = parse(startValue)\n    const endParsed = parse(endValue)\n\n    function formatDisplay(h: number, m: number, s: number) {\n      if (format === 'hh:mm A') {\n        const h12 = h % 12 === 0 ? 12 : h % 12\n        const p = h >= 12 ? 'PM' : 'AM'\n        return `${String(h12).padStart(2, '0')}:${String(m).padStart(2, '0')} ${p}`\n      }\n      if (format === 'HH:mm:ss') {\n        return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`\n      }\n      return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`\n    }\n\n    const display = (() => {\n      const hasStart = startParsed !== null\n      const hasEnd = endParsed !== null\n      if (!hasStart && !hasEnd) return ''\n      const startStr = hasStart\n        ? formatDisplay(startParsed.hour24, startParsed.minute, startParsed.second)\n        : ''\n      const endStr = hasEnd ? formatDisplay(endParsed.hour24, endParsed.minute, endParsed.second) : ''\n      if (hasStart && hasEnd) return `${startStr} ~ ${endStr}`\n      return startStr || endStr\n    })()\n\n    function emitRange(start: string, end: string) {\n      const next: [string, string] = [start, end]\n      if (!isControlled) setInternal(next)\n      onValueChange?.(next)\n    }\n\n    function emitStart(v: string) {\n      emitRange(v, endValue || v)\n    }\n\n    function emitEnd(v: string) {\n      emitRange(startValue || v, v)\n    }\n\n    function pickNow(which: 'start' | 'end') {\n      if (readOnly) return\n      const d = new Date()\n      const sStep = Math.max(1, secondStep)\n      const rawS = d.getSeconds()\n      const snappedS = Math.round(rawS / sStep) * sStep\n      const secCarry = Math.floor(snappedS / 60)\n      const s = snappedS % 60\n\n      const mStep = Math.max(1, minuteStep)\n      const rawM = d.getMinutes() + secCarry\n      const snappedM = Math.round(rawM / mStep) * mStep\n      const minCarry = Math.floor(snappedM / 60)\n      const min = snappedM % 60\n\n      const h = d.getHours() + minCarry\n      const hourStr = String(h).padStart(2, '0')\n      const minStr = String(min).padStart(2, '0')\n      const secStr = String(s).padStart(2, '0')\n      const v = format === 'HH:mm:ss' ? `${hourStr}:${minStr}:${secStr}` : `${hourStr}:${minStr}`\n      if (which === 'start') emitStart(v)\n      else emitEnd(v)\n    }\n\n    function applyPreset(preset: TimeRangePreset) {\n      if (readOnly) return\n      if (!isControlled) setInternal(preset.value)\n      onValueChange?.(preset.value)\n      setOpen(false)\n    }\n\n    function clear(event: React.MouseEvent) {\n      event.stopPropagation()\n      if (disabled || readOnly) return\n      if (!isControlled) setInternal(null)\n      onValueChange?.(null)\n    }\n\n    const triggerClasses = cn(\n      'min-w-[200px] justify-start gap-2 text-left font-normal',\n      !display && 'text-muted-foreground',\n      sizeClasses[size],\n      status && statusClasses[status],\n      triggerClassName,\n      className,\n    )\n\n    return (\n      <Popover open={open} onOpenChange={setOpen}>\n        <PopoverTrigger asChild>\n          <Button\n            ref={ref}\n            type=\"button\"\n            variant=\"outline\"\n            disabled={disabled}\n            className={triggerClasses}\n            data-uipkge=\"\"\n            data-slot=\"time-range-picker\"\n          >\n            <Clock className=\"size-4 shrink-0\" />\n            <span className=\"flex-1 truncate\">{display || placeholder}</span>\n            {effectiveAllowClear && display && !disabled && !readOnly && (\n              <button\n                type=\"button\"\n                className=\"text-muted-foreground hover:text-foreground focus-visible:ring-ring -mr-1 inline-flex size-5 items-center justify-center rounded transition-colors focus-visible:ring-2 focus-visible:outline-none\"\n                aria-label=\"Clear time range\"\n                onClick={clear}\n              >\n                <X className=\"size-3.5\" aria-hidden=\"true\" />\n              </button>\n            )}\n          </Button>\n        </PopoverTrigger>\n        <PopoverContent className=\"w-auto p-0\" align=\"start\">\n          {presets.length > 0 && (\n            <div className=\"flex flex-col gap-0.5 border-b p-2\">\n              {presets.map((p) => (\n                <button\n                  key={p.label}\n                  type=\"button\"\n                  className=\"hover:bg-accent focus-visible:ring-ring rounded-md px-2 py-1.5 text-left text-xs transition-colors focus-visible:ring-2 focus-visible:outline-none\"\n                  onClick={() => applyPreset(p)}\n                >\n                  {p.label}\n                </button>\n              ))}\n            </div>\n          )}\n          <div className=\"flex items-center justify-between border-b px-3 py-2\">\n            <span className=\"text-muted-foreground text-xs tracking-widest uppercase\">Time Range</span>\n            <div className=\"flex gap-2\">\n              <button\n                type=\"button\"\n                className=\"text-muted-foreground hover:text-foreground focus-visible:ring-ring text-xs focus-visible:ring-2 focus-visible:outline-none\"\n                onClick={() => pickNow('start')}\n              >\n                Now (Start)\n              </button>\n              <button\n                type=\"button\"\n                className=\"text-muted-foreground hover:text-foreground text-xs\"\n                onClick={() => pickNow('end')}\n              >\n                Now (End)\n              </button>\n            </div>\n          </div>\n          <div className=\"flex\">\n            <div className=\"flex flex-col\">\n              <div className=\"text-muted-foreground border-b px-3 py-1.5 text-center text-xs font-medium\">Start</div>\n              <TimeColumns\n                value={startValue}\n                use24Hour={use24Hour}\n                use12Hours={use12Hours}\n                minuteStep={minuteStep}\n                hourStep={hourStep}\n                secondStep={secondStep}\n                minTime={minTime}\n                maxTime={maxTime}\n                format={format}\n                disabledHours={disabledHours}\n                disabledMinutes={disabledMinutes}\n                disabledSeconds={disabledSeconds}\n                hideDisabledOptions={hideDisabledOptions}\n                visible={open}\n                onValueChange={emitStart}\n              />\n            </div>\n            <div className=\"bg-border w-px\" />\n            <div className=\"flex flex-col\">\n              <div className=\"text-muted-foreground border-b px-3 py-1.5 text-center text-xs font-medium\">End</div>\n              <TimeColumns\n                value={endValue}\n                use24Hour={use24Hour}\n                use12Hours={use12Hours}\n                minuteStep={minuteStep}\n                hourStep={hourStep}\n                secondStep={secondStep}\n                minTime={minTime}\n                maxTime={maxTime}\n                format={format}\n                disabledHours={disabledHours}\n                disabledMinutes={disabledMinutes}\n                disabledSeconds={disabledSeconds}\n                hideDisabledOptions={hideDisabledOptions}\n                visible={open}\n                onValueChange={emitEnd}\n              />\n            </div>\n          </div>\n        </PopoverContent>\n      </Popover>\n    )\n  },\n)\nTimeRangePicker.displayName = 'TimeRangePicker'\n\nexport { TimePicker, TimeColumns, TimeRangePicker }\n",
      "type": "registry:ui",
      "target": "~/components/ui/time-picker/time-picker.tsx"
    },
    {
      "path": "packages/registry-react/components/time-picker/index.ts",
      "content": "export {\n  TimePicker,\n  TimeColumns,\n  TimeRangePicker,\n  type TimePickerProps,\n  type TimeColumnsProps,\n  type TimeRangePickerProps,\n  type TimeRangePreset,\n  type TimePreset,\n  type TimeParts,\n  type TimeFormat,\n  type TimePickerSize,\n  type TimePickerStatus,\n} from './time-picker'\n",
      "type": "registry:ui",
      "target": "~/components/ui/time-picker/index.ts"
    }
  ],
  "dependencies": [
    "lucide-react"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/react/button.json",
    "https://uipkge.dev/r/react/popover.json",
    "https://uipkge.dev/r/react/scroll-area.json"
  ],
  "description": "Standalone time input — hours, minutes, optional seconds, and 12h/24h modes. Pairs with Date Picker for full datetime entry.",
  "categories": [
    "date-time"
  ]
}