UNPKG

laif-ds

Version:

Design System di Laif con componenti React basati su principi di Atomic Design

493 lines (404 loc) 17.6 kB
# DatePicker ## Overview Date picker component that supports both **single date** and **date range** selection, with optional **time picker** columns. Composed of a trigger with calendar icon and a `Calendar` component in a popover. Supports min/max date constraints, allowed dates, custom display format, sizes, localization, clearable selection, and initial calendar month. --- ## Props ### Common Props | Prop | Type | Default | Description | | ---------------------- | --------------------------------------- | ------------------ | ---------------------------------------------------------------------------- | | `mode` | `"single" \| "range"` | `"single"` | Selection mode: single date or date range. | | `placeholder` | `string` | `"Seleziona data"` | Text when no date is selected. | | `dateFormat` | `string` | `"dd/MM/yyyy"` | `date-fns` format string for the trigger text. Auto-adjusts with `showTime`. | | `className` | `string` | `""` | Additional classes for the trigger element. | | `wrpClassName` | `string` | `""` | Additional classes for the outer wrapper `<div>`. | | `labelClassName` | `string` | `""` | Additional classes for the label element. | | `label` | `string \| React.ReactNode` | `undefined` | Label displayed above the date picker. | | `disabled` | `boolean` | `false` | Disables interactions and the popover. | | `size` | `"sm" \| "default" \| "lg"` | `"default"` | Trigger size affecting height and icon size. | | `clearable` | `boolean` | `false` | Show a clear (X) button to reset the selection. | | `onClear` | `() => void` | `undefined` | Callback fired when the value is cleared via the clear button. | | `minDate` | `Date` | `undefined` | Minimum selectable date. Dates before this are disabled. | | `maxDate` | `Date` | `undefined` | Maximum selectable date. Dates after this are disabled. | | `availableDates` | `Date[]` | `undefined` | Only these dates are selectable; all others are disabled. | | `locale` | `Partial<Locale>` | `it` (Italian) | Locale for date formatting (from date-fns). | | `initialCalendarMonth` | `Date` | `undefined` | Initial month to display in calendar (overridden by value). | | `numberOfMonths` | `number` | `1` | Number of months to display side-by-side (useful for range mode). | | `customCalendarProps` | `React.ComponentProps<typeof Calendar>` | `undefined` | Custom props forwarded to the underlying Calendar component. | | `id` | `string` | auto-generated | HTML id attribute for the trigger element. | | `data-testid` | `string` | `undefined` | Test identifier attribute for E2E testing (e.g. Playwright). | #### Deprecated Common Props | Prop | Type | Replacement | Description | | --------------- | -------- | ----------- | ---------------------------------------------------------- | | `firstDate` | `Date` | `minDate` | ⚠️ Deprecated. Use `minDate` instead. | | `lastDate` | `Date` | `maxDate` | ⚠️ Deprecated. Use `maxDate` instead. | | `buttonVariant` | `string` | `className` | ⚠️ Deprecated. This prop is unused. Style via `className`. | ### Single Mode Props (mode="single" or undefined) | Prop | Type | Default | Description | | ------------- | ----------------------------------- | ----------- | ------------------------------------------------------------- | | `value` | `Date` | `undefined` | Controlled selected date. | | `onChange` | `(date: Date \| undefined) => void` | `undefined` | Called when the date changes. | | `showTime` | `boolean` | `false` | Show time picker columns (HH, MM) alongside the calendar. | | `withSeconds` | `boolean` | `false` | Show seconds column in the time picker (requires `showTime`). | | `minuteStep` | `number` | `1` | Step interval for minute options (`1`-`59`, requires `showTime`). | | `secondStep` | `number` | `1` | Step interval for second options (`1`-`59`, useful with `withSeconds`). | ### Range Mode Props (mode="range") | Prop | Type | Default | Description | | ---------- | ------------------------------------------ | ----------- | ----------------------------------- | | `value` | `DateRange` (`{ from?: Date, to?: Date }`) | `undefined` | Controlled selected date range. | | `onChange` | `(range: DateRange \| undefined) => void` | `undefined` | Called when the date range changes. | --- ## Behavior ### Single Mode - **Selection**: Click a date to select it. The selected date is highlighted. - **Display**: Shows the formatted date in the trigger (e.g., "13/01/2025"). ### Range Mode - **Selection**: Click to set the start date, click again to set the end date. The range is highlighted. - **Display**: Shows "from - to" format in the trigger (e.g., "13/01/2025 - 20/01/2025"). - **Multiple months**: Use `numberOfMonths={2}` to show two months side by side for easier range selection. ### Time Picker (Single Mode Only) - **Activation**: Set `showTime` to display hour and minute columns next to the calendar. - **Seconds**: Set `withSeconds` alongside `showTime` to add a seconds column. - **Steps**: Use `minuteStep` and `secondStep` to control minute/second increments in their respective columns. - **Auto-close**: When `showTime` is enabled, the popover stays open after selecting a date so the user can adjust the time. Without `showTime`, the popover closes on date selection. - **Format auto-detection**: If no `dateFormat` is provided, the component automatically uses `"dd/MM/yyyy HH:mm"` (or `"dd/MM/yyyy HH:mm:ss"` with `withSeconds`). - **Time preservation**: When changing the date, the previously selected time is preserved. ### Clearable - **Clear button**: When `clearable` is `true` and a value is selected, an X button appears in the trigger. - **Callback**: The `onClear` callback fires when the clear button is clicked. - **Works in both modes**: Clearing resets the value to `undefined` in both single and range modes. ### Common Behavior - **Disabled dates**: `minDate`, `maxDate`, and `availableDates` are combined into the `Calendar`'s `disabled` matcher array. Legacy `firstDate`/`lastDate` are also supported. - **Disabled state**: When `disabled` is true, the popover will not open and the trigger is non-interactive with reduced opacity. - **Formatting**: `dateFormat` is applied via `date-fns/format` to the selected date(s) in the trigger. - **Calendar month**: The calendar displays the month of the selected date (or `from` date in range mode), or `initialCalendarMonth` if no date is selected. - **State synchronization**: The component syncs internal state with the `value` prop via useEffect. - **Localization**: The `locale` prop affects both the calendar display and the formatted date in the trigger. --- ## Examples ### Basic ```tsx import { DatePicker } from "laif-ds"; export function BasicDatePicker() { return <DatePicker />; } ``` ### Controlled with State ```tsx import { DatePicker } from "laif-ds"; import { useState } from "react"; export function ControlledDatePicker() { const [date, setDate] = useState<Date | undefined>(new Date()); return ( <DatePicker value={date} onChange={setDate} placeholder="Seleziona una data" dateFormat="PPP" className="w-[300px]" label="Data di nascita" /> ); } ``` ### Range Mode - Basic ```tsx import { DatePicker } from "laif-ds"; import { useState } from "react"; import { DateRange } from "react-day-picker"; export function RangeDatePicker() { const [range, setRange] = useState<DateRange | undefined>(); return ( <DatePicker mode="range" value={range} onChange={setRange} placeholder="Seleziona intervallo" /> ); } ``` ### Range Mode - With Two Months ```tsx import { DatePicker } from "laif-ds"; import { addDays } from "date-fns"; import { useState } from "react"; import { DateRange } from "react-day-picker"; export function RangeWithTwoMonths() { const [range, setRange] = useState<DateRange | undefined>({ from: new Date(), to: addDays(new Date(), 7), }); return ( <DatePicker mode="range" value={range} onChange={setRange} placeholder="Seleziona intervallo" numberOfMonths={2} label="Periodo di prenotazione" /> ); } ``` ### Range Mode - With Constraints ```tsx import { DatePicker } from "laif-ds"; import { addDays } from "date-fns"; import { useState } from "react"; import { DateRange } from "react-day-picker"; export function RangeWithConstraints() { const today = new Date(); const [range, setRange] = useState<DateRange | undefined>(); return ( <DatePicker mode="range" value={range} onChange={setRange} placeholder="Seleziona (prossimi 30 giorni)" firstDate={today} lastDate={addDays(today, 30)} numberOfMonths={2} /> ); } ``` ### Clearable ```tsx import { DatePicker } from "laif-ds"; import { useState } from "react"; export function ClearableDatePicker() { const [date, setDate] = useState<Date | undefined>(new Date()); return ( <DatePicker value={date} onChange={setDate} placeholder="Seleziona data" clearable onClear={() => console.log("cleared!")} label="Data cancellabile" /> ); } ``` ### With Time Picker ```tsx import { DatePicker } from "laif-ds"; import { useState } from "react"; export function DateTimePicker() { const [date, setDate] = useState<Date | undefined>(); return ( <DatePicker value={date} onChange={setDate} showTime minuteStep={5} label="Data e ora" placeholder="Seleziona data e ora" /> ); } ``` ### With Time Picker and Seconds ```tsx import { DatePicker } from "laif-ds"; import { useState } from "react"; export function DateTimeWithSeconds() { const [date, setDate] = useState<Date | undefined>(new Date()); return ( <DatePicker value={date} onChange={setDate} showTime withSeconds minuteStep={5} secondStep={10} label="Data e ora con secondi" /> ); } ``` ### With Min/Max Dates ```tsx import { DatePicker } from "laif-ds"; import { useState } from "react"; export function WithMinMaxDates() { const [date, setDate] = useState<Date | undefined>(); return ( <DatePicker value={date} onChange={setDate} placeholder="Seleziona una data nel range" minDate={new Date("2025-09-20")} maxDate={new Date("2025-09-28")} /> ); } ``` ### With Available Dates ```tsx import { DatePicker } from "laif-ds"; import { useState } from "react"; export function WithAvailableDates() { const [date, setDate] = useState<Date | undefined>(); return ( <DatePicker value={date} onChange={setDate} placeholder="Scegli una delle date disponibili" availableDates={[ new Date("2025-09-22"), new Date("2025-09-25"), new Date("2025-09-27"), ]} /> ); } ``` ### With Localization ```tsx import { DatePicker } from "laif-ds"; import { enGB } from "date-fns/locale"; import { useState } from "react"; export function LocalizedDatePicker() { const [date, setDate] = useState<Date | undefined>(); return ( <DatePicker value={date} onChange={setDate} placeholder="Select a date" locale={enGB} dateFormat="PPP" label="Date (English)" /> ); } ``` ### With Initial Calendar Month ```tsx import { DatePicker } from "laif-ds"; import { useState } from "react"; export function WithInitialMonth() { const [date, setDate] = useState<Date | undefined>(); return ( <DatePicker value={date} onChange={setDate} placeholder="Seleziona una data" initialCalendarMonth={new Date("2000-01-01")} label="Data (calendario inizia nel 2000)" /> ); } ``` ### With Custom Calendar Props ```tsx import { DatePicker } from "laif-ds"; import { useState } from "react"; export function WithCustomCalendar() { const [date, setDate] = useState<Date | undefined>(); return ( <DatePicker value={date} onChange={setDate} placeholder="Seleziona una data" customCalendarProps={{ mode: "single", selected: date, onSelect: setDate, numberOfMonths: 2, showOutsideDays: true, }} label="Data (calendario a 2 mesi)" /> ); } ``` ### In Dialog or Drawer ```tsx import { DatePicker } from "laif-ds"; import { Dialog, DialogContent, DialogTrigger } from "laif-ds"; import { Button } from "laif-ds"; import { useState } from "react"; export function DatePickerInDialog() { const [date, setDate] = useState<Date | undefined>(); return ( <Dialog> <DialogTrigger asChild> <Button>Apri Dialog</Button> </DialogTrigger> <DialogContent> <DatePicker value={date} onChange={setDate} placeholder="Seleziona una data" label="Data" /> </DialogContent> </Dialog> ); } ``` ### In Drawer ```tsx import { DatePicker } from "laif-ds"; import { Drawer, DrawerContent, DrawerTrigger, DrawerHeader, DrawerTitle, DrawerDescription, DrawerFooter, DrawerClose, } from "laif-ds"; import { Button } from "laif-ds"; import { useState } from "react"; export function DatePickerInDrawer() { const [date, setDate] = useState<Date | undefined>(new Date("2023-08-12")); return ( <Drawer> <DrawerTrigger asChild> <Button variant="outline">Apri Drawer con DatePicker</Button> </DrawerTrigger> <DrawerContent> <DrawerHeader> <DrawerTitle>Seleziona una data</DrawerTitle> <DrawerDescription> Scegli una data dal calendario qui sotto. </DrawerDescription> </DrawerHeader> <div className="px-4 py-4"> <DatePicker placeholder="Seleziona una data" value={date} onChange={setDate} /> </div> {date && ( <div className="text-d-secondary-foreground px-4 text-sm"> Data selezionata: {date.toLocaleDateString("it-IT")} </div> )} <DrawerFooter> <DrawerClose asChild> <Button variant="outline">Chiudi</Button> </DrawerClose> </DrawerFooter> </DrawerContent> </Drawer> ); } ``` --- ## Notes - **Sizing**: Use `size` to control the trigger height and icon size (sm: h-8, default: h-9, lg: h-10). - **Popover width**: The popover content automatically sizes to fit the Calendar (and time columns when `showTime` is enabled). - **Internationalization**: Use `locale` prop for calendar localization and `dateFormat` for display formatting. - **Deprecated props**: `firstDate`/`lastDate` → use `minDate`/`maxDate`. `buttonVariant` → use `className`. - **Accessibility**: The trigger has proper ARIA attributes and keyboard navigation support. - **State management**: Component maintains internal state but syncs with external `value` prop. - **Range mode**: Use `mode="range"` for date range selection. The `value` and `onChange` types change accordingly. `showTime` is not available in range mode. - **Multiple months**: In range mode, use `numberOfMonths={2}` to display two months side by side for better UX. - **DateRange type**: Import `DateRange` from `react-day-picker` when using range mode: `{ from?: Date, to?: Date }`. - **Clearable**: Use `clearable` to allow users to reset the selection. Works in both single and range modes. - **Label styling**: Use `labelClassName` to apply custom styles to the label element.