{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "calendar-agenda",
  "title": "Calendar Agenda",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "A day agenda with native date entry, adjacent-day controls, and accessible event times.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.ballmac.com/r/i18n.json"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/calendar-agenda.tsx",
      "content": "// Ballmac UI: Calendar Agenda. https://ui.ballmac.com/components/calendar-agenda\n\"use client\"\n\nimport * as React from \"react\"\nimport { ChevronLeft, ChevronRight } from \"lucide-react\"\nimport { cn } from \"@/lib/utils\"\nimport { useLocale, useMessages } from \"@/lib/ballmac/i18n\"\n\ntype AgendaEvent = {\n  /** Stable event ID. */ id: string\n  /** ISO date (YYYY-MM-DD). */ date: string\n  /** 24-hour time (HH:mm), or omitted for all-day. */ time?: string\n  /** Event title. */ title: string\n  /** Optional supporting detail. */ description?: string\n  /** Optional category. */ label?: string\n}\ntype CalendarAgendaProps = Omit<React.ComponentProps<\"div\">, \"defaultValue\"> & {\n  /** Agenda events to display. */\n  events: AgendaEvent[]\n  /** Controlled selected ISO date. */\n  value?: string\n  /** Initial selected ISO date. */\n  defaultValue?: string\n  /** Called when the selected date changes. */\n  onValueChange?: (date: string) => void\n  /** Fixed locale for date and time labels. */\n  locale?: string\n  /** Empty-day message. */\n  emptyMessage?: string\n}\nfunction shiftDay(date: string, days: number) {\n  const parsed = new Date(`${date}T00:00:00Z`)\n  parsed.setUTCDate(parsed.getUTCDate() + days)\n  return parsed.toISOString().slice(0, 10)\n}\nfunction CalendarAgenda({\n  className,\n  events,\n  value,\n  defaultValue,\n  onValueChange,\n  locale,\n  emptyMessage,\n  ...props\n}: CalendarAgendaProps) {\n  const defaultLocale = useLocale()\n  locale ??= defaultLocale\n  const msg = useMessages()\n  emptyMessage ??= msg(\"calendar-agenda.emptyMessage\", \"No events scheduled\")\n  const [internal, setInternal] = React.useState(\n    defaultValue ?? events[0]?.date ?? \"\",\n  )\n  const selected = value ?? internal\n  const day = selected ? new Date(`${selected}T00:00:00Z`) : null\n  const heading =\n    day && !Number.isNaN(day.getTime())\n      ? new Intl.DateTimeFormat(locale, {\n          weekday: \"long\",\n          month: \"long\",\n          day: \"numeric\",\n          timeZone: \"UTC\",\n        }).format(day)\n      : \"Choose a date\"\n  const daily = events\n    .filter((event) => event.date === selected)\n    .sort((a, b) => (a.time ?? \"\").localeCompare(b.time ?? \"\"))\n  function choose(date: string) {\n    if (value === undefined) setInternal(date)\n    onValueChange?.(date)\n  }\n  return (\n    <div\n      data-slot=\"calendar-agenda\"\n      className={cn(\n        \"bg-card min-w-0 rounded-xl border border-border p-4 shadow-sm\",\n        className,\n      )}\n      {...props}\n    >\n      <div className=\"flex flex-wrap items-center justify-between gap-3 border-b border-border pb-4\">\n        <div>\n          <p className=\"text-muted-foreground text-xs font-medium uppercase tracking-wide\">\n            {msg(\"calendar-agenda.agenda\", \"Agenda\")}\n          </p>\n          <h3 className=\"mt-1 text-base font-semibold\">{heading}</h3>\n        </div>\n        <div className=\"flex items-center gap-1\">\n          <button\n            type=\"button\"\n            aria-label={msg(\"calendar-agenda.previousDay\", \"Previous day\")}\n            disabled={!selected}\n            onClick={() => choose(shiftDay(selected, -1))}\n            className=\"hover:bg-accent flex size-8 items-center justify-center rounded-md outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:opacity-40\"\n          >\n            <ChevronLeft aria-hidden=\"true\" className=\"size-4 rtl:rotate-180\" />\n          </button>\n          <input\n            type=\"date\"\n            aria-label={msg(\"calendar-agenda.chooseDate\", \"Choose date\")}\n            value={selected}\n            onChange={(event) => choose(event.target.value)}\n            className=\"border-input bg-background h-8 max-w-32 rounded-md border px-1 text-xs outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50\"\n          />\n          <button\n            type=\"button\"\n            aria-label={msg(\"calendar-agenda.nextDay\", \"Next day\")}\n            disabled={!selected}\n            onClick={() => choose(shiftDay(selected, 1))}\n            className=\"hover:bg-accent flex size-8 items-center justify-center rounded-md outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:opacity-40\"\n          >\n            <ChevronRight aria-hidden=\"true\" className=\"size-4 rtl:rotate-180\" />\n          </button>\n        </div>\n      </div>\n      <ol className=\"mt-3 flex flex-col divide-y divide-border\">\n        {daily.length ? (\n          daily.map((event) => (\n            <li\n              key={event.id}\n              data-slot=\"calendar-agenda-event\"\n              className=\"grid grid-cols-[4rem_minmax(0,1fr)] gap-3 py-3\"\n            >\n              <time\n                dateTime={\n                  event.time ? `${event.date}T${event.time}` : event.date\n                }\n                className=\"text-muted-foreground pt-0.5 text-xs tabular-nums\"\n              >\n                {event.time\n                  ? new Intl.DateTimeFormat(locale, {\n                      hour: \"numeric\",\n                      minute: \"2-digit\",\n                      timeZone: \"UTC\",\n                    }).format(new Date(`${event.date}T${event.time}:00Z`))\n                  : \"All day\"}\n              </time>\n              <div className=\"min-w-0 border-s-2 border-primary ps-3\">\n                <p className=\"text-sm font-medium\">{event.title}</p>\n                {event.description && (\n                  <p className=\"text-muted-foreground mt-1 text-xs\">\n                    {event.description}\n                  </p>\n                )}\n                {event.label && (\n                  <span className=\"text-primary mt-1 block text-xs font-medium\">\n                    {event.label}\n                  </span>\n                )}\n              </div>\n            </li>\n          ))\n        ) : (\n          <li\n            data-slot=\"calendar-agenda-empty\"\n            className=\"text-muted-foreground py-8 text-center text-sm\"\n          >\n            {emptyMessage}\n          </li>\n        )}\n      </ol>\n    </div>\n  )\n}\nexport { CalendarAgenda, type CalendarAgendaProps, type AgendaEvent }\n",
      "type": "registry:ui",
      "target": "@components/ballmac/calendar-agenda.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "calendar",
      "agenda",
      "events"
    ],
    "version": "1.0.0",
    "updated": "2026-09-30",
    "ai": {
      "summary": "A day agenda with native date entry, adjacent-day controls, and accessible event times.",
      "whenToUse": [
        "Show scheduled events for one day"
      ],
      "whenNotToUse": [
        "Use a full calendar for month-level date picking"
      ],
      "composesWith": [
        "timeline"
      ],
      "a11y": [
        {
          "keys": "Tab / Enter / Space",
          "action": "Changes the day through native date input and buttons"
        }
      ],
      "customization": [
        "Controlled and uncontrolled state",
        "Content and token-based className styling"
      ]
    },
    "examples": [
      "calendar-agenda-demo",
      "calendar-agenda-states"
    ],
    "url": "https://ui.ballmac.com/components/calendar-agenda"
  },
  "categories": [
    "data-display"
  ],
  "type": "registry:ui"
}