@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.
Files changed (62) hide show
  1. package/dist/components/CurrencyInput/CurrencyInput.d.ts +1 -1
  2. package/dist/components/CurrencyInput/hooks/use-currency-setting.d.ts +2 -2
  3. package/dist/components/ProfileMenu/ProfileMenu.d.ts +1 -1
  4. package/dist/components/SearchBar/SearchBar.d.ts +21 -0
  5. package/dist/components/SearchBar/index.d.ts +3 -0
  6. package/dist/components/data-display/Badge.md +39 -71
  7. package/dist/components/data-display/DataTable.md +1 -1
  8. package/dist/components/data-display/InfoSign.md +98 -74
  9. package/dist/components/data-display/Typography.md +97 -363
  10. package/dist/components/feedback/Dialog.md +62 -76
  11. package/dist/components/feedback/Modal.md +44 -259
  12. package/dist/components/feedback/llms.txt +0 -2
  13. package/dist/components/index.d.ts +2 -0
  14. package/dist/components/inputs/Autocomplete.md +107 -356
  15. package/dist/components/inputs/ButtonGroup.md +106 -115
  16. package/dist/components/inputs/Calendar.md +459 -98
  17. package/dist/components/inputs/CurrencyInput.md +5 -183
  18. package/dist/components/inputs/DatePicker.md +431 -108
  19. package/dist/components/inputs/DateRangePicker.md +492 -131
  20. package/dist/components/inputs/FilterMenu.md +19 -169
  21. package/dist/components/inputs/FilterableCheckboxGroup.md +23 -123
  22. package/dist/components/inputs/IconButton.md +88 -137
  23. package/dist/components/inputs/Input.md +0 -5
  24. package/dist/components/inputs/MonthPicker.md +422 -95
  25. package/dist/components/inputs/MonthRangePicker.md +466 -89
  26. package/dist/components/inputs/PercentageInput.md +16 -185
  27. package/dist/components/inputs/RadioButton.md +35 -163
  28. package/dist/components/inputs/RadioTileGroup.md +61 -150
  29. package/dist/components/inputs/SearchBar.md +44 -0
  30. package/dist/components/inputs/Select.md +326 -222
  31. package/dist/components/inputs/Switch.md +376 -136
  32. package/dist/components/inputs/Textarea.md +10 -213
  33. package/dist/components/inputs/Uploader/Uploader.md +66 -145
  34. package/dist/components/inputs/llms.txt +1 -3
  35. package/dist/components/navigation/Breadcrumbs.md +322 -80
  36. package/dist/components/navigation/Dropdown.md +221 -92
  37. package/dist/components/navigation/IconMenuButton.md +502 -40
  38. package/dist/components/navigation/InsetDrawer.md +738 -68
  39. package/dist/components/navigation/Link.md +298 -39
  40. package/dist/components/navigation/Menu.md +285 -92
  41. package/dist/components/navigation/MenuButton.md +448 -55
  42. package/dist/components/navigation/Pagination.md +338 -47
  43. package/dist/components/navigation/ProfileMenu.md +268 -45
  44. package/dist/components/navigation/Stepper.md +28 -160
  45. package/dist/components/navigation/Tabs.md +316 -57
  46. package/dist/components/surfaces/Sheet.md +334 -151
  47. package/dist/index.browser.js +15 -13
  48. package/dist/index.browser.js.map +4 -4
  49. package/dist/index.cjs +289 -288
  50. package/dist/index.d.ts +1 -1
  51. package/dist/index.js +426 -369
  52. package/dist/llms.txt +1 -8
  53. package/framer/index.js +1 -1
  54. package/package.json +16 -15
  55. package/dist/chunks/rehype-accent-FZRUD7VI.js +0 -39
  56. package/dist/components/feedback/CircularProgress.md +0 -257
  57. package/dist/components/feedback/Skeleton.md +0 -280
  58. package/dist/components/inputs/FormControl.md +0 -361
  59. package/dist/components/inputs/RadioList.md +0 -241
  60. package/dist/components/inputs/Slider.md +0 -334
  61. package/dist/guides/ThemeProvider.md +0 -116
  62. package/dist/guides/llms.txt +0 -9
@@ -2,15 +2,7 @@
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 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`.
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.
14
6
 
15
7
  ```tsx
16
8
  <Calendar />
@@ -20,13 +12,22 @@ Calendar accepts `Date` objects (not strings) and uses an array-based value form
20
12
  | ------ | ----------- | ------- |
