@instructure/ui-date-input 9.5.2 → 9.6.1-pr-snapshot-1726659472372
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/CHANGELOG.md +22 -0
- package/es/DateInput2/index.js +149 -77
- package/es/DateInput2/props.js +3 -5
- package/lib/DateInput2/index.js +149 -77
- package/lib/DateInput2/props.js +3 -5
- package/package.json +20 -20
- package/src/DateInput/README.md +1 -1
- package/src/DateInput2/README.md +186 -64
- package/src/DateInput2/index.tsx +151 -88
- package/src/DateInput2/props.ts +32 -32
- package/tsconfig.build.tsbuildinfo +1 -1
- package/types/DateInput2/index.d.ts +10 -13
- package/types/DateInput2/index.d.ts.map +1 -1
- package/types/DateInput2/props.d.ts +14 -22
- package/types/DateInput2/props.d.ts.map +1 -1
package/CHANGELOG.md
CHANGED
@@ -3,6 +3,28 @@
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
5
5
|
|
6
|
+
## [9.6.1-pr-snapshot-1726659472372](https://github.com/instructure/instructure-ui/compare/v9.6.0...v9.6.1-pr-snapshot-1726659472372) (2024-09-18)
|
7
|
+
|
8
|
+
|
9
|
+
### Features
|
10
|
+
|
11
|
+
* **ui-date-input:** improve DateInput2 api, extend docs ([27bc27f](https://github.com/instructure/instructure-ui/commit/27bc27f8a5e18cf3cabee8b5ea18f75629728ab6))
|
12
|
+
|
13
|
+
|
14
|
+
|
15
|
+
|
16
|
+
|
17
|
+
# [9.6.0](https://github.com/instructure/instructure-ui/compare/v9.5.2...v9.6.0) (2024-08-14)
|
18
|
+
|
19
|
+
|
20
|
+
### Features
|
21
|
+
|
22
|
+
* **ui-calendar,ui-date-input:** improve DateInput2 onChange callback, add date formatting option, extend docs ([4e2c23c](https://github.com/instructure/instructure-ui/commit/4e2c23c3288885e49030f1f471d61b2fed29b54c))
|
23
|
+
|
24
|
+
|
25
|
+
|
26
|
+
|
27
|
+
|
6
28
|
## [9.5.2](https://github.com/instructure/instructure-ui/compare/v9.5.1...v9.5.2) (2024-08-05)
|
7
29
|
|
8
30
|
|
package/es/DateInput2/index.js
CHANGED
@@ -35,23 +35,85 @@ import { passthroughProps } from '@instructure/ui-react-utils';
|
|
35
35
|
import { ApplyLocaleContext, Locale } from '@instructure/ui-i18n';
|
36
36
|
import { jsx } from '@instructure/emotion';
|
37
37
|
import { propTypes } from './props';
|
38
|
-
function
|
39
|
-
|
40
|
-
//
|
41
|
-
|
38
|
+
function parseLocaleDate(dateString = '', locale, timeZone) {
|
39
|
+
// This function may seem complicated but it basically does one thing:
|
40
|
+
// Given a dateString, a locale and a timeZone. The dateString is assumed to be formatted according
|
41
|
+
// to the locale. So if the locale is `en-us` the dateString is expected to be in the format of M/D/YYYY.
|
42
|
+
// The dateString is also assumed to be in the given timeZone, so "1/1/2020" in "America/Los_Angeles" timezone is
|
43
|
+
// expected to be "2020-01-01T08:00:00.000Z" in UTC time.
|
44
|
+
// This function tries to parse the dateString taking these variables into account and return a javascript Date object
|
45
|
+
// that is adjusted to be in UTC.
|
46
|
+
|
47
|
+
// Split string on '.', whitespace, '/', ',' or '-' using regex: /[.\s/.-]+/.
|
48
|
+
// The '+' allows splitting on consecutive delimiters.
|
49
|
+
// `.filter(Boolean)` is needed because some locales have a delimeter at the end (e.g.: "hu-hu")
|
50
|
+
const splitDate = dateString.split(/[,.\s/.-]+/).filter(Boolean);
|
51
|
+
|
52
|
+
// create a locale formatted new date to later extract the order and delimeter information
|
53
|
+
const localeDate = new Intl.DateTimeFormat(locale).formatToParts(new Date());
|
54
|
+
let index = 0;
|
55
|
+
let day, month, year;
|
56
|
+
localeDate.forEach(part => {
|
57
|
+
if (part.type === 'month') {
|
58
|
+
month = parseInt(splitDate[index], 10);
|
59
|
+
index++;
|
60
|
+
} else if (part.type === 'day') {
|
61
|
+
day = parseInt(splitDate[index], 10);
|
62
|
+
index++;
|
63
|
+
} else if (part.type === 'year') {
|
64
|
+
year = parseInt(splitDate[index], 10);
|
65
|
+
index++;
|
66
|
+
}
|
67
|
+
});
|
68
|
+
|
69
|
+
// sensible year limitations
|
70
|
+
if (!year || !month || !day || year < 1000 || year > 9999) return null;
|
71
|
+
|
72
|
+
// create utc date from year, month (zero indexed) and day
|
73
|
+
const date = new Date(Date.UTC(year, month - 1, day));
|
74
|
+
|
75
|
+
// Format date string in the provided timezone
|
76
|
+
const parts = new Intl.DateTimeFormat('en-US', {
|
77
|
+
timeZone,
|
78
|
+
year: 'numeric',
|
79
|
+
month: '2-digit',
|
80
|
+
day: '2-digit',
|
81
|
+
hour: '2-digit',
|
82
|
+
minute: '2-digit',
|
83
|
+
second: '2-digit',
|
84
|
+
hour12: false
|
85
|
+
}).formatToParts(date);
|
86
|
+
|
87
|
+
// Extract the date and time parts from the formatted string
|
88
|
+
const dateStringInTimezone = parts.reduce((acc, part) => {
|
89
|
+
return part.type === 'literal' ? acc : {
|
90
|
+
...acc,
|
91
|
+
[part.type]: part.value
|
92
|
+
};
|
93
|
+
}, {});
|
94
|
+
|
95
|
+
// Create a date string in the format 'YYYY-MM-DDTHH:mm:ss'
|
96
|
+
const dateInTimezone = `${dateStringInTimezone.year}-${dateStringInTimezone.month}-${dateStringInTimezone.day}T${dateStringInTimezone.hour}:${dateStringInTimezone.minute}:${dateStringInTimezone.second}`;
|
97
|
+
|
98
|
+
// Calculate time difference for timezone offset
|
99
|
+
const timeDiff = new Date(dateInTimezone + 'Z').getTime() - date.getTime();
|
100
|
+
const newTime = new Date(date.getTime() - timeDiff);
|
101
|
+
// Return the UTC Date corresponding to the time in the specified timezone
|
102
|
+
return newTime;
|
42
103
|
}
|
43
104
|
|
44
105
|
/**
|
45
106
|
---
|
46
107
|
category: components
|
47
108
|
---
|
109
|
+
|
110
|
+
@module experimental
|
48
111
|
**/
|
49
112
|
const DateInput2 = ({
|
50
113
|
renderLabel,
|
51
114
|
screenReaderLabels,
|
52
115
|
isRequired = false,
|
53
116
|
interaction = 'enabled',
|
54
|
-
size = 'medium',
|
55
117
|
isInline = false,
|
56
118
|
value,
|
57
119
|
messages,
|
@@ -59,76 +121,23 @@ const DateInput2 = ({
|
|
59
121
|
onChange,
|
60
122
|
onBlur,
|
61
123
|
withYearPicker,
|
62
|
-
onRequestValidateDate,
|
63
124
|
invalidDateErrorMessage,
|
64
125
|
locale,
|
65
126
|
timezone,
|
66
127
|
placeholder,
|
128
|
+
dateFormat,
|
129
|
+
onRequestValidateDate,
|
130
|
+
// margin, TODO enable this prop
|
67
131
|
...rest
|
68
132
|
}) => {
|
69
|
-
const _useState = useState(''),
|
70
|
-
_useState2 = _slicedToArray(_useState, 2),
|
71
|
-
selectedDate = _useState2[0],
|
72
|
-
setSelectedDate = _useState2[1];
|
73
|
-
const _useState3 = useState(messages || []),
|
74
|
-
_useState4 = _slicedToArray(_useState3, 2),
|
75
|
-
inputMessages = _useState4[0],
|
76
|
-
setInputMessages = _useState4[1];
|
77
|
-
const _useState5 = useState(false),
|
78
|
-
_useState6 = _slicedToArray(_useState5, 2),
|
79
|
-
showPopover = _useState6[0],
|
80
|
-
setShowPopover = _useState6[1];
|
81
133
|
const localeContext = useContext(ApplyLocaleContext);
|
82
|
-
useEffect(() => {
|
83
|
-
// when `value` is changed, validation runs again and removes the error message if validation passes
|
84
|
-
// but it's NOT adding error message if validation fails for better UX
|
85
|
-
validateInput(true);
|
86
|
-
}, [value]);
|
87
|
-
useEffect(() => {
|
88
|
-
setInputMessages(messages || []);
|
89
|
-
}, [messages]);
|
90
|
-
const handleInputChange = (e, value) => {
|
91
|
-
onChange === null || onChange === void 0 ? void 0 : onChange(e, value);
|
92
|
-
// blur event formats the input which should trigger parsing
|
93
|
-
if (e.type !== 'blur') {
|
94
|
-
setSelectedDate(parseDate(value));
|
95
|
-
}
|
96
|
-
};
|
97
|
-
const handleDateSelected = (dateString, _momentDate, e) => {
|
98
|
-
const formattedDate = new Date(dateString).toLocaleDateString(getLocale(), {
|
99
|
-
month: 'long',
|
100
|
-
year: 'numeric',
|
101
|
-
day: 'numeric',
|
102
|
-
timeZone: getTimezone()
|
103
|
-
});
|
104
|
-
handleInputChange(e, formattedDate);
|
105
|
-
setSelectedDate(dateString);
|
106
|
-
setShowPopover(false);
|
107
|
-
onRequestValidateDate === null || onRequestValidateDate === void 0 ? void 0 : onRequestValidateDate(dateString, true);
|
108
|
-
};
|
109
|
-
|
110
|
-
// onlyRemoveError is used to remove the error msg immediately when the user inputs a valid date (and don't wait for blur event)
|
111
|
-
const validateInput = (onlyRemoveError = false) => {
|
112
|
-
// don't validate empty input
|
113
|
-
if (!value || parseDate(value) || selectedDate) {
|
114
|
-
setInputMessages(messages || []);
|
115
|
-
return true;
|
116
|
-
}
|
117
|
-
// only show error if there is no user provided validation callback
|
118
|
-
if (!onlyRemoveError && typeof invalidDateErrorMessage === 'string' && !onRequestValidateDate) {
|
119
|
-
setInputMessages([{
|
120
|
-
type: 'error',
|
121
|
-
text: invalidDateErrorMessage
|
122
|
-
}]);
|
123
|
-
}
|
124
|
-
return false;
|
125
|
-
};
|
126
134
|
const getLocale = () => {
|
127
135
|
if (locale) {
|
128
136
|
return locale;
|
129
137
|
} else if (localeContext.locale) {
|
130
138
|
return localeContext.locale;
|
131
139
|
}
|
140
|
+
// default to the system's locale
|
132
141
|
return Locale.browserLocale();
|
133
142
|
};
|
134
143
|
const getTimezone = () => {
|
@@ -140,21 +149,86 @@ const DateInput2 = ({
|
|
140
149
|
// default to the system's timezone
|
141
150
|
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
142
151
|
};
|
152
|
+
const _useState = useState(messages || []),
|
153
|
+
_useState2 = _slicedToArray(_useState, 2),
|
154
|
+
inputMessages = _useState2[0],
|
155
|
+
setInputMessages = _useState2[1];
|
156
|
+
const _useState3 = useState(false),
|
157
|
+
_useState4 = _slicedToArray(_useState3, 2),
|
158
|
+
showPopover = _useState4[0],
|
159
|
+
setShowPopover = _useState4[1];
|
160
|
+
useEffect(() => {
|
161
|
+
setInputMessages(messages || []);
|
162
|
+
}, [messages]);
|
163
|
+
useEffect(() => {
|
164
|
+
const _parseDate = parseDate(value),
|
165
|
+
_parseDate2 = _slicedToArray(_parseDate, 2),
|
166
|
+
utcIsoDate = _parseDate2[1];
|
167
|
+
// clear error messages if date becomes valid
|
168
|
+
if (utcIsoDate || !value) {
|
169
|
+
setInputMessages(messages || []);
|
170
|
+
}
|
171
|
+
}, [value]);
|
172
|
+
const parseDate = (dateString = '') => {
|
173
|
+
let date = null;
|
174
|
+
if (dateFormat) {
|
175
|
+
if (typeof dateFormat === 'string') {
|
176
|
+
// use dateFormat instead of the user locale
|
177
|
+
date = parseLocaleDate(dateString, dateFormat, getTimezone());
|
178
|
+
} else if (dateFormat.parser) {
|
179
|
+
date = dateFormat.parser(dateString);
|
180
|
+
}
|
181
|
+
} else {
|
182
|
+
// no dateFormat prop passed, use locale for formatting
|
183
|
+
date = parseLocaleDate(dateString, getLocale(), getTimezone());
|
184
|
+
}
|
185
|
+
return date ? [formatDate(date), date.toISOString()] : ['', ''];
|
186
|
+
};
|
187
|
+
const formatDate = date => {
|
188
|
+
// use formatter function if provided
|
189
|
+
if (typeof dateFormat !== 'string' && dateFormat !== null && dateFormat !== void 0 && dateFormat.formatter) {
|
190
|
+
return dateFormat.formatter(date);
|
191
|
+
}
|
192
|
+
// if dateFormat set to a locale, use that, otherwise default to the user's locale
|
193
|
+
return date.toLocaleDateString(typeof dateFormat === 'string' ? dateFormat : getLocale(), {
|
194
|
+
timeZone: getTimezone(),
|
195
|
+
calendar: 'gregory',
|
196
|
+
numberingSystem: 'latn'
|
197
|
+
});
|
198
|
+
};
|
199
|
+
const handleInputChange = (e, newValue) => {
|
200
|
+
const _parseDate3 = parseDate(newValue),
|
201
|
+
_parseDate4 = _slicedToArray(_parseDate3, 2),
|
202
|
+
utcIsoDate = _parseDate4[1];
|
203
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(e, newValue, utcIsoDate);
|
204
|
+
};
|
205
|
+
const handleDateSelected = (dateString, _momentDate, e) => {
|
206
|
+
setShowPopover(false);
|
207
|
+
const newValue = formatDate(new Date(dateString));
|
208
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(e, newValue, dateString);
|
209
|
+
onRequestValidateDate === null || onRequestValidateDate === void 0 ? void 0 : onRequestValidateDate(e, newValue, dateString);
|
210
|
+
};
|
143
211
|
const handleBlur = e => {
|
144
|
-
const
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
}
|
152
|
-
|
212
|
+
const _parseDate5 = parseDate(value),
|
213
|
+
_parseDate6 = _slicedToArray(_parseDate5, 2),
|
214
|
+
localeDate = _parseDate6[0],
|
215
|
+
utcIsoDate = _parseDate6[1];
|
216
|
+
if (localeDate) {
|
217
|
+
if (localeDate !== value) {
|
218
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(e, localeDate, utcIsoDate);
|
219
|
+
}
|
220
|
+
} else if (value && invalidDateErrorMessage) {
|
221
|
+
setInputMessages([{
|
222
|
+
type: 'error',
|
223
|
+
text: invalidDateErrorMessage
|
224
|
+
}]);
|
153
225
|
}
|
154
|
-
onRequestValidateDate === null || onRequestValidateDate === void 0 ? void 0 : onRequestValidateDate(value,
|
155
|
-
onBlur === null || onBlur === void 0 ? void 0 : onBlur(e);
|
226
|
+
onRequestValidateDate === null || onRequestValidateDate === void 0 ? void 0 : onRequestValidateDate(e, value || '', utcIsoDate);
|
227
|
+
onBlur === null || onBlur === void 0 ? void 0 : onBlur(e, value || '', utcIsoDate);
|
156
228
|
};
|
229
|
+
const selectedDate = parseDate(value)[1];
|
157
230
|
return jsx(TextInput, Object.assign({}, passthroughProps(rest), {
|
231
|
+
// margin={'large'} TODO add this prop to TextInput
|
158
232
|
renderLabel: renderLabel,
|
159
233
|
onChange: handleInputChange,
|
160
234
|
onBlur: handleBlur,
|
@@ -162,7 +236,6 @@ const DateInput2 = ({
|
|
162
236
|
value: value,
|
163
237
|
placeholder: placeholder,
|
164
238
|
width: width,
|
165
|
-
size: size,
|
166
239
|
display: isInline ? 'inline-block' : 'block',
|
167
240
|
messages: inputMessages,
|
168
241
|
interaction: interaction,
|
@@ -172,7 +245,6 @@ const DateInput2 = ({
|
|
172
245
|
withBorder: false,
|
173
246
|
screenReaderLabel: screenReaderLabels.calendarIcon,
|
174
247
|
shape: "circle",
|
175
|
-
size: size,
|
176
248
|
interaction: interaction
|
177
249
|
}, _IconCalendarMonthLin || (_IconCalendarMonthLin = jsx(IconCalendarMonthLine, null))),
|
178
250
|
isShowingContent: showPopover,
|
@@ -187,8 +259,8 @@ const DateInput2 = ({
|
|
187
259
|
onDateSelected: handleDateSelected,
|
188
260
|
selectedDate: selectedDate,
|
189
261
|
visibleMonth: selectedDate,
|
190
|
-
locale:
|
191
|
-
timezone:
|
262
|
+
locale: getLocale(),
|
263
|
+
timezone: getTimezone(),
|
192
264
|
role: "listbox",
|
193
265
|
renderNextMonthButton: jsx(IconButton, {
|
194
266
|
size: "small",
|
package/es/DateInput2/props.js
CHANGED
@@ -29,7 +29,6 @@ const propTypes = {
|
|
29
29
|
renderLabel: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired,
|
30
30
|
screenReaderLabels: PropTypes.object.isRequired,
|
31
31
|
value: controllable(PropTypes.string),
|
32
|
-
size: PropTypes.oneOf(['small', 'medium', 'large']),
|
33
32
|
placeholder: PropTypes.string,
|
34
33
|
onChange: PropTypes.func,
|
35
34
|
onBlur: PropTypes.func,
|
@@ -38,12 +37,11 @@ const propTypes = {
|
|
38
37
|
isInline: PropTypes.bool,
|
39
38
|
width: PropTypes.string,
|
40
39
|
messages: PropTypes.arrayOf(FormPropTypes.message),
|
41
|
-
onRequestShowCalendar: PropTypes.func,
|
42
|
-
onRequestHideCalendar: PropTypes.func,
|
43
|
-
onRequestValidateDate: PropTypes.func,
|
44
40
|
invalidDateErrorMessage: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
|
45
41
|
locale: PropTypes.string,
|
46
42
|
timezone: PropTypes.string,
|
47
|
-
withYearPicker: PropTypes.object
|
43
|
+
withYearPicker: PropTypes.object,
|
44
|
+
dateFormat: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
45
|
+
onRequestValidateDate: PropTypes.func
|
48
46
|
};
|
49
47
|
export { propTypes };
|
package/lib/DateInput2/index.js
CHANGED
@@ -44,23 +44,85 @@ var _IconCalendarMonthLin, _IconArrowOpenEndSoli, _IconArrowOpenStartSo;
|
|
44
44
|
* SOFTWARE.
|
45
45
|
*/
|
46
46
|
/** @jsx jsx */
|
47
|
-
function
|
48
|
-
|
49
|
-
//
|
50
|
-
|
47
|
+
function parseLocaleDate(dateString = '', locale, timeZone) {
|
48
|
+
// This function may seem complicated but it basically does one thing:
|
49
|
+
// Given a dateString, a locale and a timeZone. The dateString is assumed to be formatted according
|
50
|
+
// to the locale. So if the locale is `en-us` the dateString is expected to be in the format of M/D/YYYY.
|
51
|
+
// The dateString is also assumed to be in the given timeZone, so "1/1/2020" in "America/Los_Angeles" timezone is
|
52
|
+
// expected to be "2020-01-01T08:00:00.000Z" in UTC time.
|
53
|
+
// This function tries to parse the dateString taking these variables into account and return a javascript Date object
|
54
|
+
// that is adjusted to be in UTC.
|
55
|
+
|
56
|
+
// Split string on '.', whitespace, '/', ',' or '-' using regex: /[.\s/.-]+/.
|
57
|
+
// The '+' allows splitting on consecutive delimiters.
|
58
|
+
// `.filter(Boolean)` is needed because some locales have a delimeter at the end (e.g.: "hu-hu")
|
59
|
+
const splitDate = dateString.split(/[,.\s/.-]+/).filter(Boolean);
|
60
|
+
|
61
|
+
// create a locale formatted new date to later extract the order and delimeter information
|
62
|
+
const localeDate = new Intl.DateTimeFormat(locale).formatToParts(new Date());
|
63
|
+
let index = 0;
|
64
|
+
let day, month, year;
|
65
|
+
localeDate.forEach(part => {
|
66
|
+
if (part.type === 'month') {
|
67
|
+
month = parseInt(splitDate[index], 10);
|
68
|
+
index++;
|
69
|
+
} else if (part.type === 'day') {
|
70
|
+
day = parseInt(splitDate[index], 10);
|
71
|
+
index++;
|
72
|
+
} else if (part.type === 'year') {
|
73
|
+
year = parseInt(splitDate[index], 10);
|
74
|
+
index++;
|
75
|
+
}
|
76
|
+
});
|
77
|
+
|
78
|
+
// sensible year limitations
|
79
|
+
if (!year || !month || !day || year < 1000 || year > 9999) return null;
|
80
|
+
|
81
|
+
// create utc date from year, month (zero indexed) and day
|
82
|
+
const date = new Date(Date.UTC(year, month - 1, day));
|
83
|
+
|
84
|
+
// Format date string in the provided timezone
|
85
|
+
const parts = new Intl.DateTimeFormat('en-US', {
|
86
|
+
timeZone,
|
87
|
+
year: 'numeric',
|
88
|
+
month: '2-digit',
|
89
|
+
day: '2-digit',
|
90
|
+
hour: '2-digit',
|
91
|
+
minute: '2-digit',
|
92
|
+
second: '2-digit',
|
93
|
+
hour12: false
|
94
|
+
}).formatToParts(date);
|
95
|
+
|
96
|
+
// Extract the date and time parts from the formatted string
|
97
|
+
const dateStringInTimezone = parts.reduce((acc, part) => {
|
98
|
+
return part.type === 'literal' ? acc : {
|
99
|
+
...acc,
|
100
|
+
[part.type]: part.value
|
101
|
+
};
|
102
|
+
}, {});
|
103
|
+
|
104
|
+
// Create a date string in the format 'YYYY-MM-DDTHH:mm:ss'
|
105
|
+
const dateInTimezone = `${dateStringInTimezone.year}-${dateStringInTimezone.month}-${dateStringInTimezone.day}T${dateStringInTimezone.hour}:${dateStringInTimezone.minute}:${dateStringInTimezone.second}`;
|
106
|
+
|
107
|
+
// Calculate time difference for timezone offset
|
108
|
+
const timeDiff = new Date(dateInTimezone + 'Z').getTime() - date.getTime();
|
109
|
+
const newTime = new Date(date.getTime() - timeDiff);
|
110
|
+
// Return the UTC Date corresponding to the time in the specified timezone
|
111
|
+
return newTime;
|
51
112
|
}
|
52
113
|
|
53
114
|
/**
|
54
115
|
---
|
55
116
|
category: components
|
56
117
|
---
|
118
|
+
|
119
|
+
@module experimental
|
57
120
|
**/
|
58
121
|
const DateInput2 = ({
|
59
122
|
renderLabel,
|
60
123
|
screenReaderLabels,
|
61
124
|
isRequired = false,
|
62
125
|
interaction = 'enabled',
|
63
|
-
size = 'medium',
|
64
126
|
isInline = false,
|
65
127
|
value,
|
66
128
|
messages,
|
@@ -68,76 +130,23 @@ const DateInput2 = ({
|
|
68
130
|
onChange,
|
69
131
|
onBlur,
|
70
132
|
withYearPicker,
|
71
|
-
onRequestValidateDate,
|
72
133
|
invalidDateErrorMessage,
|
73
134
|
locale,
|
74
135
|
timezone,
|
75
136
|
placeholder,
|
137
|
+
dateFormat,
|
138
|
+
onRequestValidateDate,
|
139
|
+
// margin, TODO enable this prop
|
76
140
|
...rest
|
77
141
|
}) => {
|
78
|
-
const _useState = (0, _react.useState)(''),
|
79
|
-
_useState2 = (0, _slicedToArray2.default)(_useState, 2),
|
80
|
-
selectedDate = _useState2[0],
|
81
|
-
setSelectedDate = _useState2[1];
|
82
|
-
const _useState3 = (0, _react.useState)(messages || []),
|
83
|
-
_useState4 = (0, _slicedToArray2.default)(_useState3, 2),
|
84
|
-
inputMessages = _useState4[0],
|
85
|
-
setInputMessages = _useState4[1];
|
86
|
-
const _useState5 = (0, _react.useState)(false),
|
87
|
-
_useState6 = (0, _slicedToArray2.default)(_useState5, 2),
|
88
|
-
showPopover = _useState6[0],
|
89
|
-
setShowPopover = _useState6[1];
|
90
142
|
const localeContext = (0, _react.useContext)(_ApplyLocaleContext.ApplyLocaleContext);
|
91
|
-
(0, _react.useEffect)(() => {
|
92
|
-
// when `value` is changed, validation runs again and removes the error message if validation passes
|
93
|
-
// but it's NOT adding error message if validation fails for better UX
|
94
|
-
validateInput(true);
|
95
|
-
}, [value]);
|
96
|
-
(0, _react.useEffect)(() => {
|
97
|
-
setInputMessages(messages || []);
|
98
|
-
}, [messages]);
|
99
|
-
const handleInputChange = (e, value) => {
|
100
|
-
onChange === null || onChange === void 0 ? void 0 : onChange(e, value);
|
101
|
-
// blur event formats the input which should trigger parsing
|
102
|
-
if (e.type !== 'blur') {
|
103
|
-
setSelectedDate(parseDate(value));
|
104
|
-
}
|
105
|
-
};
|
106
|
-
const handleDateSelected = (dateString, _momentDate, e) => {
|
107
|
-
const formattedDate = new Date(dateString).toLocaleDateString(getLocale(), {
|
108
|
-
month: 'long',
|
109
|
-
year: 'numeric',
|
110
|
-
day: 'numeric',
|
111
|
-
timeZone: getTimezone()
|
112
|
-
});
|
113
|
-
handleInputChange(e, formattedDate);
|
114
|
-
setSelectedDate(dateString);
|
115
|
-
setShowPopover(false);
|
116
|
-
onRequestValidateDate === null || onRequestValidateDate === void 0 ? void 0 : onRequestValidateDate(dateString, true);
|
117
|
-
};
|
118
|
-
|
119
|
-
// onlyRemoveError is used to remove the error msg immediately when the user inputs a valid date (and don't wait for blur event)
|
120
|
-
const validateInput = (onlyRemoveError = false) => {
|
121
|
-
// don't validate empty input
|
122
|
-
if (!value || parseDate(value) || selectedDate) {
|
123
|
-
setInputMessages(messages || []);
|
124
|
-
return true;
|
125
|
-
}
|
126
|
-
// only show error if there is no user provided validation callback
|
127
|
-
if (!onlyRemoveError && typeof invalidDateErrorMessage === 'string' && !onRequestValidateDate) {
|
128
|
-
setInputMessages([{
|
129
|
-
type: 'error',
|
130
|
-
text: invalidDateErrorMessage
|
131
|
-
}]);
|
132
|
-
}
|
133
|
-
return false;
|
134
|
-
};
|
135
143
|
const getLocale = () => {
|
136
144
|
if (locale) {
|
137
145
|
return locale;
|
138
146
|
} else if (localeContext.locale) {
|
139
147
|
return localeContext.locale;
|
140
148
|
}
|
149
|
+
// default to the system's locale
|
141
150
|
return _Locale.Locale.browserLocale();
|
142
151
|
};
|
143
152
|
const getTimezone = () => {
|
@@ -149,21 +158,86 @@ const DateInput2 = ({
|
|
149
158
|
// default to the system's timezone
|
150
159
|
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
151
160
|
};
|
161
|
+
const _useState = (0, _react.useState)(messages || []),
|
162
|
+
_useState2 = (0, _slicedToArray2.default)(_useState, 2),
|
163
|
+
inputMessages = _useState2[0],
|
164
|
+
setInputMessages = _useState2[1];
|
165
|
+
const _useState3 = (0, _react.useState)(false),
|
166
|
+
_useState4 = (0, _slicedToArray2.default)(_useState3, 2),
|
167
|
+
showPopover = _useState4[0],
|
168
|
+
setShowPopover = _useState4[1];
|
169
|
+
(0, _react.useEffect)(() => {
|
170
|
+
setInputMessages(messages || []);
|
171
|
+
}, [messages]);
|
172
|
+
(0, _react.useEffect)(() => {
|
173
|
+
const _parseDate = parseDate(value),
|
174
|
+
_parseDate2 = (0, _slicedToArray2.default)(_parseDate, 2),
|
175
|
+
utcIsoDate = _parseDate2[1];
|
176
|
+
// clear error messages if date becomes valid
|
177
|
+
if (utcIsoDate || !value) {
|
178
|
+
setInputMessages(messages || []);
|
179
|
+
}
|
180
|
+
}, [value]);
|
181
|
+
const parseDate = (dateString = '') => {
|
182
|
+
let date = null;
|
183
|
+
if (dateFormat) {
|
184
|
+
if (typeof dateFormat === 'string') {
|
185
|
+
// use dateFormat instead of the user locale
|
186
|
+
date = parseLocaleDate(dateString, dateFormat, getTimezone());
|
187
|
+
} else if (dateFormat.parser) {
|
188
|
+
date = dateFormat.parser(dateString);
|
189
|
+
}
|
190
|
+
} else {
|
191
|
+
// no dateFormat prop passed, use locale for formatting
|
192
|
+
date = parseLocaleDate(dateString, getLocale(), getTimezone());
|
193
|
+
}
|
194
|
+
return date ? [formatDate(date), date.toISOString()] : ['', ''];
|
195
|
+
};
|
196
|
+
const formatDate = date => {
|
197
|
+
// use formatter function if provided
|
198
|
+
if (typeof dateFormat !== 'string' && dateFormat !== null && dateFormat !== void 0 && dateFormat.formatter) {
|
199
|
+
return dateFormat.formatter(date);
|
200
|
+
}
|
201
|
+
// if dateFormat set to a locale, use that, otherwise default to the user's locale
|
202
|
+
return date.toLocaleDateString(typeof dateFormat === 'string' ? dateFormat : getLocale(), {
|
203
|
+
timeZone: getTimezone(),
|
204
|
+
calendar: 'gregory',
|
205
|
+
numberingSystem: 'latn'
|
206
|
+
});
|
207
|
+
};
|
208
|
+
const handleInputChange = (e, newValue) => {
|
209
|
+
const _parseDate3 = parseDate(newValue),
|
210
|
+
_parseDate4 = (0, _slicedToArray2.default)(_parseDate3, 2),
|
211
|
+
utcIsoDate = _parseDate4[1];
|
212
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(e, newValue, utcIsoDate);
|
213
|
+
};
|
214
|
+
const handleDateSelected = (dateString, _momentDate, e) => {
|
215
|
+
setShowPopover(false);
|
216
|
+
const newValue = formatDate(new Date(dateString));
|
217
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(e, newValue, dateString);
|
218
|
+
onRequestValidateDate === null || onRequestValidateDate === void 0 ? void 0 : onRequestValidateDate(e, newValue, dateString);
|
219
|
+
};
|
152
220
|
const handleBlur = e => {
|
153
|
-
const
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
}
|
161
|
-
|
221
|
+
const _parseDate5 = parseDate(value),
|
222
|
+
_parseDate6 = (0, _slicedToArray2.default)(_parseDate5, 2),
|
223
|
+
localeDate = _parseDate6[0],
|
224
|
+
utcIsoDate = _parseDate6[1];
|
225
|
+
if (localeDate) {
|
226
|
+
if (localeDate !== value) {
|
227
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(e, localeDate, utcIsoDate);
|
228
|
+
}
|
229
|
+
} else if (value && invalidDateErrorMessage) {
|
230
|
+
setInputMessages([{
|
231
|
+
type: 'error',
|
232
|
+
text: invalidDateErrorMessage
|
233
|
+
}]);
|
162
234
|
}
|
163
|
-
onRequestValidateDate === null || onRequestValidateDate === void 0 ? void 0 : onRequestValidateDate(value,
|
164
|
-
onBlur === null || onBlur === void 0 ? void 0 : onBlur(e);
|
235
|
+
onRequestValidateDate === null || onRequestValidateDate === void 0 ? void 0 : onRequestValidateDate(e, value || '', utcIsoDate);
|
236
|
+
onBlur === null || onBlur === void 0 ? void 0 : onBlur(e, value || '', utcIsoDate);
|
165
237
|
};
|
238
|
+
const selectedDate = parseDate(value)[1];
|
166
239
|
return (0, _emotion.jsx)(_TextInput.TextInput, Object.assign({}, (0, _passthroughProps.passthroughProps)(rest), {
|
240
|
+
// margin={'large'} TODO add this prop to TextInput
|
167
241
|
renderLabel: renderLabel,
|
168
242
|
onChange: handleInputChange,
|
169
243
|
onBlur: handleBlur,
|
@@ -171,7 +245,6 @@ const DateInput2 = ({
|
|
171
245
|
value: value,
|
172
246
|
placeholder: placeholder,
|
173
247
|
width: width,
|
174
|
-
size: size,
|
175
248
|
display: isInline ? 'inline-block' : 'block',
|
176
249
|
messages: inputMessages,
|
177
250
|
interaction: interaction,
|
@@ -181,7 +254,6 @@ const DateInput2 = ({
|
|
181
254
|
withBorder: false,
|
182
255
|
screenReaderLabel: screenReaderLabels.calendarIcon,
|
183
256
|
shape: "circle",
|
184
|
-
size: size,
|
185
257
|
interaction: interaction
|
186
258
|
}, _IconCalendarMonthLin || (_IconCalendarMonthLin = (0, _emotion.jsx)(_IconCalendarMonthLine.IconCalendarMonthLine, null))),
|
187
259
|
isShowingContent: showPopover,
|
@@ -196,8 +268,8 @@ const DateInput2 = ({
|
|
196
268
|
onDateSelected: handleDateSelected,
|
197
269
|
selectedDate: selectedDate,
|
198
270
|
visibleMonth: selectedDate,
|
199
|
-
locale:
|
200
|
-
timezone:
|
271
|
+
locale: getLocale(),
|
272
|
+
timezone: getTimezone(),
|
201
273
|
role: "listbox",
|
202
274
|
renderNextMonthButton: (0, _emotion.jsx)(_IconButton.IconButton, {
|
203
275
|
size: "small",
|
package/lib/DateInput2/props.js
CHANGED
@@ -36,7 +36,6 @@ const propTypes = exports.propTypes = {
|
|
36
36
|
renderLabel: _propTypes.default.oneOfType([_propTypes.default.node, _propTypes.default.func]).isRequired,
|
37
37
|
screenReaderLabels: _propTypes.default.object.isRequired,
|
38
38
|
value: (0, _controllable.controllable)(_propTypes.default.string),
|
39
|
-
size: _propTypes.default.oneOf(['small', 'medium', 'large']),
|
40
39
|
placeholder: _propTypes.default.string,
|
41
40
|
onChange: _propTypes.default.func,
|
42
41
|
onBlur: _propTypes.default.func,
|
@@ -45,11 +44,10 @@ const propTypes = exports.propTypes = {
|
|
45
44
|
isInline: _propTypes.default.bool,
|
46
45
|
width: _propTypes.default.string,
|
47
46
|
messages: _propTypes.default.arrayOf(_FormPropTypes.FormPropTypes.message),
|
48
|
-
onRequestShowCalendar: _propTypes.default.func,
|
49
|
-
onRequestHideCalendar: _propTypes.default.func,
|
50
|
-
onRequestValidateDate: _propTypes.default.func,
|
51
47
|
invalidDateErrorMessage: _propTypes.default.oneOfType([_propTypes.default.func, _propTypes.default.string]),
|
52
48
|
locale: _propTypes.default.string,
|
53
49
|
timezone: _propTypes.default.string,
|
54
|
-
withYearPicker: _propTypes.default.object
|
50
|
+
withYearPicker: _propTypes.default.object,
|
51
|
+
dateFormat: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.object]),
|
52
|
+
onRequestValidateDate: _propTypes.default.func
|
55
53
|
};
|