@ceed/ads 1.29.0 → 1.30.0-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 (64) hide show
  1. package/dist/components/CurrencyInput/CurrencyInput.d.ts +1 -1
  2. package/dist/components/CurrencyInput/hooks/use-currency-setting.d.ts +2 -2
  3. package/dist/components/DataTable/hooks.d.ts +2 -1
  4. package/dist/components/DataTable/utils.d.ts +1 -0
  5. package/dist/components/ProfileMenu/ProfileMenu.d.ts +1 -1
  6. package/dist/components/SearchBar/SearchBar.d.ts +21 -0
  7. package/dist/components/SearchBar/index.d.ts +3 -0
  8. package/dist/components/data-display/Badge.md +39 -71
  9. package/dist/components/data-display/DataTable.md +1 -1
  10. package/dist/components/data-display/InfoSign.md +98 -74
  11. package/dist/components/data-display/Typography.md +97 -363
  12. package/dist/components/feedback/Dialog.md +62 -76
  13. package/dist/components/feedback/Modal.md +44 -259
  14. package/dist/components/feedback/llms.txt +0 -2
  15. package/dist/components/index.d.ts +2 -0
  16. package/dist/components/inputs/Autocomplete.md +107 -356
  17. package/dist/components/inputs/ButtonGroup.md +106 -115
  18. package/dist/components/inputs/Calendar.md +459 -98
  19. package/dist/components/inputs/CurrencyInput.md +5 -183
  20. package/dist/components/inputs/DatePicker.md +431 -108
  21. package/dist/components/inputs/DateRangePicker.md +492 -131
  22. package/dist/components/inputs/FilterMenu.md +19 -169
  23. package/dist/components/inputs/FilterableCheckboxGroup.md +23 -123
  24. package/dist/components/inputs/IconButton.md +88 -137
  25. package/dist/components/inputs/Input.md +0 -5
  26. package/dist/components/inputs/MonthPicker.md +422 -95
  27. package/dist/components/inputs/MonthRangePicker.md +466 -89
  28. package/dist/components/inputs/PercentageInput.md +16 -185
  29. package/dist/components/inputs/RadioButton.md +35 -163
  30. package/dist/components/inputs/RadioTileGroup.md +61 -150
  31. package/dist/components/inputs/SearchBar.md +44 -0
  32. package/dist/components/inputs/Select.md +326 -222
  33. package/dist/components/inputs/Switch.md +376 -136
  34. package/dist/components/inputs/Textarea.md +10 -213
  35. package/dist/components/inputs/Uploader/Uploader.md +66 -145
  36. package/dist/components/inputs/llms.txt +1 -3
  37. package/dist/components/navigation/Breadcrumbs.md +322 -80
  38. package/dist/components/navigation/Dropdown.md +221 -92
  39. package/dist/components/navigation/IconMenuButton.md +502 -40
  40. package/dist/components/navigation/InsetDrawer.md +738 -68
  41. package/dist/components/navigation/Link.md +298 -39
  42. package/dist/components/navigation/Menu.md +285 -92
  43. package/dist/components/navigation/MenuButton.md +448 -55
  44. package/dist/components/navigation/Pagination.md +338 -47
  45. package/dist/components/navigation/ProfileMenu.md +268 -45
  46. package/dist/components/navigation/Stepper.md +28 -160
  47. package/dist/components/navigation/Tabs.md +316 -57
  48. package/dist/components/surfaces/Sheet.md +334 -151
  49. package/dist/index.browser.js +15 -13
  50. package/dist/index.browser.js.map +4 -4
  51. package/dist/index.cjs +313 -291
  52. package/dist/index.d.ts +1 -1
  53. package/dist/index.js +450 -372
  54. package/dist/llms.txt +1 -8
  55. package/framer/index.js +1 -1
  56. package/package.json +16 -15
  57. package/dist/chunks/rehype-accent-FZRUD7VI.js +0 -39
  58. package/dist/components/feedback/CircularProgress.md +0 -257
  59. package/dist/components/feedback/Skeleton.md +0 -280
  60. package/dist/components/inputs/FormControl.md +0 -361
  61. package/dist/components/inputs/RadioList.md +0 -241
  62. package/dist/components/inputs/Slider.md +0 -334
  63. package/dist/guides/ThemeProvider.md +0 -116
  64. package/dist/guides/llms.txt +0 -9
