{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "calendar",
  "title": "Calendar",
  "description": "A full-size calendar for showing what is on: month grid with labels, a seven day strip, a grouped agenda or a whole year, with a keyboard grid and any locale.",
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "utils",
    "https://miseui.junsu4621.workers.dev/r/motion.json"
  ],
  "files": [
    {
      "path": "registry/miseui/ui/calendar.tsx",
      "content": "import type * as React from \"react\"\nimport { useMemo, useRef, useState } from \"react\"\nimport { ChevronLeft, ChevronRight } from \"lucide-react\"\nimport { AnimatePresence, motion, useReducedMotion, type Variants } from \"motion/react\"\nimport { SPRING_DEFAULT } from \"@/registry/miseui/lib/motion\"\nimport { firstDayOfWeek, startOfDay, type WeekStart } from \"@/registry/miseui/ui/date-picker-math\"\nimport { indexEvents, periodLabel, stepPeriod, type CalendarEvent, type CalendarVariant } from \"@/registry/miseui/ui/calendar-math\"\nimport { MonthView, WeekView } from \"@/registry/miseui/ui/calendar-grid\"\nimport { useCalendarView } from \"@/registry/miseui/ui/calendar-state\"\nimport { AgendaView, YearView, type CalendarLabels, type CalendarViewProps } from \"@/registry/miseui/ui/calendar-views\"\nimport { cn } from \"@/registry/miseui/lib/utils\"\n\ntype CalendarEffect = \"slide\"\n\nconst SLIDE_DISTANCE_PX = 16\nconst NAV_BUTTON_CLASS = cn(\n  \"flex size-8 items-center justify-center rounded-md text-muted-foreground outline-none pointer-coarse:size-10\",\n  \"transition-colors duration-(--duration-fast) ease-(--ease-mise) hover:bg-accent hover:text-foreground\",\n  \"focus-visible:ring-[3px] focus-visible:ring-ring/50 [&_svg]:size-4\",\n)\n\nconst DEFAULT_LABELS: CalendarLabels = {\n  previous: \"Previous\",\n  next: \"Next\",\n  today: \"Today\",\n  empty: \"Nothing scheduled this month.\",\n  noEvents: \"Nothing scheduled.\",\n  events: (count) => (count === 1 ? \"1 event\" : `${count} events`),\n  more: (count) => `${count} more`,\n}\n\n/** Shared-axis: the outgoing period leaves the way the new one comes from, so forward always reads as forward. */\nconst SLIDE_VARIANTS: Variants = {\n  enter: (direction: number) => ({ x: direction * SLIDE_DISTANCE_PX, opacity: 0 }),\n  centre: { x: 0, opacity: 1 },\n  exit: (direction: number) => ({ x: -direction * SLIDE_DISTANCE_PX, opacity: 0 }),\n}\n\nconst VIEWS: Record<CalendarVariant, (view: CalendarViewProps) => React.ReactNode> = {\n  month: MonthView,\n  week: WeekView,\n  agenda: AgendaView,\n  year: YearView,\n}\n\ntype RootAttributes = Omit<React.ComponentProps<\"div\">, \"children\" | \"defaultValue\">\n\ninterface CalendarProps extends RootAttributes {\n  events?: readonly CalendarEvent[]\n  /** `month` grid with labels, `week` seven day strip, `agenda` grouped list, `year` twelve small months. */\n  variant?: CalendarVariant\n  /** `slide` moves periods along a shared axis on a spring; a quick second press interrupts it. */\n  effect?: CalendarEffect\n  /** The active day. Which period is on screen follows from it. */\n  date?: Date\n  defaultDate?: Date\n  onDateChange?: (date: Date) => void\n  /** A day, or the first of a month in `year`, was chosen. Arrow keys alone do not fire it. */\n  onDateSelect?: (date: Date) => void\n  /** Makes events interactive. Without it they are plain labels. */\n  onEventSelect?: (event: CalendarEvent) => void\n  /** BCP 47 tag. Names, numbers and the first day of the week all follow it. */\n  locale?: string\n  /** Overrides the locale's first day of the week. 0 is Sunday. */\n  weekStartsOn?: WeekStart\n  /** Stands in for the clock, for tests and for a server that decides what today is. */\n  today?: Date\n  labels?: Partial<CalendarLabels>\n}\n\ninterface CalendarHeaderProps {\n  title: string\n  labels: CalendarLabels\n  onToday: () => void\n  onStep: (amount: number) => void\n}\n\nfunction CalendarHeader({ title, labels, onToday, onStep }: CalendarHeaderProps) {\n  return (\n    <header className=\"flex flex-wrap items-center gap-2\">\n      <h3 aria-live=\"polite\" className=\"mr-auto min-w-0 text-[15px] font-medium\">{title}</h3>\n      <button\n        type=\"button\"\n        className={cn(NAV_BUTTON_CLASS, \"w-auto px-2 text-[13px] text-foreground max-sm:pointer-coarse:hidden pointer-coarse:w-auto\")}\n        onClick={onToday}\n      >\n        {labels.today}\n      </button>\n      <div className=\"flex\">\n        <button type=\"button\" aria-label={labels.previous} className={NAV_BUTTON_CLASS} onClick={() => onStep(-1)}>\n          <ChevronLeft aria-hidden=\"true\" />\n        </button>\n        <button type=\"button\" aria-label={labels.next} className={NAV_BUTTON_CLASS} onClick={() => onStep(1)}>\n          <ChevronRight aria-hidden=\"true\" />\n        </button>\n      </div>\n    </header>\n  )\n}\n\ninterface CalendarStageProps {\n  viewKey: string\n  direction: number\n  slide: boolean\n  children: React.ReactNode\n}\n\nfunction CalendarStage({ viewKey, direction, slide, children }: CalendarStageProps) {\n  if (!slide) return <div data-view-key={viewKey} className=\"min-w-0\">{children}</div>\n  return (\n    <div className=\"relative min-w-0 overflow-hidden\">\n      <AnimatePresence initial={false} mode=\"popLayout\" custom={direction}>\n        <motion.div\n          key={viewKey}\n          data-view-key={viewKey}\n          custom={direction}\n          variants={SLIDE_VARIANTS}\n          initial=\"enter\"\n          animate=\"centre\"\n          exit=\"exit\"\n          transition={SPRING_DEFAULT}\n        >\n          {children}\n        </motion.div>\n      </AnimatePresence>\n    </div>\n  )\n}\n\nfunction Calendar({\n  events, variant = \"month\", effect, date, defaultDate, onDateChange, onDateSelect, onEventSelect,\n  locale, weekStartsOn, today, labels, className, ...props\n}: CalendarProps) {\n  const rootRef = useRef<HTMLDivElement>(null)\n  const reduceMotion = useReducedMotion() ?? false\n  const [now] = useState(() => startOfDay(new Date()))\n  const todayDay = today ? startOfDay(today) : now\n  const firstDay = useMemo(() => weekStartsOn ?? firstDayOfWeek(locale), [weekStartsOn, locale])\n  const state = useCalendarView({ variant, weekStartsOn: firstDay, date, defaultDate, today: todayDay, onDateChange }, rootRef)\n  const index = useMemo(() => indexEvents(events ?? []), [events])\n  const dayFormat = useMemo(() => new Intl.DateTimeFormat(locale, { dateStyle: \"full\" }), [locale])\n  const weekdayFormat = useMemo(() => new Intl.DateTimeFormat(locale, { weekday: \"short\" }), [locale])\n  const merged = { ...DEFAULT_LABELS, ...labels }\n  const title = periodLabel(state.date, variant, locale, firstDay)\n  const pick = (day: Date) => {\n    state.change(day)\n    onDateSelect?.(day)\n  }\n  const View = VIEWS[variant]\n  return (\n    <div ref={rootRef} data-slot=\"calendar\" data-variant={variant} data-effect={effect} className={cn(\"@container grid w-full min-w-0 gap-3\", className)} {...props}>\n      <CalendarHeader title={title} labels={merged} onToday={() => state.change(todayDay)} onStep={(amount) => state.change(stepPeriod(state.date, variant, amount))} />\n      <CalendarStage viewKey={state.viewKey} direction={state.direction} slide={effect === \"slide\" && !reduceMotion}>\n        <View\n          date={state.date} today={todayDay} title={title} index={index} locale={locale} weekStartsOn={firstDay} labels={merged}\n          dayFormat={dayFormat} weekdayFormat={weekdayFormat} onGridKeyDown={state.onGridKeyDown} onEventSelect={onEventSelect} onPick={pick}\n        />\n      </CalendarStage>\n    </div>\n  )\n}\n\nexport { Calendar, type CalendarProps, type CalendarEvent, type CalendarVariant, type CalendarEffect, type CalendarLabels }\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/miseui/ui/calendar-grid.tsx",
      "content": "import { useMemo } from \"react\"\nimport {\n  buildMonthGrid,\n  dateKey,\n  isSameDay,\n  isSameMonth,\n  weekdayLabels,\n} from \"@/registry/miseui/ui/date-picker-math\"\nimport { weekOf, type CalendarEvent } from \"@/registry/miseui/ui/calendar-math\"\nimport { EventRow, type CalendarViewProps } from \"@/registry/miseui/ui/calendar-views\"\nimport { cn } from \"@/registry/miseui/lib/utils\"\n\nconst MONTH_CHIPS = 2\nconst WEEK_CHIPS = 4\nconst MAX_MARKS = 3\n\n/** Today is the one filled circle on the screen; the active day only gets a quiet fill so the two never compete. */\nfunction dayNumberClass(today: boolean, active: boolean) {\n  if (today) return \"bg-mise font-medium text-mise-foreground\"\n  return active ? \"bg-accent font-medium\" : \"\"\n}\n\ninterface EventChipProps {\n  event: CalendarEvent\n  focusable: boolean\n  wrap: boolean\n  onEventSelect?: (event: CalendarEvent) => void\n}\n\n/** Without a handler it stays text and lets the click fall through to the day under it. */\nfunction EventChip({ event, focusable, wrap, onEventSelect }: EventChipProps) {\n  const className = cn(\n    \"block w-full rounded-sm bg-mise-soft px-1 text-left text-[13px] leading-5 text-foreground\",\n    wrap ? \"line-clamp-2 break-words\" : \"truncate\",\n  )\n  if (!onEventSelect) {\n    return <span data-slot=\"calendar-event\" className={cn(className, \"pointer-events-none\")}>{event.title}</span>\n  }\n  return (\n    <button\n      type=\"button\"\n      data-slot=\"calendar-event\"\n      tabIndex={focusable ? 0 : -1}\n      title={event.title}\n      className={cn(className, \"relative z-10 outline-none hover:bg-mise-soft/70 focus-visible:ring-[3px] focus-visible:ring-ring/50\")}\n      onClick={() => onEventSelect(event)}\n    >\n      {event.title}\n    </button>\n  )\n}\n\ninterface DayCellProps {\n  day: Date\n  outside: boolean\n  layout: \"month\" | \"week\"\n  view: CalendarViewProps\n}\n\nfunction DayButton({ day, outside, layout, view, eventCount }: DayCellProps & { eventCount: number }) {\n  const today = isSameDay(day, view.today)\n  const label = view.dayFormat.format(day)\n  return (\n    <button\n      type=\"button\"\n      data-date={dateKey(day)}\n      data-focus-key={dateKey(day)}\n      data-outside={outside ? \"true\" : undefined}\n      tabIndex={isSameDay(day, view.date) ? 0 : -1}\n      aria-label={eventCount ? `${label}, ${view.labels.events(eventCount)}` : label}\n      aria-current={today ? \"date\" : undefined}\n      className={cn(\n        \"group/day block w-full rounded-md text-[13px] outline-none after:absolute after:inset-0\",\n        layout === \"week\" && \"flex flex-col items-center gap-1 py-1\",\n      )}\n      onClick={() => view.onPick(day)}\n    >\n      {layout === \"week\" ? <span aria-hidden=\"true\" className=\"text-muted-foreground\">{view.weekdayFormat.format(day)}</span> : null}\n      <span\n        aria-hidden=\"true\"\n        className={cn(\n          \"flex size-6 items-center justify-center rounded-full tabular-nums\",\n          \"group-hover/day:bg-accent group-focus-visible/day:ring-[3px] group-focus-visible/day:ring-ring/50\",\n          outside && !today && \"text-muted-foreground\",\n          dayNumberClass(today, isSameDay(day, view.date)),\n          today && \"group-hover/day:bg-mise\",\n        )}\n      >\n        {day.getDate()}\n      </span>\n    </button>\n  )\n}\n\nfunction DayCell({ day, outside, layout, view }: DayCellProps) {\n  const events = view.index.get(dateKey(day)) ?? []\n  const active = isSameDay(day, view.date)\n  const limit = layout === \"month\" ? MONTH_CHIPS : WEEK_CHIPS\n  const hidden = events.length - limit\n  return (\n    <td\n      role=\"gridcell\"\n      aria-selected={active}\n      className={cn(\n        \"relative p-1 align-top\",\n        layout === \"month\" ? \"h-12 border-t border-l first:border-l-0 @xl:h-24\" : \"@xl:h-40\",\n        outside && \"bg-muted/50\",\n      )}\n    >\n      <DayButton day={day} outside={outside} layout={layout} view={view} eventCount={events.length} />\n      {events.length ? (\n        <span aria-hidden=\"true\" className=\"mt-1 flex justify-center gap-1 @xl:hidden\">\n          {events.slice(0, MAX_MARKS).map((event) => (\n            <span key={event.id} className=\"h-0.5 w-1 rounded-full bg-mise\" />\n          ))}\n        </span>\n      ) : null}\n      <div className=\"mt-1 hidden gap-1 @xl:grid\">\n        {events.slice(0, limit).map((event) => (\n          <EventChip key={event.id} event={event} focusable={active} wrap={layout === \"week\"} onEventSelect={view.onEventSelect} />\n        ))}\n        {hidden > 0 ? <span className=\"pointer-events-none px-1 text-[13px] leading-5 text-muted-foreground\">{view.labels.more(hidden)}</span> : null}\n      </div>\n    </td>\n  )\n}\n\n/** Phones have no room for labels in a cell, so the active day's events are listed under the grid instead. */\nfunction DayList({ view, limit }: { view: CalendarViewProps; limit: number }) {\n  const events = view.index.get(dateKey(view.date)) ?? []\n  return (\n    <section\n      data-slot=\"calendar-day-list\"\n      aria-label={view.dayFormat.format(view.date)}\n      className={cn(\"grid min-h-16 content-start gap-2 border-t pt-3\", events.length <= limit && \"@xl:hidden\")}\n    >\n      <p className=\"text-[13px] text-muted-foreground\">{view.dayFormat.format(view.date)}</p>\n      {events.length ? (\n        <ul className=\"grid gap-1\">\n          {events.map((event) => (\n            <li key={event.id}><EventRow event={event} onEventSelect={view.onEventSelect} /></li>\n          ))}\n        </ul>\n      ) : (\n        <p className=\"text-sm text-muted-foreground\">{view.labels.noEvents}</p>\n      )}\n    </section>\n  )\n}\n\nfunction MonthView(view: CalendarViewProps) {\n  const weeks = useMemo(() => buildMonthGrid(view.date, view.weekStartsOn), [view.date, view.weekStartsOn])\n  const weekdays = useMemo(() => weekdayLabels(view.locale, view.weekStartsOn), [view.locale, view.weekStartsOn])\n  return (\n    <div className=\"grid min-w-0 gap-3\">\n      <table role=\"grid\" aria-label={view.title} className=\"w-full table-fixed border-separate border-spacing-0 border-b\" onKeyDown={view.onGridKeyDown}>\n        <thead>\n          <tr>\n            {weekdays.map((weekday) => (\n              <th key={weekday.long} scope=\"col\" abbr={weekday.long} className=\"h-8 p-0 text-[13px] font-normal text-muted-foreground\">\n                {weekday.short}\n              </th>\n            ))}\n          </tr>\n        </thead>\n        <tbody>\n          {weeks.map((week) => (\n            <tr key={dateKey(week[0])}>\n              {week.map((day) => (\n                <DayCell key={dateKey(day)} day={day} outside={!isSameMonth(day, view.date)} layout=\"month\" view={view} />\n              ))}\n            </tr>\n          ))}\n        </tbody>\n      </table>\n      <DayList view={view} limit={MONTH_CHIPS} />\n    </div>\n  )\n}\n\nfunction WeekView(view: CalendarViewProps) {\n  const days = useMemo(() => weekOf(view.date, view.weekStartsOn), [view.date, view.weekStartsOn])\n  return (\n    <div className=\"grid min-w-0 gap-3\">\n      <table role=\"grid\" aria-label={view.title} className=\"w-full table-fixed border-separate border-spacing-0\" onKeyDown={view.onGridKeyDown}>\n        <tbody>\n          <tr>\n            {days.map((day) => (\n              <DayCell key={dateKey(day)} day={day} outside={false} layout=\"week\" view={view} />\n            ))}\n          </tr>\n        </tbody>\n      </table>\n      <DayList view={view} limit={WEEK_CHIPS} />\n    </div>\n  )\n}\n\nexport { MonthView, WeekView }\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/miseui/ui/calendar-views.tsx",
      "content": "import type * as React from \"react\"\nimport { useMemo } from \"react\"\nimport { buildMonthGrid, dateKey, isSameDay, isSameMonth, monthKey, type WeekStart } from \"@/registry/miseui/ui/date-picker-math\"\nimport {\n  agendaGroups,\n  eventsInMonth,\n  monthsOfYear,\n  type CalendarEvent,\n  type EventIndex,\n} from \"@/registry/miseui/ui/calendar-math\"\nimport { cn } from \"@/registry/miseui/lib/utils\"\n\ninterface CalendarLabels {\n  previous: string\n  next: string\n  today: string\n  /** Shown by `agenda` when the month has nothing in it. */\n  empty: string\n  /** Shown under the grid when the active day has nothing in it. */\n  noEvents: string\n  events: (count: number) => string\n  more: (count: number) => string\n}\n\ninterface CalendarViewProps {\n  date: Date\n  today: Date\n  title: string\n  index: EventIndex\n  locale?: string\n  weekStartsOn: WeekStart\n  labels: CalendarLabels\n  dayFormat: Intl.DateTimeFormat\n  weekdayFormat: Intl.DateTimeFormat\n  onPick: (day: Date) => void\n  onEventSelect?: (event: CalendarEvent) => void\n  onGridKeyDown: (event: React.KeyboardEvent) => void\n}\n\ninterface EventRowProps {\n  event: CalendarEvent\n  onEventSelect?: (event: CalendarEvent) => void\n}\n\nfunction EventRow({ event, onEventSelect }: EventRowProps) {\n  const body = (\n    <>\n      <span className=\"block text-sm break-words\">{event.title}</span>\n      {event.detail ? <span className=\"block text-[13px] break-words text-muted-foreground\">{event.detail}</span> : null}\n    </>\n  )\n  if (!onEventSelect) return <div data-slot=\"calendar-event\" className=\"min-w-0 py-1\">{body}</div>\n  return (\n    <button\n      type=\"button\"\n      data-slot=\"calendar-event\"\n      className={cn(\n        \"block min-h-10 w-full min-w-0 rounded-md px-2 py-1 text-left outline-none\",\n        \"transition-colors duration-(--duration-fast) ease-(--ease-mise) hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/50\",\n      )}\n      onClick={() => onEventSelect(event)}\n    >\n      {body}\n    </button>\n  )\n}\n\nfunction AgendaView({ date, today, index, labels, locale, onEventSelect }: CalendarViewProps) {\n  const groups = useMemo(() => agendaGroups(index, date), [index, date])\n  const weekday = useMemo(() => new Intl.DateTimeFormat(locale, { weekday: \"short\" }), [locale])\n  const fullDay = useMemo(() => new Intl.DateTimeFormat(locale, { dateStyle: \"full\" }), [locale])\n  if (groups.length === 0) return <p className=\"py-8 text-center text-sm text-muted-foreground\">{labels.empty}</p>\n  return (\n    <ol className=\"grid min-w-0\">\n      {groups.map((group) => {\n        const isToday = isSameDay(group.day, today)\n        return (\n          <li key={dateKey(group.day)} className=\"grid grid-cols-[48px_minmax(0,1fr)] gap-4 border-t py-3\">\n            <div aria-label={fullDay.format(group.day)} role=\"group\" className=\"grid content-start justify-items-start gap-1\">\n              <span aria-hidden=\"true\" className=\"text-[13px] text-muted-foreground\">{weekday.format(group.day)}</span>\n              <span\n                aria-hidden=\"true\"\n                className={cn(\n                  \"flex size-8 items-center justify-center rounded-full text-base font-medium tabular-nums\",\n                  isToday && \"bg-mise text-mise-foreground\",\n                )}\n              >\n                {group.day.getDate()}\n              </span>\n            </div>\n            <ul className=\"grid min-w-0 content-start gap-1\">\n              {group.events.map((event) => (\n                <li key={event.id} className=\"min-w-0\"><EventRow event={event} onEventSelect={onEventSelect} /></li>\n              ))}\n            </ul>\n          </li>\n        )\n      })}\n    </ol>\n  )\n}\n\ninterface MiniMonthProps {\n  month: Date\n  view: CalendarViewProps\n  monthFormat: Intl.DateTimeFormat\n  titleFormat: Intl.DateTimeFormat\n}\n\nfunction miniDayClass(day: Date, month: Date, view: CalendarViewProps) {\n  if (!isSameMonth(day, month)) return \"invisible\"\n  if (isSameDay(day, view.today)) return \"rounded-sm bg-mise font-medium text-mise-foreground\"\n  return view.index.get(dateKey(day))?.length ? \"rounded-sm bg-mise-soft font-medium text-foreground\" : \"text-muted-foreground\"\n}\n\nfunction MiniMonth({ month, view, monthFormat, titleFormat }: MiniMonthProps) {\n  const weeks = useMemo(() => buildMonthGrid(month, view.weekStartsOn), [month, view.weekStartsOn])\n  const count = eventsInMonth(view.index, month)\n  const current = isSameMonth(month, view.date)\n  const name = titleFormat.format(month)\n  return (\n    <li className=\"relative min-w-0 rounded-md p-2 transition-colors duration-(--duration-fast) ease-(--ease-mise) hover:bg-accent\">\n      <button\n        type=\"button\"\n        data-focus-key={monthKey(month)}\n        tabIndex={current ? 0 : -1}\n        aria-current={current ? \"true\" : undefined}\n        aria-label={count ? `${name}, ${view.labels.events(count)}` : name}\n        className=\"mb-1 block rounded-sm text-left text-[13px] font-medium outline-none after:absolute after:inset-0 after:rounded-md focus-visible:after:ring-[3px] focus-visible:after:ring-ring/50\"\n        onClick={() => view.onPick(month)}\n      >\n        {monthFormat.format(month)}\n      </button>\n      {/* A thumbnail: the button above already says what it holds, and 42 extra stops would drown a screen reader. */}\n      <div aria-hidden=\"true\" className=\"grid grid-cols-7 text-center text-[10px] leading-4 tabular-nums\">\n        {weeks.flat().map((day) => (\n          <span key={dateKey(day)} className={miniDayClass(day, month, view)}>{day.getDate()}</span>\n        ))}\n      </div>\n    </li>\n  )\n}\n\nfunction YearView(view: CalendarViewProps) {\n  const months = useMemo(() => monthsOfYear(view.date), [view.date])\n  const monthFormat = useMemo(() => new Intl.DateTimeFormat(view.locale, { month: \"long\" }), [view.locale])\n  const titleFormat = useMemo(() => new Intl.DateTimeFormat(view.locale, { month: \"long\", year: \"numeric\" }), [view.locale])\n  return (\n    <ul data-slot=\"calendar-year\" className=\"grid min-w-0 grid-cols-2 gap-2 @lg:grid-cols-3 @3xl:grid-cols-4\" onKeyDown={view.onGridKeyDown}>\n      {months.map((month) => (\n        <MiniMonth key={monthKey(month)} month={month} view={view} monthFormat={monthFormat} titleFormat={titleFormat} />\n      ))}\n    </ul>\n  )\n}\n\nexport { AgendaView, EventRow, YearView, type CalendarLabels, type CalendarViewProps }\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/miseui/ui/calendar-math.ts",
      "content": "import {\n  addDays,\n  addMonths,\n  dateKey,\n  monthKey,\n  startOfDay,\n  startOfMonth,\n  type WeekStart,\n} from \"@/registry/miseui/ui/date-picker-math\"\n\ntype CalendarVariant = \"month\" | \"week\" | \"agenda\" | \"year\"\n\ninterface CalendarEvent {\n  id: string\n  title: string\n  /** The day it happens, or its first day when `end` is set. */\n  date: Date\n  /** Last day of a multi-day event, inclusive. */\n  end?: Date\n  /** A second line in lists, such as a place or an owner. */\n  detail?: string\n}\n\ntype EventIndex = ReadonlyMap<string, readonly CalendarEvent[]>\n\nconst DAY_MS = 86_400_000\nconst DAYS_IN_WEEK = 7\nconst MONTHS_IN_YEAR = 12\n/** A mistyped end year would otherwise expand one event into thousands of day entries. */\nconst MAX_EVENT_SPAN_DAYS = 92\n\nfunction eventDays(event: CalendarEvent) {\n  const start = startOfDay(event.date)\n  const end = event.end ? startOfDay(event.end) : start\n  // Rounded because a span across a daylight saving change is not a whole number of 24 hour days.\n  const span = Math.round((end.getTime() - start.getTime()) / DAY_MS)\n  const length = Math.min(Math.max(span, 0), MAX_EVENT_SPAN_DAYS) + 1\n  return Array.from({ length }, (_, offset) => addDays(start, offset))\n}\n\n/** Keyed by day so every cell on screen can ask for its events without scanning the whole list. */\nfunction indexEvents(events: readonly CalendarEvent[]): EventIndex {\n  const index = new Map<string, readonly CalendarEvent[]>()\n  events.forEach((event) => {\n    eventDays(event).forEach((day) => {\n      const key = dateKey(day)\n      index.set(key, [...(index.get(key) ?? []), event])\n    })\n  })\n  return index\n}\n\nfunction weekOf(date: Date, weekStartsOn: WeekStart) {\n  const lead = (date.getDay() - weekStartsOn + DAYS_IN_WEEK) % DAYS_IN_WEEK\n  const start = addDays(date, -lead)\n  return Array.from({ length: DAYS_IN_WEEK }, (_, offset) => addDays(start, offset))\n}\n\nfunction stepPeriod(date: Date, variant: CalendarVariant, amount: number) {\n  if (variant === \"week\") return addDays(date, DAYS_IN_WEEK * amount)\n  return addMonths(date, variant === \"year\" ? MONTHS_IN_YEAR * amount : amount)\n}\n\n/** What one screen of this variant shows. A change of key is what triggers the slide. */\nfunction periodKey(date: Date, variant: CalendarVariant, weekStartsOn: WeekStart) {\n  if (variant === \"week\") return dateKey(weekOf(date, weekStartsOn)[0])\n  return variant === \"year\" ? String(date.getFullYear()) : monthKey(date)\n}\n\nfunction periodLabel(date: Date, variant: CalendarVariant, locale: string | undefined, weekStartsOn: WeekStart) {\n  if (variant === \"year\") return new Intl.DateTimeFormat(locale, { year: \"numeric\" }).format(date)\n  if (variant !== \"week\") return new Intl.DateTimeFormat(locale, { month: \"long\", year: \"numeric\" }).format(date)\n  const week = weekOf(date, weekStartsOn)\n  const format = new Intl.DateTimeFormat(locale, { month: \"short\", day: \"numeric\", year: \"numeric\" })\n  return format.formatRange(week[0], week[DAYS_IN_WEEK - 1])\n}\n\nfunction agendaGroups(index: EventIndex, month: Date) {\n  const first = startOfMonth(month)\n  const length = new Date(first.getFullYear(), first.getMonth() + 1, 0).getDate()\n  return Array.from({ length }, (_, offset) => addDays(first, offset)).flatMap((day) => {\n    const events = index.get(dateKey(day))\n    return events?.length ? [{ day, events }] : []\n  })\n}\n\nfunction monthsOfYear(date: Date) {\n  return Array.from({ length: MONTHS_IN_YEAR }, (_, month) => new Date(date.getFullYear(), month, 1))\n}\n\nfunction eventsInMonth(index: EventIndex, month: Date) {\n  const unique = new Set(agendaGroups(index, month).flatMap((group) => group.events.map((event) => event.id)))\n  return unique.size\n}\n\nexport {\n  agendaGroups,\n  eventsInMonth,\n  indexEvents,\n  monthsOfYear,\n  periodKey,\n  periodLabel,\n  stepPeriod,\n  weekOf,\n  type CalendarEvent,\n  type CalendarVariant,\n  type EventIndex,\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/miseui/ui/calendar-state.ts",
      "content": "import type * as React from \"react\"\nimport { useEffect, useRef, useState } from \"react\"\nimport { addMonths, dateKey, monthKey, nextFocusDate, startOfDay, type WeekStart } from \"@/registry/miseui/ui/date-picker-math\"\nimport { periodKey, type CalendarVariant } from \"@/registry/miseui/ui/calendar-math\"\n\nfunction yearKeyTarget(key: string, date: Date, target: HTMLElement) {\n  const list = target.closest(\"[data-slot='calendar-year']\")\n  // Reads the live column count, because the grid changes from two to four columns with the container.\n  const columns = list ? getComputedStyle(list).gridTemplateColumns.split(\" \").length : 1\n  const moves: Record<string, () => Date> = {\n    ArrowLeft: () => addMonths(date, -1),\n    ArrowRight: () => addMonths(date, 1),\n    ArrowUp: () => addMonths(date, -columns),\n    ArrowDown: () => addMonths(date, columns),\n    Home: () => new Date(date.getFullYear(), 0, 1),\n    End: () => new Date(date.getFullYear(), 11, 1),\n  }\n  return moves[key]?.() ?? null\n}\n\ninterface CalendarViewStateOptions {\n  variant: CalendarVariant\n  weekStartsOn: WeekStart\n  date?: Date\n  defaultDate?: Date\n  today: Date\n  onDateChange?: (date: Date) => void\n}\n\nfunction useCalendarView(options: CalendarViewStateOptions, rootRef: React.RefObject<HTMLDivElement | null>) {\n  const { variant, weekStartsOn, today, onDateChange } = options\n  const [uncontrolled, setUncontrolled] = useState(() => startOfDay(options.defaultDate ?? today))\n  const [direction, setDirection] = useState(1)\n  const pendingFocus = useRef(false)\n  const date = options.date ? startOfDay(options.date) : uncontrolled\n  const time = date.getTime()\n  const key = periodKey(date, variant, weekStartsOn)\n  const focusKey = variant === \"year\" ? monthKey(date) : dateKey(date)\n\n  useEffect(() => {\n    if (!pendingFocus.current) return\n    pendingFocus.current = false\n    // Scoped to the current period: during a slide the outgoing one is still mounted and may hold the same day.\n    rootRef.current?.querySelector<HTMLElement>(`[data-view-key='${key}'] [data-focus-key='${focusKey}']`)?.focus()\n  }, [time, key, focusKey, rootRef])\n\n  const change = (next: Date, moveFocus = false) => {\n    pendingFocus.current = moveFocus\n    setDirection(next.getTime() < time ? -1 : 1)\n    setUncontrolled(next)\n    onDateChange?.(next)\n  }\n  const onGridKeyDown = (event: React.KeyboardEvent) => {\n    const target = event.target as HTMLElement\n    if (!target.dataset.focusKey) return\n    const next = variant === \"year\"\n      ? yearKeyTarget(event.key, date, target)\n      : nextFocusDate(event.key, date, event.shiftKey, weekStartsOn)\n    if (!next) return\n    event.preventDefault()\n    change(next, true)\n  }\n  return { date, direction, viewKey: key, change, onGridKeyDown }\n}\n\nexport { useCalendarView }\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/miseui/ui/date-picker-math.ts",
      "content": "/** Every date here is local midnight. `toISOString` is avoided throughout because it shifts the day in any zone east of UTC. */\nexport interface DateRange {\n  start: Date\n  /** Null while the second click is still to come. */\n  end: Date | null\n}\n\nexport type WeekStart = 0 | 1 | 2 | 3 | 4 | 5 | 6\nexport type RangeRole = \"single\" | \"start\" | \"end\" | \"middle\"\n\nconst WEEK_ROWS = 6\nconst DAYS_IN_WEEK = 7\nconst SUNDAY_FIRST_REGIONS = new Set([\"US\", \"CA\", \"MX\", \"JP\", \"KR\", \"TW\", \"IL\", \"BR\", \"ZA\", \"PH\", \"IN\", \"AU\", \"HK\", \"SG\", \"TH\"])\n\nfunction startOfDay(date: Date) {\n  return new Date(date.getFullYear(), date.getMonth(), date.getDate())\n}\n\nfunction startOfMonth(date: Date) {\n  return new Date(date.getFullYear(), date.getMonth(), 1)\n}\n\n/** Built from parts and not by adding 24 hours, which breaks on the two days a year with daylight saving changes. */\nfunction addDays(date: Date, amount: number) {\n  return new Date(date.getFullYear(), date.getMonth(), date.getDate() + amount)\n}\n\n/** Jan 31 plus a month is Feb 28, not Mar 3. */\nfunction addMonths(date: Date, amount: number) {\n  const first = new Date(date.getFullYear(), date.getMonth() + amount, 1)\n  const lastDay = new Date(first.getFullYear(), first.getMonth() + 1, 0).getDate()\n  return new Date(first.getFullYear(), first.getMonth(), Math.min(date.getDate(), lastDay))\n}\n\nfunction isSameDay(a: Date | null | undefined, b: Date | null | undefined) {\n  return Boolean(a && b) && a?.getTime() === b?.getTime()\n}\n\nfunction isSameMonth(a: Date, b: Date) {\n  return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth()\n}\n\nfunction pad(value: number) {\n  return String(value).padStart(2, \"0\")\n}\n\nfunction monthKey(date: Date) {\n  return `${date.getFullYear()}-${pad(date.getMonth() + 1)}`\n}\n\nfunction dateKey(date: Date) {\n  return `${monthKey(date)}-${pad(date.getDate())}`\n}\n\nfunction clampDay(date: Date, min?: Date, max?: Date) {\n  if (min && date < min) return min\n  if (max && date > max) return max\n  return date\n}\n\n/** Always six weeks, so the calendar keeps one height and the page does not jump between months. */\nfunction buildMonthGrid(month: Date, weekStartsOn: WeekStart) {\n  const first = startOfMonth(month)\n  const lead = (first.getDay() - weekStartsOn + DAYS_IN_WEEK) % DAYS_IN_WEEK\n  const start = addDays(first, -lead)\n  return Array.from({ length: WEEK_ROWS }, (_, week) =>\n    Array.from({ length: DAYS_IN_WEEK }, (_, day) => addDays(start, week * DAYS_IN_WEEK + day)),\n  )\n}\n\n/** weekInfo where the browser has it, a short list of regions where it does not (Firefox). */\nfunction firstDayOfWeek(locale?: string): WeekStart {\n  try {\n    const tag = locale ?? new Intl.DateTimeFormat().resolvedOptions().locale\n    const info = new Intl.Locale(tag) as Intl.Locale & {\n      getWeekInfo?: () => { firstDay: number }\n      weekInfo?: { firstDay: number }\n    }\n    const firstDay = info.getWeekInfo?.().firstDay ?? info.weekInfo?.firstDay\n    if (firstDay) return (firstDay % DAYS_IN_WEEK) as WeekStart\n    return SUNDAY_FIRST_REGIONS.has(info.maximize().region ?? \"\") ? 0 : 1\n  } catch {\n    return 0\n  }\n}\n\nfunction weekdayLabels(locale: string | undefined, weekStartsOn: WeekStart) {\n  const short = new Intl.DateTimeFormat(locale, { weekday: \"short\" })\n  const long = new Intl.DateTimeFormat(locale, { weekday: \"long\" })\n  // 2023-01-01 was a Sunday, which makes the offset from weekStartsOn direct.\n  return Array.from({ length: DAYS_IN_WEEK }, (_, index) => {\n    const day = new Date(2023, 0, 1 + weekStartsOn + index)\n    return { short: short.format(day), long: long.format(day) }\n  })\n}\n\n/** Where each grid key takes the focus. Null when the key is not a grid key. */\nfunction nextFocusDate(key: string, day: Date, shiftKey: boolean, weekStartsOn: WeekStart) {\n  const intoWeek = (day.getDay() - weekStartsOn + DAYS_IN_WEEK) % DAYS_IN_WEEK\n  const moves: Record<string, () => Date> = {\n    ArrowLeft: () => addDays(day, -1),\n    ArrowRight: () => addDays(day, 1),\n    ArrowUp: () => addDays(day, -DAYS_IN_WEEK),\n    ArrowDown: () => addDays(day, DAYS_IN_WEEK),\n    Home: () => addDays(day, -intoWeek),\n    End: () => addDays(day, DAYS_IN_WEEK - 1 - intoWeek),\n    PageUp: () => addMonths(day, shiftKey ? -12 : -1),\n    PageDown: () => addMonths(day, shiftKey ? 12 : 1),\n  }\n  return moves[key]?.() ?? null\n}\n\n/** First click starts, second click finishes in whichever order, third click starts over. */\nfunction nextRange(current: DateRange | null, day: Date): DateRange {\n  if (!current || current.end) return { start: day, end: null }\n  return day < current.start ? { start: day, end: current.start } : { start: current.start, end: day }\n}\n\n/** `preview` stands in for the missing end while someone is still choosing it. */\nfunction rangeRole(day: Date, range: DateRange | null, preview: Date | null): RangeRole | null {\n  if (!range) return null\n  const other = range.end ?? preview ?? range.start\n  const [low, high] = other < range.start ? [other, range.start] : [range.start, other]\n  if (day < low || day > high) return null\n  if (isSameDay(low, high)) return \"single\"\n  if (isSameDay(day, low)) return \"start\"\n  return isSameDay(day, high) ? \"end\" : \"middle\"\n}\n\nfunction numericFormat(locale?: string) {\n  return new Intl.DateTimeFormat(locale, { year: \"numeric\", month: \"2-digit\", day: \"2-digit\" })\n}\n\n/** The placeholder, in the order this locale writes dates: MM/DD/YYYY, DD/MM/YYYY, YYYY. MM. DD. */\nfunction datePattern(locale?: string) {\n  const names: Record<string, string> = { year: \"YYYY\", month: \"MM\", day: \"DD\" }\n  return numericFormat(locale)\n    .formatToParts(new Date(2000, 0, 31))\n    .map((part) => names[part.type] ?? part.value)\n    .join(\"\")\n}\n\n/** Accepts any separators and 2-digit years. A leading 4-digit number always means year first, as in ISO. */\nfunction parseDateText(text: string, locale?: string) {\n  const numbers = text.match(/\\d+/g)\n  if (numbers?.length !== 3) return null\n  const localeOrder = numericFormat(locale)\n    .formatToParts(new Date(2000, 0, 31))\n    .map((part) => part.type)\n    .filter((type) => type === \"year\" || type === \"month\" || type === \"day\")\n  const order = numbers[0].length === 4 ? [\"year\", \"month\", \"day\"] : localeOrder\n  const parts = Object.fromEntries(order.map((type, index) => [type, Number(numbers[index])]))\n  const year = parts.year < 100 ? parts.year + 2000 : parts.year\n  const date = new Date(year, parts.month - 1, parts.day)\n  // new Date quietly turns Feb 30 into Mar 2, so a date that changed on the way through was never real.\n  return date.getMonth() === parts.month - 1 && date.getDate() === parts.day ? date : null\n}\n\nexport {\n  addDays,\n  addMonths,\n  buildMonthGrid,\n  clampDay,\n  dateKey,\n  datePattern,\n  firstDayOfWeek,\n  isSameDay,\n  isSameMonth,\n  monthKey,\n  nextFocusDate,\n  nextRange,\n  numericFormat,\n  parseDateText,\n  rangeRole,\n  startOfDay,\n  startOfMonth,\n  weekdayLabels,\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "display"
  ],
  "type": "registry:ui"
}