@ceed/cds 1.24.1-next.3 → 1.26.0

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 (65) hide show
  1. package/dist/chunks/rehype-accent-FZRUD7VI.js +39 -0
  2. package/dist/components/CurrencyInput/CurrencyInput.d.ts +1 -1
  3. package/dist/components/CurrencyInput/hooks/use-currency-setting.d.ts +2 -2
  4. package/dist/components/DataTable/components.d.ts +2 -1
  5. package/dist/components/DataTable/hooks.d.ts +1 -1
  6. package/dist/components/DataTable/styled.d.ts +3 -1
  7. package/dist/components/DataTable/types.d.ts +11 -0
  8. package/dist/components/DataTable/utils.d.ts +2 -2
  9. package/dist/components/RadioTileGroup/RadioTileGroup.d.ts +56 -0
  10. package/dist/components/RadioTileGroup/index.d.ts +3 -0
  11. package/dist/components/data-display/DataTable.md +177 -1
  12. package/dist/components/data-display/InfoSign.md +74 -91
  13. package/dist/components/data-display/Typography.md +411 -94
  14. package/dist/components/feedback/CircularProgress.md +257 -0
  15. package/dist/components/feedback/Dialog.md +76 -62
  16. package/dist/components/feedback/Modal.md +430 -138
  17. package/dist/components/feedback/Skeleton.md +280 -0
  18. package/dist/components/feedback/llms.txt +2 -0
  19. package/dist/components/index.d.ts +1 -0
  20. package/dist/components/inputs/Autocomplete.md +356 -107
  21. package/dist/components/inputs/ButtonGroup.md +115 -104
  22. package/dist/components/inputs/CurrencyInput.md +183 -5
  23. package/dist/components/inputs/DatePicker.md +108 -431
  24. package/dist/components/inputs/DateRangePicker.md +131 -492
  25. package/dist/components/inputs/FilterableCheckboxGroup.md +145 -19
  26. package/dist/components/inputs/FormControl.md +361 -0
  27. package/dist/components/inputs/IconButton.md +137 -88
  28. package/dist/components/inputs/Input.md +204 -73
  29. package/dist/components/inputs/MonthPicker.md +95 -422
  30. package/dist/components/inputs/MonthRangePicker.md +89 -466
  31. package/dist/components/inputs/PercentageInput.md +185 -16
  32. package/dist/components/inputs/RadioButton.md +163 -35
  33. package/dist/components/inputs/RadioList.md +241 -0
  34. package/dist/components/inputs/RadioTileGroup.md +507 -0
  35. package/dist/components/inputs/Select.md +222 -326
  36. package/dist/components/inputs/Slider.md +334 -0
  37. package/dist/components/inputs/Switch.md +143 -376
  38. package/dist/components/inputs/Textarea.md +213 -10
  39. package/dist/components/inputs/Uploader/Uploader.md +145 -66
  40. package/dist/components/inputs/llms.txt +4 -0
  41. package/dist/components/navigation/Breadcrumbs.md +57 -308
  42. package/dist/components/navigation/Drawer.md +180 -0
  43. package/dist/components/navigation/Dropdown.md +98 -215
  44. package/dist/components/navigation/IconMenuButton.md +40 -502
  45. package/dist/components/navigation/InsetDrawer.md +281 -650
  46. package/dist/components/navigation/Link.md +31 -348
  47. package/dist/components/navigation/Menu.md +92 -285
  48. package/dist/components/navigation/MenuButton.md +55 -448
  49. package/dist/components/navigation/Pagination.md +47 -338
  50. package/dist/components/navigation/Stepper.md +160 -28
  51. package/dist/components/navigation/Tabs.md +57 -316
  52. package/dist/components/surfaces/Accordions.md +49 -804
  53. package/dist/components/surfaces/Card.md +97 -157
  54. package/dist/components/surfaces/Divider.md +83 -234
  55. package/dist/components/surfaces/Sheet.md +153 -328
  56. package/dist/guides/ThemeProvider.md +89 -0
  57. package/dist/guides/llms.txt +9 -0
  58. package/dist/index.browser.js +224 -0
  59. package/dist/index.browser.js.map +7 -0
  60. package/dist/index.cjs +726 -425
  61. package/dist/index.d.ts +1 -1
  62. package/dist/index.js +641 -396
  63. package/dist/llms.txt +9 -0
  64. package/framer/index.js +1 -163
  65. package/package.json +22 -17
@@ -2,7 +2,9 @@
2
2
 
3
3
  ## Introduction
4
4
 
