laif-ds
Version:
Design System di Laif con componenti React basati su principi di Atomic Design
493 lines (404 loc) • 17.6 kB
Markdown
# 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.