{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "calendar-minimal",
  "title": "Minimal calendar",
  "description": "Styled calendar: the Minimal set. 5 decorative calendar variations (BareCalendar, GhostCalendar, ThinCalendar, DenseCalendar, CleanCalendar) 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-minimal.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ChevronLeft, ChevronRight } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Minimal calendar family: 5 plain month grids that pull the shell back (borderless, ghost, thin, tight, clean). 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. */\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 denseSizes: Record<StyledSize, string> = {\n  sm: \"size-6 text-[0.65rem]\",\n  md: \"size-7 text-xs\",\n  lg: \"size-8 text-xs\",\n  xl: \"size-9 text-sm\",\n}\n\nconst dayBase =\n  \"relative inline-flex items-center justify-center 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 text-muted-foreground outline-none transition-colors 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\ninterface MinimalSpec {\n  /** Kok yuzey. */\n  root: string\n  /** Baslik satiri. */\n  header: string\n  /** Baslik metni. */\n  title: string\n  /** Prev/next butonlari. */\n  nav: string\n  /** Hafta basliklari. */\n  weekday: string\n  /** Izgara bosluklari. */\n  gap: string\n  /** Secilmemis gun. */\n  day: string\n  /** Secili gun. */\n  selected: string\n  /** Ay disi gunler. */\n  outside: string\n  /** Hucre olcek tablosu (varsayilan cellSizes). */\n  cell?: Record<StyledSize, string>\n}\n\nexport interface MinimalCalendarProps {\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 MinimalSpec. */\nfunction MinimalCalendarBase({\n  spec,\n  props,\n}: {\n  spec: MinimalSpec\n  props: MinimalCalendarProps\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  const cell = spec.cell ?? cellSizes\n\n  return (\n    <div\n      data-slot=\"styled-calendar\"\n      role=\"grid\"\n      aria-labelledby={titleId}\n      className={cn(\"inline-block\", spec.root, className)}\n    >\n      <div className={cn(\"flex items-center justify-between gap-2\", spec.header)}>\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={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={cn(\"grid grid-cols-7\", spec.gap)}>\n        {WEEKDAYS.map((w) => (\n          <div\n            key={w}\n            role=\"columnheader\"\n            className={cn(\"flex items-center justify-center py-1\", spec.weekday)}\n          >\n            {w}\n          </div>\n        ))}\n      </div>\n      {weeks.map((week) => (\n        <div key={week[0].toISOString()} role=\"row\" className={cn(\"grid grid-cols-7\", spec.gap)}>\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                  cell[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. BareCalendar: no card, no border, only the grid ---- */\n\nconst bareSpec: MinimalSpec = {\n  root: \"bg-transparent text-foreground\",\n  header: \"mb-2 px-0.5\",\n  title: \"text-sm font-medium\",\n  nav: \"\",\n  weekday: \"text-xs font-medium text-muted-foreground\",\n  gap: \"gap-1\",\n  day: \"rounded-md text-foreground hover:bg-accent hover:text-accent-foreground\",\n  selected: \"bg-foreground text-background hover:bg-foreground hover:text-background\",\n  outside: \"text-muted-foreground/40\",\n}\n\nexport function BareCalendar(props: MinimalCalendarProps) {\n  return <MinimalCalendarBase spec={bareSpec} props={props} />\n}\n\n/* ---- 2. GhostCalendar: the selected day is only a light fill ---- */\n\nconst ghostSpec: MinimalSpec = {\n  root: \"rounded-xl bg-[color-mix(in_oklab,var(--color-foreground)_3%,transparent)] p-3 text-foreground\",\n  header: \"mb-2 px-0.5\",\n  title: \"text-sm font-medium\",\n  nav: \"\",\n  weekday: \"text-xs font-normal text-muted-foreground\",\n  gap: \"gap-1\",\n  day: \"rounded-lg text-muted-foreground hover:bg-[color-mix(in_oklab,var(--color-foreground)_6%,transparent)] hover:text-foreground\",\n  selected:\n    \"bg-[color-mix(in_oklab,var(--color-foreground)_10%,transparent)] font-medium text-foreground hover:bg-[color-mix(in_oklab,var(--color-foreground)_10%,transparent)] hover:text-foreground\",\n  outside: \"text-muted-foreground/40\",\n}\n\nexport function GhostCalendar(props: MinimalCalendarProps) {\n  return <MinimalCalendarBase spec={ghostSpec} props={props} />\n}\n\n/* ---- 3. ThinCalendar: ince kenarlik, secili gun ince halka ---- */\n\nconst thinSpec: MinimalSpec = {\n  root: \"rounded-lg border border-border bg-card p-3 text-card-foreground\",\n  header: \"mb-2 px-0.5\",\n  title: \"text-sm font-normal tracking-wide\",\n  nav: \"\",\n  weekday: \"text-[0.65rem] font-normal uppercase tracking-wider text-muted-foreground\",\n  gap: \"gap-1\",\n  day: \"rounded-full font-light text-foreground hover:bg-accent hover:text-accent-foreground\",\n  selected:\n    \"bg-transparent font-normal text-foreground ring-1 ring-foreground hover:bg-transparent hover:text-foreground\",\n  outside: \"text-muted-foreground/40\",\n}\n\nexport function ThinCalendar(props: MinimalCalendarProps) {\n  return <MinimalCalendarBase spec={thinSpec} props={props} />\n}\n\n/* ---- 4. DenseCalendar: kucuk hucreler, sifira yakin bosluk ---- */\n\nconst denseSpec: MinimalSpec = {\n  root: \"rounded-lg border border-border bg-card p-2 text-card-foreground\",\n  header: \"mb-1 px-0.5\",\n  title: \"text-xs font-medium\",\n  nav: \"\",\n  weekday: \"text-[0.6rem] font-medium text-muted-foreground\",\n  gap: \"gap-px\",\n  day: \"rounded-sm text-foreground hover:bg-accent hover:text-accent-foreground\",\n  selected: \"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground\",\n  outside: \"text-muted-foreground/40\",\n  cell: denseSizes,\n}\n\nexport function DenseCalendar(props: MinimalCalendarProps) {\n  return <MinimalCalendarBase spec={denseSpec} props={props} />\n}\n\n/* ---- 5. CleanCalendar: bol beyaz alan, alt cizgili baslik ---- */\n\nconst cleanSpec: MinimalSpec = {\n  root: \"rounded-2xl border border-border bg-background p-5 text-foreground\",\n  header: \"mb-3 border-b border-border pb-3\",\n  title: \"text-sm font-semibold tracking-tight\",\n  nav: \"\",\n  weekday: \"text-[0.65rem] font-medium uppercase tracking-wide text-muted-foreground\",\n  gap: \"gap-1.5\",\n  day: \"rounded-md text-foreground hover:bg-accent hover:text-accent-foreground\",\n  selected: \"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground\",\n  outside: \"text-muted-foreground/40\",\n}\n\nexport function CleanCalendar(props: MinimalCalendarProps) {\n  return <MinimalCalendarBase spec={cleanSpec} props={props} />\n}\n",
      "type": "registry:component",
      "target": "components/ui/calendar-minimal.tsx"
    }
  ],
  "categories": [
    "styled",
    "calendar"
  ],
  "type": "registry:component"
}