@@ -2,9 +2,7 @@
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 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.
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.
8
6
 
9
7
  ```tsx
10
8
  <DateRangePicker onChange={onChange} />
@@ -30,22 +28,14 @@ DateRangePicker is ideal for booking systems, reporting filters, scheduling, and
30
28
  | hideClearButton | — | — |
31
29
  | size | — | — |
32
30
 
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.
31
+ > ⚠️ **Usage Warning** ⚠️
37
32
  >
38
- > ```tsx
39
- > // Recommended: use built-in props
40
- > <DateRangePicker label="Period" helperText="Select start and end dates" />
33
+ > DateRangePicker involves complex date range handling:
41
34
  >
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
- > ```
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
49
39
 
50
40
  ## Usage
51
41
 
@@ -65,9 +55,19 @@ function DateRangeForm() {
65
55
  }
66
56
  ```
67
57
 
68
- ## Sizes
58
+ ## Examples
69
59
 
70
- DateRangePicker supports three sizes (`sm`, `md`, `lg`) to fit different layouts and density requirements.
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.
71
71
 
72
72
  ```tsx
73
73
  <Stack gap={2}>
@@ -77,11 +77,20 @@ DateRangePicker supports three sizes (`sm`, `md`, `lg`) to fit different layouts
77
77
  </Stack>
78
78
  ```
79
79
 
80
- ## Form Features
80
+ ### Disabled
81
+
82
+ Prevent user interaction when disabled.
83
+
84
+ ```tsx
85
+ <DateRangePicker
86
+ onChange={onChange}
87
+ disabled
88
+ />
89
+ ```
81
90
 
82
- ### Label
91
+ ### With Label
83
92
 
84
- Add a label above the date range picker using the `label` prop.
93
+ Add a label above the date range picker.
85
94
 
86
95
  ```tsx
87
96
  <DateRangePicker
@@ -90,9 +99,9 @@ Add a label above the date range picker using the `label` prop.
90
99
  />
91
100
  ```
92
101
 
93
- ### Helper Text
102
+ ### With Helper Text
94
103
 
95
- Provide additional guidance below the input using the `helperText` prop.
104
+ Provide additional guidance below the input.
96
105
 
97
106
  ```tsx
98
107
  <DateRangePicker
@@ -104,7 +113,7 @@ Provide additional guidance below the input using the `helperText` prop.
104
113
 
105
114
  ### Error State
106
115
 
107
- Display validation errors by combining the `error` and `helperText` props.
116
+ Show validation errors with error styling.
108
117
 
109
118
  ```tsx
110
119
  <DateRangePicker
@@ -117,7 +126,7 @@ Display validation errors by combining the `error` and `helperText` props.
117
126
 
118
127
  ### Required Field
119
128
 
120
- Mark the field as required in forms. This displays a required indicator next to the label.
129
+ Mark the field as required in forms.
121
130
 
122
131
  ```tsx
123
132
  <DateRangePicker
@@ -128,11 +137,9 @@ Mark the field as required in forms. This displays a required indicator next to
128
137
  />
129
138
  ```
130
139
 
131
- ## Date Restrictions
132
-
133
140
  ### Minimum Date
134
141
 
135
- Restrict selection to dates on or after a specified minimum date using the `minDate` prop.
142
+ Restrict selection to dates on or after a minimum date.
136
143
 
137
144
  ```tsx
138
145
  <DateRangePicker
@@ -143,7 +150,7 @@ Restrict selection to dates on or after a specified minimum date using the `minD
143
150
 
144
151
  ### Maximum Date
145
152
 
146
- Restrict selection to dates on or before a specified maximum date using the `maxDate` prop.
153
+ Restrict selection to dates on or before a maximum date.
147
154
 
