# Calendar

## Introduction

Calendar is a low-level UI component that renders an interactive calendar grid for date selection. It provides the visual foundation for higher-level picker components like DatePicker, DateRangePicker, and MonthPicker. While you'll rarely use Calendar directly in forms, it's useful for building custom date selection interfaces or embedded calendar views where you need full control over the calendar behavior.

```tsx
<Calendar />
```

| Field  | Description | Default |
| ------ | ----------- | ------- |
| locale | —           | —       |

> ⚠️ **Usage Warning** ⚠️
>
> Calendar is a **building block component** intended for custom implementations:
>
> - **For standard date input**: Use `DatePicker` instead
> - **For date ranges**: Use `DateRangePicker` instead
> - **For month selection**: Use `MonthPicker` or `MonthRangePicker`
> - **Value type**: Calendar uses `Date` objects, not strings like picker components
> - **No input field**: Calendar is just the grid, with no text input

## Usage

```tsx
import { Calendar } from '@ceed/cds';

function DateViewer() {
  const [date, setDate] = useState<Date | null>(null);

  return (
    <Calendar
      value={date ? [date, undefined] : undefined}
      onChange={(dates) => setDate(dates[0] || null)}
    />
  );
}
```

## Examples

### Controlled

Parent component manages the calendar state.

```tsx
<Calendar />
```

### Uncontrolled

Calendar manages its own state internally.

```tsx
<Calendar />
```

### With Locale

Display calendar in different languages.

```tsx
<Calendar locale="de" />
```

### Range Selection

Enable selection of date ranges (start and end dates).

```tsx
<Calendar rangeSelection />
```

### Default Value with Range Selection

Pre-populate a date range when the calendar loads.

```tsx
<Calendar rangeSelection defaultValue={[new Date(2024, 2, 1), new Date(2024, 2, 20)]} />
```

### Only Month View

Show only the month grid without day selection.

```tsx
<Calendar
  views={['month']}
  defaultValue={[new Date(2024, 2, 1), undefined]}
/>
```

### All Views

Show both month and day views with navigation between them.

```tsx
<Calendar views={['month', 'day']} rangeSelection defaultValue={[new Date(2024, 2, 1), new Date(2024, 2, 20)]} />
```

### Month Selection

Use the calendar for month-level selection only.

```tsx
<Calendar view="month" value={[value, undefined]} onMonthChange={setValue} />
```

### Minimum Date

Restrict selection to dates on or after a minimum date.

```tsx
<Calendar minDate={new Date('2024-02-15')} rangeSelection />
```

### Maximum Date

Restrict selection to dates on or before a maximum date.

```tsx
<Calendar maxDate={new Date('2024-02-15')} rangeSelection />
```

### Disable Future Dates

Prevent selection of any future dates.

```tsx
<Calendar disableFuture rangeSelection />
```

### Disable Past Dates

Prevent selection of any past dates.

```tsx
<Calendar disablePast rangeSelection />
```

### Custom Date Disabling

Use `shouldDisableDate` to disable specific dates like weekends.

```tsx
<Calendar shouldDisableDate={date => [0, 6].includes(date.getDay())} />
```

### Weekends Disabled with Past Dates

Combine multiple restrictions: disable weekends and past dates.

```tsx
<Calendar shouldDisableDate={date => [0, 6].includes(date.getDay())} disablePast />
```

### Complex Date Restrictions

Disable weekends, past dates, and limit to one week ahead.

```tsx
<Calendar shouldDisableDate={date => [0, 6].includes(date.getDay()) || date.getTime() >= new Date().getTime() + 7 * 24 * 60 * 60 * 1000} disablePast />
```

## When to Use

### ✅ Good Use Cases

- **Custom date picker UI**: Building a specialized date selection interface
- **Embedded calendar**: Showing a calendar inline within a page layout
- **Dashboard widget**: Calendar as a date navigation widget
- **Event calendar**: Base for event/appointment calendars
- **Custom range pickers**: Building specialized range selection UI
- **Full control needed**: When you need direct access to calendar behavior

### ❌ When Not to Use

- **Form date input**: Use `DatePicker` for standard form fields
- **Date range forms**: Use `DateRangePicker` for start/end date pairs
- **Month selection forms**: Use `MonthPicker` or `MonthRangePicker`
- **Simple date display**: Use a text display or formatted date
- **Date with time**: Calendar doesn't include time selection

## Common Use Cases

### Embedded Calendar Widget

