@ceed/ads 1.23.3 → 1.23.5
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/data-display/Badge.md +71 -39
- package/dist/components/data-display/InfoSign.md +74 -98
- package/dist/components/data-display/Typography.md +310 -61
- package/dist/components/feedback/CircularProgress.md +257 -0
- package/dist/components/feedback/Skeleton.md +280 -0
- package/dist/components/feedback/llms.txt +2 -0
- package/dist/components/inputs/ButtonGroup.md +115 -106
- package/dist/components/inputs/Calendar.md +98 -459
- package/dist/components/inputs/CurrencyInput.md +181 -8
- package/dist/components/inputs/DatePicker.md +108 -436
- package/dist/components/inputs/DateRangePicker.md +130 -496
- package/dist/components/inputs/FilterMenu.md +169 -19
- package/dist/components/inputs/FilterableCheckboxGroup.md +119 -24
- package/dist/components/inputs/FormControl.md +361 -0
- package/dist/components/inputs/IconButton.md +137 -88
- package/dist/components/inputs/MonthPicker.md +95 -427
- package/dist/components/inputs/MonthRangePicker.md +89 -471
- package/dist/components/inputs/PercentageInput.md +183 -19
- package/dist/components/inputs/RadioButton.md +163 -35
- package/dist/components/inputs/RadioList.md +241 -0
- package/dist/components/inputs/RadioTileGroup.md +146 -62
- package/dist/components/inputs/Select.md +219 -328
- package/dist/components/inputs/Slider.md +334 -0
- package/dist/components/inputs/Switch.md +136 -376
- package/dist/components/inputs/Textarea.md +209 -11
- package/dist/components/inputs/Uploader/Uploader.md +145 -66
- package/dist/components/inputs/llms.txt +3 -0
- package/dist/components/navigation/Breadcrumbs.md +80 -322
- package/dist/components/navigation/Dropdown.md +92 -221
- package/dist/components/navigation/IconMenuButton.md +40 -502
- package/dist/components/navigation/InsetDrawer.md +68 -738
- package/dist/components/navigation/Link.md +39 -298
- package/dist/components/navigation/Menu.md +92 -285
- package/dist/components/navigation/MenuButton.md +55 -448
- package/dist/components/navigation/Pagination.md +47 -338
- package/dist/components/navigation/ProfileMenu.md +45 -268
- package/dist/components/navigation/Stepper.md +160 -28
- package/dist/components/navigation/Tabs.md +57 -316
- package/dist/components/surfaces/Sheet.md +150 -333
- package/dist/guides/ThemeProvider.md +116 -0
- package/dist/guides/llms.txt +9 -0
- package/dist/llms.txt +8 -0
- 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
|
|
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
|
|
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
|
-
##
|
|
43
|
-
|
|
44
|
-
### Controlled
|
|
41
|
+
## Controlled
|
|
45
42
|
|
|
46
|
-
|
|
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
|
-
|
|
49
|
+
## Uncontrolled
|
|
53
50
|
|
|
54
|
-
Calendar manages its own state
|
|
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
|
-
|
|
57
|
+
## Locale Support
|
|
61
58
|
|
|
62
|
-
Display
|
|
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
|
-
|
|
65
|
+
## Range Selection
|
|
69
66
|
|
|
70
|
-
Enable
|
|
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
|
-
|
|
73
|
+
## Default Value with Range Selection
|
|
77
74
|
|
|
78
|
-
Pre-populate a date range when the calendar
|
|
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
|
-
|
|
81
|
+
## View Modes
|
|
82
|
+
|
|
83
|
+
### Month View Only
|
|
85
84
|
|
|
86
|
-
Show only the month grid
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
152
|
+
### Weekends and Past Dates Disabled
|
|
152
153
|
|
|
153
|
-
Combine
|
|
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"
|
|
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"
|
|
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
|
-
###
|
|
196
|
+
### Booking Calendar with Disabled Dates
|
|
212
197
|
|
|
213
198
|
```tsx
|
|
214
|
-
function
|
|
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
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
<
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
|
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]>([
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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={
|
|
240
|
+
onChange={(dates) => {
|
|
241
|
+
setRange(dates);
|
|
242
|
+
if (dates[0] && dates[1]) onRangeSelect(dates);
|
|
243
|
+
}}
|
|
329
244
|
disablePast
|
|
330
245
|
/>
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
-
|
|
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
|
-
// ✅
|
|
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
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
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
|
-
|
|
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
|
-
// ✅
|
|
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
|
-
|
|
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. **
|
|
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
|
-
|
|
620
|
-
|
|
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
|
-
|
|
291
|
+
// ❌ Inline function recreated every render
|
|
292
|
+
<Calendar shouldDisableDate={(date) => expensiveCheck(date)} />
|
|
628
293
|
```
|
|
629
294
|
|
|
630
|
-
|
|
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
|
-
//
|
|
636
|
-
<Calendar value={[
|
|
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
|
-
|
|
649
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|