@acusti/date-picker 0.12.0 → 0.14.0
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/README.md +625 -27
- package/dist/DatePicker.d.ts +2 -2
- package/dist/MonthCalendar.d.ts +1 -1
- package/dist/index.js +249 -253
- package/dist/index.js.map +1 -1
- package/dist/styles/date-picker.d.ts +1 -1
- package/dist/styles/month-calendar.d.ts +1 -1
- package/package.json +11 -11
package/README.md
CHANGED
|
@@ -5,70 +5,668 @@
|
|
|
5
5
|
[](https://bundlejs.com/?q=%40acusti%2Fdate-picker)
|
|
6
6
|
[](https://socket.dev/npm/package/@acusti/date-picker/overview/0.8.0)
|
|
7
7
|
|
|
8
|
-
A
|
|
9
|
-
|
|
8
|
+
A comprehensive React date picker library with support for single date
|
|
9
|
+
selection, date ranges, and two-up month calendar views. Built with
|
|
10
|
+
accessibility and user experience in mind, featuring smooth navigation,
|
|
11
|
+
intelligent date range handling, and customizable styling.
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
## Key Features
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
- **Single & Range Selection** - Pick individual dates or date ranges with
|
|
16
|
+
intelligent range logic
|
|
17
|
+
- **Two-Up Calendar View** - Display two months side-by-side for easier
|
|
18
|
+
range selection
|
|
19
|
+
- **Month Navigation** - Smooth navigation with optional month limits for
|
|
20
|
+
business logic
|
|
21
|
+
- **Smart Range Handling** - Automatic date swapping and preview
|
|
22
|
+
highlighting
|
|
23
|
+
- **Flexible Date Input** - Accepts Date objects, ISO strings, or
|
|
24
|
+
timestamps
|
|
25
|
+
- **Customizable Display** - Month abbreviations, custom styling, and
|
|
26
|
+
layout options
|
|
27
|
+
- **Built-in Styling** - Attractive default styles with CSS custom property
|
|
28
|
+
theming
|
|
29
|
+
- **Accessibility Ready** - Keyboard navigation and screen reader support
|
|
15
30
|
|
|
16
|
-
##
|
|
31
|
+
## Installation
|
|
17
32
|
|
|
18
|
-
```
|
|
33
|
+
```bash
|
|
19
34
|
npm install @acusti/date-picker
|
|
20
35
|
# or
|
|
21
36
|
yarn add @acusti/date-picker
|
|
22
37
|
```
|
|
23
38
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
To render a two-up date picker for selecting date ranges, handling date
|
|
27
|
-
selections via the `onChange` prop and showing months using abbreviations:
|
|
39
|
+
## Quick Start
|
|
28
40
|
|
|
29
41
|
```tsx
|
|
30
42
|
import { DatePicker } from '@acusti/date-picker';
|
|
31
43
|
import { useState } from 'react';
|
|
32
44
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
45
|
+
// Simple single date picker
|
|
46
|
+
function SimpleDatePicker() {
|
|
47
|
+
const [selectedDate, setSelectedDate] = useState('');
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<DatePicker
|
|
51
|
+
onChange={({ dateStart }) => setSelectedDate(dateStart)}
|
|
52
|
+
dateStart={selectedDate}
|
|
53
|
+
/>
|
|
36
54
|
);
|
|
37
|
-
|
|
55
|
+
}
|
|
38
56
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
57
|
+
// Date range picker
|
|
58
|
+
function DateRangePicker() {
|
|
59
|
+
const [dateRange, setDateRange] = useState({ start: '', end: '' });
|
|
60
|
+
|
|
61
|
+
const handleChange = ({ dateStart, dateEnd }) => {
|
|
62
|
+
setDateRange({ start: dateStart, end: dateEnd || '' });
|
|
44
63
|
};
|
|
45
64
|
|
|
46
65
|
return (
|
|
47
66
|
<DatePicker
|
|
48
67
|
isRange
|
|
49
68
|
isTwoUp
|
|
50
|
-
onChange={
|
|
69
|
+
onChange={handleChange}
|
|
70
|
+
dateStart={dateRange.start}
|
|
71
|
+
dateEnd={dateRange.end}
|
|
51
72
|
useMonthAbbreviations
|
|
52
73
|
/>
|
|
53
74
|
);
|
|
54
75
|
}
|
|
55
76
|
```
|
|
56
77
|
|
|
57
|
-
|
|
78
|
+
## API Reference
|
|
58
79
|
|
|
59
|
-
|
|
80
|
+
### DatePicker Component
|
|
60
81
|
|
|
61
|
-
```
|
|
82
|
+
```tsx
|
|
62
83
|
type Props = {
|
|
84
|
+
/** Additional CSS class name for styling */
|
|
63
85
|
className?: string;
|
|
64
|
-
|
|
65
|
-
|
|
86
|
+
|
|
87
|
+
/** End date for range selection (Date object, ISO string, timestamp, or null) */
|
|
88
|
+
dateEnd?: Date | string | number | null;
|
|
89
|
+
|
|
90
|
+
/** Start date for single or range selection (Date object, ISO string, timestamp, or null) */
|
|
91
|
+
dateStart?: Date | string | number | null;
|
|
92
|
+
|
|
93
|
+
/** Initial month to display (number of months since January 1970) */
|
|
66
94
|
initialMonth?: number;
|
|
95
|
+
|
|
96
|
+
/** Enable date range selection mode */
|
|
67
97
|
isRange?: boolean;
|
|
98
|
+
|
|
99
|
+
/** Display two months side-by-side */
|
|
68
100
|
isTwoUp?: boolean;
|
|
101
|
+
|
|
102
|
+
/** Earliest month that can be navigated to */
|
|
69
103
|
monthLimitFirst?: number;
|
|
104
|
+
|
|
105
|
+
/** Latest month that can be navigated to */
|
|
70
106
|
monthLimitLast?: number;
|
|
71
|
-
|
|
107
|
+
|
|
108
|
+
/** Callback when dates are selected */
|
|
109
|
+
onChange: (payload: {
|
|
110
|
+
dateEnd?: string | null;
|
|
111
|
+
dateStart: string;
|
|
112
|
+
}) => void;
|
|
113
|
+
|
|
114
|
+
/** Show end date’s month initially (when both start and end dates exist) */
|
|
115
|
+
showEndInitially?: boolean;
|
|
116
|
+
|
|
117
|
+
/** Use abbreviated month names (Jan, Feb, etc.) */
|
|
72
118
|
useMonthAbbreviations?: boolean;
|
|
73
119
|
};
|
|
74
120
|
```
|
|
121
|
+
|
|
122
|
+
### MonthCalendar Component
|
|
123
|
+
|
|
124
|
+
For advanced use cases, you can use the individual month calendar:
|
|
125
|
+
|
|
126
|
+
```tsx
|
|
127
|
+
import { MonthCalendar } from '@acusti/date-picker';
|
|
128
|
+
|
|
129
|
+
type MonthCalendarProps = {
|
|
130
|
+
className?: string;
|
|
131
|
+
dateEnd?: Date | string | number | null;
|
|
132
|
+
dateEndPreview?: string | null;
|
|
133
|
+
dateStart?: Date | string | number | null;
|
|
134
|
+
isRange?: boolean;
|
|
135
|
+
month: number; // Months since January 1970
|
|
136
|
+
onChange?: (date: string) => void;
|
|
137
|
+
onChangeEndPreview?: (date: string) => void;
|
|
138
|
+
title?: string;
|
|
139
|
+
};
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Utility Functions
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
import {
|
|
146
|
+
getMonthFromDate,
|
|
147
|
+
getYearFromMonth,
|
|
148
|
+
getMonthNameFromMonth,
|
|
149
|
+
getMonthAbbreviationFromMonth,
|
|
150
|
+
} from '@acusti/date-picker';
|
|
151
|
+
|
|
152
|
+
// Convert Date to month number (months since Jan 1970)
|
|
153
|
+
const monthNumber = getMonthFromDate(new Date());
|
|
154
|
+
|
|
155
|
+
// Convert month number to calendar year
|
|
156
|
+
const year = getYearFromMonth(monthNumber);
|
|
157
|
+
|
|
158
|
+
// Get full month name
|
|
159
|
+
const monthName = getMonthNameFromMonth(monthNumber); // "January"
|
|
160
|
+
|
|
161
|
+
// Get abbreviated month name
|
|
162
|
+
const monthAbbr = getMonthAbbreviationFromMonth(monthNumber); // "Jan"
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Usage Examples
|
|
166
|
+
|
|
167
|
+
### Booking System Date Range
|
|
168
|
+
|
|
169
|
+
**[🎮 Live Demo](https://uikit.acusti.ca/?path=/story/uikit-controls-datepicker-datepicker--booking-system-date-range)**
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
import { DatePicker } from '@acusti/date-picker';
|
|
173
|
+
import { useState } from 'react';
|
|
174
|
+
|
|
175
|
+
function BookingDatePicker() {
|
|
176
|
+
const [checkIn, setCheckIn] = useState('');
|
|
177
|
+
const [checkOut, setCheckOut] = useState('');
|
|
178
|
+
const isValid = checkIn && checkOut;
|
|
179
|
+
|
|
180
|
+
// Limit to next 12 months only
|
|
181
|
+
const today = new Date();
|
|
182
|
+
const monthLimitFirst = getMonthFromDate(today);
|
|
183
|
+
const monthLimitLast = monthLimitFirst + 12;
|
|
184
|
+
|
|
185
|
+
return (
|
|
186
|
+
<div className="booking-date-picker">
|
|
187
|
+
<h3>Select Your Stay</h3>
|
|
188
|
+
<DatePicker
|
|
189
|
+
className="booking-date-picker-story"
|
|
190
|
+
isRange
|
|
191
|
+
isTwoUp
|
|
192
|
+
monthLimitFirst={monthLimitFirst}
|
|
193
|
+
monthLimitLast={monthLimitLast}
|
|
194
|
+
onChange={({ dateStart, dateEnd }) => {
|
|
195
|
+
setCheckIn(dateStart);
|
|
196
|
+
setCheckOut(dateEnd ?? '');
|
|
197
|
+
}}
|
|
198
|
+
dateStart={checkIn}
|
|
199
|
+
dateEnd={checkOut}
|
|
200
|
+
useMonthAbbreviations
|
|
201
|
+
/>
|
|
202
|
+
|
|
203
|
+
{isValid ? (
|
|
204
|
+
<div
|
|
205
|
+
className="booking-summary"
|
|
206
|
+
style={{
|
|
207
|
+
marginTop: '16px',
|
|
208
|
+
padding: '16px',
|
|
209
|
+
border: '1px solid #e1e5e9',
|
|
210
|
+
borderRadius: '8px',
|
|
211
|
+
backgroundColor: '#f8f9fa',
|
|
212
|
+
}}
|
|
213
|
+
>
|
|
214
|
+
<p>
|
|
215
|
+
<strong>Check-in:</strong>{' '}
|
|
216
|
+
{new Date(checkIn).toLocaleDateString()}
|
|
217
|
+
</p>
|
|
218
|
+
<p>
|
|
219
|
+
<strong>Check-out:</strong>{' '}
|
|
220
|
+
{new Date(checkOut).toLocaleDateString()}
|
|
221
|
+
</p>
|
|
222
|
+
<p>
|
|
223
|
+
<strong>Duration:</strong>{' '}
|
|
224
|
+
{(() => {
|
|
225
|
+
const checkInTime = new Date(
|
|
226
|
+
checkIn,
|
|
227
|
+
).getTime();
|
|
228
|
+
const checkOutTime = new Date(
|
|
229
|
+
checkOut,
|
|
230
|
+
).getTime();
|
|
231
|
+
const diffTime = checkOutTime - checkInTime;
|
|
232
|
+
const diffDays = Math.ceil(
|
|
233
|
+
diffTime / (1000 * 60 * 60 * 24),
|
|
234
|
+
);
|
|
235
|
+
return Math.max(0, diffDays);
|
|
236
|
+
})()}{' '}
|
|
237
|
+
nights
|
|
238
|
+
</p>
|
|
239
|
+
</div>
|
|
240
|
+
) : null}
|
|
241
|
+
</div>
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Event Scheduler
|
|
247
|
+
|
|
248
|
+
**[🎮 Live Demo](https://uikit.acusti.ca/?path=/story/uikit-controls-datepicker-datepicker--event-scheduler)**
|
|
249
|
+
|
|
250
|
+
```tsx
|
|
251
|
+
import { DatePicker } from '@acusti/date-picker';
|
|
252
|
+
import { useState } from 'react';
|
|
253
|
+
|
|
254
|
+
function EventScheduler() {
|
|
255
|
+
const [eventDate, setEventDate] = useState('');
|
|
256
|
+
const [showPicker, setShowPicker] = useState(false);
|
|
257
|
+
|
|
258
|
+
// Only allow future dates
|
|
259
|
+
const monthLimitFirst = getMonthFromDate(new Date());
|
|
260
|
+
|
|
261
|
+
return (
|
|
262
|
+
<div className="event-scheduler">
|
|
263
|
+
<div style={{ marginBottom: '16px' }}>
|
|
264
|
+
<label
|
|
265
|
+
htmlFor="event-date"
|
|
266
|
+
style={{
|
|
267
|
+
display: 'block',
|
|
268
|
+
marginBottom: '8px',
|
|
269
|
+
fontWeight: 500,
|
|
270
|
+
}}
|
|
271
|
+
>
|
|
272
|
+
Event Date:
|
|
273
|
+
</label>
|
|
274
|
+
<input
|
|
275
|
+
id="event-date"
|
|
276
|
+
type="text"
|
|
277
|
+
value={
|
|
278
|
+
eventDate
|
|
279
|
+
? new Date(eventDate).toLocaleDateString()
|
|
280
|
+
: ''
|
|
281
|
+
}
|
|
282
|
+
onClick={() => setShowPicker(true)}
|
|
283
|
+
placeholder="Click to select date"
|
|
284
|
+
readOnly
|
|
285
|
+
style={{
|
|
286
|
+
padding: '8px 12px',
|
|
287
|
+
border: '2px solid #e1e5e9',
|
|
288
|
+
borderRadius: '6px',
|
|
289
|
+
cursor: 'pointer',
|
|
290
|
+
width: '200px',
|
|
291
|
+
}}
|
|
292
|
+
/>
|
|
293
|
+
</div>
|
|
294
|
+
|
|
295
|
+
{showPicker ? (
|
|
296
|
+
<div
|
|
297
|
+
style={{
|
|
298
|
+
position: 'relative',
|
|
299
|
+
zIndex: 1000,
|
|
300
|
+
marginTop: '8px',
|
|
301
|
+
}}
|
|
302
|
+
>
|
|
303
|
+
<div
|
|
304
|
+
style={{
|
|
305
|
+
padding: '16px',
|
|
306
|
+
border: '1px solid #e1e5e9',
|
|
307
|
+
borderRadius: '8px',
|
|
308
|
+
backgroundColor: 'white',
|
|
309
|
+
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
|
|
310
|
+
}}
|
|
311
|
+
>
|
|
312
|
+
<DatePicker
|
|
313
|
+
className="event-scheduler-story"
|
|
314
|
+
monthLimitFirst={monthLimitFirst}
|
|
315
|
+
onChange={({ dateStart }) => {
|
|
316
|
+
setEventDate(dateStart);
|
|
317
|
+
setShowPicker(false);
|
|
318
|
+
}}
|
|
319
|
+
dateStart={eventDate}
|
|
320
|
+
/>
|
|
321
|
+
<button
|
|
322
|
+
onClick={() => setShowPicker(false)}
|
|
323
|
+
style={{
|
|
324
|
+
marginTop: '12px',
|
|
325
|
+
padding: '8px 16px',
|
|
326
|
+
border: '1px solid #ccc',
|
|
327
|
+
borderRadius: '4px',
|
|
328
|
+
cursor: 'pointer',
|
|
329
|
+
}}
|
|
330
|
+
>
|
|
331
|
+
Cancel
|
|
332
|
+
</button>
|
|
333
|
+
</div>
|
|
334
|
+
</div>
|
|
335
|
+
) : null}
|
|
336
|
+
</div>
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Report Date Range Filter
|
|
342
|
+
|
|
343
|
+
```tsx
|
|
344
|
+
import { DatePicker } from '@acusti/date-picker';
|
|
345
|
+
import { useState, useEffect } from 'react';
|
|
346
|
+
|
|
347
|
+
function ReportFilter() {
|
|
348
|
+
const [dateRange, setDateRange] = useState({
|
|
349
|
+
start: '',
|
|
350
|
+
end: '',
|
|
351
|
+
});
|
|
352
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
353
|
+
|
|
354
|
+
// Limit to past 2 years for historical reports
|
|
355
|
+
const today = new Date();
|
|
356
|
+
const monthLimitLast = getMonthFromDate(today);
|
|
357
|
+
const monthLimitFirst = monthLimitLast - 24;
|
|
358
|
+
|
|
359
|
+
const handleApplyRange = ({ dateStart, dateEnd }) => {
|
|
360
|
+
setDateRange({
|
|
361
|
+
start: dateStart,
|
|
362
|
+
end: dateEnd || dateStart,
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
if (dateEnd) {
|
|
366
|
+
setIsOpen(false);
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
const formatDateRange = () => {
|
|
371
|
+
if (!dateRange.start) return 'Select date range';
|
|
372
|
+
|
|
373
|
+
const startDate = new Date(dateRange.start).toLocaleDateString();
|
|
374
|
+
const endDate = dateRange.end
|
|
375
|
+
? new Date(dateRange.end).toLocaleDateString()
|
|
376
|
+
: startDate;
|
|
377
|
+
|
|
378
|
+
return `${startDate} - ${endDate}`;
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
return (
|
|
382
|
+
<div className="report-filter">
|
|
383
|
+
<button
|
|
384
|
+
className="date-range-button"
|
|
385
|
+
onClick={() => setIsOpen(!isOpen)}
|
|
386
|
+
>
|
|
387
|
+
📅 {formatDateRange()}
|
|
388
|
+
</button>
|
|
389
|
+
|
|
390
|
+
{isOpen ? (
|
|
391
|
+
<div className="date-picker-dropdown">
|
|
392
|
+
<DatePicker
|
|
393
|
+
isRange
|
|
394
|
+
isTwoUp
|
|
395
|
+
monthLimitFirst={monthLimitFirst}
|
|
396
|
+
monthLimitLast={monthLimitLast}
|
|
397
|
+
onChange={handleApplyRange}
|
|
398
|
+
dateStart={dateRange.start}
|
|
399
|
+
dateEnd={dateRange.end}
|
|
400
|
+
showEndInitially={!!dateRange.end}
|
|
401
|
+
/>
|
|
402
|
+
</div>
|
|
403
|
+
) : null}
|
|
404
|
+
</div>
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### Birthday Picker with Year Limits
|
|
410
|
+
|
|
411
|
+
**[🎮 Live Demo](https://uikit.acusti.ca/?path=/story/uikit-controls-datepicker-datepicker--birthday-picker)**
|
|
412
|
+
|
|
413
|
+
```tsx
|
|
414
|
+
import { DatePicker, getMonthFromDate } from '@acusti/date-picker';
|
|
415
|
+
import { useState } from 'react';
|
|
416
|
+
|
|
417
|
+
function BirthdayPicker() {
|
|
418
|
+
const [birthday, setBirthday] = useState('');
|
|
419
|
+
|
|
420
|
+
// Reasonable age limits: 13 to 120 years ago
|
|
421
|
+
const today = new Date();
|
|
422
|
+
const maxAge = new Date(
|
|
423
|
+
today.getFullYear() - 120,
|
|
424
|
+
today.getMonth(),
|
|
425
|
+
today.getDate(),
|
|
426
|
+
);
|
|
427
|
+
const minAge = new Date(
|
|
428
|
+
today.getFullYear() - 13,
|
|
429
|
+
today.getMonth(),
|
|
430
|
+
today.getDate(),
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
const monthLimitFirst = getMonthFromDate(maxAge);
|
|
434
|
+
const monthLimitLast = getMonthFromDate(minAge);
|
|
435
|
+
|
|
436
|
+
// Start showing calendar at a reasonable age (25 years ago)
|
|
437
|
+
const defaultMonth = getMonthFromDate(
|
|
438
|
+
new Date(
|
|
439
|
+
today.getFullYear() - 25,
|
|
440
|
+
today.getMonth(),
|
|
441
|
+
today.getDate(),
|
|
442
|
+
),
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
return (
|
|
446
|
+
<div className="birthday-picker">
|
|
447
|
+
<h3>Enter Your Birthday</h3>
|
|
448
|
+
<DatePicker
|
|
449
|
+
initialMonth={defaultMonth}
|
|
450
|
+
monthLimitFirst={monthLimitFirst}
|
|
451
|
+
monthLimitLast={monthLimitLast}
|
|
452
|
+
onChange={({ dateStart }) => setBirthday(dateStart)}
|
|
453
|
+
dateStart={birthday}
|
|
454
|
+
/>
|
|
455
|
+
|
|
456
|
+
{birthday ? (
|
|
457
|
+
<p
|
|
458
|
+
style={{
|
|
459
|
+
marginTop: '16px',
|
|
460
|
+
padding: '12px',
|
|
461
|
+
backgroundColor: '#e3f2fd',
|
|
462
|
+
borderRadius: '6px',
|
|
463
|
+
}}
|
|
464
|
+
>
|
|
465
|
+
<strong>
|
|
466
|
+
You are{' '}
|
|
467
|
+
{Math.floor(
|
|
468
|
+
(today.getTime() -
|
|
469
|
+
new Date(birthday).getTime()) /
|
|
470
|
+
(1000 * 60 * 60 * 24 * 365.25),
|
|
471
|
+
)}{' '}
|
|
472
|
+
years old
|
|
473
|
+
</strong>
|
|
474
|
+
</p>
|
|
475
|
+
) : null}
|
|
476
|
+
</div>
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
### Multi-Month Navigation
|
|
482
|
+
|
|
483
|
+
**[🎮 Live Demo](https://uikit.acusti.ca/?path=/story/uikit-controls-datepicker-datepicker--flexible-date-picker)**
|
|
484
|
+
|
|
485
|
+
```tsx
|
|
486
|
+
import { DatePicker, getMonthFromDate } from '@acusti/date-picker';
|
|
487
|
+
import { useState } from 'react';
|
|
488
|
+
|
|
489
|
+
function FlexibleDatePicker() {
|
|
490
|
+
const [selectedDate, setSelectedDate] = useState('');
|
|
491
|
+
const [viewMode, setViewMode] = useState<'single' | 'double'>(
|
|
492
|
+
'single',
|
|
493
|
+
);
|
|
494
|
+
|
|
495
|
+
return (
|
|
496
|
+
<div className="flexible-date-picker">
|
|
497
|
+
<div style={{ marginBottom: '16px' }}>
|
|
498
|
+
<label style={{ marginRight: '16px' }}>
|
|
499
|
+
<input
|
|
500
|
+
type="radio"
|
|
501
|
+
checked={viewMode === 'single'}
|
|
502
|
+
onChange={() => setViewMode('single')}
|
|
503
|
+
style={{ marginRight: '4px' }}
|
|
504
|
+
/>
|
|
505
|
+
Single Month
|
|
506
|
+
</label>
|
|
507
|
+
<label>
|
|
508
|
+
<input
|
|
509
|
+
type="radio"
|
|
510
|
+
checked={viewMode === 'double'}
|
|
511
|
+
onChange={() => setViewMode('double')}
|
|
512
|
+
style={{ marginRight: '4px' }}
|
|
513
|
+
/>
|
|
514
|
+
Two Months
|
|
515
|
+
</label>
|
|
516
|
+
</div>
|
|
517
|
+
|
|
518
|
+
<DatePicker
|
|
519
|
+
isTwoUp={viewMode === 'double'}
|
|
520
|
+
useMonthAbbreviations={viewMode === 'double'}
|
|
521
|
+
onChange={({ dateStart }) => setSelectedDate(dateStart)}
|
|
522
|
+
dateStart={selectedDate}
|
|
523
|
+
/>
|
|
524
|
+
|
|
525
|
+
{selectedDate ? (
|
|
526
|
+
<div
|
|
527
|
+
style={{
|
|
528
|
+
marginTop: '16px',
|
|
529
|
+
padding: '12px',
|
|
530
|
+
border: '1px solid #e1e5e9',
|
|
531
|
+
borderRadius: '6px',
|
|
532
|
+
}}
|
|
533
|
+
>
|
|
534
|
+
<strong>Selected:</strong>{' '}
|
|
535
|
+
{new Date(selectedDate).toLocaleDateString()}
|
|
536
|
+
<br />
|
|
537
|
+
<strong>Day of week:</strong>{' '}
|
|
538
|
+
{new Date(selectedDate).toLocaleDateString('en', {
|
|
539
|
+
weekday: 'long',
|
|
540
|
+
})}
|
|
541
|
+
</div>
|
|
542
|
+
) : null}
|
|
543
|
+
</div>
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
### Custom Month Calendar Usage
|
|
549
|
+
|
|
550
|
+
```tsx
|
|
551
|
+
import { MonthCalendar, getMonthFromDate } from '@acusti/date-picker';
|
|
552
|
+
import { useState } from 'react';
|
|
553
|
+
|
|
554
|
+
function CustomCalendarGrid() {
|
|
555
|
+
const [selectedDates, setSelectedDates] = useState<string[]>([]);
|
|
556
|
+
const currentMonth = getMonthFromDate(new Date());
|
|
557
|
+
|
|
558
|
+
const handleDateSelect = (date: string) => {
|
|
559
|
+
setSelectedDates((prev) =>
|
|
560
|
+
prev.includes(date)
|
|
561
|
+
? prev.filter((d) => d !== date)
|
|
562
|
+
: [...prev, date],
|
|
563
|
+
);
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
return (
|
|
567
|
+
<div>
|
|
568
|
+
<h3>Multi-Select Calendar</h3>
|
|
569
|
+
<p>Click dates to select/deselect multiple dates</p>
|
|
570
|
+
|
|
571
|
+
<MonthCalendar
|
|
572
|
+
month={currentMonth}
|
|
573
|
+
onChange={handleDateSelect}
|
|
574
|
+
title="Select Multiple Dates"
|
|
575
|
+
/>
|
|
576
|
+
|
|
577
|
+
<div className="selected-dates">
|
|
578
|
+
<h4>Selected Dates ({selectedDates.length}):</h4>
|
|
579
|
+
<ul>
|
|
580
|
+
{selectedDates.map((date) => (
|
|
581
|
+
<li key={date}>
|
|
582
|
+
{new Date(date).toLocaleDateString()}
|
|
583
|
+
</li>
|
|
584
|
+
))}
|
|
585
|
+
</ul>
|
|
586
|
+
</div>
|
|
587
|
+
</div>
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
## Styling
|
|
593
|
+
|
|
594
|
+
The date picker uses CSS custom properties for easy theming:
|
|
595
|
+
|
|
596
|
+
```css
|
|
597
|
+
.date-picker {
|
|
598
|
+
/* Calendar colors */
|
|
599
|
+
--date-picker-bg: #ffffff;
|
|
600
|
+
--date-picker-border: #e0e0e0;
|
|
601
|
+
--date-picker-text: #333333;
|
|
602
|
+
|
|
603
|
+
/* Selected date colors */
|
|
604
|
+
--date-picker-selected-bg: #007bff;
|
|
605
|
+
--date-picker-selected-text: #ffffff;
|
|
606
|
+
|
|
607
|
+
/* Range selection colors */
|
|
608
|
+
--date-picker-range-bg: #e3f2fd;
|
|
609
|
+
--date-picker-range-border: #2196f3;
|
|
610
|
+
|
|
611
|
+
/* Hover states */
|
|
612
|
+
--date-picker-hover-bg: #f5f5f5;
|
|
613
|
+
|
|
614
|
+
/* Navigation arrows */
|
|
615
|
+
--date-picker-arrow-color: #666666;
|
|
616
|
+
--date-picker-arrow-hover: #333333;
|
|
617
|
+
|
|
618
|
+
/* Month header */
|
|
619
|
+
--date-picker-header-text: #333333;
|
|
620
|
+
--date-picker-header-bg: #f8f9fa;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/* Custom styling example */
|
|
624
|
+
.booking-calendar {
|
|
625
|
+
--date-picker-selected-bg: #28a745;
|
|
626
|
+
--date-picker-range-bg: #d4edda;
|
|
627
|
+
--date-picker-range-border: #28a745;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
.event-calendar {
|
|
631
|
+
--date-picker-selected-bg: #6f42c1;
|
|
632
|
+
--date-picker-range-bg: #e2d9f3;
|
|
633
|
+
}
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
## Month Number System
|
|
637
|
+
|
|
638
|
+
The date picker uses an internal month numbering system where months are
|
|
639
|
+
represented as the number of months since January 1970:
|
|
640
|
+
|
|
641
|
+
- January 1970 = 0
|
|
642
|
+
- February 1970 = 1
|
|
643
|
+
- January 2024 = 648
|
|
644
|
+
- etc.
|
|
645
|
+
|
|
646
|
+
This system allows for efficient month calculations and navigation. The
|
|
647
|
+
utility functions handle the conversion between this system and standard
|
|
648
|
+
dates.
|
|
649
|
+
|
|
650
|
+
## Browser Compatibility
|
|
651
|
+
|
|
652
|
+
- **Modern Browsers** - Chrome, Firefox, Safari, Edge (latest versions)
|
|
653
|
+
- **Mobile Support** - iOS Safari, Android Chrome
|
|
654
|
+
- **SSR Compatible** - Works with Next.js, React Router, and other SSR
|
|
655
|
+
frameworks
|
|
656
|
+
|
|
657
|
+
## Common Use Cases
|
|
658
|
+
|
|
659
|
+
- **Booking Systems** - Hotels, flights, rental properties
|
|
660
|
+
- **Event Management** - Conference registration, appointment scheduling
|
|
661
|
+
- **Reporting Tools** - Date range filters for analytics
|
|
662
|
+
- **Form Inputs** - Birthday selection, deadline setting
|
|
663
|
+
- **Content Management** - Publishing date selection
|
|
664
|
+
- **E-commerce** - Delivery date selection, sale periods
|
|
665
|
+
- **Project Management** - Milestone and deadline tracking
|
|
666
|
+
|
|
667
|
+
## Demo
|
|
668
|
+
|
|
669
|
+
See the
|
|
670
|
+
[Storybook documentation and examples](https://uikit.acusti.ca/?path=/docs/uikit-controls-datepicker-datepicker--docs)
|
|
671
|
+
for interactive demonstrations of all date picker features and
|
|
672
|
+
configurations.
|
package/dist/DatePicker.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export type Props = {
|
|
2
2
|
className?: string;
|
|
3
|
-
dateEnd?: Date | number | string;
|
|
4
|
-
dateStart?: Date | number | string;
|
|
3
|
+
dateEnd?: Date | null | number | string;
|
|
4
|
+
dateStart?: Date | null | number | string;
|
|
5
5
|
initialMonth?: number;
|
|
6
6
|
isRange?: boolean;
|
|
7
7
|
isTwoUp?: boolean;
|