```tsx
function CalendarWidget() {
  const [selectedDate, setSelectedDate] = useState<Date>(new Date());

  return (
    <Box sx={{ p: 2, border: '1px solid', borderColor: 'divider', borderRadius: 'md' }}>
      <Typography level="title-md" mb={2}>
        Select a Date
      </Typography>
      <Calendar
        value={[selectedDate, undefined]}
        onChange={(dates) => dates[0] && setSelectedDate(dates[0])}
      />
      <Typography level="body-sm" mt={2}>
        Selected: {selectedDate.toLocaleDateString()}
      </Typography>
    </Box>
  );
}
```

### Event Calendar with Highlighted Dates

```tsx
function EventCalendar({ events }) {
  const [viewDate, setViewDate] = useState<Date>(new Date());

  // Get dates with events
  const eventDates = useMemo(() => {
    return events.map((e) => e.date.toDateString());
  }, [events]);

  const hasEvent = (date: Date) => eventDates.includes(date.toDateString());

  return (
    <Stack gap={2}>
      <Calendar
        value={[viewDate, undefined]}
        onChange={(dates) => dates[0] && setViewDate(dates[0])}
      />
      {hasEvent(viewDate) && (
        <Box>
          <Typography level="title-sm">Events on {viewDate.toLocaleDateString()}</Typography>
          {events
            .filter((e) => e.date.toDateString() === viewDate.toDateString())
            .map((event) => (
              <Typography key={event.id} level="body-sm">
                {event.title}
              </Typography>
            ))}
        </Box>
      )}
    </Stack>
  );
}
```

### Appointment Booking Calendar

```tsx
function BookingCalendar({ availableSlots, onBook }) {
  const [selectedDate, setSelectedDate] = useState<Date | null>(null);

  // Get available dates
  const availableDates = useMemo(() => {
    return new Set(availableSlots.map((slot) => slot.date.toDateString()));
  }, [availableSlots]);

  // Disable dates without available slots
  const shouldDisableDate = (date: Date) => {
    const isPast = date < new Date(new Date().setHours(0, 0, 0, 0));
    const hasNoSlots = !availableDates.has(date.toDateString());
    return isPast || hasNoSlots;
  };

  const slotsForSelectedDate = useMemo(() => {
    if (!selectedDate) return [];
    return availableSlots.filter(
      (slot) => slot.date.toDateString() === selectedDate.toDateString()
    );
  }, [selectedDate, availableSlots]);

  return (
    <Stack direction="row" gap={3}>
      <Calendar
        value={selectedDate ? [selectedDate, undefined] : undefined}
        onChange={(dates) => setSelectedDate(dates[0] || null)}
        shouldDisableDate={shouldDisableDate}
        disablePast
      />
      {selectedDate && (
        <Box sx={{ minWidth: 200 }}>
          <Typography level="title-sm" mb={1}>
            Available Times on {selectedDate.toLocaleDateString()}
          </Typography>
          <Stack gap={1}>
            {slotsForSelectedDate.map((slot) => (
              <Button
                key={slot.id}
                variant="outlined"
                size="sm"
                onClick={() => onBook(slot)}
              >
                {slot.time}
              </Button>
            ))}
          </Stack>
        </Box>
      )}
    </Stack>
  );
}
```

### Date Range Selection with Preview

```tsx
function DateRangeSelector({ onRangeSelect }) {
  const [range, setRange] = useState<[Date | undefined, Date | undefined]>([undefined, undefined]);

  const handleChange = (dates: [Date | undefined, Date | undefined]) => {
    setRange(dates);
    if (dates[0] && dates[1]) {
      onRangeSelect({ start: dates[0], end: dates[1] });
    }
  };

  const dayCount = useMemo(() => {
    if (!range[0] || !range[1]) return 0;
    const diff = range[1].getTime() - range[0].getTime();
    return Math.ceil(diff / (1000 * 60 * 60 * 24)) + 1;
  }, [range]);

  return (
    <Stack gap={2}>
      <Calendar
        rangeSelection
        value={range}
        onChange={handleChange}
        disablePast
      />
      <Box>
        {range[0] && (
          <Typography level="body-sm">
            Start: {range[0].toLocaleDateString()}
          </Typography>
        )}
        {range[1] && (
          <Typography level="body-sm">
            End: {range[1].toLocaleDateString()} ({dayCount} days)
          </Typography>
        )}
        {!range[0] && (
          <Typography level="body-sm" color="neutral">
            Select start date
          </Typography>
        )}
      </Box>
    </Stack>
  );
}
```

### Multi-Language Calendar

