# DateRangePicker

## Introduction

DateRangePicker is a form input component that allows users to select a date range (start date and end date) from a calendar popup or by typing directly into the input field. It provides flexible date formatting options, date range restrictions, and supports both controlled and uncontrolled modes. DateRangePicker is ideal for booking systems, reporting filters, scheduling, and any scenario requiring a start-to-end date selection.

```tsx
<DateRangePicker onChange={onChange} />
```

| Field           | Description | Default |
| --------------- | ----------- | ------- |
| name            | —           | —       |
| value           | —           | —       |
| defaultValue    | —           | —       |
| disabled        | —           | —       |
| required        | —           | —       |
| label           | —           | —       |
| error           | —           | —       |
| helperText      | —           | —       |
| minDate         | —           | —       |
| maxDate         | —           | —       |
| disableFuture   | —           | —       |
| disablePast     | —           | —       |
| format          | —           | —       |
| displayFormat   | —           | —       |
| inputReadOnly   | —           | —       |
| hideClearButton | —           | —       |
| size            | —           | —       |

> ⚠️ **Usage Warning** ⚠️
>
> DateRangePicker involves complex date range handling:
>
> - **Value format**: Values use the format `"startDate - endDate"` (e.g., `"2024/04/01 - 2024/04/15"`)
> - **Format consistency**: Both dates must match the `format` prop
> - **Start/End validation**: Ensure start date is before end date
> - **Range span**: Consider maximum range limits for performance and UX

> 💡 **Use built-in form props**
>
> This component natively supports form elements such as `label` and `helperText` props.
> When building forms, use these built-in props instead of manually composing labels and helper text with Typography.

## Usage

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

function DateRangeForm() {
  const [dateRange, setDateRange] = useState('');

  return (
    <DateRangePicker
      label="Select Date Range"
      value={dateRange}
      onChange={(e) => setDateRange(e.target.value)}
    />
  );
}
```

## Examples

### Playground

Interactive example with all controls.

```tsx
<DateRangePicker onChange={onChange} />
```

### Sizes

DateRangePicker supports three sizes for different layouts.

```tsx
<Stack gap={2}>
  <DateRangePicker size="sm" />
  <DateRangePicker size="md" />
  <DateRangePicker size="lg" />
</Stack>
```

### Disabled

Prevent user interaction when disabled.

```tsx
<DateRangePicker
  onChange={onChange}
  disabled
/>
```

### With Label

Add a label above the date range picker.

```tsx
<DateRangePicker
  onChange={onChange}
  label="Date Range"
/>
```

### With Helper Text

Provide additional guidance below the input.

```tsx
<DateRangePicker
  onChange={onChange}
  label="Date"
  helperText="Please select a date"
/>
```

### Error State

Show validation errors with error styling.

```tsx
<DateRangePicker
  onChange={onChange}
  label="Date"
  helperText="Please select a date"
  error
/>
```

### Required Field

Mark the field as required in forms.

```tsx
<DateRangePicker
  onChange={onChange}
  label="Label"
  helperText="I'm helper text"
  required
/>
```

### Minimum Date

Restrict selection to dates on or after a minimum date.

```tsx
<DateRangePicker
  onChange={onChange}
  minDate="2024-04-10"
/>
```

### Maximum Date

Restrict selection to dates on or before a maximum date.

```tsx
<DateRangePicker
  onChange={onChange}
  maxDate="2024-04-10"
/>
```

### Disable Future Dates

Prevent selection of dates in the future.

```tsx
<DateRangePicker
  onChange={onChange}
  disableFuture
/>
```

### Disable Past Dates

Prevent selection of dates in the past.

```tsx
<DateRangePicker
  onChange={onChange}
  disablePast
