@ceed/ads 1.20.0 → 1.20.1-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/ProfileMenu/ProfileMenu.d.ts +1 -1
- package/dist/components/data-display/Markdown.md +832 -0
- package/dist/components/feedback/Dialog.md +605 -3
- package/dist/components/feedback/Modal.md +656 -24
- package/dist/components/feedback/llms.txt +1 -1
- package/dist/components/inputs/Autocomplete.md +734 -2
- package/dist/components/inputs/Calendar.md +655 -1
- package/dist/components/inputs/DatePicker.md +699 -3
- package/dist/components/inputs/DateRangePicker.md +815 -1
- package/dist/components/inputs/MonthPicker.md +626 -4
- package/dist/components/inputs/MonthRangePicker.md +682 -4
- package/dist/components/inputs/Select.md +600 -0
- package/dist/components/layout/Container.md +507 -0
- package/dist/components/navigation/Breadcrumbs.md +582 -0
- package/dist/components/navigation/IconMenuButton.md +693 -0
- package/dist/components/navigation/InsetDrawer.md +1150 -3
- package/dist/components/navigation/Link.md +526 -0
- package/dist/components/navigation/MenuButton.md +632 -0
- package/dist/components/navigation/NavigationGroup.md +401 -1
- package/dist/components/navigation/NavigationItem.md +311 -0
- package/dist/components/navigation/Navigator.md +373 -0
- package/dist/components/navigation/Pagination.md +521 -0
- package/dist/components/navigation/ProfileMenu.md +605 -0
- package/dist/components/navigation/Tabs.md +609 -7
- package/dist/components/surfaces/Accordions.md +947 -3
- package/dist/index.cjs +3 -1
- package/dist/index.js +3 -1
- package/dist/llms.txt +1 -1
- package/framer/index.js +1 -1
- package/package.json +3 -2
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
## Introduction
|
|
4
4
|
|
|
5
|
+
DatePicker is a form input component that allows users to select a single 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. DatePicker is essential for forms that require date input, such as booking systems, scheduling interfaces, and data filtering.
|
|
6
|
+
|
|
5
7
|
```tsx
|
|
6
8
|
<DatePicker onChange={fn()} />
|
|
7
9
|
```
|
|
@@ -26,8 +28,47 @@
|
|
|
26
28
|
| disableFuture | — | — |
|
|
27
29
|
| disablePast | — | — |
|
|
28
30
|
|
|
31
|
+
> ⚠️ **Usage Warning** ⚠️
|
|
32
|
+
>
|
|
33
|
+
> DatePicker involves complex date handling logic:
|
|
34
|
+
>
|
|
35
|
+
> - **Format vs DisplayFormat**: `format` affects the value in `onChange`, while `displayFormat` affects what users see
|
|
36
|
+
> - **Value format consistency**: The `value` and `defaultValue` props must match the `format` prop
|
|
37
|
+
> - **Date validation**: Invalid date strings can cause unexpected behavior
|
|
38
|
+
> - **Timezone considerations**: Be aware of timezone issues when working with dates
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import { DatePicker } from '@ceed/ads';
|
|
44
|
+
|
|
45
|
+
function DateForm() {
|
|
46
|
+
const [date, setDate] = useState('');
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<DatePicker
|
|
50
|
+
label="Select Date"
|
|
51
|
+
value={date}
|
|
52
|
+
onChange={(e) => setDate(e.target.value)}
|
|
53
|
+
/>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Examples
|
|
59
|
+
|
|
60
|
+
### Playground
|
|
61
|
+
|
|
62
|
+
Interactive example with all controls.
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
<DatePicker onChange={fn()} />
|
|
66
|
+
```
|
|
67
|
+
|
|
29
68
|
### Sizes
|
|
30
69
|
|
|
70
|
+
DatePicker supports three sizes for different layouts.
|
|
71
|
+
|
|
31
72
|
```tsx
|
|
32
73
|
<Stack gap={2}>
|
|
33
74
|
<DatePicker {...args} size="sm" />
|
|
@@ -36,7 +77,9 @@
|
|
|
36
77
|
</Stack>
|
|
37
78
|
```
|
|
38
79
|
|
|
39
|
-
###
|
|
80
|
+
### With Label
|
|
81
|
+
|
|
82
|
+
Add a label above the date picker.
|
|
40
83
|
|
|
41
84
|
```tsx
|
|
42
85
|
<DatePicker
|
|
@@ -45,7 +88,9 @@
|
|
|
45
88
|
/>
|
|
46
89
|
```
|
|
47
90
|
|
|
48
|
-
###
|
|
91
|
+
### With Helper Text
|
|
92
|
+
|
|
93
|
+
Provide additional guidance below the input.
|
|
49
94
|
|
|
50
95
|
```tsx
|
|
51
96
|
<DatePicker
|
|
@@ -55,7 +100,9 @@
|
|
|
55
100
|
/>
|
|
56
101
|
```
|
|
57
102
|
|
|
58
|
-
### Error
|
|
103
|
+
### Error State
|
|
104
|
+
|
|
105
|
+
Show validation errors with error styling.
|
|
59
106
|
|
|
60
107
|
```tsx
|
|
61
108
|
<DatePicker
|
|
@@ -65,3 +112,652 @@
|
|
|
65
112
|
error
|
|
66
113
|
/>
|
|
67
114
|
```
|
|
115
|
+
|
|
116
|
+
### Required Field
|
|
117
|
+
|
|
118
|
+
Mark the field as required in forms.
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
<DatePicker
|
|
122
|
+
onChange={fn()}
|
|
123
|
+
label="Label"
|
|
124
|
+
helperText="I'm helper text"
|
|
125
|
+
required
|
|
126
|
+
/>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Disabled
|
|
130
|
+
|
|
131
|
+
Prevent user interaction when disabled.
|
|
132
|
+
|
|
133
|
+
```tsx
|
|
134
|
+
<DatePicker
|
|
135
|
+
onChange={fn()}
|
|
136
|
+
disabled
|
|
137
|
+
/>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Minimum Date
|
|
141
|
+
|
|
142
|
+
Restrict selection to dates on or after a minimum date.
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
<DatePicker
|
|
146
|
+
onChange={fn()}
|
|
147
|
+
minDate="2024-04-10"
|
|
148
|
+
/>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Maximum Date
|
|
152
|
+
|
|
153
|
+
Restrict selection to dates on or before a maximum date.
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
<DatePicker
|
|
157
|
+
onChange={fn()}
|
|
158
|
+
maxDate="2024-04-10"
|
|
159
|
+
/>
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Disable Future Dates
|
|
163
|
+
|
|
164
|
+
Prevent selection of dates in the future.
|
|
165
|
+
|
|
166
|
+
```tsx
|
|
167
|
+
<DatePicker
|
|
168
|
+
onChange={fn()}
|
|
169
|
+
disableFuture
|
|
170
|
+
/>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Disable Past Dates
|
|
174
|
+
|
|
175
|
+
Prevent selection of dates in the past.
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
<DatePicker
|
|
179
|
+
onChange={fn()}
|
|
180
|
+
disablePast
|
|
181
|
+
/>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Controlled
|
|
185
|
+
|
|
186
|
+
Parent component manages the date state.
|
|
187
|
+
|
|
188
|
+
```tsx
|
|
189
|
+
<Stack gap={2}>
|
|
190
|
+
<DatePicker {...args} value={value} onChange={e => setValue(e.target.value)} />
|
|
191
|
+
<Button onClick={() => {
|
|
192
|
+
const currentValue = new Date(value);
|
|
193
|
+
currentValue.setDate(currentValue.getDate() + 1);
|
|
194
|
+
const year = currentValue.getFullYear();
|
|
195
|
+
const month = String(currentValue.getMonth() + 1).padStart(2, '0');
|
|
196
|
+
const day = String(currentValue.getDate()).padStart(2, '0');
|
|
197
|
+
setValue(`${year}/${month}/${day}`);
|
|
198
|
+
}}>
|
|
199
|
+
Next Day
|
|
200
|
+
</Button>
|
|
201
|
+
</Stack>
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Uncontrolled
|
|
205
|
+
|
|
206
|
+
Component manages its own state internally.
|
|
207
|
+
|
|
208
|
+
```tsx
|
|
209
|
+
<DatePicker
|
|
210
|
+
onChange={fn()}
|
|
211
|
+
label="Uncontrolled DatePicker"
|
|
212
|
+
helperText="Please select a date"
|
|
213
|
+
defaultValue="2024/04/01"
|
|
214
|
+
/>
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### With Formats
|
|
218
|
+
|
|
219
|
+
Different value formats for the `onChange` event.
|
|
220
|
+
|
|
221
|
+
```tsx
|
|
222
|
+
<Stack gap={2}>
|
|
223
|
+
<DatePicker {...args} value={value['YYYY.MM.DD']} label="YYYY.MM.DD" name="YYYY.MM.DD" format="YYYY.MM.DD" onChange={handleChange} />
|
|
224
|
+
<DatePicker {...args} value={value['YYYY/MM/DD']} label="YYYY/MM/DD" name="YYYY/MM/DD" format="YYYY/MM/DD" onChange={handleChange} />
|
|
225
|
+
<DatePicker {...args} value={value['MM/DD/YYYY']} label="MM/DD/YYYY" name="MM/DD/YYYY" format="MM/DD/YYYY" onChange={handleChange} />
|
|
226
|
+
<DatePicker {...args} value={value['YYYY-MM-DD']} label="YYYY-MM-DD" name="YYYY-MM-DD" format="YYYY-MM-DD" onChange={handleChange} />
|
|
227
|
+
<DatePicker {...args} value={value['DD/MM/YYYY']} label="DD/MM/YYYY" name="DD/MM/YYYY" format="DD/MM/YYYY" onChange={handleChange} />
|
|
228
|
+
<DatePicker {...args} value={value['DD.MM.YYYY']} label="DD.MM.YYYY" name="DD.MM.YYYY" format="DD.MM.YYYY" onChange={handleChange} />
|
|
229
|
+
</Stack>
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### With Display Formats
|
|
233
|
+
|
|
234
|
+
Different display formats shown in the input field.
|
|
235
|
+
|
|
236
|
+
```tsx
|
|
237
|
+
<Stack gap={2}>
|
|
238
|
+
<DatePicker {...args} value={value1} label="YYYY.MM.DD" name="YYYY.MM.DD" displayFormat="YYYY.MM.DD" onChange={e => {
|
|
239
|
+
setValue1(e.target.value);
|
|
240
|
+
args.onChange?.(e);
|
|
241
|
+
}} />
|
|
242
|
+
<DatePicker {...args} value={value2} label="YYYY/MM/DD" name="YYYY/MM/DD" displayFormat="YYYY/MM/DD" onChange={e => {
|
|
243
|
+
setValue2(e.target.value);
|
|
244
|
+
args.onChange?.(e);
|
|
245
|
+
}} />
|
|
246
|
+
<DatePicker {...args} value={value3} label="MM/DD/YYYY" name="MM/DD/YYYY" displayFormat="MM/DD/YYYY" onChange={e => {
|
|
247
|
+
setValue3(e.target.value);
|
|
248
|
+
args.onChange?.(e);
|
|
249
|
+
}} />
|
|
250
|
+
<DatePicker {...args} value={value4} label="YYYY-MM-DD" name="YYYY-MM-DD" displayFormat="YYYY-MM-DD" onChange={e => {
|
|
251
|
+
setValue4(e.target.value);
|
|
252
|
+
args.onChange?.(e);
|
|
253
|
+
}} />
|
|
254
|
+
<DatePicker {...args} value={value5} label="DD/MM/YYYY" name="DD/MM/YYYY" displayFormat="DD/MM/YYYY" onChange={e => {
|
|
255
|
+
setValue5(e.target.value);
|
|
256
|
+
args.onChange?.(e);
|
|
257
|
+
}} />
|
|
258
|
+
<DatePicker {...args} value={value6} label="DD.MM.YYYY" name="DD.MM.YYYY" displayFormat="DD.MM.YYYY" onChange={e => {
|
|
259
|
+
setValue6(e.target.value);
|
|
260
|
+
args.onChange?.(e);
|
|
261
|
+
}} />
|
|
262
|
+
</Stack>
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Input Read Only
|
|
266
|
+
|
|
267
|
+
Allow calendar selection only, prevent typing.
|
|
268
|
+
|
|
269
|
+
```tsx
|
|
270
|
+
<DatePicker
|
|
271
|
+
onChange={fn()}
|
|
272
|
+
value="2024/04/01"
|
|
273
|
+
inputReadOnly
|
|
274
|
+
/>
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Read Only
|
|
278
|
+
|
|
279
|
+
Fully read-only state with no interaction.
|
|
280
|
+
|
|
281
|
+
```tsx
|
|
282
|
+
<DatePicker
|
|
283
|
+
onChange={fn()}
|
|
284
|
+
value="2024/04/01"
|
|
285
|
+
readOnly
|
|
286
|
+
/>
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Hide Clear Button
|
|
290
|
+
|
|
291
|
+
Remove the clear button from the calendar popup.
|
|
292
|
+
|
|
293
|
+
```tsx
|
|
294
|
+
<DatePicker
|
|
295
|
+
onChange={fn()}
|
|
296
|
+
value="2024/04/01"
|
|
297
|
+
hideClearButton
|
|
298
|
+
/>
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Custom Date Disabling
|
|
302
|
+
|
|
303
|
+
Use `shouldDisableDate` to disable specific dates.
|
|
304
|
+
|
|
305
|
+
```tsx
|
|
306
|
+
<DatePicker
|
|
307
|
+
onChange={fn()}
|
|
308
|
+
disablePast
|
|
309
|
+
shouldDisableDate={date => [0, 6].includes(new Date(date).getDay()) || new Date(date).getTime() >= new Date().getTime() + 7 * 24 * 60 * 60 * 1000}
|
|
310
|
+
/>
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Synchronized Date Pickers
|
|
314
|
+
|
|
315
|
+
Multiple date pickers sharing the same value.
|
|
316
|
+
|
|
317
|
+
```tsx
|
|
318
|
+
<Stack gap={2}>
|
|
319
|
+
<DatePicker label="Select Date" value={value} inputReadOnly onChange={e => {
|
|
320
|
+
setValue(e.target.value);
|
|
321
|
+
setValue2(e.target.value);
|
|
322
|
+
}} />
|
|
323
|
+
<DatePicker label="Synchronized" value={value2} disabled />
|
|
324
|
+
</Stack>
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
## When to Use
|
|
328
|
+
|
|
329
|
+
### ✅ Good Use Cases
|
|
330
|
+
|
|
331
|
+
- **Form date fields**: Birthdays, appointment dates, due dates
|
|
332
|
+
- **Booking systems**: Hotel check-in/out, flight dates, reservations
|
|
333
|
+
- **Scheduling**: Event dates, meeting times, deadlines
|
|
334
|
+
- **Data filtering**: Date range filters in reports and dashboards
|
|
335
|
+
- **Historical data**: Recording when events occurred
|
|
336
|
+
- **Deadline selection**: Task due dates, project milestones
|
|
337
|
+
|
|
338
|
+
### ❌ When Not to Use
|
|
339
|
+
|
|
340
|
+
- **Date ranges**: Use DateRangePicker for start/end date pairs
|
|
341
|
+
- **Month/Year only**: Use MonthPicker for month-level granularity
|
|
342
|
+
- **Time selection**: Use a dedicated TimePicker component
|
|
343
|
+
- **Relative dates**: For "last 7 days" style filters, use dropdown selection
|
|
344
|
+
- **Known date sets**: For selecting from predefined dates, consider Select or RadioButton
|
|
345
|
+
|
|
346
|
+
## Common Use Cases
|
|
347
|
+
|
|
348
|
+
### Form with Validation
|
|
349
|
+
|
|
350
|
+
```tsx
|
|
351
|
+
function BookingForm() {
|
|
352
|
+
const [checkIn, setCheckIn] = useState('');
|
|
353
|
+
const [error, setError] = useState('');
|
|
354
|
+
|
|
355
|
+
const handleSubmit = () => {
|
|
356
|
+
if (!checkIn) {
|
|
357
|
+
setError('Please select a check-in date');
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
// Submit form
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
return (
|
|
364
|
+
<form onSubmit={handleSubmit}>
|
|
365
|
+
<DatePicker
|
|
366
|
+
label="Check-in Date"
|
|
367
|
+
value={checkIn}
|
|
368
|
+
onChange={(e) => {
|
|
369
|
+
setCheckIn(e.target.value);
|
|
370
|
+
setError('');
|
|
371
|
+
}}
|
|
372
|
+
error={!!error}
|
|
373
|
+
helperText={error || 'Select your arrival date'}
|
|
374
|
+
disablePast
|
|
375
|
+
required
|
|
376
|
+
/>
|
|
377
|
+
<Button type="submit">Book Now</Button>
|
|
378
|
+
</form>
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Date Range Restriction
|
|
384
|
+
|
|
385
|
+
```tsx
|
|
386
|
+
function EventScheduler() {
|
|
387
|
+
const [eventDate, setEventDate] = useState('');
|
|
388
|
+
|
|
389
|
+
// Event must be within the next 30 days
|
|
390
|
+
const today = new Date();
|
|
391
|
+
const maxDate = new Date(today.getTime() + 30 * 24 * 60 * 60 * 1000);
|
|
392
|
+
|
|
393
|
+
const formatDate = (date) => {
|
|
394
|
+
const year = date.getFullYear();
|
|
395
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
396
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
397
|
+
return `${year}/${month}/${day}`;
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
return (
|
|
401
|
+
<DatePicker
|
|
402
|
+
label="Event Date"
|
|
403
|
+
value={eventDate}
|
|
404
|
+
onChange={(e) => setEventDate(e.target.value)}
|
|
405
|
+
minDate={formatDate(today)}
|
|
406
|
+
maxDate={formatDate(maxDate)}
|
|
407
|
+
helperText="Select a date within the next 30 days"
|
|
408
|
+
/>
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
### Custom Date Validation
|
|
414
|
+
|
|
415
|
+
```tsx
|
|
416
|
+
function AppointmentPicker() {
|
|
417
|
+
const [date, setDate] = useState('');
|
|
418
|
+
|
|
419
|
+
// Disable weekends and holidays
|
|
420
|
+
const shouldDisableDate = (dateString) => {
|
|
421
|
+
const date = new Date(dateString);
|
|
422
|
+
const day = date.getDay();
|
|
423
|
+
|
|
424
|
+
// Disable weekends (Saturday = 6, Sunday = 0)
|
|
425
|
+
if (day === 0 || day === 6) return true;
|
|
426
|
+
|
|
427
|
+
// Disable specific holidays
|
|
428
|
+
const holidays = ['2024/01/01', '2024/12/25'];
|
|
429
|
+
if (holidays.includes(dateString)) return true;
|
|
430
|
+
|
|
431
|
+
return false;
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
return (
|
|
435
|
+
<DatePicker
|
|
436
|
+
label="Appointment Date"
|
|
437
|
+
value={date}
|
|
438
|
+
onChange={(e) => setDate(e.target.value)}
|
|
439
|
+
shouldDisableDate={shouldDisableDate}
|
|
440
|
+
disablePast
|
|
441
|
+
helperText="Weekdays only, excluding holidays"
|
|
442
|
+
/>
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### Different Regional Formats
|
|
448
|
+
|
|
449
|
+
```tsx
|
|
450
|
+
function RegionalDateForm({ locale }) {
|
|
451
|
+
const [date, setDate] = useState('');
|
|
452
|
+
|
|
453
|
+
// Format based on locale
|
|
454
|
+
const getDisplayFormat = () => {
|
|
455
|
+
switch (locale) {
|
|
456
|
+
case 'en-US':
|
|
457
|
+
return 'MM/DD/YYYY';
|
|
458
|
+
case 'en-GB':
|
|
459
|
+
return 'DD/MM/YYYY';
|
|
460
|
+
case 'de-DE':
|
|
461
|
+
return 'DD.MM.YYYY';
|
|
462
|
+
default:
|
|
463
|
+
return 'YYYY/MM/DD';
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
return (
|
|
468
|
+
<DatePicker
|
|
469
|
+
label="Date"
|
|
470
|
+
value={date}
|
|
471
|
+
onChange={(e) => setDate(e.target.value)}
|
|
472
|
+
format="YYYY/MM/DD" // Internal value format stays consistent
|
|
473
|
+
displayFormat={getDisplayFormat()} // Display varies by locale
|
|
474
|
+
/>
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
### Mobile-Friendly Input
|
|
480
|
+
|
|
481
|
+
```tsx
|
|
482
|
+
function MobileDatePicker() {
|
|
483
|
+
const [date, setDate] = useState('');
|
|
484
|
+
|
|
485
|
+
return (
|
|
486
|
+
<DatePicker
|
|
487
|
+
label="Select Date"
|
|
488
|
+
value={date}
|
|
489
|
+
onChange={(e) => setDate(e.target.value)}
|
|
490
|
+
inputReadOnly // Prevent keyboard on mobile, use calendar only
|
|
491
|
+
size="lg" // Larger touch targets
|
|
492
|
+
/>
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
### Form Integration with React Hook Form
|
|
498
|
+
|
|
499
|
+
```tsx
|
|
500
|
+
function DateFormWithHookForm() {
|
|
501
|
+
const { register, handleSubmit, setValue, watch } = useForm();
|
|
502
|
+
const birthDate = watch('birthDate');
|
|
503
|
+
|
|
504
|
+
return (
|
|
505
|
+
<form onSubmit={handleSubmit(onSubmit)}>
|
|
506
|
+
<DatePicker
|
|
507
|
+
label="Birth Date"
|
|
508
|
+
value={birthDate || ''}
|
|
509
|
+
onChange={(e) => setValue('birthDate', e.target.value)}
|
|
510
|
+
disableFuture
|
|
511
|
+
required
|
|
512
|
+
/>
|
|
513
|
+
</form>
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
## Props and Customization
|
|
519
|
+
|
|
520
|
+
### Key Props
|
|
521
|
+
|
|
522
|
+
| Prop | Type | Default | Description |
|
|
523
|
+
| ------------------- | ----------------------------------------------------------- | ---------------- | ------------------------------------------- |
|
|
524
|
+
| `value` | `string` | - | Controlled date value (must match `format`) |
|
|
525
|
+
| `defaultValue` | `string` | - | Default value for uncontrolled mode |
|
|
526
|
+
| `onChange` | `(e: { target: { name?: string; value: string } }) => void` | - | Change handler |
|
|
527
|
+
| `format` | `string` | `'YYYY/MM/DD'` | Format for `value` and `onChange` |
|
|
528
|
+
| `displayFormat` | `string` | Same as `format` | Format displayed in the input |
|
|
529
|
+
| `label` | `string` | - | Label text |
|
|
530
|
+
| `helperText` | `string` | - | Helper text below input |
|
|
531
|
+
| `error` | `boolean` | `false` | Error state |
|
|
532
|
+
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Component size |
|
|
533
|
+
| `disabled` | `boolean` | `false` | Disabled state |
|
|
534
|
+
| `required` | `boolean` | `false` | Required field indicator |
|
|
535
|
+
| `minDate` | `string` | - | Minimum selectable date |
|
|
536
|
+
| `maxDate` | `string` | - | Maximum selectable date |
|
|
537
|
+
| `disableFuture` | `boolean` | `false` | Disable all future dates |
|
|
538
|
+
| `disablePast` | `boolean` | `false` | Disable all past dates |
|
|
539
|
+
| `shouldDisableDate` | `(date: string) => boolean` | - | Custom date disable function |
|
|
540
|
+
| `inputReadOnly` | `boolean` | `false` | Prevent typing, calendar only |
|
|
541
|
+
| `readOnly` | `boolean` | `false` | Fully read-only |
|
|
542
|
+
| `hideClearButton` | `boolean` | `false` | Hide clear button in calendar |
|
|
543
|
+
|
|
544
|
+
### Format vs DisplayFormat
|
|
545
|
+
|
|
546
|
+
Understanding the difference between `format` and `displayFormat`:
|
|
547
|
+
|
|
548
|
+
```tsx
|
|
549
|
+
// format: Affects the value in onChange
|
|
550
|
+
// displayFormat: Affects what users see in the input
|
|
551
|
+
|
|
552
|
+
<DatePicker
|
|
553
|
+
format="YYYY-MM-DD" // onChange returns "2024-04-15"
|
|
554
|
+
displayFormat="MM/DD/YYYY" // Input shows "04/15/2024"
|
|
555
|
+
onChange={(e) => {
|
|
556
|
+
console.log(e.target.value); // "2024-04-15"
|
|
557
|
+
}}
|
|
558
|
+
/>
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
### Supported Format Tokens
|
|
562
|
+
|
|
563
|
+
| Token | Description | Example |
|
|
564
|
+
| ------ | ------------- | ------- |
|
|
565
|
+
| `YYYY` | 4-digit year | 2024 |
|
|
566
|
+
| `MM` | 2-digit month | 04 |
|
|
567
|
+
| `DD` | 2-digit day | 15 |
|
|
568
|
+
|
|
569
|
+
Common format patterns:
|
|
570
|
+
|
|
571
|
+
- `YYYY/MM/DD` - ISO-like (default)
|
|
572
|
+
- `YYYY-MM-DD` - ISO 8601
|
|
573
|
+
- `MM/DD/YYYY` - US format
|
|
574
|
+
- `DD/MM/YYYY` - European format
|
|
575
|
+
- `DD.MM.YYYY` - German format
|
|
576
|
+
- `YYYY.MM.DD` - East Asian format
|
|
577
|
+
|
|
578
|
+
### Controlled vs Uncontrolled
|
|
579
|
+
|
|
580
|
+
```tsx
|
|
581
|
+
// Uncontrolled - component manages state
|
|
582
|
+
<DatePicker
|
|
583
|
+
defaultValue="2024/04/01"
|
|
584
|
+
onChange={(e) => console.log(e.target.value)}
|
|
585
|
+
/>
|
|
586
|
+
|
|
587
|
+
// Controlled - you manage state
|
|
588
|
+
const [date, setDate] = useState('2024/04/01');
|
|
589
|
+
<DatePicker
|
|
590
|
+
value={date}
|
|
591
|
+
onChange={(e) => setDate(e.target.value)}
|
|
592
|
+
/>
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
## Accessibility
|
|
596
|
+
|
|
597
|
+
DatePicker includes built-in accessibility features:
|
|
598
|
+
|
|
599
|
+
### ARIA Attributes
|
|
600
|
+
|
|
601
|
+
- Input has proper `role="textbox"`
|
|
602
|
+
- Calendar button has `aria-label="Toggle Calendar"`
|
|
603
|
+
- Calendar popup uses `role="tooltip"` with proper labeling
|
|
604
|
+
- Date buttons announce the full date to screen readers
|
|
605
|
+
|
|
606
|
+
### Keyboard Navigation
|
|
607
|
+
|
|
608
|
+
- **Tab**: Move focus between input and calendar button
|
|
609
|
+
- **Enter/Space**: Open calendar when focused on button
|
|
610
|
+
- **Arrow Keys**: Navigate within calendar
|
|
611
|
+
- **Escape**: Close calendar popup
|
|
612
|
+
- **Enter**: Select focused date
|
|
613
|
+
|
|
614
|
+
### Screen Reader Support
|
|
615
|
+
|
|
616
|
+
```tsx
|
|
617
|
+
// Dates are announced with full context
|
|
618
|
+
<button aria-label="April 15, 2024">15</button>
|
|
619
|
+
|
|
620
|
+
// Navigation buttons are descriptive
|
|
621
|
+
<button aria-label="Previous Month">←</button>
|
|
622
|
+
<button aria-label="Next Month">→</button>
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
### Focus Management
|
|
626
|
+
|
|
627
|
+
- Focus moves to calendar when opened
|
|
628
|
+
- Focus returns to input when calendar closes
|
|
629
|
+
- Clear visual focus indicators on all interactive elements
|
|
630
|
+
|
|
631
|
+
## Best Practices
|
|
632
|
+
|
|
633
|
+
### ✅ Do
|
|
634
|
+
|
|
635
|
+
1. **Use appropriate date restrictions**: Set min/max dates when applicable
|
|
636
|
+
|
|
637
|
+
```tsx
|
|
638
|
+
// ✅ Good: Reasonable date constraints
|
|
639
|
+
<DatePicker
|
|
640
|
+
label="Birth Date"
|
|
641
|
+
disableFuture
|
|
642
|
+
minDate="1900/01/01"
|
|
643
|
+
/>
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
2. **Provide clear labels and helper text**: Guide users on expected input
|
|
647
|
+
|
|
648
|
+
```tsx
|
|
649
|
+
// ✅ Good: Clear guidance
|
|
650
|
+
<DatePicker
|
|
651
|
+
label="Preferred Delivery Date"
|
|
652
|
+
helperText="Select a weekday within the next 2 weeks"
|
|
653
|
+
/>
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
3. **Use consistent formats**: Match your application's locale conventions
|
|
657
|
+
|
|
658
|
+
```tsx
|
|
659
|
+
// ✅ Good: Consistent with app locale
|
|
660
|
+
<DatePicker
|
|
661
|
+
format="YYYY-MM-DD" // API format
|
|
662
|
+
displayFormat="MM/DD/YYYY" // US locale display
|
|
663
|
+
/>
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
4. **Handle empty states**: Consider what empty value means in your context
|
|
667
|
+
|
|
668
|
+
```tsx
|
|
669
|
+
// ✅ Good: Clear empty state handling
|
|
670
|
+
const [date, setDate] = useState('');
|
|
671
|
+
{date ? <span>Selected: {date}</span> : <span>No date selected</span>}
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
### ❌ Don't
|
|
675
|
+
|
|
676
|
+
1. **Don't use inconsistent value formats**: Always match value format to `format` prop
|
|
677
|
+
|
|
678
|
+
```tsx
|
|
679
|
+
// ❌ Bad: Mismatched formats
|
|
680
|
+
<DatePicker
|
|
681
|
+
format="YYYY/MM/DD"
|
|
682
|
+
value="04/15/2024" // Wrong! Should be "2024/04/15"
|
|
683
|
+
/>
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
2. **Don't disable validation feedback**: Show errors when dates are invalid
|
|
687
|
+
|
|
688
|
+
```tsx
|
|
689
|
+
// ❌ Bad: No error feedback
|
|
690
|
+
<DatePicker value={invalidDate} />
|
|
691
|
+
|
|
692
|
+
// ✅ Good: Show validation state
|
|
693
|
+
<DatePicker value={date} error={!isValid} helperText={errorMessage} />
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
3. **Don't forget mobile users**: Use `inputReadOnly` for better mobile experience
|
|
697
|
+
|
|
698
|
+
4. **Don't use placeholder as label**: Use the `label` prop instead
|
|
699
|
+
|
|
700
|
+
```tsx
|
|
701
|
+
// ❌ Bad: Placeholder as label
|
|
702
|
+
<DatePicker placeholder="Birth Date" />
|
|
703
|
+
|
|
704
|
+
// ✅ Good: Proper label
|
|
705
|
+
<DatePicker label="Birth Date" />
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
## Performance Considerations
|
|
709
|
+
|
|
710
|
+
### Memoize shouldDisableDate
|
|
711
|
+
|
|
712
|
+
For complex date validation logic, memoize the function:
|
|
713
|
+
|
|
714
|
+
```tsx
|
|
715
|
+
const shouldDisableDate = useCallback((dateString) => {
|
|
716
|
+
const date = new Date(dateString);
|
|
717
|
+
// Complex validation logic
|
|
718
|
+
return isHoliday(date) || isWeekend(date);
|
|
719
|
+
}, [holidays]); // Only recreate when holidays change
|
|
720
|
+
|
|
721
|
+
<DatePicker shouldDisableDate={shouldDisableDate} />
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
### Controlled Component Optimization
|
|
725
|
+
|
|
726
|
+
When using controlled mode, avoid unnecessary state updates:
|
|
727
|
+
|
|
728
|
+
```tsx
|
|
729
|
+
const handleChange = useCallback((e) => {
|
|
730
|
+
setDate(e.target.value);
|
|
731
|
+
}, []);
|
|
732
|
+
|
|
733
|
+
<DatePicker value={date} onChange={handleChange} />
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
### Format Conversion
|
|
737
|
+
|
|
738
|
+
When working with APIs that expect different date formats:
|
|
739
|
+
|
|
740
|
+
```tsx
|
|
741
|
+
function DateField({ value, onChange, apiFormat = 'YYYY-MM-DD' }) {
|
|
742
|
+
// Convert from API format to display
|
|
743
|
+
const displayValue = useMemo(() =>
|
|
744
|
+
convertFormat(value, apiFormat, 'YYYY/MM/DD'),
|
|
745
|
+
[value, apiFormat]
|
|
746
|
+
);
|
|
747
|
+
|
|
748
|
+
const handleChange = useCallback((e) => {
|
|
749
|
+
// Convert back to API format
|
|
750
|
+
onChange(convertFormat(e.target.value, 'YYYY/MM/DD', apiFormat));
|
|
751
|
+
}, [onChange, apiFormat]);
|
|
752
|
+
|
|
753
|
+
return (
|
|
754
|
+
<DatePicker
|
|
755
|
+
value={displayValue}
|
|
756
|
+
onChange={handleChange}
|
|
757
|
+
format="YYYY/MM/DD"
|
|
758
|
+
/>
|
|
759
|
+
);
|
|
760
|
+
}
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
DatePicker is a fundamental form component for date input. Choose appropriate date restrictions based on your use case, maintain consistent formats throughout your application, and provide clear guidance to users for the best experience.
|