```tsx
function LocalizedCalendar({ userLocale }) {
  const [date, setDate] = useState<Date>(new Date());

  // Map common locales
  const getCalendarLocale = (locale: string) => {
    const localeMap: Record<string, string> = {
      'en-US': 'en',
      'ko-KR': 'ko',
      'ja-JP': 'ja',
      'de-DE': 'de',
      'fr-FR': 'fr',
      'zh-CN': 'zh',
    };
    return localeMap[locale] || 'en';
  };

  return (
    <Calendar
      value={[date, undefined]}
      onChange={(dates) => dates[0] && setDate(dates[0])}
      locale={getCalendarLocale(userLocale)}
    />
  );
}
```

### Month Navigation Widget

```tsx
function MonthNavigator({ onMonthChange }) {
  const [currentMonth, setCurrentMonth] = useState<Date>(new Date());

  const handleMonthChange = (date: Date) => {
    setCurrentMonth(date);
    onMonthChange(date);
  };

  return (
    <Box>
      <Typography level="title-md" mb={2}>
        {currentMonth.toLocaleDateString('en-US', { month: 'long', year: 'numeric' })}
      </Typography>
      <Calendar
        view="month"
        views={['month']}
        value={[currentMonth, undefined]}
        onMonthChange={handleMonthChange}
      />
    </Box>
  );
}
```

## Props and Customization

### Key Props

| Prop                | Type                                                      | Default   | Description                                      |
| ------------------- | --------------------------------------------------------- | --------- | ------------------------------------------------ |
| `value`             | `[Date \| undefined, Date \| undefined]`                  | -         | Controlled value as array `[startDate, endDate]` |
| `defaultValue`      | `[Date \| undefined, Date \| undefined]`                  | -         | Default value for uncontrolled mode              |
| `onChange`          | `(dates: [Date \| undefined, Date \| undefined]) => void` | -         | Change handler                                   |
| `rangeSelection`    | `boolean`                                                 | `false`   | Enable date range selection mode                 |
| `view`              | `'day' \| 'month'`                                        | `'day'`   | Current view mode                                |
| `views`             | `('day' \| 'month')[]`                                    | `['day']` | Available views for navigation                   |
| `locale`            | `string`                                                  | `'en'`    | Locale for month/day names                       |
| `minDate`           | `Date`                                                    | -         | Minimum selectable date                          |
| `maxDate`           | `Date`                                                    | -         | Maximum selectable date                          |
| `disableFuture`     | `boolean`                                                 | `false`   | Disable all future dates                         |
| `disablePast`       | `boolean`                                                 | `false`   | Disable all past dates                           |
| `shouldDisableDate` | `(date: Date) => boolean`                                 | -         | Custom function to disable specific dates        |
| `onMonthChange`     | `(date: Date) => void`                                    | -         | Callback when month changes                      |

### Value Format

Calendar uses `Date` objects instead of strings:

```tsx
// Single date selection (use undefined for second element)
<Calendar
  value={[new Date('2024-04-15'), undefined]}
  onChange={(dates) => console.log(dates[0])} // Date object
/>

// Range selection
<Calendar
  rangeSelection
  value={[new Date('2024-04-01'), new Date('2024-04-15')]}
  onChange={(dates) => {
    const [start, end] = dates;
    console.log('Start:', start); // Date object
    console.log('End:', end);     // Date object
  }}
/>
```

### View Modes

```tsx
// Day view only (default)
<Calendar views={['day']} />

// Month view only
<Calendar views={['month']} view="month" />

// Both views with navigation
<Calendar views={['month', 'day']} />
```

### Date Restrictions

```tsx
// Restrict to specific range
<Calendar
  minDate={new Date('2024-01-01')}
  maxDate={new Date('2024-12-31')}
/>

// Disable weekends
<Calendar
  shouldDisableDate={(date) => [0, 6].includes(date.getDay())}
/>

// Combine multiple restrictions
<Calendar
  disablePast
  disableFuture={false}
  maxDate={new Date(Date.now() + 30 * 24 * 60 * 60 * 1000)} // 30 days ahead
  shouldDisableDate={(date) => [0, 6].includes(date.getDay())}
/>
```

## Accessibility

Calendar includes built-in accessibility features:

### ARIA Attributes

- Calendar uses proper grid roles for navigation
- Days are announced with full date context
- Disabled dates are marked as `aria-disabled`
- Selected dates use `aria-selected`

### Keyboard Navigation

- **Arrow Keys**: Navigate between days
- **Page Up/Down**: Navigate between months
- **Home/End**: Jump to start/end of week
- **Enter/Space**: Select focused date
- **Tab**: Move focus to calendar navigation buttons

### Screen Reader Support

