@ceed/ads 1.29.1 → 1.30.0-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/CurrencyInput/CurrencyInput.d.ts +1 -1
- package/dist/components/CurrencyInput/hooks/use-currency-setting.d.ts +2 -2
- package/dist/components/ProfileMenu/ProfileMenu.d.ts +1 -1
- package/dist/components/SearchBar/SearchBar.d.ts +21 -0
- package/dist/components/SearchBar/index.d.ts +3 -0
- package/dist/components/data-display/Badge.md +39 -71
- package/dist/components/data-display/DataTable.md +1 -1
- package/dist/components/data-display/InfoSign.md +98 -74
- package/dist/components/data-display/Typography.md +97 -363
- package/dist/components/feedback/Dialog.md +62 -76
- package/dist/components/feedback/Modal.md +44 -259
- package/dist/components/feedback/llms.txt +0 -2
- package/dist/components/index.d.ts +2 -0
- package/dist/components/inputs/Autocomplete.md +107 -356
- package/dist/components/inputs/ButtonGroup.md +106 -115
- package/dist/components/inputs/Calendar.md +459 -98
- package/dist/components/inputs/CurrencyInput.md +5 -183
- package/dist/components/inputs/DatePicker.md +431 -108
- package/dist/components/inputs/DateRangePicker.md +492 -131
- package/dist/components/inputs/FilterMenu.md +19 -169
- package/dist/components/inputs/FilterableCheckboxGroup.md +23 -123
- package/dist/components/inputs/IconButton.md +88 -137
- package/dist/components/inputs/Input.md +0 -5
- package/dist/components/inputs/MonthPicker.md +422 -95
- package/dist/components/inputs/MonthRangePicker.md +466 -89
- package/dist/components/inputs/PercentageInput.md +16 -185
- package/dist/components/inputs/RadioButton.md +35 -163
- package/dist/components/inputs/RadioTileGroup.md +61 -150
- package/dist/components/inputs/SearchBar.md +44 -0
- package/dist/components/inputs/Select.md +326 -222
- package/dist/components/inputs/Switch.md +376 -136
- package/dist/components/inputs/Textarea.md +10 -213
- package/dist/components/inputs/Uploader/Uploader.md +66 -145
- package/dist/components/inputs/llms.txt +1 -3
- package/dist/components/navigation/Breadcrumbs.md +322 -80
- package/dist/components/navigation/Dropdown.md +221 -92
- package/dist/components/navigation/IconMenuButton.md +502 -40
- package/dist/components/navigation/InsetDrawer.md +738 -68
- package/dist/components/navigation/Link.md +298 -39
- package/dist/components/navigation/Menu.md +285 -92
- package/dist/components/navigation/MenuButton.md +448 -55
- package/dist/components/navigation/Pagination.md +338 -47
- package/dist/components/navigation/ProfileMenu.md +268 -45
- package/dist/components/navigation/Stepper.md +28 -160
- package/dist/components/navigation/Tabs.md +316 -57
- package/dist/components/surfaces/Sheet.md +334 -151
- package/dist/index.browser.js +15 -13
- package/dist/index.browser.js.map +4 -4
- package/dist/index.cjs +289 -288
- package/dist/index.d.ts +1 -1
- package/dist/index.js +426 -369
- package/dist/llms.txt +1 -8
- package/framer/index.js +1 -1
- package/package.json +16 -15
- package/dist/chunks/rehype-accent-FZRUD7VI.js +0 -39
- package/dist/components/feedback/CircularProgress.md +0 -257
- package/dist/components/feedback/Skeleton.md +0 -280
- package/dist/components/inputs/FormControl.md +0 -361
- package/dist/components/inputs/RadioList.md +0 -241
- package/dist/components/inputs/Slider.md +0 -334
- package/dist/guides/ThemeProvider.md +0 -116
- package/dist/guides/llms.txt +0 -9
|
@@ -2,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
|
|
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
|
|
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
|
-
##
|
|
42
|
+
## Examples
|
|
43
|
+
|
|
44
|
+
### Controlled
|
|
42
45
|
|
|
43
|
-
|
|
46
|
+
Parent component manages the calendar state.
|
|
44
47
|
|
|
45
48
|
```tsx
|
|
46
49
|
<Calendar />
|
|
47
50
|
```
|
|
48
51
|
|
|
49
|
-
|
|
52
|
+
### Uncontrolled
|
|
50
53
|
|
|
51
|
-
|
|
54
|
+
Calendar manages its own state internally.
|
|
52
55
|
|
|
53
56
|
```tsx
|
|
54
57
|
<Calendar />
|
|
55
58
|
```
|
|
56
59
|
|
|
57
|
-
|
|
60
|
+
### With Locale
|
|
58
61
|
|
|
59
|
-
Display
|
|
62
|
+
Display calendar in different languages.
|
|
60
63
|
|
|
61
64
|
```tsx
|
|
62
65
|
<Calendar locale="de" />
|
|
63
66
|
```
|
|
64
67
|
|
|
65
|
-
|
|
68
|
+
### Range Selection
|
|
66
69
|
|
|
67
|
-
Enable
|
|
70
|
+
Enable selection of date ranges (start and end dates).
|
|
68
71
|
|
|
69
72
|
```tsx
|
|
70
73
|
<Calendar rangeSelection />
|
|
71
74
|
```
|
|
72
75
|
|
|
73
|
-
|
|
76
|
+
### Default Value with Range Selection
|
|
74
77
|
|
|
75
|
-
Pre-populate a date range when the calendar
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
### Month View Only
|
|
84
|
+
### Only Month View
|
|
84
85
|
|
|
85
|
-
Show only the month grid
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
151
|
+
### Weekends Disabled with Past Dates
|
|
153
152
|
|
|
154
|
-
Combine
|
|
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
|
|
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"
|
|
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"
|
|
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
|
-
###
|
|
211
|
+
### Event Calendar with Highlighted Dates
|
|
197
212
|
|
|
198
213
|
```tsx
|
|
199
|
-
function
|
|
200
|
-
const [
|
|
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
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
<
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
|
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
|
-
|
|
226
|
-
|
|
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={
|
|
241
|
-
setRange(dates);
|
|
242
|
-
if (dates[0] && dates[1]) onRangeSelect(dates);
|
|
243
|
-
}}
|
|
328
|
+
onChange={handleChange}
|
|
244
329
|
disablePast
|
|
245
330
|
/>
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
-
|
|
529
|
+
### ✅ Do
|
|
530
|
+
|
|
531
|
+
1. **Use appropriate picker components for forms**: Default to DatePicker/DateRangePicker
|
|
259
532
|
|
|
260
533
|
```tsx
|
|
261
|
-
// ✅ Use DatePicker for
|
|
262
|
-
<DatePicker
|
|
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
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
-
|
|
553
|
+
3. **Use appropriate date restrictions**: Prevent invalid selections
|
|
271
554
|
|
|
272
555
|
```tsx
|
|
273
|
-
// ✅
|
|
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
|
-
|
|
280
|
-
|
|
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. **
|
|
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
|
-
|
|
289
|
-
|
|
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
|
-
|
|
292
|
-
<Calendar shouldDisableDate={(date) => expensiveCheck(date)} />
|
|
627
|
+
<Calendar shouldDisableDate={shouldDisableDate} />
|
|
293
628
|
```
|
|
294
629
|
|
|
295
|
-
|
|
630
|
+
### Avoid Inline Object Creation
|
|
631
|
+
|
|
632
|
+
When using controlled mode, avoid creating new arrays on every render:
|
|
296
633
|
|
|
297
634
|
```tsx
|
|
298
|
-
//
|
|
299
|
-
<Calendar value={[
|
|
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
|
-
|
|
302
|
-
|
|
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
|
-
|
|
656
|
+
### Lazy Date Calculations
|
|
306
657
|
|
|
307
|
-
|
|
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
|
-
|
|
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.
|