21
13
  | locale | — | — |
22
14
 
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
+
23
25
  ## Usage
24
26
 
25
27
  ```tsx
26
28
  import { Calendar } from '@ceed/ads';
27
- import { useState } from 'react';
28
29
 
29
- function DateSelector() {
30
+ function DateViewer() {
30
31
  const [date, setDate] = useState<Date | null>(null);
31
32
 
32
33
  return (
@@ -38,51 +39,51 @@ function DateSelector() {
38
39
  }
39
40
  ```
40
41
 
41
- ## Controlled
42
+ ## Examples
43
+
44
+ ### Controlled
42
45
 
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.
46
+ Parent component manages the calendar state.
44
47
 
45
48
  ```tsx
46
49
  <Calendar />
47
50
  ```
48
51
 
49
- ## Uncontrolled
52
+ ### Uncontrolled
50
53
 
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.
54
+ Calendar manages its own state internally.
52
55
 
53
56
  ```tsx
54
57
  <Calendar />
55
58
  ```
56
59
 
57
- ## Locale Support
60
+ ### With Locale
58
61
 
59
- Display month names, day abbreviations, and first-day-of-week according to a specific locale using the `locale` prop.
62
+ Display calendar in different languages.
60
63
 
61
64
  ```tsx
62
65
  <Calendar locale="de" />
63
66
  ```
64
67
 
65
- ## Range Selection
68
+ ### Range Selection
66
69
 
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.
70
+ Enable selection of date ranges (start and end dates).
68
71
 
69
72
  ```tsx
70
73
  <Calendar rangeSelection />
71
74
  ```
72
75
 
73
- ## Default Value with Range Selection
76
+ ### Default Value with Range Selection
74
77
 
75
- Pre-populate a date range when the calendar initially renders by providing a `defaultValue` array alongside `rangeSelection`.
78
+ Pre-populate a date range when the calendar loads.
76
79
 
77
80
  ```tsx
78
81
  <Calendar rangeSelection defaultValue={[new Date(2024, 2, 1), new Date(2024, 2, 20)]} />
79
82
  ```
80
83
 
81
- ## View Modes
82
-
83
- ### Month View Only
84
+ ### Only Month View
84
85
 
85
- Show only the month grid for month-level navigation and selection.
86
+ Show only the month grid without day selection.
86
87
 
87
88
  ```tsx
88
89
  <Calendar
@@ -93,7 +94,7 @@ Show only the month grid for month-level navigation and selection.
93
94
 
94
95
  ### All Views
95
96
 
96
- Enable both month and day views with navigation between them, giving users the flexibility to zoom in and out of the calendar.
97
+ Show both month and day views with navigation between them.
97
98
 
98
99
  ```tsx
99
100
  <Calendar views={['month', 'day']} rangeSelection defaultValue={[new Date(2024, 2, 1), new Date(2024, 2, 20)]} />
@@ -101,17 +102,15 @@ Enable both month and day views with navigation between them, giving users the f
101
102
 
102
103
  ### Month Selection
103
104
 
104
- Use the calendar exclusively for selecting a month. Combine `view="month"` with the `onMonthChange` callback to capture month-level changes.
105
+ Use the calendar for month-level selection only.
105
106
 
106
107
  ```tsx
107
108
  <Calendar view="month" value={[value, undefined]} onMonthChange={setValue} />
108
109
  ```
109
110
 
110
- ## Date Restrictions
111
-
112
111
  ### Minimum Date
113
112
 
114
- Restrict selection to dates on or after a specified minimum date using the `minDate` prop.
113
+ Restrict selection to dates on or after a minimum date.
115
114
 
116
115
  ```tsx
117
116
  <Calendar minDate={new Date('2024-02-15')} rangeSelection />
@@ -119,7 +118,7 @@ Restrict selection to dates on or after a specified minimum date using the `minD
119
118
 
120
119
  ### Maximum Date
121
120
 
122
- Restrict selection to dates on or before a specified maximum date using the `maxDate` prop.
121
+ Restrict selection to dates on or before a maximum date.
123
122
 
124
123
  ```tsx
125
124
  <Calendar maxDate={new Date('2024-02-15')} rangeSelection />
@@ -127,7 +126,7 @@ Restrict selection to dates on or before a specified maximum date using the `max
127
126
 
128
127
  ### Disable Future Dates