5
- DateRangePicker is a form input component that allows users to select a date range (start date and end date) from a calendar popup or by typing directly into the input field. It provides flexible date formatting options, date range restrictions, and supports both controlled and uncontrolled modes. DateRangePicker is ideal for booking systems, reporting filters, scheduling, and any scenario requiring a start-to-end date selection.
5
+ DateRangePicker is a form input component that allows users to select a date range (start date and end date) from a calendar popup or by typing directly into the input field. It supports flexible date formatting, date restrictions, and both controlled and uncontrolled modes.
6
+
7
+ DateRangePicker is ideal for booking systems, reporting filters, scheduling, and any scenario requiring a start-to-end date selection. For single date selection, use DatePicker instead. For month-level granularity, use MonthRangePicker.
6
8
 
7
9
  ```tsx
8
10
  <DateRangePicker onChange={onChange} />
@@ -28,14 +30,22 @@ DateRangePicker is a form input component that allows users to select a date ran
28
30
  | hideClearButton | — | — |
29
31
  | size | — | — |
30
32
 
31
- > ⚠️ **Usage Warning** ⚠️
33
+ > **Use built-in form props**
34
+ >
35
+ > This component natively supports form elements such as `label` and `helperText` props.
36
+ > When building forms, use these built-in props instead of manually composing labels and helper text with Typography.
32
37
  >
33
- > DateRangePicker involves complex date range handling:
38
+ > ```tsx
39
+ > // Recommended: use built-in props
40
+ > <DateRangePicker label="Period" helperText="Select start and end dates" />
34
41
  >
35
- > - **Value format**: Values use the format `"startDate - endDate"` (e.g., `"2024/04/01 - 2024/04/15"`)
36
- > - **Format consistency**: Both dates must match the `format` prop
37
- > - **Start/End validation**: Ensure start date is before end date
38
- > - **Range span**: Consider maximum range limits for performance and UX
42
+ > // Not recommended: manually composing with Typography
43
+ > <FormControl>
44
+ > <Typography level="title-sm" component="label">Period</Typography>
45
+ > <DateRangePicker />
46
+ > <Typography level="body-xs" color="neutral">Select start and end dates</Typography>
47
+ > </FormControl>
48
+ > ```
39
49
 
40
50
  ## Usage
41
51
 
@@ -55,19 +65,9 @@ function DateRangeForm() {
55
65
  }
56
66
  ```
57
67
 
58
- ## Examples
68
+ ## Sizes
59
69
 
60
- ### Playground
61
-
62
- Interactive example with all controls.
63
-
64
- ```tsx
65
- <DateRangePicker onChange={onChange} />
66
- ```
67
-
68
- ### Sizes
69
-
70
- DateRangePicker supports three sizes for different layouts.
70
+ DateRangePicker supports three sizes (`sm`, `md`, `lg`) to fit different layouts and density requirements.
71
71
 
72
72
  ```tsx
73
73
  <Stack gap={2}>
@@ -77,20 +77,11 @@ DateRangePicker supports three sizes for different layouts.
77
77
  </Stack>
78
78
  ```
79
79
 
80
- ### Disabled
81
-
82
- Prevent user interaction when disabled.
83
-
84
- ```tsx
85
- <DateRangePicker
86
- onChange={onChange}
87
- disabled
88
- />
89
- ```
80
+ ## Form Features
90
81
 
91
- ### With Label
82
+ ### Label
92
83
 
93
- Add a label above the date range picker.
84
+ Add a label above the date range picker using the `label` prop.
94
85
 
95
86
  ```tsx
96
87
  <DateRangePicker
@@ -99,9 +90,9 @@ Add a label above the date range picker.
99
90
  />
100
91
  ```
101
92
 
102
- ### With Helper Text
93
+ ### Helper Text
103
94
 
104
- Provide additional guidance below the input.
95
+ Provide additional guidance below the input using the `helperText` prop.
105
96
 
106
97
  ```tsx
107
98
  <DateRangePicker
@@ -113,7 +104,7 @@ Provide additional guidance below the input.
113
104
 
114
105
  ### Error State
115
106
 
116
- Show validation errors with error styling.
107
+ Display validation errors by combining the `error` and `helperText` props.
117
108
 
118
109
  ```tsx
119
110
  <DateRangePicker
@@ -126,7 +117,7 @@ Show validation errors with error styling.
126
117
 
127
118
  ### Required Field
128
119
 
129
- Mark the field as required in forms.
120
+ Mark the field as required in forms. This displays a required indicator next to the label.
130
121
 
131
122
  ```tsx
132
123
  <DateRangePicker
@@ -137,9 +128,11 @@ Mark the field as required in forms.
137
128
  />
138
129
  ```
139
130
 
