@instructure/ui-date-input 9.6.0 → 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/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-pr-snapshot-1726659472372",
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-pr-snapshot-1726659472372",
27
+ "@instructure/ui-babel-preset": "9.6.1-pr-snapshot-1726659472372",
28
+ "@instructure/ui-buttons": "9.6.1-pr-snapshot-1726659472372",
29
+ "@instructure/ui-scripts": "9.6.1-pr-snapshot-1726659472372",
30
+ "@instructure/ui-test-utils": "9.6.1-pr-snapshot-1726659472372",
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-pr-snapshot-1726659472372",
39
+ "@instructure/shared-types": "9.6.1-pr-snapshot-1726659472372",
40
+ "@instructure/ui-calendar": "9.6.1-pr-snapshot-1726659472372",
41
+ "@instructure/ui-form-field": "9.6.1-pr-snapshot-1726659472372",
42
+ "@instructure/ui-i18n": "9.6.1-pr-snapshot-1726659472372",
43
+ "@instructure/ui-icons": "9.6.1-pr-snapshot-1726659472372",
44
+ "@instructure/ui-popover": "9.6.1-pr-snapshot-1726659472372",
45
+ "@instructure/ui-position": "9.6.1-pr-snapshot-1726659472372",
46
+ "@instructure/ui-prop-types": "9.6.1-pr-snapshot-1726659472372",
47
+ "@instructure/ui-react-utils": "9.6.1-pr-snapshot-1726659472372",
48
+ "@instructure/ui-selectable": "9.6.1-pr-snapshot-1726659472372",
49
+ "@instructure/ui-testable": "9.6.1-pr-snapshot-1726659472372",
50
+ "@instructure/ui-text-input": "9.6.1-pr-snapshot-1726659472372",
51
+ "@instructure/ui-utils": "9.6.1-pr-snapshot-1726659472372",
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,28 +2,37 @@
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
- <DateInput2
16
- renderLabel="Choose a date"
17
- screenReaderLabels={{
18
- calendarIcon: 'Calendar',
19
- nextMonthButton: 'Next month',
20
- prevMonthButton: 'Previous month'
21
- }}
22
- value={this.state.value}
23
- width="20rem"
24
- onChange={(e, value) => this.setState({ value })}
25
- invalidDateErrorMessage="Invalid date"
26
- />
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>
27
36
  )
28
37
  }
29
38
  }
@@ -33,8 +42,58 @@ This component is an updated version of [`DateInput`](/#DateInput) that's easier
33
42
 
34
43
  - ```js
35
44
  const Example = () => {
36
- const [value, setValue] = useState('')
45
+ const [inputValue, setInputValue] = useState('')
46
+ const [dateString, setDateString] = useState('')
37
47
  return (
48
+ <div>
49
+ <DateInput2
50
+ renderLabel="Choose a date"
51
+ screenReaderLabels={{
52
+ calendarIcon: 'Calendar',
53
+ nextMonthButton: 'Next month',
54
+ prevMonthButton: 'Previous month'
55
+ }}
56
+ value={inputValue}
57
+ width="20rem"
58
+ onChange={(e, inputValue, dateString) => {
59
+ setInputValue(inputValue)
60
+ setDateString(dateString)
61
+ }}
62
+ invalidDateErrorMessage="Invalid date"
63
+ />
64
+ <p>
65
+ Input Value: <code>{inputValue}</code>
66
+ <br />
67
+ UTC Date String: <code>{dateString}</code>
68
+ </p>
69
+ </div>
70
+ )
71
+ }
72
+
73
+ render(<Example />)
74
+ ```
75
+
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 have a limitation of excluding 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
+
94
+ return (
95
+ <div>
96
+ <p>US locale with german date format:</p>
38
97
  <DateInput2
39
98
  renderLabel="Choose a date"
40
99
  screenReaderLabels={{
@@ -42,42 +101,87 @@ This component is an updated version of [`DateInput`](/#DateInput) that's easier
42
101
  nextMonthButton: 'Next month',
43
102
  prevMonthButton: 'Previous month'
44
103
  }}
45
- value={value}
46
104
  width="20rem"
105
+ value={value}
106
+ locale="en-us"
107
+ dateFormat="de-de"
47
108
  onChange={(e, value) => setValue(value)}
48
- invalidDateErrorMessage="Invalid date"
49
109
  />
50
- )
51
- }
110
+ <p>US locale with ISO 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={{
122
+ parser: (input) => {
123
+ // split input on '.', whitespace, '/', ',' or '-' using regex: /[.\s/.-]+/
124
+ // the '+' allows splitting on consecutive delimiters
125
+ const [year, month, day] = input.split(/[,.\s/.-]+/)
126
+ const newDate = new Date(year, month-1, day)
127
+ return isNaN(newDate) ? '' : newDate
128
+ },
129
+ formatter: (date) => {
130
+ // vanilla js formatter but you could use a date library instead
131
+ const year = date.getFullYear()
132
+ // month is zero indexed so add 1
133
+ const month = `${date.getMonth() + 1}`.padStart(2, '0')
134
+ const day = `${date.getDate()}`.padStart(2, '0')
135
+ return `${year}-${month}-${day}`
136
+ }
137
+ }}
138
+ onChange={(e, value) => setValue2(value)}
139
+ />
140
+ </div>
141
+ )
142
+ }
52
143
 
