@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.
- package/dist/components/ProfileMenu/ProfileMenu.d.ts +1 -1
- package/dist/components/data-display/Markdown.md +832 -0
- package/dist/components/feedback/Dialog.md +605 -3
- package/dist/components/feedback/Modal.md +656 -24
- package/dist/components/feedback/llms.txt +1 -1
- package/dist/components/inputs/Autocomplete.md +734 -2
- package/dist/components/inputs/Calendar.md +655 -1
- package/dist/components/inputs/DatePicker.md +699 -3
- package/dist/components/inputs/DateRangePicker.md +815 -1
- package/dist/components/inputs/MonthPicker.md +626 -4
- package/dist/components/inputs/MonthRangePicker.md +682 -4
- package/dist/components/inputs/Select.md +600 -0
- package/dist/components/layout/Container.md +507 -0
- package/dist/components/navigation/Breadcrumbs.md +582 -0
- package/dist/components/navigation/IconMenuButton.md +693 -0
- package/dist/components/navigation/InsetDrawer.md +1150 -3
- package/dist/components/navigation/Link.md +526 -0
- package/dist/components/navigation/MenuButton.md +632 -0
- package/dist/components/navigation/NavigationGroup.md +401 -1
- package/dist/components/navigation/NavigationItem.md +311 -0
- package/dist/components/navigation/Navigator.md +373 -0
- package/dist/components/navigation/Pagination.md +521 -0
- package/dist/components/navigation/ProfileMenu.md +605 -0
- package/dist/components/navigation/Tabs.md +609 -7
- package/dist/components/surfaces/Accordions.md +947 -3
- package/dist/index.cjs +3 -1
- package/dist/index.js +3 -1
- package/dist/llms.txt +1 -1
- package/framer/index.js +1 -1
- package/package.json +3 -2
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
## Introduction
|
|
4
4
|
|
|
5
|
+
MonthPicker is a form input component that allows users to select a specific month and year from a calendar popup. Unlike DatePicker which allows day-level selection, MonthPicker focuses on month-level granularity. It displays months in a grid format for easy selection and is ideal for scenarios like billing periods, report months, subscription dates, or any use case where day-level precision is unnecessary.
|
|
6
|
+
|
|
5
7
|
```tsx
|
|
6
8
|
<MonthPicker />
|
|
7
9
|
```
|
|
@@ -23,8 +25,47 @@
|
|
|
23
25
|
| format | — | — |
|
|
24
26
|
| onChange | — | — |
|
|
25
27
|
|
|
28
|
+
> ⚠️ **Usage Warning** ⚠️
|
|
29
|
+
>
|
|
30
|
+
> MonthPicker has some unique formatting behaviors:
|
|
31
|
+
>
|
|
32
|
+
> - **Internal value format**: Values internally use full date format (`"YYYY/MM/DD"`) with day set to `01`
|
|
33
|
+
> - **Display vs Value**: Input displays `YYYY/MM` but `onChange` receives `YYYY/MM/01`
|
|
34
|
+
> - **Format consistency**: Ensure your `value` prop matches the `format` prop
|
|
35
|
+
> - **displayFormat options**: Supports special token `MMMM` for full month names
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
import { MonthPicker } from '@ceed/ads';
|
|
41
|
+
|
|
42
|
+
function MonthForm() {
|
|
43
|
+
const [month, setMonth] = useState('');
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<MonthPicker
|
|
47
|
+
label="Select Month"
|
|
48
|
+
value={month}
|
|
49
|
+
onChange={(e) => setMonth(e.target.value)}
|
|
50
|
+
/>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Examples
|
|
56
|
+
|
|
57
|
+
### Playground
|
|
58
|
+
|
|
59
|
+
Interactive example with all controls.
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
<MonthPicker />
|
|
63
|
+
```
|
|
64
|
+
|
|
26
65
|
### Sizes
|
|
27
66
|
|
|
67
|
+
MonthPicker supports three sizes for different layouts.
|
|
68
|
+
|
|
28
69
|
```tsx
|
|
29
70
|
<Stack gap={2}>
|
|
30
71
|
<MonthPicker size="sm" />
|
|
@@ -33,13 +74,17 @@
|
|
|
33
74
|
</Stack>
|
|
34
75
|
```
|
|
35
76
|
|
|
36
|
-
###
|
|
77
|
+
### With Label
|
|
78
|
+
|
|
79
|
+
Add a label above the month picker.
|
|
37
80
|
|
|
38
81
|
```tsx
|
|
39
82
|
<MonthPicker label="Date" />
|
|
40
83
|
```
|
|
41
84
|
|
|
42
|
-
###
|
|
85
|
+
### With Helper Text
|
|
86
|
+
|
|
87
|
+
Provide additional guidance below the input.
|
|
43
88
|
|
|
44
89
|
```tsx
|
|
45
90
|
<MonthPicker
|
|
@@ -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
|
<MonthPicker
|
|
@@ -58,7 +105,92 @@
|
|
|
58
105
|
/>
|
|
59
106
|
```
|
|
60
107
|
|
|
61
|
-
###
|
|
108
|
+
### Required Field
|
|
109
|
+
|
|
110
|
+
Mark the field as required in forms.
|
|
111
|
+
|
|
112
|
+
```tsx
|
|
113
|
+
<MonthPicker
|
|
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
|
+
<MonthPicker disabled />
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Minimum Date
|
|
129
|
+
|
|
130
|
+
Restrict selection to months on or after a minimum date.
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
<MonthPicker 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
|
+
<MonthPicker maxDate="2024-04-10" />
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Disable Future
|
|
145
|
+
|
|
146
|
+
Prevent selection of months in the future.
|
|
147
|
+
|
|
148
|
+
```tsx
|
|
149
|
+
<MonthPicker disableFuture />
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Disable Past
|
|
153
|
+
|
|
154
|
+
Prevent selection of months in the past.
|
|
155
|
+
|
|
156
|
+
```tsx
|
|
157
|
+
<MonthPicker disablePast />
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Controlled
|
|
161
|
+
|
|
162
|
+
Parent component manages the month state.
|
|
163
|
+
|
|
164
|
+
```tsx
|
|
165
|
+
<Stack gap={2}>
|
|
166
|
+
<MonthPicker {...args} value={value} onChange={e => setValue(e.target.value)} />
|
|
167
|
+
<Button onClick={() => {
|
|
168
|
+
const currentValue = new Date(value);
|
|
169
|
+
currentValue.setMonth(currentValue.getMonth() + 1);
|
|
170
|
+
const year = currentValue.getFullYear();
|
|
171
|
+
const month = String(currentValue.getMonth() + 1).padStart(2, '0');
|
|
172
|
+
setValue(`${year}/${month}/01`);
|
|
173
|
+
}}>
|
|
174
|
+
Next Month
|
|
175
|
+
</Button>
|
|
176
|
+
</Stack>
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Uncontrolled
|
|
180
|
+
|
|
181
|
+
Component manages its own state internally.
|
|
182
|
+
|
|
183
|
+
```tsx
|
|
184
|
+
<MonthPicker
|
|
185
|
+
label="Uncontrolled MonthPicker"
|
|
186
|
+
helperText="Please select a date"
|
|
187
|
+
defaultValue="2024/04/01"
|
|
188
|
+
/>
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### With Formats
|
|
192
|
+
|
|
193
|
+
Different value formats for the `onChange` event.
|
|
62
194
|
|
|
63
195
|
```tsx
|
|
64
196
|
<Stack gap={2}>
|
|
@@ -70,3 +202,493 @@
|
|
|
70
202
|
<MonthPicker {...args} value={value['DD.MM.YYYY']} label="DD.MM.YYYY" name="DD.MM.YYYY" format="DD.MM.YYYY" onChange={handleChange} />
|
|
71
203
|
</Stack>
|
|
72
204
|
```
|
|
205
|
+
|
|
206
|
+
### With Display Formats
|
|
207
|
+
|
|
208
|
+
Different display formats including full month names.
|
|
209
|
+
|
|
210
|
+
```tsx
|
|
211
|
+
<Stack gap={2}>
|
|
212
|
+
<MonthPicker {...args} value={value1} label="YYYY.MM" name="YYYY.MM" displayFormat="YYYY.MM" onChange={e => {
|
|
213
|
+
setValue1(e.target.value);
|
|
214
|
+
args.onChange?.(e);
|
|
215
|
+
}} />
|
|
216
|
+
<MonthPicker {...args} value={value2} label="YYYY/MM" name="YYYY/MM" displayFormat="YYYY/MM" onChange={e => {
|
|
217
|
+
setValue2(e.target.value);
|
|
218
|
+
args.onChange?.(e);
|
|
219
|
+
}} />
|
|
220
|
+
<MonthPicker {...args} value={value3} label="MM/YYYY" name="MM/YYYY" displayFormat="MM/YYYY" onChange={e => {
|
|
221
|
+
setValue3(e.target.value);
|
|
222
|
+
args.onChange?.(e);
|
|
223
|
+
}} />
|
|
224
|
+
<MonthPicker {...args} value={value4} label="YYYY-MM" name="YYYY-MM" displayFormat="YYYY-MM" onChange={e => {
|
|
225
|
+
setValue4(e.target.value);
|
|
226
|
+
args.onChange?.(e);
|
|
227
|
+
}} />
|
|
228
|
+
<MonthPicker {...args} value={value5} label="YYYY-MM-DD" name="YYYY-MM-DD" displayFormat="YYYY-MM-DD" onChange={e => {
|
|
229
|
+
setValue5(e.target.value);
|
|
230
|
+
args.onChange?.(e);
|
|
231
|
+
}} />
|
|
232
|
+
<MonthPicker {...args} value={value6} label="MMMM YYYY" name="MMMM YYYY" displayFormat="MMMM YYYY" onChange={e => {
|
|
233
|
+
setValue6(e.target.value);
|
|
234
|
+
args.onChange?.(e);
|
|
235
|
+
}} />
|
|
236
|
+
</Stack>
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## When to Use
|
|
240
|
+
|
|
241
|
+
### ✅ Good Use Cases
|
|
242
|
+
|
|
243
|
+
- **Billing periods**: Monthly invoices, subscription billing cycles
|
|
244
|
+
- **Report filtering**: Monthly reports, quarterly summaries
|
|
245
|
+
- **Fiscal periods**: Fiscal months, budget allocation periods
|
|
246
|
+
- **Expiration dates**: Credit card expiry (month/year)
|
|
247
|
+
- **Historical data**: Selecting past months for data analysis
|
|
248
|
+
- **Scheduling**: Monthly recurring events
|
|
249
|
+
|
|
250
|
+
### ❌ When Not to Use
|
|
251
|
+
|
|
252
|
+
- **Specific dates**: Use DatePicker when day selection matters
|
|
253
|
+
- **Date ranges**: Use DateRangePicker or MonthRangePicker
|
|
254
|
+
- **Week selection**: Consider a custom week picker
|
|
255
|
+
- **Quarter selection**: Consider a dropdown with Q1-Q4 options
|
|
256
|
+
- **Year only**: Use a year picker or simple dropdown
|
|
257
|
+
|
|
258
|
+
## Common Use Cases
|
|
259
|
+
|
|
260
|
+
### Billing Period Selection
|
|
261
|
+
|
|
262
|
+
```tsx
|
|
263
|
+
function BillingPeriodSelector() {
|
|
264
|
+
const [billingMonth, setBillingMonth] = useState('');
|
|
265
|
+
|
|
266
|
+
const handleGenerate = () => {
|
|
267
|
+
const [year, month] = billingMonth.split('/');
|
|
268
|
+
generateInvoice({ year: parseInt(year), month: parseInt(month) });
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
return (
|
|
272
|
+
<Stack gap={2}>
|
|
273
|
+
<MonthPicker
|
|
274
|
+
label="Billing Period"
|
|
275
|
+
value={billingMonth}
|
|
276
|
+
onChange={(e) => setBillingMonth(e.target.value)}
|
|
277
|
+
disableFuture
|
|
278
|
+
helperText="Select the month for invoice generation"
|
|
279
|
+
/>
|
|
280
|
+
<Button onClick={handleGenerate} disabled={!billingMonth}>
|
|
281
|
+
Generate Invoice
|
|
282
|
+
</Button>
|
|
283
|
+
</Stack>
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Report Month Filter
|
|
289
|
+
|
|
290
|
+
```tsx
|
|
291
|
+
function ReportFilters({ onFilter }) {
|
|
292
|
+
const [reportMonth, setReportMonth] = useState('');
|
|
293
|
+
|
|
294
|
+
// Get current month as default
|
|
295
|
+
useEffect(() => {
|
|
296
|
+
const now = new Date();
|
|
297
|
+
const year = now.getFullYear();
|
|
298
|
+
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
299
|
+
setReportMonth(`${year}/${month}/01`);
|
|
300
|
+
}, []);
|
|
301
|
+
|
|
302
|
+
return (
|
|
303
|
+
<Stack direction="row" gap={2} alignItems="flex-end">
|
|
304
|
+
<MonthPicker
|
|
305
|
+
label="Report Month"
|
|
306
|
+
value={reportMonth}
|
|
307
|
+
onChange={(e) => setReportMonth(e.target.value)}
|
|
308
|
+
disableFuture
|
|
309
|
+
displayFormat="MMMM YYYY" // Shows "January 2024"
|
|
310
|
+
/>
|
|
311
|
+
<Button onClick={() => onFilter(reportMonth)}>
|
|
312
|
+
Generate Report
|
|
313
|
+
</Button>
|
|
314
|
+
</Stack>
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Credit Card Expiry
|
|
320
|
+
|
|
321
|
+
```tsx
|
|
322
|
+
function CreditCardExpiry() {
|
|
323
|
+
const [expiry, setExpiry] = useState('');
|
|
324
|
+
|
|
325
|
+
// Get current date for minimum
|
|
326
|
+
const today = new Date();
|
|
327
|
+
const minDate = `${today.getFullYear()}/${String(today.getMonth() + 1).padStart(2, '0')}/01`;
|
|
328
|
+
|
|
329
|
+
return (
|
|
330
|
+
<MonthPicker
|
|
331
|
+
label="Card Expiry"
|
|
332
|
+
value={expiry}
|
|
333
|
+
onChange={(e) => setExpiry(e.target.value)}
|
|
334
|
+
minDate={minDate}
|
|
335
|
+
displayFormat="MM/YYYY" // Common credit card format
|
|
336
|
+
helperText="Enter card expiration date"
|
|
337
|
+
required
|
|
338
|
+
/>
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### Fiscal Period Selector
|
|
344
|
+
|
|
345
|
+
```tsx
|
|
346
|
+
function FiscalPeriodSelector({ fiscalYearStart = 4 }) { // April
|
|
347
|
+
const [selectedMonth, setSelectedMonth] = useState('');
|
|
348
|
+
const [fiscalPeriod, setFiscalPeriod] = useState('');
|
|
349
|
+
|
|
350
|
+
// Calculate fiscal period from selected month
|
|
351
|
+
useEffect(() => {
|
|
352
|
+
if (!selectedMonth) return;
|
|
353
|
+
|
|
354
|
+
const [year, month] = selectedMonth.split('/').map(Number);
|
|
355
|
+
const fiscalMonth = month >= fiscalYearStart
|
|
356
|
+
? month - fiscalYearStart + 1
|
|
357
|
+
: month + (12 - fiscalYearStart + 1);
|
|
358
|
+
const fiscalYear = month >= fiscalYearStart ? year : year - 1;
|
|
359
|
+
|
|
360
|
+
setFiscalPeriod(`FY${fiscalYear} P${fiscalMonth}`);
|
|
361
|
+
}, [selectedMonth, fiscalYearStart]);
|
|
362
|
+
|
|
363
|
+
return (
|
|
364
|
+
<Stack gap={2}>
|
|
365
|
+
<MonthPicker
|
|
366
|
+
label="Select Period"
|
|
367
|
+
value={selectedMonth}
|
|
368
|
+
onChange={(e) => setSelectedMonth(e.target.value)}
|
|
369
|
+
displayFormat="MMMM YYYY"
|
|
370
|
+
/>
|
|
371
|
+
{fiscalPeriod && (
|
|
372
|
+
<Typography level="body-sm">Fiscal Period: {fiscalPeriod}</Typography>
|
|
373
|
+
)}
|
|
374
|
+
</Stack>
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### Monthly Budget Allocation
|
|
380
|
+
|
|
381
|
+
```tsx
|
|
382
|
+
function BudgetAllocation({ departments }) {
|
|
383
|
+
const [selectedMonth, setSelectedMonth] = useState('');
|
|
384
|
+
const [budgets, setBudgets] = useState({});
|
|
385
|
+
|
|
386
|
+
// Restrict to future months only
|
|
387
|
+
const today = new Date();
|
|
388
|
+
const nextMonth = new Date(today.getFullYear(), today.getMonth() + 1, 1);
|
|
389
|
+
const minDate = `${nextMonth.getFullYear()}/${String(nextMonth.getMonth() + 1).padStart(2, '0')}/01`;
|
|
390
|
+
|
|
391
|
+
return (
|
|
392
|
+
<form>
|
|
393
|
+
<MonthPicker
|
|
394
|
+
label="Budget Month"
|
|
395
|
+
value={selectedMonth}
|
|
396
|
+
onChange={(e) => setSelectedMonth(e.target.value)}
|
|
397
|
+
minDate={minDate}
|
|
398
|
+
displayFormat="MMMM YYYY"
|
|
399
|
+
helperText="Select month for budget allocation"
|
|
400
|
+
required
|
|
401
|
+
/>
|
|
402
|
+
|
|
403
|
+
{selectedMonth && departments.map((dept) => (
|
|
404
|
+
<CurrencyInput
|
|
405
|
+
key={dept.id}
|
|
406
|
+
label={`${dept.name} Budget`}
|
|
407
|
+
value={budgets[dept.id] || ''}
|
|
408
|
+
onChange={(value) => setBudgets(prev => ({
|
|
409
|
+
...prev,
|
|
410
|
+
[dept.id]: value
|
|
411
|
+
}))}
|
|
412
|
+
/>
|
|
413
|
+
))}
|
|
414
|
+
|
|
415
|
+
<Button type="submit">Allocate Budget</Button>
|
|
416
|
+
</form>
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### Year-Month Comparison
|
|
422
|
+
|
|
423
|
+
```tsx
|
|
424
|
+
function MonthComparison() {
|
|
425
|
+
const [month1, setMonth1] = useState('');
|
|
426
|
+
const [month2, setMonth2] = useState('');
|
|
427
|
+
|
|
428
|
+
return (
|
|
429
|
+
<Stack gap={2}>
|
|
430
|
+
<Stack direction="row" gap={2}>
|
|
431
|
+
<MonthPicker
|
|
432
|
+
label="Compare From"
|
|
433
|
+
value={month1}
|
|
434
|
+
onChange={(e) => setMonth1(e.target.value)}
|
|
435
|
+
disableFuture
|
|
436
|
+
/>
|
|
437
|
+
<MonthPicker
|
|
438
|
+
label="Compare To"
|
|
439
|
+
value={month2}
|
|
440
|
+
onChange={(e) => setMonth2(e.target.value)}
|
|
441
|
+
disableFuture
|
|
442
|
+
minDate={month1} // Must be after first month
|
|
443
|
+
/>
|
|
444
|
+
</Stack>
|
|
445
|
+
<Button disabled={!month1 || !month2}>
|
|
446
|
+
Compare Months
|
|
447
|
+
</Button>
|
|
448
|
+
</Stack>
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
## Props and Customization
|
|
454
|
+
|
|
455
|
+
### Key Props
|
|
456
|
+
|
|
457
|
+
| Prop | Type | Default | Description |
|
|
458
|
+
| --------------- | ----------------------------------------------------------- | -------------- | --------------------------------------- |
|
|
459
|
+
| `value` | `string` | - | Controlled value (format: `YYYY/MM/DD`) |
|
|
460
|
+
| `defaultValue` | `string` | - | Default value for uncontrolled mode |
|
|
461
|
+
| `onChange` | `(e: { target: { name?: string; value: string } }) => void` | - | Change handler |
|
|
462
|
+
| `format` | `string` | `'YYYY/MM/DD'` | Format for `value` and `onChange` |
|
|
463
|
+
| `displayFormat` | `string` | `'YYYY/MM'` | Format displayed in the input |
|
|
464
|
+
| `label` | `string` | - | Label text |
|
|
465
|
+
| `helperText` | `string` | - | Helper text below input |
|
|
466
|
+
| `error` | `boolean` | `false` | Error state |
|
|
467
|
+
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Component size |
|
|
468
|
+
| `disabled` | `boolean` | `false` | Disabled state |
|
|
469
|
+
| `required` | `boolean` | `false` | Required field indicator |
|
|
470
|
+
| `minDate` | `string` | - | Minimum selectable month |
|
|
471
|
+
| `maxDate` | `string` | - | Maximum selectable month |
|
|
472
|
+
| `disableFuture` | `boolean` | `false` | Disable all future months |
|
|
473
|
+
| `disablePast` | `boolean` | `false` | Disable all past months |
|
|
474
|
+
|
|
475
|
+
### Display Format Tokens
|
|
476
|
+
|
|
477
|
+
| Token | Description | Example |
|
|
478
|
+
| ------ | --------------- | ------- |
|
|
479
|
+
| `YYYY` | 4-digit year | 2024 |
|
|
480
|
+
| `MM` | 2-digit month | 04 |
|
|
481
|
+
| `DD` | 2-digit day | 01 |
|
|
482
|
+
| `MMMM` | Full month name | January |
|
|
483
|
+
|
|
484
|
+
Common display format patterns:
|
|
485
|
+
|
|
486
|
+
- `YYYY/MM` - Default (2024/04)
|
|
487
|
+
- `YYYY-MM` - ISO-like (2024-04)
|
|
488
|
+
- `MM/YYYY` - European (04/2024)
|
|
489
|
+
- `MMMM YYYY` - Full name (April 2024)
|
|
490
|
+
- `YYYY.MM` - Period separator (2024.04)
|
|
491
|
+
|
|
492
|
+
### Value Format Understanding
|
|
493
|
+
|
|
494
|
+
```tsx
|
|
495
|
+
// MonthPicker always uses day=01 in values
|
|
496
|
+
// Even though display shows "2024/04", the value is "2024/04/01"
|
|
497
|
+
|
|
498
|
+
<MonthPicker
|
|
499
|
+
value="2024/04/01" // Must include day
|
|
500
|
+
displayFormat="YYYY/MM" // Shows "2024/04"
|
|
501
|
+
onChange={(e) => {
|
|
502
|
+
console.log(e.target.value); // "2024/04/01"
|
|
503
|
+
// Extract just year and month
|
|
504
|
+
const [year, month] = e.target.value.split('/');
|
|
505
|
+
console.log(`${year}-${month}`); // "2024-04"
|
|
506
|
+
}}
|
|
507
|
+
/>
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
### Format vs DisplayFormat
|
|
511
|
+
|
|
512
|
+
```tsx
|
|
513
|
+
// format: Affects the value in onChange
|
|
514
|
+
// displayFormat: Affects what users see in the input
|
|
515
|
+
|
|
516
|
+
<MonthPicker
|
|
517
|
+
format="YYYY-MM-DD" // onChange returns "2024-04-01"
|
|
518
|
+
displayFormat="MMMM YYYY" // Input shows "April 2024"
|
|
519
|
+
onChange={(e) => {
|
|
520
|
+
console.log(e.target.value); // "2024-04-01"
|
|
521
|
+
}}
|
|
522
|
+
/>
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
### Controlled vs Uncontrolled
|
|
526
|
+
|
|
527
|
+
```tsx
|
|
528
|
+
// Uncontrolled - component manages state
|
|
529
|
+
<MonthPicker
|
|
530
|
+
defaultValue="2024/04/01"
|
|
531
|
+
onChange={(e) => console.log(e.target.value)}
|
|
532
|
+
/>
|
|
533
|
+
|
|
534
|
+
// Controlled - you manage state
|
|
535
|
+
const [month, setMonth] = useState('2024/04/01');
|
|
536
|
+
<MonthPicker
|
|
537
|
+
value={month}
|
|
538
|
+
onChange={(e) => setMonth(e.target.value)}
|
|
539
|
+
/>
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
## Accessibility
|
|
543
|
+
|
|
544
|
+
MonthPicker includes built-in accessibility features:
|
|
545
|
+
|
|
546
|
+
### ARIA Attributes
|
|
547
|
+
|
|
548
|
+
- Input has proper `role="textbox"`
|
|
549
|
+
- Calendar button has `aria-label="Toggle Calendar"`
|
|
550
|
+
- Month grid uses proper navigation roles
|
|
551
|
+
- Selected month marked with `aria-selected`
|
|
552
|
+
|
|
553
|
+
### Keyboard Navigation
|
|
554
|
+
|
|
555
|
+
- **Tab**: Move focus between input and calendar button
|
|
556
|
+
- **Enter/Space**: Open calendar when focused on button
|
|
557
|
+
- **Arrow Keys**: Navigate between months in calendar
|
|
558
|
+
- **Escape**: Close calendar popup
|
|
559
|
+
- **Enter**: Select focused month
|
|
560
|
+
|
|
561
|
+
### Screen Reader Support
|
|
562
|
+
|
|
563
|
+
```tsx
|
|
564
|
+
// Months are announced with full context
|
|
565
|
+
<button aria-label="April 2024">Apr</button>
|
|
566
|
+
|
|
567
|
+
// Navigation buttons are descriptive
|
|
568
|
+
<button aria-label="Previous Year">←</button>
|
|
569
|
+
<button aria-label="Next Year">→</button>
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
### Focus Management
|
|
573
|
+
|
|
574
|
+
- Focus moves to current year's month grid when opened
|
|
575
|
+
- Focus returns to input when calendar closes
|
|
576
|
+
- Clear visual focus indicators on all interactive elements
|
|
577
|
+
|
|
578
|
+
## Best Practices
|
|
579
|
+
|
|
580
|
+
### ✅ Do
|
|
581
|
+
|
|
582
|
+
1. **Use appropriate display formats**: Match the display format to user expectations
|
|
583
|
+
|
|
584
|
+
```tsx
|
|
585
|
+
// ✅ Good: User-friendly display format
|
|
586
|
+
<MonthPicker
|
|
587
|
+
displayFormat="MMMM YYYY" // "January 2024" is clearer
|
|
588
|
+
label="Billing Month"
|
|
589
|
+
/>
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
2. **Set reasonable date limits**: Restrict months based on context
|
|
593
|
+
|
|
594
|
+
```tsx
|
|
595
|
+
// ✅ Good: Logical constraints for billing
|
|
596
|
+
<MonthPicker
|
|
597
|
+
disableFuture // Can't bill for future months
|
|
598
|
+
minDate="2020/01/01" // Company started in 2020
|
|
599
|
+
/>
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
3. **Provide context in helper text**: Guide users on selection purpose
|
|
603
|
+
|
|
604
|
+
```tsx
|
|
605
|
+
// ✅ Good: Clear guidance
|
|
606
|
+
<MonthPicker
|
|
607
|
+
label="Statement Month"
|
|
608
|
+
helperText="Select the month for your account statement"
|
|
609
|
+
/>
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
4. **Handle the day value**: Remember values include day set to 01
|
|
613
|
+
|
|
614
|
+
```tsx
|
|
615
|
+
// ✅ Good: Extract month/year when needed
|
|
616
|
+
const handleChange = (e) => {
|
|
617
|
+
const [year, month] = e.target.value.split('/');
|
|
618
|
+
setSelectedPeriod({ year: parseInt(year), month: parseInt(month) });
|
|
619
|
+
};
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
### ❌ Don't
|
|
623
|
+
|
|
624
|
+
1. **Don't expect day-only display in values**: Values always include day
|
|
625
|
+
|
|
626
|
+
```tsx
|
|
627
|
+
// ❌ Bad: Expecting value without day
|
|
628
|
+
<MonthPicker
|
|
629
|
+
value="2024/04" // Wrong! Needs "2024/04/01"
|
|
630
|
+
/>
|
|
631
|
+
|
|
632
|
+
// ✅ Good: Full value format
|
|
633
|
+
<MonthPicker
|
|
634
|
+
value="2024/04/01"
|
|
635
|
+
/>
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
2. **Don't use inconsistent formats**: Match value format to format prop
|
|
639
|
+
|
|
640
|
+
```tsx
|
|
641
|
+
// ❌ Bad: Mismatched formats
|
|
642
|
+
<MonthPicker
|
|
643
|
+
format="YYYY/MM/DD"
|
|
644
|
+
value="04/2024" // Wrong format
|
|
645
|
+
/>
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
3. **Don't forget to handle empty states**: Validate before processing
|
|
649
|
+
|
|
650
|
+
4. **Don't use for day-level selection**: Use DatePicker when days matter
|
|
651
|
+
|
|
652
|
+
## Performance Considerations
|
|
653
|
+
|
|
654
|
+
### Memoize Handlers
|
|
655
|
+
|
|
656
|
+
When using MonthPicker in complex forms:
|
|
657
|
+
|
|
658
|
+
```tsx
|
|
659
|
+
const handleChange = useCallback((e) => {
|
|
660
|
+
setMonth(e.target.value);
|
|
661
|
+
}, []);
|
|
662
|
+
|
|
663
|
+
<MonthPicker value={month} onChange={handleChange} />
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
### Extract Month/Year Efficiently
|
|
667
|
+
|
|
668
|
+
When you need just the month and year:
|
|
669
|
+
|
|
670
|
+
```tsx
|
|
671
|
+
const { year, month } = useMemo(() => {
|
|
672
|
+
if (!selectedMonth) return { year: null, month: null };
|
|
673
|
+
const [year, month] = selectedMonth.split('/').map(Number);
|
|
674
|
+
return { year, month };
|
|
675
|
+
}, [selectedMonth]);
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
### Date Calculations
|
|
679
|
+
|
|
680
|
+
For fiscal year or period calculations:
|
|
681
|
+
|
|
682
|
+
```tsx
|
|
683
|
+
const fiscalData = useMemo(() => {
|
|
684
|
+
if (!selectedMonth) return null;
|
|
685
|
+
|
|
686
|
+
const [year, month] = selectedMonth.split('/').map(Number);
|
|
687
|
+
const fiscalYear = month >= 4 ? year : year - 1;
|
|
688
|
+
const fiscalQuarter = Math.ceil((month >= 4 ? month - 3 : month + 9) / 3);
|
|
689
|
+
|
|
690
|
+
return { fiscalYear, fiscalQuarter };
|
|
691
|
+
}, [selectedMonth]);
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
MonthPicker simplifies month-level date selection for billing, reporting, and scheduling scenarios. Remember that values always include day (set to 01), and use displayFormat to show user-friendly month representations like full month names.
|