129
128
 
130
- Prevent selection of any date after today with the `disableFuture` prop.
129
+ Prevent selection of any future dates.
131
130
 
132
131
  ```tsx
133
132
  <Calendar disableFuture rangeSelection />
@@ -135,7 +134,7 @@ Prevent selection of any date after today with the `disableFuture` prop.
135
134
 
136
135
  ### Disable Past Dates
137
136
 
138
- Prevent selection of any date before today with the `disablePast` prop.
137
+ Prevent selection of any past dates.
139
138
 
140
139
  ```tsx
141
140
  <Calendar disablePast rangeSelection />
@@ -143,15 +142,15 @@ Prevent selection of any date before today with the `disablePast` prop.
143
142
 
144
143
  ### Custom Date Disabling
145
144
 
146
- Use the `shouldDisableDate` callback to implement arbitrary disabling logic, such as blocking weekends.
145
+ Use `shouldDisableDate` to disable specific dates like weekends.
147
146
 
148
147
  ```tsx
149
148
  <Calendar shouldDisableDate={date => [0, 6].includes(date.getDay())} />
150
149
  ```
151
150
 
152
- ### Weekends and Past Dates Disabled
151
+ ### Weekends Disabled with Past Dates
153
152
 
154
- Combine `shouldDisableDate` with `disablePast` to apply multiple restrictions simultaneously.
153
+ Combine multiple restrictions: disable weekends and past dates.
155
154
 
156
155
  ```tsx
157
156
  <Calendar shouldDisableDate={date => [0, 6].includes(date.getDay())} disablePast />
@@ -159,33 +158,49 @@ Combine `shouldDisableDate` with `disablePast` to apply multiple restrictions si
159
158
 
160
159
  ### Complex Date Restrictions
161
160
 
162
- Disable weekends, past dates, and limit selection to one week ahead by combining all restriction options.
161
+ Disable weekends, past dates, and limit to one week ahead.
163
162
 
164
163
  ```tsx
165
164
  <Calendar shouldDisableDate={date => [0, 6].includes(date.getDay()) || date.getTime() >= new Date().getTime() + 7 * 24 * 60 * 60 * 1000} disablePast />
166
165
  ```
167
166
 
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
+
168
186
  ## Common Use Cases
169
187
 
170
188
  ### Embedded Calendar Widget
171
189
 
172
190
  ```tsx
173
- import { Calendar } from '@ceed/ads';
174
- import { Box, Typography } from '@ceed/ads';
175
-
176
191
  function CalendarWidget() {
177
192
  const [selectedDate, setSelectedDate] = useState<Date>(new Date());
178
193
 
179
194
  return (
180
195
  <Box sx={{ p: 2, border: '1px solid', borderColor: 'divider', borderRadius: 'md' }}>
181
- <Typography level="title-md" sx={{ mb: 2 }}>
196
+ <Typography level="title-md" mb={2}>
182
197
  Select a Date
183
198
  </Typography>
184
199
  <Calendar
185
200
  value={[selectedDate, undefined]}
186
201
  onChange={(dates) => dates[0] && setSelectedDate(dates[0])}
187
202
  />
188
- <Typography level="body-sm" sx={{ mt: 2 }}>
203
+ <Typography level="body-sm" mt={2}>
189
204
  Selected: {selectedDate.toLocaleDateString()}
190
205
  </Typography>
191
206
  </Box>
@@ -193,38 +208,111 @@ function CalendarWidget() {
193
208
  }
194
209
  ```
195
210
 
196
- ### Booking Calendar with Disabled Dates
211
+ ### Event Calendar with Highlighted Dates
197
212
 
198
213
  ```tsx
199
- function BookingCalendar({ blockedDates }) {
200
- const [selectedDate, setSelectedDate] = useState<Date | null>(null);
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]);
201
221
 
202
- const blockedSet = useMemo(
203
- () => new Set(blockedDates.map((d) => d.toDateString())),
204
- [blockedDates]
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>
205
243
  );
244
+ }
245
+ ```
246
+
247
+ ### Appointment Booking Calendar
248
+
249
+ ```tsx
250
+ function BookingCalendar({ availableSlots, onBook }) {
251
+ const [selectedDate, setSelectedDate] = useState<Date | null>(null);
252
+
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]);
206
271
 
207
272
  return (
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
- />
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>
216
300
  );
217
301
  }
