@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.
- package/dist/chunks/rehype-accent-FZRUD7VI.js +39 -0
- package/dist/components/CurrencyInput/CurrencyInput.d.ts +1 -1
- package/dist/components/CurrencyInput/hooks/use-currency-setting.d.ts +2 -2
- package/dist/components/DataTable/components.d.ts +2 -1
- package/dist/components/DataTable/hooks.d.ts +1 -1
- package/dist/components/DataTable/styled.d.ts +3 -1
- package/dist/components/DataTable/types.d.ts +11 -0
- package/dist/components/DataTable/utils.d.ts +2 -2
- package/dist/components/RadioTileGroup/RadioTileGroup.d.ts +56 -0
- package/dist/components/RadioTileGroup/index.d.ts +3 -0
- package/dist/components/data-display/DataTable.md +177 -1
- package/dist/components/data-display/InfoSign.md +74 -91
- package/dist/components/data-display/Typography.md +411 -94
- package/dist/components/feedback/CircularProgress.md +257 -0
- package/dist/components/feedback/Dialog.md +76 -62
- package/dist/components/feedback/Modal.md +430 -138
- package/dist/components/feedback/Skeleton.md +280 -0
- package/dist/components/feedback/llms.txt +2 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/inputs/Autocomplete.md +356 -107
- package/dist/components/inputs/ButtonGroup.md +115 -104
- package/dist/components/inputs/CurrencyInput.md +183 -5
- package/dist/components/inputs/DatePicker.md +108 -431
- package/dist/components/inputs/DateRangePicker.md +131 -492
- package/dist/components/inputs/FilterableCheckboxGroup.md +145 -19
- package/dist/components/inputs/FormControl.md +361 -0
- package/dist/components/inputs/IconButton.md +137 -88
- package/dist/components/inputs/Input.md +204 -73
- package/dist/components/inputs/MonthPicker.md +95 -422
- package/dist/components/inputs/MonthRangePicker.md +89 -466
- package/dist/components/inputs/PercentageInput.md +185 -16
- package/dist/components/inputs/RadioButton.md +163 -35
- package/dist/components/inputs/RadioList.md +241 -0
- package/dist/components/inputs/RadioTileGroup.md +507 -0
- package/dist/components/inputs/Select.md +222 -326
- package/dist/components/inputs/Slider.md +334 -0
- package/dist/components/inputs/Switch.md +143 -376
- package/dist/components/inputs/Textarea.md +213 -10
- package/dist/components/inputs/Uploader/Uploader.md +145 -66
- package/dist/components/inputs/llms.txt +4 -0
- package/dist/components/navigation/Breadcrumbs.md +57 -308
- package/dist/components/navigation/Drawer.md +180 -0
- package/dist/components/navigation/Dropdown.md +98 -215
- package/dist/components/navigation/IconMenuButton.md +40 -502
- package/dist/components/navigation/InsetDrawer.md +281 -650
- package/dist/components/navigation/Link.md +31 -348
- package/dist/components/navigation/Menu.md +92 -285
- package/dist/components/navigation/MenuButton.md +55 -448
- package/dist/components/navigation/Pagination.md +47 -338
- package/dist/components/navigation/Stepper.md +160 -28
- package/dist/components/navigation/Tabs.md +57 -316
- package/dist/components/surfaces/Accordions.md +49 -804
- package/dist/components/surfaces/Card.md +97 -157
- package/dist/components/surfaces/Divider.md +83 -234
- package/dist/components/surfaces/Sheet.md +153 -328
- package/dist/guides/ThemeProvider.md +89 -0
- package/dist/guides/llms.txt +9 -0
- package/dist/index.browser.js +224 -0
- package/dist/index.browser.js.map +7 -0
- package/dist/index.cjs +726 -425
- package/dist/index.d.ts +1 -1
- package/dist/index.js +641 -396
- package/dist/llms.txt +9 -0
- package/framer/index.js +1 -163
- 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
|
|
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
|
-
>
|
|
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
|
-
>
|
|
38
|
+
> ```tsx
|
|
39
|
+
> // Recommended: use built-in props
|
|
40
|
+
> <DateRangePicker label="Period" helperText="Select start and end dates" />
|
|
34
41
|
>
|
|
35
|
-
>
|
|
36
|
-
>
|
|
37
|
-
> -
|
|
38
|
-
>
|
|
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
|
-
##
|
|
68
|
+
## Sizes
|
|
59
69
|
|
|
60
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
219
|
+
## Formatting
|
|
225
220
|
|
|
226
|
-
|
|
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
|
-
###
|
|
254
|
+
### Display Format (`displayFormat`)
|
|
258
255
|
|
|
259
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
<
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
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
|
-
###
|
|
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
|
-
<
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
//
|
|
642
|
-
|
|
459
|
+
// Correct: value matches format
|
|
460
|
+
<DateRangePicker format="YYYY-MM-DD" value="2024-04-01 - 2024-04-15" />
|
|
643
461
|
|
|
644
|
-
|
|
645
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
469
|
+
// Store ISO format, display locale format
|
|
665
470
|
<DateRangePicker
|
|
666
|
-
|
|
667
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
701
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
724
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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.
|