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