53
- render(<Example />)
54
- ```
144
+ render(<Example />)
145
+ ```
146
+
147
+ ### Timezones
148
+
149
+ 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
150
 
56
151
  ### With year picker
57
152
 
58
153
  - ```js
59
154
  class Example extends React.Component {
60
- state = { value: '' }
155
+ state = { inputValue: '', dateString: '' }
61
156
 
62
157
  render() {
63
158
  return (
64
- <DateInput2
65
- renderLabel="Choose a date"
66
- screenReaderLabels={{
67
- calendarIcon: 'Calendar',
68
- nextMonthButton: 'Next month',
69
- prevMonthButton: 'Previous month'
70
- }}
71
- width="20rem"
72
- value={this.state.value}
73
- onChange={(e, value) => this.setState({ value })}
74
- invalidDateErrorMessage="Invalid date"
75
- withYearPicker={{
76
- screenReaderLabel: 'Year picker',
77
- startYear: 1900,
78
- endYear: 2024
79
- }}
80
- />
159
+ <div>
160
+ <DateInput2
161
+ renderLabel="Choose a date"
162
+ screenReaderLabels={{
163
+ calendarIcon: 'Calendar',
164
+ nextMonthButton: 'Next month',
165
+ prevMonthButton: 'Previous month'
166
+ }}
167
+ value={this.state.inputValue}
168
+ width="20rem"
169
+ onChange={(e, inputValue, dateString) => {
170
+ this.setState({ dateString, inputValue })
171
+ }}
172
+ invalidDateErrorMessage="Invalid date"
173
+ withYearPicker={{
174
+ screenReaderLabel: 'Year picker',
175
+ startYear: 1900,
176
+ endYear: 2024
177
+ }}
178
+ />
179
+ <p>
180
+ Input Value: <code>{this.state.inputValue}</code>
181
+ <br />
182
+ UTC Date String: <code>{this.state.dateString}</code>
183
+ </p>
184
+ </div>
81
185
  )
82
186
  }
83
187
  }
@@ -87,26 +191,36 @@ This component is an updated version of [`DateInput`](/#DateInput) that's easier
87
191
 
88
192
  - ```js
89
193
  const Example = () => {
90
- const [value, setValue] = useState('')
91
-
194
+ const [inputValue, setInputValue] = useState('')
195
+ const [dateString, setDateString] = useState('')
92
196
  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
- />
197
+ <div>
198
+ <DateInput2
199
+ renderLabel="Choose a date"
200
+ screenReaderLabels={{
201
+ calendarIcon: 'Calendar',
202
+ nextMonthButton: 'Next month',
203
+ prevMonthButton: 'Previous month'
204
+ }}
205
+ value={inputValue}
206
+ width="20rem"
207
+ onChange={(e, inputValue, dateString) => {
208
+ setInputValue(inputValue)
209
+ setDateString(dateString)
210
+ }}
211
+ invalidDateErrorMessage="Invalid date"
212
+ withYearPicker={{
213
+ screenReaderLabel: 'Year picker',
214
+ startYear: 1900,
215
+ endYear: 2024
216
+ }}
217
+ />
218
+ <p>
219
+ Input Value: <code>{inputValue}</code>
220
+ <br />
221
+ UTC Date String: <code>{dateString}</code>
222
+ </p>
223
+ </div>
110
224
  )
111
225
  }
112
226
 
