@ceed/ads 1.23.3 → 1.23.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/components/data-display/Badge.md +71 -39
  2. package/dist/components/data-display/InfoSign.md +74 -98
  3. package/dist/components/data-display/Typography.md +310 -61
  4. package/dist/components/feedback/CircularProgress.md +257 -0
  5. package/dist/components/feedback/Skeleton.md +280 -0
  6. package/dist/components/feedback/llms.txt +2 -0
  7. package/dist/components/inputs/ButtonGroup.md +115 -106
  8. package/dist/components/inputs/Calendar.md +98 -459
  9. package/dist/components/inputs/CurrencyInput.md +181 -8
  10. package/dist/components/inputs/DatePicker.md +108 -436
  11. package/dist/components/inputs/DateRangePicker.md +130 -496
  12. package/dist/components/inputs/FilterMenu.md +169 -19
  13. package/dist/components/inputs/FilterableCheckboxGroup.md +119 -24
  14. package/dist/components/inputs/FormControl.md +368 -0
  15. package/dist/components/inputs/IconButton.md +137 -88
  16. package/dist/components/inputs/MonthPicker.md +95 -427
  17. package/dist/components/inputs/MonthRangePicker.md +89 -471
  18. package/dist/components/inputs/PercentageInput.md +183 -19
  19. package/dist/components/inputs/RadioButton.md +163 -35
  20. package/dist/components/inputs/RadioList.md +241 -0
  21. package/dist/components/inputs/RadioTileGroup.md +146 -62
  22. package/dist/components/inputs/Select.md +219 -328
  23. package/dist/components/inputs/Slider.md +334 -0
  24. package/dist/components/inputs/Switch.md +136 -376
  25. package/dist/components/inputs/Textarea.md +209 -11
  26. package/dist/components/inputs/Uploader/Uploader.md +145 -66
  27. package/dist/components/inputs/llms.txt +3 -0
  28. package/dist/components/navigation/Breadcrumbs.md +80 -322
  29. package/dist/components/navigation/Dropdown.md +92 -221
  30. package/dist/components/navigation/IconMenuButton.md +40 -502
  31. package/dist/components/navigation/InsetDrawer.md +68 -738
  32. package/dist/components/navigation/Link.md +39 -298
  33. package/dist/components/navigation/Menu.md +92 -285
  34. package/dist/components/navigation/MenuButton.md +55 -448
  35. package/dist/components/navigation/Pagination.md +47 -338
  36. package/dist/components/navigation/ProfileMenu.md +45 -268
  37. package/dist/components/navigation/Stepper.md +160 -28
  38. package/dist/components/navigation/Tabs.md +57 -316
  39. package/dist/components/surfaces/Sheet.md +150 -333
  40. package/dist/guides/ThemeProvider.md +116 -0
  41. package/dist/guides/llms.txt +9 -0
  42. package/dist/llms.txt +8 -0
  43. package/package.json +1 -1
@@ -2,7 +2,15 @@
2
2
 
3
3
  ## Introduction
4
4
 
5
- Calendar is a low-level UI component that renders an interactive calendar grid for date selection. It provides the visual foundation for higher-level picker components like DatePicker, DateRangePicker, and MonthPicker. While you'll rarely use Calendar directly in forms, it's useful for building custom date selection interfaces or embedded calendar views where you need full control over the calendar behavior.
5
+ Calendar is a low-level UI component that renders an interactive calendar grid for date selection. It provides the visual foundation for higher-level picker components like DatePicker, DateRangePicker, and MonthPicker. While you will rarely use Calendar directly in forms, it is useful for building custom date selection interfaces or embedded calendar views where you need full control over the calendar behavior.
6
+
7
+ Calendar accepts `Date` objects (not strings) and uses an array-based value format `[startDate, endDate]` to support both single-date and range selection modes. It supports multiple locales, view modes (day and month), and flexible date restriction options.
8
+
9
+ > **Note**: Calendar is a **building block component** intended for custom implementations.
10
+ >
11
+ > - For standard date input, use `DatePicker` instead.
12
+ > - For date ranges, use `DateRangePicker` instead.
13
+ > - For month selection, use `MonthPicker` or `MonthRangePicker`.
6
14
 
