laif-ds
Version:
Design System di Laif con componenti React basati su principi di Atomic Design
280 lines (234 loc) • 10.8 kB
Markdown
# Calendar
## Overview
Flexible calendar component built on `react-day-picker`. Supports single, multiple, and range selections, localization, week settings, and rich customization via class names and sub-components. Features custom month/year dropdowns for easy navigation.
---
## Props
Props extend `DayPicker` props with additional customization options.
| Prop | Type | Default | Description |
| ----------------- | ----------------------------------------------------------------------------- | ----------- | ----------------------------------------------- |
| `mode` | `"default" \| "single" \| "multiple" \| "range"` | `"default"` | Selection mode. |
| `selected` | `Date \| Date[] \| DateRange` | `undefined` | Controlled selection value. |
| `defaultMonth` | `Date` | `undefined` | Month to display initially. |
| `minDate` | `Date` | `undefined` | Minimum selectable date (also sets startMonth). |
| `maxDate` | `Date` | `undefined` | Maximum selectable date (also sets endMonth). |
| `disabled` | `Matcher \| Matcher[]` | `undefined` | Disabled dates (combined with minDate/maxDate). |
| `showOutsideDays` | `boolean` | `true` | Show days from adjacent months. |
| `ISOWeek` | `boolean` | `false` | Use ISO week numbering. |
| `fixedWeeks` | `boolean` | `false` | Always show 6 weeks. |
| `numberOfMonths` | `number` | `1` | Number of months to display. |
| `locale` | `Locale` | `undefined` | Locale object for formatting (from date-fns). |
| `weekStartsOn` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6` | `0` | Start of the week (0=Sun, 1=Mon, ...). |
| `buttonVariant` | `"default" \| "destructive" \| "outline" \| "secondary" \| "ghost" \| "link"` | `"ghost"` | Variant for navigation and day buttons. |
| `formatters` | `Partial<DayPickerFormatters>` | `{}` | Custom label formatters. |
| `components` | `Partial<DayPickerComponents>` | `{}` | Custom component overrides. |
| `className` | `string` | `""` | Additional container classes. |
| `classNames` | `Partial<DayPickerClassNames>` | `{}` | Custom class names for internal elements. |
| `onSelect` | `(value: Date \| Date[] \| DateRange \| undefined) => void` | `undefined` | Called when selection changes. |
| `onDayClick` | `(day: Date) => void` | `undefined` | Called on day click. |
| `onMonthChange` | `(month: Date) => void` | `undefined` | Called when the displayed month changes. |
---
## Behavior
- **Selection modes**: Supports default (no selection), single date, multiple dates, and date range selection with visual feedback.
- **Custom caption**: Features custom month/year dropdowns using AppSelect for easy navigation across months and years.
- **Date constraints**: `minDate` and `maxDate` automatically disable dates outside the range and restrict month/year navigation.
- **Disabled dates**: The `disabled` prop is combined with `minDate`/`maxDate` constraints into a unified matcher array.
- **Navigation**: Previous/next month buttons use `buttonVariant` styling and are disabled when reaching min/max boundaries.
- **Year range**: By default, allows navigation 50 years before and after the current year (overridden by minDate/maxDate).
- **Localization**: Supply `locale`, `weekStartsOn`, and `formatters` for internationalization support.
- **Visual feedback**: Today's date shows a dot indicator, selected dates are highlighted, range selections show start/middle/end styling.
- **Accessibility**: Full keyboard navigation and focus management with proper ARIA attributes.
---
## Examples
### Basic
```tsx
import { Calendar } from "laif-ds";
export function BasicCalendar() {
return (
<Calendar
className="border-d-border rounded-md border"
showOutsideDays
onDayClick={(d) => console.log("day clicked", d)}
onMonthChange={(m) => console.log("month", m)}
/>
);
}
```
### With Min/Max Date Constraints
```tsx
import { Calendar } from "laif-ds";
import { useState } from "react";
export function MinMaxCalendar() {
const [date, setDate] = useState<Date | undefined>(new Date());
return (
<Calendar
mode="single"
selected={date}
onSelect={setDate}
minDate={new Date(2001, 8, 11)}
maxDate={new Date(2026, 11, 10)}
className="border-d-border rounded-md border"
/>
);
}
```
### Single Selection
```tsx
import { useState } from "react";
import { Calendar } from "laif-ds";
export function SingleSelection() {
const [date, setDate] = useState<Date | undefined>(new Date());
return (
<div className="flex flex-col gap-2">
<Calendar
mode="single"
selected={date}
onSelect={setDate}
className="border-d-border rounded-md border"
/>
<p className="text-d-secondary-foreground text-center text-sm">
{date ? date.toDateString() : "Nessuna data selezionata"}
</p>
</div>
);
}
```
### Multiple Selection
```tsx
import { useState } from "react";
import { Calendar } from "laif-ds";
export function MultipleSelection() {
const [dates, setDates] = useState<Date[] | undefined>([new Date()]);
return (
<div className="flex flex-col gap-2">
<Calendar
mode="multiple"
selected={dates}
onSelect={setDates}
className="border-d-border rounded-md border"
/>
<p className="text-d-secondary-foreground text-center text-sm">
{dates && dates.length > 0
? `${dates.length} date selezionate`
: "Nessuna data selezionata"}
</p>
</div>
);
}
```
### Range Selection
```tsx
import { useState } from "react";
import { Calendar } from "laif-ds";
import type { DateRange } from "react-day-picker";
export function RangeSelection() {
const [range, setRange] = useState<DateRange | undefined>({
from: new Date(),
to: new Date(),
});
return (
<div className="flex flex-col gap-2">
<Calendar
mode="range"
selected={range}
onSelect={setRange}
className="border-d-border rounded-md border"
numberOfMonths={2}
showOutsideDays
/>
<p className="text-d-secondary-foreground text-center text-sm">
{range?.from && range?.to
? `${range.from.toDateString()} - ${range.to.toDateString()}`
: range?.from
? `Da ${range.from.toDateString()}`
: "Seleziona un intervallo"}
</p>
</div>
);
}
```
### Disabled Dates
```tsx
import { useState } from "react";
import { Calendar } from "laif-ds";
import { addDays } from "date-fns";
export function DisabledDates() {
const [date, setDate] = useState<Date | undefined>(new Date());
const disabledDays = [
{ from: addDays(new Date(), 1), to: addDays(new Date(), 5) },
new Date(new Date().setDate(15)),
{ before: addDays(new Date(), -10) },
];
return (
<Calendar
mode="single"
selected={date}
onSelect={setDate}
disabled={disabledDays}
className="border-d-border rounded-md border"
/>
);
}
```
### Localized (Italian) and Week Start
```tsx
import { Calendar } from "laif-ds";
import { it } from "date-fns/locale";
import { format } from "date-fns";
export function LocalizedCalendar() {
return (
<Calendar
className="border-d-border rounded-md border"
locale={it}
weekStartsOn={1}
formatters={{
formatMonthCaption: (date: Date) =>
format(date, "MMMM yyyy", { locale: it }),
formatWeekdayName: (date: Date) =>
format(date, "EEEEEE", { locale: it }),
}}
/>
);
}
```
### ISO Week, Fixed Weeks, Multiple Months
```tsx
import { Calendar } from "laif-ds";
export function ISOWeekCalendar() {
return (
<Calendar
className="border-d-border rounded-md border"
ISOWeek
weekStartsOn={1}
/>
);
}
export function FixedWeeksCalendar() {
return (
<Calendar
className="border-d-border rounded-md border"
fixedWeeks
showOutsideDays
/>
);
}
export function MultiMonthCalendar() {
return (
<Calendar
className="border-d-border rounded-md border"
numberOfMonths={2}
showOutsideDays
/>
);
}
```
---
## Notes
- **Custom caption component**: The calendar uses a custom `MonthCaption` component with searchable AppSelect dropdowns for month and year selection.
- **Month/year navigation**: Months and years outside the minDate/maxDate range are automatically disabled in the dropdowns.
- **Default year range**: Without minDate/maxDate, the calendar allows navigation 50 years before and after the current year.
- **Customization**: Use `classNames` and `components` props to override internal parts and styling.
- **Button variants**: `buttonVariant` controls the appearance of navigation buttons and day buttons.
- **Range selection**: Range mode highlights start, middle, and end days with appropriate rounded edges and background colors.
- **Today indicator**: Current date shows a small dot at the bottom of the day cell.
- **Design tokens**: Uses design tokens for consistent theming across light/dark modes.
- **Performance**: Efficient rendering with React.useMemo for computed values and proper ref management.