@ceed/ads 1.20.0 → 1.20.1-next.1

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