7
15
  ```tsx
8
16
  <Calendar />
@@ -12,22 +20,13 @@ Calendar is a low-level UI component that renders an interactive calendar grid f
12
20
  | ------ | ----------- | ------- |
13
21
  | locale | — | — |
14
22
 
15
- > ⚠️ **Usage Warning** ⚠️
16
- >
17
- > Calendar is a **building block component** intended for custom implementations:
18
- >
19
- > - **For standard date input**: Use `DatePicker` instead
20
- > - **For date ranges**: Use `DateRangePicker` instead
21
- > - **For month selection**: Use `MonthPicker` or `MonthRangePicker`
22
- > - **Value type**: Calendar uses `Date` objects, not strings like picker components
23
- > - **No input field**: Calendar is just the grid, with no text input
24
-
25
23
  ## Usage
26
24
 
27
25
  ```tsx
28
26
  import { Calendar } from '@ceed/ads';
27
+ import { useState } from 'react';
29
28
 
30
- function DateViewer() {
29
+ function DateSelector() {
31
30
  const [date, setDate] = useState<Date | null>(null);
32
31
 
33
32
  return (
@@ -39,51 +38,51 @@ function DateViewer() {
39
38
  }
40
39
  ```
41
40
 
42
- ## Examples
43
-
44
- ### Controlled
41
+ ## Controlled
45
42
 
46
- Parent component manages the calendar state.
43
+ In controlled mode, the parent component manages the calendar's selected date via the `value` and `onChange` props. This is the recommended approach for most use cases.
47
44
 
48
45
  ```tsx
49
46
  <Calendar />
50
47
  ```
51
48
 
52
- ### Uncontrolled
49
+ ## Uncontrolled
53
50
 
54
- Calendar manages its own state internally.
51
+ In uncontrolled mode, Calendar manages its own internal state. Use this when you do not need to programmatically read or set the selected date.
55
52
 
56
53
  ```tsx
57
54
  <Calendar />
58
55
  ```
59
56
 
60
- ### With Locale
57
+ ## Locale Support
61
58
 
62
- Display calendar in different languages.
59
+ Display month names, day abbreviations, and first-day-of-week according to a specific locale using the `locale` prop.
63
60
 
64
61
  ```tsx
65
62
  <Calendar locale="de" />
66
63
  ```
67
64
 
68
- ### Range Selection
65
+ ## Range Selection
69
66
 
70
- Enable selection of date ranges (start and end dates).
67
+ Enable `rangeSelection` to allow users to pick a start and end date. The first click sets the start date and the second click sets the end date. Dates between the two are visually highlighted.
71
68
 
72
69
  ```tsx
73
70
  <Calendar rangeSelection />
74
71
  ```
75
72
 
76
- ### Default Value with Range Selection
73
+ ## Default Value with Range Selection
77
74
 
78
- Pre-populate a date range when the calendar loads.
75
+ Pre-populate a date range when the calendar initially renders by providing a `defaultValue` array alongside `rangeSelection`.
79
76
 
80
77
  ```tsx
81
78
  <Calendar rangeSelection defaultValue={[new Date(2024, 2, 1), new Date(2024, 2, 20)]} />
82
79
  ```
83
80
 
84
- ### Only Month View
81
+ ## View Modes
82
+
83
+ ### Month View Only
85
84
 
86
- Show only the month grid without day selection.
85
+ Show only the month grid for month-level navigation and selection.
87
86
 
88
87
  ```tsx
89
88
  <Calendar
@@ -94,7 +93,7 @@ Show only the month grid without day selection.
94
93
 
95
94
  ### All Views
96
95
 
97
- Show both month and day views with navigation between them.
96
+ Enable both month and day views with navigation between them, giving users the flexibility to zoom in and out of the calendar.
98
97
 
99
98
  ```tsx
100
99
  <Calendar views={['month', 'day']} rangeSelection defaultValue={[new Date(2024, 2, 1), new Date(2024, 2, 20)]} />
@@ -102,15 +101,17 @@ Show both month and day views with navigation between them.
102
101
 
103
102
  ### Month Selection
104
103
 
105
- Use the calendar for month-level selection only.
104
+ Use the calendar exclusively for selecting a month. Combine `view="month"` with the `onMonthChange` callback to capture month-level changes.
106
105
 
107
106
  ```tsx
108
107
  <Calendar view="month" value={[value, undefined]} onMonthChange={setValue} />
109
108
  ```
110
109
 
110
+ ## Date Restrictions
111
+
111
112
  ### Minimum Date
112
113
 
113
- Restrict selection to dates on or after a minimum date.
114
+ Restrict selection to dates on or after a specified minimum date using the `minDate` prop.
114
115
 
115
116
  ```tsx
116
117
  <Calendar minDate={new Date('2024-02-15')} rangeSelection />
@@ -118,7 +119,7 @@ Restrict selection to dates on or after a minimum date.
118
119
 
119
120
  ### Maximum Date
120
121
 
121
- Restrict selection to dates on or before a maximum date.
122
+ Restrict selection to dates on or before a specified maximum date using the `maxDate` prop.
122
123
 
123
124
  ```tsx
124
125
  <Calendar maxDate={new Date('2024-02-15')} rangeSelection />
@@ -126,7 +127,7 @@ Restrict selection to dates on or before a maximum date.
126
127
 
127
128
  ### Disable Future Dates
128
129
 
129
- Prevent selection of any future dates.
130
+ Prevent selection of any date after today with the `disableFuture` prop.
130
131
 
131
132
  ```tsx
132
133
  <Calendar disableFuture rangeSelection />
@@ -134,7 +135,7 @@ Prevent selection of any future dates.
134
135
 
135
136
  ### Disable Past Dates
136
137
 
137
- Prevent selection of any past dates.
138
+ Prevent selection of any date before today with the `disablePast` prop.
138
139
 
139
140
  ```tsx
140
141
  <Calendar disablePast rangeSelection />
@@ -142,15 +143,15 @@ Prevent selection of any past dates.
142
143
 
143
144
  ### Custom Date Disabling
144
145
 
145
- Use `shouldDisableDate` to disable specific dates like weekends.
146
+ Use the `shouldDisableDate` callback to implement arbitrary disabling logic, such as blocking weekends.
146
147
 
147
148
  ```tsx
148
149
  <Calendar shouldDisableDate={date => [0, 6].includes(date.getDay())} />
149
150
  ```
150
151
 
151
- ### Weekends Disabled with Past Dates
152
+ ### Weekends and Past Dates Disabled
152
153
 
153
- Combine multiple restrictions: disable weekends and past dates.
154
+ Combine `shouldDisableDate` with `disablePast` to apply multiple restrictions simultaneously.
154
155
 
155
156
  ```tsx
156
157
  <Calendar shouldDisableDate={date => [0, 6].includes(date.getDay())} disablePast />
@@ -158,49 +159,33 @@ Combine multiple restrictions: disable weekends and past dates.
158
159
 
159
160
  ### Complex Date Restrictions
160
161
 
161
- Disable weekends, past dates, and limit to one week ahead.
162
+ Disable weekends, past dates, and limit selection to one week ahead by combining all restriction options.
162
163
 
163
164
  ```tsx
164
165
  <Calendar shouldDisableDate={date => [0, 6].includes(date.getDay()) || date.getTime() >= new Date().getTime() + 7 * 24 * 60 * 60 * 1000} disablePast />
165
166
  ```
166
167
 
167
- ## When to Use
168
-
169
- ### ✅ Good Use Cases
170
-
171
- - **Custom date picker UI**: Building a specialized date selection interface
172
- - **Embedded calendar**: Showing a calendar inline within a page layout
173
- - **Dashboard widget**: Calendar as a date navigation widget
174
- - **Event calendar**: Base for event/appointment calendars
175
- - **Custom range pickers**: Building specialized range selection UI
176
- - **Full control needed**: When you need direct access to calendar behavior
177
-
178
- ### ❌ When Not to Use
179
-
180
- - **Form date input**: Use `DatePicker` for standard form fields
181
- - **Date range forms**: Use `DateRangePicker` for start/end date pairs
182
- - **Month selection forms**: Use `MonthPicker` or `MonthRangePicker`
183
- - **Simple date display**: Use a text display or formatted date
184
- - **Date with time**: Calendar doesn't include time selection
185
-
186
168
  ## Common Use Cases
187
169
 
188
170
  ### Embedded Calendar Widget
189
171
 
190
172
  ```tsx
173
+ import { Calendar } from '@ceed/ads';
174
+ import { Box, Typography } from '@ceed/ads';
175
+
191
176
  function CalendarWidget() {
192
177
  const [selectedDate, setSelectedDate] = useState<Date>(new Date());
193
178
 
194
179
  return (
195
180
  <Box sx={{ p: 2, border: '1px solid', borderColor: 'divider', borderRadius: 'md' }}>
196
- <Typography level="title-md" mb={2}>
181
+ <Typography level="title-md" sx={{ mb: 2 }}>
197
182
  Select a Date
198
183
  </Typography>
199
184
  <Calendar
200
185
  value={[selectedDate, undefined]}
201
186
  onChange={(dates) => dates[0] && setSelectedDate(dates[0])}
202
187
  />
203
- <Typography level="body-sm" mt={2}>
188
+ <Typography level="body-sm" sx={{ mt: 2 }}>
204
189
  Selected: {selectedDate.toLocaleDateString()}
205
190
  </Typography>
206
191
  </Box>
@@ -208,111 +193,38 @@ function CalendarWidget() {
208
193
  }
209
194
  ```
210
195
 
211
- ### Event Calendar with Highlighted Dates
196
+ ### Booking Calendar with Disabled Dates
212
197
 
213
198
  ```tsx
214
- function EventCalendar({ events }) {
215
- const [viewDate, setViewDate] = useState<Date>(new Date());
216
-
217
- // Get dates with events
218
- const eventDates = useMemo(() => {
219
- return events.map((e) => e.date.toDateString());
220
- }, [events]);
221
-
222
- const hasEvent = (date: Date) => eventDates.includes(date.toDateString());
223
-
224
- return (
225
- <Stack gap={2}>
226
- <Calendar
227
- value={[viewDate, undefined]}
228
- onChange={(dates) => dates[0] && setViewDate(dates[0])}
229
- />
230
- {hasEvent(viewDate) && (
231
- <Box>
232
- <Typography level="title-sm">Events on {viewDate.toLocaleDateString()}</Typography>
233
- {events
234
- .filter((e) => e.date.toDateString() === viewDate.toDateString())
235
- .map((event) => (
236
- <Typography key={event.id} level="body-sm">
237
- {event.title}
238
- </Typography>
239
- ))}
240
- </Box>
241
- )}
242
- </Stack>
243
- );
244
- }
245
- ```
246
-
247
- ### Appointment Booking Calendar
248
-
249
- ```tsx
250
- function BookingCalendar({ availableSlots, onBook }) {
199
+ function BookingCalendar({ blockedDates }) {
251
200
  const [selectedDate, setSelectedDate] = useState<Date | null>(null);
252
201
 
253
- // Get available dates
254
- const availableDates = useMemo(() => {
255
- return new Set(availableSlots.map((slot) => slot.date.toDateString()));
256
- }, [availableSlots]);
257
-
258
- // Disable dates without available slots
259
- const shouldDisableDate = (date: Date) => {
260
- const isPast = date < new Date(new Date().setHours(0, 0, 0, 0));
261
- const hasNoSlots = !availableDates.has(date.toDateString());
262
- return isPast || hasNoSlots;
263
- };
264
-
265
- const slotsForSelectedDate = useMemo(() => {
266
- if (!selectedDate) return [];
267
- return availableSlots.filter(
268
- (slot) => slot.date.toDateString() === selectedDate.toDateString()
269
- );
270
- }, [selectedDate, availableSlots]);
202
+ const blockedSet = useMemo(
203
+ () => new Set(blockedDates.map((d) => d.toDateString())),
204
+ [blockedDates]
205
+ );
271
206
 
272
207
  return (
273
- <Stack direction="row" gap={3}>
274
- <Calendar
275
- value={selectedDate ? [selectedDate, undefined] : undefined}
276
- onChange={(dates) => setSelectedDate(dates[0] || null)}
277
- shouldDisableDate={shouldDisableDate}
278
- disablePast
279
- />
280
- {selectedDate && (
281
- <Box sx={{ minWidth: 200 }}>
282
- <Typography level="title-sm" mb={1}>
283
- Available Times on {selectedDate.toLocaleDateString()}
284
- </Typography>
285
- <Stack gap={1}>
286
- {slotsForSelectedDate.map((slot) => (
287
- <Button
288
- key={slot.id}
289
- variant="outlined"
290
- size="sm"
291
- onClick={() => onBook(slot)}
292
- >
293
- {slot.time}
294
- </Button>
295
- ))}
296
- </Stack>
297
- </Box>
298
- )}
299
- </Stack>
208
+ <Calendar
209
+ value={selectedDate ? [selectedDate, undefined] : undefined}
210
+ onChange={(dates) => setSelectedDate(dates[0] || null)}
211
+ disablePast
212
+ shouldDisableDate={(date) =>
213
+ [0, 6].includes(date.getDay()) || blockedSet.has(date.toDateString())
214
+ }
215
+ />
300
216
  );
301
217
  }
302
218
  ```
303
219
 
304
- ### Date Range Selection with Preview
220
+ ### Date Range Selector with Summary
305
221
 
306
222
  ```tsx
307
223
  function DateRangeSelector({ onRangeSelect }) {
308
- const [range, setRange] = useState<[Date | undefined, Date | undefined]>([undefined, undefined]);
309
-
310
- const handleChange = (dates: [Date | undefined, Date | undefined]) => {
311
- setRange(dates);
312
- if (dates[0] && dates[1]) {
313
- onRangeSelect({ start: dates[0], end: dates[1] });
314
- }
315
- };
224
+ const [range, setRange] = useState<[Date | undefined, Date | undefined]>([
225
+ undefined,
226
+ undefined,
227
+ ]);
316
228
 
317
229
  const dayCount = useMemo(() => {
318
230
  if (!range[0] || !range[1]) return 0;
@@ -325,349 +237,76 @@ function DateRangeSelector({ onRangeSelect }) {
325
237
  <Calendar
326
238
  rangeSelection
327
239
  value={range}
328
- onChange={handleChange}
240
+ onChange={(dates) => {
241
+ setRange(dates);
242
+ if (dates[0] && dates[1]) onRangeSelect(dates);
243
+ }}
329
244
  disablePast
330
245
  />
331
- <Box>
332
- {range[0] && (
333
- <Typography level="body-sm">
334
- Start: {range[0].toLocaleDateString()}
335
- </Typography>
336
- )}
337
- {range[1] && (
338
- <Typography level="body-sm">
339
- End: {range[1].toLocaleDateString()} ({dayCount} days)
340
- </Typography>
341
- )}
342
- {!range[0] && (
343
- <Typography level="body-sm" color="neutral">
344
- Select start date
345
- </Typography>
346
- )}
347
- </Box>
246
+ {range[0] && range[1] && (
247
+ <Typography level="body-sm">
248
+ {range[0].toLocaleDateString()} - {range[1].toLocaleDateString()} ({dayCount} days)
249
+ </Typography>
250
+ )}
348
251
  </Stack>
349
252
  );
350
253
  }
351
254
  ```
352
255
 
353
- ### Multi-Language Calendar
354
-
355
- ```tsx
356
- function LocalizedCalendar({ userLocale }) {
357
- const [date, setDate] = useState<Date>(new Date());
358
-
359
- // Map common locales
360
- const getCalendarLocale = (locale: string) => {
361
- const localeMap: Record<string, string> = {
362
- 'en-US': 'en',
363
- 'ko-KR': 'ko',
364
- 'ja-JP': 'ja',
365
- 'de-DE': 'de',
366
- 'fr-FR': 'fr',
367
- 'zh-CN': 'zh',
368
- };
369
- return localeMap[locale] || 'en';
370
- };
371
-
372
- return (
373
- <Calendar
374
- value={[date, undefined]}
375
- onChange={(dates) => dates[0] && setDate(dates[0])}
376
- locale={getCalendarLocale(userLocale)}
377
- />
378
- );
379
- }
380
- ```
381
-
382
- ### Month Navigation Widget
383
-
384
- ```tsx
385
- function MonthNavigator({ onMonthChange }) {
386
- const [currentMonth, setCurrentMonth] = useState<Date>(new Date());
387
-
388
- const handleMonthChange = (date: Date) => {
389
- setCurrentMonth(date);
390
- onMonthChange(date);
391
- };
392
-
393
- return (
394
- <Box>
395
- <Typography level="title-md" mb={2}>
396
- {currentMonth.toLocaleDateString('en-US', { month: 'long', year: 'numeric' })}
397
- </Typography>
398
- <Calendar
399
- view="month"
400
- views={['month']}
401
- value={[currentMonth, undefined]}
402
- onMonthChange={handleMonthChange}
403
- />
404
- </Box>
405
- );
406
- }
407
- ```
408
-
409
- ## Props and Customization
410
-
411
- ### Key Props
412
-
413
- | Prop | Type | Default | Description |
414
- | ------------------- | --------------------------------------------------------- | --------- | ------------------------------------------------ |
415
- | `value` | `[Date \| undefined, Date \| undefined]` | - | Controlled value as array `[startDate, endDate]` |
416
- | `defaultValue` | `[Date \| undefined, Date \| undefined]` | - | Default value for uncontrolled mode |
417
- | `onChange` | `(dates: [Date \| undefined, Date \| undefined]) => void` | - | Change handler |
418
- | `rangeSelection` | `boolean` | `false` | Enable date range selection mode |
419
- | `view` | `'day' \| 'month'` | `'day'` | Current view mode |
420
- | `views` | `('day' \| 'month')[]` | `['day']` | Available views for navigation |
421
- | `locale` | `string` | `'en'` | Locale for month/day names |
422
- | `minDate` | `Date` | - | Minimum selectable date |
423
- | `maxDate` | `Date` | - | Maximum selectable date |
424
- | `disableFuture` | `boolean` | `false` | Disable all future dates |
425
- | `disablePast` | `boolean` | `false` | Disable all past dates |
426
- | `shouldDisableDate` | `(date: Date) => boolean` | - | Custom function to disable specific dates |
427
- | `onMonthChange` | `(date: Date) => void` | - | Callback when month changes |
428
-
429
- ### Value Format
430
-
431
- Calendar uses `Date` objects instead of strings:
432
-
433
- ```tsx
434
- // Single date selection (use undefined for second element)
435
- <Calendar
436
- value={[new Date('2024-04-15'), undefined]}
437
- onChange={(dates) => console.log(dates[0])} // Date object
438
- />
439
-
440
- // Range selection
441
- <Calendar
442
- rangeSelection
443
- value={[new Date('2024-04-01'), new Date('2024-04-15')]}
444
- onChange={(dates) => {
445
- const [start, end] = dates;
446
- console.log('Start:', start); // Date object
447
- console.log('End:', end); // Date object
448
- }}
449
- />
450
- ```
451
-
452
- ### View Modes
453
-
454
- ```tsx
455
- // Day view only (default)
456
- <Calendar views={['day']} />
457
-
458
- // Month view only
459
- <Calendar views={['month']} view="month" />
460
-
461
- // Both views with navigation
462
- <Calendar views={['month', 'day']} />
463
- ```
464
-
465
- ### Date Restrictions
466
-
467
- ```tsx
468
- // Restrict to specific range
469
- <Calendar
470
- minDate={new Date('2024-01-01')}
471
- maxDate={new Date('2024-12-31')}
472
- />
473
-
474
- // Disable weekends
475
- <Calendar
476
- shouldDisableDate={(date) => [0, 6].includes(date.getDay())}
477
- />
478
-
479
- // Combine multiple restrictions
480
- <Calendar
481
- disablePast
482
- disableFuture={false}
483
- maxDate={new Date(Date.now() + 30 * 24 * 60 * 60 * 1000)} // 30 days ahead
484
- shouldDisableDate={(date) => [0, 6].includes(date.getDay())}
485
- />
486
- ```
487
-
488
- ## Accessibility
489
-
490
- Calendar includes built-in accessibility features:
491
-
492
- ### ARIA Attributes
493
-
494
- - Calendar uses proper grid roles for navigation
495
- - Days are announced with full date context
496
- - Disabled dates are marked as `aria-disabled`
497
- - Selected dates use `aria-selected`
498
-
499
- ### Keyboard Navigation
500
-
501
- - **Arrow Keys**: Navigate between days
502
- - **Page Up/Down**: Navigate between months
503
- - **Home/End**: Jump to start/end of week
504
- - **Enter/Space**: Select focused date
505
- - **Tab**: Move focus to calendar navigation buttons
506
-
507
- ### Screen Reader Support
508
-
509
- ```tsx
510
- // Days are announced with full context
511
- <button aria-label="April 15, 2024">15</button>
512
-
513
- // Month navigation buttons are descriptive
514
- <button aria-label="Previous Month">←</button>
515
- <button aria-label="Next Month">→</button>
516
-
517
- // Disabled dates are announced
518
- <button aria-label="April 20, 2024 (unavailable)" aria-disabled="true">20</button>
519
- ```
520
-
521
- ### Focus Management
522
-
523
- - Focus remains within calendar during navigation
524
- - Visual focus indicator on active day
525
- - Disabled dates are not focusable
526
-
527
256
  ## Best Practices
528
257
 
529
- ### Do
530
-
531
- 1. **Use appropriate picker components for forms**: Default to DatePicker/DateRangePicker
258
+ 1. **Prefer higher-level picker components for form fields.** Use DatePicker or DateRangePicker unless you need a custom embedded calendar UI.
532
259
 
533
260
  ```tsx
534
- // ✅ Good: Use DatePicker for form input
535
- <DatePicker
536
- label="Birth Date"
537
- value={date}
538
- onChange={(e) => setDate(e.target.value)}
539
- />
540
- ```
541
-
542
- 2. **Provide clear context when using Calendar directly**: Add surrounding UI to explain the calendar's purpose
543
-
544
- ```tsx
545
- // ✅ Good: Clear context around calendar
546
- <Box>
547
- <Typography level="title-md">Select Appointment Date</Typography>
548
- <Calendar value={[date, undefined]} onChange={handleChange} />
549
- <Typography level="body-sm">Selected: {date?.toLocaleDateString()}</Typography>
550
- </Box>
551
- ```
261
+ // ✅ Use DatePicker for standard form input
262
+ <DatePicker label="Birth Date" value={date} onChange={handleChange} />
552
263
 
553
- 3. **Use appropriate date restrictions**: Prevent invalid selections
554
-
555
- ```tsx
556
- // ✅ Good: Reasonable constraints for booking
557
- <Calendar
558
- disablePast
559
- maxDate={new Date(Date.now() + 90 * 24 * 60 * 60 * 1000)} // 90 days ahead
560
- shouldDisableDate={(date) => [0, 6].includes(date.getDay())} // No weekends
561
- />
264
+ // Using Calendar directly in a form without context
265
+ <form>
266
+ <Calendar onChange={handleChange} />
267
+ </form>
562
268
  ```
563
269
 
564
- 4. **Handle the array value format correctly**: Calendar always uses `[start, end]` format
270
+ 2. **Handle the array value format correctly.** Calendar always uses `[start, end]` format, even for single date selection.
565
271
 
566
272
  ```tsx
567
- // ✅ Good: Proper value handling
568
- const [date, setDate] = useState<Date | null>(null);
273
+ // ✅ Proper single-date handling
569
274
  <Calendar
570
275
  value={date ? [date, undefined] : undefined}
571
276
  onChange={(dates) => setDate(dates[0] || null)}
572
277
  />
573
- ```
574
-
575
- ### ❌ Don't
576
-
577
- 1. **Don't use Calendar when DatePicker suffices**: For standard form inputs, use the picker components
578
-
579
- ```tsx
580
- // ❌ Bad: Using Calendar for simple date input
581
- <form>
582
- <Calendar onChange={handleChange} /> {/* Missing label, not form-friendly */}
583
- </form>
584
-
585
- // ✅ Good: Use DatePicker
586
- <DatePicker label="Date" value={value} onChange={handleChange} />
587
- ```
588
278
 
589
- 2. **Don't forget to handle both elements of the array**: Even for single selection
590
-
591
- ```tsx
592
- // ❌ Bad: Ignoring array format
593
- <Calendar onChange={(date) => setDate(date)} />
594
-
595
- // ✅ Good: Proper array handling
596
- <Calendar onChange={(dates) => setDate(dates[0])} />
279
+ // Treating value as a single Date
280
+ <Calendar value={date} onChange={(d) => setDate(d)} />
597
281
  ```
598
282
 
599
- 3. **Don't mix Date objects with string values**: Calendar uses Date objects
600
-
601
- ```tsx
602
- // ❌ Bad: Using string values
603
- <Calendar value={["2024-04-15", undefined]} />
604
-
605
- // ✅ Good: Using Date objects
606
- <Calendar value={[new Date("2024-04-15"), undefined]} />
607
- ```
608
-
609
- 4. **Don't forget locale for international users**: Set appropriate locale
610
-
611
- ## Performance Considerations
612
-
613
- ### Memoize shouldDisableDate
614
-
615
- For complex date validation, memoize the function:
283
+ 3. **Memoize `shouldDisableDate` for performance.** Complex date validation logic runs on every date cell render, so use `useCallback` or `useMemo` to prevent unnecessary recalculations.
616
284
 
617
285
  ```tsx
286
+ // ✅ Memoized disable function
618
287
  const shouldDisableDate = useCallback((date: Date) => {
619
- // Check if weekend
620
- if ([0, 6].includes(date.getDay())) return true;
621
-
622
- // Check against holiday list
623
- const dateString = date.toISOString().split('T')[0];
624
- return holidays.includes(dateString);
625
- }, [holidays]); // Only recreate when holidays change
288
+ return [0, 6].includes(date.getDay()) || holidays.has(date.toDateString());
289
+ }, [holidays]);
626
290
 
627
- <Calendar shouldDisableDate={shouldDisableDate} />
291
+ // Inline function recreated every render
292
+ <Calendar shouldDisableDate={(date) => expensiveCheck(date)} />
628
293
  ```
629
294
 
630
- ### Avoid Inline Object Creation
631
-
632
- When using controlled mode, avoid creating new arrays on every render:
295
+ 4. **Always use Date objects, not strings.** Calendar expects native `Date` objects for `value`, `defaultValue`, `minDate`, and `maxDate`.
633
296
 
634
297
  ```tsx
635
- // Bad: New array on every render
636
- <Calendar value={[date, undefined]} />
637
-
638
- // ✅ Good: Memoize the value
639
- const calendarValue = useMemo(
640
- () => (date ? [date, undefined] : undefined),
641
- [date]
642
- );
643
- <Calendar value={calendarValue} />
644
- ```
645
-
646
- ### Optimize Event Handlers
298
+ // Using Date objects
299
+ <Calendar value={[new Date('2024-04-15'), undefined]} />
647
300
 
648
- ```tsx
649
- const handleChange = useCallback((dates: [Date | undefined, Date | undefined]) => {
650
- setSelectedDate(dates[0] || null);
651
- }, []);
652
-
653
- <Calendar onChange={handleChange} />
301
+ // ❌ Using string values
302
+ <Calendar value={['2024-04-15', undefined]} />
654
303
  ```
655
304
 
656
- ### Lazy Date Calculations
657
-
658
- For complex availability calculations:
305
+ 5. **Provide surrounding context when using Calendar directly.** Add a heading or label to explain the calendar's purpose, since Calendar itself has no built-in label.
659
306
 
660
- ```tsx
661
- const disabledDates = useMemo(() => {
662
- // Pre-calculate disabled dates once
663
- return new Set(
664
- blockedDates.map((d) => d.toDateString())
665
- );
666
- }, [blockedDates]);
667
-
668
- const shouldDisableDate = useCallback((date: Date) => {
669
- return disabledDates.has(date.toDateString());
670
- }, [disabledDates]);
671
- ```
307
+ ## Accessibility
672
308
 
673
- Calendar is a foundational component for building custom date selection interfaces. For most use cases, prefer the higher-level picker components (DatePicker, DateRangePicker, MonthPicker) which provide built-in form integration, labels, and text input. Use Calendar directly when you need full control over the calendar UI or are building specialized date-related features.
309
+ - **Keyboard navigation**: Arrow keys navigate between days, Page Up/Down switches months, Home/End jumps to start/end of the week, and Enter/Space selects the focused date.
310
+ - **ARIA attributes**: Days are announced with full date context. Selected dates use `aria-selected` and disabled dates are marked with `aria-disabled`.
311
+ - **Focus management**: Focus remains within the calendar during navigation. Disabled dates are skipped during keyboard traversal.
312
+ - **Screen reader support**: Month navigation buttons include descriptive labels (e.g., "Previous Month", "Next Month"), and each day cell is announced with its full date.