148
155
  ```tsx
149
156
  <DateRangePicker
@@ -154,7 +161,7 @@ Restrict selection to dates on or before a specified maximum date using the `max
154
161
 
155
162
  ### Disable Future Dates
156
163
 
157
- Prevent selection of dates in the future using `disableFuture`. Useful for report filters and historical data queries.
164
+ Prevent selection of dates in the future.
158
165
 
159
166
  ```tsx
160
167
  <DateRangePicker
@@ -165,7 +172,7 @@ Prevent selection of dates in the future using `disableFuture`. Useful for repor
165
172
 
166
173
  ### Disable Past Dates
167
174
 
168
- Prevent selection of dates in the past using `disablePast`. Useful for booking and scheduling forms.
175
+ Prevent selection of dates in the past.
169
176
 
170
177
  ```tsx
171
178
  <DateRangePicker
@@ -174,11 +181,9 @@ Prevent selection of dates in the past using `disablePast`. Useful for booking a
174
181
  />
175
182
  ```
176
183
 
177
- ## Controlled vs Uncontrolled
178
-
179
184
  ### Controlled
180
185
 
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.
186
+ Parent component manages the date range state.
182
187
 
183
188
  ```tsx
184
189
  <Stack gap={2}>
@@ -205,7 +210,7 @@ In controlled mode, the parent component manages the date range state via `value
205
210
 
206
211
  ### Uncontrolled
207
212
 
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.
213
+ Component manages its own state internally.
209
214
 
210
215
  ```tsx
211
216
  <DateRangePicker
@@ -216,11 +221,9 @@ In uncontrolled mode, the component manages its own state internally. Set an ini
216
221
  />
217
222
  ```
218
223
 
219
- ## Formatting
224
+ ### With Formats
220
225
 
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.
226
+ Different value formats for the `onChange` event.
224
227
 
225
228
  ```tsx
226
229
  <Stack gap={2}>
@@ -251,9 +254,9 @@ The `format` prop controls the format of the value returned in `onChange`. The i
251
254
  </Stack>
252
255
  ```
253
256
 
254
- ### Display Format (`displayFormat`)
257
+ ### With Display Formats
255
258
 
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.
259
+ Different display formats shown in the input field.
257
260
 
258
261
  ```tsx
259
262
  <Stack gap={2}>
@@ -284,38 +287,9 @@ The `displayFormat` prop controls what the user sees in the input field. This is
284
287
  </Stack>
285
288
  ```
286
289
 
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
-
316
290
  ### Input Read Only
317
291
 
318
- Allow calendar selection only while preventing direct typing. Useful on mobile devices where keyboard input is impractical.
292
+ Allow calendar selection only, prevent typing.
319
293
 
320
294
  ```tsx
321
295
  <DateRangePicker
@@ -327,7 +301,7 @@ Allow calendar selection only while preventing direct typing. Useful on mobile d
327
301
 
328
302
  ### Read Only
329
303
 
330
- Fully read-only state where neither typing nor calendar selection is available. Use this to display a date range value without allowing changes.
304
+ Fully read-only state with no interaction.
331
305
 
332
306
  ```tsx
333
307
  <DateRangePicker
@@ -337,11 +311,9 @@ Fully read-only state where neither typing nor calendar selection is available.
337
311
  />
338
312
  ```
339
313
 
340
- ## Additional Options
341
-
342
314
  ### Hide Clear Button
343
315
 
344
- Remove the clear button from the calendar popup using `hideClearButton`.
316
+ Remove the clear button from the calendar popup.
345
317
 
346
318
  ```tsx
347
319
  <DateRangePicker
@@ -353,7 +325,7 @@ Remove the clear button from the calendar popup using `hideClearButton`.
353
325
 
354
326
  ### With Reset Button
355
327
 
356
- Example of integrating an external reset button to clear the selected date range programmatically.
328
+ Example with an external reset button.
357
329
 
358
330
  ```tsx
