# MonthRangePicker

## Introduction

MonthRangePicker is a form input component that allows users to select a range of months (start month and end month) from a calendar popup. Unlike DateRangePicker which provides day-level granularity, MonthRangePicker focuses on month-level selection. It is ideal for scenarios like fiscal period reporting, quarterly comparisons, subscription duration, or any use case requiring a span of months without specific day selection.

```tsx
<MonthRangePicker />
```

| Field         | Description | Default |
| ------------- | ----------- | ------- |
| value         | —           | —       |
| defaultValue  | —           | —       |
| name          | —           | —       |
| disabled      | —           | —       |
| required      | —           | —       |
| label         | —           | —       |
| error         | —           | —       |
| helperText    | —           | —       |
| minDate       | —           | —       |
| maxDate       | —           | —       |
| disableFuture | —           | —       |
| disablePast   | —           | —       |
| format        | —           | —       |
| onChange      | —           | —       |

> ⚠️ **Usage Warning** ⚠️
>
> MonthRangePicker has unique formatting behaviors:
>
> - **Value format**: Values use `"YYYY/MM - YYYY/MM"` format (without day component)
> - **Range validation**: Ensure start month is before or equal to end month
> - **Format consistency**: Both months in the range must match the `format` prop
> - **Month boundaries**: Consider inclusive vs exclusive range interpretations

> 💡 **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 { MonthRangePicker } from '@ceed/ads';

function MonthRangeForm() {
  const [monthRange, setMonthRange] = useState('');

  return (
    <MonthRangePicker
      label="Select Month Range"
      value={monthRange}
      onChange={(e) => setMonthRange(e.target.value)}
    />
  );
}
```

## Examples

### Playground

Interactive example with all controls.

```tsx
<MonthRangePicker />
```

### Sizes

MonthRangePicker supports three sizes for different layouts.

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

### With Label

Add a label above the month range picker.

```tsx
<MonthRangePicker label="Month Range" />
```

### With Helper Text

Provide additional guidance below the input.

```tsx
<MonthRangePicker
  label="Month"
  helperText="Please select a month"
/>
```

### Error State

Show validation errors with error styling.

```tsx
<MonthRangePicker
  label="Month"
  helperText="Please select a month"
  error
/>
```

### Required Field

Mark the field as required in forms.

```tsx
<MonthRangePicker
  label="Label"
  helperText="I'm helper text"
  required
/>
```

### Disabled

Prevent user interaction when disabled.

```tsx
<MonthRangePicker disabled />
```

### Minimum Date

Restrict selection to months on or after a minimum date.

```tsx
<MonthRangePicker minDate="2024-04-10" />
```

### Maximum Date

Restrict selection to months on or before a maximum date.

```tsx
<MonthRangePicker maxDate="2024-04-10" />
```

### Disable Future

Prevent selection of months in the future.

```tsx
<MonthRangePicker disableFuture />
```

### Disable Past

Prevent selection of months in the past.

```tsx
<MonthRangePicker disablePast />
```

### Controlled

Parent component manages the month range state.

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

### Uncontrolled

Component manages its own state internally.

```tsx
<MonthRangePicker
  label="Uncontrolled MonthPicker"
  helperText="Please select a date"
  defaultValue="2024/04"
/>
```

### With Formats

Different value formats for regional preferences.

```tsx
<Stack gap={2}>
  <MonthRangePicker {...args} value={value['YYYY.MM']} label="YYYY.MM" name="YYYY.MM" format="YYYY.MM" onChange={handleChange} />
  <MonthRangePicker {...args} value={value['YYYY/MM']} label="YYYY/MM" name="YYYY/MM" format="YYYY/MM" onChange={handleChange} />
  <MonthRangePicker {...args} value={value['MM/YYYY']} label="MM/YYYY" name="MM/YYYY" format="MM/YYYY" onChange={handleChange} />
  <MonthRangePicker {...args} value={value['YYYY-MM']} label="YYYY-MM" name="YYYY-MM" format="YYYY-MM" onChange={handleChange} />