131
+ ## Date Restrictions
132
+
140
133
  ### Minimum Date
141
134
 
142
- Restrict selection to dates on or after a minimum date.
135
+ Restrict selection to dates on or after a specified minimum date using the `minDate` prop.
143
136
 
144
137
  ```tsx
145
138
  <DateRangePicker
@@ -150,7 +143,7 @@ Restrict selection to dates on or after a minimum date.
150
143
 
151
144
  ### Maximum Date
152
145
 
153
- Restrict selection to dates on or before a maximum date.
146
+ Restrict selection to dates on or before a specified maximum date using the `maxDate` prop.
154
147
 
155
148
  ```tsx
156
149
  <DateRangePicker
@@ -161,7 +154,7 @@ Restrict selection to dates on or before a maximum date.
161
154
 
162
155
  ### Disable Future Dates
163
156
 
164
- Prevent selection of dates in the future.
157
+ Prevent selection of dates in the future using `disableFuture`. Useful for report filters and historical data queries.
165
158
 
166
159
  ```tsx
167
160
  <DateRangePicker
@@ -172,7 +165,7 @@ Prevent selection of dates in the future.
172
165
 
173
166
  ### Disable Past Dates
174
167
 
175
- Prevent selection of dates in the past.
168
+ Prevent selection of dates in the past using `disablePast`. Useful for booking and scheduling forms.
176
169
 
177
170
  ```tsx
178
171
  <DateRangePicker
@@ -181,9 +174,11 @@ Prevent selection of dates in the past.
181
174
  />
182
175
  ```
183
176
 
177
+ ## Controlled vs Uncontrolled
178
+
184
179
  ### Controlled
185
180
 
186
- Parent component manages the date range state.
181
+ In controlled mode, the parent component manages the date range state via `value` and `onChange`. This gives full control over the value, allowing programmatic updates.
187
182
 
188
183
  ```tsx
189
184
  <Stack gap={2}>
@@ -210,7 +205,7 @@ Parent component manages the date range state.
210
205
 
211
206
  ### Uncontrolled
212
207
 
213
- Component manages its own state internally.
208
+ In uncontrolled mode, the component manages its own state internally. Set an initial value with `defaultValue` and use `onChange` to react to user selections.
214
209
 
215
210
  ```tsx
216
211
  <DateRangePicker
@@ -221,9 +216,11 @@ Component manages its own state internally.
221
216
  />
222
217
  ```
223
218
 
224
- ### With Formats
219
+ ## Formatting
225
220
 
226
- Different value formats for the `onChange` event.
221
+ ### Value Format (`format`)
222
+
223
+ The `format` prop controls the format of the value returned in `onChange`. The input always displays in `YYYY/MM/DD` by default, but the `onChange` value follows the specified format.
227
224
 
228
225
  ```tsx
229
226
  <Stack gap={2}>
@@ -254,9 +251,9 @@ Different value formats for the `onChange` event.
254
251
  </Stack>
255
252
  ```
256
253
 
257
- ### With Display Formats
254
+ ### Display Format (`displayFormat`)
258
255
 
259
- Different display formats shown in the input field.
256
+ The `displayFormat` prop controls what the user sees in the input field. This is independent of the `format` prop, allowing you to store data in one format while displaying another.
260
257
 
261
258
  ```tsx
262
259
  <Stack gap={2}>
@@ -287,9 +284,38 @@ Different display formats shown in the input field.
287
284
  </Stack>
288
285
  ```
289
286
 
287
+ **Supported format tokens:**
288
+
289
+ | Token | Description | Example |
290
+ | ------ | ------------- | ------- |
291
+ | `YYYY` | 4-digit year | 2024 |
292
+ | `MM` | 2-digit month | 04 |
293
+ | `DD` | 2-digit day | 15 |
294
+
295
+ ```tsx
296
+ // format affects onChange value; displayFormat affects what users see
297
+ <DateRangePicker
298
+ format="YYYY-MM-DD" // onChange returns "2024-04-01 - 2024-04-15"
299
+ displayFormat="MM/DD/YYYY" // Input shows "04/01/2024 - 04/15/2024"
300
+ />
301
+ ```
302
+
303
+ ## Interaction Modes
304
+
305
+ ### Disabled
306
+
307
+ Prevent all user interaction when the component is disabled.
308
+
309
+ ```tsx
310
+ <DateRangePicker
311
+ onChange={onChange}
312
+ disabled
313
+ />
314
+ ```
315
+
290
316
  ### Input Read Only
291
317
 
292
- Allow calendar selection only, prevent typing.
318
+ Allow calendar selection only while preventing direct typing. Useful on mobile devices where keyboard input is impractical.
293
319
 