@@ -115,9 +229,9 @@ This component is an updated version of [`DateInput`](/#DateInput) that's easier
115
229
 
116
230
  ### Date validation
117
231
 
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.
232
+ 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 based on [parsing](/#DateInput2/#Parsing%20and%20formatting%20dates).
119
233
 
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:
234
+ If you want to do a more complex validation than the above (e.g. only allow a subset of dates) you can use the `onBlur` and `messages` props.
121
235
 
122
236
  ```js
123
237
  ---
@@ -125,18 +239,22 @@ type: example
125
239
  ---
126
240
  const Example = () => {
127
241
  const [value, setValue] = useState('')
242
+ const [dateString, setDateString] = useState('')
128
243
  const [messages, setMessages] = useState([])
129
244
 
130
- const handleDateValidation = (dateString, isValidDate) => {
131
- if (!isValidDate) {
245
+ const handleDateValidation = (e, inputValue, utcIsoDate) => {
246
+ const date = new Date(utcIsoDate)
247
+ console.log(utcIsoDate)
248
+ // don't validate empty input
249
+ if (!utcIsoDate && inputValue.length > 0) {
132
250
  setMessages([{
133
251
  type: 'error',
134
252
  text: 'This is not a valid date'
135
253
  }])
136
- } else if (new Date(dateString) < new Date('January 1, 1900')) {
254
+ } else if (date < new Date('1990-01-01')) {
137
255
  setMessages([{
138
256
  type: 'error',
139
- text: 'Use date after January 1, 1900'
257
+ text: 'Select date after January 1, 1990'
140
258
  }])
141
259
  } else {
142
260
  setMessages([])
@@ -145,7 +263,7 @@ const Example = () => {
145
263
 
146
264
  return (
147
265
  <DateInput2
148
- renderLabel="Choose a date after January 1, 1900"
266
+ renderLabel="Choose a date after January 1, 1990"
149
267
  screenReaderLabels={{
150
268
  calendarIcon: 'Calendar',
151
269
  nextMonthButton: 'Next month',
@@ -167,101 +285,3 @@ const Example = () => {
167
285
 
168
286
  render(<Example />)
169
287
  ```
170
-
171
- ### Date formatting
172
-
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.
174
- Something to pay attention to is that the date string passed back in the callback function **is in UTC timezone**.
175
-
176
- ```js
177
- ---
178
- type: example
179
- ---
180
- const Example = () => {
181
- const [value1, setValue1] = useState('')
182
- const [value2, setValue2] = useState('')
183
- const [value3, setValue3] = useState('')
184
-
185
- const shortDateFormatFn = (dateString, locale, timezone) => {
186
- return new Date(dateString).toLocaleDateString(locale, {
187
- month: 'numeric',
188
- year: 'numeric',
189
- day: 'numeric',
190
- timeZone: timezone,
191
- })
192
- }
193
-
194
- const isoDateFormatFn = (dateString, locale, timezone) => {
195
- // this is a simple way to get ISO8601 date in a specific timezone but should not be used in production
196
- // please use a proper date library instead like date-fns, luxon or dayjs
197
- const localeDate = new Date(dateString).toLocaleDateString('sv', {
198
- month: 'numeric',
199
- year: 'numeric',
200
- day: 'numeric',
201
- timeZone: timezone,
202
- })
203
-
204
- return localeDate
205
- }
206
-
207
- return (
208
- <div style={{display: 'flex', flexDirection: 'column', gap: '1.5rem'}}>
209
- <DateInput2
210
- renderLabel="Default format"
211
- screenReaderLabels={{
212
- calendarIcon: 'Calendar',
213
- nextMonthButton: 'Next month',
214
- prevMonthButton: 'Previous month'
215
- }}
216
- isInline
217
- width="20rem"
218
- value={value1}
219
- onChange={(e, value) => setValue1(value)}
220
- withYearPicker={{
221
- screenReaderLabel: 'Year picker',
222
- startYear: 1900,
223
- endYear: 2024
224
- }}
225
- />
226
- <DateInput2
227
- renderLabel="Short format in current locale"
228
- screenReaderLabels={{
229
- calendarIcon: 'Calendar',
230
- nextMonthButton: 'Next month',
231
- prevMonthButton: 'Previous month'
232
- }}
233
- isInline
234
- width="20rem"
235
- value={value2}
236
- onChange={(e, value) => setValue2(value)}
237
- formatDate={shortDateFormatFn}
238
- withYearPicker={{
239
- screenReaderLabel: 'Year picker',
240
- startYear: 1900,
241
- endYear: 2024
242
- }}
243
- />
244
- <DateInput2
245
- renderLabel="ISO8601"
246
- screenReaderLabels={{
247
- calendarIcon: 'Calendar',
248
- nextMonthButton: 'Next month',
249
- prevMonthButton: 'Previous month'
250
- }}
251
- isInline
252
- width="20rem"
253
- value={value3}
254
- onChange={(e, value) => setValue3(value)}
255
- formatDate={isoDateFormatFn}
256
- withYearPicker={{
257
- screenReaderLabel: 'Year picker',
258
- startYear: 1900,
259
- endYear: 2024
260
- }}
261
- />
262
- </div>
263
- )
264
- }
265
-
266
- render(<Example />)
267
- ```