@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 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
 
@@ -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
- function parseDate(dateString) {
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
- return isNaN(date.getTime()) ? '' : date.toISOString();
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 _useState = useState(''),
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
- selectedDate = _useState2[0],
81
- setSelectedDate = _useState2[1];
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
- setSelectedDate(parseDate(value || ''));
101
- }, []);
102
- const handleInputChange = (e, newValue, parsedDate = '') => {
103
- // blur event formats the input which shouldn't trigger parsing
104
- if (e.type !== 'blur') {
105
- setSelectedDate(parseDate(newValue));
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
- const parsedDate = parseDate(dateString);
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 (!value || parseDate(value) || selectedDate) {
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
- const isInputValid = validateInput(false);
153
- if (isInputValid && selectedDate) {
154
- const formattedDate = formatDate(selectedDate, getLocale(), getTimezone());
155
- handleInputChange(e, formattedDate, selectedDate);
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
- onRequestValidateDate === null || onRequestValidateDate === void 0 ? void 0 : onRequestValidateDate(value, isInputValid);
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: 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: selectedDate,
193
- visibleMonth: selectedDate,
218
+ selectedDate: value,
219
+ visibleMonth: value,
194
220
  locale: getLocale(),
195
221
  timezone: getTimezone(),
196
222
  role: "listbox",
@@ -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,
@@ -44,9 +44,37 @@ var _IconCalendarMonthLin, _IconArrowOpenEndSoli, _IconArrowOpenStartSo;
44
44
  * SOFTWARE.
45
45
  */
46
46
  /** @jsx jsx */
47
- function parseDate(dateString) {
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
- return isNaN(date.getTime()) ? '' : date.toISOString();
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 _useState = (0, _react.useState)(''),
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
- selectedDate = _useState2[0],
90
- setSelectedDate = _useState2[1];
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
- setSelectedDate(parseDate(value || ''));
110
- }, []);
111
- const handleInputChange = (e, newValue, parsedDate = '') => {
112
- // blur event formats the input which shouldn't trigger parsing
113
- if (e.type !== 'blur') {
114
- setSelectedDate(parseDate(newValue));
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
- const parsedDate = parseDate(dateString);
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 (!value || parseDate(value) || selectedDate) {
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
- const isInputValid = validateInput(false);
162
- if (isInputValid && selectedDate) {
163
- const formattedDate = formatDate(selectedDate, getLocale(), getTimezone());
164
- handleInputChange(e, formattedDate, selectedDate);
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
- onRequestValidateDate === null || onRequestValidateDate === void 0 ? void 0 : onRequestValidateDate(value, isInputValid);
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: 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: selectedDate,
202
- visibleMonth: selectedDate,
227
+ selectedDate: value,
228
+ visibleMonth: value,
203
229
  locale: getLocale(),
204
230
  timezone: getTimezone(),
205
231
  role: "listbox",
@@ -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.0",
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.0",
27
- "@instructure/ui-babel-preset": "9.6.0",
28
- "@instructure/ui-buttons": "9.6.0",
29
- "@instructure/ui-scripts": "9.6.0",
30
- "@instructure/ui-test-utils": "9.6.0",
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.0",
39
- "@instructure/shared-types": "9.6.0",
40
- "@instructure/ui-calendar": "9.6.0",
41
- "@instructure/ui-form-field": "9.6.0",
42
- "@instructure/ui-i18n": "9.6.0",
43
- "@instructure/ui-icons": "9.6.0",
44
- "@instructure/ui-popover": "9.6.0",
45
- "@instructure/ui-position": "9.6.0",
46
- "@instructure/ui-prop-types": "9.6.0",
47
- "@instructure/ui-react-utils": "9.6.0",
48
- "@instructure/ui-selectable": "9.6.0",
49
- "@instructure/ui-testable": "9.6.0",
50
- "@instructure/ui-text-input": "9.6.0",
51
- "@instructure/ui-utils": "9.6.0",
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
  },
@@ -2,16 +2,53 @@
2
2
  describes: DateInput2
3
3
  ---
4
4
 
5
- This component is an updated version of [`DateInput`](/#DateInput) that's easier to configure for developers, has a better UX, better accessibility features and a year picker. We recommend using this instead of `DateInput` which will be deprecated in the future.
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
- state = { value: '' }
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={this.state.value}
59
+ value={dateString}
23
60
  width="20rem"
24
- onChange={(e, value) => this.setState({ value })}
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
- - ```js
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
- render(<Example />)
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 an parse the user provided date and displays the error message if it fails. Validation is only triggered on the blur event of the input field.
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 the 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:
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 dates can be set via the `formatDate` property. It will be applied if the user clicks on a date in the date picker of after blur event from the input field.
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