Skip to content

Commit 6a6e433

Browse files
committed
feat(mobile): add Calendar component and replace native date picker
New Features: - Add standalone Calendar component for date selection - Support min/max date constraints with automatic navigation - Support disabled dates and highlighted date ranges - Comprehensive accessibility with screen reader support Enhancements: - Integrate Calendar into DatePicker with confirmation flow - Add triggerDisabled prop to Tooltip for better a11y around "disabled" triggers - Add calendarStyle, highlightedDates, and seedDate props to DatePicker - Add custom a11y labels for Calendar navigation Dependencies: - Remove react-native-date-picker (no longer needed) Tests: - Add Storybook for Calendar - Add unit tests for Calendar, DatePicker, Tooltip Documentation: - Added mobile docs for Calendar - Updated mobile docs for DatePicker and Tooltip - Removed references to react-native-date-picker in Web docs for DatePicker
1 parent 37da354 commit 6a6e433

File tree

22 files changed

+2356
-133
lines changed

22 files changed

+2356
-133
lines changed
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
### Basic usage
2+
3+
A basic Calendar with date selection functionality. The Calendar component is used within the DatePicker and can also be used independently.
4+
5+
```jsx
6+
function Example() {
7+
const [selectedDate, setSelectedDate] = useState(new Date());
8+
9+
return (
10+
<Calendar
11+
selectedDate={selectedDate}
12+
onPressDate={setSelectedDate}
13+
/>
14+
);
15+
}
16+
```
17+
18+
### No selection
19+
20+
A Calendar without an initially selected date.
21+
22+
```jsx
23+
function Example() {
24+
const [selectedDate, setSelectedDate] = useState(null);
25+
26+
return (
27+
<Calendar
28+
selectedDate={selectedDate}
29+
onPressDate={setSelectedDate}
30+
/>
31+
);
32+
}
33+
```
34+
35+
### Seeding the calendar
36+
37+
The `seedDate` prop controls which month the Calendar opens to when there is no selected date value. Defaults to today when undefined.
38+
39+
```jsx
40+
function Example() {
41+
const [selectedDate, setSelectedDate] = useState(null);
42+
43+
const today = new Date(new Date().setHours(0, 0, 0, 0));
44+
const seedDate = new Date(today.getFullYear(), today.getMonth() + 1, 15);
45+
46+
return (
47+
<Calendar
48+
selectedDate={selectedDate}
49+
onPressDate={setSelectedDate}
50+
seedDate={seedDate}
51+
/>
52+
);
53+
}
54+
```
55+
56+
### Minimum and maximum dates
57+
58+
Use `minDate` and `maxDate` to restrict the selectable date range. Navigation to dates before the `minDate` and after the `maxDate` is disabled. Make sure to provide the `disabledDateError` prop.
59+
60+
```jsx
61+
function Example() {
62+
const [selectedDate, setSelectedDate] = useState(new Date());
63+
64+
const today = new Date(new Date().setHours(0, 0, 0, 0));
65+
const lastMonth15th = new Date(today.getFullYear(), today.getMonth() - 1, 15);
66+
const nextMonth15th = new Date(today.getFullYear(), today.getMonth() + 1, 15);
67+
68+
return (
69+
<Calendar
70+
selectedDate={selectedDate}
71+
onPressDate={setSelectedDate}
72+
minDate={lastMonth15th}
73+
maxDate={nextMonth15th}
74+
disabledDateError="Date is outside allowed range"
75+
/>
76+
);
77+
}
78+
```
79+
80+
### Future dates only
81+
82+
Restrict selection to future dates by setting `minDate` to today.
83+
84+
```jsx
85+
function Example() {
86+
const [selectedDate, setSelectedDate] = useState(null);
87+
88+
const today = new Date(new Date().setHours(0, 0, 0, 0));
89+
90+
return (
91+
<Calendar
92+
selectedDate={selectedDate}
93+
onPressDate={setSelectedDate}
94+
minDate={today}
95+
disabledDateError="Past dates are not available"
96+
/>
97+
);
98+
}
99+
```
100+
101+
### Highlighted dates
102+
103+
Use `highlightedDates` to visually emphasize specific dates or date ranges. A number is created for every individual date within a tuple range, so do not abuse this with massive ranges.
104+
105+
```jsx
106+
function Example() {
107+
const [selectedDate, setSelectedDate] = useState(new Date());
108+
109+
const today = new Date(new Date().setHours(0, 0, 0, 0));
110+
const yesterday = new Date(today.getFullYear(), today.getMonth(), today.getDate() - 1);
111+
const nextWeek = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 7);
112+
113+
return (
114+
<Calendar
115+
selectedDate={selectedDate}
116+
onPressDate={setSelectedDate}
117+
highlightedDates={[yesterday, today, nextWeek]}
118+
/>
119+
);
120+
}
121+
```
122+
123+
### Disabled dates
124+
125+
Use `disabledDates` to prevent selection of specific dates or date ranges. Make sure to provide the `disabledDateError` prop.
126+
127+
```jsx
128+
function Example() {
129+
const [selectedDate, setSelectedDate] = useState(null);
130+
131+
const today = new Date(new Date().setHours(0, 0, 0, 0));
132+
133+
// Disable weekends for demonstration
134+
const getNextWeekendDates = (centerDate) => {
135+
const weekends = [];
136+
const currentDate = new Date(centerDate);
137+
138+
// Find next 4 weekends
139+
for (let i = 0; i < 4; i++) {
140+
// Find next Saturday
141+
const daysUntilSaturday = (6 - currentDate.getDay() + 7) % 7 || 7;
142+
currentDate.setDate(currentDate.getDate() + daysUntilSaturday);
143+
144+
const saturday = new Date(currentDate);
145+
const sunday = new Date(currentDate);
146+
sunday.setDate(sunday.getDate() + 1);
147+
148+
weekends.push([saturday, sunday]);
149+
150+
// Move to next week
151+
currentDate.setDate(currentDate.getDate() + 7);
152+
}
153+
154+
return weekends;
155+
};
156+
157+
return (
158+
<Calendar
159+
selectedDate={selectedDate}
160+
onPressDate={setSelectedDate}
161+
disabledDates={getNextWeekendDates(today)}
162+
disabledDateError="Weekends are not available"
163+
/>
164+
);
165+
}
166+
```
167+
168+
### Date ranges
169+
170+
Highlight a date range using a tuple `[startDate, endDate]`.
171+
172+
```jsx
173+
function Example() {
174+
const [selectedDate, setSelectedDate] = useState(new Date());
175+
176+
const today = new Date(new Date().setHours(0, 0, 0, 0));
177+
const yesterday = new Date(today.getFullYear(), today.getMonth(), today.getDate() - 1);
178+
const nextWeek = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 7);
179+
180+
return (
181+
<Calendar
182+
selectedDate={selectedDate}
183+
onPressDate={setSelectedDate}
184+
highlightedDates={[[yesterday, nextWeek]]}
185+
/>
186+
);
187+
}
188+
```
189+
190+
### Hidden controls
191+
192+
Hide the navigation arrows with `hideControls`. This is typically used when `minDate` and `maxDate` are set to the first and last days of the same month.
193+
194+
```jsx
195+
function Example() {
196+
const [selectedDate, setSelectedDate] = useState(new Date());
197+
198+
const today = new Date(new Date().setHours(0, 0, 0, 0));
199+
const firstDayOfMonth = new Date(today.getFullYear(), today.getMonth(), 1);
200+
const lastDayOfMonth = new Date(today.getFullYear(), today.getMonth() + 1, 0);
201+
202+
return (
203+
<Calendar
204+
selectedDate={selectedDate}
205+
onPressDate={setSelectedDate}
206+
minDate={firstDayOfMonth}
207+
maxDate={lastDayOfMonth}
208+
hideControls
209+
/>
210+
);
211+
}
212+
```
213+
214+
### Disabled
215+
216+
Disable the entire Calendar with the `disabled` prop.
217+
218+
```jsx
219+
function Example() {
220+
const selectedDate = new Date();
221+
222+
return (
223+
<Calendar
224+
selectedDate={selectedDate}
225+
disabled
226+
/>
227+
);
228+
}
229+
```
230+
231+
### Accessibility
232+
233+
Always provide accessibility labels for the navigation controls and error messages for disabled dates.
234+
235+
```jsx
236+
function Example() {
237+
const [selectedDate, setSelectedDate] = useState(new Date());
238+
239+
const today = new Date(new Date().setHours(0, 0, 0, 0));
240+
const nextMonth = new Date(today.getFullYear(), today.getMonth() + 1, today.getDate());
241+
242+
return (
243+
<Calendar
244+
selectedDate={selectedDate}
245+
onPressDate={setSelectedDate}
246+
maxDate={nextMonth}
247+
disabledDateError="Date is not available for selection"
248+
nextArrowAccessibilityLabel="Go to next month"
249+
previousArrowAccessibilityLabel="Go to previous month"
250+
/>
251+
);
252+
}
253+
```
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import ComponentPropsTable from '@site/src/components/page/ComponentPropsTable';
2+
3+
import mobilePropsData from ':docgen/mobile/dates/Calendar/data';
4+
import { sharedParentTypes } from ':docgen/_types/sharedParentTypes';
5+
import { sharedTypeAliases } from ':docgen/_types/sharedTypeAliases';
6+
7+
<ComponentPropsTable
8+
props={mobilePropsData}
9+
sharedTypeAliases={sharedTypeAliases}
10+
sharedParentTypes={sharedParentTypes}
11+
/>
Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
id: calendar
33
title: Calendar
4-
platform_switcher_options: { web: true, mobile: false }
4+
platform_switcher_options: { web: true, mobile: true }
55
hide_title: true
66
---
77