294
320
  ```tsx
295
321
  <DateRangePicker
@@ -301,7 +327,7 @@ Allow calendar selection only, prevent typing.
301
327
 
302
328
  ### Read Only
303
329
 
304
- Fully read-only state with no interaction.
330
+ Fully read-only state where neither typing nor calendar selection is available. Use this to display a date range value without allowing changes.
305
331
 
306
332
  ```tsx
307
333
  <DateRangePicker
@@ -311,9 +337,11 @@ Fully read-only state with no interaction.
311
337
  />
312
338
  ```
313
339
 
340
+ ## Additional Options
341
+
314
342
  ### Hide Clear Button
315
343
 
316
- Remove the clear button from the calendar popup.
344
+ Remove the clear button from the calendar popup using `hideClearButton`.
317
345
 
318
346
  ```tsx
319
347
  <DateRangePicker
@@ -325,7 +353,7 @@ Remove the clear button from the calendar popup.
325
353
 
326
354
  ### With Reset Button
327
355
 
328
- Example with an external reset button.
356
+ Example of integrating an external reset button to clear the selected date range programmatically.
329
357
 
330
358
  ```tsx
331
359
  <div style={{
@@ -339,25 +367,6 @@ Example with an external reset button.
339
367
  </div>
340
368
  ```
341
369
 
342
- ## When to Use
343
-
344
- ### ✅ Good Use Cases
345
-
346
- - **Booking systems**: Hotel check-in/out, car rental periods
347
- - **Report filtering**: Date range filters for analytics and reports
348
- - **Event scheduling**: Conference dates, project timelines
349
- - **Leave requests**: Vacation start and end dates
350
- - **Subscription periods**: Billing cycles, membership durations
351
- - **Data exports**: Selecting date ranges for exporting data
352
-
353
- ### ❌ When Not to Use
354
-
355
- - **Single date selection**: Use DatePicker instead
356
- - **Month/Year ranges**: Use MonthRangePicker for month-level granularity
357
- - **Predefined periods**: For "Last 7 days", "This month", use dropdown selection
358
- - **Time ranges**: For time-based ranges, use dedicated time components
359
- - **Recurring dates**: For repeating schedules, consider a custom solution
360
-
361
370
  ## Common Use Cases
362
371
 
363
372
  ### Booking Form
@@ -367,38 +376,29 @@ function BookingForm() {
367
376
  const [dates, setDates] = useState('');
368
377
  const [error, setError] = useState('');
369
378
 
370
- const validateRange = (value) => {
371
- if (!value) return 'Please select dates';
372
- const [start, end] = value.split(' - ');
373
- const startDate = new Date(start);
374
- const endDate = new Date(end);
375
- const days = (endDate - startDate) / (1000 * 60 * 60 * 24);
376
- if (days > 30) return 'Maximum stay is 30 days';
377
- if (days < 1) return 'Minimum stay is 1 night';
378
- return '';
379
- };
380
-
381
379
  const handleChange = (e) => {
382
380
  const value = e.target.value;
383
381
  setDates(value);
384
- setError(validateRange(value));
382
+
383
+ if (!value) {
384
+ setError('Please select dates');
385
+ } else {
386
+ const [start, end] = value.split(' - ');
387
+ const days = (new Date(end) - new Date(start)) / (1000 * 60 * 60 * 24);
388
+ setError(days > 30 ? 'Maximum stay is 30 days' : '');
389
+ }
385
390
  };
386
391
 
387
392
  return (
388
- <form>
389
- <DateRangePicker
390
- label="Check-in / Check-out"
391
- value={dates}
392
- onChange={handleChange}
393
- error={!!error}
394
- helperText={error || 'Select your stay dates'}
395
- disablePast
396
- required
397
- />
398
- <Button type="submit" disabled={!!error || !dates}>
399
- Search Availability
400
- </Button>
401
- </form>
393
+ <DateRangePicker
394
+ label="Check-in / Check-out"
395
+ value={dates}
396
+ onChange={handleChange}
397
+ error={!!error}
398
+ helperText={error || 'Select your stay dates'}
399
+ disablePast
400
+ required
401
+ />
402
402
  );
403
403
  }
404
404
  ```
@@ -421,6 +421,7 @@ function ReportFilters({ onFilter }) {
421
421
  label="Report Period"
422
422
  value={dateRange}
423
423
  onChange={(e) => setDateRange(e.target.value)}
424
+ format="YYYY-MM-DD"
424
425
  disableFuture
425
426
  helperText="Select date range for the report"
426
427
  />
@@ -430,440 +431,78 @@ function ReportFilters({ onFilter }) {
430
431
  }
431
432
  ```
