{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "calendar",
  "type": "registry:component",
  "title": "Calendar",
  "description": "Selects a date from a month grid. Selected day fills with ink; today has a 1px outline.",
  "registryDependencies": [
    "https://vlak.dev/r/vlak-base.json",
    "https://vlak.dev/r/inter.json",
    "https://vlak.dev/r/pagination.json",
    "https://vlak.dev/r/vlak-lib.json",
    "https://vlak.dev/r/icons.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/calendar.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as stylex from \"@stylexjs/stylex\";\nimport { vlak, mq } from \"./tokens.stylex\";\nimport { rs } from \"./rs\";\nimport { Icon } from \"./icons\";\n\nexport interface CalendarProps\n  extends Omit<React.HTMLAttributes<HTMLDivElement>, \"onSelect\" | \"defaultValue\"> {\n  value?: Date;\n  defaultValue?: Date;\n  onValueChange?: (date: Date) => void;\n  /** @deprecated Use `onValueChange`. */\n  onSelect?: (date: Date) => void;\n  defaultMonth?: Date;\n  /** 0 = Sunday, 1 = Monday. */\n  weekStart?: 0 | 1;\n  /** Keep six week rows. False shows only the four to six rows this month needs. */\n  fixedWeeks?: boolean;\n  /** Move focus to the roving day on mount (a date picker opening). */\n  autoFocus?: boolean;\n  min?: Date;\n  max?: Date;\n  disabled?: boolean;\n  isDateDisabled?: (date: Date) => boolean;\n  locale?: string;\n}\n\nconst DOW = [\"Mo\", \"Tu\", \"We\", \"Th\", \"Fr\", \"Sa\", \"Su\"];\nconst DOW_LONG = [\"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\", \"Sunday\"];\n\nfunction sameDay(a: Date | undefined, b: Date): boolean {\n  return (\n    !!a && a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate()\n  );\n}\nconst sameMonth = (a: Date | undefined, b: Date) =>\n  !!a && a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth();\n/** Numeric Date constructors reinterpret years 0–99 as 1900–1999. */\nfunction civilDate(year: number, month: number, day: number): Date {\n  const date = new Date(0);\n  date.setHours(0, 0, 0, 0);\n  date.setFullYear(year, month, day);\n  return date;\n}\nconst startOfMonth = (d: Date) => civilDate(d.getFullYear(), d.getMonth(), 1);\nconst dayKey = (d: Date) => `${d.getFullYear()}-${d.getMonth()}-${d.getDate()}`;\nconst addDays = (d: Date, n: number) => civilDate(d.getFullYear(), d.getMonth(), d.getDate() + n);\n/** Same day-of-month `n` months on, clamped to the target month's length. */\nfunction addMonths(d: Date, n: number): Date {\n  const first = civilDate(d.getFullYear(), d.getMonth() + n, 1);\n  const last = civilDate(first.getFullYear(), first.getMonth() + 1, 0).getDate();\n  return civilDate(first.getFullYear(), first.getMonth(), Math.min(d.getDate(), last));\n}\nconst longDate = (d: Date, locale = \"en\") =>\n  d.toLocaleDateString(locale, { weekday: \"long\", day: \"numeric\", month: \"long\", year: \"numeric\" });\n\nconst styles = stylex.create({\n  cal: {\n    width: \"19.25rem\",\n    maxWidth: \"100%\",\n    overflowX: \"auto\",\n  },\n  head: {\n    display: \"flex\",\n    flexWrap: \"wrap\",\n    justifyContent: \"space-between\",\n    alignItems: \"center\",\n    minHeight: vlak.hit,\n    marginBottom: \"0.25rem\",\n  },\n  title: {\n    fontSize: \"0.875rem\",\n    fontWeight: 500,\n    letterSpacing: \"-0.01em\",\n    lineHeight: 20 / 14,\n    color: vlak.ink,\n  },\n  nav: {\n    display: \"flex\",\n    gap: 0,\n    flexShrink: 0,\n    marginInlineStart: \"auto\",\n  },\n  page: {\n    boxSizing: \"border-box\",\n    width: vlak.hit,\n    height: vlak.hit,\n    minWidth: vlak.hit,\n    minHeight: vlak.hit,\n    borderRadius: vlak.radiusSm,\n    display: \"flex\",\n    alignItems: \"center\",\n    justifyContent: \"center\",\n    fontSize: \"0.8125rem\",\n    color: vlak.gray,\n    borderWidth: 0,\n    borderStyle: \"none\",\n    padding: 0,\n    backgroundColor: {\n      default: \"transparent\",\n      \":hover:not(:disabled):not([aria-disabled='true'])\": { default: null, [mq.hover]: { default: vlak.controlFill, [mq.forcedColors]: \"ButtonFace\" } },\n    },\n    fontFamily: \"inherit\",\n    cursor: \"pointer\",\n    opacity: { default: 1, \":disabled\": 0.4 },\n    outlineWidth: {\n      default: null,\n      \":focus-visible\": 2,\n    },\n    outlineStyle: {\n      default: null,\n      \":focus-visible\": \"solid\",\n    },\n    outlineColor: {\n      default: null,\n      \":focus-visible\": { default: vlak.ink, [mq.forcedColors]: \"CanvasText\" },\n    },\n    outlineOffset: {\n      default: null,\n      \":focus-visible\": -2,\n    },\n  },\n  icon: {\n    display: \"block\",\n    color: \"inherit\",\n  },\n  grid: {\n    width: \"100%\",\n  },\n  row: {\n    display: \"grid\",\n    gridTemplateColumns: \"repeat(7, minmax(44px, 1fr))\",\n  },\n  dow: {\n    fontSize: \"0.75rem\",\n    fontWeight: 500,\n    color: vlak.gray,\n    textAlign: \"center\",\n    lineHeight: 20 / 12,\n    paddingTop: \"0.25rem\",\n    paddingBottom: \"0.25rem\",\n    paddingInline: 0,\n  },\n  day: {\n    opacity: { default: 1, '[aria-disabled=\"true\"]': 0.4 },\n    transition: {\n      default: vlak.transition,\n      [mq.reduce]: \"none\",\n    },\n    boxSizing: \"border-box\",\n    width: \"100%\",\n    height: vlak.hit,\n    minWidth: \"44px\",\n    minHeight: vlak.hit,\n    display: \"flex\",\n    alignItems: \"center\",\n    justifyContent: \"center\",\n    fontSize: \"0.8125rem\",\n    fontVariantNumeric: \"tabular-nums\",\n    color: vlak.ink,\n    backgroundColor: {\n      default: \"transparent\",\n      \":hover:not(:disabled):not([aria-disabled='true'])\": { default: null, [mq.hover]: { default: vlak.controlFill, [mq.forcedColors]: \"ButtonFace\" } },\n    },\n    borderWidth: 0,\n    borderStyle: \"none\",\n    borderRadius: vlak.radiusSm,\n    cursor: \"pointer\",\n    fontFamily: \"inherit\",\n    padding: 0,\n    outlineWidth: {\n      default: null,\n      \":focus-visible\": 2,\n    },\n    outlineStyle: {\n      default: null,\n      \":focus-visible\": \"solid\",\n    },\n    outlineColor: {\n      default: null,\n      \":focus-visible\": { default: vlak.ink, [mq.forcedColors]: \"CanvasText\" },\n    },\n    outlineOffset: {\n      default: null,\n      \":focus-visible\": -2,\n    },\n  },\n  /* Gray at full opacity stays above 4.5:1 on paper and on the dark ground. */\n  out: {\n    color: vlak.gray,\n  },\n  today: {\n    boxShadow: `inset 0 0 0 1px ${vlak.divider}`,\n  },\n  selected: {\n    transition: {\n      default: vlak.transition,\n      [mq.reduce]: \"none\",\n    },\n    backgroundColor: {\n      default: vlak.ink,\n      \":hover:not(:disabled):not([aria-disabled='true'])\": { default: null, [mq.hover]: { default: vlak.ink, [mq.forcedColors]: \"Highlight\" } },\n      [mq.forcedColors]: \"Highlight\",\n    },\n    borderRadius: vlak.radiusSm,\n    color: { default: vlak.paper, [mq.forcedColors]: \"HighlightText\" },\n    fontWeight: 600,\n    forcedColorAdjust: \"none\",\n    boxShadow: \"none\",\n    outlineColor: {\n      default: null,\n      \":focus-visible\": { default: vlak.paper, [mq.forcedColors]: \"HighlightText\" },\n    },\n    outlineOffset: { default: null, \":focus-visible\": -4 },\n  },\n});\n\n/**\n * Month grid with one roving tab stop. Arrows move by day and week,\n * Home/End to the week's ends, PageUp/PageDown by month (Shift: year).\n * Selected is ink; today is a hairline.\n */\nexport const Calendar = React.forwardRef<HTMLDivElement, CalendarProps>(function Calendar({\n  value,\n  defaultValue,\n  onValueChange,\n  onSelect,\n  defaultMonth,\n  weekStart = 1,\n  fixedWeeks = true,\n  autoFocus,\n  min,\n  max,\n  disabled = false,\n  isDateDisabled,\n  locale = \"en\",\n  className,\n  style,\n  onKeyDown,\n  ...props\n}, ref) {\n  const idBase = React.useId();\n  const titleId = `${idBase}-title`;\n  const isControlled = value !== undefined;\n  const [inner, setInner] = React.useState(defaultValue);\n  const selectedDate = isControlled ? value : inner;\n  const today = new Date();\n  const dateOnly = (d: Date) => civilDate(d.getFullYear(), d.getMonth(), d.getDate()).getTime();\n  const unavailable = (d: Date) => disabled || (!!min && dateOnly(d) < dateOnly(min)) || (!!max && dateOnly(d) > dateOnly(max)) || !!isDateDisabled?.(d);\n\n  const [month, setMonth] = React.useState(() => startOfMonth(selectedDate ?? defaultMonth ?? today));\n  const [focusDate, setFocusDate] = React.useState(\n    () => selectedDate ?? (sameMonth(today, month) ? today : month),\n  );\n  const cellRefs = React.useRef(new Map<string, HTMLButtonElement>());\n  const focusPending = React.useRef(false);\n\n  /* Follow the value when it changes underneath: show its month, rove to it. */\n  const valueKey = selectedDate ? dayKey(selectedDate) : \"\";\n  const seenValueKey = React.useRef(valueKey);\n  React.useEffect(() => {\n    if (seenValueKey.current === valueKey) return;\n    seenValueKey.current = valueKey;\n    if (selectedDate) {\n      setMonth(startOfMonth(selectedDate));\n      setFocusDate(selectedDate);\n    }\n  }, [valueKey, selectedDate]);\n\n  /* The roving cell is always inside the shown month. */\n  const roving = sameMonth(focusDate, month)\n    ? focusDate\n    : sameMonth(selectedDate, month)\n      ? selectedDate!\n      : sameMonth(today, month)\n        ? today\n        : month;\n\n  React.useEffect(() => {\n    if (!focusPending.current) return;\n    focusPending.current = false;\n    cellRefs.current.get(dayKey(roving))?.focus();\n  });\n\n  /* A picker requests focus after its measured overlay becomes visible. */\n  const rovingOnMount = React.useRef(roving);\n  React.useEffect(() => {\n    if (autoFocus) cellRefs.current.get(dayKey(rovingOnMount.current))?.focus();\n  }, [autoFocus]);\n\n  const first = civilDate(month.getFullYear(), month.getMonth(), 1);\n  const lead = (first.getDay() - weekStart + 7) % 7;\n  const start = civilDate(month.getFullYear(), month.getMonth(), 1 - lead);\n  const monthDays = civilDate(month.getFullYear(), month.getMonth() + 1, 0).getDate();\n  const rowCount = fixedWeeks ? 6 : Math.ceil((lead + monthDays) / 7);\n  const weeks = Array.from({ length: rowCount }, (_, w) =>\n    Array.from({ length: 7 }, (_, d) => addDays(start, w * 7 + d)),\n  );\n  const title = month.toLocaleDateString(locale, { month: \"long\", year: \"numeric\" });\n  const weekdayDates = Array.from({ length: 7 }, (_, i) => new Date(2024, 0, 7 + weekStart + i));\n  const dows = weekdayDates.map((d) => d.toLocaleDateString(locale, { weekday: \"narrow\" }));\n  const dowsLong = weekdayDates.map((d) => d.toLocaleDateString(locale, { weekday: \"long\" }));\n\n  const shift = (delta: number) => {\n    const next = addMonths(month, delta);\n    setMonth(startOfMonth(next));\n    setFocusDate(addMonths(roving, delta));\n  };\n\n  const moveFocus = (next: Date) => {\n    if (min && dateOnly(next) < dateOnly(min)) next = min;\n    if (max && dateOnly(next) > dateOnly(max)) next = max;\n    setFocusDate(next);\n    if (!sameMonth(next, month)) setMonth(startOfMonth(next));\n    focusPending.current = true;\n  };\n\n  const choose = (d: Date) => {\n    if (unavailable(d)) return;\n    if (!isControlled) setInner(d);\n    onValueChange?.(d);\n    onSelect?.(d);\n    setFocusDate(d);\n    if (!sameMonth(d, month)) setMonth(startOfMonth(d));\n  };\n\n  const onGridKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n    onKeyDown?.(e);\n    if (e.defaultPrevented) return;\n    const from = roving;\n    const dow = (from.getDay() - weekStart + 7) % 7;\n    switch (e.key) {\n      case \"ArrowLeft\":\n        e.preventDefault();\n        moveFocus(addDays(from, -1));\n        return;\n      case \"ArrowRight\":\n        e.preventDefault();\n        moveFocus(addDays(from, 1));\n        return;\n      case \"ArrowUp\":\n        e.preventDefault();\n        moveFocus(addDays(from, -7));\n        return;\n      case \"ArrowDown\":\n        e.preventDefault();\n        moveFocus(addDays(from, 7));\n        return;\n      case \"Home\":\n        e.preventDefault();\n        moveFocus(addDays(from, -dow));\n        return;\n      case \"End\":\n        e.preventDefault();\n        moveFocus(addDays(from, 6 - dow));\n        return;\n      case \"PageUp\":\n        e.preventDefault();\n        moveFocus(addMonths(from, e.shiftKey ? -12 : -1));\n        return;\n      case \"PageDown\":\n        e.preventDefault();\n        moveFocus(addMonths(from, e.shiftKey ? 12 : 1));\n        return;\n      default:\n        return;\n    }\n  };\n\n  const cal = rs([\"rs-cal\", className], styles.cal);\n  const head = rs([\"rs-cal-head\"], styles.head);\n  const titleSx = rs([\"rs-cal-title\"], styles.title);\n  const nav = rs([\"rs-cal-nav\"], styles.nav);\n  const page = rs([\"rs-page\", \"rs-cal-page\"], styles.page);\n  const icon = rs([\"rs-cal-icon\"], styles.icon);\n  const grid = rs([\"rs-cal-grid\"], styles.grid);\n  const row = rs([\"rs-cal-row\"], styles.row);\n  const dow = rs([\"rs-cal-dow\"], styles.dow);\n\n  return (\n    <div ref={ref} {...props} className={cal.className} style={{ ...cal.style, ...style }}>\n      <div className={head.className} style={head.style}>\n        <span id={titleId} className={titleSx.className} style={titleSx.style} aria-live=\"polite\">\n          {title}\n        </span>\n        <span className={nav.className} style={nav.style}>\n          <button type=\"button\" className={page.className} style={page.style} disabled={disabled || (!!min && startOfMonth(month) <= startOfMonth(min))} aria-label=\"Previous month\" onClick={() => shift(-1)}>\n            <Icon name=\"chevron-left\" size={16} className={icon.className} style={icon.style} />\n          </button>\n          <button type=\"button\" className={page.className} style={page.style} disabled={disabled || (!!max && startOfMonth(month) >= startOfMonth(max))} aria-label=\"Next month\" onClick={() => shift(1)}>\n            <Icon name=\"chevron-right\" size={16} className={icon.className} style={icon.style} />\n          </button>\n        </span>\n      </div>\n      <div className={grid.className} style={grid.style} role=\"grid\" aria-labelledby={titleId} onKeyDown={onGridKeyDown}>\n        <div className={row.className} style={row.style} role=\"row\">\n          {dows.map((d, i) => (\n            <span key={dowsLong[i]} className={dow.className} style={dow.style} role=\"columnheader\" aria-label={dowsLong[i]}>\n              {d}\n            </span>\n          ))}\n        </div>\n        {weeks.map((week, w) => (\n          <div key={w} className={row.className} style={row.style} role=\"row\">\n            {week.map((d) => {\n              const out = d.getMonth() !== month.getMonth();\n              const selected = sameDay(selectedDate, d);\n              const isToday = sameDay(today, d);\n              const key = dayKey(d);\n              const day = rs(\n                [\"rs-cal-day\", out && \"rs-cal-day-out\", isToday && \"rs-cal-day-today\", selected && \"rs-cal-day-selected\"],\n                styles.day,\n                out && styles.out,\n                isToday && styles.today,\n                selected && styles.selected,\n              );\n              return (\n                <button\n                  key={key}\n                  ref={(el) => {\n                    if (el) cellRefs.current.set(key, el);\n                    else cellRefs.current.delete(key);\n                  }}\n                  type=\"button\"\n                  role=\"gridcell\"\n                  tabIndex={!disabled && !out && sameDay(roving, d) ? 0 : -1}\n                  className={day.className}\n                  style={day.style}\n                  aria-selected={selected}\n                  aria-disabled={unavailable(d) || undefined}\n                  aria-current={isToday ? \"date\" : undefined}\n                  aria-label={longDate(d, locale)}\n                  onClick={() => choose(d)}\n                >\n                  {d.getDate()}\n                </button>\n              );\n            })}\n          </div>\n        ))}\n      </div>\n    </div>\n  );\n});\n",
      "type": "registry:component",
      "target": "components/vlak/calendar.tsx"
    },
    {
      "path": "vlak/styles/calendar.css",
      "content": "/* ── calendar: generated from packages/react/src/components/calendar.tsx ── */\n.rs-cal{width:19.25rem;max-width:100%;overflow-x:auto}\n.rs-cal-head{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;min-height:var(--hit);margin-bottom:0.25rem}\n.rs-cal-title{font-size:0.875rem;font-weight:500;letter-spacing:-0.01em;line-height:1.4285714285714286;color:var(--text)}\n.rs-cal-nav{display:flex;gap:0;flex-shrink:0;margin-inline-start:auto}\n.rs-page.rs-cal-page{box-sizing:border-box;width:var(--hit);height:var(--hit);min-width:var(--hit);min-height:var(--hit);border-radius:var(--radius-sm);display:flex;align-items:center;justify-content:center;font-size:0.8125rem;color:var(--text-secondary);border-width:0;border-style:none;padding:0;background-color:transparent;font-family:inherit;cursor:pointer;opacity:1}\n.rs-page.rs-cal-page:disabled{opacity:0.4}\n.rs-page.rs-cal-page:focus-visible{outline-width:2px;outline-style:solid;outline-color:var(--text);outline-offset:-2px}\n@media (hover: hover) and (pointer: fine){.rs-page.rs-cal-page:hover:not(:disabled):not([aria-disabled='true']){background-color:var(--control-fill)}}\n@media (hover: hover) and (pointer: fine) and (forced-colors: active){.rs-page.rs-cal-page:hover:not(:disabled):not([aria-disabled='true']){background-color:ButtonFace}}\n@media (forced-colors: active){.rs-page.rs-cal-page:focus-visible{outline-color:CanvasText}}\n.rs-cal-icon{display:block;color:inherit}\n.rs-cal-grid{width:100%}\n.rs-cal-row{display:grid;grid-template-columns:repeat(7, minmax(44px, 1fr))}\n.rs-cal-dow{font-size:0.75rem;font-weight:500;color:var(--text-secondary);text-align:center;line-height:1.6666666666666667;padding-top:0.25rem;padding-bottom:0.25rem;padding-inline:0}\n.rs-cal-day{opacity:1;transition:var(--transition);box-sizing:border-box;width:100%;height:var(--hit);min-width:44px;min-height:var(--hit);display:flex;align-items:center;justify-content:center;font-size:0.8125rem;font-variant-numeric:tabular-nums;color:var(--text);background-color:transparent;border-width:0;border-style:none;border-radius:var(--radius-sm);cursor:pointer;font-family:inherit;padding:0}\n.rs-cal-day[aria-disabled=\"true\"]{opacity:0.4}\n.rs-cal-day:focus-visible{outline-width:2px;outline-style:solid;outline-color:var(--text);outline-offset:-2px}\n@media (prefers-reduced-motion: reduce){.rs-cal-day{transition:none}}\n@media (hover: hover) and (pointer: fine){.rs-cal-day:hover:not(:disabled):not([aria-disabled='true']){background-color:var(--control-fill)}}\n@media (hover: hover) and (pointer: fine) and (forced-colors: active){.rs-cal-day:hover:not(:disabled):not([aria-disabled='true']){background-color:ButtonFace}}\n@media (forced-colors: active){.rs-cal-day:focus-visible{outline-color:CanvasText}}\n.rs-cal-day-out{color:var(--text-secondary)}\n.rs-cal-day-today{box-shadow:inset 0 0 0 1px var(--divider)}\n.rs-cal-day-selected{transition:var(--transition);background-color:var(--text);border-radius:var(--radius-sm);color:var(--bg);font-weight:600;forced-color-adjust:none;box-shadow:none}\n.rs-cal-day-selected:focus-visible{outline-color:var(--bg);outline-offset:-4px}\n@media (prefers-reduced-motion: reduce){.rs-cal-day-selected{transition:none}}\n@media (hover: hover) and (pointer: fine){.rs-cal-day-selected:hover:not(:disabled):not([aria-disabled='true']){background-color:var(--text)}}\n@media (hover: hover) and (pointer: fine) and (forced-colors: active){.rs-cal-day-selected:hover:not(:disabled):not([aria-disabled='true']){background-color:Highlight}}\n@media (forced-colors: active){.rs-cal-day-selected{background-color:Highlight;color:HighlightText}.rs-cal-day-selected:focus-visible{outline-color:HighlightText}}\n",
      "type": "registry:file",
      "target": "styles/vlak/calendar.css"
    }
  ],
  "meta": {
    "vlak": {
      "category": "forms",
      "classes": [
        "rs-cal",
        "rs-cal-head",
        "rs-cal-title",
        "rs-cal-nav",
        "rs-cal-grid",
        "rs-cal-row",
        "rs-cal-dow",
        "rs-cal-day",
        "rs-cal-day-out",
        "rs-cal-day-today",
        "rs-cal-day-selected",
        "rs-cal-icon",
        "rs-cal-page"
      ],
      "snippet": "<div class=\"rs-cal\"><div class=\"rs-cal-head\"><span class=\"rs-cal-title\">July 2026</span><span class=\"rs-cal-nav\"><button class=\"rs-page\"><svg viewBox=\"0 0 16 16\" width=\"12\" height=\"12\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M10.5 3.75 L5.5 8.25 L10.5 12.75\" vector-effect=\"non-scaling-stroke\"/></svg></button><button class=\"rs-page\"><svg viewBox=\"0 0 16 16\" width=\"12\" height=\"12\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M5.5 3.75 L10.5 8.25 L5.5 12.75\" vector-effect=\"non-scaling-stroke\"/></svg></button></span></div><div class=\"rs-cal-grid\" role=\"grid\"><div class=\"rs-cal-row\" role=\"row\"><span class=\"rs-cal-dow\" role=\"columnheader\">Mo</span><span class=\"rs-cal-dow\" role=\"columnheader\">Tu</span><span class=\"rs-cal-dow\" role=\"columnheader\">We</span><span class=\"rs-cal-dow\" role=\"columnheader\">Th</span><span class=\"rs-cal-dow\" role=\"columnheader\">Fr</span><span class=\"rs-cal-dow\" role=\"columnheader\">Sa</span><span class=\"rs-cal-dow\" role=\"columnheader\">Su</span></div><div class=\"rs-cal-row\" role=\"row\"><button class=\"rs-cal-day\" role=\"gridcell\" tabindex=\"-1\">20</button><button class=\"rs-cal-day\" role=\"gridcell\" tabindex=\"-1\">21</button><button class=\"rs-cal-day\" role=\"gridcell\" tabindex=\"-1\">22</button><button class=\"rs-cal-day\" role=\"gridcell\" tabindex=\"-1\">23</button><button class=\"rs-cal-day rs-cal-day-selected\" role=\"gridcell\" tabindex=\"0\" aria-selected=\"true\">24</button><button class=\"rs-cal-day rs-cal-day-today\" role=\"gridcell\" tabindex=\"-1\" aria-current=\"date\">25</button><button class=\"rs-cal-day\" role=\"gridcell\" tabindex=\"-1\">26</button></div></div></div>",
      "cssOnly": false,
      "registryDependencies": [
        "pagination",
        "vlak-lib",
        "icons"
      ],
      "aliases": [
        "Calendar",
        "Date grid",
        "Month view",
        "Day picker"
      ],
      "example": "import { useState } from \"react\";\nimport { Calendar } from \"@noorddev/vlak-react\";\n\nconst [date, setDate] = useState<Date>();\n\n<Calendar value={date} onValueChange={setDate} weekStart={1} />",
      "usage": {
        "use": [
          "Picking one day when the month context matters: bookings, deadlines, schedules.",
          "Inline on the page; DatePicker wraps it in a trigger."
        ],
        "avoid": [
          "Typing a known date; use Input type=\"date\".",
          "Ranges; the grid selects one day."
        ]
      },
      "keyboard": [
        {
          "keys": "Tab",
          "does": "Focuses the month buttons, then the roving day"
        },
        {
          "keys": "Arrow left, Arrow right",
          "does": "Previous or next day"
        },
        {
          "keys": "Arrow up, Arrow down",
          "does": "Same day the week before or after"
        },
        {
          "keys": "Home, End",
          "does": "First or last day of the week"
        },
        {
          "keys": "Page up, Page down",
          "does": "Same day the month before or after"
        },
        {
          "keys": "Shift + Page up, Shift + Page down",
          "does": "Same day the year before or after"
        },
        {
          "keys": "Enter, Space",
          "does": "Selects the focused available day"
        }
      ],
      "a11y": [
        "Renders role=\"grid\" labelled by the month title, which is aria-live=\"polite\"; rows are role=\"row\" and weekday headers are role=\"columnheader\" with long names.",
        "Days are <button role=\"gridcell\"> with a full-date aria-label, aria-selected, and aria-current=\"date\" on today; one roving tab stop.",
        "Previous and next month buttons are labelled. Controlled with value and onValueChange, or uncontrolled with defaultValue.",
        "Days and month controls are 44px targets. min, max and isDateDisabled prevent selection; unavailable days expose aria-disabled while remaining discoverable with arrows.",
        "locale formats the month, weekdays and full-date labels; weekStart sets Sunday or Monday independently. disabled removes the day grid from the tab order.",
        "fixedWeeks keeps six date rows by default. Set it to false for only the four to six weeks occupied by the current month."
      ]
    }
  }
}