/>
```

### Controlled

Parent component manages the date range state.

```tsx
<Stack gap={2}>
  <DateRangePicker {...args} onChange={e => {
    args.onChange?.(e);
    setValue(e.target.value);
  }} value={value} />
  <Button onClick={() => {
    const [start, end] = value.split(' - ');
    function shiftMonth(dateString: string) {
      const currentValue = new Date(dateString);
      currentValue.setMonth(currentValue.getMonth() + 1);
      const year = currentValue.getFullYear();
      const month = String(currentValue.getMonth() + 1).padStart(2, '0');
      const day = String(currentValue.getDate()).padStart(2, '0');
      return `${year}/${month}/${day}`;
    }
    setValue(`${shiftMonth(start)} - ${shiftMonth(end)}`);
  }}>
  Shift Month
</Button>
</Stack>
```

### Uncontrolled

Component manages its own state internally.

```tsx
<DateRangePicker
  onChange={onChange}
  label="Uncontrolled DateRangePicker"
  helperText="Please select a date"
  defaultValue="2024/04/01 - 2025/04/01"
/>
```

### With Formats

Different value formats for the `onChange` event.

```tsx
<Stack gap={2}>
  <DateRangePicker {...args} value={value1} label="YYYY.MM.DD" name="YYYY.MM.DD" format="YYYY.MM.DD" onChange={e => {
    setValue1(e.target.value);
    args.onChange?.(e);
  }} />
  <DateRangePicker {...args} value={value2} label="YYYY/MM/DD" name="YYYY/MM/DD" format="YYYY/MM/DD" onChange={e => {
    setValue2(e.target.value);
    args.onChange?.(e);
  }} />
  <DateRangePicker {...args} value={value3} label="MM/DD/YYYY" name="MM/DD/YYYY" format="MM/DD/YYYY" onChange={e => {
    setValue3(e.target.value);
    args.onChange?.(e);
  }} />
  <DateRangePicker {...args} value={value4} label="YYYY-MM-DD" name="YYYY-MM-DD" format="YYYY-MM-DD" onChange={e => {
    setValue4(e.target.value);
    args.onChange?.(e);
  }} />
  <DateRangePicker {...args} value={value5} label="DD/MM/YYYY" name="DD/MM/YYYY" format="DD/MM/YYYY" onChange={e => {
    setValue5(e.target.value);
    args.onChange?.(e);
  }} />
  <DateRangePicker {...args} value={value6} label="DD.MM.YYYY" name="DD.MM.YYYY" format="DD.MM.YYYY" onChange={e => {
    setValue6(e.target.value);
    args.onChange?.(e);
  }} />
</Stack>
```

### With Display Formats

Different display formats shown in the input field.

```tsx
<Stack gap={2}>
  <DateRangePicker {...args} value={value1} label="YYYY.MM.DD" name="YYYY.MM.DD" displayFormat="YYYY.MM.DD" onChange={e => {
    setValue1(e.target.value);
    args.onChange?.(e);
  }} />
  <DateRangePicker {...args} value={value2} label="YYYY/MM/DD" name="YYYY/MM/DD" displayFormat="YYYY/MM/DD" onChange={e => {
    setValue2(e.target.value);
    args.onChange?.(e);
  }} />
  <DateRangePicker {...args} value={value3} label="MM/DD/YYYY" name="MM/DD/YYYY" displayFormat="MM/DD/YYYY" onChange={e => {
    setValue3(e.target.value);
    args.onChange?.(e);
  }} />
  <DateRangePicker {...args} value={value4} label="YYYY-MM-DD" name="YYYY-MM-DD" displayFormat="YYYY-MM-DD" onChange={e => {
    setValue4(e.target.value);
    args.onChange?.(e);
  }} />
  <DateRangePicker {...args} value={value5} label="DD/MM/YYYY" name="DD/MM/YYYY" displayFormat="DD/MM/YYYY" onChange={e => {
    setValue5(e.target.value);
    args.onChange?.(e);
  }} />
  <DateRangePicker {...args} value={value6} label="DD.MM.YYYY" name="DD.MM.YYYY" displayFormat="DD.MM.YYYY" onChange={e => {
    setValue6(e.target.value);
    args.onChange?.(e);
  }} />