432
433
 
433
- ### Leave Request Form
434
-
435
- ```tsx
436
- function LeaveRequestForm({ minDate, maxDate }) {
437
- const [leaveDates, setLeaveDates] = useState('');
438
- const [leaveType, setLeaveType] = useState('annual');
439
-
440
- // Calculate business days
441
- const getBusinessDays = (dateRange) => {
442
- if (!dateRange) return 0;
443
- const [start, end] = dateRange.split(' - ').map(d => new Date(d));
444
- let count = 0;
445
- const current = new Date(start);
446
- while (current <= end) {
447
- const day = current.getDay();
448
- if (day !== 0 && day !== 6) count++;
449
- current.setDate(current.getDate() + 1);
450
- }
451
- return count;
452
- };
453
-
454
- const businessDays = getBusinessDays(leaveDates);
455
-
456
- return (
457
- <form>
458
- <Select
459
- label="Leave Type"
460
- value={leaveType}
461
- onChange={(e) => setLeaveType(e.target.value)}
462
- >
463
- <Option value="annual">Annual Leave</Option>
464
- <Option value="sick">Sick Leave</Option>
465
- <Option value="personal">Personal Leave</Option>
466
- </Select>
467
-
468
- <DateRangePicker
469
- label="Leave Period"
470
- value={leaveDates}
471
- onChange={(e) => setLeaveDates(e.target.value)}
472
- minDate={minDate}
473
- maxDate={maxDate}
474
- disablePast
475
- helperText={businessDays > 0 ? `${businessDays} business days` : 'Select dates'}
476
- required
477
- />
478
-
479
- <Button type="submit">Submit Request</Button>
480
- </form>
481
- );
482
- }
483
- ```
484
-
485
- ### Date Range with Presets
486
-
487
- ```tsx
488
- function DateRangeWithPresets() {
489
- const [dateRange, setDateRange] = useState('');
490
-
491
- const formatDate = (date) => {
492
- const year = date.getFullYear();
493
- const month = String(date.getMonth() + 1).padStart(2, '0');
494
- const day = String(date.getDate()).padStart(2, '0');
495
- return `${year}/${month}/${day}`;
496
- };
497
-
498
- const applyPreset = (preset) => {
499
- const today = new Date();
500
- let start, end;
501
-
502
- switch (preset) {
503
- case 'last7':
504
- end = today;
505
- start = new Date(today.getTime() - 7 * 24 * 60 * 60 * 1000);
506
- break;
507
- case 'last30':
508
- end = today;
509
- start = new Date(today.getTime() - 30 * 24 * 60 * 60 * 1000);
510
- break;
511
- case 'thisMonth':
512
- start = new Date(today.getFullYear(), today.getMonth(), 1);
513
- end = new Date(today.getFullYear(), today.getMonth() + 1, 0);
514
- break;
515
- case 'lastMonth':
516
- start = new Date(today.getFullYear(), today.getMonth() - 1, 1);
517
- end = new Date(today.getFullYear(), today.getMonth(), 0);
518
- break;
519
- default:
520
- return;
521
- }
522
-
523
- setDateRange(`${formatDate(start)} - ${formatDate(end)}`);
524
- };
525
-
526
- return (
527
- <Stack gap={2}>
528
- <Stack direction="row" gap={1}>
529
- <Button variant="plain" onClick={() => applyPreset('last7')}>
530
- Last 7 Days
531
- </Button>
532
- <Button variant="plain" onClick={() => applyPreset('last30')}>
533
- Last 30 Days
534
- </Button>
535
- <Button variant="plain" onClick={() => applyPreset('thisMonth')}>
536
- This Month
537
- </Button>
538
- <Button variant="plain" onClick={() => applyPreset('lastMonth')}>
539
- Last Month
540
- </Button>
541
- </Stack>
542
- <DateRangePicker
543
- label="Custom Range"
544
- value={dateRange}
545
- onChange={(e) => setDateRange(e.target.value)}
546
- disableFuture
547
- />
548
- </Stack>
549
- );
550
- }
551
- ```
552
-
553
- ### API Integration
434
+ ### Separate API and Display Formats
554
435
 
