@instructure/ui-date-input 10.2.3-snapshot-9 → 10.2.3-snapshot-11

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.
@@ -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
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instructure/ui-date-input",
3
- "version": "10.2.3-snapshot-9",
3
+ "version": "10.2.3-snapshot-11",
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": "10.2.3-snapshot-9",
27
- "@instructure/ui-babel-preset": "10.2.3-snapshot-9",
28
- "@instructure/ui-buttons": "10.2.3-snapshot-9",
29
- "@instructure/ui-scripts": "10.2.3-snapshot-9",
30
- "@instructure/ui-test-utils": "10.2.3-snapshot-9",
26
+ "@instructure/ui-axe-check": "10.2.3-snapshot-11",
27
+ "@instructure/ui-babel-preset": "10.2.3-snapshot-11",
28
+ "@instructure/ui-buttons": "10.2.3-snapshot-11",
29
+ "@instructure/ui-scripts": "10.2.3-snapshot-11",
30
+ "@instructure/ui-test-utils": "10.2.3-snapshot-11",
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": "10.2.3-snapshot-9",
39
- "@instructure/shared-types": "10.2.3-snapshot-9",
40
- "@instructure/ui-calendar": "10.2.3-snapshot-9",
41
- "@instructure/ui-form-field": "10.2.3-snapshot-9",
42
- "@instructure/ui-i18n": "10.2.3-snapshot-9",
43
- "@instructure/ui-icons": "10.2.3-snapshot-9",
44
- "@instructure/ui-popover": "10.2.3-snapshot-9",
45
- "@instructure/ui-position": "10.2.3-snapshot-9",
46
- "@instructure/ui-prop-types": "10.2.3-snapshot-9",
47
- "@instructure/ui-react-utils": "10.2.3-snapshot-9",
48
- "@instructure/ui-selectable": "10.2.3-snapshot-9",
49
- "@instructure/ui-testable": "10.2.3-snapshot-9",
50
- "@instructure/ui-text-input": "10.2.3-snapshot-9",
51
- "@instructure/ui-utils": "10.2.3-snapshot-9",
38
+ "@instructure/emotion": "10.2.3-snapshot-11",
39
+ "@instructure/shared-types": "10.2.3-snapshot-11",
40
+ "@instructure/ui-calendar": "10.2.3-snapshot-11",
41
+ "@instructure/ui-form-field": "10.2.3-snapshot-11",
42
+ "@instructure/ui-i18n": "10.2.3-snapshot-11",
43
+ "@instructure/ui-icons": "10.2.3-snapshot-11",
44
+ "@instructure/ui-popover": "10.2.3-snapshot-11",
45
+ "@instructure/ui-position": "10.2.3-snapshot-11",
46
+ "@instructure/ui-prop-types": "10.2.3-snapshot-11",
47
+ "@instructure/ui-react-utils": "10.2.3-snapshot-11",
48
+ "@instructure/ui-selectable": "10.2.3-snapshot-11",
49
+ "@instructure/ui-testable": "10.2.3-snapshot-11",
50
+ "@instructure/ui-text-input": "10.2.3-snapshot-11",
51
+ "@instructure/ui-utils": "10.2.3-snapshot-11",
52
52
  "moment-timezone": "^0.5.45",
53
53
  "prop-types": "^15.8.1"
54
54
  },
@@ -2,7 +2,7 @@
2
2
  describes: DateInput
3
3
  ---
4
4
 