@@ -10,16 +10,26 @@ import { ComponentHeader } from '@site/src/components/page/ComponentHeader';
1010
import { ComponentTabsContainer } from '@site/src/components/page/ComponentTabsContainer';
1111

1212
import webPropsToc from ':docgen/web/dates/Calendar/toc-props';
13+
import mobilePropsToc from ':docgen/mobile/dates/Calendar/toc-props';
14+
1315
import WebPropsTable from './_webPropsTable.mdx';
16+
import MobilePropsTable from './_mobilePropsTable.mdx';
17+
import MobileExamples, { toc as mobileExamplesToc } from './_mobileExamples.mdx';
1418
import WebExamples, { toc as webExamplesToc } from './_webExamples.mdx';
19+
1520
import webMetadata from './webMetadata.json';
21+
import mobileMetadata from './mobileMetadata.json';
1622

1723
<VStack gap={5}>
18-
<ComponentHeader title="Calendar" webMetadata={webMetadata} />
24+
<ComponentHeader title="Calendar" webMetadata={webMetadata} mobileMetadata={mobileMetadata} />
1925
<ComponentTabsContainer
2026
webPropsTable={<WebPropsTable />}
2127
webExamples={<WebExamples />}
28+
mobilePropsTable={<MobilePropsTable />}
29+
mobileExamples={<MobileExamples />}
2230
webExamplesToc={webExamplesToc}
31+
mobileExamplesToc={mobileExamplesToc}
2332
webPropsToc={webPropsToc}
33+
mobilePropsToc={mobilePropsToc}
2434
/>
2535
</VStack>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"import": "import { Calendar } from '@coinbase/cds-mobile/dates/Calendar'",
3+
"source": "https://github.com/coinbase/cds/blob/master/packages/mobile/src/dates/Calendar.tsx",
4+
"description": "Calendar is a flexible, accessible date grid component for selecting dates, supporting keyboard navigation, disabled/highlighted dates, and custom rendering.",
5+
"relatedComponents": [
6+
{
7+
"label": "DatePicker",
8+
"url": "/components/other/DatePicker/"
9+
}
10+
]
11+
}

0 commit comments

Comments
 (0)