{
  "name": "DatePicker",
  "source": "src/components/DatePicker.tsx",
  "propsType": "DatePickerProps",
  "description": "DatePicker — a segmented date field with a calendar attached.\n\nWHY NOT `<input type=\"date\">`\n\nThe native control is genuinely good on mobile and genuinely unusable as a\ndesign system component on desktop: its calendar cannot be styled at all, it\ncannot express an unavailable date that is not part of a continuous min/max\nrange, and Safari on macOS renders no calendar button whatsoever. A system\nwhose date field looks like a different control in every browser is not a\ndesign system, so this rebuilds it — and pays for that by having to rebuild\nthe keyboard handling and the screen-reader semantics the native control gave\naway for free. React Aria's `useDatePicker` is what builds them back.\n\nTHE VALUE IS A STRING, THE DISPLAY IS NOT\n\n`value` is always `YYYY-MM-DD` in the Gregorian calendar. What the user SEES\nis their own locale's field order and their own calendar system, resolved\nfrom `Intl` — so a German reader edits DD.MM.YYYY and the value still comes\nback as `2026-04-12`. See `iso-date.ts` for why the public type is not\nreact-aria's `CalendarDate`.\n\nTHE FIELD IS EDITABLE WITHOUT THE CALENDAR, AND THAT IS THE POINT\n\nTyping a date is faster than clicking one for anybody who knows the date they\nwant, and paging a calendar back to a birth year is miserable. The segments\ntake digits and arrow keys directly; the calendar is for the cases where the\ndate is being chosen rather than recalled.",
  "import": "import { DatePicker } from 'ionbase-ui';",
  "status": "stable",
  "summary": "A segmented date field with a calendar attached, taking and returning a YYYY-MM-DD string.",
  "useWhen": [
    "the user picks one calendar date — an event date, a due date, a birthday",
    "the date has to be typed as well as clicked, because the user often already knows it",
    "some dates must be refused individually rather than as a continuous range"
  ],
  "useInstead": [
    {
      "when": "the user picks a start and an end",
      "use": "DateRangePicker",
      "why": "two DatePickers cannot stop the end preceding the start, cannot show the days between them, and give the user two calendars to reconcile from memory"
    },
    {
      "when": "the value is a month or a year rather than a day",
      "use": "Select",
      "why": "a calendar grid to choose 'March' is twelve options rendered as thirty-one"
    },
    {
      "when": "the field is one of a short, known set of dates the product defines",
      "use": "Select or RadioGroup",
      "why": "picking from four delivery slots is a choice from a list, not a date"
    },
    {
      "when": "a time of day is part of the value — one moment, such as when a run starts",
      "use": "DateTimePicker",
      "why": "this picker is fixed to day granularity; a DateTimePicker holds the date and the time as one value and checks a bound such as \"not before now\" against both. A TimeField beside this is only for a time that is a separate, optional choice"
    },
    {
      "when": "the day is chosen by where it falls in the month, and it is the only question — a day to resume on, in a Modal",
      "use": "Calendar",
      "why": "the picker's calendar is a popover, a second layer over the dialog; a Calendar shows the month in the dialog itself"
    }
  ],
  "variants": {
    "size": {
      "sm": {
        "use": "dense forms, table filters"
      },
      "md": {
        "use": "the default"
      },
      "lg": {
        "use": "prominent single-field forms"
      }
    }
  },
  "a11y": {
    "guarantees": [
      "each editable segment is its own tab stop, as in Chrome's native <input type=date>; the locale's separators are not, and arrow keys move between segments as well, so neither convention has to be learned",
      "the segments sit in one labelled group, so the field's name is announced once on entry rather than repeated per segment",
      "each segment announces its own name, value and bounds, so 'month, 4, minimum 1, maximum 12' is heard rather than an opaque number",
      "field order and separators come from Intl, so a German reader edits DD.MM.YYYY and a US reader MM/DD/YYYY from the same component and the same value",
      "the calendar overlay is modal: focus moves into the grid, the rest of the page is hidden from assistive tech, Escape closes it and focus returns to the field",
      "every day cell carries the full date as its accessible name in the reader's own language, so the narrow weekday headers are never load-bearing",
      "unavailable and disabled days are distinguished visually and in the accessible name, not by colour alone",
      "today is marked with a dot rather than a ring, so it cannot be mistaken for a second selected day",
      "the selected fill and the segment focus box are redeclared under forced-colors, where background-color is discarded",
      "a typed date outside `minValue`/`maxValue` turns the field invalid and names the bound in the reader's own format, without `isInvalid` — the value is kept, not silently clamped"
    ],
    "requires": [
      "`label`, or an `aria-label` when no visible label is rendered",
      "`calendarLabel` wording if 'Open calendar' is wrong for the context — it is the button's whole accessible name"
    ]
  },
  "antiPatterns": [
    {
      "dont": "passing a Date object, a timestamp, or a locale-formatted string as `value`",
      "why": "the value is a calendar date with no time and no zone, spelled YYYY-MM-DD. `new Date('2026-04-12')` parses as UTC midnight and displays as the 11th for every reader west of Greenwich. A malformed value throws with the prop name rather than rendering an empty field"
    },
    {
      "dont": "using `isDateUnavailable` to express a lower or upper bound",
      "why": "`minValue` and `maxValue` also disable the navigation arrows past the bound, so the user cannot page into a month with nothing selectable in it. A predicate is for the holes inside the bounds"
    },
    {
      "dont": "hiding the field and leaving only the calendar button",
      "why": "typing a date is faster than clicking one for anyone who already knows it, and paging a calendar back to a birth year is the worst interaction in any form"
    },
    {
      "dont": "styling a day cell with :hover for the keyboard highlight",
      "why": "use [data-selected], [data-today], [data-disabled] and [data-unavailable]. Pointer hover and keyboard focus are different states here and a grid where they disagree has two apparent cursors"
    },
    {
      "dont": "reading the posted form value from the segments",
      "why": "they are spinbuttons with no name. Pass `name` and the component posts the ISO value in a hidden input"
    },
    {
      "dont": "wrapping it in a <label>",
      "why": "the group is a set of spinbuttons rather than one form control, so there is nothing for htmlFor to point at. Pass `label` and react-aria wires the name through aria-labelledby"
    },
    {
      "dont": "adding a roving tabindex so the field becomes one tab stop",
      "why": "it buys back two tabs per field and costs the ability to Tab straight to the year, which is the segment users most often fix on its own. It also diverges from the native date input, which is the convention they already have"
    }
  ],
  "slots": {
    "label": {
      "expects": "what the date is for — 'Event starts', not 'Date'"
    },
    "description": {
      "expects": "helper text under the field; replaced by `errorMessage` when `isInvalid`"
    },
    "errorMessage": {
      "expects": "what is wrong and what would be right, shown only when `isInvalid`"
    }
  },
  "stylesheet": "src/styles/date-picker.css",
  "tokens": [
    "--border-disabled",
    "--border-error-strong",
    "--border-focus",
    "--border-subtle",
    "--border-width-default",
    "--border-width-thick",
    "--font-family-sans",
    "--font-weight-medium",
    "--font-weight-regular",
    "--font-weight-semibold",
    "--icon-disabled",
    "--icon-interactive",
    "--icon-on-color",
    "--icon-primary",
    "--icon-secondary",
    "--icon-size-sm",
    "--ion-calendar-day-size",
    "--ion-duration-base",
    "--ion-ease-out",
    "--ion-input-icon-size",
    "--ion-shadow-shadow-lg",
    "--radius-2xs",
    "--radius-full",
    "--radius-md",
    "--radius-sm",
    "--spacing-12",
    "--spacing-2",
    "--spacing-24",
    "--spacing-32",
    "--spacing-4",
    "--spacing-6",
    "--spacing-8",
    "--surface-disabled",
    "--surface-hover",
    "--surface-primary",
    "--surface-raised",
    "--text-default",
    "--text-disabled",
    "--text-on-color",
    "--text-secondary",
    "--text-tertiary",
    "--type-body-sm",
    "--type-body-sm-line-height",
    "--type-caption",
    "--type-caption-line-height"
  ],
  "props": {
    "label": {
      "type": "React.ReactNode",
      "required": false,
      "origin": "own",
      "description": "Field label. Required for a usable control — see `a11y.requires`."
    },
    "description": {
      "type": "React.ReactNode",
      "required": false,
      "origin": "own",
      "description": "Helper text below the field."
    },
    "errorMessage": {
      "type": "React.ReactNode",
      "required": false,
      "origin": "own",
      "description": "Replaces the helper text while the field is invalid — `isInvalid`, or its own bounds check."
    },
    "isInvalid": {
      "type": "boolean | undefined",
      "required": false,
      "origin": "own"
    },
    "isDisabled": {
      "type": "boolean | undefined",
      "required": false,
      "origin": "own"
    },
    "isReadOnly": {
      "type": "boolean | undefined",
      "required": false,
      "origin": "own"
    },
    "isRequired": {
      "type": "boolean | undefined",
      "required": false,
      "origin": "own"
    },
    "size": {
      "type": "DatePickerSize | undefined",
      "required": false,
      "origin": "own",
      "description": "Matches Input's `Size` variant: Small, Medium, Large.",
      "values": [
        "sm",
        "md",
        "lg"
      ]
    },
    "value": {
      "type": "string | null | undefined",
      "required": false,
      "origin": "own",
      "description": "The selected date as `YYYY-MM-DD`. `null` means nothing is selected.\n\nNot a `Date` and not a timestamp — a calendar date has no time and no\ntimezone, so it cannot shift across midnight the way `new Date('2026-04-12')`\ndoes for every reader west of Greenwich. A malformed value throws rather\nthan rendering an empty field."
    },
    "defaultValue": {
      "type": "string | undefined",
      "required": false,
      "origin": "own",
      "description": "The initial date as `YYYY-MM-DD`, for an uncontrolled picker."
    },
    "onChange": {
      "type": "((value: IsoDate | null) => void) | undefined",
      "required": false,
      "origin": "own",
      "description": "Fires with `YYYY-MM-DD`, or `null` when the field is cleared."
    },
    "minValue": {
      "type": "string | undefined",
      "required": false,
      "origin": "own",
      "description": "Earliest selectable date, `YYYY-MM-DD`. Earlier dates are unreachable."
    },
    "maxValue": {
      "type": "string | undefined",
      "required": false,
      "origin": "own",
      "description": "Latest selectable date, `YYYY-MM-DD`."
    },
    "isDateUnavailable": {
      "type": "((date: IsoDate) => boolean) | undefined",
      "required": false,
      "origin": "own",
      "description": "Individual dates the calendar refuses — weekends, holidays, booked days.\nReceives `YYYY-MM-DD`. Use `minValue`/`maxValue` for a continuous bound;\nthis is for the holes inside it."
    },
    "name": {
      "type": "string | undefined",
      "required": false,
      "origin": "own",
      "description": "Posts the value under this name, for an uncontrolled form."
    },
    "calendarLabel": {
      "type": "string | undefined",
      "required": false,
      "origin": "own",
      "description": "Accessible label for the button that opens the calendar."
    },
    "className": {
      "type": "string | undefined",
      "required": false,
      "origin": "own"
    },
    "wrapperClassName": {
      "type": "string | undefined",
      "required": false,
      "origin": "own"
    },
    "id": {
      "type": "string | undefined",
      "required": false,
      "origin": "own"
    }
  },
  "propCounts": {
    "own": 19,
    "aria": 0,
    "dom": 0,
    "other": 0
  }
}
