@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.
Files changed (30) hide show
  1. package/dist/components/ProfileMenu/ProfileMenu.d.ts +1 -1
  2. package/dist/components/data-display/Markdown.md +832 -0
  3. package/dist/components/feedback/Dialog.md +605 -3
  4. package/dist/components/feedback/Modal.md +656 -24
  5. package/dist/components/feedback/llms.txt +1 -1
  6. package/dist/components/inputs/Autocomplete.md +734 -2
  7. package/dist/components/inputs/Calendar.md +655 -1
  8. package/dist/components/inputs/DatePicker.md +699 -3
  9. package/dist/components/inputs/DateRangePicker.md +815 -1
  10. package/dist/components/inputs/MonthPicker.md +626 -4
  11. package/dist/components/inputs/MonthRangePicker.md +682 -4
  12. package/dist/components/inputs/Select.md +600 -0
  13. package/dist/components/layout/Container.md +507 -0
  14. package/dist/components/navigation/Breadcrumbs.md +582 -0
  15. package/dist/components/navigation/IconMenuButton.md +693 -0
  16. package/dist/components/navigation/InsetDrawer.md +1150 -3
  17. package/dist/components/navigation/Link.md +526 -0
  18. package/dist/components/navigation/MenuButton.md +632 -0
  19. package/dist/components/navigation/NavigationGroup.md +401 -1
  20. package/dist/components/navigation/NavigationItem.md +311 -0
  21. package/dist/components/navigation/Navigator.md +373 -0
  22. package/dist/components/navigation/Pagination.md +521 -0
  23. package/dist/components/navigation/ProfileMenu.md +605 -0
  24. package/dist/components/navigation/Tabs.md +609 -7
  25. package/dist/components/surfaces/Accordions.md +947 -3
  26. package/dist/index.cjs +3 -1
  27. package/dist/index.js +3 -1
  28. package/dist/llms.txt +1 -1
  29. package/framer/index.js +1 -1
  30. package/package.json +3 -2
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Introduction
4
4
 
5
- > **참고**: 해당 컴포넌트를 직접적으로 사용하는 일은 거의 없습니다. 날짜 선택과 관련된 컴포넌트가 필요하면 DatePicker, DateRangePicker, MonthPicker 같은 컴포넌트를 확인해보세요.
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.
6
6
 
7
7
  ```tsx
8
8
  <Calendar />
@@ -12,8 +12,662 @@
12
12
  | ------ | ----------- | ------- |
13
13
  | locale | — | — |
14
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
+
15
25
  ## Usage
16
26
 
17
27
  ```tsx
18
28
  import { Calendar } from '@ceed/ads';
