@spark-web/date-picker
Version:
--- title: DatePicker storybookPath: forms-date-picker isExperimentalPackage: true ---
160 lines (132 loc) • 9.57 kB
Markdown
# @spark-web/date-picker — AI Context
## What this package is
A single-date picker that combines a masked text input (`dd/MM/yyyy`) with a
pop-up month calendar. Users can either type a date in `dd/MM/yyyy` format or
click the calendar button to open a one-month view and select a day. The
calendar header offers month and year dropdowns (as well as the prev/next
arrows) for quickly jumping across months and years. Exports a single component,
`DatePicker`. It is a controlled component — you own `value` (a
`Date | undefined`) and update it from `onChange`.
## What this is NOT
- Not a native `<input type="date">` — it renders a `@spark-web/text-input` with
a custom masked format and a popper-positioned calendar, not the browser's
native date control; do not expect native date semantics
- Not a date-range picker — `value` is a single `Date`; there is no start/end
range API
- Not a date-time picker — it selects a calendar day only, with no time
component
- Not its own labelled field — like the other form inputs it relies on a
wrapping `@spark-web/field` for the label and disabled state (it reads
`disabled` from `useFieldContext`)
## Props interface
`DatePicker` is the intersection of three groups. The calendar/value props are
its own; the remaining input props are forwarded to the underlying
`@spark-web/text-input` (`DateInputProps` minus the props the picker controls
itself — `buttonOnClick`, `buttonRef`, `children`, `onChange`, `value`).
| Prop | Type | Default | Notes |
| ------------------- | --------------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `value` | `Date \| undefined` | — | Controlled value of the field (required) |
| `onChange` | `(day: Date \| undefined) => void` | — | Fired on calendar select or valid typed input |
| `minDate?` | `Date` | — | Days before this date are not selectable |
| `maxDate?` | `Date` | — | Days after this date are not selectable |
| `initialMonth?` | `Date` | — | Calendar month shown initially when no `value` set |
| `fromYear?` | `number` | — | Earliest year in the caption's year dropdown. Defaults to `minDate`'s year, else 100 years before now. Does not restrict day navigation |
| `toYear?` | `number` | — | Latest year in the caption's year dropdown. Defaults to `maxDate`'s year, else 10 years after now. Does not restrict day navigation |
| `data?` | `DataAttributeMap` | — | Sets data attributes on the root `Stack` |
| `name?` | `string` | — | Forwarded to the text input |
| `placeholder?` | `string` | — | Forwarded; defaults internally to `DD/MM/YYYY` |
| `required?` | `boolean` | — | Forwarded to the text input |
| `autoComplete?` | `HTMLInputAutoCompleteAttribute` | — | Forwarded to the text input |
| `overflowStrategy?` | `'nowrap' \| 'truncate' \| 'breakword'` | — | Manages text overflow on the input |
| `onBlur?` | `FocusEventHandler<HTMLInputElement>` | — | Forwarded (note: the component also sets its own blur logic) |
| `onFocus?` | `FocusEventHandler<HTMLInputElement>` | — | Forwarded (note: the component also sets its own focus logic) |
| `onInput?` | `FormEventHandler<HTMLInputElement>` | — | Forwarded to the text input |
`type`, `pattern`, `inputMode`, and `children` are stripped from the forwarded
text-input props — they are fixed by the component.
## Token usage
`DatePicker` itself sets no raw color/spacing; it composes
`@spark-web/text-input` (which carries the field tokens) and renders the
calendar popover. Calendar positioning is handled by `react-popper`
(`placement: 'bottom-start'`, 8px offset) with `zIndex: 1` on the floating
layer. The trailing calendar trigger is a `BaseButton` (`@spark-web/button`)
with a `CalendarIcon` whose tone is `'disabled'` when the field is disabled,
else `'neutral'`. All visual tokens come from the composed Spark components and
the active theme.
## Composition
- Built from `Stack` (`@spark-web/stack`), `TextInput` + `InputAdornment`
(`@spark-web/text-input`), `BaseButton` (`@spark-web/button`), `CalendarIcon`
(`@spark-web/icon`), `useFieldContext` (`@spark-web/field`)
- The calendar is `CalendarSingle`, built on `react-day-picker`; positioning
uses `react-popper`
- The calendar header (arrow nav + month/year dropdowns) is a custom `Caption`
override (`calendar-caption.tsx`, `MonthYearCaption`). Arrows call
`useNavigation().goToMonth(previous/nextMonth)`; tapping the month/year opens
a full-card overlay list (also navigating via `goToMonth`). That list is a
`react-select` instance rendered inline (no body portal) and styled with Spark
tokens (`theme.components.textInput.menuOption`), matching the
`@spark-web/combobox` react-select approach. The year range only bounds the
dropdown list — it does not restrict day-by-day navigation
- Date parsing/formatting uses `date-fns` with the fixed format `dd/MM/yyyy`
- `disabled` is sourced from the surrounding `Field` via `useFieldContext`, not
a direct prop
### Role in the vendor-admin patterns
In the vendor-admin patterns `DatePicker` serves two roles: a date filter on the
list page and a date field on the form page. In both cases it is wrapped in a
`@spark-web/field` so the label and disabled state resolve correctly, and it is
driven as a controlled input from local/form state.
## Correct usage
### A date field inside a Field (form-page pattern)
```tsx
import { DatePicker } from '@spark-web/date-picker';
import { Field } from '@spark-web/field';
import { useState } from 'react';
function StartDateField() {
const [value, setValue] = useState<Date | undefined>();
return (
<Field label="Start date">
<DatePicker value={value} onChange={setValue} />
</Field>
);
}
```
### Constrained to a selectable window
```tsx
import { addDays, subDays } from 'date-fns';
<Field label="Appointment date">
<DatePicker
value={value}
onChange={setValue}
minDate={subDays(new Date(), 7)}
maxDate={addDays(new Date(), 30)}
/>
</Field>;
```
### Empty calendar opening on a specific month
```tsx
<Field label="Birth date">
<DatePicker
value={value}
onChange={setValue}
initialMonth={new Date('1990-01-01')}
/>
</Field>
```
## Do NOTs
- NEVER use `DatePicker` outside a `@spark-web/field` — it reads `disabled` from
`useFieldContext`, and the label/accessibility come from `Field`
- NEVER treat it as uncontrolled — `value` and `onChange` are required; without
them the typed/selected date has nowhere to go
- NEVER expect a non-`dd/MM/yyyy` mask — the input format is fixed in source; do
not pass `pattern`/`type`/`inputMode` to change it (they are stripped)
- NEVER use it for date ranges or date-times — `value` is a single `Date`; use a
range/time solution outside this package
- NEVER pass a string to `value` — it must be a `Date | undefined`; the string
masking is an internal detail of the input
## Component gaps / known limitations
- Single date only — no range or multi-date selection, and no time-of-day
- The mask is hard-coded to `dd/MM/yyyy`; there is no locale/format prop
- `disabled` cannot be set directly on `DatePicker` — it must come from the
enclosing `Field` context
- Consumer `onFocus`/`onBlur` are forwarded but the component attaches its own
focus/blur behaviour (placeholder seeding, cursor positioning), so custom
handlers run alongside that internal logic