218
302
  ```
219
303
 
220
- ### Date Range Selector with Summary
304
+ ### Date Range Selection with Preview
221
305
 
222
306
  ```tsx
223
307
  function DateRangeSelector({ onRangeSelect }) {
224
- const [range, setRange] = useState<[Date | undefined, Date | undefined]>([
225
- undefined,
226
- undefined,
227
- ]);
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
+ };
228
316
 
229
317
  const dayCount = useMemo(() => {
230
318
  if (!range[0] || !range[1]) return 0;
@@ -237,76 +325,349 @@ function DateRangeSelector({ onRangeSelect }) {
237
325
  <Calendar
238
326
  rangeSelection
239
327
  value={range}
240
- onChange={(dates) => {
241
- setRange(dates);
242
- if (dates[0] && dates[1]) onRangeSelect(dates);
243
- }}
328
+ onChange={handleChange}
244
329
  disablePast
245
330
  />
246
- {range[0] && range[1] && (
247
- <Typography level="body-sm">
248
- {range[0].toLocaleDateString()} - {range[1].toLocaleDateString()} ({dayCount} days)
249
- </Typography>
250
- )}
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>
251
348
  </Stack>
252
349
  );
253
350
  }
254
351
  ```
255
352
 
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
+
256
527
  ## Best Practices
257
528
 
258
- 1. **Prefer higher-level picker components for form fields.** Use DatePicker or DateRangePicker unless you need a custom embedded calendar UI.
529
+ ### Do
530
+
531
+ 1. **Use appropriate picker components for forms**: Default to DatePicker/DateRangePicker
259
532
 
260
533
  ```tsx
261
- // ✅ Use DatePicker for standard form input
262
- <DatePicker label="Birth Date" value={date} onChange={handleChange} />
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
+ ```
263
541
 
264
- // Using Calendar directly in a form without context
265
- <form>
266
- <Calendar onChange={handleChange} />
267
- </form>
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>
268
551
  ```
269
552
 
270
- 2. **Handle the array value format correctly.** Calendar always uses `[start, end]` format, even for single date selection.
553
+ 3. **Use appropriate date restrictions**: Prevent invalid selections
271
554
 
272
555
  ```tsx
273
- // ✅ Proper single-date handling
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
+ />
562
+ ```
563
+
564
+ 4. **Handle the array value format correctly**: Calendar always uses `[start, end]` format
565
+
566
+ ```tsx
567
+ // ✅ Good: Proper value handling
568
+ const [date, setDate] = useState<Date | null>(null);
274
569
  <Calendar
275
570
  value={date ? [date, undefined] : undefined}
276
571
  onChange={(dates) => setDate(dates[0] || null)}
277
572
  />
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
+ ```
278
588
 
279
- // Treating value as a single Date
280
- <Calendar value={date} onChange={(d) => setDate(d)} />
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])} />
281
597
  ```
282
598
 
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.
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:
284
616
 
285
617
  ```tsx
286
- // ✅ Memoized disable function
287
618
  const shouldDisableDate = useCallback((date: Date) => {
288
- return [0, 6].includes(date.getDay()) || holidays.has(date.toDateString());
289
- }, [holidays]);
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
290
626
 
291
- // Inline function recreated every render
292
- <Calendar shouldDisableDate={(date) => expensiveCheck(date)} />
627
+ <Calendar shouldDisableDate={shouldDisableDate} />
293
628
  ```
294
629
 
295
- 4. **Always use Date objects, not strings.** Calendar expects native `Date` objects for `value`, `defaultValue`, `minDate`, and `maxDate`.
630
+ ### Avoid Inline Object Creation
631
+
632
+ When using controlled mode, avoid creating new arrays on every render:
296
633
 
297
634
  ```tsx
298
- // Using Date objects
299
- <Calendar value={[new Date('2024-04-15'), undefined]} />
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
300
647
 
301
- // ❌ Using string values
302
- <Calendar value={['2024-04-15', undefined]} />
648
+ ```tsx
649
+ const handleChange = useCallback((dates: [Date | undefined, Date | undefined]) => {
650
+ setSelectedDate(dates[0] || null);
651
+ }, []);
652
+
653
+ <Calendar onChange={handleChange} />
303
654
  ```
304
655
 
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.
656
+ ### Lazy Date Calculations
306
657
 
307
- ## Accessibility
658
+ For complex availability calculations:
659
+
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
+ ```
308
672
 
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.
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.