555
436
  ```tsx
556
437
  function DataExport() {
557
438
  const [dateRange, setDateRange] = useState('');
558
- const [isExporting, setIsExporting] = useState(false);
559
-
560
- const handleExport = async () => {
561
- if (!dateRange) return;
562
-
563
- setIsExporting(true);
564
- const [startDate, endDate] = dateRange.split(' - ');
565
-
566
- try {
567
- const response = await api.exportData({
568
- startDate: startDate.replace(/\//g, '-'), // Convert to API format
569
- endDate: endDate.replace(/\//g, '-'),
570
- });
571
- downloadFile(response.data);
572
- } finally {
573
- setIsExporting(false);
574
- }
575
- };
576
439
 
577
440
  return (
578
- <Stack gap={2}>
579
- <DateRangePicker
580
- label="Export Period"
581
- value={dateRange}
582
- onChange={(e) => setDateRange(e.target.value)}
583
- format="YYYY/MM/DD"
584
- disableFuture
585
- required
586
- />
587
- <Button
588
- onClick={handleExport}
589
- loading={isExporting}
590
- disabled={!dateRange}
591
- >
592
- Export Data
593
- </Button>
594
- </Stack>
441
+ <DateRangePicker
442
+ label="Export Period"
443
+ value={dateRange}
444
+ onChange={(e) => setDateRange(e.target.value)}
445
+ format="YYYY-MM-DD"
446
+ displayFormat="DD/MM/YYYY"
447
+ disableFuture
448
+ required
449
+ />
595
450
  );
596
451
  }
597
452
  ```
598
453
 
599
- ## Props and Customization
600
-
601
- ### Key Props
602
-
603
- | Prop | Type | Default | Description |
604
- | ----------------- | ----------------------------------------------------------- | ---------------- | ------------------------------------------ |
605
- | `value` | `string` | - | Controlled value (`"startDate - endDate"`) |
606
- | `defaultValue` | `string` | - | Default value for uncontrolled mode |
607
- | `onChange` | `(e: { target: { name?: string; value: string } }) => void` | - | Change handler |
608
- | `format` | `string` | `'YYYY/MM/DD'` | Format for `value` and `onChange` |
609
- | `displayFormat` | `string` | Same as `format` | Format displayed in the input |
610
- | `label` | `string` | - | Label text |
611
- | `helperText` | `string` | - | Helper text below input |
612
- | `error` | `boolean` | `false` | Error state |
613
- | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Component size |
614
- | `disabled` | `boolean` | `false` | Disabled state |
615
- | `required` | `boolean` | `false` | Required field indicator |
616
- | `minDate` | `string` | - | Minimum selectable date |
617
- | `maxDate` | `string` | - | Maximum selectable date |
618
- | `disableFuture` | `boolean` | `false` | Disable all future dates |
619
- | `disablePast` | `boolean` | `false` | Disable all past dates |
620
- | `inputReadOnly` | `boolean` | `false` | Prevent typing, calendar only |
621
- | `readOnly` | `boolean` | `false` | Fully read-only |
622
- | `hideClearButton` | `boolean` | `false` | Hide clear button in calendar |
623
-
624
- ### Value Format
625
-
626
- The value is always a string with start and end dates separated by `-`:
627
-
628
- ```tsx
629
- // Value format: "startDate - endDate"
630
- const value = "2024/04/01 - 2024/04/15";
631
-
632
- // Parsing the value
633
- const [startDate, endDate] = value.split(' - ');
634
- console.log(startDate); // "2024/04/01"
635
- console.log(endDate); // "2024/04/15"
636
- ```
454
+ ## Best Practices
637
455
 
638
- ### Format vs DisplayFormat
456
+ 1. **Match `value` to `format`**: The `value` and `defaultValue` strings must always match the `format` prop. Mismatched formats will cause unexpected behavior.
639
457
 
640
458
  ```tsx
641
- // format: Affects the value in onChange
642
- // displayFormat: Affects what users see in the input
459
+ // Correct: value matches format
460
+ <DateRangePicker format="YYYY-MM-DD" value="2024-04-01 - 2024-04-15" />
643
461
 
644
- <DateRangePicker
645
- format="YYYY-MM-DD" // onChange returns "2024-04-01 - 2024-04-15"
646
- displayFormat="MM/DD/YYYY" // Input shows "04/01/2024 - 04/15/2024"
647
- onChange={(e) => {
648
- console.log(e.target.value); // "2024-04-01 - 2024-04-15"
649
- }}
650
- />
462
+ // Incorrect: value does not match format
463
+ <DateRangePicker format="YYYY/MM/DD" value="04/01/2024 - 04/15/2024" />
651
464
  ```
652
465
 
653
- ### Supported Format Tokens
654
-
655
- | Token | Description | Example |
656
- | ------ | ------------- | ------- |
657
- | `YYYY` | 4-digit year | 2024 |
658
- | `MM` | 2-digit month | 04 |
659
- | `DD` | 2-digit day | 15 |
660
-
661
- ### Controlled vs Uncontrolled
466
+ 2. **Use `displayFormat` to separate concerns**: Store data in an API-friendly format (e.g., `YYYY-MM-DD`) while displaying a user-friendly format (e.g., `MM/DD/YYYY`).
662
467
 