</Stack>
```

### With Reset Button

Example with an external reset button.

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

## When to Use

### ✅ Good Use Cases

- **Fiscal period reports**: Q1-Q4 reports, fiscal year ranges
- **Subscription periods**: Multi-month subscription durations
- **Budget planning**: Budget allocation across multiple months
- **Historical comparisons**: Compare data across month spans
- **Seasonal analysis**: Analyzing seasonal trends
- **Project timelines**: Multi-month project duration

### ❌ When Not to Use

- **Single month selection**: Use MonthPicker instead
- **Specific date ranges**: Use DateRangePicker when days matter
- **Year selection**: Use a year picker or dropdown
- **Quarter selection**: Consider custom quarter selector
- **Indefinite periods**: Use separate start/end fields with "ongoing" option

## Common Use Cases

### Fiscal Period Report

```tsx
function FiscalReportSelector() {
  const [period, setPeriod] = useState('');

  const handleGenerate = () => {
    if (!period) return;
    const [startMonth, endMonth] = period.split(' - ');
    generateReport({ startMonth, endMonth });
  };

  return (
    <Stack gap={2}>
      <MonthRangePicker
        label="Fiscal Period"
        value={period}
        onChange={(e) => setPeriod(e.target.value)}
        disableFuture
        helperText="Select the fiscal period for the report"
      />
      <Button onClick={handleGenerate} disabled={!period}>
        Generate Report
      </Button>
    </Stack>
  );
}
```

### Subscription Duration

```tsx
function SubscriptionForm() {
  const [duration, setDuration] = useState('');

  // Calculate months count
  const getMonthsCount = (value) => {
    if (!value) return 0;
    const [start, end] = value.split(' - ');
    const [startYear, startMonth] = start.split('/').map(Number);
    const [endYear, endMonth] = end.split('/').map(Number);
    return (endYear - startYear) * 12 + (endMonth - startMonth) + 1;
  };

  const monthsCount = getMonthsCount(duration);

  return (
    <form>
      <MonthRangePicker
        label="Subscription Period"
        value={duration}
        onChange={(e) => setDuration(e.target.value)}
        disablePast
        helperText={monthsCount > 0 ? `${monthsCount} month subscription` : 'Select period'}
        required
      />
      <Typography level="body-sm">
        Total: ${monthsCount * 9.99}/period
      </Typography>
      <Button type="submit">Subscribe</Button>
    </form>
  );
}
```

### Budget Planning

```tsx
function BudgetPlanningForm({ departments }) {
  const [planningPeriod, setPlanningPeriod] = useState('');
  const [allocations, setAllocations] = useState({});

  // Default to next fiscal year
  useEffect(() => {
    const now = new Date();
    const nextYear = now.getFullYear() + 1;
    setPlanningPeriod(`${nextYear}/01 - ${nextYear}/12`);
  }, []);

  return (
    <form>
      <MonthRangePicker
        label="Budget Planning Period"
        value={planningPeriod}
        onChange={(e) => setPlanningPeriod(e.target.value)}
        format="YYYY/MM"
        helperText="Select the months for budget allocation"
      />

      {planningPeriod && departments.map((dept) => (
        <CurrencyInput
          key={dept.id}
          label={dept.name}
          value={allocations[dept.id] || ''}
          onChange={(value) => setAllocations(prev => ({
            ...prev,
            [dept.id]: value
          }))}
        />
      ))}

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

### Year-over-Year Comparison

```tsx
function YearOverYearComparison() {
  const [currentPeriod, setCurrentPeriod] = useState('');
  const [previousPeriod, setPreviousPeriod] = useState('');

  // Auto-calculate previous period
  useEffect(() => {
    if (!currentPeriod) return;

    const [start, end] = currentPeriod.split(' - ');
    const [startYear, startMonth] = start.split('/').map(Number);
    const [endYear, endMonth] = end.split('/').map(Number);

    const prevStart = `${startYear - 1}/${String(startMonth).padStart(2, '0')}`;
    const prevEnd = `${endYear - 1}/${String(endMonth).padStart(2, '0')}`;
    setPreviousPeriod(`${prevStart} - ${prevEnd}`);
  }, [currentPeriod]);

  return (
    <Stack gap={2}>
      <MonthRangePicker
        label="Current Period"
        value={currentPeriod}
        onChange={(e) => setCurrentPeriod(e.target.value)}
        disableFuture
      />
      <MonthRangePicker
        label="Previous Period (Auto-calculated)"
        value={previousPeriod}
        disabled
      />
      <Button disabled={!currentPeriod}>
        Compare Periods
      </Button>
    </Stack>
  );
}
```

### Seasonal Analysis

```tsx
function SeasonalAnalysis() {
  const [period, setPeriod] = useState('');

  const getSeason = (monthRange) => {
    if (!monthRange) return '';
    const [start, end] = monthRange.split(' - ');
    const startMonth = parseInt(start.split('/')[1]);
    const endMonth = parseInt(end.split('/')[1]);

    // Simple season detection based on months
    if (startMonth >= 3 && endMonth <= 5) return 'Spring';
    if (startMonth >= 6 && endMonth <= 8) return 'Summer';
    if (startMonth >= 9 && endMonth <= 11) return 'Fall';
    if ((startMonth >= 12 || startMonth <= 2)) return 'Winter';
    return 'Custom Period';
  };

  return (
    <Stack gap={2}>
      <MonthRangePicker
        label="Analysis Period"
        value={period}
        onChange={(e) => setPeriod(e.target.value)}
        disableFuture
      />
      {period && (
        <Typography level="body-sm">
          Season: {getSeason(period)}
        </Typography>
      )}
    </Stack>
  );
}
```

### Data Export with Range

```tsx
function DataExportForm() {
  const [exportRange, setExportRange] = useState('');
  const [isExporting, setIsExporting] = useState(false);

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

    setIsExporting(true);
    const [startMonth, endMonth] = exportRange.split(' - ');

    try {
      const response = await api.exportData({
        startMonth: startMonth.replace('/', '-'),
        endMonth: endMonth.replace('/', '-'),
      });
      downloadFile(response.data);
    } finally {
      setIsExporting(false);
    }
  };

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

## Props and Customization

### Key Props

| Prop            | Type                                                        | Default     | Description                              |
| --------------- | ----------------------------------------------------------- | ----------- | ---------------------------------------- |
| `value`         | `string`                                                    | -           | Controlled value (`"YYYY/MM - YYYY/MM"`) |
| `defaultValue`  | `string`                                                    | -           | Default value for uncontrolled mode      |
| `onChange`      | `(e: { target: { name?: string; value: string } }) => void` | -           | Change handler                           |
| `format`        | `string`                                                    | `'YYYY/MM'` | Format for value and display             |
| `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 month                 |
| `maxDate`       | `string`                                                    | -           | Maximum selectable month                 |
| `disableFuture` | `boolean`                                                   | `false`     | Disable all future months                |
| `disablePast`   | `boolean`                                                   | `false`     | Disable all past months                  |

### Value Format

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

```tsx
// Value format: "YYYY/MM - YYYY/MM"
const value = "2024/04 - 2024/09";

// Parsing the value
const [startMonth, endMonth] = value.split(' - ');
console.log(startMonth); // "2024/04"
console.log(endMonth);   // "2024/09"
```

### Supported Format Tokens

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

Common format patterns:

- `YYYY/MM` - Default (2024/04)
- `YYYY-MM` - ISO-like (2024-04)
- `MM/YYYY` - European (04/2024)
- `YYYY.MM` - Period separator (2024.04)

### Format Affects Both Display and Value

Unlike DatePicker components, MonthRangePicker's `format` prop affects both the display and the value:

```tsx
<MonthRangePicker
  format="YYYY-MM"  // Both display AND onChange value use this format
  onChange={(e) => {
    console.log(e.target.value); // "2024-04 - 2024-09"
  }}
/>
```

### Controlled vs Uncontrolled

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

// Controlled - you manage state
const [monthRange, setMonthRange] = useState('2024/04 - 2024/09');
<MonthRangePicker
  value={monthRange}
  onChange={(e) => setMonthRange(e.target.value)}
/>
```

## Accessibility

MonthRangePicker includes built-in accessibility features:

### ARIA Attributes

- Input has proper `role="textbox"`
- Calendar button has `aria-label="Toggle Calendar"`
- Month grid uses proper navigation roles
- Selected months marked with `aria-selected`

### Keyboard Navigation

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

### Screen Reader Support

```tsx
// Range selection is announced
// First click: "Start month: April 2024"
// Second click: "End month: September 2024"

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

### Focus Management

- Focus moves to current year's month grid 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 months

## Best Practices

### ✅ Do

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

```tsx
// ✅ Good: Validate the range
const validateRange = (value) => {
  const [start, end] = value.split(' - ');
  const [startYear, startMonth] = start.split('/').map(Number);
  const [endYear, endMonth] = end.split('/').map(Number);

  if (startYear > endYear) return false;
  if (startYear === endYear && startMonth > endMonth) return false;
  return true;
};
```

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

```tsx
// ✅ Good: Display duration
const getMonthsCount = (value) => {
  if (!value) return 0;
  const [start, end] = value.split(' - ');
  const [startYear, startMonth] = start.split('/').map(Number);
  const [endYear, endMonth] = end.split('/').map(Number);
  return (endYear - startYear) * 12 + (endMonth - startMonth) + 1;
};

<MonthRangePicker
  helperText={`${getMonthsCount(range)} months selected`}
/>
```

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

```tsx
// ✅ Good: Limit range span
const MAX_MONTHS = 24;
const validateMaxRange = (value) => {
  return getMonthsCount(value) <= MAX_MONTHS;
};
```

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

```tsx
// ✅ Good: Consistent with app format
<MonthRangePicker
  format="YYYY-MM"  // ISO format for API compatibility
/>
```

### ❌ Don't

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

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

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

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

```tsx
// ❌ Bad: Mismatched formats
<MonthRangePicker
  format="YYYY/MM"
  value="04/2024 - 09/2024"  // Wrong! Should use YYYY/MM
/>

// ✅ Good: Matching formats
<MonthRangePicker
  format="YYYY/MM"
  value="2024/04 - 2024/09"
/>
```

3. **Don't forget to handle empty states**: Validate before processing

4. **Don't use for day-level selection**: Use DateRangePicker when days matter

## Performance Considerations

### Memoize Handlers

When using MonthRangePicker in complex forms:

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

<MonthRangePicker value={monthRange} onChange={handleChange} />
```

### Parse Values Efficiently

When processing month range values:

```tsx
const { startYear, startMonth, endYear, endMonth, monthsCount } = useMemo(() => {
  if (!monthRange) return { startYear: null, startMonth: null, endYear: null, endMonth: null, monthsCount: 0 };

  const [start, end] = monthRange.split(' - ');
  const [startYear, startMonth] = start.split('/').map(Number);
  const [endYear, endMonth] = end.split('/').map(Number);
  const monthsCount = (endYear - startYear) * 12 + (endMonth - startMonth) + 1;

  return { startYear, startMonth, endYear, endMonth, monthsCount };
}, [monthRange]);
```

### Format Conversion for APIs

When working with APIs that expect different formats:

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

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

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

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

MonthRangePicker provides an efficient way to select multi-month periods for fiscal reporting, budget planning, and data analysis. Remember that the `format` prop affects both display and value, and always validate that the start month precedes the end month.
