{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "calendar-week",
  "title": "Week calendar",
  "description": "Styled calendar: the Week set. 5 decorative calendar variations (WeekCalendar, DaysCalendar, AgendaCalendar, StripCalendar, RowCalendar) 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-week.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Week calendar family: 5 calendars offering week and agenda views instead of a month grid. Determinism: the visible week and month derive from a FIXED constant (January 2026), NOT from the clock; the agenda data is a hand-written fixed table. The week-based views jump a month with a double chevron and a week with a single one; the agenda and row views jump a month. Colour comes ONLY from semantic tokens. */\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 addDays(d: Date, n: number) {\n  const next = new Date(d.getFullYear(), d.getMonth(), d.getDate())\n  next.setDate(next.getDate() + n)\n  return next\n}\n\n/* Pazartesi baslangicli hafta basi. */\nfunction startOfWeek(d: Date) {\n  const offset = (d.getDay() + 6) % 7\n  return addDays(new Date(d.getFullYear(), d.getMonth(), d.getDate()), -offset)\n}\n\nfunction daysInMonth(d: Date) {\n  return new Date(d.getFullYear(), d.getMonth() + 1, 0).getDate()\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 buildWeek(anchor: Date): Date[] {\n  const start = startOfWeek(anchor)\n  const days: Date[] = []\n  for (let i = 0; i < 7; i++) days.push(addDays(start, i))\n  return days\n}\n\nfunction buildMonthGrid(month: Date): Date[] {\n  const first = startOfMonth(month)\n  const offset = (first.getDay() + 6) % 7\n  const start = addDays(first, -offset)\n  const days: Date[] = []\n  for (let i = 0; i < 42; i++) days.push(addDays(start, i))\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/* For week-based views: week and month steps. */\nfunction useVisibleWeek(defaultMonth?: Date) {\n  const [anchor, setAnchor] = React.useState<Date>(() =>\n    startOfWeek(startOfMonth(defaultMonth ?? FIXED_MONTH))\n  )\n  const prevWeek = React.useCallback(() => setAnchor((a) => addDays(a, -7)), [])\n  const nextWeek = React.useCallback(() => setAnchor((a) => addDays(a, 7)), [])\n  const prevMonth = React.useCallback(\n    () => setAnchor((a) => startOfWeek(addMonths(a, -1))),\n    []\n  )\n  const nextMonth = React.useCallback(\n    () => setAnchor((a) => startOfWeek(addMonths(a, 1))),\n    []\n  )\n  return { anchor, prevWeek, nextWeek, prevMonth, nextMonth }\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 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 rootBase =\n  \"rounded-xl border border-border bg-card p-3 text-card-foreground\"\n\nconst focusRing =\n  \"outline-none focus-visible:z-10 focus-visible:ring-[3px] focus-visible:ring-ring/50\"\n\nconst selectedFill =\n  \"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground\"\n\nexport interface WeekCalendarProps {\n  className?: string\n  size?: StyledSize\n  defaultMonth?: Date\n  defaultValue?: Date\n  onSelect?: (d: Date) => void\n}\n\n/* Hafta tabanli gorunumlerin ortak basligi: ay/hafta adimlari + gorunur ay adi. */\nfunction WeekHeader({\n  titleId,\n  anchor,\n  prevWeek,\n  nextWeek,\n  prevMonth,\n  nextMonth,\n}: {\n  titleId: string\n  anchor: Date\n  prevWeek: () => void\n  nextWeek: () => void\n  prevMonth: () => void\n  nextMonth: () => void\n}) {\n  return (\n    <div className=\"mb-2 flex items-center justify-between gap-1 px-0.5\">\n      <div className=\"flex items-center gap-0.5\">\n        <button type=\"button\" onClick={prevMonth} aria-label=\"Previous month\" className={navButton}>\n          <ChevronsLeft />\n        </button>\n        <button type=\"button\" onClick={prevWeek} aria-label=\"Previous week\" className={navButton}>\n          <ChevronLeft />\n        </button>\n      </div>\n      <div id={titleId} aria-live=\"polite\" className=\"text-sm font-medium\">\n        {MONTHS[anchor.getMonth()]} {anchor.getFullYear()}\n      </div>\n      <div className=\"flex items-center gap-0.5\">\n        <button type=\"button\" onClick={nextWeek} aria-label=\"Next week\" className={navButton}>\n          <ChevronRight />\n        </button>\n        <button type=\"button\" onClick={nextMonth} aria-label=\"Next month\" className={navButton}>\n          <ChevronsRight />\n        </button>\n      </div>\n    </div>\n  )\n}\n\n/* ---- 1. WeekCalendar: tek hafta, gun adi ustte, buyuk gun numarasi ---- */\n\nconst weekCellSizes: Record<StyledSize, string> = {\n  sm: \"h-12 text-sm\",\n  md: \"h-14 text-base\",\n  lg: \"h-16 text-lg\",\n  xl: \"h-20 text-xl\",\n}\n\nexport function WeekCalendar({\n  className,\n  size = \"md\",\n  defaultMonth,\n  defaultValue,\n  onSelect,\n}: WeekCalendarProps) {\n  const { anchor, prevWeek, nextWeek, prevMonth, nextMonth } = useVisibleWeek(defaultMonth)\n  const { selected, select } = useSelected(defaultValue, onSelect)\n  const titleId = React.useId()\n  const days = buildWeek(anchor)\n\n  return (\n    <div\n      data-slot=\"styled-calendar\"\n      role=\"grid\"\n      aria-labelledby={titleId}\n      className={cn(rootBase, \"inline-block\", className)}\n    >\n      <WeekHeader\n        titleId={titleId}\n        anchor={anchor}\n        prevWeek={prevWeek}\n        nextWeek={nextWeek}\n        prevMonth={prevMonth}\n        nextMonth={nextMonth}\n      />\n      <div role=\"row\" className=\"grid grid-cols-7 gap-1\">\n        {days.map((d, i) => {\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                \"flex min-w-11 flex-col items-center justify-center gap-1 rounded-lg font-medium text-foreground transition-colors duration-(--motion-fast) ease-(--motion-ease) hover:bg-accent hover:text-accent-foreground motion-reduce:transition-none\",\n                focusRing,\n                weekCellSizes[size],\n                isSelected && selectedFill\n              )}\n            >\n              <span\n                className={cn(\n                  \"text-[0.65rem] font-medium uppercase leading-none\",\n                  isSelected ? \"text-primary-foreground/80\" : \"text-muted-foreground\"\n                )}\n              >\n                {WEEKDAYS[i]}\n              </span>\n              <span className=\"leading-none tabular-nums\">{d.getDate()}</span>\n            </button>\n          )\n        })}\n      </div>\n    </div>\n  )\n}\n\n/* ---- 2. DaysCalendar: yatay kaydirilabilir gun hapleri ---- */\n\nconst dayPillSizes: Record<StyledSize, string> = {\n  sm: \"h-12 w-11 text-sm\",\n  md: \"h-14 w-12 text-base\",\n  lg: \"h-16 w-14 text-lg\",\n  xl: \"h-20 w-16 text-xl\",\n}\n\nexport function DaysCalendar({\n  className,\n  size = \"md\",\n  defaultMonth,\n  defaultValue,\n  onSelect,\n}: WeekCalendarProps) {\n  const { anchor, prevWeek, nextWeek, prevMonth, nextMonth } = useVisibleWeek(defaultMonth)\n  const { selected, select } = useSelected(defaultValue, onSelect)\n  const titleId = React.useId()\n  const days = buildWeek(anchor)\n\n  return (\n    <div\n      data-slot=\"styled-calendar\"\n      role=\"grid\"\n      aria-labelledby={titleId}\n      className={cn(rootBase, \"inline-block max-w-full\", className)}\n    >\n      <WeekHeader\n        titleId={titleId}\n        anchor={anchor}\n        prevWeek={prevWeek}\n        nextWeek={nextWeek}\n        prevMonth={prevMonth}\n        nextMonth={nextMonth}\n      />\n      <div role=\"row\" className=\"flex gap-1.5 overflow-x-auto pb-1\">\n        {days.map((d, i) => {\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                \"flex shrink-0 flex-col items-center justify-center gap-1 rounded-full border border-border font-medium text-foreground transition-colors duration-(--motion-fast) ease-(--motion-ease) hover:bg-accent hover:text-accent-foreground motion-reduce:transition-none\",\n                focusRing,\n                dayPillSizes[size],\n                isSelected && cn(selectedFill, \"border-primary\")\n              )}\n            >\n              <span\n                className={cn(\n                  \"text-[0.65rem] font-medium uppercase leading-none\",\n                  isSelected ? \"text-primary-foreground/80\" : \"text-muted-foreground\"\n                )}\n              >\n                {WEEKDAYS[i]}\n              </span>\n              <span className=\"leading-none tabular-nums\">{d.getDate()}</span>\n            </button>\n          )\n        })}\n      </div>\n    </div>\n  )\n}\n\n/* ---- 3. AgendaCalendar: gorunur ayin ajanda kayitlarini dikey listeler ---- */\n\n/* Sabit ajanda tablosu: ayin gunu -> baslik. Deterministik. */\nconst AGENDA: Record<number, string> = {\n  5: \"Design review\",\n  8: \"Sprint planning\",\n  13: \"Release cut\",\n  16: \"Customer call\",\n  21: \"Retrospective\",\n  27: \"Roadmap sync\",\n}\n\nconst agendaRowSizes: Record<StyledSize, string> = {\n  sm: \"gap-2 px-2 py-1.5 text-xs\",\n  md: \"gap-3 px-3 py-2 text-sm\",\n  lg: \"gap-3 px-3 py-2.5 text-sm\",\n  xl: \"gap-4 px-4 py-3 text-base\",\n}\n\nexport function AgendaCalendar({\n  className,\n  size = \"md\",\n  defaultMonth,\n  defaultValue,\n  onSelect,\n}: WeekCalendarProps) {\n  const { visible, goPrev, goNext } = useVisibleMonth(defaultMonth)\n  const { selected, select } = useSelected(defaultValue, onSelect)\n  const titleId = React.useId()\n  const total = daysInMonth(visible)\n  const entries = Object.keys(AGENDA)\n    .map((k) => Number(k))\n    .filter((day) => day <= total)\n    .sort((a, b) => a - b)\n    .map((day) => ({ day, date: new Date(visible.getFullYear(), visible.getMonth(), day) }))\n\n  return (\n    <div\n      data-slot=\"styled-calendar\"\n      role=\"grid\"\n      aria-labelledby={titleId}\n      className={cn(rootBase, \"inline-block w-full max-w-sm\", className)}\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 className=\"flex flex-col gap-1\">\n        {entries.map(({ day, date }) => {\n          const isSelected = isSameDay(date, selected)\n          return (\n            <div key={day} role=\"row\">\n              <button\n                type=\"button\"\n                role=\"gridcell\"\n                aria-label={ariaLabel(date)}\n                aria-selected={isSelected}\n                onClick={() => select(date)}\n                className={cn(\n                  \"flex w-full items-center rounded-lg text-left text-foreground transition-colors duration-(--motion-fast) ease-(--motion-ease) hover:bg-accent hover:text-accent-foreground motion-reduce:transition-none\",\n                  focusRing,\n                  agendaRowSizes[size],\n                  isSelected && selectedFill\n                )}\n              >\n                <span className=\"flex w-8 shrink-0 flex-col items-center\">\n                  <span\n                    className={cn(\n                      \"text-[0.65rem] font-medium uppercase leading-none\",\n                      isSelected ? \"text-primary-foreground/80\" : \"text-muted-foreground\"\n                    )}\n                  >\n                    {WEEKDAYS[(date.getDay() + 6) % 7]}\n                  </span>\n                  <span className=\"font-semibold leading-tight tabular-nums\">{day}</span>\n                </span>\n                <span\n                  aria-hidden=\"true\"\n                  className={cn(\n                    \"h-6 w-px shrink-0\",\n                    isSelected ? \"bg-primary-foreground/40\" : \"bg-border\"\n                  )}\n                />\n                <span className=\"truncate\">{AGENDA[day]}</span>\n              </button>\n            </div>\n          )\n        })}\n      </div>\n    </div>\n  )\n}\n\n/* ---- 4. StripCalendar: tek satirlik, cok sik hafta seridi ---- */\n\nconst stripCellSizes: Record<StyledSize, string> = {\n  sm: \"h-7 w-7 text-[0.7rem]\",\n  md: \"h-8 w-8 text-xs\",\n  lg: \"h-9 w-9 text-sm\",\n  xl: \"h-10 w-10 text-sm\",\n}\n\nexport function StripCalendar({\n  className,\n  size = \"md\",\n  defaultMonth,\n  defaultValue,\n  onSelect,\n}: WeekCalendarProps) {\n  const { anchor, prevWeek, nextWeek, prevMonth, nextMonth } = useVisibleWeek(defaultMonth)\n  const { selected, select } = useSelected(defaultValue, onSelect)\n  const titleId = React.useId()\n  const days = buildWeek(anchor)\n\n  return (\n    <div\n      data-slot=\"styled-calendar\"\n      role=\"grid\"\n      aria-labelledby={titleId}\n      className={cn(\n        \"inline-flex items-center gap-2 rounded-full border border-border bg-card px-2 py-1.5 text-card-foreground\",\n        className\n      )}\n    >\n      <div className=\"flex items-center\">\n        <button type=\"button\" onClick={prevMonth} aria-label=\"Previous month\" className={navButton}>\n          <ChevronsLeft />\n        </button>\n        <button type=\"button\" onClick={prevWeek} aria-label=\"Previous week\" className={navButton}>\n          <ChevronLeft />\n        </button>\n      </div>\n      <span id={titleId} aria-live=\"polite\" className=\"sr-only\">\n        {MONTHS[anchor.getMonth()]} {anchor.getFullYear()}\n      </span>\n      <div role=\"row\" className=\"flex items-center gap-0.5\">\n        {days.map((d) => {\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                \"inline-flex items-center justify-center rounded-full font-medium text-foreground tabular-nums transition-colors duration-(--motion-fast) ease-(--motion-ease) hover:bg-accent hover:text-accent-foreground motion-reduce:transition-none\",\n                focusRing,\n                stripCellSizes[size],\n                isSelected && selectedFill\n              )}\n            >\n              {d.getDate()}\n            </button>\n          )\n        })}\n      </div>\n      <div className=\"flex items-center\">\n        <button type=\"button\" onClick={nextWeek} aria-label=\"Next week\" className={navButton}>\n          <ChevronRight />\n        </button>\n        <button type=\"button\" onClick={nextMonth} aria-label=\"Next month\" className={navButton}>\n          <ChevronsRight />\n        </button>\n      </div>\n    </div>\n  )\n}\n\n/* ---- 5. RowCalendar: ay, hafta numarasi olugu olan satirlar halinde ---- */\n\nconst rowCellSizes: Record<StyledSize, string> = {\n  sm: \"h-8 text-xs\",\n  md: \"h-9 text-sm\",\n  lg: \"h-10 text-sm\",\n  xl: \"h-11 text-base\",\n}\n\nexport function RowCalendar({\n  className,\n  size = \"md\",\n  defaultMonth,\n  defaultValue,\n  onSelect,\n}: WeekCalendarProps) {\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      role=\"grid\"\n      aria-labelledby={titleId}\n      className={cn(rootBase, \"inline-block\", className)}\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-[2rem_repeat(7,minmax(0,1fr))] gap-1\">\n        <div role=\"columnheader\" className=\"sr-only\">\n          Week\n        </div>\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      <div className=\"flex flex-col gap-1\">\n        {weeks.map((week, wi) => (\n          <div\n            key={week[0].toISOString()}\n            role=\"row\"\n            className=\"grid grid-cols-[2rem_repeat(7,minmax(0,1fr))] items-center gap-1 rounded-lg transition-colors duration-(--motion-fast) ease-(--motion-ease) hover:bg-[color-mix(in_oklab,var(--color-foreground)_4%,transparent)] motion-reduce:transition-none\"\n          >\n            <div\n              role=\"rowheader\"\n              className=\"flex items-center justify-center text-[0.65rem] font-medium text-muted-foreground tabular-nums\"\n            >\n              W{wi + 1}\n            </div>\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                    \"inline-flex min-w-9 items-center justify-center rounded-md font-normal text-foreground tabular-nums transition-colors duration-(--motion-fast) ease-(--motion-ease) hover:bg-accent hover:text-accent-foreground motion-reduce:transition-none\",\n                    focusRing,\n                    rowCellSizes[size],\n                    outside && \"text-muted-foreground/50\",\n                    isSelected && selectedFill\n                  )}\n                >\n                  {d.getDate()}\n                </button>\n              )\n            })}\n          </div>\n        ))}\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/calendar-week.tsx"
    }
  ],
  "categories": [
    "styled",
    "calendar"
  ],
  "type": "registry:component"
}