663
468
  ```tsx
664
- // Uncontrolled - component manages state
469
+ // Store ISO format, display locale format
665
470
  <DateRangePicker
666
- defaultValue="2024/04/01 - 2024/04/15"
667
- onChange={(e) => console.log(e.target.value)}
668
- />
669
-
670
- // Controlled - you manage state
671
- const [dateRange, setDateRange] = useState('2024/04/01 - 2024/04/15');
672
- <DateRangePicker
673
- value={dateRange}
674
- onChange={(e) => setDateRange(e.target.value)}
471
+ format="YYYY-MM-DD"
472
+ displayFormat="MM/DD/YYYY"
675
473
  />
676
474
  ```
677
475
 
678
- ## Accessibility
679
-
680
- DateRangePicker includes built-in accessibility features:
681
-
682
- ### ARIA Attributes
683
-
684
- - Input has proper `role="textbox"`
685
- - Calendar button has `aria-label="Toggle Calendar"`
686
- - Calendar popup uses `role="tooltip"` with proper labeling
687
- - Date buttons announce the full date to screen readers
688
-
689
- ### Keyboard Navigation
690
-
691
- - **Tab**: Move focus between input and calendar button
692
- - **Enter/Space**: Open calendar when focused on button
693
- - **Arrow Keys**: Navigate within calendar
694
- - **Escape**: Close calendar popup
695
- - **Enter**: Select focused date
696
-
697
- ### Screen Reader Support
476
+ 3. **Use `inputReadOnly` on mobile**: On touch devices, keyboard input for date ranges is error-prone. Use `inputReadOnly` to force calendar selection.
698
477
 
699
478
  ```tsx
700
- // Range selection is announced
701
- // First click: "Start date: April 1, 2024"
702
- // Second click: "End date: April 15, 2024"
703
-
704
- // Navigation buttons are descriptive
705
- <button aria-label="Previous Month">←</button>
706
- <button aria-label="Next Month">→</button>
479
+ // Better mobile experience
480
+ <DateRangePicker inputReadOnly />
707
481
  ```
708
482
 
709
- ### Focus Management
710
-
711
- - Focus moves to calendar when opened
712
- - Focus returns to input when calendar closes
713
- - Clear visual focus indicators on all interactive elements
714
- - Range selection provides visual feedback between start and end dates
715
-
716
- ## Best Practices
717
-
718
- ### ✅ Do
719
-
720
- 1. **Validate date ranges**: Ensure start date is before end date
483
+ 4. **Set reasonable date boundaries**: Use `minDate`, `maxDate`, `disablePast`, or `disableFuture` to prevent users from selecting invalid ranges.
721
484
 
722
485
  ```tsx
723
- // Good: Validate the range
724
- const validateRange = (value) => {
725
- const [start, end] = value.split(' - ');
726
- if (new Date(start) > new Date(end)) {
727
- return 'Start date must be before end date';
728
- }
729
- return '';
730
- };
486
+ // Restrict to a specific period
487
+ <DateRangePicker minDate="2024-01-01" maxDate="2024-12-31" />
731
488
  ```
732
489
 
733
- 2. **Show range duration**: Help users understand the selected period
490
+ 5. **Display contextual helper text**: Show the duration or additional context in the `helperText` to help users understand their selection.
734
491
 
735
492
  ```tsx
736
- // ✅ Good: Display duration
737
493
  const getDuration = (value) => {
738
- if (!value) return '';
739
- const [start, end] = value.split(' - ').map(d => new Date(d));
494
+ if (!value) return 'Select dates';
495
+ const [start, end] = value.split(' - ').map((d) => new Date(d));
740
496
  const days = Math.ceil((end - start) / (1000 * 60 * 60 * 24));
741
- return `${days} day${days !== 1 ? 's' : ''}`;
497
+ return `${days} day${days !== 1 ? 's' : ''} selected`;
742
498
  };
743
499
 
744
- <DateRangePicker helperText={getDuration(dateRange) || 'Select dates'} />
745
- ```
746
-
747
- 3. **Set reasonable limits**: Prevent excessively long ranges
748
-
749
- ```tsx
750
- // ✅ Good: Limit range span
751
- const MAX_DAYS = 90;
752
- const validateMaxRange = (value) => {
753
- const [start, end] = value.split(' - ').map(d => new Date(d));
754
- const days = (end - start) / (1000 * 60 * 60 * 24);
755
- return days <= MAX_DAYS;
756
- };
500
+ <DateRangePicker helperText={getDuration(dateRange)} />
757
501
  ```
