@instructure/ui-date-input 9.6.0 → 9.6.1-snapshot-2
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 +11 -0
- package/es/DateInput2/index.js +74 -48
- package/es/DateInput2/props.js +0 -2
- package/lib/DateInput2/index.js +74 -48
- package/lib/DateInput2/props.js +0 -2
- package/package.json +20 -20
- package/src/DateInput2/README.md +62 -29
- package/src/DateInput2/index.tsx +74 -53
- package/src/DateInput2/props.ts +8 -19
- package/tsconfig.build.tsbuildinfo +1 -1
- package/types/DateInput2/index.d.ts +4 -4
- package/types/DateInput2/index.d.ts.map +1 -1
- package/types/DateInput2/props.d.ts +6 -15
- package/types/DateInput2/props.d.ts.map +1 -1
package/CHANGELOG.md
CHANGED
@@ -3,6 +3,17 @@
|
|
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-snapshot-2](https://github.com/instructure/instructure-ui/compare/v9.6.0...v9.6.1-snapshot-2) (2024-09-06)
|
7
|
+
|
8
|
+
|
9
|
+
### Features
|
10
|
+
|
11
|
+
* **ui-date-input:** improve DateInput2 api, extend docs ([42c83a2](https://github.com/instructure/instructure-ui/commit/42c83a240434dd52233c482292caa556babb5c3a))
|
12
|
+
|
13
|
+
|
14
|
+
|
15
|
+
|
16
|
+
|
6
17
|
# [9.6.0](https://github.com/instructure/instructure-ui/compare/v9.5.2...v9.6.0) (2024-08-14)
|
7
18
|
|
8
19
|
|
package/es/DateInput2/index.js
CHANGED
@@ -35,9 +35,37 @@ 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
|
-
|
38
|
+
/*
|
39
|
+
* Tries parsing a date in a given timezone, if it's not possible, returns an empty string
|
40
|
+
* If parsing is successful an ISO formatted datetime string is returned in UTC timezone
|
41
|
+
*/
|
42
|
+
function timezoneDateToUtc(dateString, timezone) {
|
43
|
+
// Don't try to parse short dateString, they are incomplete
|
44
|
+
if (dateString.length < 10) {
|
45
|
+
return '';
|
46
|
+
}
|
47
|
+
|
48
|
+
// Create a Date object from the input date string
|
39
49
|
const date = new Date(dateString);
|
40
|
-
|
50
|
+
|
51
|
+
// Check if the date is valid
|
52
|
+
if (isNaN(date.getTime())) {
|
53
|
+
return '';
|
54
|
+
}
|
55
|
+
|
56
|
+
// snippet from https://stackoverflow.com/a/57842203
|
57
|
+
// but it might need to be improved:
|
58
|
+
// "This produces incorrect datetimes for several hours surrounding daylight saving times if the
|
59
|
+
// computer running the code is in a zone that doesn't obey the same daylight saving shifts as the target zone."
|
60
|
+
const utcDate = new Date(date.toLocaleString('en-US', {
|
61
|
+
timeZone: 'UTC'
|
62
|
+
}));
|
63
|
+
const tzDate = new Date(date.toLocaleString('en-US', {
|
64
|
+
timeZone: timezone
|
65
|
+
}));
|
66
|
+
const offset = utcDate.getTime() - tzDate.getTime();
|
67
|
+
const newDate = new Date(date.getTime() + offset);
|
68
|
+
return newDate.toISOString();
|
41
69
|
}
|
42
70
|
function defaultDateFormatter(dateString, locale, timezone) {
|
43
71
|
return new Date(dateString).toLocaleDateString(locale, {
|
@@ -52,6 +80,8 @@ function defaultDateFormatter(dateString, locale, timezone) {
|
|
52
80
|
---
|
53
81
|
category: components
|
54
82
|
---
|
83
|
+
|
84
|
+
@module experimental
|
55
85
|
**/
|
56
86
|
const DateInput2 = ({
|
57
87
|
renderLabel,
|
@@ -75,10 +105,29 @@ const DateInput2 = ({
|
|
75
105
|
// margin, TODO enable this prop
|
76
106
|
...rest
|
77
107
|
}) => {
|
78
|
-
const
|
108
|
+
const localeContext = useContext(ApplyLocaleContext);
|
109
|
+
const getLocale = () => {
|
110
|
+
if (locale) {
|
111
|
+
return locale;
|
112
|
+
} else if (localeContext.locale) {
|
113
|
+
return localeContext.locale;
|
114
|
+
}
|
115
|
+
// default to the system's locale
|
116
|
+
return Locale.browserLocale();
|
117
|
+
};
|
118
|
+
const getTimezone = () => {
|
119
|
+
if (timezone) {
|
120
|
+
return timezone;
|
121
|
+
} else if (localeContext.timezone) {
|
122
|
+
return localeContext.timezone;
|
123
|
+
}
|
124
|
+
// default to the system's timezone
|
125
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
126
|
+
};
|
127
|
+
const _useState = useState(value ? formatDate(value, getLocale(), getTimezone()) : ''),
|
79
128
|
_useState2 = _slicedToArray(_useState, 2),
|
80
|
-
|
81
|
-
|
129
|
+
inputValue = _useState2[0],
|
130
|
+
setInputValue = _useState2[1];
|
82
131
|
const _useState3 = useState(messages || []),
|
83
132
|
_useState4 = _slicedToArray(_useState3, 2),
|
84
133
|
inputMessages = _useState4[0],
|
@@ -87,38 +136,29 @@ const DateInput2 = ({
|
|
87
136
|
_useState6 = _slicedToArray(_useState5, 2),
|
88
137
|
showPopover = _useState6[0],
|
89
138
|
setShowPopover = _useState6[1];
|
90
|
-
const localeContext = useContext(ApplyLocaleContext);
|
91
|
-
useEffect(() => {
|
92
|
-
// when `value` is changed, validation removes the error message if passes
|
93
|
-
// but it's NOT adding error message if validation fails for better UX
|
94
|
-
validateInput(true);
|
95
|
-
}, [value]);
|
96
139
|
useEffect(() => {
|
97
140
|
setInputMessages(messages || []);
|
98
141
|
}, [messages]);
|
99
142
|
useEffect(() => {
|
100
|
-
|
101
|
-
}, []);
|
102
|
-
const handleInputChange = (e, newValue
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
}
|
107
|
-
onChange === null || onChange === void 0 ? void 0 : onChange(e, newValue, parsedDate);
|
143
|
+
validateInput(true);
|
144
|
+
}, [inputValue]);
|
145
|
+
const handleInputChange = (e, newValue) => {
|
146
|
+
setInputValue(newValue);
|
147
|
+
const parsedInput = timezoneDateToUtc(newValue, getTimezone());
|
148
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(e, parsedInput, newValue);
|
108
149
|
};
|
109
150
|
const handleDateSelected = (dateString, _momentDate, e) => {
|
110
151
|
const formattedDate = formatDate(dateString, getLocale(), getTimezone());
|
111
|
-
|
112
|
-
setSelectedDate(parsedDate);
|
113
|
-
handleInputChange(e, formattedDate, parsedDate);
|
152
|
+
setInputValue(formattedDate);
|
114
153
|
setShowPopover(false);
|
154
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(e, dateString, formattedDate);
|
115
155
|
onRequestValidateDate === null || onRequestValidateDate === void 0 ? void 0 : onRequestValidateDate(dateString, true);
|
116
156
|
};
|
117
157
|
|
118
158
|
// onlyRemoveError is used to remove the error msg immediately when the user inputs a valid date (and don't wait for blur event)
|
119
159
|
const validateInput = (onlyRemoveError = false) => {
|
120
160
|
// don't validate empty input
|
121
|
-
if (!
|
161
|
+
if (!inputValue || timezoneDateToUtc(inputValue, getTimezone()) || value) {
|
122
162
|
setInputMessages(messages || []);
|
123
163
|
return true;
|
124
164
|
}
|
@@ -131,30 +171,16 @@ const DateInput2 = ({
|
|
131
171
|
}
|
132
172
|
return false;
|
133
173
|
};
|
134
|
-
const getLocale = () => {
|
135
|
-
if (locale) {
|
136
|
-
return locale;
|
137
|
-
} else if (localeContext.locale) {
|
138
|
-
return localeContext.locale;
|
139
|
-
}
|
140
|
-
return Locale.browserLocale();
|
141
|
-
};
|
142
|
-
const getTimezone = () => {
|
143
|
-
if (timezone) {
|
144
|
-
return timezone;
|
145
|
-
} else if (localeContext.timezone) {
|
146
|
-
return localeContext.timezone;
|
147
|
-
}
|
148
|
-
// default to the system's timezone
|
149
|
-
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
150
|
-
};
|
151
174
|
const handleBlur = e => {
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
175
|
+
if (value) {
|
176
|
+
const formattedDate = formatDate(value, getLocale(), getTimezone());
|
177
|
+
if (formattedDate !== inputValue) {
|
178
|
+
setInputValue(formattedDate);
|
179
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(e, value, formattedDate);
|
180
|
+
}
|
156
181
|
}
|
157
|
-
|
182
|
+
validateInput(false);
|
183
|
+
onRequestValidateDate === null || onRequestValidateDate === void 0 ? void 0 : onRequestValidateDate(value, !!value);
|
158
184
|
onBlur === null || onBlur === void 0 ? void 0 : onBlur(e);
|
159
185
|
};
|
160
186
|
return jsx(TextInput, Object.assign({}, passthroughProps(rest), {
|
@@ -163,7 +189,7 @@ const DateInput2 = ({
|
|
163
189
|
onChange: handleInputChange,
|
164
190
|
onBlur: handleBlur,
|
165
191
|
isRequired: isRequired,
|
166
|
-
value:
|
192
|
+
value: inputValue,
|
167
193
|
placeholder: placeholder,
|
168
194
|
width: width,
|
169
195
|
size: size,
|
@@ -189,8 +215,8 @@ const DateInput2 = ({
|
|
189
215
|
}, jsx(Calendar, {
|
190
216
|
withYearPicker: withYearPicker,
|
191
217
|
onDateSelected: handleDateSelected,
|
192
|
-
selectedDate:
|
193
|
-
visibleMonth:
|
218
|
+
selectedDate: value,
|
219
|
+
visibleMonth: value,
|
194
220
|
locale: getLocale(),
|
195
221
|
timezone: getTimezone(),
|
196
222
|
role: "listbox",
|
package/es/DateInput2/props.js
CHANGED
@@ -38,8 +38,6 @@ const propTypes = {
|
|
38
38
|
isInline: PropTypes.bool,
|
39
39
|
width: PropTypes.string,
|
40
40
|
messages: PropTypes.arrayOf(FormPropTypes.message),
|
41
|
-
onRequestShowCalendar: PropTypes.func,
|
42
|
-
onRequestHideCalendar: PropTypes.func,
|
43
41
|
onRequestValidateDate: PropTypes.func,
|
44
42
|
invalidDateErrorMessage: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
|
45
43
|
locale: PropTypes.string,
|
package/lib/DateInput2/index.js
CHANGED
@@ -44,9 +44,37 @@ var _IconCalendarMonthLin, _IconArrowOpenEndSoli, _IconArrowOpenStartSo;
|
|
44
44
|
* SOFTWARE.
|
45
45
|
*/
|
46
46
|
/** @jsx jsx */
|
47
|
-
|
47
|
+
/*
|
48
|
+
* Tries parsing a date in a given timezone, if it's not possible, returns an empty string
|
49
|
+
* If parsing is successful an ISO formatted datetime string is returned in UTC timezone
|
50
|
+
*/
|
51
|
+
function timezoneDateToUtc(dateString, timezone) {
|
52
|
+
// Don't try to parse short dateString, they are incomplete
|
53
|
+
if (dateString.length < 10) {
|
54
|
+
return '';
|
55
|
+
}
|
56
|
+
|
57
|
+
// Create a Date object from the input date string
|
48
58
|
const date = new Date(dateString);
|
49
|
-
|
59
|
+
|
60
|
+
// Check if the date is valid
|
61
|
+
if (isNaN(date.getTime())) {
|
62
|
+
return '';
|
63
|
+
}
|
64
|
+
|
65
|
+
// snippet from https://stackoverflow.com/a/57842203
|
66
|
+
// but it might need to be improved:
|
67
|
+
// "This produces incorrect datetimes for several hours surrounding daylight saving times if the
|
68
|
+
// computer running the code is in a zone that doesn't obey the same daylight saving shifts as the target zone."
|
69
|
+
const utcDate = new Date(date.toLocaleString('en-US', {
|
70
|
+
timeZone: 'UTC'
|
71
|
+
}));
|
72
|
+
const tzDate = new Date(date.toLocaleString('en-US', {
|
73
|
+
timeZone: timezone
|
74
|
+
}));
|
75
|
+
const offset = utcDate.getTime() - tzDate.getTime();
|
76
|
+
const newDate = new Date(date.getTime() + offset);
|
77
|
+
return newDate.toISOString();
|
50
78
|
}
|
51
79
|
function defaultDateFormatter(dateString, locale, timezone) {
|
52
80
|
return new Date(dateString).toLocaleDateString(locale, {
|
@@ -61,6 +89,8 @@ function defaultDateFormatter(dateString, locale, timezone) {
|
|
61
89
|
---
|
62
90
|
category: components
|
63
91
|
---
|
92
|
+
|
93
|
+
@module experimental
|
64
94
|
**/
|
65
95
|
const DateInput2 = ({
|
66
96
|
renderLabel,
|
@@ -84,10 +114,29 @@ const DateInput2 = ({
|
|
84
114
|
// margin, TODO enable this prop
|
85
115
|
...rest
|
86
116
|
}) => {
|
87
|
-
const
|
117
|
+
const localeContext = (0, _react.useContext)(_ApplyLocaleContext.ApplyLocaleContext);
|
118
|
+
const getLocale = () => {
|
119
|
+
if (locale) {
|
120
|
+
return locale;
|
121
|
+
} else if (localeContext.locale) {
|
122
|
+
return localeContext.locale;
|
123
|
+
}
|
124
|
+
// default to the system's locale
|
125
|
+
return _Locale.Locale.browserLocale();
|
126
|
+
};
|
127
|
+
const getTimezone = () => {
|
128
|
+
if (timezone) {
|
129
|
+
return timezone;
|
130
|
+
} else if (localeContext.timezone) {
|
131
|
+
return localeContext.timezone;
|
132
|
+
}
|
133
|
+
// default to the system's timezone
|
134
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
135
|
+
};
|
136
|
+
const _useState = (0, _react.useState)(value ? formatDate(value, getLocale(), getTimezone()) : ''),
|
88
137
|
_useState2 = (0, _slicedToArray2.default)(_useState, 2),
|
89
|
-
|
90
|
-
|
138
|
+
inputValue = _useState2[0],
|
139
|
+
setInputValue = _useState2[1];
|
91
140
|
const _useState3 = (0, _react.useState)(messages || []),
|
92
141
|
_useState4 = (0, _slicedToArray2.default)(_useState3, 2),
|
93
142
|
inputMessages = _useState4[0],
|
@@ -96,38 +145,29 @@ const DateInput2 = ({
|
|
96
145
|
_useState6 = (0, _slicedToArray2.default)(_useState5, 2),
|
97
146
|
showPopover = _useState6[0],
|
98
147
|
setShowPopover = _useState6[1];
|
99
|
-
const localeContext = (0, _react.useContext)(_ApplyLocaleContext.ApplyLocaleContext);
|
100
|
-
(0, _react.useEffect)(() => {
|
101
|
-
// when `value` is changed, validation removes the error message if passes
|
102
|
-
// but it's NOT adding error message if validation fails for better UX
|
103
|
-
validateInput(true);
|
104
|
-
}, [value]);
|
105
148
|
(0, _react.useEffect)(() => {
|
106
149
|
setInputMessages(messages || []);
|
107
150
|
}, [messages]);
|
108
151
|
(0, _react.useEffect)(() => {
|
109
|
-
|
110
|
-
}, []);
|
111
|
-
const handleInputChange = (e, newValue
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
}
|
116
|
-
onChange === null || onChange === void 0 ? void 0 : onChange(e, newValue, parsedDate);
|
152
|
+
validateInput(true);
|
153
|
+
}, [inputValue]);
|
154
|
+
const handleInputChange = (e, newValue) => {
|
155
|
+
setInputValue(newValue);
|
156
|
+
const parsedInput = timezoneDateToUtc(newValue, getTimezone());
|
157
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(e, parsedInput, newValue);
|
117
158
|
};
|
118
159
|
const handleDateSelected = (dateString, _momentDate, e) => {
|
119
160
|
const formattedDate = formatDate(dateString, getLocale(), getTimezone());
|
120
|
-
|
121
|
-
setSelectedDate(parsedDate);
|
122
|
-
handleInputChange(e, formattedDate, parsedDate);
|
161
|
+
setInputValue(formattedDate);
|
123
162
|
setShowPopover(false);
|
163
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(e, dateString, formattedDate);
|
124
164
|
onRequestValidateDate === null || onRequestValidateDate === void 0 ? void 0 : onRequestValidateDate(dateString, true);
|
125
165
|
};
|
126
166
|
|
127
167
|
// onlyRemoveError is used to remove the error msg immediately when the user inputs a valid date (and don't wait for blur event)
|
128
168
|
const validateInput = (onlyRemoveError = false) => {
|
129
169
|
// don't validate empty input
|
130
|
-
if (!
|
170
|
+
if (!inputValue || timezoneDateToUtc(inputValue, getTimezone()) || value) {
|
131
171
|
setInputMessages(messages || []);
|
132
172
|
return true;
|
133
173
|
}
|
@@ -140,30 +180,16 @@ const DateInput2 = ({
|
|
140
180
|
}
|
141
181
|
return false;
|
142
182
|
};
|
143
|
-
const getLocale = () => {
|
144
|
-
if (locale) {
|
145
|
-
return locale;
|
146
|
-
} else if (localeContext.locale) {
|
147
|
-
return localeContext.locale;
|
148
|
-
}
|
149
|
-
return _Locale.Locale.browserLocale();
|
150
|
-
};
|
151
|
-
const getTimezone = () => {
|
152
|
-
if (timezone) {
|
153
|
-
return timezone;
|
154
|
-
} else if (localeContext.timezone) {
|
155
|
-
return localeContext.timezone;
|
156
|
-
}
|
157
|
-
// default to the system's timezone
|
158
|
-
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
159
|
-
};
|
160
183
|
const handleBlur = e => {
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
184
|
+
if (value) {
|
185
|
+
const formattedDate = formatDate(value, getLocale(), getTimezone());
|
186
|
+
if (formattedDate !== inputValue) {
|
187
|
+
setInputValue(formattedDate);
|
188
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(e, value, formattedDate);
|
189
|
+
}
|
165
190
|
}
|
166
|
-
|
191
|
+
validateInput(false);
|
192
|
+
onRequestValidateDate === null || onRequestValidateDate === void 0 ? void 0 : onRequestValidateDate(value, !!value);
|
167
193
|
onBlur === null || onBlur === void 0 ? void 0 : onBlur(e);
|
168
194
|
};
|
169
195
|
return (0, _emotion.jsx)(_TextInput.TextInput, Object.assign({}, (0, _passthroughProps.passthroughProps)(rest), {
|
@@ -172,7 +198,7 @@ const DateInput2 = ({
|
|
172
198
|
onChange: handleInputChange,
|
173
199
|
onBlur: handleBlur,
|
174
200
|
isRequired: isRequired,
|
175
|
-
value:
|
201
|
+
value: inputValue,
|
176
202
|
placeholder: placeholder,
|
177
203
|
width: width,
|
178
204
|
size: size,
|
@@ -198,8 +224,8 @@ const DateInput2 = ({
|
|
198
224
|
}, (0, _emotion.jsx)(_Calendar.Calendar, {
|
199
225
|
withYearPicker: withYearPicker,
|
200
226
|
onDateSelected: handleDateSelected,
|
201
|
-
selectedDate:
|
202
|
-
visibleMonth:
|
227
|
+
selectedDate: value,
|
228
|
+
visibleMonth: value,
|
203
229
|
locale: getLocale(),
|
204
230
|
timezone: getTimezone(),
|
205
231
|
role: "listbox",
|
package/lib/DateInput2/props.js
CHANGED
@@ -45,8 +45,6 @@ const propTypes = exports.propTypes = {
|
|
45
45
|
isInline: _propTypes.default.bool,
|
46
46
|
width: _propTypes.default.string,
|
47
47
|
messages: _propTypes.default.arrayOf(_FormPropTypes.FormPropTypes.message),
|
48
|
-
onRequestShowCalendar: _propTypes.default.func,
|
49
|
-
onRequestHideCalendar: _propTypes.default.func,
|
50
48
|
onRequestValidateDate: _propTypes.default.func,
|
51
49
|
invalidDateErrorMessage: _propTypes.default.oneOfType([_propTypes.default.func, _propTypes.default.string]),
|
52
50
|
locale: _propTypes.default.string,
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@instructure/ui-date-input",
|
3
|
-
"version": "9.6.
|
3
|
+
"version": "9.6.1-snapshot-2",
|
4
4
|
"description": "A UI component library made by Instructure Inc.",
|
5
5
|
"author": "Instructure, Inc. Engineering and Product Design",
|
6
6
|
"module": "./es/index.js",
|
@@ -23,11 +23,11 @@
|
|
23
23
|
},
|
24
24
|
"license": "MIT",
|
25
25
|
"devDependencies": {
|
26
|
-
"@instructure/ui-axe-check": "9.6.
|
27
|
-
"@instructure/ui-babel-preset": "9.6.
|
28
|
-
"@instructure/ui-buttons": "9.6.
|
29
|
-
"@instructure/ui-scripts": "9.6.
|
30
|
-
"@instructure/ui-test-utils": "9.6.
|
26
|
+
"@instructure/ui-axe-check": "9.6.1-snapshot-2",
|
27
|
+
"@instructure/ui-babel-preset": "9.6.1-snapshot-2",
|
28
|
+
"@instructure/ui-buttons": "9.6.1-snapshot-2",
|
29
|
+
"@instructure/ui-scripts": "9.6.1-snapshot-2",
|
30
|
+
"@instructure/ui-test-utils": "9.6.1-snapshot-2",
|
31
31
|
"@testing-library/jest-dom": "^6.4.6",
|
32
32
|
"@testing-library/react": "^15.0.7",
|
33
33
|
"@testing-library/user-event": "^14.5.2",
|
@@ -35,20 +35,20 @@
|
|
35
35
|
},
|
36
36
|
"dependencies": {
|
37
37
|
"@babel/runtime": "^7.24.5",
|
38
|
-
"@instructure/emotion": "9.6.
|
39
|
-
"@instructure/shared-types": "9.6.
|
40
|
-
"@instructure/ui-calendar": "9.6.
|
41
|
-
"@instructure/ui-form-field": "9.6.
|
42
|
-
"@instructure/ui-i18n": "9.6.
|
43
|
-
"@instructure/ui-icons": "9.6.
|
44
|
-
"@instructure/ui-popover": "9.6.
|
45
|
-
"@instructure/ui-position": "9.6.
|
46
|
-
"@instructure/ui-prop-types": "9.6.
|
47
|
-
"@instructure/ui-react-utils": "9.6.
|
48
|
-
"@instructure/ui-selectable": "9.6.
|
49
|
-
"@instructure/ui-testable": "9.6.
|
50
|
-
"@instructure/ui-text-input": "9.6.
|
51
|
-
"@instructure/ui-utils": "9.6.
|
38
|
+
"@instructure/emotion": "9.6.1-snapshot-2",
|
39
|
+
"@instructure/shared-types": "9.6.1-snapshot-2",
|
40
|
+
"@instructure/ui-calendar": "9.6.1-snapshot-2",
|
41
|
+
"@instructure/ui-form-field": "9.6.1-snapshot-2",
|
42
|
+
"@instructure/ui-i18n": "9.6.1-snapshot-2",
|
43
|
+
"@instructure/ui-icons": "9.6.1-snapshot-2",
|
44
|
+
"@instructure/ui-popover": "9.6.1-snapshot-2",
|
45
|
+
"@instructure/ui-position": "9.6.1-snapshot-2",
|
46
|
+
"@instructure/ui-prop-types": "9.6.1-snapshot-2",
|
47
|
+
"@instructure/ui-react-utils": "9.6.1-snapshot-2",
|
48
|
+
"@instructure/ui-selectable": "9.6.1-snapshot-2",
|
49
|
+
"@instructure/ui-testable": "9.6.1-snapshot-2",
|
50
|
+
"@instructure/ui-text-input": "9.6.1-snapshot-2",
|
51
|
+
"@instructure/ui-utils": "9.6.1-snapshot-2",
|
52
52
|
"moment-timezone": "^0.5.45",
|
53
53
|
"prop-types": "^15.8.1"
|
54
54
|
},
|
package/src/DateInput2/README.md
CHANGED
@@ -2,16 +2,53 @@
|
|
2
2
|
describes: DateInput2
|
3
3
|
---
|
4
4
|
|
5
|
-
|
5
|
+
`DateInput2` is an experimental upgrade to the existing [`DateInput`](/#DateInput) component, offering easier configuration, better UX, improved accessibility, and a year picker. While it addresses key limitations of `DateInput`, it's still in the experimental phase, with some missing unit tests and potential (though unlikely) API changes.
|
6
|
+
|
7
|
+
`DateInput` will be deprecated in the future, but for now, developers can start using `DateInput2` and provide feedback.
|
6
8
|
|
7
9
|
### Minimal config
|
8
10
|
|
9
11
|
- ```js
|
10
12
|
class Example extends React.Component {
|
11
|
-
|
13
|
+
initialDate = '2024-09-09T14:00:00.000Z'
|
14
|
+
state = { dateString: this.initialDate, inputValue: '' }
|
12
15
|
|
13
16
|
render() {
|
14
17
|
return (
|
18
|
+
<div>
|
19
|
+
<DateInput2
|
20
|
+
renderLabel="Choose a date"
|
21
|
+
screenReaderLabels={{
|
22
|
+
calendarIcon: 'Calendar',
|
23
|
+
nextMonthButton: 'Next month',
|
24
|
+
prevMonthButton: 'Previous month'
|
25
|
+
}}
|
26
|
+
value={this.state.dateString}
|
27
|
+
width="20rem"
|
28
|
+
onChange={(e, dateString, inputValue) => {
|
29
|
+
this.setState({ dateString, inputValue })
|
30
|
+
}}
|
31
|
+
invalidDateErrorMessage="Invalid date"
|
32
|
+
/>
|
33
|
+
<p>
|
34
|
+
UTC Date String: <code>{this.state.dateString}</code>
|
35
|
+
<br />
|
36
|
+
Input Value: <code>{this.state.inputValue}</code>
|
37
|
+
</p>
|
38
|
+
</div>
|
39
|
+
)
|
40
|
+
}
|
41
|
+
}
|
42
|
+
|
43
|
+
render(<Example />)
|
44
|
+
```
|
45
|
+
|
46
|
+
- ```js
|
47
|
+
const Example = () => {
|
48
|
+
const [dateString, setDateString] = useState('')
|
49
|
+
const [inputValue, setInputValue] = useState('')
|
50
|
+
return (
|
51
|
+
<div>
|
15
52
|
<DateInput2
|
16
53
|
renderLabel="Choose a date"
|
17
54
|
screenReaderLabels={{
|
@@ -19,39 +56,35 @@ This component is an updated version of [`DateInput`](/#DateInput) that's easier
|
|
19
56
|
nextMonthButton: 'Next month',
|
20
57
|
prevMonthButton: 'Previous month'
|
21
58
|
}}
|
22
|
-
value={
|
59
|
+
value={dateString}
|
23
60
|
width="20rem"
|
24
|
-
onChange={(e,
|
61
|
+
onChange={(e, newDateString, newInputValue) => {
|
62
|
+
setDateString(newDateString)
|
63
|
+
setInputValue(newInputValue)
|
64
|
+
}}
|
25
65
|
invalidDateErrorMessage="Invalid date"
|
26
66
|
/>
|
27
|
-
|
28
|
-
|
67
|
+
<p>
|
68
|
+
UTC Date String: <code>{dateString}</code>
|
69
|
+
<br />
|
70
|
+
Input Value: <code>{inputValue}</code>
|
71
|
+
</p>
|
72
|
+
</div>
|
73
|
+
)
|
29
74
|
}
|
30
75
|
|
31
76
|
render(<Example />)
|
32
77
|
```
|
33
78
|
|
34
|
-
|
35
|
-
const Example = () => {
|
36
|
-
const [value, setValue] = useState('')
|
37
|
-
return (
|
38
|
-
<DateInput2
|
39
|
-
renderLabel="Choose a date"
|
40
|
-
screenReaderLabels={{
|
41
|
-
calendarIcon: 'Calendar',
|
42
|
-
nextMonthButton: 'Next month',
|
43
|
-
prevMonthButton: 'Previous month'
|
44
|
-
}}
|
45
|
-
value={value}
|
46
|
-
width="20rem"
|
47
|
-
onChange={(e, value) => setValue(value)}
|
48
|
-
invalidDateErrorMessage="Invalid date"
|
49
|
-
/>
|
50
|
-
)
|
51
|
-
}
|
79
|
+
### Timezones and UTC
|
52
80
|
|
53
|
-
|
54
|
-
|
81
|
+
In the example above you can see that the date is set via the `value` prop and returned from the `onChange` callback. This date is expected to be in UTC timezone. So if a user chooses September 10th 2024 with the timezone 'Europe/Budapest', the `onChange` function will return `2024-09-09T22:00:00.000Z` because Budapest is two hours ahead of UTC.
|
82
|
+
|
83
|
+
Altought it would be nice to use the date picker without timezones and leave the time out alltogether but unfortunately you cannot decouple time from dates since the timezone determines when a day ends and another starts. In certain cases this changes the month or even the year. This can affect how you store and load dates from your database: if you want to set a saved date and that date is already timezone adjusted, you have to set it to utc with your date library of choice.
|
84
|
+
|
85
|
+
### Parsing dates
|
86
|
+
|
87
|
+
When typing a date in the input field instead of using the included picker, the component tries to parse the date as you type it in. To prevent premature parsing (e.g. interpreting `2024` as `2024-01-01T00:00:00.000Z`) parsing only turns on after 10 character. Typed in dates are expected to be in [Date Time String Format](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#date_time_string_format). Any other format are implementation dependant and might differ browser by browser.
|
55
88
|
|
56
89
|
### With year picker
|
57
90
|
|
@@ -115,9 +148,9 @@ This component is an updated version of [`DateInput`](/#DateInput) that's easier
|
|
115
148
|
|
116
149
|
### Date validation
|
117
150
|
|
118
|
-
By default `DateInput2` only does date validation if the `invalidDateErrorMessage` prop is provided. This uses the browser's `Date` object to try
|
151
|
+
By default `DateInput2` only does date validation if the `invalidDateErrorMessage` prop is provided. This uses the browser's `Date` object to try and parse the user provided date and displays the error message if it fails. Validation is triggered on the blur event of the input field.
|
119
152
|
|
120
|
-
If you want to do a more complex validation than the above (e.g. only allow a subset of dates) you can use the `onRequestValidateDate` prop to pass a validation function. This function will run on blur or on selecting
|
153
|
+
If you want to do a more complex validation than the above (e.g. only allow a subset of dates) you can use the `onRequestValidateDate` prop to pass a validation function. This function will run on blur or on selecting a date from the picker. The result of the internal validation will be passed to this function. Then you have to set the error messages accordingly. Check the following example for more details:
|
121
154
|
|
122
155
|
```js
|
123
156
|
---
|
@@ -170,7 +203,7 @@ render(<Example />)
|
|
170
203
|
|
171
204
|
### Date formatting
|
172
205
|
|
173
|
-
The display format of the
|
206
|
+
The display format of the date value can be set via the `formatDate` property. It will be applied if the user clicks on a date in the date picker or after the blur event from the input field.
|
174
207
|
Something to pay attention to is that the date string passed back in the callback function **is in UTC timezone**.
|
175
208
|
|
176
209
|
```js
|