{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "calendar-tone",
  "title": "Tone calendar",
  "description": "Styled calendar: the Tone set. 5 decorative calendar variations (InfoCalendar, SuccessCalendar, WarningCalendar, DangerCalendar, MutedCalendar) 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-tone.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ChevronLeft, ChevronRight } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Tone calendar family: the same month grid in 5 semantic tones (info, success, warning, danger, muted). Determinism: the visible month derives from a FIXED constant (January 2026), NOT from the clock. Colour comes ONLY from semantic tokens; alpha through the Tailwind slash modifier or color-mix. Every root carries data-tone. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\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\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\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\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 dayBase =\n  \"relative inline-flex items-center justify-center rounded-md font-normal tabular-nums 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\nconst navBase =\n  \"inline-flex size-7 items-center justify-center rounded-md outline-none transition-colors focus-visible:ring-[3px] focus-visible:ring-ring/50 [&_svg]:size-4 [&_svg]:shrink-0 [&_i]:text-base [&_i]:leading-none\"\n\ninterface ToneSpec {\n  /** data-tone degeri. */\n  tone: string\n  /** Kok yuzey (kenarlik + hafif tonlu zemin). */\n  root: string\n  /** Baslik metni. */\n  title: string\n  /** Prev/next butonlari. */\n  nav: string\n  /** Hafta basliklari. */\n  weekday: string\n  /** Secilmemis gun. */\n  day: string\n  /** Secili gun. */\n  selected: string\n  /** Ay disi gunler. */\n  outside: string\n}\n\nexport interface ToneCalendarProps {\n  className?: string\n  size?: StyledSize\n  defaultMonth?: Date\n  defaultValue?: Date\n  onSelect?: (d: Date) => void\n}\n\n/* The shared body; there is no difference other than ToneSpec. */\nfunction ToneCalendarBase({ spec, props }: { spec: ToneSpec; props: ToneCalendarProps }) {\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={spec.tone}\n      role=\"grid\"\n      aria-labelledby={titleId}\n      className={cn(\"inline-block rounded-xl border p-3\", spec.root, className)}\n    >\n      <div className=\"mb-2 flex items-center justify-between gap-2 px-1\">\n        <button\n          type=\"button\"\n          onClick={goPrev}\n          aria-label=\"Previous month\"\n          className={cn(navBase, spec.nav)}\n        >\n          <ChevronLeft />\n        </button>\n        <div id={titleId} aria-live=\"polite\" className={cn(\"text-sm font-medium\", spec.title)}>\n          {MONTHS[visible.getMonth()]} {visible.getFullYear()}\n        </div>\n        <button\n          type=\"button\"\n          onClick={goNext}\n          aria-label=\"Next month\"\n          className={cn(navBase, spec.nav)}\n        >\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={cn(\"flex items-center justify-center py-1 text-xs font-medium\", spec.weekday)}\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            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                  spec.day,\n                  outside && spec.outside,\n                  isSelected && spec.selected\n                )}\n              >\n                {d.getDate()}\n              </button>\n            )\n          })}\n        </div>\n      ))}\n    </div>\n  )\n}\n\n/* ---- 1. InfoCalendar ---- */\n\nconst infoSpec: ToneSpec = {\n  tone: \"info\",\n  root: \"border-info/30 bg-[color-mix(in_oklab,var(--color-info)_5%,var(--color-card))] text-card-foreground\",\n  title: \"text-info\",\n  nav: \"text-info hover:bg-info-soft hover:text-info-soft-foreground\",\n  weekday: \"text-info/70\",\n  day: \"text-foreground hover:bg-info-soft hover:text-info-soft-foreground\",\n  selected: \"bg-info text-info-foreground hover:bg-info hover:text-info-foreground\",\n  outside: \"text-muted-foreground/50\",\n}\n\nexport function InfoCalendar(props: ToneCalendarProps) {\n  return <ToneCalendarBase spec={infoSpec} props={props} />\n}\n\n/* ---- 2. SuccessCalendar ---- */\n\nconst successSpec: ToneSpec = {\n  tone: \"success\",\n  root: \"border-success/30 bg-[color-mix(in_oklab,var(--color-success)_5%,var(--color-card))] text-card-foreground\",\n  title: \"text-success\",\n  nav: \"text-success hover:bg-success-soft hover:text-success-soft-foreground\",\n  weekday: \"text-success/70\",\n  day: \"text-foreground hover:bg-success-soft hover:text-success-soft-foreground\",\n  selected: \"bg-success text-success-foreground hover:bg-success hover:text-success-foreground\",\n  outside: \"text-muted-foreground/50\",\n}\n\nexport function SuccessCalendar(props: ToneCalendarProps) {\n  return <ToneCalendarBase spec={successSpec} props={props} />\n}\n\n/* ---- 3. WarningCalendar ---- */\n\nconst warningSpec: ToneSpec = {\n  tone: \"warning\",\n  root: \"border-warning/30 bg-[color-mix(in_oklab,var(--color-warning)_5%,var(--color-card))] text-card-foreground\",\n  title: \"text-warning\",\n  nav: \"text-warning hover:bg-warning-soft hover:text-warning-soft-foreground\",\n  weekday: \"text-warning/70\",\n  day: \"text-foreground hover:bg-warning-soft hover:text-warning-soft-foreground\",\n  selected: \"bg-warning text-warning-foreground hover:bg-warning hover:text-warning-foreground\",\n  outside: \"text-muted-foreground/50\",\n}\n\nexport function WarningCalendar(props: ToneCalendarProps) {\n  return <ToneCalendarBase spec={warningSpec} props={props} />\n}\n\n/* ---- 4. DangerCalendar ---- */\n\nconst dangerSpec: ToneSpec = {\n  tone: \"danger\",\n  root: \"border-danger/30 bg-[color-mix(in_oklab,var(--color-danger)_5%,var(--color-card))] text-card-foreground\",\n  title: \"text-danger\",\n  nav: \"text-danger hover:bg-danger-soft hover:text-danger-soft-foreground\",\n  weekday: \"text-danger/70\",\n  day: \"text-foreground hover:bg-danger-soft hover:text-danger-soft-foreground\",\n  selected: \"bg-danger text-danger-foreground hover:bg-danger hover:text-danger-foreground\",\n  outside: \"text-muted-foreground/50\",\n}\n\nexport function DangerCalendar(props: ToneCalendarProps) {\n  return <ToneCalendarBase spec={dangerSpec} props={props} />\n}\n\n/* ---- 5. MutedCalendar: notr, dusuk kontrastli ton ---- */\n\nconst mutedSpec: ToneSpec = {\n  tone: \"neutral\",\n  root: \"border-border bg-muted text-foreground\",\n  title: \"text-foreground\",\n  nav: \"text-muted-foreground hover:bg-background hover:text-foreground\",\n  weekday: \"text-muted-foreground\",\n  day: \"text-muted-foreground hover:bg-background hover:text-foreground\",\n  selected: \"bg-background text-foreground ring-1 ring-inset ring-border hover:bg-background hover:text-foreground\",\n  outside: \"text-muted-foreground/40\",\n}\n\nexport function MutedCalendar(props: ToneCalendarProps) {\n  return <ToneCalendarBase spec={mutedSpec} props={props} />\n}\n",
      "type": "registry:component",
      "target": "components/ui/calendar-tone.tsx"
    }
  ],
  "categories": [
    "styled",
    "calendar"
  ],
  "type": "registry:component"
}