UNPKG

@spark-web/date-picker

Version:

--- title: DatePicker storybookPath: forms-date-picker isExperimentalPackage: true ---

160 lines (132 loc) 9.57 kB
# @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