{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "scheduler",
  "type": "registry:component",
  "title": "Scheduler",
  "description": "Plans events in agenda, week, or month views with date navigation and accessible rescheduling.",
  "registryDependencies": [
    "https://vlak.dev/r/vlak-base.json",
    "https://vlak.dev/r/inter.json",
    "https://vlak.dev/r/button.json",
    "https://vlak.dev/r/icons.json",
    "https://vlak.dev/r/input.json",
    "https://vlak.dev/r/native-select.json",
    "https://vlak.dev/r/dialog.json",
    "https://vlak.dev/r/vlak-lib.json"
  ],
  "dependencies": [
    "@stylexjs/stylex"
  ],
  "devDependencies": [
    "@stylexjs/babel-plugin"
  ],
  "docs": "Vlak leaves are StyleX. Compile them with @stylexjs/babel-plugin (Vite: @stylexjs/unplugin, Next: @stylexjs/nextjs-plugin). If you would rather not run a compiler, import @noorddev/vlak-react instead: it ships precompiled with one stylesheet.",
  "files": [
    {
      "path": "vlak/scheduler.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as stylex from \"@stylexjs/stylex\";\nimport { vlak } from \"./tokens.stylex\";\nimport { rs } from \"./rs\";\nimport { Button } from \"./button\";\nimport { Icon } from \"./icons\";\nimport { Input } from \"./input\";\nimport { NativeSelect } from \"./native-select\";\nimport { Dialog, DialogTitle } from \"./dialog\";\n\nexport type SchedulerView = \"agenda\" | \"week\" | \"month\";\nexport interface SchedulerEvent { id: string; title: string; start: Date; end: Date; disabled?: boolean }\nexport interface SchedulerProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"defaultValue\"> {\n  events: readonly SchedulerEvent[];\n  value?: Date;\n  defaultValue?: Date;\n  onValueChange?: (day: Date) => void;\n  view?: SchedulerView;\n  defaultView?: SchedulerView;\n  onViewChange?: (view: SchedulerView) => void;\n  onEventSelect?: (event: SchedulerEvent) => void;\n  onSlotSelect?: (start: Date) => void;\n  onEventMove?: (event: SchedulerEvent, next: { start: Date; end: Date }) => void;\n  weekStart?: 0 | 1;\n  locale?: string;\n  /** IANA zone, for example Europe/Amsterdam. Defaults to the browser zone. */\n  timeZone?: string;\n  label?: string;\n  disabled?: boolean;\n}\nconst styles = stylex.create({\n  root: { display: \"flex\", flexDirection: \"column\", gap: \"1rem\", width: \"100%\", minWidth: 0, color: vlak.ink },\n  toolbar: { display: \"flex\", flexWrap: \"wrap\", alignItems: \"center\", gap: \"0.5rem\" },\n  action: { width: \"auto\", minWidth: vlak.hit, minHeight: vlak.hit, paddingInline: \"0.75rem\" },\n  title: { margin: 0, fontSize: \"1rem\", fontWeight: 600, lineHeight: 1.45, flex: \"1 1 12rem\" },\n  scroll: { overflowX: \"auto\", padding: \"0.25rem\", \":focus-visible\": { outlineWidth: 2, outlineStyle: \"solid\", outlineColor: vlak.ink, outlineOffset: -2 } },\n  week: { display: \"grid\", gridTemplateColumns: \"repeat(7, minmax(10rem, 1fr))\", minWidth: \"70rem\", gap: 0 },\n  day: { padding: \"0.75rem\", borderWidth: vlak.hairline, borderStyle: \"solid\", borderColor: vlak.divider, minWidth: 0 },\n  selected: { backgroundColor: vlak.controlFill },\n  date: { display: \"flex\", alignItems: \"center\", minWidth: vlak.hit, minHeight: vlak.hit, borderWidth: 0, borderRadius: vlak.radiusSm, backgroundColor: \"transparent\", color: vlak.ink, fontSize: \"0.8125rem\", fontWeight: 600, fontFamily: \"inherit\", cursor: \"pointer\", paddingInline: \"0.5rem\", \":focus-visible\": { outlineWidth: 2, outlineStyle: \"solid\", outlineColor: vlak.ink, outlineOffset: 2 } },\n  month: { width: \"100%\", minWidth: \"49rem\", tableLayout: \"fixed\", borderCollapse: \"collapse\" },\n  weekday: { padding: \"0.75rem\", fontSize: \"0.75rem\", fontWeight: 500, textAlign: \"start\", color: vlak.gray },\n  cell: { verticalAlign: \"top\", height: \"8rem\", padding: \"0.5rem\", borderWidth: vlak.hairline, borderStyle: \"solid\", borderColor: vlak.divider },\n  outside: { color: vlak.gray },\n  list: { listStyleType: \"none\", margin: 0, padding: 0, display: \"flex\", flexDirection: \"column\", gap: \"0.75rem\" },\n  event: { minWidth: 0, display: \"flex\", flexDirection: \"column\", gap: \"0.25rem\", paddingBlock: \"0.5rem\" },\n  eventButton: { display: \"flex\", flexDirection: \"column\", alignItems: \"flex-start\", gap: \"0.25rem\", width: \"100%\", minHeight: vlak.hit, padding: \"0.5rem\", borderWidth: vlak.hairline, borderStyle: \"solid\", borderColor: vlak.controlBorder, borderRadius: vlak.radiusSm, backgroundColor: vlak.paper, color: vlak.ink, fontSize: \"0.875rem\", fontFamily: \"inherit\", textAlign: \"start\", overflowWrap: \"anywhere\", cursor: \"pointer\", \":focus-visible\": { outlineWidth: 2, outlineStyle: \"solid\", outlineColor: vlak.ink, outlineOffset: 2 }, \":disabled\": { color: vlak.gray, cursor: \"not-allowed\" } },\n  time: { fontSize: \"0.75rem\", color: vlak.gray, lineHeight: 1.45, fontVariantNumeric: \"tabular-nums\" },\n  empty: { margin: 0, paddingBlock: \"0.75rem\", color: vlak.gray, fontSize: \"0.8125rem\", lineHeight: 1.45 },\n  form: { display: \"flex\", flexDirection: \"column\", gap: \"1rem\" },\n});\nconst validDate = (date: Date) => Number.isFinite(date.getTime());\n// UTC Dates below represent civil calendar days, not event instants. This keeps\n// calendar arithmetic independent from server zone and daylight-saving offsets.\nconst addDays = (date: Date, days: number) => new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate() + days));\nconst sameDay = (a: Date, b: Date) => a.getUTCFullYear() === b.getUTCFullYear() && a.getUTCMonth() === b.getUTCMonth() && a.getUTCDate() === b.getUTCDate();\nconst dateInput = (date: Date) => `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, \"0\")}-${String(date.getUTCDate()).padStart(2, \"0\")}`;\nconst zoneFormatters = new Map<string, Intl.DateTimeFormat>();\nfunction zoneParts(date: Date, timeZone: string) {\n  let formatter = zoneFormatters.get(timeZone);\n  if (!formatter) { formatter = new Intl.DateTimeFormat(\"en-GB\", { timeZone, year: \"numeric\", month: \"2-digit\", day: \"2-digit\", hour: \"2-digit\", minute: \"2-digit\", second: \"2-digit\", hourCycle: \"h23\" }); zoneFormatters.set(timeZone, formatter); }\n  const parts = Object.fromEntries(formatter.formatToParts(date).map(part => [part.type, part.value]));\n  return { year: Number(parts.year), month: Number(parts.month), day: Number(parts.day), hour: Number(parts.hour), minute: Number(parts.minute), second: Number(parts.second) };\n}\nfunction dayInZone(date: Date, timeZone: string): Date { const parts = zoneParts(date, timeZone); return new Date(Date.UTC(parts.year, parts.month - 1, parts.day)); }\nfunction timeInput(date: Date, timeZone: string): string { const parts = zoneParts(date, timeZone); return `${String(parts.hour).padStart(2, \"0\")}:${String(parts.minute).padStart(2, \"0\")}`; }\nfunction atTime(day: Date, time: string, timeZone: string): Date | null {\n  const [hours, minutes] = time.split(\":\").map(Number);\n  if (!Number.isFinite(hours) || !Number.isFinite(minutes) || hours! < 0 || hours! > 23 || minutes! < 0 || minutes! > 59) return null;\n  const target = Date.UTC(day.getUTCFullYear(), day.getUTCMonth(), day.getUTCDate(), hours, minutes);\n  const civilTimestamp = (instant: number) => { const parts = zoneParts(new Date(instant), timeZone); return Date.UTC(parts.year, parts.month - 1, parts.day, parts.hour, parts.minute, parts.second); };\n  let guess = target;\n  for (let attempt = 0; attempt < 4; attempt++) {\n    const delta = target - civilTimestamp(guess);\n    if (delta === 0) {\n      // A repeated fall-back time selects its earlier valid occurrence.\n      for (const minutesEarlier of [120, 90, 60, 30]) { const earlier = guess - minutesEarlier * 60000; if (civilTimestamp(earlier) === target) return new Date(earlier); }\n      return new Date(guess);\n    }\n    guess += delta;\n  }\n  return null; // A spring-forward gap has no matching civil clock time.\n}\nfunction parseDay(value: string): Date | null { const [year, month, day] = value.split(\"-\").map(Number); if (!year || !month || !day) return null; const result = new Date(Date.UTC(year, month - 1, day)); return validDate(result) && result.getUTCMonth() === month - 1 && result.getUTCDate() === day ? result : null; }\n\n/** Agenda, week, and month planning in an explicit or browser-local zone. Mutations are callbacks. */\nexport const Scheduler = React.forwardRef<HTMLDivElement, SchedulerProps>(function Scheduler({ events, value, defaultValue, onValueChange, view, defaultView = \"week\", onViewChange, onEventSelect, onSlotSelect, onEventMove, weekStart = 1, locale = \"en\", timeZone, label = \"Schedule\", disabled = false, className, style, ...props }, ref) {\n  const zone = timeZone ?? Intl.DateTimeFormat().resolvedOptions().timeZone;\n  const [hydrated, setHydrated] = React.useState(false);\n  React.useEffect(() => setHydrated(true), []);\n  const [inner, setInner] = React.useState(() => defaultValue && validDate(defaultValue) ? defaultValue : new Date());\n  const selected = dayInZone(value && validDate(value) ? value : inner, zone);\n  const [innerView, setInnerView] = React.useState(defaultView);\n  const currentView = view ?? innerView;\n  const [slotTime, setSlotTime] = React.useState(\"09:00\");\n  const [editing, setEditing] = React.useState<SchedulerEvent | null>(null);\n  const [editDay, setEditDay] = React.useState(\"\");\n  const [editTime, setEditTime] = React.useState(\"\");\n  const [editError, setEditError] = React.useState(\"\");\n  const [announcement, setAnnouncement] = React.useState(\"\");\n  const changeDay = (day: Date) => { if (disabled || !validDate(day)) return; const instant = atTime(day, \"00:00\", zone) ?? atTime(day, \"12:00\", zone); if (!instant) return; if (value === undefined) setInner(instant); onValueChange?.(instant); };\n  const shift = (direction: number) => {\n    if (currentView === \"month\") { const first = new Date(Date.UTC(selected.getUTCFullYear(), selected.getUTCMonth() + direction, 1)); const last = new Date(Date.UTC(first.getUTCFullYear(), first.getUTCMonth() + 1, 0)).getUTCDate(); changeDay(new Date(Date.UTC(first.getUTCFullYear(), first.getUTCMonth(), Math.min(last, selected.getUTCDate())))); }\n    else changeDay(addDays(selected, direction * (currentView === \"week\" ? 7 : 1)));\n  };\n  const sorted = React.useMemo(() => events.filter(event => validDate(event.start) && validDate(event.end) && event.end > event.start).slice().sort((a, b) => a.start.getTime() - b.start.getTime()).map(event => ({ event, firstDay: dayInZone(event.start, zone), lastDay: dayInZone(new Date(event.end.getTime() - 1), zone) })), [events, zone]);\n  const forDay = (day: Date) => sorted.filter(item => item.firstDay <= day && item.lastDay >= day).map(item => item.event);\n  const timeFormat = (date: Date) => date.toLocaleTimeString(locale, { timeZone: zone, hour: \"2-digit\", minute: \"2-digit\" });\n  const range = (event: SchedulerEvent) => sameDay(dayInZone(event.start, zone), dayInZone(event.end, zone)) ? `${timeFormat(event.start)}–${timeFormat(event.end)}` : `${event.start.toLocaleString(locale, { timeZone: zone, month: \"short\", day: \"numeric\", hour: \"2-digit\", minute: \"2-digit\" })} – ${event.end.toLocaleString(locale, { timeZone: zone, month: \"short\", day: \"numeric\", hour: \"2-digit\", minute: \"2-digit\" })}`;\n  const weekday = (day: Date) => day.toLocaleDateString(locale, { timeZone: \"UTC\", weekday: \"short\" });\n  const dayLabel = (day: Date) => day.toLocaleDateString(locale, { timeZone: \"UTC\", weekday: \"long\", month: \"long\", day: \"numeric\", year: \"numeric\" });\n  const weekBeginning = addDays(selected, -((selected.getUTCDay() - weekStart + 7) % 7));\n  const week = Array.from({ length: 7 }, (_, index) => addDays(weekBeginning, index));\n  const firstOfMonth = new Date(Date.UTC(selected.getUTCFullYear(), selected.getUTCMonth(), 1));\n  const monthBeginning = addDays(firstOfMonth, -((firstOfMonth.getUTCDay() - weekStart + 7) % 7));\n  const titleText = currentView === \"month\" ? selected.toLocaleDateString(locale, { timeZone: \"UTC\", month: \"long\", year: \"numeric\" }) : currentView === \"agenda\" ? dayLabel(selected) : `${week[0]!.toLocaleDateString(locale, { timeZone: \"UTC\", month: \"short\", day: \"numeric\" })} – ${week[6]!.toLocaleDateString(locale, { timeZone: \"UTC\", month: \"short\", day: \"numeric\", year: \"numeric\" })}`;\n  const root = rs([\"rs-scheduler\", className], styles.root);\n  const toolbar = rs([\"rs-scheduler-toolbar\"], styles.toolbar);\n  const action = rs([\"rs-scheduler-action\"], styles.action);\n  const title = rs([\"rs-scheduler-title\"], styles.title);\n  const scroll = rs([\"rs-scheduler-scroll\"], styles.scroll);\n  const weekStyle = rs([\"rs-scheduler-week\"], styles.week);\n  const dateStyle = rs([\"rs-scheduler-date\"], styles.date);\n  const monthStyle = rs([\"rs-scheduler-month\"], styles.month);\n  const weekdayStyle = rs([\"rs-scheduler-weekday\"], styles.weekday);\n  const list = rs([\"rs-scheduler-list\"], styles.list);\n  const eventStyle = rs([\"rs-scheduler-event\"], styles.event);\n  const eventButton = rs([\"rs-scheduler-event-button\"], styles.eventButton);\n  const time = rs([\"rs-scheduler-time\"], styles.time);\n  const empty = rs([\"rs-scheduler-empty\"], styles.empty);\n  const form = rs([\"rs-scheduler-form\"], styles.form);\n  // Server and browser may have different zones or clocks. The named shell is\n  // stable on both; calendar dates are introduced only after hydration.\n  if (!hydrated) return <div ref={ref} role=\"region\" aria-label={label} {...props} className={root.className} style={{ ...root.style, ...style }}><p {...empty} role=\"status\">Loading schedule…</p></div>;\n  const eventList = (day: Date) => { const items = forDay(day); return items.length ? <ol {...list}>{items.map(event => <li {...eventStyle} key={event.id}>\n    {onEventSelect ? <button {...eventButton} type=\"button\" disabled={disabled || event.disabled} onClick={() => onEventSelect(event)}>{event.title}<span {...time}>{range(event)}</span></button> : <><span>{event.title}</span><span {...time}>{range(event)}</span></>}\n    {onEventMove && <Button {...action} variant=\"ghost\" disabled={disabled || event.disabled} aria-label={`Reschedule ${event.title}`} onClick={() => { setEditing(event); setEditError(\"\"); setEditDay(dateInput(dayInZone(event.start, zone))); setEditTime(timeInput(event.start, zone)); }}>Reschedule</Button>}\n  </li>)}</ol> : <p {...empty}>No events.</p>; };\n  const chooseSlot = (day: Date) => { const next = atTime(day, slotTime, zone); if (!next) { setAnnouncement(`That time does not exist in ${zone} because the clocks change. Choose another time.`); return; } if (!disabled) onSlotSelect?.(next); };\n  return <div ref={ref} role=\"region\" aria-label={label} {...props} className={root.className} style={{ ...root.style, ...style }}>\n    <div {...toolbar}><h3 {...title} aria-live=\"polite\">{titleText}</h3><Button {...action} variant=\"ghost\" aria-label={`Previous ${currentView === \"agenda\" ? \"day\" : currentView}`} disabled={disabled} onClick={() => shift(-1)}><Icon name=\"chevron-left\" /></Button><Button {...action} variant=\"ghost\" disabled={disabled} onClick={() => changeDay(dayInZone(new Date(), zone))}>Today</Button><Button {...action} variant=\"ghost\" aria-label={`Next ${currentView === \"agenda\" ? \"day\" : currentView}`} disabled={disabled} onClick={() => shift(1)}><Icon name=\"chevron-right\" /></Button></div>\n    <div {...toolbar}><Input type=\"date\" aria-label=\"Selected date\" value={dateInput(selected)} disabled={disabled} onChange={event => { const day = parseDay(event.target.value); if (day) changeDay(day); }} /><NativeSelect aria-label=\"Schedule view\" value={currentView} disabled={disabled} onChange={event => { const next = event.target.value as SchedulerView; if (view === undefined) setInnerView(next); onViewChange?.(next); }}><option value=\"agenda\">Agenda</option><option value=\"week\">Week</option><option value=\"month\">Month</option></NativeSelect></div>\n    <p {...time}>Times in {zone}</p>\n    {onSlotSelect && <div {...toolbar}><Input type=\"time\" aria-label=\"New event time\" value={slotTime} disabled={disabled} onChange={event => setSlotTime(event.target.value)} /><Button {...action} variant=\"ghost\" disabled={disabled || !slotTime} onClick={() => chooseSlot(selected)}><Icon name=\"plus\" />Add event</Button></div>}\n    {currentView === \"agenda\" ? eventList(selected) : <div {...scroll} tabIndex={0} role=\"region\" aria-label={`${currentView === \"week\" ? \"Week\" : \"Month\"} calendar`}>\n      {currentView === \"week\" ? <div {...weekStyle}>{week.map(day => { const active = sameDay(day, selected); const cell = rs([\"rs-scheduler-day\", active && \"rs-scheduler-selected\"], styles.day, active && styles.selected); return <section key={dateInput(day)} {...cell} aria-label={dayLabel(day)}><button {...dateStyle} type=\"button\" aria-label={dayLabel(day)} aria-pressed={active} disabled={disabled} onClick={() => changeDay(day)}>{weekday(day)} {day.getUTCDate()}</button>{eventList(day)}{onSlotSelect && <Button {...action} variant=\"ghost\" disabled={disabled || !slotTime} aria-label={`Schedule on ${dayLabel(day)}`} onClick={() => chooseSlot(day)}><Icon name=\"plus\" size={12} />{slotTime}</Button>}</section>; })}</div>\n      : <table {...monthStyle} aria-label={titleText}><thead><tr>{week.map(day => <th key={day.getUTCDay()} {...weekdayStyle} scope=\"col\">{weekday(day)}</th>)}</tr></thead><tbody>{Array.from({ length: 6 }, (_, row) => <tr key={row}>{Array.from({ length: 7 }, (_, column) => { const day = addDays(monthBeginning, row * 7 + column); const active = sameDay(day, selected); const outside = day.getUTCMonth() !== selected.getUTCMonth(); const cell = rs([\"rs-scheduler-cell\", active && \"rs-scheduler-selected\", outside && \"rs-scheduler-outside\"], styles.cell, active && styles.selected, outside && styles.outside); return <td key={column} {...cell}><button {...dateStyle} type=\"button\" aria-label={dayLabel(day)} aria-pressed={active} disabled={disabled} onClick={() => changeDay(day)}>{day.getUTCDate()}</button>{eventList(day)}</td>; })}</tr>)}</tbody></table>}\n    </div>}\n    <p {...empty} role=\"status\">{announcement}</p>\n    <Dialog open={editing !== null} onClose={() => setEditing(null)} closeLabel=\"Cancel reschedule\"><DialogTitle>Reschedule {editing?.title}</DialogTitle><form {...form} onSubmit={event => { event.preventDefault(); const day = parseDay(editDay); if (!editing || !day || !editTime) return; const start = atTime(day, editTime, zone); if (!start) { setEditError(`That time does not exist in ${zone} because the clocks change. Choose another time.`); return; } const end = new Date(start.getTime() + editing.end.getTime() - editing.start.getTime()); onEventMove?.(editing, { start, end }); setAnnouncement(`${editing.title} rescheduled to ${dayLabel(dayInZone(start, zone))} at ${timeFormat(start)}.`); setEditing(null); }}><Input label=\"Date\" type=\"date\" value={editDay} required onChange={event => setEditDay(event.target.value)} /><Input label=\"Start time\" type=\"time\" value={editTime} required onChange={event => setEditTime(event.target.value)} />{editError && <p {...empty} role=\"alert\">{editError}</p>}<Button type=\"submit\">Save schedule</Button></form></Dialog>\n  </div>;\n});\n",
      "type": "registry:component",
      "target": "components/vlak/scheduler.tsx"
    },
    {
      "path": "vlak/styles/scheduler.css",
      "content": "/* ── scheduler: generated from packages/react/src/components/scheduler.tsx ── */\n.rs-scheduler{display:flex;flex-direction:column;gap:1rem;width:100%;min-width:0;color:var(--text)}\n.rs-scheduler-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:0.5rem}\n.rs-scheduler-action{width:auto;min-width:var(--hit);min-height:var(--hit);padding-inline:0.75rem}\n.rs-scheduler-title{margin:0;font-size:1rem;font-weight:600;line-height:1.45;flex:1 1 12rem}\n.rs-scheduler-scroll{overflow-x:auto;padding:0.25rem}\n.rs-scheduler-scroll:focus-visible{outline-width:2px;outline-style:solid;outline-color:var(--text);outline-offset:-2px}\n.rs-scheduler-week{display:grid;grid-template-columns:repeat(7, minmax(10rem, 1fr));min-width:70rem;gap:0}\n.rs-scheduler-day{padding:0.75rem;border-width:1px;border-style:solid;border-color:var(--divider);min-width:0}\n.rs-scheduler-selected{background-color:var(--control-fill)}\n.rs-scheduler-date{display:flex;align-items:center;min-width:var(--hit);min-height:var(--hit);border-width:0;border-radius:var(--radius-sm);background-color:transparent;color:var(--text);font-size:0.8125rem;font-weight:600;font-family:inherit;cursor:pointer;padding-inline:0.5rem}\n.rs-scheduler-date:focus-visible{outline-width:2px;outline-style:solid;outline-color:var(--text);outline-offset:2px}\n.rs-scheduler-month{width:100%;min-width:49rem;table-layout:fixed;border-collapse:collapse}\n.rs-scheduler-weekday{padding:0.75rem;font-size:0.75rem;font-weight:500;text-align:start;color:var(--text-secondary)}\n.rs-scheduler-cell{vertical-align:top;height:8rem;padding:0.5rem;border-width:1px;border-style:solid;border-color:var(--divider)}\n.rs-scheduler-outside{color:var(--text-secondary)}\n.rs-scheduler-list{list-style-type:none;margin:0;padding:0;display:flex;flex-direction:column;gap:0.75rem}\n.rs-scheduler-event{min-width:0;display:flex;flex-direction:column;gap:0.25rem;padding-block:0.5rem}\n.rs-scheduler-event-button{display:flex;flex-direction:column;align-items:flex-start;gap:0.25rem;width:100%;min-height:var(--hit);padding:0.5rem;border-width:1px;border-style:solid;border-color:var(--control-border);border-radius:var(--radius-sm);background-color:var(--bg);color:var(--text);font-size:0.875rem;font-family:inherit;text-align:start;overflow-wrap:anywhere;cursor:pointer}\n.rs-scheduler-event-button:focus-visible{outline-width:2px;outline-style:solid;outline-color:var(--text);outline-offset:2px}\n.rs-scheduler-event-button:disabled{color:var(--text-secondary);cursor:not-allowed}\n.rs-scheduler-time{font-size:0.75rem;color:var(--text-secondary);line-height:1.45;font-variant-numeric:tabular-nums}\n.rs-scheduler-empty{margin:0;padding-block:0.75rem;color:var(--text-secondary);font-size:0.8125rem;line-height:1.45}\n.rs-scheduler-form{display:flex;flex-direction:column;gap:1rem}\n",
      "type": "registry:file",
      "target": "styles/vlak/scheduler.css"
    }
  ],
  "meta": {
    "vlak": {
      "category": "patterns",
      "classes": [
        "rs-scheduler",
        "rs-scheduler-toolbar",
        "rs-scheduler-action",
        "rs-scheduler-title",
        "rs-scheduler-scroll",
        "rs-scheduler-week",
        "rs-scheduler-date",
        "rs-scheduler-month",
        "rs-scheduler-weekday",
        "rs-scheduler-list",
        "rs-scheduler-event",
        "rs-scheduler-event-button",
        "rs-scheduler-time",
        "rs-scheduler-empty",
        "rs-scheduler-form",
        "rs-scheduler-day",
        "rs-scheduler-selected",
        "rs-scheduler-cell",
        "rs-scheduler-outside"
      ],
      "snippet": "<div class=\"rs-scheduler\" role=\"region\" aria-label=\"Schedule\"><h3 class=\"rs-scheduler-title\">Monday, 7 September</h3><ol class=\"rs-scheduler-list\"><li class=\"rs-scheduler-event\"><span>Review</span><span class=\"rs-scheduler-time\">09:00–09:30</span></li></ol></div>",
      "cssOnly": false,
      "registryDependencies": [
        "button",
        "icons",
        "input",
        "native-select",
        "dialog",
        "vlak-lib"
      ],
      "aliases": [
        "Scheduler",
        "Event calendar",
        "Agenda"
      ],
      "example": "import { Scheduler } from \"@noorddev/vlak-react\";\n\n<Scheduler events={events} defaultValue={new Date(\"2026-09-07T12:00:00Z\")} timeZone=\"Europe/Amsterdam\" defaultView=\"week\" onSlotSelect={createEvent} onEventSelect={openEvent} onEventMove={rescheduleEvent} />",
      "usage": {
        "use": [
          "An event collection in agenda, week, or month views, using a named timeZone or the browser zone.",
          "Selecting a new event time or rescheduling while preserving duration."
        ],
        "avoid": [
          "Recurrence expansion or conflict enforcement; prepare those in the application."
        ]
      },
      "keyboard": [
        {
          "keys": "Tab, Enter, Space",
          "does": "Operates date navigation, event buttons, scheduling actions, and view selection."
        },
        {
          "keys": "Escape",
          "does": "Closes the rescheduling dialog and returns focus to the triggering action."
        }
      ],
      "a11y": [
        "Month view is a native table with weekday headers.",
        "Week columns and navigation are named; native date and time inputs retain platform behavior.",
        "Rescheduling uses a named native dialog and announces the new time.",
        "Event Date values and callbacks are instants; date and time inputs use the displayed zone. Invalid intervals and nonexistent daylight-saving times are rejected. Repeated times choose the earlier occurrence.",
        "Server rendering uses a stable loading shell until hydration so browser time zones and the current date cannot cause a hydration mismatch."
      ]
    }
  }
}