758
502
 
759
- 4. **Use consistent formats**: Match your application's locale
760
-
761
- ```tsx
762
- // ✅ Good: Consistent with app locale
763
- <DateRangePicker
764
- format="YYYY-MM-DD" // API format
765
- displayFormat="MM/DD/YYYY" // US locale display
766
- />
767
- ```
768
-
769
- ### ❌ Don't
770
-
771
- 1. **Don't allow invalid ranges**: Always validate start \< end
772
-
773
- ```tsx
774
- // ❌ Bad: No validation
775
- <DateRangePicker onChange={(e) => setDateRange(e.target.value)} />
776
-
777
- // ✅ Good: Validate range
778
- <DateRangePicker
779
- onChange={(e) => {
780
- if (isValidRange(e.target.value)) {
781
- setDateRange(e.target.value);
782
- }
783
- }}
784
- />
785
- ```
786
-
787
- 2. **Don't use inconsistent value formats**: Match value to format prop
788
-
789
- ```tsx
790
- // ❌ Bad: Mismatched formats
791
- <DateRangePicker
792
- format="YYYY/MM/DD"
793
- value="04/01/2024 - 04/15/2024" // Wrong!
794
- />
795
-
796
- // ✅ Good: Matching formats
797
- <DateRangePicker
798
- format="YYYY/MM/DD"
799
- value="2024/04/01 - 2024/04/15"
800
- />
801
- ```
802
-
803
- 3. **Don't forget mobile users**: Use `inputReadOnly` for better mobile experience
804
-
805
- 4. **Don't hide important context**: Show duration or business days when relevant
806
-
807
- ## Performance Considerations
808
-
809
- ### Memoize Handlers
810
-
811
- When using DateRangePicker in complex forms:
812
-
813
- ```tsx
814
- const handleChange = useCallback((e) => {
815
- setDateRange(e.target.value);
816
- }, []);
817
-
818
- <DateRangePicker value={dateRange} onChange={handleChange} />
819
- ```
820
-
821
- ### Parse Values Efficiently
822
-
823
- When processing date range values:
824
-
825
- ```tsx
826
- // Memoize parsed values
827
- const { startDate, endDate, duration } = useMemo(() => {
828
- if (!dateRange) return { startDate: null, endDate: null, duration: 0 };
829
-
830
- const [start, end] = dateRange.split(' - ');
831
- const startDate = new Date(start);
832
- const endDate = new Date(end);
833
- const duration = Math.ceil((endDate - startDate) / (1000 * 60 * 60 * 24));
834
-
835
- return { startDate, endDate, duration };
836
- }, [dateRange]);
837
- ```
838
-
839
- ### Format Conversion for APIs
840
-
841
- When working with APIs that expect different formats:
842
-
843
- ```tsx
844
- function DateRangeField({ value, onChange, apiFormat = 'YYYY-MM-DD' }) {
845
- const displayFormat = 'YYYY/MM/DD';
846
-
847
- const convertToDisplay = (apiValue) => {
848
- if (!apiValue) return '';
849
- const [start, end] = apiValue.split(' - ');
850
- return `${convertFormat(start, apiFormat, displayFormat)} - ${convertFormat(end, apiFormat, displayFormat)}`;
851
- };
852
-
853
- const convertToApi = (displayValue) => {
854
- if (!displayValue) return '';
855
- const [start, end] = displayValue.split(' - ');
856
- return `${convertFormat(start, displayFormat, apiFormat)} - ${convertFormat(end, displayFormat, apiFormat)}`;
857
- };
858
-
859
- return (
860
- <DateRangePicker
861
- value={convertToDisplay(value)}
862
- onChange={(e) => onChange(convertToApi(e.target.value))}
863
- format={displayFormat}
864
- />
865
- );
866
- }
867
- ```
503
+ ## Accessibility
868
504
 
869
- DateRangePicker is essential for scenarios requiring start and end date selection. Pay attention to date format consistency, validate ranges properly, and provide clear feedback to users about the selected period.
505
+ - The input has `role="textbox"` and the calendar button has `aria-label="Toggle Calendar"` for screen reader identification.
506
+ - **Keyboard navigation** is fully supported: use **Tab** to move between input and calendar button, **Enter/Space** to open the calendar, **Arrow Keys** to navigate dates, and **Escape** to close the popup.
507
+ - When a `label` prop is provided, the input is automatically associated with the label for assistive technologies. Always provide a `label` for form contexts.
508
+ - The calendar popup uses `role="tooltip"` with proper ARIA labeling. Date buttons announce the full date to screen readers, and range selection provides visual feedback between start and end dates.