@ceed/ads 1.23.3 → 1.23.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/data-display/Badge.md +71 -39
- package/dist/components/data-display/InfoSign.md +74 -98
- package/dist/components/data-display/Typography.md +310 -61
- package/dist/components/feedback/CircularProgress.md +257 -0
- package/dist/components/feedback/Skeleton.md +280 -0
- package/dist/components/feedback/llms.txt +2 -0
- package/dist/components/inputs/ButtonGroup.md +115 -106
- package/dist/components/inputs/Calendar.md +98 -459
- package/dist/components/inputs/CurrencyInput.md +181 -8
- package/dist/components/inputs/DatePicker.md +108 -436
- package/dist/components/inputs/DateRangePicker.md +130 -496
- package/dist/components/inputs/FilterMenu.md +169 -19
- package/dist/components/inputs/FilterableCheckboxGroup.md +119 -24
- package/dist/components/inputs/FormControl.md +368 -0
- package/dist/components/inputs/IconButton.md +137 -88
- package/dist/components/inputs/MonthPicker.md +95 -427
- package/dist/components/inputs/MonthRangePicker.md +89 -471
- package/dist/components/inputs/PercentageInput.md +183 -19
- package/dist/components/inputs/RadioButton.md +163 -35
- package/dist/components/inputs/RadioList.md +241 -0
- package/dist/components/inputs/RadioTileGroup.md +146 -62
- package/dist/components/inputs/Select.md +219 -328
- package/dist/components/inputs/Slider.md +334 -0
- package/dist/components/inputs/Switch.md +136 -376
- package/dist/components/inputs/Textarea.md +209 -11
- package/dist/components/inputs/Uploader/Uploader.md +145 -66
- package/dist/components/inputs/llms.txt +3 -0
- package/dist/components/navigation/Breadcrumbs.md +80 -322
- package/dist/components/navigation/Dropdown.md +92 -221
- package/dist/components/navigation/IconMenuButton.md +40 -502
- package/dist/components/navigation/InsetDrawer.md +68 -738
- package/dist/components/navigation/Link.md +39 -298
- package/dist/components/navigation/Menu.md +92 -285
- package/dist/components/navigation/MenuButton.md +55 -448
- package/dist/components/navigation/Pagination.md +47 -338
- package/dist/components/navigation/ProfileMenu.md +45 -268
- package/dist/components/navigation/Stepper.md +160 -28
- package/dist/components/navigation/Tabs.md +57 -316
- package/dist/components/surfaces/Sheet.md +150 -333
- package/dist/guides/ThemeProvider.md +116 -0
- package/dist/guides/llms.txt +9 -0
- package/dist/llms.txt +8 -0
- package/package.json +1 -1
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
## Introduction
|
|
4
4
|
|
|
5
|
-
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
|
|
5
|
+
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 -- users click once to set the start month and again to set the end month.
|
|
6
|
+
|
|
7
|
+
It is ideal for scenarios like fiscal period reporting, quarterly comparisons, subscription durations, or any use case requiring a span of months without specific day selection. The component supports multiple value formats, controlled and uncontrolled modes, and date range constraints.
|
|
6
8
|
|
|
7
9
|
```tsx
|
|
8
10
|
<MonthRangePicker />
|
|
@@ -25,20 +27,6 @@ MonthRangePicker is a form input component that allows users to select a range o
|
|
|
25
27
|
| format | — | — |
|
|
26
28
|
| onChange | — | — |
|
|
27
29
|
|
|
28
|
-
> ⚠️ **Usage Warning** ⚠️
|
|
29
|
-
>
|
|
30
|
-
> MonthRangePicker has unique formatting behaviors:
|
|
31
|
-
>
|
|
32
|
-
> - **Value format**: Values use `"YYYY/MM - YYYY/MM"` format (without day component)
|
|
33
|
-
> - **Range validation**: Ensure start month is before or equal to end month
|
|
34
|
-
> - **Format consistency**: Both months in the range must match the `format` prop
|
|
35
|
-
> - **Month boundaries**: Consider inclusive vs exclusive range interpretations
|
|
36
|
-
|
|
37
|
-
> 💡 **Use built-in form props**
|
|
38
|
-
>
|
|
39
|
-
> This component natively supports form elements such as `label` and `helperText` props.
|
|
40
|
-
> When building forms, use these built-in props instead of manually composing labels and helper text with Typography.
|
|
41
|
-
|
|
42
30
|
## Usage
|
|
43
31
|
|
|
44
32
|
```tsx
|
|
@@ -57,19 +45,14 @@ function MonthRangeForm() {
|
|
|
57
45
|
}
|
|
58
46
|
```
|
|
59
47
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
Interactive example with all controls.
|
|
65
|
-
|
|
66
|
-
```tsx
|
|
67
|
-
<MonthRangePicker />
|
|
68
|
-
```
|
|
48
|
+
> 💡 **Use built-in form props**
|
|
49
|
+
>
|
|
50
|
+
> This component natively supports form elements such as `label` and `helperText` props.
|
|
51
|
+
> When building forms, use these built-in props instead of manually composing labels and helper text with Typography.
|
|
69
52
|
|
|
70
|
-
|
|
53
|
+
## Sizes
|
|
71
54
|
|
|
72
|
-
MonthRangePicker supports three sizes for different layouts.
|
|
55
|
+
MonthRangePicker supports three sizes (`sm`, `md`, `lg`) for different layouts and contexts.
|
|
73
56
|
|
|
74
57
|
```tsx
|
|
75
58
|
<Stack gap={2}>
|
|
@@ -79,9 +62,11 @@ MonthRangePicker supports three sizes for different layouts.
|
|
|
79
62
|
</Stack>
|
|
80
63
|
```
|
|
81
64
|
|
|
65
|
+
## Form Field Features
|
|
66
|
+
|
|
82
67
|
### With Label
|
|
83
68
|
|
|
84
|
-
Add a label above the month range picker.
|
|
69
|
+
Add a label above the month range picker to indicate the field purpose.
|
|
85
70
|
|
|
86
71
|
```tsx
|
|
87
72
|
<MonthRangePicker label="Month Range" />
|
|
@@ -89,7 +74,7 @@ Add a label above the month range picker.
|
|
|
89
74
|
|
|
90
75
|
### With Helper Text
|
|
91
76
|
|
|
92
|
-
Provide additional guidance below the input.
|
|
77
|
+
Provide additional guidance below the input to help users understand the expected selection.
|
|
93
78
|
|
|
94
79
|
```tsx
|
|
95
80
|
<MonthRangePicker
|
|
@@ -98,41 +83,43 @@ Provide additional guidance below the input.
|
|
|
98
83
|
/>
|
|
99
84
|
```
|
|
100
85
|
|
|
101
|
-
###
|
|
86
|
+
### Required Field
|
|
102
87
|
|
|
103
|
-
|
|
88
|
+
Mark the field as required in forms. An asterisk indicator is displayed alongside the label.
|
|
104
89
|
|
|
105
90
|
```tsx
|
|
106
91
|
<MonthRangePicker
|
|
107
|
-
label="
|
|
108
|
-
helperText="
|
|
109
|
-
|
|
92
|
+
label="Label"
|
|
93
|
+
helperText="I'm helper text"
|
|
94
|
+
required
|
|
110
95
|
/>
|
|
111
96
|
```
|
|
112
97
|
|
|
113
|
-
###
|
|
98
|
+
### Error State
|
|
114
99
|
|
|
115
|
-
|
|
100
|
+
Display validation errors with error styling applied to the input, label, and helper text.
|
|
116
101
|
|
|
117
102
|
```tsx
|
|
118
103
|
<MonthRangePicker
|
|
119
|
-
label="
|
|
120
|
-
helperText="
|
|
121
|
-
|
|
104
|
+
label="Month"
|
|
105
|
+
helperText="Please select a month"
|
|
106
|
+
error
|
|
122
107
|
/>
|
|
123
108
|
```
|
|
124
109
|
|
|
125
110
|
### Disabled
|
|
126
111
|
|
|
127
|
-
Prevent user interaction when disabled.
|
|
112
|
+
Prevent user interaction when the picker is disabled.
|
|
128
113
|
|
|
129
114
|
```tsx
|
|
130
115
|
<MonthRangePicker disabled />
|
|
131
116
|
```
|
|
132
117
|
|
|
118
|
+
## Date Constraints
|
|
119
|
+
|
|
133
120
|
### Minimum Date
|
|
134
121
|
|
|
135
|
-
Restrict selection to months on or after a minimum date.
|
|
122
|
+
Restrict selection to months on or after a minimum date. Months before the limit are visually disabled.
|
|
136
123
|
|
|
137
124
|
```tsx
|
|
138
125
|
<MonthRangePicker minDate="2024-04-10" />
|
|
@@ -140,7 +127,7 @@ Restrict selection to months on or after a minimum date.
|
|
|
140
127
|
|
|
141
128
|
### Maximum Date
|
|
142
129
|
|
|
143
|
-
Restrict selection to months on or before a maximum date.
|
|
130
|
+
Restrict selection to months on or before a maximum date. Months after the limit are visually disabled.
|
|
144
131
|
|
|
145
132
|
```tsx
|
|
146
133
|
<MonthRangePicker maxDate="2024-04-10" />
|
|
@@ -148,7 +135,7 @@ Restrict selection to months on or before a maximum date.
|
|
|
148
135
|
|
|
149
136
|
### Disable Future
|
|
150
137
|
|
|
151
|
-
Prevent selection of
|
|
138
|
+
Prevent selection of any month in the future, relative to the current date.
|
|
152
139
|
|
|
153
140
|
```tsx
|
|
154
141
|
<MonthRangePicker disableFuture />
|
|
@@ -156,15 +143,17 @@ Prevent selection of months in the future.
|
|
|
156
143
|
|
|
157
144
|
### Disable Past
|
|
158
145
|
|
|
159
|
-
Prevent selection of
|
|
146
|
+
Prevent selection of any month in the past, relative to the current date.
|
|
160
147
|
|
|
161
148
|
```tsx
|
|
162
149
|
<MonthRangePicker disablePast />
|
|
163
150
|
```
|
|
164
151
|
|
|
152
|
+
## Controlled vs Uncontrolled
|
|
153
|
+
|
|
165
154
|
### Controlled
|
|
166
155
|
|
|
167
|
-
|
|
156
|
+
The parent component manages the month range state via the `value` and `onChange` props. External buttons or logic can programmatically update the selected range.
|
|
168
157
|
|
|
169
158
|
```tsx
|
|
170
159
|
<Stack gap={2}>
|
|
@@ -187,7 +176,7 @@ Parent component manages the month range state.
|
|
|
187
176
|
|
|
188
177
|
### Uncontrolled
|
|
189
178
|
|
|
190
|
-
|
|
179
|
+
The component manages its own state internally using `defaultValue`. The parent can still listen for changes via `onChange`.
|
|
191
180
|
|
|
192
181
|
```tsx
|
|
193
182
|
<MonthRangePicker
|
|
@@ -197,9 +186,11 @@ Component manages its own state internally.
|
|
|
197
186
|
/>
|
|
198
187
|
```
|
|
199
188
|
|
|
200
|
-
|
|
189
|
+
## Formats
|
|
190
|
+
|
|
191
|
+
The `format` prop determines both the display format and the value emitted by `onChange`. Unlike MonthPicker, MonthRangePicker does not have a separate `displayFormat` prop -- the `format` prop controls both.
|
|
201
192
|
|
|
202
|
-
|
|
193
|
+
The value is a string with start and end months separated by `-` (e.g., `"2024/04 - 2024/09"`).
|
|
203
194
|
|
|
204
195
|
```tsx
|
|
205
196
|
<Stack gap={2}>
|
|
@@ -212,7 +203,7 @@ Different value formats for regional preferences.
|
|
|
212
203
|
|
|
213
204
|
### With Reset Button
|
|
214
205
|
|
|
215
|
-
|
|
206
|
+
A controlled example with an external reset button to clear the selected range.
|
|
216
207
|
|
|
217
208
|
```tsx
|
|
218
209
|
<div style={{
|
|
@@ -226,25 +217,6 @@ Example with an external reset button.
|
|
|
226
217
|
</div>
|
|
227
218
|
```
|
|
228
219
|
|
|
229
|
-
## When to Use
|
|
230
|
-
|
|
231
|
-
### ✅ Good Use Cases
|
|
232
|
-
|
|
233
|
-
- **Fiscal period reports**: Q1-Q4 reports, fiscal year ranges
|
|
234
|
-
- **Subscription periods**: Multi-month subscription durations
|
|
235
|
-
- **Budget planning**: Budget allocation across multiple months
|
|
236
|
-
- **Historical comparisons**: Compare data across month spans
|
|
237
|
-
- **Seasonal analysis**: Analyzing seasonal trends
|
|
238
|
-
- **Project timelines**: Multi-month project duration
|
|
239
|
-
|
|
240
|
-
### ❌ When Not to Use
|
|
241
|
-
|
|
242
|
-
- **Single month selection**: Use MonthPicker instead
|
|
243
|
-
- **Specific date ranges**: Use DateRangePicker when days matter
|
|
244
|
-
- **Year selection**: Use a year picker or dropdown
|
|
245
|
-
- **Quarter selection**: Consider custom quarter selector
|
|
246
|
-
- **Indefinite periods**: Use separate start/end fields with "ongoing" option
|
|
247
|
-
|
|
248
220
|
## Common Use Cases
|
|
249
221
|
|
|
250
222
|
### Fiscal Period Report
|
|
@@ -276,14 +248,13 @@ function FiscalReportSelector() {
|
|
|
276
248
|
}
|
|
277
249
|
```
|
|
278
250
|
|
|
279
|
-
### Subscription Duration
|
|
251
|
+
### Subscription Duration with Month Count
|
|
280
252
|
|
|
281
253
|
```tsx
|
|
282
254
|
function SubscriptionForm() {
|
|
283
255
|
const [duration, setDuration] = useState('');
|
|
284
256
|
|
|
285
|
-
|
|
286
|
-
const getMonthsCount = (value) => {
|
|
257
|
+
const getMonthsCount = (value: string) => {
|
|
287
258
|
if (!value) return 0;
|
|
288
259
|
const [start, end] = value.split(' - ');
|
|
289
260
|
const [startYear, startMonth] = start.split('/').map(Number);
|
|
@@ -291,65 +262,19 @@ function SubscriptionForm() {
|
|
|
291
262
|
return (endYear - startYear) * 12 + (endMonth - startMonth) + 1;
|
|
292
263
|
};
|
|
293
264
|
|
|
294
|
-
const monthsCount = getMonthsCount(duration);
|
|
295
|
-
|
|
296
|
-
return (
|
|
297
|
-
<form>
|
|
298
|
-
<MonthRangePicker
|
|
299
|
-
label="Subscription Period"
|
|
300
|
-
value={duration}
|
|
301
|
-
onChange={(e) => setDuration(e.target.value)}
|
|
302
|
-
disablePast
|
|
303
|
-
helperText={monthsCount > 0 ? `${monthsCount} month subscription` : 'Select period'}
|
|
304
|
-
required
|
|
305
|
-
/>
|
|
306
|
-
<Typography level="body-sm">
|
|
307
|
-
Total: ${monthsCount * 9.99}/period
|
|
308
|
-
</Typography>
|
|
309
|
-
<Button type="submit">Subscribe</Button>
|
|
310
|
-
</form>
|
|
311
|
-
);
|
|
312
|
-
}
|
|
313
|
-
```
|
|
314
|
-
|
|
315
|
-
### Budget Planning
|
|
316
|
-
|
|
317
|
-
```tsx
|
|
318
|
-
function BudgetPlanningForm({ departments }) {
|
|
319
|
-
const [planningPeriod, setPlanningPeriod] = useState('');
|
|
320
|
-
const [allocations, setAllocations] = useState({});
|
|
321
|
-
|
|
322
|
-
// Default to next fiscal year
|
|
323
|
-
useEffect(() => {
|
|
324
|
-
const now = new Date();
|
|
325
|
-
const nextYear = now.getFullYear() + 1;
|
|
326
|
-
setPlanningPeriod(`${nextYear}/01 - ${nextYear}/12`);
|
|
327
|
-
}, []);
|
|
328
|
-
|
|
329
265
|
return (
|
|
330
|
-
<
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
label={dept.name}
|
|
343
|
-
value={allocations[dept.id] || ''}
|
|
344
|
-
onChange={(value) => setAllocations(prev => ({
|
|
345
|
-
...prev,
|
|
346
|
-
[dept.id]: value
|
|
347
|
-
}))}
|
|
348
|
-
/>
|
|
349
|
-
))}
|
|
350
|
-
|
|
351
|
-
<Button type="submit">Submit Budget Plan</Button>
|
|
352
|
-
</form>
|
|
266
|
+
<MonthRangePicker
|
|
267
|
+
label="Subscription Period"
|
|
268
|
+
value={duration}
|
|
269
|
+
onChange={(e) => setDuration(e.target.value)}
|
|
270
|
+
disablePast
|
|
271
|
+
helperText={
|
|
272
|
+
getMonthsCount(duration) > 0
|
|
273
|
+
? `${getMonthsCount(duration)} month subscription`
|
|
274
|
+
: 'Select start and end months'
|
|
275
|
+
}
|
|
276
|
+
required
|
|
277
|
+
/>
|
|
353
278
|
);
|
|
354
279
|
}
|
|
355
280
|
```
|
|
@@ -359,20 +284,14 @@ function BudgetPlanningForm({ departments }) {
|
|
|
359
284
|
```tsx
|
|
360
285
|
function YearOverYearComparison() {
|
|
361
286
|
const [currentPeriod, setCurrentPeriod] = useState('');
|
|
362
|
-
const [previousPeriod, setPreviousPeriod] = useState('');
|
|
363
|
-
|
|
364
|
-
// Auto-calculate previous period
|
|
365
|
-
useEffect(() => {
|
|
366
|
-
if (!currentPeriod) return;
|
|
367
|
-
|
|
368
|
-
const [start, end] = currentPeriod.split(' - ');
|
|
369
|
-
const [startYear, startMonth] = start.split('/').map(Number);
|
|
370
|
-
const [endYear, endMonth] = end.split('/').map(Number);
|
|
371
287
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
288
|
+
const getPreviousPeriod = (value: string) => {
|
|
289
|
+
if (!value) return '';
|
|
290
|
+
const [start, end] = value.split(' - ');
|
|
291
|
+
const [sYear, sMonth] = start.split('/').map(Number);
|
|
292
|
+
const [eYear, eMonth] = end.split('/').map(Number);
|
|
293
|
+
return `${sYear - 1}/${String(sMonth).padStart(2, '0')} - ${eYear - 1}/${String(eMonth).padStart(2, '0')}`;
|
|
294
|
+
};
|
|
376
295
|
|
|
377
296
|
return (
|
|
378
297
|
<Stack gap={2}>
|
|
@@ -384,370 +303,69 @@ function YearOverYearComparison() {
|
|
|
384
303
|
/>
|
|
385
304
|
<MonthRangePicker
|
|
386
305
|
label="Previous Period (Auto-calculated)"
|
|
387
|
-
value={
|
|
306
|
+
value={getPreviousPeriod(currentPeriod)}
|
|
388
307
|
disabled
|
|
389
308
|
/>
|
|
390
|
-
<Button disabled={!currentPeriod}>
|
|
391
|
-
Compare Periods
|
|
392
|
-
</Button>
|
|
393
|
-
</Stack>
|
|
394
|
-
);
|
|
395
|
-
}
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
### Seasonal Analysis
|
|
399
|
-
|
|
400
|
-
```tsx
|
|
401
|
-
function SeasonalAnalysis() {
|
|
402
|
-
const [period, setPeriod] = useState('');
|
|
403
|
-
|
|
404
|
-
const getSeason = (monthRange) => {
|
|
405
|
-
if (!monthRange) return '';
|
|
406
|
-
const [start, end] = monthRange.split(' - ');
|
|
407
|
-
const startMonth = parseInt(start.split('/')[1]);
|
|
408
|
-
const endMonth = parseInt(end.split('/')[1]);
|
|
409
|
-
|
|
410
|
-
// Simple season detection based on months
|
|
411
|
-
if (startMonth >= 3 && endMonth <= 5) return 'Spring';
|
|
412
|
-
if (startMonth >= 6 && endMonth <= 8) return 'Summer';
|
|
413
|
-
if (startMonth >= 9 && endMonth <= 11) return 'Fall';
|
|
414
|
-
if ((startMonth >= 12 || startMonth <= 2)) return 'Winter';
|
|
415
|
-
return 'Custom Period';
|
|
416
|
-
};
|
|
417
|
-
|
|
418
|
-
return (
|
|
419
|
-
<Stack gap={2}>
|
|
420
|
-
<MonthRangePicker
|
|
421
|
-
label="Analysis Period"
|
|
422
|
-
value={period}
|
|
423
|
-
onChange={(e) => setPeriod(e.target.value)}
|
|
424
|
-
disableFuture
|
|
425
|
-
/>
|
|
426
|
-
{period && (
|
|
427
|
-
<Typography level="body-sm">
|
|
428
|
-
Season: {getSeason(period)}
|
|
429
|
-
</Typography>
|
|
430
|
-
)}
|
|
431
|
-
</Stack>
|
|
432
|
-
);
|
|
433
|
-
}
|
|
434
|
-
```
|
|
435
|
-
|
|
436
|
-
### Data Export with Range
|
|
437
|
-
|
|
438
|
-
```tsx
|
|
439
|
-
function DataExportForm() {
|
|
440
|
-
const [exportRange, setExportRange] = useState('');
|
|
441
|
-
const [isExporting, setIsExporting] = useState(false);
|
|
442
|
-
|
|
443
|
-
const handleExport = async () => {
|
|
444
|
-
if (!exportRange) return;
|
|
445
|
-
|
|
446
|
-
setIsExporting(true);
|
|
447
|
-
const [startMonth, endMonth] = exportRange.split(' - ');
|
|
448
|
-
|
|
449
|
-
try {
|
|
450
|
-
const response = await api.exportData({
|
|
451
|
-
startMonth: startMonth.replace('/', '-'),
|
|
452
|
-
endMonth: endMonth.replace('/', '-'),
|
|
453
|
-
});
|
|
454
|
-
downloadFile(response.data);
|
|
455
|
-
} finally {
|
|
456
|
-
setIsExporting(false);
|
|
457
|
-
}
|
|
458
|
-
};
|
|
459
|
-
|
|
460
|
-
return (
|
|
461
|
-
<Stack gap={2}>
|
|
462
|
-
<MonthRangePicker
|
|
463
|
-
label="Export Period"
|
|
464
|
-
value={exportRange}
|
|
465
|
-
onChange={(e) => setExportRange(e.target.value)}
|
|
466
|
-
format="YYYY/MM"
|
|
467
|
-
disableFuture
|
|
468
|
-
required
|
|
469
|
-
/>
|
|
470
|
-
<Button
|
|
471
|
-
onClick={handleExport}
|
|
472
|
-
loading={isExporting}
|
|
473
|
-
disabled={!exportRange}
|
|
474
|
-
>
|
|
475
|
-
Export Data
|
|
476
|
-
</Button>
|
|
477
309
|
</Stack>
|
|
478
310
|
);
|
|
479
311
|
}
|
|
480
312
|
```
|
|
481
313
|
|
|
482
|
-
## Props and Customization
|
|
483
|
-
|
|
484
|
-
### Key Props
|
|
485
|
-
|
|
486
|
-
| Prop | Type | Default | Description |
|
|
487
|
-
| --------------- | ----------------------------------------------------------- | ----------- | ---------------------------------------- |
|
|
488
|
-
| `value` | `string` | - | Controlled value (`"YYYY/MM - YYYY/MM"`) |
|
|
489
|
-
| `defaultValue` | `string` | - | Default value for uncontrolled mode |
|
|
490
|
-
| `onChange` | `(e: { target: { name?: string; value: string } }) => void` | - | Change handler |
|
|
491
|
-
| `format` | `string` | `'YYYY/MM'` | Format for value and display |
|
|
492
|
-
| `label` | `string` | - | Label text |
|
|
493
|
-
| `helperText` | `string` | - | Helper text below input |
|
|
494
|
-
| `error` | `boolean` | `false` | Error state |
|
|
495
|
-
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Component size |
|
|
496
|
-
| `disabled` | `boolean` | `false` | Disabled state |
|
|
497
|
-
| `required` | `boolean` | `false` | Required field indicator |
|
|
498
|
-
| `minDate` | `string` | - | Minimum selectable month |
|
|
499
|
-
| `maxDate` | `string` | - | Maximum selectable month |
|
|
500
|
-
| `disableFuture` | `boolean` | `false` | Disable all future months |
|
|
501
|
-
| `disablePast` | `boolean` | `false` | Disable all past months |
|
|
502
|
-
|
|
503
|
-
### Value Format
|
|
504
|
-
|
|
505
|
-
The value is a string with start and end months separated by `-`:
|
|
506
|
-
|
|
507
|
-
```tsx
|
|
508
|
-
// Value format: "YYYY/MM - YYYY/MM"
|
|
509
|
-
const value = "2024/04 - 2024/09";
|
|
510
|
-
|
|
511
|
-
// Parsing the value
|
|
512
|
-
const [startMonth, endMonth] = value.split(' - ');
|
|
513
|
-
console.log(startMonth); // "2024/04"
|
|
514
|
-
console.log(endMonth); // "2024/09"
|
|
515
|
-
```
|
|
516
|
-
|
|
517
|
-
### Supported Format Tokens
|
|
518
|
-
|
|
519
|
-
| Token | Description | Example |
|
|
520
|
-
| ------ | ------------- | ------- |
|
|
521
|
-
| `YYYY` | 4-digit year | 2024 |
|
|
522
|
-
| `MM` | 2-digit month | 04 |
|
|
523
|
-
|
|
524
|
-
Common format patterns:
|
|
525
|
-
|
|
526
|
-
- `YYYY/MM` - Default (2024/04)
|
|
527
|
-
- `YYYY-MM` - ISO-like (2024-04)
|
|
528
|
-
- `MM/YYYY` - European (04/2024)
|
|
529
|
-
- `YYYY.MM` - Period separator (2024.04)
|
|
530
|
-
|
|
531
|
-
### Format Affects Both Display and Value
|
|
532
|
-
|
|
533
|
-
Unlike DatePicker components, MonthRangePicker's `format` prop affects both the display and the value:
|
|
534
|
-
|
|
535
|
-
```tsx
|
|
536
|
-
<MonthRangePicker
|
|
537
|
-
format="YYYY-MM" // Both display AND onChange value use this format
|
|
538
|
-
onChange={(e) => {
|
|
539
|
-
console.log(e.target.value); // "2024-04 - 2024-09"
|
|
540
|
-
}}
|
|
541
|
-
/>
|
|
542
|
-
```
|
|
543
|
-
|
|
544
|
-
### Controlled vs Uncontrolled
|
|
545
|
-
|
|
546
|
-
```tsx
|
|
547
|
-
// Uncontrolled - component manages state
|
|
548
|
-
<MonthRangePicker
|
|
549
|
-
defaultValue="2024/04 - 2024/09"
|
|
550
|
-
onChange={(e) => console.log(e.target.value)}
|
|
551
|
-
/>
|
|
552
|
-
|
|
553
|
-
// Controlled - you manage state
|
|
554
|
-
const [monthRange, setMonthRange] = useState('2024/04 - 2024/09');
|
|
555
|
-
<MonthRangePicker
|
|
556
|
-
value={monthRange}
|
|
557
|
-
onChange={(e) => setMonthRange(e.target.value)}
|
|
558
|
-
/>
|
|
559
|
-
```
|
|
560
|
-
|
|
561
|
-
## Accessibility
|
|
562
|
-
|
|
563
|
-
MonthRangePicker includes built-in accessibility features:
|
|
564
|
-
|
|
565
|
-
### ARIA Attributes
|
|
566
|
-
|
|
567
|
-
- Input has proper `role="textbox"`
|
|
568
|
-
- Calendar button has `aria-label="Toggle Calendar"`
|
|
569
|
-
- Month grid uses proper navigation roles
|
|
570
|
-
- Selected months marked with `aria-selected`
|
|
571
|
-
|
|
572
|
-
### Keyboard Navigation
|
|
573
|
-
|
|
574
|
-
- **Tab**: Move focus between input and calendar button
|
|
575
|
-
- **Enter/Space**: Open calendar when focused on button
|
|
576
|
-
- **Arrow Keys**: Navigate between months in calendar
|
|
577
|
-
- **Escape**: Close calendar popup
|
|
578
|
-
- **Enter**: Select focused month
|
|
579
|
-
|
|
580
|
-
### Screen Reader Support
|
|
581
|
-
|
|
582
|
-
```tsx
|
|
583
|
-
// Range selection is announced
|
|
584
|
-
// First click: "Start month: April 2024"
|
|
585
|
-
// Second click: "End month: September 2024"
|
|
586
|
-
|
|
587
|
-
// Navigation buttons are descriptive
|
|
588
|
-
<button aria-label="Previous Year">←</button>
|
|
589
|
-
<button aria-label="Next Year">→</button>
|
|
590
|
-
```
|
|
591
|
-
|
|
592
|
-
### Focus Management
|
|
593
|
-
|
|
594
|
-
- Focus moves to current year's month grid when opened
|
|
595
|
-
- Focus returns to input when calendar closes
|
|
596
|
-
- Clear visual focus indicators on all interactive elements
|
|
597
|
-
- Range selection provides visual feedback between start and end months
|
|
598
|
-
|
|
599
314
|
## Best Practices
|
|
600
315
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
1. **Validate month ranges**: Ensure start month is before end month
|
|
604
|
-
|
|
605
|
-
```tsx
|
|
606
|
-
// ✅ Good: Validate the range
|
|
607
|
-
const validateRange = (value) => {
|
|
608
|
-
const [start, end] = value.split(' - ');
|
|
609
|
-
const [startYear, startMonth] = start.split('/').map(Number);
|
|
610
|
-
const [endYear, endMonth] = end.split('/').map(Number);
|
|
611
|
-
|
|
612
|
-
if (startYear > endYear) return false;
|
|
613
|
-
if (startYear === endYear && startMonth > endMonth) return false;
|
|
614
|
-
return true;
|
|
615
|
-
};
|
|
616
|
-
```
|
|
617
|
-
|
|
618
|
-
2. **Show range duration**: Help users understand the period length
|
|
316
|
+
1. **Match value format to `format` prop**: The `value` prop must follow the same pattern as `format`, with start and end separated by `-`.
|
|
619
317
|
|
|
620
318
|
```tsx
|
|
621
|
-
// ✅ Good:
|
|
622
|
-
const getMonthsCount = (value) => {
|
|
623
|
-
if (!value) return 0;
|
|
624
|
-
const [start, end] = value.split(' - ');
|
|
625
|
-
const [startYear, startMonth] = start.split('/').map(Number);
|
|
626
|
-
const [endYear, endMonth] = end.split('/').map(Number);
|
|
627
|
-
return (endYear - startYear) * 12 + (endMonth - startMonth) + 1;
|
|
628
|
-
};
|
|
629
|
-
|
|
319
|
+
// ✅ Good: Matching formats
|
|
630
320
|
<MonthRangePicker
|
|
631
|
-
|
|
321
|
+
format="YYYY/MM"
|
|
322
|
+
value="2024/04 - 2024/09"
|
|
632
323
|
/>
|
|
633
|
-
```
|
|
634
|
-
|
|
635
|
-
3. **Set reasonable limits**: Prevent excessively long ranges
|
|
636
324
|
|
|
637
|
-
|
|
638
|
-
// ✅ Good: Limit range span
|
|
639
|
-
const MAX_MONTHS = 24;
|
|
640
|
-
const validateMaxRange = (value) => {
|
|
641
|
-
return getMonthsCount(value) <= MAX_MONTHS;
|
|
642
|
-
};
|
|
643
|
-
```
|
|
644
|
-
|
|
645
|
-
4. **Use consistent formats**: Match your application's conventions
|
|
646
|
-
|
|
647
|
-
```tsx
|
|
648
|
-
// ✅ Good: Consistent with app format
|
|
325
|
+
// ❌ Bad: Mismatched format
|
|
649
326
|
<MonthRangePicker
|
|
650
|
-
format="YYYY
|
|
327
|
+
format="YYYY/MM"
|
|
328
|
+
value="04/2024 - 09/2024"
|
|
651
329
|
/>
|
|
652
330
|
```
|
|
653
331
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
1. **Don't allow invalid ranges**: Always validate start ≤ end
|
|
332
|
+
2. **Show range duration in helper text**: Help users understand the length of the period they selected.
|
|
657
333
|
|
|
658
334
|
```tsx
|
|
659
|
-
//
|
|
660
|
-
<MonthRangePicker onChange={(e) => setRange(e.target.value)} />
|
|
661
|
-
|
|
662
|
-
// ✅ Good: Validate range
|
|
335
|
+
// ✅ Good: Dynamic helper text
|
|
663
336
|
<MonthRangePicker
|
|
664
|
-
|
|
665
|
-
if (isValidRange(e.target.value)) {
|
|
666
|
-
setRange(e.target.value);
|
|
667
|
-
}
|
|
668
|
-
}}
|
|
337
|
+
helperText={`${getMonthsCount(range)} months selected`}
|
|
669
338
|
/>
|
|
670
339
|
```
|
|
671
340
|
|
|
672
|
-
|
|
341
|
+
3. **Set reasonable date constraints**: Use `minDate`, `maxDate`, `disableFuture`, or `disablePast` to prevent invalid selections at the UI level.
|
|
673
342
|
|
|
674
343
|
```tsx
|
|
675
|
-
//
|
|
676
|
-
<MonthRangePicker
|
|
677
|
-
format="YYYY/MM"
|
|
678
|
-
value="04/2024 - 09/2024" // Wrong! Should use YYYY/MM
|
|
679
|
-
/>
|
|
680
|
-
|
|
681
|
-
// ✅ Good: Matching formats
|
|
682
|
-
<MonthRangePicker
|
|
683
|
-
format="YYYY/MM"
|
|
684
|
-
value="2024/04 - 2024/09"
|
|
685
|
-
/>
|
|
344
|
+
// ✅ Good: Logical constraints for historical reporting
|
|
345
|
+
<MonthRangePicker disableFuture minDate="2020/01" />
|
|
686
346
|
```
|
|
687
347
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
4. **Don't use for day-level selection**: Use DateRangePicker when days matter
|
|
691
|
-
|
|
692
|
-
## Performance Considerations
|
|
693
|
-
|
|
694
|
-
### Memoize Handlers
|
|
695
|
-
|
|
696
|
-
When using MonthRangePicker in complex forms:
|
|
348
|
+
4. **Validate before processing**: Always check that the value is non-empty and properly formed before parsing.
|
|
697
349
|
|
|
698
350
|
```tsx
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
351
|
+
// ✅ Good: Guard against empty values
|
|
352
|
+
const handleSubmit = () => {
|
|
353
|
+
if (!range || !range.includes(' - ')) return;
|
|
354
|
+
const [start, end] = range.split(' - ');
|
|
355
|
+
// process start and end
|
|
356
|
+
};
|
|
704
357
|
```
|
|
705
358
|
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
When processing month range values:
|
|
359
|
+
5. **Use consistent `format` across your application**: Choose one format (e.g., `YYYY-MM` for API compatibility) and stick with it.
|
|
709
360
|
|
|
710
361
|
```tsx
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
const [start, end] = monthRange.split(' - ');
|
|
715
|
-
const [startYear, startMonth] = start.split('/').map(Number);
|
|
716
|
-
const [endYear, endMonth] = end.split('/').map(Number);
|
|
717
|
-
const monthsCount = (endYear - startYear) * 12 + (endMonth - startMonth) + 1;
|
|
718
|
-
|
|
719
|
-
return { startYear, startMonth, endYear, endMonth, monthsCount };
|
|
720
|
-
}, [monthRange]);
|
|
362
|
+
// ✅ Good: ISO format for API compatibility
|
|
363
|
+
<MonthRangePicker format="YYYY-MM" />
|
|
721
364
|
```
|
|
722
365
|
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
When working with APIs that expect different formats:
|
|
726
|
-
|
|
727
|
-
```tsx
|
|
728
|
-
function MonthRangeField({ value, onChange, apiFormat = 'YYYY-MM' }) {
|
|
729
|
-
const displayFormat = 'YYYY/MM';
|
|
730
|
-
|
|
731
|
-
const convertToDisplay = (apiValue) => {
|
|
732
|
-
if (!apiValue) return '';
|
|
733
|
-
const [start, end] = apiValue.split(' - ');
|
|
734
|
-
return `${start.replace('-', '/')} - ${end.replace('-', '/')}`;
|
|
735
|
-
};
|
|
736
|
-
|
|
737
|
-
const convertToApi = (displayValue) => {
|
|
738
|
-
if (!displayValue) return '';
|
|
739
|
-
const [start, end] = displayValue.split(' - ');
|
|
740
|
-
return `${start.replace('/', '-')} - ${end.replace('/', '-')}`;
|
|
741
|
-
};
|
|
742
|
-
|
|
743
|
-
return (
|
|
744
|
-
<MonthRangePicker
|
|
745
|
-
value={convertToDisplay(value)}
|
|
746
|
-
onChange={(e) => onChange(convertToApi(e.target.value))}
|
|
747
|
-
format={displayFormat}
|
|
748
|
-
/>
|
|
749
|
-
);
|
|
750
|
-
}
|
|
751
|
-
```
|
|
366
|
+
## Accessibility
|
|
752
367
|
|
|
753
|
-
|
|
368
|
+
- The input has `role="textbox"` and the calendar toggle button has `aria-label="Toggle Calendar"` for screen reader identification.
|
|
369
|
+
- **Keyboard navigation**: Use **Tab** to move focus between the input and calendar button, **Enter/Space** to open the calendar, **Arrow Keys** to navigate months, and **Escape** to close the popup.
|
|
370
|
+
- Range selection is conveyed through visual highlighting between the start and end months in the calendar grid. Each month button has a descriptive `aria-label` (e.g., "April 2024").
|
|
371
|
+
- Focus management moves focus into the calendar grid when opened and returns focus to the input when the calendar is closed.
|