359
331
  <div style={{
@@ -367,6 +339,25 @@ Example of integrating an external reset button to clear the selected date range
367
339
  </div>
368
340
  ```
369
341
 
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
+
370
361
  ## Common Use Cases
371
362
 
372
363
  ### Booking Form
@@ -376,29 +367,38 @@ function BookingForm() {
376
367
  const [dates, setDates] = useState('');
377
368
  const [error, setError] = useState('');
378
369
 
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
+
379
381
  const handleChange = (e) => {
380
382
  const value = e.target.value;
381
383
  setDates(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
- }
384
+ setError(validateRange(value));
390
385
  };
391
386
 
392
387
  return (
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
- />
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>
402
402
  );
403
403
  }
404
404
  ```
@@ -421,7 +421,6 @@ 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"
425
424
  disableFuture
426
425
  helperText="Select date range for the report"
427
426
  />
@@ -431,78 +430,440 @@ function ReportFilters({ onFilter }) {
431
430
  }
432
431
  ```
433
432
 
434
- ### Separate API and Display Formats
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
435
554
 
436
555
  ```tsx
437
556
  function DataExport() {
438
557
  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
+ };
439
576
 
440
577
  return (
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
- />
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>
450
595
  );
451
596
  }
452
597
  ```
453
598
 
454
- ## Best Practices
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";
455
631
 
456
- 1. **Match `value` to `format`**: The `value` and `defaultValue` strings must always match the `format` prop. Mismatched formats will cause unexpected behavior.
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
+ ```
637
+
638
+ ### Format vs DisplayFormat
457
639
 
458
640
  ```tsx
459
- // Correct: value matches format
460
- <DateRangePicker format="YYYY-MM-DD" value="2024-04-01 - 2024-04-15" />
641
+ // format: Affects the value in onChange
642
+ // displayFormat: Affects what users see in the input
461
643
 
462
- // Incorrect: value does not match format
463
- <DateRangePicker format="YYYY/MM/DD" value="04/01/2024 - 04/15/2024" />
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
+ />
464
651
  ```
465
652
 
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`).
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
467
662
 
468
663
  ```tsx
469
- // Store ISO format, display locale format
664
+ // Uncontrolled - component manages state
470
665
  <DateRangePicker
471
- format="YYYY-MM-DD"
472
- displayFormat="MM/DD/YYYY"
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)}
473
675
  />
474
676
  ```
475
677
 
476
- 3. **Use `inputReadOnly` on mobile**: On touch devices, keyboard input for date ranges is error-prone. Use `inputReadOnly` to force calendar selection.
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
477
698
 
478
699
  ```tsx
479
- // Better mobile experience
480
- <DateRangePicker inputReadOnly />
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>
481
707
  ```
482
708
 
483
- 4. **Set reasonable date boundaries**: Use `minDate`, `maxDate`, `disablePast`, or `disableFuture` to prevent users from selecting invalid ranges.
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
484
721
 
485
722
  ```tsx
486
- // Restrict to a specific period
487
- <DateRangePicker minDate="2024-01-01" maxDate="2024-12-31" />
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
+ };
488
731
  ```
489
732
 
490
- 5. **Display contextual helper text**: Show the duration or additional context in the `helperText` to help users understand their selection.
733
+ 2. **Show range duration**: Help users understand the selected period
491
734
 
492
735
  ```tsx
736
+ // ✅ Good: Display duration
493
737
  const getDuration = (value) => {
494
- if (!value) return 'Select dates';
495
- const [start, end] = value.split(' - ').map((d) => new Date(d));
738
+ if (!value) return '';
739
+ const [start, end] = value.split(' - ').map(d => new Date(d));
496
740
  const days = Math.ceil((end - start) / (1000 * 60 * 60 * 24));
497
- return `${days} day${days !== 1 ? 's' : ''} selected`;
741
+ return `${days} day${days !== 1 ? 's' : ''}`;
498
742
  };
499
743
 
500
- <DateRangePicker helperText={getDuration(dateRange)} />
744
+ <DateRangePicker helperText={getDuration(dateRange) || 'Select dates'} />
501
745
  ```
502
746
 
503
- ## Accessibility
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
+ };
757
+ ```
758
+
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
+ ```
504
868
 
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.
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.