29
+
30
+ function DateViewer() {
31
+ const [date, setDate] = useState<Date | null>(null);
32
+
33
+ return (
34
+ <Calendar
35
+ value={date ? [date, undefined] : undefined}
36
+ onChange={(dates) => setDate(dates[0] || null)}
37
+ />
38
+ );
39
+ }
40
+ ```
41
+
42
+ ## Examples
43
+
44
+ ### Controlled
45
+
46
+ Parent component manages the calendar state.
47
+
48
+ ```tsx
49
+ <Calendar />
50
+ ```
51
+
52
+ ### Uncontrolled
53
+
54
+ Calendar manages its own state internally.
55
+
56
+ ```tsx
57
+ <Calendar />
58
+ ```
59
+
60
+ ### With Locale
61
+
62
+ Display calendar in different languages.
63
+
64
+ ```tsx
65
+ <Calendar locale="de" />
66
+ ```
67
+
68
+ ### Range Selection
69
+
70
+ Enable selection of date ranges (start and end dates).
71
+
72
+ ```tsx
73
+ <Calendar rangeSelection />
74
+ ```
75
+
76
+ ### Default Value with Range Selection
77
+
78
+ Pre-populate a date range when the calendar loads.
79
+
80
+ ```tsx
81
+ <Calendar rangeSelection defaultValue={[new Date(2024, 2, 1), new Date(2024, 2, 20)]} />
82
+ ```
83
+
84
+ ### Only Month View
85
+
86
+ Show only the month grid without day selection.
87
+
88
+ ```tsx
89
+ <Calendar
90
+ views={['month']}
91
+ defaultValue={[new Date(2024, 2, 1), undefined]}
92
+ />
93
+ ```
94
+
95
+ ### All Views
96
+
97
+ Show both month and day views with navigation between them.
98
+
99
+ ```tsx
100
+ <Calendar views={['month', 'day']} rangeSelection defaultValue={[new Date(2024, 2, 1), new Date(2024, 2, 20)]} />
101
+ ```
102
+
103
+ ### Month Selection
104
+
105
+ Use the calendar for month-level selection only.
106
+
107
+ ```tsx
108
+ <Calendar view="month" value={[value, undefined]} onMonthChange={setValue} />
109
+ ```
110
+
111
+ ### Minimum Date
112
+
113
+ Restrict selection to dates on or after a minimum date.
114
+
115
+ ```tsx
116
+ <Calendar minDate={new Date('2024-02-15')} rangeSelection />
117
+ ```
118
+
119
+ ### Maximum Date
120
+
121
+ Restrict selection to dates on or before a maximum date.
122
+
123
+ ```tsx
124
+ <Calendar maxDate={new Date('2024-02-15')} rangeSelection />
125
+ ```
126
+
127
+ ### Disable Future Dates
128
+
129
+ Prevent selection of any future dates.
130
+
131
+ ```tsx
132
+ <Calendar disableFuture rangeSelection />
133
+ ```
134
+
135
+ ### Disable Past Dates
136
+
137
+ Prevent selection of any past dates.
138
+
139
+ ```tsx
140
+ <Calendar disablePast rangeSelection />
141
+ ```
142
+
143
+ ### Custom Date Disabling
144
+
145
+ Use `shouldDisableDate` to disable specific dates like weekends.
146
+
147
+ ```tsx
148
+ <Calendar shouldDisableDate={date => [0, 6].includes(date.getDay())} />
149
+ ```
150
+
151
+ ### Weekends Disabled with Past Dates
152
+
153
+ Combine multiple restrictions: disable weekends and past dates.
154
+
155
+ ```tsx
156
+ <Calendar shouldDisableDate={date => [0, 6].includes(date.getDay())} disablePast />
157
+ ```
158
+
159
+ ### Complex Date Restrictions
160
+
161
+ Disable weekends, past dates, and limit to one week ahead.
162
+
163
+ ```tsx
164
+ <Calendar shouldDisableDate={date => [0, 6].includes(date.getDay()) || date.getTime() >= new Date().getTime() + 7 * 24 * 60 * 60 * 1000} disablePast />
165
+ ```
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
+
186
+ ## Common Use Cases
187
+
188
+ ### Embedded Calendar Widget
189
+
190
+ ```tsx
191
+ function CalendarWidget() {
192
+ const [selectedDate, setSelectedDate] = useState<Date>(new Date());
193
+
194
+ return (
195
+ <Box sx={{ p: 2, border: '1px solid', borderColor: 'divider', borderRadius: 'md' }}>
196
+ <Typography level="title-md" mb={2}>
197
+ Select a Date
198
+ </Typography>
199
+ <Calendar
200
+ value={[selectedDate, undefined]}
201
+ onChange={(dates) => dates[0] && setSelectedDate(dates[0])}
202
+ />
203
+ <Typography level="body-sm" mt={2}>
204
+ Selected: {selectedDate.toLocaleDateString()}
205
+ </Typography>
206
+ </Box>
207
+ );
208
+ }
209
+ ```
210
+
211
+ ### Event Calendar with Highlighted Dates
212
+
213
+ ```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 }) {
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]);
271
+
272
+ 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>
300
+ );
301
+ }
302
+ ```
303
+
304
+ ### Date Range Selection with Preview
305
+
306
+ ```tsx
307
+ 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
+ };
316
+
317
+ const dayCount = useMemo(() => {
318
+ if (!range[0] || !range[1]) return 0;
319
+ const diff = range[1].getTime() - range[0].getTime();
320
+ return Math.ceil(diff / (1000 * 60 * 60 * 24)) + 1;
321
+ }, [range]);
322
+
323
+ return (
324
+ <Stack gap={2}>
325
+ <Calendar
326
+ rangeSelection
327
+ value={range}
328
+ onChange={handleChange}
329
+ disablePast
330
+ />
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>
348
+ </Stack>
349
+ );
350
+ }
351
+ ```
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
+
527
+ ## Best Practices
528
+
529
+ ### ✅ Do
530
+
531
+ 1. **Use appropriate picker components for forms**: Default to DatePicker/DateRangePicker
532
+
533
+ ```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
+ />
19
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
+ ```
552
+
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
+ />
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);
569
+ <Calendar
570
+ value={date ? [date, undefined] : undefined}
571
+ onChange={(dates) => setDate(dates[0] || null)}
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
+ ```
588
+
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])} />
597
+ ```
598
+
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:
616
+
617
+ ```tsx
618
+ 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
626
+
627
+ <Calendar shouldDisableDate={shouldDisableDate} />
628
+ ```
629
+
630
+ ### Avoid Inline Object Creation
631
+
632
+ When using controlled mode, avoid creating new arrays on every render:
633
+
634
+ ```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
647
+
648
+ ```tsx
649
+ const handleChange = useCallback((dates: [Date | undefined, Date | undefined]) => {
650
+ setSelectedDate(dates[0] || null);
651
+ }, []);
652
+
653
+ <Calendar onChange={handleChange} />
654
+ ```
655
+
656
+ ### Lazy Date Calculations
657
+
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
+ ```
672
+
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.