@ceed/ads 1.29.1 → 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.
- package/dist/components/CurrencyInput/CurrencyInput.d.ts +1 -1
- package/dist/components/CurrencyInput/hooks/use-currency-setting.d.ts +2 -2
- package/dist/components/ProfileMenu/ProfileMenu.d.ts +1 -1
- package/dist/components/SearchBar/SearchBar.d.ts +21 -0
- package/dist/components/SearchBar/index.d.ts +3 -0
- package/dist/components/data-display/Badge.md +39 -71
- package/dist/components/data-display/DataTable.md +1 -1
- package/dist/components/data-display/InfoSign.md +98 -74
- package/dist/components/data-display/Typography.md +97 -363
- package/dist/components/feedback/Dialog.md +62 -76
- package/dist/components/feedback/Modal.md +44 -259
- package/dist/components/feedback/llms.txt +0 -2
- package/dist/components/index.d.ts +2 -0
- package/dist/components/inputs/Autocomplete.md +107 -356
- package/dist/components/inputs/ButtonGroup.md +106 -115
- package/dist/components/inputs/Calendar.md +459 -98
- package/dist/components/inputs/CurrencyInput.md +5 -183
- package/dist/components/inputs/DatePicker.md +431 -108
- package/dist/components/inputs/DateRangePicker.md +492 -131
- package/dist/components/inputs/FilterMenu.md +19 -169
- package/dist/components/inputs/FilterableCheckboxGroup.md +23 -123
- package/dist/components/inputs/IconButton.md +88 -137
- package/dist/components/inputs/Input.md +0 -5
- package/dist/components/inputs/MonthPicker.md +422 -95
- package/dist/components/inputs/MonthRangePicker.md +466 -89
- package/dist/components/inputs/PercentageInput.md +16 -185
- package/dist/components/inputs/RadioButton.md +35 -163
- package/dist/components/inputs/RadioTileGroup.md +61 -150
- package/dist/components/inputs/SearchBar.md +44 -0
- package/dist/components/inputs/Select.md +326 -222
- package/dist/components/inputs/Switch.md +376 -136
- package/dist/components/inputs/Textarea.md +10 -213
- package/dist/components/inputs/Uploader/Uploader.md +66 -145
- package/dist/components/inputs/llms.txt +1 -3
- package/dist/components/navigation/Breadcrumbs.md +322 -80
- package/dist/components/navigation/Dropdown.md +221 -92
- package/dist/components/navigation/IconMenuButton.md +502 -40
- package/dist/components/navigation/InsetDrawer.md +738 -68
- package/dist/components/navigation/Link.md +298 -39
- package/dist/components/navigation/Menu.md +285 -92
- package/dist/components/navigation/MenuButton.md +448 -55
- package/dist/components/navigation/Pagination.md +338 -47
- package/dist/components/navigation/ProfileMenu.md +268 -45
- package/dist/components/navigation/Stepper.md +28 -160
- package/dist/components/navigation/Tabs.md +316 -57
- package/dist/components/surfaces/Sheet.md +334 -151
- package/dist/index.browser.js +15 -13
- package/dist/index.browser.js.map +4 -4
- package/dist/index.cjs +289 -288
- package/dist/index.d.ts +1 -1
- package/dist/index.js +426 -369
- package/dist/llms.txt +1 -8
- package/framer/index.js +1 -1
- package/package.json +16 -15
- package/dist/chunks/rehype-accent-FZRUD7VI.js +0 -39
- package/dist/components/feedback/CircularProgress.md +0 -257
- package/dist/components/feedback/Skeleton.md +0 -280
- package/dist/components/inputs/FormControl.md +0 -361
- package/dist/components/inputs/RadioList.md +0 -241
- package/dist/components/inputs/Slider.md +0 -334
- package/dist/guides/ThemeProvider.md +0 -116
- 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
|
|
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
|
-
> **
|
|
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
|
-
>
|
|
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
|
-
>
|
|
43
|
-
>
|
|
44
|
-
>
|
|
45
|
-
>
|
|
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
|
-
##
|
|
58
|
+
## Examples
|
|
69
59
|
|
|
70
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
224
|
+
### With Formats
|
|
220
225
|
|
|
221
|
-
|
|
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
|
|
257
|
+
### With Display Formats
|
|
255
258
|
|
|
256
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
<
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
-
###
|
|
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
|
-
<
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
//
|
|
460
|
-
|
|
641
|
+
// format: Affects the value in onChange
|
|
642
|
+
// displayFormat: Affects what users see in the input
|
|
461
643
|
|
|
462
|
-
|
|
463
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
664
|
+
// Uncontrolled - component manages state
|
|
470
665
|
<DateRangePicker
|
|
471
|
-
|
|
472
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
480
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
487
|
-
|
|
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
|
-
|
|
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 '
|
|
495
|
-
const [start, end] = value.split(' - ').map(
|
|
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' : ''}
|
|
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
|
-
|
|
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
|
-
|
|
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.
|