{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "date-picker-time",
  "title": "Picker-time date",
  "description": "Styled date: the Picker-time set. 5 decorative date variations (TimeDatePicker, DateTimeDatePicker, ClockDatePicker, SlotsDatePicker, DurationDatePicker) on the ai2 token system, powered by framer-motion, reduced-motion aware and sized sm to xl. Part of the free styled layer.",
  "dependencies": [
    "motion@^12.42.2",
    "lucide-react@^1.23.0"
  ],
  "registryDependencies": [
    "@ai2/tokens"
  ],
  "files": [
    {
      "path": "registry/ai2/styled/date-picker-time.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { CalendarClock, ChevronLeft, ChevronRight, Clock } from \"lucide-react\"\nimport { AnimatePresence, motion, useReducedMotion, type Transition } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Time date picker family: 5 self-contained date + time pickers. The difference is\n   the time layer beside the calendar: an hour list, separate hour/minute columns, an\n   hour grid, appointment slots, duration selection. NO radix, NO date library, NO\n   portal, NO Intl. Each export is a complete picker: a relative wrapper + a trigger\n   (aria-haspopup=\"dialog\", aria-expanded) + a panel absolutely positioned BELOW the\n   trigger (a role=\"grid\" month grid + a time column). Picking a day updates the\n   value and closes the panel; picking a time does not close it (day first, then time).\n   It closes on an outside click or Escape (focus returns to the trigger).\n\n   CRITICAL: all dates and times derive from FIXED values (FIXED_MONTH = 2026-01, a\n   default time of 09:00) - Date.now/new Date() are NEVER called. Reading the real\n   clock breaks prerender/hydration determinism.\n\n   State trap: the selection and time state is held OUTSIDE THE POPOVER TREE - the\n   panel unmounts on close, and the selection lives on. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\nconst triggerHeight: Record<StyledSize, string> = {\n  sm: \"h-8 text-sm\",\n  md: \"h-9 text-sm\",\n  lg: \"h-10 text-base\",\n  xl: \"h-12 text-base\",\n}\n\n/* SSR-guvenli sabitler. today ASLA kullanilmaz. */\nconst FIXED_MONTH = new Date(2026, 0, 1)\nconst DEFAULT_HOUR = 9\nconst DEFAULT_MINUTE = 0\n\nconst MONTH_NAMES = [\n  \"January\",\n  \"February\",\n  \"March\",\n  \"April\",\n  \"May\",\n  \"June\",\n  \"July\",\n  \"August\",\n  \"September\",\n  \"October\",\n  \"November\",\n  \"December\",\n]\n\nconst WEEKDAY_LABELS = [\"Su\", \"Mo\", \"Tu\", \"We\", \"Th\", \"Fr\", \"Sa\"]\n\nfunction pad2(n: number): string {\n  return n < 10 ? `0${n}` : `${n}`\n}\n\nfunction formatDate(d: Date): string {\n  return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`\n}\n\nfunction formatLong(d: Date): string {\n  return `${MONTH_NAMES[d.getMonth()]} ${d.getDate()}, ${d.getFullYear()}`\n}\n\nfunction formatTime(h: number, m: number): string {\n  return `${pad2(h)}:${pad2(m)}`\n}\n\nfunction startOfMonth(d: Date): Date {\n  return new Date(d.getFullYear(), d.getMonth(), 1)\n}\n\nfunction daysInMonth(year: number, month: number): number {\n  return new Date(year, month + 1, 0).getDate()\n}\n\nfunction sameDay(a: Date | undefined, b: Date | undefined): boolean {\n  return (\n    !!a &&\n    !!b &&\n    a.getFullYear() === b.getFullYear() &&\n    a.getMonth() === b.getMonth() &&\n    a.getDate() === b.getDate()\n  )\n}\n\nfunction useDismiss(\n  open: boolean,\n  onClose: (refocus: boolean) => void,\n  ref: React.RefObject<HTMLDivElement | null>\n) {\n  React.useEffect(() => {\n    if (!open) return\n    const onKey = (e: KeyboardEvent) => {\n      if (e.key === \"Escape\") onClose(true)\n    }\n    const onPointer = (e: PointerEvent) => {\n      const node = ref.current\n      if (node && e.target instanceof Node && !node.contains(e.target)) {\n        onClose(false)\n      }\n    }\n    window.addEventListener(\"keydown\", onKey)\n    window.addEventListener(\"pointerdown\", onPointer)\n    return () => {\n      window.removeEventListener(\"keydown\", onKey)\n      window.removeEventListener(\"pointerdown\", onPointer)\n    }\n  }, [open, onClose, ref])\n}\n\nconst springTransition: Transition = { type: \"spring\" as const, stiffness: 340, damping: 26 }\n\nconst panelMotion = {\n  initial: { opacity: 0, scale: 0.96, y: -6 },\n  animate: { opacity: 1, scale: 1, y: 0 },\n  exit: { opacity: 0, scale: 0.96, y: -6 },\n}\n\nconst triggerBase =\n  \"inline-flex w-full select-none items-center gap-2 whitespace-nowrap rounded-lg px-3 font-medium outline-none transition-colors focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0 [&_i]:text-base [&_i]:leading-none\"\n\nconst navBtn =\n  \"inline-flex size-7 items-center justify-center rounded-md text-muted-foreground outline-none transition-colors hover:bg-[color-mix(in_oklab,var(--color-foreground)_6%,transparent)] hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 [&_svg]:size-4 [&_svg]:shrink-0 [&_i]:text-base [&_i]:leading-none\"\n\nconst dayBase =\n  \"relative inline-flex size-8 items-center justify-center rounded-md text-sm text-foreground outline-none transition-colors hover:bg-[color-mix(in_oklab,var(--color-foreground)_6%,transparent)] focus-visible:ring-[3px] focus-visible:ring-ring/50\"\n\nconst daySelected =\n  \"bg-primary text-primary-foreground hover:bg-[color-mix(in_oklab,var(--color-primary)_90%,transparent)]\"\n\nconst timeBtnBase =\n  \"w-full shrink-0 cursor-default select-none rounded-md px-2 py-1 text-center text-xs font-medium text-muted-foreground outline-none transition-colors hover:bg-[color-mix(in_oklab,var(--color-foreground)_6%,transparent)] hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50\"\n\nconst timeBtnActive =\n  \"bg-primary text-primary-foreground hover:bg-[color-mix(in_oklab,var(--color-primary)_90%,transparent)] hover:text-primary-foreground\"\n\nconst columnBase =\n  \"flex max-h-56 flex-col gap-0.5 overflow-y-auto scroll-smooth pr-1\"\n\nfunction CalendarBody({\n  month,\n  onMonthChange,\n  selected,\n  onPick,\n  labelId,\n}: {\n  month: Date\n  onMonthChange: (d: Date) => void\n  selected: Date | undefined\n  onPick: (d: Date) => void\n  labelId: string\n}) {\n  const year = month.getFullYear()\n  const m = month.getMonth()\n  const leading = new Date(year, m, 1).getDay()\n  const total = daysInMonth(year, m)\n\n  const cells: (Date | null)[] = []\n  for (let i = 0; i < leading; i++) cells.push(null)\n  for (let d = 1; d <= total; d++) cells.push(new Date(year, m, d))\n\n  return (\n    <div data-slot=\"styled-date-picker-calendar\" className=\"flex flex-col gap-2\">\n      <div className=\"flex items-center justify-between px-1\">\n        <button\n          type=\"button\"\n          aria-label=\"Previous month\"\n          className={navBtn}\n          onClick={() => onMonthChange(new Date(year, m - 1, 1))}\n        >\n          <ChevronLeft />\n        </button>\n        <div id={labelId} className=\"text-sm font-semibold text-foreground\">\n          {MONTH_NAMES[m]} {year}\n        </div>\n        <button\n          type=\"button\"\n          aria-label=\"Next month\"\n          className={navBtn}\n          onClick={() => onMonthChange(new Date(year, m + 1, 1))}\n        >\n          <ChevronRight />\n        </button>\n      </div>\n\n      <div className=\"grid grid-cols-7 gap-0.5\">\n        {WEEKDAY_LABELS.map((w) => (\n          <div\n            key={w}\n            className=\"flex size-8 items-center justify-center text-xs font-medium text-muted-foreground\"\n          >\n            {w}\n          </div>\n        ))}\n      </div>\n\n      <div role=\"grid\" aria-labelledby={labelId} className=\"grid grid-cols-7 gap-0.5\">\n        {cells.map((day, i) =>\n          day === null ? (\n            <span key={`empty-${i}`} className=\"size-8\" aria-hidden=\"true\" />\n          ) : (\n            <button\n              key={formatDate(day)}\n              type=\"button\"\n              role=\"gridcell\"\n              aria-label={formatLong(day)}\n              aria-selected={sameDay(day, selected)}\n              className={cn(dayBase, sameDay(day, selected) && daySelected)}\n              onClick={() => onPick(day)}\n            >\n              {day.getDate()}\n            </button>\n          )\n        )}\n      </div>\n    </div>\n  )\n}\n\nexport interface DatePickerProps {\n  className?: string\n  size?: StyledSize\n  placeholder?: string\n  value?: Date\n  defaultValue?: Date\n  onChange?: (d: Date) => void\n}\n\n/* Shared body. `timeSlot` draws the panel's hour layer; the selection state lives OUTSIDE the popover. A day click closes the panel, an hour click does not. */\nfunction TimePickerShell({\n  className,\n  size = \"md\",\n  placeholder = \"Pick a date and time\",\n  value,\n  defaultValue,\n  onChange,\n  panelWidth = \"w-[22rem]\",\n  renderTime,\n  timeLabel,\n  triggerIcon,\n}: DatePickerProps & {\n  panelWidth?: string\n  renderTime: (ctx: {\n    hour: number\n    minute: number\n    setHour: (h: number) => void\n    setMinute: (m: number) => void\n  }) => React.ReactNode\n  timeLabel: (hour: number, minute: number) => string\n  triggerIcon?: React.ReactNode\n}) {\n  const isControlled = value !== undefined\n  const [internal, setInternal] = React.useState<Date | undefined>(defaultValue)\n  const selected = isControlled ? value : internal\n\n  const [open, setOpen] = React.useState(false)\n  const [month, setMonth] = React.useState(() => startOfMonth(selected ?? FIXED_MONTH))\n  const [hour, setHour] = React.useState(DEFAULT_HOUR)\n  const [minute, setMinute] = React.useState(DEFAULT_MINUTE)\n\n  const reduce = useReducedMotion()\n  const wrapperRef = React.useRef<HTMLDivElement>(null)\n  const triggerRef = React.useRef<HTMLButtonElement>(null)\n  const panelId = React.useId()\n  const labelId = React.useId()\n\n  const close = React.useCallback((refocus: boolean) => {\n    setOpen(false)\n    if (refocus) triggerRef.current?.focus()\n  }, [])\n\n  useDismiss(open, close, wrapperRef)\n\n  const pick = (day: Date) => {\n    const next = new Date(day.getFullYear(), day.getMonth(), day.getDate(), hour, minute)\n    if (!isControlled) setInternal(next)\n    onChange?.(next)\n    setMonth(startOfMonth(next))\n    setOpen(false)\n    triggerRef.current?.focus()\n  }\n\n  const fade = { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 } }\n  const mp = reduce ? fade : panelMotion\n\n  return (\n    <div\n      ref={wrapperRef}\n      data-slot=\"styled-date-picker\"\n      className={cn(\"relative inline-flex w-64 flex-col\", className)}\n    >\n      <button\n        ref={triggerRef}\n        type=\"button\"\n        aria-haspopup=\"dialog\"\n        aria-expanded={open}\n        aria-controls={open ? panelId : undefined}\n        className={cn(\n          triggerBase,\n          triggerHeight[size],\n          \"justify-between border border-field-border bg-transparent text-foreground hover:bg-[color-mix(in_oklab,var(--color-foreground)_4%,transparent)]\"\n        )}\n        onClick={() => setOpen(!open)}\n      >\n        <span className={cn(!selected && \"text-muted-foreground\")}>\n          {selected ? `${formatDate(selected)} ${timeLabel(hour, minute)}` : placeholder}\n        </span>\n        {triggerIcon ?? <CalendarClock className=\"text-muted-foreground\" />}\n      </button>\n\n      <AnimatePresence>\n        {open ? (\n          <motion.div\n            id={panelId}\n            data-slot=\"styled-date-picker-popover\"\n            className={cn(\n              \"absolute left-0 top-full z-50 mt-2 flex origin-top gap-3 rounded-xl border border-border bg-popover p-3 text-popover-foreground shadow-lg outline-none\",\n              panelWidth\n            )}\n            initial={mp.initial}\n            animate={mp.animate}\n            exit={mp.exit}\n            transition={reduce ? ({ duration: 0.12 } as Transition) : springTransition}\n          >\n            <div className=\"min-w-0 flex-1\">\n              <CalendarBody\n                month={month}\n                onMonthChange={setMonth}\n                selected={selected}\n                onPick={pick}\n                labelId={labelId}\n              />\n            </div>\n            <div\n              data-slot=\"styled-date-picker-time\"\n              className=\"flex shrink-0 flex-col gap-1 border-l border-border pl-3\"\n            >\n              {renderTime({ hour, minute, setHour, setMinute })}\n            </div>\n          </motion.div>\n        ) : null}\n      </AnimatePresence>\n    </div>\n  )\n}\n\nconst HOURS = Array.from({ length: 24 }, (_, i) => i)\nconst HALF_HOURS: [number, number][] = HOURS.flatMap((h) => [\n  [h, 0] as [number, number],\n  [h, 30] as [number, number],\n])\nconst MINUTES = [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55]\nconst BUSINESS_SLOTS: [number, number][] = [\n  [9, 0],\n  [9, 30],\n  [10, 0],\n  [10, 30],\n  [11, 0],\n  [13, 0],\n  [13, 30],\n  [14, 0],\n  [15, 0],\n  [16, 30],\n]\nconst DURATIONS = [15, 30, 45, 60, 90, 120]\n\nfunction ColumnHeading({ children }: { children: React.ReactNode }) {\n  return (\n    <div className=\"px-1 pb-1 text-[0.6875rem] font-semibold uppercase tracking-wide text-muted-foreground\">\n      {children}\n    </div>\n  )\n}\n\n/* TimeDatePicker: tek sutunda yarim saatlik zaman listesi. */\nexport function TimeDatePicker(props: DatePickerProps) {\n  return (\n    <TimePickerShell\n      {...props}\n      timeLabel={(h, m) => formatTime(h, m)}\n      renderTime={({ hour, minute, setHour, setMinute }) => (\n        <>\n          <ColumnHeading>Time</ColumnHeading>\n          <div className={cn(columnBase, \"w-16\")}>\n            {HALF_HOURS.map(([h, m]) => (\n              <button\n                key={formatTime(h, m)}\n                type=\"button\"\n                aria-pressed={hour === h && minute === m}\n                aria-label={`Set time to ${formatTime(h, m)}`}\n                className={cn(timeBtnBase, hour === h && minute === m && timeBtnActive)}\n                onClick={() => {\n                  setHour(h)\n                  setMinute(m)\n                }}\n              >\n                {formatTime(h, m)}\n              </button>\n            ))}\n          </div>\n        </>\n      )}\n    />\n  )\n}\n\n/* DateTimeDatePicker: ayri saat ve dakika sutunlari. */\nexport function DateTimeDatePicker(props: DatePickerProps) {\n  return (\n    <TimePickerShell\n      {...props}\n      panelWidth=\"w-[24rem]\"\n      timeLabel={(h, m) => formatTime(h, m)}\n      renderTime={({ hour, minute, setHour, setMinute }) => (\n        <div className=\"flex gap-2\">\n          <div className=\"flex flex-col\">\n            <ColumnHeading>Hr</ColumnHeading>\n            <div className={cn(columnBase, \"w-11\")}>\n              {HOURS.map((h) => (\n                <button\n                  key={h}\n                  type=\"button\"\n                  aria-pressed={hour === h}\n                  aria-label={`Set hour to ${pad2(h)}`}\n                  className={cn(timeBtnBase, hour === h && timeBtnActive)}\n                  onClick={() => setHour(h)}\n                >\n                  {pad2(h)}\n                </button>\n              ))}\n            </div>\n          </div>\n          <div className=\"flex flex-col\">\n            <ColumnHeading>Min</ColumnHeading>\n            <div className={cn(columnBase, \"w-11\")}>\n              {MINUTES.map((m) => (\n                <button\n                  key={m}\n                  type=\"button\"\n                  aria-pressed={minute === m}\n                  aria-label={`Set minute to ${pad2(m)}`}\n                  className={cn(timeBtnBase, minute === m && timeBtnActive)}\n                  onClick={() => setMinute(m)}\n                >\n                  {pad2(m)}\n                </button>\n              ))}\n            </div>\n          </div>\n        </div>\n      )}\n    />\n  )\n}\n\n/* ClockDatePicker: 12'li saat izgarasi + saat ikonlu trigger. */\nexport function ClockDatePicker(props: DatePickerProps) {\n  return (\n    <TimePickerShell\n      {...props}\n      panelWidth=\"w-[24rem]\"\n      triggerIcon={<Clock className=\"text-muted-foreground\" />}\n      timeLabel={(h, m) => formatTime(h, m)}\n      renderTime={({ hour, minute, setHour, setMinute }) => (\n        <>\n          <ColumnHeading>Clock</ColumnHeading>\n          <div className=\"grid w-24 grid-cols-3 gap-0.5\">\n            {HOURS.map((h) => (\n              <button\n                key={h}\n                type=\"button\"\n                aria-pressed={hour === h}\n                aria-label={`Set hour to ${pad2(h)}`}\n                className={cn(timeBtnBase, \"px-0\", hour === h && timeBtnActive)}\n                onClick={() => setHour(h)}\n              >\n                {pad2(h)}\n              </button>\n            ))}\n          </div>\n          <ColumnHeading>Minute</ColumnHeading>\n          <div className=\"grid w-24 grid-cols-3 gap-0.5\">\n            {[0, 15, 30, 45].map((m) => (\n              <button\n                key={m}\n                type=\"button\"\n                aria-pressed={minute === m}\n                aria-label={`Set minute to ${pad2(m)}`}\n                className={cn(timeBtnBase, \"px-0\", minute === m && timeBtnActive)}\n                onClick={() => setMinute(m)}\n              >\n                {pad2(m)}\n              </button>\n            ))}\n          </div>\n        </>\n      )}\n    />\n  )\n}\n\n/* SlotsDatePicker: is saatlerine gore randevu slotlari. */\nexport function SlotsDatePicker(props: DatePickerProps) {\n  return (\n    <TimePickerShell\n      {...props}\n      placeholder={props.placeholder ?? \"Pick a slot\"}\n      timeLabel={(h, m) => formatTime(h, m)}\n      renderTime={({ hour, minute, setHour, setMinute }) => (\n        <>\n          <ColumnHeading>Slots</ColumnHeading>\n          <div className={cn(columnBase, \"w-16\")}>\n            {BUSINESS_SLOTS.map(([h, m]) => (\n              <button\n                key={formatTime(h, m)}\n                type=\"button\"\n                aria-pressed={hour === h && minute === m}\n                aria-label={`Book the ${formatTime(h, m)} slot`}\n                className={cn(timeBtnBase, hour === h && minute === m && timeBtnActive)}\n                onClick={() => {\n                  setHour(h)\n                  setMinute(m)\n                }}\n              >\n                {formatTime(h, m)}\n              </button>\n            ))}\n          </div>\n        </>\n      )}\n    />\n  )\n}\n\n/* DurationDatePicker: a start time + a duration; the trigger shows the start and the\n   end. */\nexport function DurationDatePicker(props: DatePickerProps) {\n  const [duration, setDuration] = React.useState(30)\n\n  const endLabel = React.useCallback(\n    (h: number, m: number) => {\n      const total = h * 60 + m + duration\n      return formatTime(Math.floor(total / 60) % 24, total % 60)\n    },\n    [duration]\n  )\n\n  return (\n    <TimePickerShell\n      {...props}\n      panelWidth=\"w-[24rem]\"\n      className={cn(\"w-72\", props.className)}\n      placeholder={props.placeholder ?? \"Pick a date and duration\"}\n      timeLabel={(h, m) => `${formatTime(h, m)} - ${endLabel(h, m)}`}\n      renderTime={({ hour, minute, setHour, setMinute }) => (\n        <div className=\"flex gap-2\">\n          <div className=\"flex flex-col\">\n            <ColumnHeading>Start</ColumnHeading>\n            <div className={cn(columnBase, \"w-16\")}>\n              {HALF_HOURS.map(([h, m]) => (\n                <button\n                  key={formatTime(h, m)}\n                  type=\"button\"\n                  aria-pressed={hour === h && minute === m}\n                  aria-label={`Start at ${formatTime(h, m)}`}\n                  className={cn(timeBtnBase, hour === h && minute === m && timeBtnActive)}\n                  onClick={() => {\n                    setHour(h)\n                    setMinute(m)\n                  }}\n                >\n                  {formatTime(h, m)}\n                </button>\n              ))}\n            </div>\n          </div>\n          <div className=\"flex flex-col\">\n            <ColumnHeading>Length</ColumnHeading>\n            <div className={cn(columnBase, \"w-14\")}>\n              {DURATIONS.map((d) => (\n                <button\n                  key={d}\n                  type=\"button\"\n                  aria-pressed={duration === d}\n                  aria-label={`Set duration to ${d} minutes`}\n                  className={cn(timeBtnBase, duration === d && timeBtnActive)}\n                  onClick={() => setDuration(d)}\n                >\n                  {d}m\n                </button>\n              ))}\n            </div>\n          </div>\n        </div>\n      )}\n    />\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/date-picker-time.tsx"
    }
  ],
  "categories": [
    "styled",
    "date"
  ],
  "type": "registry:component"
}