{
  "name": "DateRangePicker",
  "source": "src/components/DateRangePicker.tsx",
  "propsType": "DateRangePickerProps",
  "description": "DateRangePicker — two segmented date fields with one calendar between them.\n\nNOT TWO `DatePicker`s SIDE BY SIDE, and the difference is the whole reason\nthis exists. Two independent pickers cannot stop the end date being before\nthe start, cannot show the days between them as a band, and give the user two\ncalendars to reconcile by memory. `useDateRangePicker` owns both ends: the\nsecond click completes the range in whichever order the two dates fall, the\nband follows the pointer while it is being dragged out, and a range cannot be\nmade to run backwards.\n\nTHE VALUE COMMITS LIVE. THERE IS NO APPLY BUTTON.\n\n`onChange` fires the moment the second end lands, and the calendar closes.\nThe reference designs this was drawn from put Clear and Apply under the grid,\nwhich is a draft model: the popover holds a pending range and only Apply\npromotes it. That was considered and rejected. Escape and an outside click\nwould then silently discard a selection the user watched themselves make —\nand there is no way to tell those two gestures apart from \"I am done\". A\nbutton labelled Apply beside a value that already applied is worse still.\n\nClear stays, because clearing is a real action with no other affordance.\n\nBOTH ENDS OR NEITHER\n\n`value` has no half-open form. A range with a start and no end is a state the\ncalendar passes THROUGH between the two clicks; it is never a value the\ncaller is handed, because \"everything after March\" and \"March to today\" are\ndifferent questions and a `null` end cannot say which one was meant. A caller\nwho wants an open end passes a sentinel of their own choosing as `maxValue`.",
  "import": "import { DateRangePicker } from 'ionbase-ui';",
  "status": "stable",
  "summary": "Two segmented date fields sharing one calendar, returning an inclusive { start, end } of YYYY-MM-DD strings.",
  "useWhen": [
    "the user picks a period — a report window, a stay, a filter over time",
    "the range commonly crosses a month boundary, so both ends need to be visible at once",
    "there are conventional periods worth offering as shortcuts: last 7 days, month to date, year to date"
  ],
  "useInstead": [
    {
      "when": "the user picks one date",
      "use": "DatePicker"
    },
    {
      "when": "the choice is really a named period and the exact dates are incidental",
      "use": "Select",
      "why": "if 'Last 30 days' is the answer rather than a shortcut to one, a calendar is scaffolding around a list of five options"
    },
    {
      "when": "one end is open — everything before, or everything since",
      "use": "DatePicker",
      "why": "with the open end expressed as minValue or maxValue. This component has no half-open value on purpose; see antiPatterns"
    }
  ],
  "variants": {
    "size": {
      "sm": {
        "use": "dense forms, table filters, dashboard toolbars"
      },
      "md": {
        "use": "the default"
      },
      "lg": {
        "use": "prominent single-field forms"
      }
    }
  },
  "a11y": {
    "guarantees": [
      "both fields sit in one labelled group; each announces its own name, so 'Start Date' and 'End Date' are heard without a visible sublabel, and each editable segment is its own tab stop as it is in DatePicker",
      "the dash between them is aria-hidden — the fields have already said what they are, and a spoken dash adds nothing",
      "the calendar is modal: focus enters the grid, the page behind is hidden from assistive tech, Escape closes and focus returns",
      "a range cannot be made to run backwards, in either direction of selection, and cannot be dragged across an unavailable day",
      "preset rows are buttons in a labelled group carrying aria-pressed, so 'which shortcut is active' is answerable without seeing the tint",
      "the selected preset is marked by weight as well as by background, so it does not rely on colour alone",
      "range endpoints and the band between them keep their fill under forced-colors, where background-color is otherwise discarded",
      "the rail stacks above the grids below 32rem rather than being hidden, because shortcuts matter most on the device where dragging across two months is worst",
      "a typed date outside `minValue`/`maxValue`, or an end before the start, turns the field invalid with React Aria's localized message, without `isInvalid` — the value is kept, not silently clamped"
    ],
    "requires": [
      "`label`, or an `aria-label` when no visible label is rendered",
      "`presetsLabel` wording if the default is wrong for the context — it names the whole shortcut group",
      "preset labels that say what period they select, since the label is the button's entire accessible name"
    ]
  },
  "antiPatterns": [
    {
      "dont": "expecting an Apply button, or adding one",
      "why": "the value commits when the second end lands and the calendar closes. A draft model makes Escape and an outside click silently discard a selection the user watched themselves make, and there is no way to tell those gestures apart from 'I am done'. Clear exists because clearing has no other affordance"
    },
    {
      "dont": "passing a range with only one end, or reading one back",
      "why": "a start with no end is a state the calendar passes through between the two clicks, never a value the caller is handed. 'Everything after March' and 'March to today' are different questions and a null end cannot say which was meant"
    },
    {
      "dont": "writing a relative preset as a literal object",
      "why": "`value: { start: '…', end: '…' }` is evaluated once, when the module is first imported. 'Last 7 days' is then seven days before whenever the tab was opened. Pass a function; it is re-run each time the calendar opens"
    },
    {
      "dont": "composing this out of two DatePickers",
      "why": "two independent pickers cannot stop the end preceding the start, cannot draw the band between the ends, and make the user reconcile two calendars from memory"
    },
    {
      "dont": "setting visibleMonths to 1 for a filter that usually spans weeks",
      "why": "the user then pages back and forth to see both ends of their own selection. One month is for ranges that genuinely sit inside one"
    },
    {
      "dont": "treating `end` as exclusive",
      "why": "the range is closed. A stay of 3 nights from the 1st ends on the 4th in checkout terms and on the 3rd here; convert at your boundary, not in the picker"
    }
  ],
  "slots": {
    "label": {
      "expects": "what the period is for — 'Reporting period', not 'Dates'"
    },
    "presets": {
      "expects": "conventional periods for this screen, longest-lived first: today, last 7 days, month to date, year to date"
    },
    "description": {
      "expects": "helper text under the fields; replaced by `errorMessage` when `isInvalid`"
    },
    "errorMessage": {
      "expects": "what is wrong with the range and what would be right, shown only when `isInvalid`"
    }
  },
  "stylesheet": "src/styles/date-range-picker.css",
  "tokens": [
    "--border-focus",
    "--border-subtle",
    "--border-width-default",
    "--border-width-thick",
    "--font-family-sans",
    "--font-weight-medium",
    "--font-weight-regular",
    "--icon-disabled",
    "--icon-interactive",
    "--ion-duration-base",
    "--ion-ease-out",
    "--ion-input-icon-size",
    "--radius-2xs",
    "--radius-full",
    "--radius-none",
    "--radius-sm",
    "--spacing-12",
    "--spacing-2",
    "--spacing-4",
    "--spacing-6",
    "--spacing-8",
    "--surface-hover",
    "--surface-primary",
    "--surface-primary-subtle",
    "--surface-primary-subtle-hover",
    "--surface-raised",
    "--text-default",
    "--text-disabled",
    "--text-interactive",
    "--text-interactive-hover",
    "--text-on-color",
    "--text-primary",
    "--text-tertiary",
    "--type-body-sm",
    "--type-body-sm-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": "DateRangePickerSize | undefined",
      "required": false,
      "origin": "own",
      "description": "Matches Input's `Size` variant: Small, Medium, Large.",
      "values": [
        "sm",
        "md",
        "lg"
      ]
    },
    "value": {
      "type": "DateRange | null | undefined",
      "required": false,
      "origin": "own",
      "description": "The selected range, both ends as `YYYY-MM-DD`. `null` means no range.\nA half-open range is not representable on purpose — see `antiPatterns`."
    },
    "defaultValue": {
      "type": "DateRange | undefined",
      "required": false,
      "origin": "own",
      "description": "The initial range, for an uncontrolled picker."
    },
    "onChange": {
      "type": "((value: DateRange | null) => void) | undefined",
      "required": false,
      "origin": "own",
      "description": "Fires with both ends, or `null` when the range is cleared."
    },
    "minValue": {
      "type": "string | undefined",
      "required": false,
      "origin": "own",
      "description": "Earliest selectable date, `YYYY-MM-DD`."
    },
    "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. Receives `YYYY-MM-DD`.\n\nA range may not be dragged ACROSS one of these — react-aria stops the\nselection at the first unavailable day, which is what makes \"book a room\nfor these nights\" correct rather than merely styled."
    },
    "presets": {
      "type": "readonly DateRangePreset[] | undefined",
      "required": false,
      "origin": "own",
      "description": "Shortcut rows beside the calendar. Omitted, no rail is rendered."
    },
    "visibleMonths": {
      "type": "2 | 3 | 1 | undefined",
      "required": false,
      "origin": "own",
      "description": "How many months the calendar shows at once. Two by default: a range that\ncrosses a month boundary is the common case, and a one-month calendar\nmakes the user page back and forth to see both ends of their own selection."
    },
    "isClearable": {
      "type": "boolean | undefined",
      "required": false,
      "origin": "own",
      "description": "Show a Clear button under the calendar. Defaults to `true`."
    },
    "name": {
      "type": "string | undefined",
      "required": false,
      "origin": "own",
      "description": "Posts `<name>-start` and `<name>-end` for an uncontrolled form."
    },
    "calendarLabel": {
      "type": "string | undefined",
      "required": false,
      "origin": "own",
      "description": "Accessible label for the button that opens the calendar."
    },
    "clearLabel": {
      "type": "string | undefined",
      "required": false,
      "origin": "own"
    },
    "presetsLabel": {
      "type": "string | undefined",
      "required": false,
      "origin": "own"
    },
    "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": 24,
    "aria": 0,
    "dom": 0,
    "other": 0
  }
}