5
- > **Important:** You can now use are updated version [`DateInput2`](/#DateInput2) which is easier to configure for developers, has a better UX, better accessibility features and a year picker. We recommend using that instead of `DateInput` which will be deprecated in the future.
5
+ > _Note:_ you can now try the updated (but still experimental) [`DateInput2`](/#DateInput2) which is easier to configure for developers, has a better UX, better accessibility features and a year picker. We recommend using that instead of `DateInput` which will be deprecated in the future.
6
6
 
7
7
  The `DateInput` component provides a visual interface for inputting date data.
8
8
 
@@ -2,16 +2,50 @@
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
+ > _Warning_: `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 API changes.
6
6
 
7
7
  ### Minimal config
8
8
 
9
9
  - ```js
10
10
  class Example extends React.Component {
11
- state = { value: '' }
11
+ state = { inputValue: '', dateString: '' }
12
12
 
13
13
  render() {
14
14
  return (
15
+ <div>
16
+ <DateInput2
17
+ renderLabel="Choose a date"
18
+ screenReaderLabels={{
19
+ calendarIcon: 'Calendar',
20
+ nextMonthButton: 'Next month',
21
+ prevMonthButton: 'Previous month'
22
+ }}
23
+ value={this.state.inputValue}
24
+ width="20rem"
25
+ onChange={(e, inputValue, dateString) => {
26
+ this.setState({ dateString, inputValue })
27
+ }}
28
+ invalidDateErrorMessage="Invalid date"
29
+ />
30
+ <p>
31
+ Input Value: <code>{this.state.inputValue}</code>
32
+ <br />
33
+ UTC Date String: <code>{this.state.dateString}</code>
34
+ </p>
35
+ </div>
36
+ )
37
+ }
38
+ }
39
+
40
+ render(<Example />)
41
+ ```
42
+
43
+ - ```js
44
+ const Example = () => {
45
+ const [inputValue, setInputValue] = useState('')
46
+ const [dateString, setDateString] = useState('')
47
+ return (
48
+ <div>
15
49
  <DateInput2
16
50
  renderLabel="Choose a date"
17
51
  screenReaderLabels={{
@@ -19,22 +53,48 @@ This component is an updated version of [`DateInput`](/#DateInput) that's easier
19
53
  nextMonthButton: 'Next month',
20
54
  prevMonthButton: 'Previous month'
21
55
  }}
22
- value={this.state.value}
56
+ value={inputValue}
23
57
  width="20rem"
24
- onChange={(e, value) => this.setState({ value })}
58
+ onChange={(e, inputValue, dateString) => {
59
+ setInputValue(inputValue)
60
+ setDateString(dateString)
61
+ }}
25
62
  invalidDateErrorMessage="Invalid date"
26
63
  />
27
- )
28
- }
64
+ <p>
65
+ Input Value: <code>{inputValue}</code>
66
+ <br />
67
+ UTC Date String: <code>{dateString}</code>
68
+ </p>
69
+ </div>
70
+ )
29
71
  }
30
72
 
31
73
  render(<Example />)
32
74
  ```
33
75
 
34
- - ```js
35
- const Example = () => {
36
- const [value, setValue] = useState('')
37
- return (
76
+ ### Parsing and formatting dates
77
+
78
+ When typing in a date manually (instead of using the included picker), the component tries to parse the date as you type it in. By default parsing is based on the user's locale which determines the order of day, month and year (e.g.: a user with US locale will have MONTH/DAY/YEAR order, and someone with GB locale will have DAY/MONTH/YEAR order).
79
+
80
+ Any of the following separators can be used when typing a date: `,`, `-`, `.`, `/` or a whitespace however on blur the date will be formatted according to the locale and separators will be changed and leading zeros also adjusted.
81
+
82
+ If you want different parsing and formatting then the current locale you can use the `dateFormat` prop which accepts either a string with a name of a different locale (so you can use US date format even if the user is France) or a parser and formatter functions.
83
+
84
+ The default parser also has a limitation of not working with years before `1000` and after `9999`. These values are invalid by default but not with custom parsers.
85
+
86
+ ```js
87
+ ---
88
+ type: example
89
+ ---
90
+ const Example = () => {
91
+ const [value, setValue] = useState('')
92
+ const [value2, setValue2] = useState('')
93
+ const [value3, setValue3] = useState('')
94
+
95
+ return (
96
+ <div>
97
+ <p>US locale with default format:</p>
38
98
  <DateInput2
39
99
  renderLabel="Choose a date"
40
100
  screenReaderLabels={{
@@ -42,25 +102,113 @@ This component is an updated version of [`DateInput`](/#DateInput) that's easier
42
102
  nextMonthButton: 'Next month',
43
103
  prevMonthButton: 'Previous month'
44
104
  }}
45
- value={value}
46
105
  width="20rem"
106
+ value={value}
107
+ locale="en-us"
47
108
  onChange={(e, value) => setValue(value)}
48
- invalidDateErrorMessage="Invalid date"
49
109
  />
50
- )
51
- }
110
+ <p>US locale with german date format:</p>
111
+ <DateInput2
112
+ renderLabel="Choose a date"
113
+ screenReaderLabels={{
114
+ calendarIcon: 'Calendar',
115
+ nextMonthButton: 'Next month',
116
+ prevMonthButton: 'Previous month'
117
+ }}
118
+ width="20rem"
119
+ value={value2}
120
+ locale="en-us"
121
+ dateFormat="de-de"
122
+ onChange={(e, value) => setValue2(value)}
123
+ />
124
+ <p>US locale with ISO date format:</p>
125
+ <DateInput2
126
+ renderLabel="Choose a date"
127
+ screenReaderLabels={{
128
+ calendarIcon: 'Calendar',
129
+ nextMonthButton: 'Next month',
130
+ prevMonthButton: 'Previous month'
131
+ }}
132
+ width="20rem"
133
+ value={value3}
134
+ locale="en-us"
135
+ dateFormat={{
136
+ parser: (input) => {
137
+ // split input on '.', whitespace, '/', ',' or '-' using regex: /[.\s/.-]+/
138
+ // the '+' allows splitting on consecutive delimiters
139
+ const [year, month, day] = input.split(/[,.\s/.-]+/)
140
+ const newDate = new Date(year, month-1, day)
141
+ return isNaN(newDate) ? '' : newDate
142
+ },
143
+ formatter: (date) => {
144
+ // vanilla js formatter but you could use a date library instead
145
+ const year = date.getFullYear()
146
+ // month is zero indexed so add 1
147
+ const month = `${date.getMonth() + 1}`.padStart(2, '0')
148
+ const day = `${date.getDate()}`.padStart(2, '0')
149
+ return `${year}-${month}-${day}`
150
+ }
151
+ }}
152
+ onChange={(e, value) => setValue3(value)}
153
+ />
154
+ </div>
155
+ )
156
+ }
52
157
 
53
- render(<Example />)
54
- ```
158
+ render(<Example />)
159
+ ```
160
+
161
+ ### Timezones
162
+
163
+ In the examples above you can see that the `onChange` callback also return a UTC date string. This means it is timezone adjusted. If the timezone is not set via the `timezone` prop, it is calculated/assumed from the user's machine. 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 (summertime).
55
164
 
56
165
  ### With year picker
57
166
 
58
167
  - ```js
59
168
  class Example extends React.Component {
60
- state = { value: '' }
169
+ state = { inputValue: '', dateString: '' }
61
170
 
62
171
  render() {
63
172
  return (
173
+ <div>
174
+ <DateInput2
175
+ renderLabel="Choose a date"
176
+ screenReaderLabels={{
177
+ calendarIcon: 'Calendar',
178
+ nextMonthButton: 'Next month',
179
+ prevMonthButton: 'Previous month'
180
+ }}
181
+ value={this.state.inputValue}
182
+ width="20rem"
183
+ onChange={(e, inputValue, dateString) => {
184
+ this.setState({ dateString, inputValue })
185
+ }}
186
+ invalidDateErrorMessage="Invalid date"
187
+ withYearPicker={{
188
+ screenReaderLabel: 'Year picker',
189
+ startYear: 1900,
190
+ endYear: 2024
191
+ }}
192
+ />
193
+ <p>
194
+ Input Value: <code>{this.state.inputValue}</code>
195
+ <br />
196
+ UTC Date String: <code>{this.state.dateString}</code>
197
+ </p>
198
+ </div>
199
+ )
200
+ }
201
+ }
202
+
203
+ render(<Example />)
204
+ ```
205
+
206
+ - ```js
207
+ const Example = () => {
208
+ const [inputValue, setInputValue] = useState('')
209
+ const [dateString, setDateString] = useState('')
210
+ return (
211
+ <div>
64
212
  <DateInput2
65
213
  renderLabel="Choose a date"
66
214
  screenReaderLabels={{
@@ -68,9 +216,12 @@ This component is an updated version of [`DateInput`](/#DateInput) that's easier
68
216
  nextMonthButton: 'Next month',
69
217
  prevMonthButton: 'Previous month'
70
218
  }}
219
+ value={inputValue}
71
220
  width="20rem"
72
- value={this.state.value}
73
- onChange={(e, value) => this.setState({ value })}
221
+ onChange={(e, inputValue, dateString) => {
222
+ setInputValue(inputValue)
223
+ setDateString(dateString)
224
+ }}
74
225
  invalidDateErrorMessage="Invalid date"
75
226
  withYearPicker={{
76
227
  screenReaderLabel: 'Year picker',
@@ -78,42 +229,23 @@ This component is an updated version of [`DateInput`](/#DateInput) that's easier
78
229
  endYear: 2024
79
230
  }}
80
231
  />
81
- )
82
- }
232
+ <p>
233
+ Input Value: <code>{inputValue}</code>
234
+ <br />
235
+ UTC Date String: <code>{dateString}</code>
236
+ </p>
237
+ </div>
238
+ )
83
239
  }
84
240
 
85
241
  render(<Example />)
86
242
  ```
87
243
 
88
- - ```js
89
- const Example = () => {
90
- const [value, setValue] = useState('')
91
-
92
- return (
93
- <DateInput2
94
- renderLabel="Choose a date"
95
- screenReaderLabels={{
96
- calendarIcon: 'Calendar',
97
- nextMonthButton: 'Next month',
98
- prevMonthButton: 'Previous month'
99
- }}
100
- width="20rem"
101
- value={value}
102
- onChange={(e, value) => setValue(value)}
103
- invalidDateErrorMessage="Invalid date"
104
- withYearPicker={{
105
- screenReaderLabel: 'Year picker',
106
- startYear: 1900,
107
- endYear: 2024
108
- }}
109
- />
110
- )
111
- }
244
+ ### Date validation
112
245
 
113
- render(<Example />)
114
- ```
246
+ By default `DateInput2` only does date validation if the `invalidDateErrorMessage` prop is provided. Validation is triggered on the blur event of the input field. Invalid dates are determined current locale.
115
247
 
116
- ### With custom validation
248
+ If you want to do more complex validation (e.g. only allow a subset of dates) you can use the `onRequestValidateDate` and `messages` props.
117
249
 
118
250
  ```js
119
251
  ---
@@ -121,18 +253,24 @@ type: example
121
253
  ---
122
254
  const Example = () => {
123
255
  const [value, setValue] = useState('')
256
+ const [dateString, setDateString] = useState('')
124
257
  const [messages, setMessages] = useState([])
125
258
 
126
- const handleDateValidation = (dateString, isValidDate) => {
127
- if (!isValidDate) {
259
+ const handleDateValidation = (e, inputValue, utcIsoDate) => {
260
+ // utcIsoDate will be an empty string if the input cannot be parsed as a date
261
+
262
+ const date = new Date(utcIsoDate)
263
+
264
+ // don't validate empty input
265
+ if (!utcIsoDate && inputValue.length > 0) {
128
266
  setMessages([{
129
267
  type: 'error',
130
268
  text: 'This is not a valid date'
131
269
  }])
132
- } else if (new Date(dateString) < new Date('January 1, 1900')) {
270
+ } else if (date < new Date('1990-01-01')) {
133
271
  setMessages([{
134
272
  type: 'error',
135
- text: 'Use date after January 1, 1900'
273
+ text: 'Select date after January 1, 1990'
136
274
  }])
137
275
  } else {
138
276
  setMessages([])
@@ -141,7 +279,7 @@ const Example = () => {
141
279
 
142
280
  return (
143
281
  <DateInput2
144
- renderLabel="Choose a date after January 1, 1900"
282
+ renderLabel="Choose a date after January 1, 1990"
145
283
  screenReaderLabels={{
146
284
  calendarIcon: 'Calendar',
147
285
  nextMonthButton: 'Next month',
@@ -163,3 +301,7 @@ const Example = () => {
163
301
 
164
302
  render(<Example />)
165
303
  ```
304
+
305
+ ### Date format hint
306
+
307
+ If the `placeholder` property is undefined it will display a hint for the date format (like `DD/MM/YYYY`). Usually it is recommended to leave it as it is for a better user experience.