@ceed/ads 1.23.3 → 1.23.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/data-display/Badge.md +71 -39
- package/dist/components/data-display/InfoSign.md +74 -98
- package/dist/components/data-display/Typography.md +310 -61
- package/dist/components/feedback/CircularProgress.md +257 -0
- package/dist/components/feedback/Skeleton.md +280 -0
- package/dist/components/feedback/llms.txt +2 -0
- package/dist/components/inputs/ButtonGroup.md +115 -106
- package/dist/components/inputs/Calendar.md +98 -459
- package/dist/components/inputs/CurrencyInput.md +181 -8
- package/dist/components/inputs/DatePicker.md +108 -436
- package/dist/components/inputs/DateRangePicker.md +130 -496
- package/dist/components/inputs/FilterMenu.md +169 -19
- package/dist/components/inputs/FilterableCheckboxGroup.md +119 -24
- package/dist/components/inputs/FormControl.md +361 -0
- package/dist/components/inputs/IconButton.md +137 -88
- package/dist/components/inputs/MonthPicker.md +95 -427
- package/dist/components/inputs/MonthRangePicker.md +89 -471
- package/dist/components/inputs/PercentageInput.md +183 -19
- package/dist/components/inputs/RadioButton.md +163 -35
- package/dist/components/inputs/RadioList.md +241 -0
- package/dist/components/inputs/RadioTileGroup.md +146 -62
- package/dist/components/inputs/Select.md +219 -328
- package/dist/components/inputs/Slider.md +334 -0
- package/dist/components/inputs/Switch.md +136 -376
- package/dist/components/inputs/Textarea.md +209 -11
- package/dist/components/inputs/Uploader/Uploader.md +145 -66
- package/dist/components/inputs/llms.txt +3 -0
- package/dist/components/navigation/Breadcrumbs.md +80 -322
- package/dist/components/navigation/Dropdown.md +92 -221
- package/dist/components/navigation/IconMenuButton.md +40 -502
- package/dist/components/navigation/InsetDrawer.md +68 -738
- package/dist/components/navigation/Link.md +39 -298
- 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/ProfileMenu.md +45 -268
- package/dist/components/navigation/Stepper.md +160 -28
- package/dist/components/navigation/Tabs.md +57 -316
- package/dist/components/surfaces/Sheet.md +150 -333
- package/dist/guides/ThemeProvider.md +116 -0
- package/dist/guides/llms.txt +9 -0
- package/dist/llms.txt +8 -0
- 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
|
|
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
|
-
>
|
|
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
|
-
##
|
|
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
|
|
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
|
-
|
|
80
|
+
## Form Features
|
|
86
81
|
|
|
87
|
-
|
|
82
|
+
### Label
|
|
88
83
|
|
|
89
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
219
|
+
## Formatting
|
|
220
|
+
|
|
221
|
+
### Value Format (`format`)
|
|
230
222
|
|
|
231
|
-
|
|
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
|
-
###
|
|
254
|
+
### Display Format (`displayFormat`)
|
|
263
255
|
|
|
264
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
<
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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
|
-
###
|
|
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
|
-
<
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
//
|
|
647
|
-
|
|
459
|
+
// Correct: value matches format
|
|
460
|
+
<DateRangePicker format="YYYY-MM-DD" value="2024-04-01 - 2024-04-15" />
|
|
648
461
|
|
|
649
|
-
|
|
650
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
469
|
+
// Store ISO format, display locale format
|
|
670
470
|
<DateRangePicker
|
|
671
|
-
|
|
672
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
706
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
729
|
-
|
|
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
|
-
|
|
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)
|
|
500
|
+
<DateRangePicker helperText={getDuration(dateRange)} />
|
|
750
501
|
```
|
|
751
502
|
|
|
752
|
-
|
|
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
|
-
|
|
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.
|