import type { ControlSize } from "@charcuterie/tokens"; import type { ReactNode } from "react"; import type { SlotProps } from "../slotProps.ts"; import type { DatePreset } from "./datePresets.ts"; import type { DateInputKeywords } from "./parseDateInput.ts"; import type { DateRange } from "./plainDate.ts"; export type DatePickerProps = SlotProps & { className?: string; /** * Sunday 0 through Saturday 6. Omit and the locale decides, via * `Intl.Locale`'s `getWeekInfo` — which is why there is no * `isMondayFirst` here. */ firstDayOfWeek?: number; isDisabled?: boolean; /** * Start-and-end mode. A **mode, not a sibling component** — see * the component docblock, and the `Combobox` `isMultiple` * precedent it follows. */ isRange?: boolean; /** Override the English relative words (`today`, `next`, `+3d`). */ keywords?: DateInputKeywords; /** * Required. It names the field for a screen reader **and** names * the calendar dialog and its grids, which a `role="dialog"` takes * from nothing else. Inside a `Field`, pass the same text as the * `Field`'s own `label` — they are the same name and WCAG 2.5.3 * wants them to agree. */ label: string; /** Omit for the platform's resolved locale. */ locale?: string; /** ISO `YYYY-MM-DD`. Days after this are unselectable. */ maxValue?: string; /** ISO `YYYY-MM-DD`. Days before this are unselectable. */ minValue?: string; /** * An ISO `YYYY-MM-DD` in single mode, a `{ end, start }` of them in * range mode, and `null` when cleared. Never a `Date`, and that is * the point — see the docblock. */ onChange?: (value: DateRange | null | string) => void; placeholder?: string; /** Pass `[]` to drop the shortcut row entirely. */ presets?: readonly DatePreset[]; size?: ControlSize; /** * Which zone's calendar "today" is read from, when `today` is * omitted. Ignored when `today` is supplied — which is what every * test and every story does. */ timeZone?: string; /** * The clock, injected, as an ISO `YYYY-MM-DD`. * * Every relative form (`tomorrow`, `next fri`, `+14d`), the * `aria-current="date"` marker, and the preset row resolve against * this. Supplying it is what makes the component testable at all: * a date picker tested against the real "today" passes on the day * it was written and fails silently afterwards. Omitted, it is * read **once**, at mount, from `timeZone` or the device. */ today?: string; /** **Initial** only, like every other value prop in this library. */ value?: DateRange | string; }; /** * A date field: a text input you can actually type into, and a * calendar dialog hanging off it. * * ### The value is a calendar date, never an instant * * `onChange` reports an ISO `YYYY-MM-DD` string — the same format * `` uses — and never a `Date`. A `Date` is a * count of milliseconds, so reading one back always goes through the * browser's timezone, and a due date picked at 23:30 in Denver comes * back as the previous day in London. `plainDate.ts` has the full * argument and the arithmetic; `plainDate.test.ts` re-runs it under * UTC+14 and UTC-11 and asserts nothing moves. * * ### Typing is the feature * * The grid is the fallback, not the interface. `tomorrow`, * `next fri`, `+14d`, `19 aug`, `8/19`, `2026-08-19` and a bare `19` * all resolve, in the active locale, against an injected `today` — * and **the resolution is echoed underneath the field, in a live * region, before anything commits.** Nothing here guesses in * silence: an ambiguous month prefix comes back as "'ju' could be * June or July", not as June. `parseDateInput.ts` is the grammar and * the reasoning. * * ### Range is a mode, not a sibling * * `isRange` follows * [Combobox's `isMultiple`](../../../docs/decisions/2026-08-05-combobox-multi-select-stays-ismultiple-not-a-separate-component.md) * for the same three reasons, which hold even harder here: * * - **No distinct role.** The family is named by ARIA role. A range * picker is the same `role="dialog"` over the same `role="grid"`; * there is no `daterange` role to name a `DateRangePicker` after, * so the name would describe a *presentation*. * - **The delta is small and the shared part is everything.** The * parser, the locale plumbing, the min/max clamp, the keyboard * grid, the panel, the presets and the echo line are identical. * What changes is a second input, a second month, and a band * drawn between two days. * - **`Combobox` already set the precedent for the value shape.** * Its `selectedValue` is `readonly string[] | string`; this one is * `DateRange | string`. A discriminated props union would type * better and was rejected for that consistency, and because * Storybook's docgen renders a union props type as an untyped * object control. * * ### The panel is a container, and it is queried as one * * `@container` sits on the panel, and everything inside sizes to the * panel's own inline size rather than to the window: one-letter * weekday headers and 32px cells below `--cq-xs`, two-letter and * 36px above it, 40px above `--cq-sm`, and in range mode the two * months stack below `--cq-sm` and sit side by side above it. That * is not the same question a media query asks — the owner browses * zoomed in, where a 390px phone reports a 195px layout viewport and * a desktop window at 200% has no room either. The `size` middleware * clamps the panel to the space actually available, and the * container query reads what it got. * * ### It owns its own open state, and its own value * * Assembled, like [`Picker`](../Picker/Picker.tsx): the * `useVisibility` is in here, because a field that needs the caller * to wire `isVisible` is a field the caller has to assemble. And * `value` **seeds** rather than controls, matching `Select`, * `SegmentedControl` and `Combobox` — one owner for the value, which * is the whole of the state layer's argument. */ export declare const DatePicker: ({ className, firstDayOfWeek, isDisabled, isRange, keywords, label, locale, maxValue, minValue, onChange, placeholder, presets, size, timeZone, today, value, ...receivedSlotProps }: DatePickerProps) => ReactNode; //# sourceMappingURL=DatePicker.d.ts.map