{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "calendar-event",
  "title": "Event calendar",
  "description": "Styled calendar: the Event set. 5 decorative calendar variations (DotCalendar, BadgeCalendar, BarCalendar, CountCalendar, ToneEventCalendar) on the ai2 token system, driven by CSS token transitions and sized sm to xl. Part of the free styled layer.",
  "dependencies": [
    "lucide-react@^1.23.0"
  ],
  "registryDependencies": [
    "@ai2/tokens"
  ],
  "files": [
    {
      "path": "registry/ai2/styled/calendar-event.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ChevronLeft, ChevronRight } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Event calendar family: 5 month grids where the day cells carry an event marker. Determinism: the visible month derives from a FIXED constant (January 2026), NOT from the clock; the event data is a hand-written fixed table as well. That way the server and client output stay byte-identical (prerender and hydration safe). Colour comes ONLY from semantic tokens; transparency through the Tailwind slash modifier or color-mix. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\n/* Locale'e bagimli olmayan sabit isim tablolari. */\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 = [\"Mo\", \"Tu\", \"We\", \"Th\", \"Fr\", \"Sa\", \"Su\"] as const\n\nconst FIXED_MONTH = new Date(2026, 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) izgarasi; pazartesi baslangicli, tasmalar dahil. */\nfunction buildMonthGrid(month: Date): Date[] {\n  const first = startOfMonth(month)\n  const offset = (first.getDay() + 6) % 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 toWeeks(days: Date[]): Date[][] {\n  const weeks: Date[][] = []\n  for (let i = 0; i < days.length; i += 7) weeks.push(days.slice(i, i + 7))\n  return weeks\n}\n\nfunction ariaLabel(d: Date) {\n  return `${MONTHS[d.getMonth()]} ${d.getDate()}, ${d.getFullYear()}`\n}\n\n/* The visible month state; always held OUTSIDE the grid. */\nfunction useVisibleMonth(defaultMonth?: Date) {\n  const [visible, setVisible] = React.useState<Date>(() =>\n    startOfMonth(defaultMonth ?? FIXED_MONTH)\n  )\n  const goPrev = React.useCallback(() => setVisible((m) => addMonths(m, -1)), [])\n  const goNext = React.useCallback(() => setVisible((m) => addMonths(m, 1)), [])\n  return { visible, goPrev, goNext }\n}\n\nfunction useSelected(defaultValue?: Date, onSelect?: (d: Date) => void) {\n  const [selected, setSelected] = React.useState<Date | undefined>(defaultValue)\n  const select = React.useCallback(\n    (d: Date) => {\n      setSelected(d)\n      onSelect?.(d)\n    },\n    [onSelect]\n  )\n  return { selected, select }\n}\n\n/* Sabit etkinlik tablosu: ayin gunu -> etkinlik sayisi. Deterministik. */\nconst EVENT_COUNTS: Record<number, number> = {\n  3: 1,\n  6: 2,\n  9: 3,\n  12: 1,\n  15: 2,\n  18: 1,\n  21: 3,\n  24: 2,\n  27: 1,\n  30: 2,\n}\n\ntype EventTone = \"info\" | \"success\" | \"warning\" | \"danger\"\n\n/* Sabit ton tablosu: ayin gunu -> semantic ton. Deterministik. */\nconst EVENT_TONES: Record<number, EventTone> = {\n  3: \"info\",\n  6: \"success\",\n  9: \"danger\",\n  12: \"warning\",\n  15: \"info\",\n  18: \"success\",\n  21: \"danger\",\n  24: \"warning\",\n  27: \"info\",\n  30: \"success\",\n}\n\nfunction eventCount(d: Date, outside: boolean) {\n  if (outside) return 0\n  return EVENT_COUNTS[d.getDate()] ?? 0\n}\n\nfunction eventTone(d: Date): EventTone {\n  return EVENT_TONES[d.getDate()] ?? \"info\"\n}\n\nconst cellSizes: Record<StyledSize, string> = {\n  sm: \"h-9 w-8 text-xs\",\n  md: \"h-10 w-9 text-sm\",\n  lg: \"h-11 w-10 text-sm\",\n  xl: \"h-12 w-11 text-base\",\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 [&_i]:text-base [&_i]:leading-none\"\n\nconst dayBase =\n  \"relative inline-flex flex-col items-center justify-center gap-0.5 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\nconst daySelected =\n  \"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground\"\n\nconst dayOutside = \"text-muted-foreground/50\"\n\nexport interface EventCalendarProps {\n  className?: string\n  size?: StyledSize\n  defaultMonth?: Date\n  defaultValue?: Date\n  onSelect?: (d: Date) => void\n}\n\ninterface DayContext {\n  date: Date\n  outside: boolean\n  isSelected: boolean\n  count: number\n}\n\n/* The shared body: a header (prev/next + the month name) + weekday headings +\n   weekly role=\"row\" rows. The variants only produce the day content. */\nfunction EventCalendarBase({\n  props,\n  tone,\n  renderDay,\n}: {\n  props: EventCalendarProps\n  tone?: string\n  renderDay: (ctx: DayContext) => React.ReactNode\n}) {\n  const { className, size = \"md\", defaultMonth, defaultValue, onSelect } = props\n  const { visible, goPrev, goNext } = useVisibleMonth(defaultMonth)\n  const { selected, select } = useSelected(defaultValue, onSelect)\n  const titleId = React.useId()\n  const weeks = toWeeks(buildMonthGrid(visible))\n\n  return (\n    <div\n      data-slot=\"styled-calendar\"\n      data-tone={tone}\n      role=\"grid\"\n      aria-labelledby={titleId}\n      className={cn(\n        \"inline-block rounded-xl border border-border bg-card p-3 text-card-foreground\",\n        className\n      )}\n    >\n      <div className=\"mb-2 flex items-center justify-between gap-2 px-1\">\n        <button type=\"button\" onClick={goPrev} aria-label=\"Previous month\" className={navButton}>\n          <ChevronLeft />\n        </button>\n        <div id={titleId} aria-live=\"polite\" className=\"text-sm font-medium\">\n          {MONTHS[visible.getMonth()]} {visible.getFullYear()}\n        </div>\n        <button type=\"button\" onClick={goNext} aria-label=\"Next month\" className={navButton}>\n          <ChevronRight />\n        </button>\n      </div>\n      <div role=\"row\" className=\"grid grid-cols-7 gap-1\">\n        {WEEKDAYS.map((w) => (\n          <div\n            key={w}\n            role=\"columnheader\"\n            className=\"flex items-center justify-center py-1 text-xs font-medium text-muted-foreground\"\n          >\n            {w}\n          </div>\n        ))}\n      </div>\n      {weeks.map((week) => (\n        <div key={week[0].toISOString()} role=\"row\" className=\"grid grid-cols-7 gap-1\">\n          {week.map((d) => {\n            const outside = d.getMonth() !== visible.getMonth()\n            const isSelected = isSameDay(d, selected)\n            const count = eventCount(d, outside)\n            return (\n              <button\n                key={d.toISOString()}\n                type=\"button\"\n                role=\"gridcell\"\n                aria-label={ariaLabel(d)}\n                aria-selected={isSelected}\n                onClick={() => select(d)}\n                className={cn(\n                  dayBase,\n                  cellSizes[size],\n                  outside && dayOutside,\n                  isSelected && daySelected\n                )}\n              >\n                {renderDay({ date: d, outside, isSelected, count })}\n              </button>\n            )\n          })}\n        </div>\n      ))}\n    </div>\n  )\n}\n\n/* ---- 1. DotCalendar: gun numarasinin altinda etkinlik basina bir nokta ---- */\n\nexport function DotCalendar(props: EventCalendarProps) {\n  return (\n    <EventCalendarBase\n      props={props}\n      renderDay={({ date, isSelected, count }) => (\n        <>\n          <span className=\"leading-none\">{date.getDate()}</span>\n          <span className=\"flex h-1 items-center justify-center gap-0.5\">\n            {Array.from({ length: count }).map((_, i) => (\n              <span\n                key={i}\n                className={cn(\n                  \"size-1 rounded-full\",\n                  isSelected ? \"bg-primary-foreground\" : \"bg-primary\"\n                )}\n              />\n            ))}\n          </span>\n        </>\n      )}\n    />\n  )\n}\n\n/* ---- 2. BadgeCalendar: sag ust kosede etkinlik sayisi rozeti ---- */\n\nexport function BadgeCalendar(props: EventCalendarProps) {\n  return (\n    <EventCalendarBase\n      props={props}\n      renderDay={({ date, isSelected, count }) => (\n        <>\n          <span className=\"leading-none\">{date.getDate()}</span>\n          {count > 0 ? (\n            <span\n              className={cn(\n                \"absolute right-0.5 top-0.5 inline-flex min-w-3 items-center justify-center rounded-full px-0.5 text-[0.6rem] font-medium leading-none\",\n                isSelected ? \"bg-primary-foreground text-primary\" : \"bg-primary text-primary-foreground\"\n              )}\n            >\n              {count}\n            </span>\n          ) : null}\n        </>\n      )}\n    />\n  )\n}\n\n/* ---- 3. BarCalendar: hucre altinda yogunlukla orantili bir cubuk ---- */\n\nexport function BarCalendar(props: EventCalendarProps) {\n  return (\n    <EventCalendarBase\n      props={props}\n      renderDay={({ date, isSelected, count }) => (\n        <>\n          <span className=\"leading-none\">{date.getDate()}</span>\n          <span className=\"flex h-1 w-full items-center px-1.5\">\n            {count > 0 ? (\n              <span\n                style={{ width: `${(Math.min(count, 3) / 3) * 100}%` }}\n                className={cn(\n                  \"block h-0.5 rounded-full\",\n                  isSelected ? \"bg-primary-foreground\" : \"bg-primary\"\n                )}\n              />\n            ) : null}\n          </span>\n        </>\n      )}\n    />\n  )\n}\n\n/* ---- 4. CountCalendar: gun numarasinin altinda \"N events\" sayaci ---- */\n\nexport function CountCalendar(props: EventCalendarProps) {\n  return (\n    <EventCalendarBase\n      props={props}\n      renderDay={({ date, isSelected, count }) => (\n        <>\n          <span className=\"leading-none\">{date.getDate()}</span>\n          <span\n            className={cn(\n              \"h-2.5 text-[0.6rem] leading-none tabular-nums\",\n              isSelected ? \"text-primary-foreground/80\" : \"text-muted-foreground\"\n            )}\n          >\n            {count > 0 ? count : \"\"}\n          </span>\n        </>\n      )}\n    />\n  )\n}\n\n/* ---- 5. ToneEventCalendar: sabit ton tablosuna gore renkli noktalar ---- */\n\nconst toneDot: Record<EventTone, string> = {\n  info: \"bg-info\",\n  success: \"bg-success\",\n  warning: \"bg-warning\",\n  danger: \"bg-danger\",\n}\n\nexport function ToneEventCalendar(props: EventCalendarProps) {\n  return (\n    <EventCalendarBase\n      props={props}\n      tone=\"neutral\"\n      renderDay={({ date, isSelected, count }) => (\n        <>\n          <span className=\"leading-none\">{date.getDate()}</span>\n          <span className=\"flex h-1 items-center justify-center gap-0.5\">\n            {Array.from({ length: count }).map((_, i) => (\n              <span\n                key={i}\n                className={cn(\n                  \"size-1 rounded-full\",\n                  isSelected ? \"bg-primary-foreground\" : toneDot[eventTone(date)]\n                )}\n              />\n            ))}\n          </span>\n        </>\n      )}\n    />\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/calendar-event.tsx"
    }
  ],
  "categories": [
    "styled",
    "calendar"
  ],
  "type": "registry:component"
}