{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "calendar-styled",
  "title": "Calendar essentials",
  "description": "Styled calendar: the essentials set. 5 decorative calendar variations (ClassicCalendar, RoundedCalendar, CompactCalendar, GlassCalendar, RangeCalendar) 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/calendar-styled.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { motion, useReducedMotion } from \"motion/react\"\nimport { ChevronLeft, ChevronRight } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Styled calendar family: 5 decorative date pickers with an always-visible\n   (inline) month grid. The visible month is computed from state; to avoid an\n   SSR/hydration mismatch the default month is FIXED (January 2025), NOT\n   new Date()/today. The \"today\" ring is added only after mount (in an effect), so\n   the server output stays deterministic. Color comes ONLY from semantic tokens;\n   transparency uses the Tailwind slash opacity modifier (which compiles to\n   color-mix), NEVER a raw var(--x)/0.4. framer-motion (motion/react) slides on a\n   month change and is disabled under reduced-motion. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\n/* Deterministic: fixed name tables that do not depend on the locale\n   (toLocaleString can give a different result on the server and the client). */\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] as const\n\nconst WEEKDAYS_MONDAY = [\"Mo\", \"Tu\", \"We\", \"Th\", \"Fr\", \"Sa\", \"Su\"] as const\nconst WEEKDAYS_SUNDAY = [\"Su\", \"Mo\", \"Tu\", \"We\", \"Th\", \"Fr\", \"Sa\"] as const\n\nconst FIXED_MONTH = new Date(2025, 0, 1)\n\nfunction startOfMonth(d: Date) {\n  return new Date(d.getFullYear(), d.getMonth(), 1)\n}\n\nfunction addMonths(d: Date, n: number) {\n  return new Date(d.getFullYear(), d.getMonth() + n, 1)\n}\n\nfunction isSameDay(a: Date | null | undefined, b: Date | null | undefined) {\n  return (\n    !!a &&\n    !!b &&\n    a.getFullYear() === b.getFullYear() &&\n    a.getMonth() === b.getMonth() &&\n    a.getDate() === b.getDate()\n  )\n}\n\n/* Verilen ayin 6x7 (42 gun) izgarasini kurar; onceki/sonraki ay tasmalari\n   dahil. weekStartsOn: 0 = Pazar, 1 = Pazartesi. */\nfunction buildMonthGrid(month: Date, weekStartsOn: 0 | 1): Date[] {\n  const first = startOfMonth(month)\n  const offset = (first.getDay() - weekStartsOn + 7) % 7\n  const start = new Date(first)\n  start.setDate(first.getDate() - offset)\n  const days: Date[] = []\n  for (let i = 0; i < 42; i++) {\n    const d = new Date(start)\n    d.setDate(start.getDate() + i)\n    days.push(d)\n  }\n  return days\n}\n\nfunction ariaLabel(d: Date) {\n  return `${MONTHS[d.getMonth()]} ${d.getDate()}, ${d.getFullYear()}`\n}\n\n/* Gorunur ayi kontrollu (month) / kontrolsuz (defaultMonth) yonetir; prev/next\n   yon bilgisiyle birlikte. */\nfunction useVisibleMonth(month?: Date, defaultMonth?: Date) {\n  const [internal, setInternal] = React.useState<Date>(() =>\n    startOfMonth(defaultMonth ?? FIXED_MONTH)\n  )\n  const [dir, setDir] = React.useState(0)\n  const visible = month ? startOfMonth(month) : internal\n  const goPrev = React.useCallback(() => {\n    setDir(-1)\n    if (month === undefined) setInternal((m) => addMonths(m, -1))\n  }, [month])\n  const goNext = React.useCallback(() => {\n    setDir(1)\n    if (month === undefined) setInternal((m) => addMonths(m, 1))\n  }, [month])\n  return { visible, dir, goPrev, goNext }\n}\n\n/* Secili gunu kontrollu (value) / kontrolsuz (defaultValue) yonetir. */\nfunction useSelected(value?: Date, defaultValue?: Date, onSelect?: (d: Date) => void) {\n  const [internal, setInternal] = React.useState<Date | undefined>(defaultValue)\n  const selected = value ?? internal\n  const select = React.useCallback(\n    (d: Date) => {\n      if (value === undefined) setInternal(d)\n      onSelect?.(d)\n    },\n    [value, onSelect]\n  )\n  return { selected, select }\n}\n\n/* \"Today\" is computed only on the client (after mount) -> the SSR stays\n   deterministic. */\nfunction useToday() {\n  const [today, setToday] = React.useState<Date | null>(null)\n  React.useEffect(() => {\n    setToday(new Date())\n  }, [])\n  return today\n}\n\nconst cellSizes: Record<StyledSize, string> = {\n  sm: \"size-8 text-xs\",\n  md: \"size-9 text-sm\",\n  lg: \"size-10 text-sm\",\n  xl: \"size-11 text-base\",\n}\n\nconst compactSizes: Record<StyledSize, string> = {\n  sm: \"size-7 text-[0.7rem]\",\n  md: \"size-8 text-xs\",\n  lg: \"size-9 text-sm\",\n  xl: \"size-10 text-sm\",\n}\n\nconst navButton =\n  \"inline-flex size-7 items-center justify-center rounded-md text-muted-foreground outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 [&_svg]:size-4 [&_svg]:shrink-0\"\n\ninterface FrameProps {\n  className?: string\n  root: string\n  header: string\n  title: string\n  weekday: string\n  weekdays: readonly string[]\n  visible: Date\n  dir: number\n  gridClassName: string\n  onPrev: () => void\n  onNext: () => void\n  children: React.ReactNode\n}\n\n/* Shared shell: a bordered body plus a header (prev/next plus the month name) plus weekday headers plus a grid that slides on month change. */\nfunction CalendarFrame({\n  className,\n  root,\n  header,\n  title,\n  weekday,\n  weekdays,\n  visible,\n  dir,\n  gridClassName,\n  onPrev,\n  onNext,\n  children,\n}: FrameProps) {\n  const reduce = useReducedMotion()\n  const monthKey = `${visible.getFullYear()}-${visible.getMonth()}`\n  return (\n    <div data-slot=\"styled-calendar\" role=\"grid\" aria-label=\"Calendar\" className={cn(root, className)}>\n      <div className={header}>\n        <button type=\"button\" onClick={onPrev} aria-label=\"Previous month\" className={navButton}>\n          <ChevronLeft />\n        </button>\n        <div aria-live=\"polite\" className={title}>\n          {MONTHS[visible.getMonth()]} {visible.getFullYear()}\n        </div>\n        <button type=\"button\" onClick={onNext} aria-label=\"Next month\" className={navButton}>\n          <ChevronRight />\n        </button>\n      </div>\n      <div role=\"row\" className={cn(\"grid grid-cols-7\", gridClassName)}>\n        {weekdays.map((w) => (\n          <div\n            key={w}\n            role=\"columnheader\"\n            className={cn(\"flex items-center justify-center py-1\", weekday)}\n          >\n            {w}\n          </div>\n        ))}\n      </div>\n      <div className=\"relative overflow-hidden\">\n        <motion.div\n          key={monthKey}\n          initial={reduce ? false : { x: dir >= 0 ? \"18%\" : \"-18%\", opacity: 0 }}\n          animate={reduce ? { x: 0, opacity: 1 } : { x: 0, opacity: 1 }}\n          transition={reduce ? { duration: 0 } : { duration: 0.22, ease: \"easeOut\" }}\n          className={cn(\"grid grid-cols-7\", gridClassName)}\n        >\n          {children}\n        </motion.div>\n      </div>\n    </div>\n  )\n}\n\ninterface DayAppearance {\n  base: string\n  selected: string\n  today: string\n  outside: string\n  cell?: Record<StyledSize, string>\n}\n\ninterface BaseCalendarProps {\n  className?: string\n  size?: StyledSize\n  month?: Date\n  defaultMonth?: Date\n  value?: Date\n  defaultValue?: Date\n  onSelect?: (d: Date) => void\n}\n\ninterface Chrome {\n  root: string\n  header: string\n  title: string\n  weekday: string\n  weekdays: readonly string[]\n  gridClassName: string\n  weekStartsOn: 0 | 1\n}\n\n/* Shared body for the single-select variants (Classic/Rounded/Compact/Glass); only the appearance class sets differ. */\nfunction SingleCalendar({\n  chrome,\n  day,\n  props,\n}: {\n  chrome: Chrome\n  day: DayAppearance\n  props: BaseCalendarProps\n}) {\n  const { className, size = \"md\", month, defaultMonth, value, defaultValue, onSelect } = props\n  const { visible, dir, goPrev, goNext } = useVisibleMonth(month, defaultMonth)\n  const { selected, select } = useSelected(value, defaultValue, onSelect)\n  const today = useToday()\n  const cell = day.cell ?? cellSizes\n  const days = buildMonthGrid(visible, chrome.weekStartsOn)\n\n  return (\n    <CalendarFrame\n      className={className}\n      root={chrome.root}\n      header={chrome.header}\n      title={chrome.title}\n      weekday={chrome.weekday}\n      weekdays={chrome.weekdays}\n      visible={visible}\n      dir={dir}\n      gridClassName={chrome.gridClassName}\n      onPrev={goPrev}\n      onNext={goNext}\n    >\n      {days.map((d) => {\n        const outside = d.getMonth() !== visible.getMonth()\n        const isSel = isSameDay(d, selected)\n        const isToday = isSameDay(d, today)\n        return (\n          <button\n            key={d.toISOString()}\n            type=\"button\"\n            role=\"gridcell\"\n            aria-label={ariaLabel(d)}\n            aria-pressed={isSel}\n            onClick={() => select(d)}\n            className={cn(\n              day.base,\n              cell[size],\n              outside && day.outside,\n              isToday && !isSel && day.today,\n              isSel && day.selected\n            )}\n          >\n            {d.getDate()}\n          </button>\n        )\n      })}\n    </CalendarFrame>\n  )\n}\n\n/* ---- 1. ClassicCalendar: temiz, kenarlikli ay izgarasi ---- */\n\nconst classicChrome: Chrome = {\n  root: \"inline-block rounded-xl border border-border bg-card p-3 text-card-foreground\",\n  header: \"mb-2 flex items-center justify-between gap-2 px-1\",\n  title: \"text-sm font-medium\",\n  weekday: \"text-xs font-medium text-muted-foreground\",\n  weekdays: WEEKDAYS_MONDAY,\n  gridClassName: \"gap-1\",\n  weekStartsOn: 1,\n}\n\nconst classicDay: DayAppearance = {\n  base: \"relative inline-flex items-center justify-center rounded-md font-normal text-foreground outline-none transition-colors duration-(--motion-fast) ease-(--motion-ease) hover:bg-accent hover:text-accent-foreground focus-visible:z-10 focus-visible:ring-[3px] focus-visible:ring-ring/50 motion-reduce:transition-none\",\n  selected: \"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground\",\n  today: \"ring-1 ring-inset ring-ring\",\n  outside: \"text-muted-foreground/50\",\n}\n\nexport function ClassicCalendar(props: BaseCalendarProps) {\n  return <SingleCalendar chrome={classicChrome} day={classicDay} props={props} />\n}\n\n/* ---- 2. RoundedCalendar: tam yuvarlak hucreler, secili = dolu daire ---- */\n\nconst roundedChrome: Chrome = {\n  root: \"inline-block rounded-2xl border border-border bg-card p-3 text-card-foreground\",\n  header: \"mb-2 flex items-center justify-between gap-2 px-1\",\n  title: \"text-sm font-semibold\",\n  weekday: \"text-xs font-medium text-muted-foreground\",\n  weekdays: WEEKDAYS_MONDAY,\n  gridClassName: \"gap-1\",\n  weekStartsOn: 1,\n}\n\nconst roundedDay: DayAppearance = {\n  base: \"relative inline-flex items-center justify-center rounded-full font-normal text-foreground outline-none transition-colors duration-(--motion-fast) ease-(--motion-ease) hover:bg-accent hover:text-accent-foreground focus-visible:z-10 focus-visible:ring-[3px] focus-visible:ring-ring/50 motion-reduce:transition-none\",\n  selected: \"bg-primary text-primary-foreground shadow-sm hover:bg-primary hover:text-primary-foreground\",\n  today: \"ring-1 ring-ring\",\n  outside: \"text-muted-foreground/50\",\n}\n\nexport function RoundedCalendar(props: BaseCalendarProps) {\n  return <SingleCalendar chrome={roundedChrome} day={roundedDay} props={props} />\n}\n\n/* ---- 4. CompactCalendar: daha sik hucreler, kucuk baslik ---- */\n\nconst compactChrome: Chrome = {\n  root: \"inline-block rounded-lg border border-border bg-card p-2 text-card-foreground\",\n  header: \"mb-1 flex items-center justify-between gap-1 px-0.5\",\n  title: \"text-xs font-medium\",\n  weekday: \"text-[0.65rem] font-medium text-muted-foreground\",\n  weekdays: WEEKDAYS_SUNDAY,\n  gridClassName: \"gap-0.5\",\n  weekStartsOn: 0,\n}\n\nconst compactDay: DayAppearance = {\n  base: \"relative inline-flex items-center justify-center rounded-md font-normal text-foreground outline-none transition-colors duration-(--motion-fast) ease-(--motion-ease) hover:bg-accent hover:text-accent-foreground focus-visible:z-10 focus-visible:ring-[3px] focus-visible:ring-ring/50 motion-reduce:transition-none\",\n  selected: \"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground\",\n  today: \"ring-1 ring-inset ring-ring\",\n  outside: \"text-muted-foreground/50\",\n  cell: compactSizes,\n}\n\nexport function CompactCalendar(props: BaseCalendarProps) {\n  return <SingleCalendar chrome={compactChrome} day={compactDay} props={props} />\n}\n\n/* ---- 5. GlassCalendar: buzlu cam kart + backdrop-blur ---- */\n\nconst glassChrome: Chrome = {\n  root: \"relative inline-block overflow-hidden rounded-2xl border border-border/60 bg-card/80 p-3 text-card-foreground shadow-[0_10px_40px_-16px_color-mix(in_oklab,var(--color-foreground)_45%,transparent)] supports-[backdrop-filter]:bg-card/55 supports-[backdrop-filter]:backdrop-blur-xl\",\n  header: \"mb-2 flex items-center justify-between gap-2 px-1\",\n  title: \"text-sm font-medium\",\n  weekday: \"text-xs font-medium text-muted-foreground\",\n  weekdays: WEEKDAYS_MONDAY,\n  gridClassName: \"gap-1\",\n  weekStartsOn: 1,\n}\n\nconst glassDay: DayAppearance = {\n  base: \"relative inline-flex items-center justify-center rounded-lg font-normal text-foreground outline-none transition-colors duration-(--motion-fast) ease-(--motion-ease) hover:bg-accent/60 hover:text-accent-foreground focus-visible:z-10 focus-visible:ring-[3px] focus-visible:ring-ring/50 motion-reduce:transition-none\",\n  selected: \"bg-primary text-primary-foreground shadow-sm hover:bg-primary hover:text-primary-foreground\",\n  today: \"ring-1 ring-inset ring-ring\",\n  outside: \"text-muted-foreground/50\",\n}\n\nexport function GlassCalendar(props: BaseCalendarProps) {\n  return <SingleCalendar chrome={glassChrome} day={glassDay} props={props} />\n}\n\n/* ---- 3. RangeCalendar: a start + end range selection, with the days in\n   between highlighted by a token fill. The outer signature is singular (onSelect\n   reports the clicked day on every click); the range state is held inside the\n   component. ---- */\n\nconst rangeCellSizes: Record<StyledSize, string> = {\n  sm: \"h-8 w-full text-xs\",\n  md: \"h-9 w-full text-sm\",\n  lg: \"h-10 w-full text-sm\",\n  xl: \"h-11 w-full text-base\",\n}\n\nexport function RangeCalendar({\n  className,\n  size = \"md\",\n  month,\n  defaultMonth,\n  value,\n  defaultValue,\n  onSelect,\n}: BaseCalendarProps) {\n  const { visible, dir, goPrev, goNext } = useVisibleMonth(month, defaultMonth)\n  const today = useToday()\n  const [start, setStart] = React.useState<Date | null>(\n    value ?? defaultValue ?? null\n  )\n  const [end, setEnd] = React.useState<Date | null>(null)\n\n  const handleSelect = React.useCallback(\n    (d: Date) => {\n      onSelect?.(d)\n      if (start === null || end !== null) {\n        setStart(d)\n        setEnd(null)\n        return\n      }\n      if (d.getTime() < start.getTime()) {\n        setEnd(start)\n        setStart(d)\n      } else {\n        setEnd(d)\n      }\n    },\n    [start, end, onSelect]\n  )\n\n  const days = buildMonthGrid(visible, 1)\n\n  return (\n    <CalendarFrame\n      className={className}\n      root=\"inline-block rounded-xl border border-border bg-card p-3 text-card-foreground\"\n      header=\"mb-2 flex items-center justify-between gap-2 px-1\"\n      title=\"text-sm font-medium\"\n      weekday=\"text-xs font-medium text-muted-foreground\"\n      weekdays={WEEKDAYS_MONDAY}\n      visible={visible}\n      dir={dir}\n      gridClassName=\"gap-y-1\"\n      onPrev={goPrev}\n      onNext={goNext}\n    >\n      {days.map((d) => {\n        const outside = d.getMonth() !== visible.getMonth()\n        const isStart = isSameDay(d, start)\n        const isEnd = isSameDay(d, end)\n        const isEndpoint = isStart || isEnd\n        const t = d.getTime()\n        const inRange =\n          start !== null &&\n          end !== null &&\n          t > start.getTime() &&\n          t < end.getTime()\n        const isToday = isSameDay(d, today)\n        return (\n          <button\n            key={d.toISOString()}\n            type=\"button\"\n            role=\"gridcell\"\n            aria-label={ariaLabel(d)}\n            aria-pressed={isEndpoint}\n            onClick={() => handleSelect(d)}\n            className={cn(\n              \"relative inline-flex items-center justify-center font-normal text-foreground outline-none transition-colors duration-(--motion-fast) ease-(--motion-ease) focus-visible:z-10 focus-visible:ring-[3px] focus-visible:ring-ring/50 motion-reduce:transition-none\",\n              rangeCellSizes[size],\n              inRange && \"bg-accent text-accent-foreground\",\n              isStart && \"rounded-l-md\",\n              isEnd && \"rounded-r-md\",\n              isStart && isEnd && \"rounded-md\",\n              isEndpoint &&\n                \"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground\",\n              !isEndpoint && !inRange && \"rounded-md hover:bg-accent hover:text-accent-foreground\",\n              outside && !isEndpoint && !inRange && \"text-muted-foreground/50\",\n              isToday && !isEndpoint && !inRange && \"ring-1 ring-inset ring-ring\"\n            )}\n          >\n            {d.getDate()}\n          </button>\n        )\n      })}\n    </CalendarFrame>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/calendar-styled.tsx"
    }
  ],
  "categories": [
    "styled",
    "calendar"
  ],
  "type": "registry:component"
}