@carbon-labs/react-date-picker 0.2.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/LICENSE +201 -0
- package/README.md +35 -0
- package/es/_virtual/_rollupPluginBabelHelpers.js +18 -0
- package/es/components/Calendar.d.ts +37 -0
- package/es/components/Calendar.js +267 -0
- package/es/components/DatePicker.d.ts +90 -0
- package/es/components/DatePicker.js +182 -0
- package/es/components/DatePickerInput.d.ts +139 -0
- package/es/components/DatePickerInput.js +149 -0
- package/es/components/DatePickerSkeleton.d.ts +22 -0
- package/es/components/DatePickerSkeleton.js +39 -0
- package/es/hooks/useDatePicker.d.ts +130 -0
- package/es/hooks/useDatePicker.js +364 -0
- package/es/index.d.ts +18 -0
- package/es/index.js +13 -0
- package/lib/_virtual/_rollupPluginBabelHelpers.js +20 -0
- package/lib/components/Calendar.d.ts +37 -0
- package/lib/components/Calendar.js +269 -0
- package/lib/components/DatePicker.d.ts +90 -0
- package/lib/components/DatePicker.js +184 -0
- package/lib/components/DatePickerInput.d.ts +139 -0
- package/lib/components/DatePickerInput.js +151 -0
- package/lib/components/DatePickerSkeleton.d.ts +22 -0
- package/lib/components/DatePickerSkeleton.js +41 -0
- package/lib/hooks/useDatePicker.d.ts +130 -0
- package/lib/hooks/useDatePicker.js +366 -0
- package/lib/index.d.ts +18 -0
- package/lib/index.js +31 -0
- package/package.json +51 -0
- package/scss/date-picker.scss +20 -0
- package/telemetry.yml +6 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright IBM Corp. 2024
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the Apache-2.0 license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
var React = require('react');
|
|
11
|
+
var classNames = require('classnames');
|
|
12
|
+
var datePicker = require('@carbon-labs/primitives/date-picker');
|
|
13
|
+
|
|
14
|
+
var _svg, _svg2;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Calendar component props
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Day cell in the calendar grid
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Generate calendar grid for a given month
|
|
26
|
+
*
|
|
27
|
+
* @param {Temporal.PlainDate} viewDate - The date to generate calendar for
|
|
28
|
+
* @param {Temporal.PlainDate | null} minDate - Minimum selectable date
|
|
29
|
+
* @param {Temporal.PlainDate | null} maxDate - Maximum selectable date
|
|
30
|
+
* @returns {DayCell[][]} 2D array of day cells (weeks x days)
|
|
31
|
+
*/
|
|
32
|
+
function generateCalendarGrid(viewDate, minDate, maxDate) {
|
|
33
|
+
const today = Temporal.Now.plainDateISO();
|
|
34
|
+
const firstDayOfMonth = viewDate.with({
|
|
35
|
+
day: 1
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Get the day of week for the first day (0 = Sunday)
|
|
39
|
+
// Convert Temporal dayOfWeek (1=Monday) to JS (0=Sunday)
|
|
40
|
+
const jsDate = datePicker.plainDateToDate(firstDayOfMonth);
|
|
41
|
+
const firstDayOfWeek = jsDate.getDay();
|
|
42
|
+
|
|
43
|
+
// Calculate start date (may be from previous month)
|
|
44
|
+
const startDate = datePicker.addDays(firstDayOfMonth, -firstDayOfWeek);
|
|
45
|
+
|
|
46
|
+
// Generate 6 weeks (42 days) to ensure consistent calendar height
|
|
47
|
+
const weeks = [];
|
|
48
|
+
let currentDate = startDate;
|
|
49
|
+
for (let week = 0; week < 6; week++) {
|
|
50
|
+
const days = [];
|
|
51
|
+
for (let day = 0; day < 7; day++) {
|
|
52
|
+
const isCurrentMonth = currentDate.month === viewDate.month;
|
|
53
|
+
const isToday = Temporal.PlainDate.compare(currentDate, today) === 0;
|
|
54
|
+
|
|
55
|
+
// Check if date is disabled
|
|
56
|
+
let isDisabled = false;
|
|
57
|
+
if (minDate && Temporal.PlainDate.compare(currentDate, minDate) < 0) {
|
|
58
|
+
isDisabled = true;
|
|
59
|
+
}
|
|
60
|
+
if (maxDate && Temporal.PlainDate.compare(currentDate, maxDate) > 0) {
|
|
61
|
+
isDisabled = true;
|
|
62
|
+
}
|
|
63
|
+
days.push({
|
|
64
|
+
date: currentDate,
|
|
65
|
+
isCurrentMonth,
|
|
66
|
+
isDisabled,
|
|
67
|
+
isToday
|
|
68
|
+
});
|
|
69
|
+
currentDate = datePicker.addDays(currentDate, 1);
|
|
70
|
+
}
|
|
71
|
+
weeks.push(days);
|
|
72
|
+
}
|
|
73
|
+
return weeks;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Calendar component
|
|
78
|
+
* Renders a calendar grid for date selection
|
|
79
|
+
*/
|
|
80
|
+
function Calendar({
|
|
81
|
+
context,
|
|
82
|
+
onDateSelect,
|
|
83
|
+
onNavigate,
|
|
84
|
+
className
|
|
85
|
+
}) {
|
|
86
|
+
// Use Carbon's standard 'cds' prefix to match Carbon's date-picker styles
|
|
87
|
+
const prefix = 'cds';
|
|
88
|
+
const {
|
|
89
|
+
viewDate,
|
|
90
|
+
startDate,
|
|
91
|
+
endDate,
|
|
92
|
+
minDate,
|
|
93
|
+
maxDate,
|
|
94
|
+
focusedDate,
|
|
95
|
+
mode
|
|
96
|
+
} = context;
|
|
97
|
+
|
|
98
|
+
// Generate calendar grid
|
|
99
|
+
const calendarGrid = React.useMemo(() => {
|
|
100
|
+
if (!viewDate) {
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
return generateCalendarGrid(viewDate, minDate, maxDate);
|
|
104
|
+
}, [viewDate, minDate, maxDate]);
|
|
105
|
+
|
|
106
|
+
// Navigation handlers
|
|
107
|
+
const handlePrevMonth = React.useCallback(() => {
|
|
108
|
+
onNavigate('PREV_MONTH');
|
|
109
|
+
}, [onNavigate]);
|
|
110
|
+
const handleNextMonth = React.useCallback(() => {
|
|
111
|
+
onNavigate('NEXT_MONTH');
|
|
112
|
+
}, [onNavigate]);
|
|
113
|
+
const handleDateClick = React.useCallback((date, isDisabled) => {
|
|
114
|
+
if (!isDisabled) {
|
|
115
|
+
onDateSelect(date);
|
|
116
|
+
}
|
|
117
|
+
}, [onDateSelect]);
|
|
118
|
+
|
|
119
|
+
// Check if a date is selected
|
|
120
|
+
const isDateSelected = React.useCallback(date => {
|
|
121
|
+
if (!startDate) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
if (Temporal.PlainDate.compare(date, startDate) === 0) {
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
if (endDate && Temporal.PlainDate.compare(date, endDate) === 0) {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
return false;
|
|
131
|
+
}, [startDate, endDate]);
|
|
132
|
+
|
|
133
|
+
// Check if a date is in the selected range
|
|
134
|
+
const isDateInRange = React.useCallback(date => {
|
|
135
|
+
if (mode !== 'range' || !startDate) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// If endDate exists, use it for the range
|
|
140
|
+
if (endDate) {
|
|
141
|
+
return Temporal.PlainDate.compare(date, startDate) > 0 && Temporal.PlainDate.compare(date, endDate) < 0;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// If endDate doesn't exist but focusedDate does (user is selecting end date),
|
|
145
|
+
// show the range between startDate and focusedDate
|
|
146
|
+
if (focusedDate) {
|
|
147
|
+
const earlierDate = Temporal.PlainDate.compare(startDate, focusedDate) < 0 ? startDate : focusedDate;
|
|
148
|
+
const laterDate = Temporal.PlainDate.compare(startDate, focusedDate) < 0 ? focusedDate : startDate;
|
|
149
|
+
return Temporal.PlainDate.compare(date, earlierDate) > 0 && Temporal.PlainDate.compare(date, laterDate) < 0;
|
|
150
|
+
}
|
|
151
|
+
return false;
|
|
152
|
+
}, [mode, startDate, endDate, focusedDate]);
|
|
153
|
+
|
|
154
|
+
// Check if a date is the range start (not currently used but kept for future range styling)
|
|
155
|
+
React.useCallback(date => {
|
|
156
|
+
if (mode !== 'range' || !startDate) {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
return Temporal.PlainDate.compare(date, startDate) === 0;
|
|
160
|
+
}, [mode, startDate]);
|
|
161
|
+
|
|
162
|
+
// Check if a date is the range end (not currently used but kept for future range styling)
|
|
163
|
+
React.useCallback(date => {
|
|
164
|
+
if (mode !== 'range' || !endDate) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
return Temporal.PlainDate.compare(date, endDate) === 0;
|
|
168
|
+
}, [mode, endDate]);
|
|
169
|
+
|
|
170
|
+
// Check if a date is focused
|
|
171
|
+
const isDateFocused = React.useCallback(date => {
|
|
172
|
+
if (!focusedDate) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
return Temporal.PlainDate.compare(date, focusedDate) === 0;
|
|
176
|
+
}, [focusedDate]);
|
|
177
|
+
if (!viewDate) {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
const calendarClasses = classNames(`${prefix}--date-picker__calendar`, className);
|
|
181
|
+
|
|
182
|
+
// Format month and year for display
|
|
183
|
+
const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
|
|
184
|
+
const monthYear = `${monthNames[viewDate.month - 1]} ${viewDate.year}`;
|
|
185
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
186
|
+
className: calendarClasses,
|
|
187
|
+
role: "grid",
|
|
188
|
+
"aria-label": "Calendar",
|
|
189
|
+
tabIndex: 0
|
|
190
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
191
|
+
className: `${prefix}--date-picker__month`
|
|
192
|
+
}, /*#__PURE__*/React.createElement("button", {
|
|
193
|
+
type: "button",
|
|
194
|
+
className: `${prefix}--date-picker__month-nav`,
|
|
195
|
+
onClick: handlePrevMonth,
|
|
196
|
+
"aria-label": "Previous month"
|
|
197
|
+
}, _svg || (_svg = /*#__PURE__*/React.createElement("svg", {
|
|
198
|
+
focusable: "false",
|
|
199
|
+
preserveAspectRatio: "xMidYMid meet",
|
|
200
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
201
|
+
fill: "currentColor",
|
|
202
|
+
width: "16",
|
|
203
|
+
height: "16",
|
|
204
|
+
viewBox: "0 0 16 16",
|
|
205
|
+
"aria-hidden": "true"
|
|
206
|
+
}, /*#__PURE__*/React.createElement("path", {
|
|
207
|
+
d: "M11 8l-5 5-.7-.7L9.6 8 5.3 3.7 6 3z",
|
|
208
|
+
transform: "rotate(180 8 8)"
|
|
209
|
+
})))), /*#__PURE__*/React.createElement("div", {
|
|
210
|
+
className: `${prefix}--date-picker__current-month`
|
|
211
|
+
}, monthYear), /*#__PURE__*/React.createElement("button", {
|
|
212
|
+
type: "button",
|
|
213
|
+
className: `${prefix}--date-picker__month-nav`,
|
|
214
|
+
onClick: handleNextMonth,
|
|
215
|
+
"aria-label": "Next month"
|
|
216
|
+
}, _svg2 || (_svg2 = /*#__PURE__*/React.createElement("svg", {
|
|
217
|
+
focusable: "false",
|
|
218
|
+
preserveAspectRatio: "xMidYMid meet",
|
|
219
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
220
|
+
fill: "currentColor",
|
|
221
|
+
width: "16",
|
|
222
|
+
height: "16",
|
|
223
|
+
viewBox: "0 0 16 16",
|
|
224
|
+
"aria-hidden": "true"
|
|
225
|
+
}, /*#__PURE__*/React.createElement("path", {
|
|
226
|
+
d: "M11 8l-5 5-.7-.7L9.6 8 5.3 3.7 6 3z"
|
|
227
|
+
}))))), /*#__PURE__*/React.createElement("div", {
|
|
228
|
+
className: `${prefix}--date-picker__weekdays`
|
|
229
|
+
}, ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'].map(day => /*#__PURE__*/React.createElement("div", {
|
|
230
|
+
key: day,
|
|
231
|
+
className: `${prefix}--date-picker__weekday`,
|
|
232
|
+
title: day
|
|
233
|
+
}, day))), /*#__PURE__*/React.createElement("div", {
|
|
234
|
+
className: `${prefix}--date-picker__days`
|
|
235
|
+
}, calendarGrid.flat().map((dayCell, index) => {
|
|
236
|
+
const {
|
|
237
|
+
date,
|
|
238
|
+
isCurrentMonth,
|
|
239
|
+
isDisabled,
|
|
240
|
+
isToday
|
|
241
|
+
} = dayCell;
|
|
242
|
+
const selected = isDateSelected(date);
|
|
243
|
+
const inRange = isDateInRange(date);
|
|
244
|
+
const focused = isDateFocused(date);
|
|
245
|
+
const dayClasses = classNames(`${prefix}--date-picker__day`, {
|
|
246
|
+
today: isToday,
|
|
247
|
+
selected: selected,
|
|
248
|
+
inRange: inRange,
|
|
249
|
+
prevMonthDay: !isCurrentMonth && date.month < viewDate.month,
|
|
250
|
+
nextMonthDay: !isCurrentMonth && date.month > viewDate.month,
|
|
251
|
+
disabled: isDisabled,
|
|
252
|
+
focused: focused
|
|
253
|
+
});
|
|
254
|
+
return /*#__PURE__*/React.createElement("button", {
|
|
255
|
+
key: index,
|
|
256
|
+
type: "button",
|
|
257
|
+
className: dayClasses,
|
|
258
|
+
onClick: () => handleDateClick(date, isDisabled),
|
|
259
|
+
disabled: isDisabled,
|
|
260
|
+
"aria-label": `${monthNames[date.month - 1]} ${date.day}, ${date.year}`,
|
|
261
|
+
"aria-pressed": selected || undefined,
|
|
262
|
+
"aria-current": isToday ? 'date' : undefined,
|
|
263
|
+
tabIndex: focused ? 0 : -1
|
|
264
|
+
}, date.day);
|
|
265
|
+
})));
|
|
266
|
+
}
|
|
267
|
+
Calendar.displayName = 'Calendar';
|
|
268
|
+
|
|
269
|
+
exports.Calendar = Calendar;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright IBM Corp. 2026
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the Apache-2.0 license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
import React from 'react';
|
|
8
|
+
/**
|
|
9
|
+
* DatePicker component props
|
|
10
|
+
* Maintains 100% backwards compatibility with Carbon React v11 API
|
|
11
|
+
*/
|
|
12
|
+
export interface DatePickerProps {
|
|
13
|
+
/**
|
|
14
|
+
* The type of date picker (Carbon API uses datePickerType, not mode)
|
|
15
|
+
*/
|
|
16
|
+
datePickerType?: 'simple' | 'single' | 'range';
|
|
17
|
+
/**
|
|
18
|
+
* The date format string (Flatpickr-compatible format)
|
|
19
|
+
*/
|
|
20
|
+
dateFormat?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Minimum selectable date (mm/dd/yyyy format - Carbon API)
|
|
23
|
+
*/
|
|
24
|
+
minDate?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Maximum selectable date (mm/dd/yyyy format - Carbon API)
|
|
27
|
+
*/
|
|
28
|
+
maxDate?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Locale for date formatting
|
|
31
|
+
*/
|
|
32
|
+
locale?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Whether the picker is read-only (Carbon uses readOnly, not readonly)
|
|
35
|
+
*/
|
|
36
|
+
readOnly?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Whether to use the light variant
|
|
39
|
+
*/
|
|
40
|
+
light?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Whether to use the short variant
|
|
43
|
+
*/
|
|
44
|
+
short?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Whether to allow manual input
|
|
47
|
+
*/
|
|
48
|
+
allowInput?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Whether to close calendar on date selection
|
|
51
|
+
*/
|
|
52
|
+
closeOnSelect?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Change handler - receives array of Date objects (Carbon API)
|
|
55
|
+
*/
|
|
56
|
+
onChange?: (dates: Date[]) => void;
|
|
57
|
+
/**
|
|
58
|
+
* Close handler
|
|
59
|
+
*/
|
|
60
|
+
onClose?: () => void;
|
|
61
|
+
/**
|
|
62
|
+
* Open handler
|
|
63
|
+
*/
|
|
64
|
+
onOpen?: () => void;
|
|
65
|
+
/**
|
|
66
|
+
* Additional CSS class names
|
|
67
|
+
*/
|
|
68
|
+
className?: string;
|
|
69
|
+
/**
|
|
70
|
+
* Children (DatePickerInput components)
|
|
71
|
+
*/
|
|
72
|
+
children?: React.ReactNode;
|
|
73
|
+
/**
|
|
74
|
+
* Initial value
|
|
75
|
+
*/
|
|
76
|
+
value?: string;
|
|
77
|
+
/**
|
|
78
|
+
* Append the calendar to a specific element
|
|
79
|
+
*/
|
|
80
|
+
appendTo?: HTMLElement;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* DatePicker component
|
|
84
|
+
* Main wrapper component that orchestrates date picker functionality
|
|
85
|
+
* Maintains 100% backwards compatibility with Carbon React v11
|
|
86
|
+
*/
|
|
87
|
+
export declare function DatePicker({ datePickerType, dateFormat, minDate, maxDate, locale, readOnly, light, short, allowInput, closeOnSelect, onChange, onClose, onOpen, className, children, value, }: DatePickerProps): React.JSX.Element;
|
|
88
|
+
export declare namespace DatePicker {
|
|
89
|
+
var displayName: string;
|
|
90
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright IBM Corp. 2024
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the Apache-2.0 license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
var React = require('react');
|
|
11
|
+
var classNames = require('classnames');
|
|
12
|
+
var useDatePicker = require('../hooks/useDatePicker.js');
|
|
13
|
+
var Calendar = require('./Calendar.js');
|
|
14
|
+
var datePicker = require('@carbon-labs/primitives/date-picker');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* DatePicker component props
|
|
18
|
+
* Maintains 100% backwards compatibility with Carbon React v11 API
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* DatePicker component
|
|
23
|
+
* Main wrapper component that orchestrates date picker functionality
|
|
24
|
+
* Maintains 100% backwards compatibility with Carbon React v11
|
|
25
|
+
*/
|
|
26
|
+
function DatePicker({
|
|
27
|
+
datePickerType = 'single',
|
|
28
|
+
dateFormat = 'm/d/Y',
|
|
29
|
+
minDate,
|
|
30
|
+
maxDate,
|
|
31
|
+
locale = 'en',
|
|
32
|
+
readOnly = false,
|
|
33
|
+
light = false,
|
|
34
|
+
short = false,
|
|
35
|
+
allowInput = true,
|
|
36
|
+
closeOnSelect = true,
|
|
37
|
+
onChange,
|
|
38
|
+
onClose,
|
|
39
|
+
onOpen,
|
|
40
|
+
className,
|
|
41
|
+
children,
|
|
42
|
+
value
|
|
43
|
+
}) {
|
|
44
|
+
const containerRef = React.useRef(null);
|
|
45
|
+
|
|
46
|
+
// Use Carbon's standard 'cds' prefix to match Carbon's date-picker styles
|
|
47
|
+
const prefix = 'cds';
|
|
48
|
+
|
|
49
|
+
// Use the date picker hook
|
|
50
|
+
const {
|
|
51
|
+
context,
|
|
52
|
+
isOpen,
|
|
53
|
+
selectDate,
|
|
54
|
+
openCalendar,
|
|
55
|
+
closeCalendar,
|
|
56
|
+
handleInputFocus,
|
|
57
|
+
handleInputBlur,
|
|
58
|
+
handleInputChange,
|
|
59
|
+
send,
|
|
60
|
+
startInputRef,
|
|
61
|
+
endInputRef,
|
|
62
|
+
calendarRef
|
|
63
|
+
} = useDatePicker.useDatePicker({
|
|
64
|
+
datePickerType,
|
|
65
|
+
value,
|
|
66
|
+
minDate,
|
|
67
|
+
maxDate,
|
|
68
|
+
dateFormat,
|
|
69
|
+
closeOnSelect,
|
|
70
|
+
readOnly,
|
|
71
|
+
onChange,
|
|
72
|
+
onOpen,
|
|
73
|
+
onClose
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Handle calendar navigation
|
|
77
|
+
const handleNavigate = eventType => {
|
|
78
|
+
send(eventType);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// Handle calendar icon click - toggle calendar open/close
|
|
82
|
+
const handleIconClick = () => {
|
|
83
|
+
if (isOpen) {
|
|
84
|
+
closeCalendar();
|
|
85
|
+
} else {
|
|
86
|
+
openCalendar();
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// Clone children and inject props
|
|
91
|
+
const childArray = React.Children.toArray(children);
|
|
92
|
+
const enhancedChildren = childArray.map((child, index) => {
|
|
93
|
+
if (! /*#__PURE__*/React.isValidElement(child)) {
|
|
94
|
+
return child;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Determine which input this is (start or end for range mode)
|
|
98
|
+
const isStartInput = index === 0;
|
|
99
|
+
const isEndInput = index === 1 && datePickerType === 'range';
|
|
100
|
+
|
|
101
|
+
// Get the appropriate ref
|
|
102
|
+
const inputRef = isStartInput ? startInputRef : isEndInput ? endInputRef : undefined;
|
|
103
|
+
|
|
104
|
+
// Get the appropriate value - always use a string to keep input controlled
|
|
105
|
+
let inputValue = child.props.value ?? '';
|
|
106
|
+
if (inputValue === '') {
|
|
107
|
+
if (isStartInput && context.startDate) {
|
|
108
|
+
inputValue = datePicker.formatPlainDate(context.startDate, context.dateFormat);
|
|
109
|
+
} else if (isEndInput && context.endDate) {
|
|
110
|
+
inputValue = datePicker.formatPlainDate(context.endDate, context.dateFormat);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Clone the child with enhanced props
|
|
115
|
+
const enhancedProps = {
|
|
116
|
+
...child.props,
|
|
117
|
+
value: inputValue,
|
|
118
|
+
// Always a string, never undefined
|
|
119
|
+
disabled: child.props.disabled || context.isDisabled,
|
|
120
|
+
readOnly: child.props.readOnly || readOnly,
|
|
121
|
+
onFocus: e => {
|
|
122
|
+
child.props.onFocus?.(e);
|
|
123
|
+
handleInputFocus(isEndInput ? 'to' : 'from');
|
|
124
|
+
},
|
|
125
|
+
onBlur: e => {
|
|
126
|
+
child.props.onBlur?.(e);
|
|
127
|
+
handleInputBlur();
|
|
128
|
+
},
|
|
129
|
+
onChange: e => {
|
|
130
|
+
child.props.onChange?.(e);
|
|
131
|
+
handleInputChange(e.target.value, isEndInput ? 'to' : 'from');
|
|
132
|
+
},
|
|
133
|
+
onIconClick: () => {
|
|
134
|
+
child.props.onIconClick?.();
|
|
135
|
+
handleIconClick();
|
|
136
|
+
},
|
|
137
|
+
hideIcon: datePickerType === 'simple' || child.props.hideIcon
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// Wrap each input in a container div with proper Carbon classes
|
|
141
|
+
const containerClasses = classNames(`${prefix}--date-picker-container`, {
|
|
142
|
+
[`${prefix}--date-picker-container--single`]: datePickerType === 'single',
|
|
143
|
+
[`${prefix}--date-picker-container--from`]: isStartInput && datePickerType === 'range',
|
|
144
|
+
[`${prefix}--date-picker-container--to`]: isEndInput && datePickerType === 'range'
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Use cloneElement with ref separately to avoid TypeScript issues
|
|
148
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
149
|
+
key: child.props.id,
|
|
150
|
+
className: containerClasses
|
|
151
|
+
}, /*#__PURE__*/React.cloneElement(child, {
|
|
152
|
+
...enhancedProps,
|
|
153
|
+
// @ts-expect-error - ref is valid but TypeScript doesn't recognize it in cloneElement
|
|
154
|
+
ref: inputRef
|
|
155
|
+
}));
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Generate class names
|
|
159
|
+
const wrapperClasses = classNames(`${prefix}--date-picker`, {
|
|
160
|
+
[`${prefix}--date-picker--simple`]: datePickerType === 'simple',
|
|
161
|
+
[`${prefix}--date-picker--single`]: datePickerType === 'single',
|
|
162
|
+
[`${prefix}--date-picker--range`]: datePickerType === 'range',
|
|
163
|
+
[`${prefix}--date-picker--light`]: light,
|
|
164
|
+
[`${prefix}--date-picker--short`]: short,
|
|
165
|
+
[`${prefix}--date-picker--open`]: isOpen
|
|
166
|
+
}, className);
|
|
167
|
+
|
|
168
|
+
// Don't render calendar for simple mode
|
|
169
|
+
const shouldRenderCalendar = datePickerType !== 'simple' && isOpen;
|
|
170
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
171
|
+
ref: containerRef,
|
|
172
|
+
className: wrapperClasses
|
|
173
|
+
}, enhancedChildren, shouldRenderCalendar && /*#__PURE__*/React.createElement("div", {
|
|
174
|
+
ref: calendarRef,
|
|
175
|
+
className: `${prefix}--date-picker__calendar-container`
|
|
176
|
+
}, /*#__PURE__*/React.createElement(Calendar.Calendar, {
|
|
177
|
+
context: context,
|
|
178
|
+
onDateSelect: selectDate,
|
|
179
|
+
onNavigate: handleNavigate
|
|
180
|
+
})));
|
|
181
|
+
}
|
|
182
|
+
DatePicker.displayName = 'DatePicker';
|
|
183
|
+
|
|
184
|
+
exports.DatePicker = DatePicker;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright IBM Corp. 2026
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the Apache-2.0 license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
import React from 'react';
|
|
8
|
+
/**
|
|
9
|
+
* DatePickerInput component props
|
|
10
|
+
* Maintains 100% backwards compatibility with Carbon React v11 API
|
|
11
|
+
*/
|
|
12
|
+
export interface DatePickerInputProps {
|
|
13
|
+
/**
|
|
14
|
+
* The unique identifier for the input (required)
|
|
15
|
+
*/
|
|
16
|
+
id: string;
|
|
17
|
+
/**
|
|
18
|
+
* The label text
|
|
19
|
+
*/
|
|
20
|
+
labelText?: React.ReactNode;
|
|
21
|
+
/**
|
|
22
|
+
* The placeholder text
|
|
23
|
+
*/
|
|
24
|
+
placeholder?: string;
|
|
25
|
+
/**
|
|
26
|
+
* The input size
|
|
27
|
+
*/
|
|
28
|
+
size?: 'sm' | 'md' | 'lg';
|
|
29
|
+
/**
|
|
30
|
+
* Whether the input is disabled
|
|
31
|
+
*/
|
|
32
|
+
disabled?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Whether the input is invalid
|
|
35
|
+
*/
|
|
36
|
+
invalid?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* The invalid text to display
|
|
39
|
+
*/
|
|
40
|
+
invalidText?: React.ReactNode;
|
|
41
|
+
/**
|
|
42
|
+
* Whether the input has a warning
|
|
43
|
+
*/
|
|
44
|
+
warn?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* The warning text to display
|
|
47
|
+
*/
|
|
48
|
+
warnText?: React.ReactNode;
|
|
49
|
+
/**
|
|
50
|
+
* Helper text to display below the input
|
|
51
|
+
*/
|
|
52
|
+
helperText?: React.ReactNode;
|
|
53
|
+
/**
|
|
54
|
+
* Whether to hide the label visually (still accessible)
|
|
55
|
+
*/
|
|
56
|
+
hideLabel?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Additional CSS class names
|
|
59
|
+
*/
|
|
60
|
+
className?: string;
|
|
61
|
+
/**
|
|
62
|
+
* The input value
|
|
63
|
+
*/
|
|
64
|
+
value?: string;
|
|
65
|
+
/**
|
|
66
|
+
* Default value for uncontrolled component
|
|
67
|
+
*/
|
|
68
|
+
defaultValue?: string;
|
|
69
|
+
/**
|
|
70
|
+
* Change handler
|
|
71
|
+
*/
|
|
72
|
+
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
|
73
|
+
/**
|
|
74
|
+
* Focus handler
|
|
75
|
+
*/
|
|
76
|
+
onFocus?: (event: React.FocusEvent<HTMLInputElement>) => void;
|
|
77
|
+
/**
|
|
78
|
+
* Blur handler
|
|
79
|
+
*/
|
|
80
|
+
onBlur?: (event: React.FocusEvent<HTMLInputElement>) => void;
|
|
81
|
+
/**
|
|
82
|
+
* Keydown handler
|
|
83
|
+
*/
|
|
84
|
+
onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => void;
|
|
85
|
+
/**
|
|
86
|
+
* Icon description for accessibility
|
|
87
|
+
*/
|
|
88
|
+
iconDescription?: string;
|
|
89
|
+
/**
|
|
90
|
+
* Calendar icon click handler
|
|
91
|
+
*/
|
|
92
|
+
onIconClick?: () => void;
|
|
93
|
+
/**
|
|
94
|
+
* Whether to show the calendar icon
|
|
95
|
+
*/
|
|
96
|
+
hideIcon?: boolean;
|
|
97
|
+
/**
|
|
98
|
+
* Pattern for input validation
|
|
99
|
+
*/
|
|
100
|
+
pattern?: string;
|
|
101
|
+
/**
|
|
102
|
+
* Type of input
|
|
103
|
+
*/
|
|
104
|
+
type?: string;
|
|
105
|
+
/**
|
|
106
|
+
* Whether the input is read-only
|
|
107
|
+
*/
|
|
108
|
+
readOnly?: boolean;
|
|
109
|
+
/**
|
|
110
|
+
* Autocomplete attribute
|
|
111
|
+
*/
|
|
112
|
+
autoComplete?: string;
|
|
113
|
+
/**
|
|
114
|
+
* Name attribute
|
|
115
|
+
*/
|
|
116
|
+
name?: string;
|
|
117
|
+
/**
|
|
118
|
+
* Whether the field is required
|
|
119
|
+
*/
|
|
120
|
+
required?: boolean;
|
|
121
|
+
/**
|
|
122
|
+
* Callback when calendar opens (for internal use)
|
|
123
|
+
*/
|
|
124
|
+
onOpen?: () => void;
|
|
125
|
+
/**
|
|
126
|
+
* Decorator component (e.g., AI Label)
|
|
127
|
+
*/
|
|
128
|
+
decorator?: React.ReactNode;
|
|
129
|
+
/**
|
|
130
|
+
* @deprecated Use `decorator` instead. Will be removed in next major version.
|
|
131
|
+
*/
|
|
132
|
+
slug?: React.ReactNode;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* DatePickerInput component
|
|
136
|
+
* Input field for date picker with Carbon Design System styling
|
|
137
|
+
* Maintains 100% backwards compatibility with Carbon React v11
|
|
138
|
+
*/
|
|
139
|
+
export declare const DatePickerInput: React.ForwardRefExoticComponent<DatePickerInputProps & React.RefAttributes<HTMLInputElement>>;
|