</Stack>
```

### Input Read Only

Allow calendar selection only, prevent typing.

```tsx
<DateRangePicker
  onChange={onChange}
  defaultValue="2024/05/03 - 2024/06/03"
  inputReadOnly
/>
```

### Read Only

Fully read-only state with no interaction.

```tsx
<DateRangePicker
  onChange={onChange}
  value="2024/05/03 - 2024/06/03"
  readOnly
/>
```

### Hide Clear Button

Remove the clear button from the calendar popup.

```tsx
<DateRangePicker
  onChange={onChange}
  defaultValue="2024/05/03 - 2024/06/03"
  hideClearButton
/>
```

### With Reset Button

Example with an external reset button.

```tsx
<div style={{
  display: 'flex',
  gap: '10px'
}}>
<DateRangePicker {...props} value={value} onChange={event => {
  setValue(event.target.value);
}} />
<Button onClick={() => setValue('')}>Reset</Button>
</div>
```

## When to Use

### ✅ Good Use Cases

- **Booking systems**: Hotel check-in/out, car rental periods
- **Report filtering**: Date range filters for analytics and reports
- **Event scheduling**: Conference dates, project timelines
- **Leave requests**: Vacation start and end dates
- **Subscription periods**: Billing cycles, membership durations
- **Data exports**: Selecting date ranges for exporting data

### ❌ When Not to Use

- **Single date selection**: Use DatePicker instead
- **Month/Year ranges**: Use MonthRangePicker for month-level granularity
- **Predefined periods**: For "Last 7 days", "This month", use dropdown selection
- **Time ranges**: For time-based ranges, use dedicated time components
- **Recurring dates**: For repeating schedules, consider a custom solution

## Common Use Cases

### Booking Form

```tsx
function BookingForm() {
  const [dates, setDates] = useState('');
  const [error, setError] = useState('');

  const validateRange = (value) => {
    if (!value) return 'Please select dates';
    const [start, end] = value.split(' - ');
    const startDate = new Date(start);
    const endDate = new Date(end);
    const days = (endDate - startDate) / (1000 * 60 * 60 * 24);
    if (days > 30) return 'Maximum stay is 30 days';
    if (days < 1) return 'Minimum stay is 1 night';
    return '';
  };

  const handleChange = (e) => {
    const value = e.target.value;
    setDates(value);
    setError(validateRange(value));
  };

  return (
    <form>
      <DateRangePicker
        label="Check-in / Check-out"
        value={dates}
        onChange={handleChange}
        error={!!error}
        helperText={error || 'Select your stay dates'}
        disablePast
        required
      />
      <Button type="submit" disabled={!!error || !dates}>
        Search Availability
      </Button>
    </form>
  );
}
```

### Report Date Filter

```tsx
function ReportFilters({ onFilter }) {
  const [dateRange, setDateRange] = useState('');

  const handleApply = () => {
    if (!dateRange) return;
    const [startDate, endDate] = dateRange.split(' - ');
    onFilter({ startDate, endDate });
  };

  return (
    <Stack direction="row" gap={2} alignItems="flex-end">
      <DateRangePicker
        label="Report Period"
        value={dateRange}
        onChange={(e) => setDateRange(e.target.value)}
        disableFuture
        helperText="Select date range for the report"
      />
      <Button onClick={handleApply}>Apply Filter</Button>
    </Stack>
  );
}
```

### Leave Request Form

```tsx
function LeaveRequestForm({ minDate, maxDate }) {
  const [leaveDates, setLeaveDates] = useState('');
  const [leaveType, setLeaveType] = useState('annual');

  // Calculate business days
  const getBusinessDays = (dateRange) => {
    if (!dateRange) return 0;
    const [start, end] = dateRange.split(' - ').map(d => new Date(d));
    let count = 0;
    const current = new Date(start);
    while (current <= end) {
      const day = current.getDay();
      if (day !== 0 && day !== 6) count++;
      current.setDate(current.getDate() + 1);
    }
    return count;
  };

  const businessDays = getBusinessDays(leaveDates);

  return (
    <form>
      <Select
        label="Leave Type"
        value={leaveType}
        onChange={(e) => setLeaveType(e.target.value)}
      >
        <Option value="annual">Annual Leave</Option>
        <Option value="sick">Sick Leave</Option>
        <Option value="personal">Personal Leave</Option>
      </Select>

      <DateRangePicker
        label="Leave Period"
        value={leaveDates}
        onChange={(e) => setLeaveDates(e.target.value)}
        minDate={minDate}
        maxDate={maxDate}
        disablePast
        helperText={businessDays > 0 ? `${businessDays} business days` : 'Select dates'}
        required
      />

      <Button type="submit">Submit Request</Button>
    </form>
  );
}
```

### Date Range with Presets

```tsx
function DateRangeWithPresets() {
  const [dateRange, setDateRange] = useState('');

  const formatDate = (date) => {
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, '0');
    const day = String(date.getDate()).padStart(2, '0');
    return `${year}/${month}/${day}`;
  };

  const applyPreset = (preset) => {
    const today = new Date();
    let start, end;

    switch (preset) {
      case 'last7':
        end = today;
        start = new Date(today.getTime() - 7 * 24 * 60 * 60 * 1000);
        break;
      case 'last30':
        end = today;
        start = new Date(today.getTime() - 30 * 24 * 60 * 60 * 1000);
        break;
      case 'thisMonth':
        start = new Date(today.getFullYear(), today.getMonth(), 1);
        end = new Date(today.getFullYear(), today.getMonth() + 1, 0);
        break;
      case 'lastMonth':
        start = new Date(today.getFullYear(), today.getMonth() - 1, 1);
        end = new Date(today.getFullYear(), today.getMonth(), 0);
        break;
      default:
        return;
    }

    setDateRange(`${formatDate(start)} - ${formatDate(end)}`);
  };

  return (
    <Stack gap={2}>
      <Stack direction="row" gap={1}>
        <Button variant="plain" onClick={() => applyPreset('last7')}>
          Last 7 Days
        </Button>
        <Button variant="plain" onClick={() => applyPreset('last30')}>
          Last 30 Days
        </Button>
        <Button variant="plain" onClick={() => applyPreset('thisMonth')}>
          This Month
        </Button>
        <Button variant="plain" onClick={() => applyPreset('lastMonth')}>
          Last Month
        </Button>
      </Stack>
      <DateRangePicker
        label="Custom Range"
        value={dateRange}
        onChange={(e) => setDateRange(e.target.value)}
        disableFuture
      />
    </Stack>
  );
}
```

### API Integration

```tsx
function DataExport() {
  const [dateRange, setDateRange] = useState('');
  const [isExporting, setIsExporting] = useState(false);

  const handleExport = async () => {
    if (!dateRange) return;

    setIsExporting(true);
    const [startDate, endDate] = dateRange.split(' - ');

    try {
      const response = await api.exportData({
        startDate: startDate.replace(/\//g, '-'), // Convert to API format
        endDate: endDate.replace(/\//g, '-'),
      });
      downloadFile(response.data);
    } finally {
      setIsExporting(false);
    }
  };

  return (
    <Stack gap={2}>
      <DateRangePicker
        label="Export Period"
        value={dateRange}
        onChange={(e) => setDateRange(e.target.value)}
        format="YYYY/MM/DD"
        disableFuture
        required
      />
      <Button
        onClick={handleExport}
        loading={isExporting}
        disabled={!dateRange}
      >
        Export Data
      </Button>
    </Stack>
  );
}
```

## Props and Customization

### Key Props

| Prop              | Type                                                        | Default          | Description                                |
| ----------------- | ----------------------------------------------------------- | ---------------- | ------------------------------------------ |
| `value`           | `string`                                                    | -                | Controlled value (`"startDate - endDate"`) |
| `defaultValue`    | `string`                                                    | -                | Default value for uncontrolled mode        |
| `onChange`        | `(e: { target: { name?: string; value: string } }) => void` | -                | Change handler                             |
| `format`          | `string`                                                    | `'YYYY/MM/DD'`   | Format for `value` and `onChange`          |
| `displayFormat`   | `string`                                                    | Same as `format` | Format displayed in the input              |
| `label`           | `string`                                                    | -                | Label text                                 |
| `helperText`      | `string`                                                    | -                | Helper text below input                    |
| `error`           | `boolean`                                                   | `false`          | Error state                                |
| `size`            | `'sm' \| 'md' \| 'lg'`                                      | `'md'`           | Component size                             |
| `disabled`        | `boolean`                                                   | `false`          | Disabled state                             |
| `required`        | `boolean`                                                   | `false`          | Required field indicator                   |
| `minDate`         | `string`                                                    | -                | Minimum selectable date                    |
| `maxDate`         | `string`                                                    | -                | Maximum selectable date                    |
| `disableFuture`   | `boolean`                                                   | `false`          | Disable all future dates                   |
| `disablePast`     | `boolean`                                                   | `false`          | Disable all past dates                     |
| `inputReadOnly`   | `boolean`                                                   | `false`          | Prevent typing, calendar only              |
| `readOnly`        | `boolean`                                                   | `false`          | Fully read-only                            |
| `hideClearButton` | `boolean`                                                   | `false`          | Hide clear button in calendar              |

### Value Format

The value is always a string with start and end dates separated by `-`:

```tsx
// Value format: "startDate - endDate"
const value = "2024/04/01 - 2024/04/15";

// Parsing the value
const [startDate, endDate] = value.split(' - ');
console.log(startDate); // "2024/04/01"
console.log(endDate);   // "2024/04/15"
```

### Format vs DisplayFormat

```tsx
// format: Affects the value in onChange
// displayFormat: Affects what users see in the input

<DateRangePicker
  format="YYYY-MM-DD"       // onChange returns "2024-04-01 - 2024-04-15"
  displayFormat="MM/DD/YYYY" // Input shows "04/01/2024 - 04/15/2024"
  onChange={(e) => {
    console.log(e.target.value); // "2024-04-01 - 2024-04-15"
  }}
/>
```

### Supported Format Tokens

| Token  | Description   | Example |
| ------ | ------------- | ------- |
| `YYYY` | 4-digit year  | 2024    |
| `MM`   | 2-digit month | 04      |
| `DD`   | 2-digit day   | 15      |

### Controlled vs Uncontrolled

```tsx
// Uncontrolled - component manages state
<DateRangePicker
  defaultValue="2024/04/01 - 2024/04/15"
  onChange={(e) => console.log(e.target.value)}
/>

// Controlled - you manage state
const [dateRange, setDateRange] = useState('2024/04/01 - 2024/04/15');
<DateRangePicker
  value={dateRange}
  onChange={(e) => setDateRange(e.target.value)}
/>
```

## Accessibility

DateRangePicker includes built-in accessibility features:

### ARIA Attributes

- Input has proper `role="textbox"`
- Calendar button has `aria-label="Toggle Calendar"`
- Calendar popup uses `role="tooltip"` with proper labeling
- Date buttons announce the full date to screen readers

### Keyboard Navigation

- **Tab**: Move focus between input and calendar button
- **Enter/Space**: Open calendar when focused on button
- **Arrow Keys**: Navigate within calendar
- **Escape**: Close calendar popup
- **Enter**: Select focused date

### Screen Reader Support

```tsx
// Range selection is announced
// First click: "Start date: April 1, 2024"
// Second click: "End date: April 15, 2024"

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

### Focus Management

- Focus moves to calendar when opened
- Focus returns to input when calendar closes
- Clear visual focus indicators on all interactive elements
- Range selection provides visual feedback between start and end dates

## Best Practices

### ✅ Do

1. **Validate date ranges**: Ensure start date is before end date

```tsx
// ✅ Good: Validate the range
const validateRange = (value) => {
  const [start, end] = value.split(' - ');
  if (new Date(start) > new Date(end)) {
    return 'Start date must be before end date';
  }
  return '';
};
```

2. **Show range duration**: Help users understand the selected period

```tsx
// ✅ Good: Display duration
const getDuration = (value) => {
  if (!value) return '';
  const [start, end] = value.split(' - ').map(d => new Date(d));
  const days = Math.ceil((end - start) / (1000 * 60 * 60 * 24));
  return `${days} day${days !== 1 ? 's' : ''}`;
};

<DateRangePicker helperText={getDuration(dateRange) || 'Select dates'} />
```

3. **Set reasonable limits**: Prevent excessively long ranges

```tsx
// ✅ Good: Limit range span
const MAX_DAYS = 90;
const validateMaxRange = (value) => {
  const [start, end] = value.split(' - ').map(d => new Date(d));
  const days = (end - start) / (1000 * 60 * 60 * 24);
  return days <= MAX_DAYS;
};
```

4. **Use consistent formats**: Match your application's locale

```tsx
// ✅ Good: Consistent with app locale
<DateRangePicker
  format="YYYY-MM-DD"  // API format
  displayFormat="MM/DD/YYYY"  // US locale display
/>
```

### ❌ Don't

1. **Don't allow invalid ranges**: Always validate start \< end

```tsx
// ❌ Bad: No validation
<DateRangePicker onChange={(e) => setDateRange(e.target.value)} />

// ✅ Good: Validate range
<DateRangePicker
  onChange={(e) => {
    if (isValidRange(e.target.value)) {
      setDateRange(e.target.value);
    }
  }}
/>
```

2. **Don't use inconsistent value formats**: Match value to format prop

```tsx
// ❌ Bad: Mismatched formats
<DateRangePicker
  format="YYYY/MM/DD"
  value="04/01/2024 - 04/15/2024"  // Wrong!
/>

// ✅ Good: Matching formats
<DateRangePicker
  format="YYYY/MM/DD"
  value="2024/04/01 - 2024/04/15"
/>
```

3. **Don't forget mobile users**: Use `inputReadOnly` for better mobile experience

4. **Don't hide important context**: Show duration or business days when relevant

## Performance Considerations

### Memoize Handlers

When using DateRangePicker in complex forms:

```tsx
const handleChange = useCallback((e) => {
  setDateRange(e.target.value);
}, []);

<DateRangePicker value={dateRange} onChange={handleChange} />
```

### Parse Values Efficiently

When processing date range values:

```tsx
// Memoize parsed values
const { startDate, endDate, duration } = useMemo(() => {
  if (!dateRange) return { startDate: null, endDate: null, duration: 0 };

  const [start, end] = dateRange.split(' - ');
  const startDate = new Date(start);
  const endDate = new Date(end);
  const duration = Math.ceil((endDate - startDate) / (1000 * 60 * 60 * 24));

  return { startDate, endDate, duration };
}, [dateRange]);
```

### Format Conversion for APIs

When working with APIs that expect different formats:

```tsx
function DateRangeField({ value, onChange, apiFormat = 'YYYY-MM-DD' }) {
  const displayFormat = 'YYYY/MM/DD';

  const convertToDisplay = (apiValue) => {
    if (!apiValue) return '';
    const [start, end] = apiValue.split(' - ');
    return `${convertFormat(start, apiFormat, displayFormat)} - ${convertFormat(end, apiFormat, displayFormat)}`;
  };

  const convertToApi = (displayValue) => {
    if (!displayValue) return '';
    const [start, end] = displayValue.split(' - ');
    return `${convertFormat(start, displayFormat, apiFormat)} - ${convertFormat(end, displayFormat, apiFormat)}`;
  };

  return (
    <DateRangePicker
      value={convertToDisplay(value)}
      onChange={(e) => onChange(convertToApi(e.target.value))}
      format={displayFormat}
    />
  );
}
```

DateRangePicker is essential for scenarios requiring start and end date selection. Pay attention to date format consistency, validate ranges properly, and provide clear feedback to users about the selected period.
