{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "calendar",
  "title": "Calendar",
  "author": "Poyraz Avsever",
  "description": "Poyraz Soft Glass calendar with semantic states and composable variants.",
  "dependencies": [
    "lucide-react@^0.574.0"
  ],
  "registryDependencies": [
    "@poyraz/poyraz-utils",
    "@poyraz/poyraz-theme",
    "@poyraz/button"
  ],
  "files": [
    {
      "path": "registry/poyraz/ui/phase7/molecules/calendar.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { ChevronLeft, ChevronRight } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\nimport { Button } from \"@/components/ui/atoms/button\";\n\n/* ================================================================== */\n/*  HELPERS                                                            */\n/* ================================================================== */\n\nconst DAYS = [\"Mo\", \"Tu\", \"We\", \"Th\", \"Fr\", \"Sa\", \"Su\"];\nconst MONTHS = [\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];\nconst MONTHS_SHORT = [\n  \"Jan\",\n  \"Feb\",\n  \"Mar\",\n  \"Apr\",\n  \"May\",\n  \"Jun\",\n  \"Jul\",\n  \"Aug\",\n  \"Sep\",\n  \"Oct\",\n  \"Nov\",\n  \"Dec\",\n];\n\nfunction getDaysInMonth(year: number, month: number) {\n  return new Date(year, month + 1, 0).getDate();\n}\n\nfunction getFirstDayOfWeek(year: number, month: number) {\n  const day = new Date(year, month, 1).getDay();\n  // Convert Sunday=0 to Monday-first (Mon=0 … Sun=6)\n  return day === 0 ? 6 : day - 1;\n}\n\nfunction isSameDay(a: Date, b: Date) {\n  return (\n    a.getFullYear() === b.getFullYear() &&\n    a.getMonth() === b.getMonth() &&\n    a.getDate() === b.getDate()\n  );\n}\n\nfunction isToday(date: Date) {\n  return isSameDay(date, new Date());\n}\n\ntype CalendarView = \"days\" | \"months\" | \"years\";\n\n/* ================================================================== */\n/*  CALENDAR                                                           */\n/* ================================================================== */\n\nexport interface DateRange {\n  from?: Date;\n  to?: Date;\n}\n\ninterface CalendarBaseProps {\n  /** Minimum selectable date */\n  minDate?: Date;\n  /** Maximum selectable date */\n  maxDate?: Date;\n  className?: string;\n  surface?: \"plain\" | \"solid\" | \"soft\" | \"glass\";\n  radius?: \"none\" | \"sm\" | \"md\" | \"lg\" | \"xl\";\n  size?: \"compact\" | \"default\" | \"spacious\";\n  initialMonth?: Date;\n  onMonthChange?: (month: Date) => void;\n}\n\nexport interface CalendarSingleProps extends CalendarBaseProps {\n  mode?: \"single\";\n  selected?: Date;\n  defaultSelected?: Date;\n  onSelect?: (date: Date | undefined) => void;\n}\n\nexport interface CalendarRangeProps extends CalendarBaseProps {\n  mode: \"range\";\n  selected?: DateRange;\n  defaultSelected?: DateRange;\n  onSelect?: (range: DateRange | undefined) => void;\n}\n\nexport type CalendarProps = CalendarSingleProps | CalendarRangeProps;\n\nfunction Calendar(props: CalendarProps) {\n  const {\n    minDate,\n    maxDate,\n    className,\n    initialMonth,\n    onMonthChange,\n    radius = \"lg\",\n    size = \"default\",\n    surface = \"plain\",\n  } = props;\n  const mode = props.mode ?? \"single\";\n  const isControlled = Object.prototype.hasOwnProperty.call(props, \"selected\");\n  const [internalSelection, setInternalSelection] = React.useState<Date | DateRange | undefined>(\n    props.defaultSelected,\n  );\n  const selection = isControlled ? props.selected : internalSelection;\n  const selectedDate = selection instanceof Date ? selection : selection?.from;\n  const selectedRange = mode === \"range\" && !(selection instanceof Date) ? selection : undefined;\n  const [viewYear, setViewYear] = React.useState(() =>\n    (initialMonth ?? selectedDate ?? new Date()).getFullYear(),\n  );\n  const [viewMonth, setViewMonth] = React.useState(() =>\n    (initialMonth ?? selectedDate ?? new Date()).getMonth(),\n  );\n  const [view, setView] = React.useState<CalendarView>(\"days\");\n  // Year decade range start for years grid\n  const [decadeStart, setDecadeStart] = React.useState(() => {\n    const y = (initialMonth ?? selectedDate ?? new Date()).getFullYear();\n    return y - (y % 12);\n  });\n\n  const daysInMonth = getDaysInMonth(viewYear, viewMonth);\n  const firstDay = getFirstDayOfWeek(viewYear, viewMonth);\n\n  const prevMonth = () => {\n    if (viewMonth === 0) {\n      setViewMonth(11);\n      setViewYear((y) => y - 1);\n    } else {\n      setViewMonth((m) => m - 1);\n    }\n  };\n\n  React.useEffect(() => {\n    onMonthChange?.(new Date(viewYear, viewMonth, 1));\n  }, [onMonthChange, viewMonth, viewYear]);\n\n  const emitSelection = (next: Date | DateRange | undefined) => {\n    if (!isControlled) setInternalSelection(next);\n    if (mode === \"range\") {\n      (props as CalendarRangeProps).onSelect?.(next as DateRange | undefined);\n    } else {\n      (props as CalendarSingleProps).onSelect?.(next as Date | undefined);\n    }\n  };\n\n  const selectDate = (date: Date) => {\n    if (mode === \"single\") {\n      emitSelection(date);\n      return;\n    }\n    const current = selectedRange;\n    if (!current?.from || current.to || date < current.from) {\n      emitSelection({ from: date, to: undefined });\n    } else {\n      emitSelection({ from: current.from, to: date });\n    }\n  };\n\n  const isInRange = (date: Date) =>\n    Boolean(\n      selectedRange?.from &&\n      selectedRange?.to &&\n      date > selectedRange.from &&\n      date < selectedRange.to,\n    );\n\n  const nextMonth = () => {\n    if (viewMonth === 11) {\n      setViewMonth(0);\n      setViewYear((y) => y + 1);\n    } else {\n      setViewMonth((m) => m + 1);\n    }\n  };\n\n  const isDisabled = (date: Date) => {\n    if (minDate) {\n      const min = new Date(minDate.getFullYear(), minDate.getMonth(), minDate.getDate());\n      if (date < min) return true;\n    }\n    if (maxDate) {\n      const max = new Date(\n        maxDate.getFullYear(),\n        maxDate.getMonth(),\n        maxDate.getDate(),\n        23,\n        59,\n        59,\n        999,\n      );\n      if (date > max) return true;\n    }\n    return false;\n  };\n\n  /* ── Days view ──────────────────────────────────────────────────── */\n\n  const renderDays = () => {\n    const cells: React.ReactNode[] = [];\n\n    // Empty cells before first day\n    for (let i = 0; i < firstDay; i++) {\n      cells.push(<div key={`empty-${i}`} />);\n    }\n\n    // Day cells\n    for (let day = 1; day <= daysInMonth; day++) {\n      const date = new Date(viewYear, viewMonth, day);\n      const rangeStart = Boolean(selectedRange?.from && isSameDay(date, selectedRange.from));\n      const rangeEnd = Boolean(selectedRange?.to && isSameDay(date, selectedRange.to));\n      const rangeMiddle = isInRange(date);\n      const sel =\n        mode === \"single\"\n          ? Boolean(selectedDate && isSameDay(date, selectedDate))\n          : rangeStart || rangeEnd;\n      const today = isToday(date);\n      const disabled = isDisabled(date);\n\n      cells.push(\n        <button\n          key={day}\n          type=\"button\"\n          disabled={disabled}\n          onClick={() => selectDate(date)}\n          aria-label={date.toLocaleDateString()}\n          aria-pressed={sel}\n          data-selected={sel ? \"\" : undefined}\n          data-today={today ? \"\" : undefined}\n          data-range-start={rangeStart ? \"\" : undefined}\n          data-range-middle={rangeMiddle ? \"\" : undefined}\n          data-range-end={rangeEnd ? \"\" : undefined}\n          className={cn(\n            \"text-sm font-medium cursor-pointer\",\n            size === \"compact\" && \"h-7 w-7\",\n            size === \"default\" && \"h-8 w-8\",\n            size === \"spacious\" && \"h-10 w-10\",\n            \"flex items-center justify-center\",\n            \"transition-[color,background-color,transform] duration-[var(--poyraz-motion-duration-fast)] ease-[var(--poyraz-motion-ease-out)] active:scale-[var(--poyraz-motion-scale-press-small)]\",\n            \"hover:bg-accent\",\n            \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n            today && !sel && !rangeMiddle && \"ring-1 ring-inset ring-primary/45 text-primary\",\n            rangeMiddle &&\n              \"rounded-none bg-primary-muted text-primary-muted-foreground hover:bg-primary-muted\",\n            rangeStart &&\n              selectedRange?.to &&\n              \"rounded-l-md rounded-r-none bg-primary text-primary-foreground\",\n            rangeStart && !selectedRange?.to && \"rounded-md bg-primary text-primary-foreground\",\n            rangeEnd && \"rounded-l-none rounded-r-md bg-primary text-primary-foreground\",\n            sel &&\n              mode === \"single\" &&\n              \"rounded-md bg-primary text-primary-foreground shadow-sm hover:bg-primary-hover\",\n            disabled && \"opacity-30 cursor-not-allowed hover:bg-transparent\",\n          )}\n        >\n          {day}\n        </button>,\n      );\n    }\n\n    return (\n      <>\n        {/* Header */}\n        <div className=\"flex items-center justify-between mb-3\">\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            className=\"h-7 w-7 active:scale-95\"\n            onClick={prevMonth}\n            aria-label=\"Previous month\"\n          >\n            <ChevronLeft className=\"h-4 w-4\" />\n          </Button>\n          <button\n            type=\"button\"\n            onClick={() => setView(\"months\")}\n            className={cn(\n              \"text-sm font-bold uppercase tracking-wide cursor-pointer\",\n              \"px-2 py-1 hover:bg-accent transition-[color,background-color,border-color] duration-[var(--poyraz-motion-duration-fast)] ease-[var(--poyraz-motion-ease-out)]\",\n              \"border-b border-transparent hover:border-border-strong\",\n            )}\n          >\n            {MONTHS[viewMonth]} {viewYear}\n          </button>\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            className=\"h-7 w-7 active:scale-95\"\n            onClick={nextMonth}\n            aria-label=\"Next month\"\n          >\n            <ChevronRight className=\"h-4 w-4\" />\n          </Button>\n        </div>\n\n        {/* Day labels */}\n        <div className=\"grid grid-cols-7 mb-1\">\n          {DAYS.map((d) => (\n            <div\n              key={d}\n              className={cn(\n                \"flex items-center justify-center text-[11px] font-bold uppercase tracking-wider text-placeholder\",\n                size === \"compact\" && \"h-7 w-7\",\n                size === \"default\" && \"h-8 w-8\",\n                size === \"spacious\" && \"h-10 w-10\",\n              )}\n            >\n              {d}\n            </div>\n          ))}\n        </div>\n\n        {/* Day grid */}\n        <div className=\"grid grid-cols-7 animate-poyraz-slide-in-from-bottom\">{cells}</div>\n      </>\n    );\n  };\n\n  /* ── Months view ────────────────────────────────────────────────── */\n\n  const renderMonths = () => {\n    const now = new Date();\n    return (\n      <>\n        {/* Header */}\n        <div className=\"flex items-center justify-between mb-3\">\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            className=\"h-7 w-7 active:scale-95\"\n            onClick={() => setViewYear((y) => y - 1)}\n            aria-label=\"Previous year\"\n          >\n            <ChevronLeft className=\"h-4 w-4\" />\n          </Button>\n          <button\n            type=\"button\"\n            onClick={() => {\n              setDecadeStart(viewYear - (viewYear % 12));\n              setView(\"years\");\n            }}\n            className={cn(\n              \"text-sm font-bold uppercase tracking-wide cursor-pointer\",\n              \"px-2 py-1 hover:bg-accent transition-[color,background-color,border-color] duration-[var(--poyraz-motion-duration-fast)] ease-[var(--poyraz-motion-ease-out)]\",\n              \"border-b border-transparent hover:border-border-strong\",\n            )}\n          >\n            {viewYear}\n          </button>\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            className=\"h-7 w-7 active:scale-95\"\n            onClick={() => setViewYear((y) => y + 1)}\n            aria-label=\"Next year\"\n          >\n            <ChevronRight className=\"h-4 w-4\" />\n          </Button>\n        </div>\n\n        {/* Month grid: 4×3 */}\n        <div className=\"grid grid-cols-3 gap-1 animate-poyraz-slide-in-from-bottom\">\n          {MONTHS_SHORT.map((m, i) => {\n            const isCurrent = i === now.getMonth() && viewYear === now.getFullYear();\n            const isSelected =\n              selectedDate &&\n              i === selectedDate.getMonth() &&\n              viewYear === selectedDate.getFullYear();\n            return (\n              <button\n                key={m}\n                type=\"button\"\n                onClick={() => {\n                  setViewMonth(i);\n                  setView(\"days\");\n                }}\n                className={cn(\n                  \"h-8 text-sm font-medium cursor-pointer\",\n                  \"flex items-center justify-center\",\n                  \"transition-[color,background-color,transform] duration-[var(--poyraz-motion-duration-fast)] ease-[var(--poyraz-motion-ease-out)] active:scale-[var(--poyraz-motion-scale-press-small)]\",\n                  \"hover:bg-accent\",\n                  \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n                  isCurrent && !isSelected && \"border border-primary\",\n                  isSelected && \"bg-primary text-primary-foreground hover:bg-primary-hover\",\n                )}\n              >\n                {m}\n              </button>\n            );\n          })}\n        </div>\n      </>\n    );\n  };\n\n  /* ── Years view ─────────────────────────────────────────────────── */\n\n  const renderYears = () => {\n    const now = new Date();\n    const years = Array.from({ length: 12 }, (_, i) => decadeStart + i);\n    return (\n      <>\n        {/* Header */}\n        <div className=\"flex items-center justify-between mb-3\">\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            className=\"h-7 w-7 active:scale-95\"\n            onClick={() => setDecadeStart((d) => d - 12)}\n            aria-label=\"Previous decade\"\n          >\n            <ChevronLeft className=\"h-4 w-4\" />\n          </Button>\n          <span className=\"text-sm font-bold uppercase tracking-wide\">\n            {decadeStart} – {decadeStart + 11}\n          </span>\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            className=\"h-7 w-7 active:scale-95\"\n            onClick={() => setDecadeStart((d) => d + 12)}\n            aria-label=\"Next decade\"\n          >\n            <ChevronRight className=\"h-4 w-4\" />\n          </Button>\n        </div>\n\n        {/* Year grid: 4×3 */}\n        <div className=\"grid grid-cols-3 gap-1 animate-poyraz-slide-in-from-bottom\">\n          {years.map((y) => {\n            const isCurrent = y === now.getFullYear();\n            const isSelected = selectedDate && y === selectedDate.getFullYear();\n            return (\n              <button\n                key={y}\n                type=\"button\"\n                onClick={() => {\n                  setViewYear(y);\n                  setView(\"months\");\n                }}\n                className={cn(\n                  \"h-8 text-sm font-medium cursor-pointer\",\n                  \"flex items-center justify-center\",\n                  \"transition-[color,background-color,transform] duration-[var(--poyraz-motion-duration-fast)] ease-[var(--poyraz-motion-ease-out)] active:scale-[var(--poyraz-motion-scale-press-small)]\",\n                  \"hover:bg-accent\",\n                  \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n                  isCurrent && !isSelected && \"border border-primary\",\n                  isSelected && \"bg-primary text-primary-foreground hover:bg-primary-hover\",\n                )}\n              >\n                {y}\n              </button>\n            );\n          })}\n        </div>\n      </>\n    );\n  };\n\n  return (\n    <div\n      data-slot=\"calendar\"\n      data-mode={mode}\n      data-surface={surface}\n      className={cn(\n        \"select-none p-3 animate-poyraz-fade-in motion-reduce:animate-none\",\n        surface === \"solid\" && \"border border-border bg-surface shadow-sm\",\n        surface === \"soft\" && \"border border-transparent bg-surface-subtle\",\n        surface === \"glass\" &&\n          \"border border-glass-border-outer bg-glass shadow-md backdrop-blur-glass\",\n        radius === \"none\" && \"rounded-none\",\n        radius === \"sm\" && \"rounded-sm\",\n        radius === \"md\" && \"rounded-md\",\n        radius === \"lg\" && \"rounded-lg\",\n        radius === \"xl\" && \"rounded-xl\",\n        className,\n      )}\n    >\n      {view === \"days\" && renderDays()}\n      {view === \"months\" && renderMonths()}\n      {view === \"years\" && renderYears()}\n    </div>\n  );\n}\nCalendar.displayName = \"Calendar\";\n\nexport { Calendar };\n",
      "type": "registry:ui",
      "target": "@ui/molecules/calendar.tsx"
    }
  ],
  "meta": {
    "category": "phase-7-composite",
    "phase": "beta"
  },
  "type": "registry:ui"
}