```tsx
// Days are announced with full context
<button aria-label="April 15, 2024">15</button>

// Month navigation buttons are descriptive
<button aria-label="Previous Month">←</button>
<button aria-label="Next Month">→</button>

// Disabled dates are announced
<button aria-label="April 20, 2024 (unavailable)" aria-disabled="true">20</button>
```

### Focus Management

- Focus remains within calendar during navigation
- Visual focus indicator on active day
- Disabled dates are not focusable

## Best Practices

### ✅ Do

1. **Use appropriate picker components for forms**: Default to DatePicker/DateRangePicker

```tsx
// ✅ Good: Use DatePicker for form input
<DatePicker
  label="Birth Date"
  value={date}
  onChange={(e) => setDate(e.target.value)}
/>
```

2. **Provide clear context when using Calendar directly**: Add surrounding UI to explain the calendar's purpose

```tsx
// ✅ Good: Clear context around calendar
<Box>
  <Typography level="title-md">Select Appointment Date</Typography>
  <Calendar value={[date, undefined]} onChange={handleChange} />
  <Typography level="body-sm">Selected: {date?.toLocaleDateString()}</Typography>
</Box>
```

3. **Use appropriate date restrictions**: Prevent invalid selections

```tsx
// ✅ Good: Reasonable constraints for booking
<Calendar
  disablePast
  maxDate={new Date(Date.now() + 90 * 24 * 60 * 60 * 1000)} // 90 days ahead
  shouldDisableDate={(date) => [0, 6].includes(date.getDay())} // No weekends
/>
```

4. **Handle the array value format correctly**: Calendar always uses `[start, end]` format

```tsx
// ✅ Good: Proper value handling
const [date, setDate] = useState<Date | null>(null);
<Calendar
  value={date ? [date, undefined] : undefined}
  onChange={(dates) => setDate(dates[0] || null)}
/>
```

### ❌ Don't

1. **Don't use Calendar when DatePicker suffices**: For standard form inputs, use the picker components

```tsx
// ❌ Bad: Using Calendar for simple date input
<form>
  <Calendar onChange={handleChange} /> {/* Missing label, not form-friendly */}
</form>

// ✅ Good: Use DatePicker
<DatePicker label="Date" value={value} onChange={handleChange} />
```

2. **Don't forget to handle both elements of the array**: Even for single selection

```tsx
// ❌ Bad: Ignoring array format
<Calendar onChange={(date) => setDate(date)} />

// ✅ Good: Proper array handling
<Calendar onChange={(dates) => setDate(dates[0])} />
```

3. **Don't mix Date objects with string values**: Calendar uses Date objects

```tsx
// ❌ Bad: Using string values
<Calendar value={["2024-04-15", undefined]} />

// ✅ Good: Using Date objects
<Calendar value={[new Date("2024-04-15"), undefined]} />
```

4. **Don't forget locale for international users**: Set appropriate locale

## Performance Considerations

### Memoize shouldDisableDate

For complex date validation, memoize the function:

```tsx
const shouldDisableDate = useCallback((date: Date) => {
  // Check if weekend
  if ([0, 6].includes(date.getDay())) return true;

  // Check against holiday list
  const dateString = date.toISOString().split('T')[0];
  return holidays.includes(dateString);
}, [holidays]); // Only recreate when holidays change

<Calendar shouldDisableDate={shouldDisableDate} />
```

### Avoid Inline Object Creation

When using controlled mode, avoid creating new arrays on every render:

```tsx
// ❌ Bad: New array on every render
<Calendar value={[date, undefined]} />

// ✅ Good: Memoize the value
const calendarValue = useMemo(
  () => (date ? [date, undefined] : undefined),
  [date]
);
<Calendar value={calendarValue} />
```

### Optimize Event Handlers

```tsx
const handleChange = useCallback((dates: [Date | undefined, Date | undefined]) => {
  setSelectedDate(dates[0] || null);
}, []);

<Calendar onChange={handleChange} />
```

### Lazy Date Calculations

For complex availability calculations:

```tsx
const disabledDates = useMemo(() => {
  // Pre-calculate disabled dates once
  return new Set(
    blockedDates.map((d) => d.toDateString())
  );
}, [blockedDates]);

const shouldDisableDate = useCallback((date: Date) => {
  return disabledDates.has(date.toDateString());
}, [disabledDates]);
```

Calendar is a foundational component for building custom date selection interfaces. For most use cases, prefer the higher-level picker components (DatePicker, DateRangePicker, MonthPicker) which provide built-in form integration, labels, and text input. Use Calendar directly when you need full control over the calendar UI or are building specialized date-related features.
