@instructure/ui-date-input 9.6.0 → 10.0.1-pr-snapshot-1723800180253

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.
@@ -25,6 +25,7 @@
25
25
  /** @jsx jsx */
26
26
  import { useState, useEffect, useContext } from 'react'
27
27
  import type { SyntheticEvent } from 'react'
28
+ import moment from 'moment-timezone'
28
29
  import { Calendar } from '@instructure/ui-calendar'
29
30
  import { IconButton } from '@instructure/ui-buttons'
30
31
  import {
@@ -42,24 +43,25 @@ import { jsx } from '@instructure/emotion'
42
43
  import { propTypes } from './props'
43
44
  import type { DateInput2Props } from './props'
44
45
  import type { FormMessage } from '@instructure/ui-form-field'
45
- import type { Moment } from '@instructure/ui-i18n'
46
46
 
47
- function parseDate(dateString: string): string {
48
- const date = new Date(dateString)
49
- return isNaN(date.getTime()) ? '' : date.toISOString()
47
+ function isValidDate(dateString: string): boolean {
48
+ return !isNaN(new Date(dateString).getTime())
50
49
  }
51
50
 
52
- function defaultDateFormatter(
51
+ function isValidMomentDate(
53
52
  dateString: string,
54
53
  locale: string,
55
54
  timezone: string
56
- ) {
57
- return new Date(dateString).toLocaleDateString(locale, {
58
- month: 'long',
59
- year: 'numeric',
60
- day: 'numeric',
61
- timeZone: timezone
62
- })
55
+ ): boolean {
56
+ return moment
57
+ .tz(
58
+ dateString,
59
+ [moment.ISO_8601, 'llll', 'LLLL', 'lll', 'LLL', 'll', 'LL', 'l', 'L'],
60
+ locale,
61
+ true,
62
+ timezone
63
+ )
64
+ .isValid()
63
65
  }
64
66
 
65
67
  /**
@@ -85,8 +87,6 @@ const DateInput2 = ({
85
87
  locale,
86
88
  timezone,
87
89
  placeholder,
88
- formatDate = defaultDateFormatter,
89
- // margin, TODO enable this prop
90
90
  ...rest
91
91
  }: DateInput2Props) => {
92
92
  const [selectedDate, setSelectedDate] = useState<string>('')
@@ -97,8 +97,6 @@ const DateInput2 = ({
97
97
  const localeContext = useContext(ApplyLocaleContext)
98
98
 
99
99
  useEffect(() => {
100
- // when `value` is changed, validation removes the error message if passes
101
- // but it's NOT adding error message if validation fails for better UX
102
100
  validateInput(true)
103
101
  }, [value])
104
102
 
@@ -106,53 +104,46 @@ const DateInput2 = ({
106
104
  setInputMessages(messages || [])
107
105
  }, [messages])
108
106
 
109
- useEffect(() => {
110
- setSelectedDate(parseDate(value || ''))
111
- }, [])
112
-
113
- const handleInputChange = (
114
- e: SyntheticEvent,
115
- newValue: string,
116
- parsedDate: string = ''
117
- ) => {
118
- // blur event formats the input which shouldn't trigger parsing
119
- if (e.type !== 'blur') {
120
- setSelectedDate(parseDate(newValue))
121
- }
122
- onChange?.(e, newValue, parsedDate)
107
+ const handleInputChange = (e: SyntheticEvent, value: string) => {
108
+ onChange?.(e, value)
123
109
  }
124
110
 
125
111
  const handleDateSelected = (
126
112
  dateString: string,
127
- _momentDate: Moment,
113
+ _momentDate: any, // real type is Moment but used `any` to avoid importing the moment lib
128
114
  e: SyntheticEvent
129
115
  ) => {
130
- const formattedDate = formatDate(dateString, getLocale(), getTimezone())
131
- const parsedDate = parseDate(dateString)
132
- setSelectedDate(parsedDate)
133
- handleInputChange(e, formattedDate, parsedDate)
116
+ const formattedDate = new Date(dateString).toLocaleDateString(getLocale(), {
117
+ month: 'long',
118
+ year: 'numeric',
119
+ day: 'numeric',
120
+ timeZone: getTimezone()
121
+ })
122
+ handleInputChange(e, formattedDate)
134
123
  setShowPopover(false)
135
- onRequestValidateDate?.(dateString, true)
124
+ onRequestValidateDate?.(formattedDate, true)
136
125
  }
137
126
 
138
- // onlyRemoveError is used to remove the error msg immediately when the user inputs a valid date (and don't wait for blur event)
139
127
  const validateInput = (onlyRemoveError = false): boolean => {
140
- // don't validate empty input
141
- if (!value || parseDate(value) || selectedDate) {
128
+ // TODO `isValidDate` and `isValidMomentDate` basically have the same functionality but the latter is a bit more strict (e.g.: `33` is only valid in `isValidMomentDate`)
129
+ // in the future we should get rid of moment but currently Calendar is using it for validation too so we can only remove it simultaneously
130
+ // otherwise DateInput could pass invalid dates to Calendar and break it
131
+ if (
132
+ (isValidDate(value || '') &&
133
+ isValidMomentDate(value || '', getLocale(), getTimezone())) ||
134
+ value === ''
135
+ ) {
136
+ setSelectedDate(value || '')
142
137
  setInputMessages(messages || [])
143
138
  return true
144
139
  }
145
- // only show error if there is no user provided validation callback
146
- if (
147
- !onlyRemoveError &&
148
- typeof invalidDateErrorMessage === 'string' &&
149
- !onRequestValidateDate
150
- ) {
151
- setInputMessages([
140
+ if (!onlyRemoveError && typeof invalidDateErrorMessage === 'string') {
141
+ setInputMessages((messages) => [
152
142
  {
153
143
  type: 'error',
154
144
  text: invalidDateErrorMessage
155
- }
145
+ },
146
+ ...messages
156
147
  ])
157
148
  }
158
149
 
@@ -180,9 +171,14 @@ const DateInput2 = ({
180
171
 
181
172
  const handleBlur = (e: SyntheticEvent) => {
182
173
  const isInputValid = validateInput(false)
183
- if (isInputValid && selectedDate) {
184
- const formattedDate = formatDate(selectedDate, getLocale(), getTimezone())
185
- handleInputChange(e, formattedDate, selectedDate)
174
+ if (isInputValid && value) {
175
+ const formattedDate = new Date(value).toLocaleDateString(getLocale(), {
176
+ month: 'long',
177
+ year: 'numeric',
178
+ day: 'numeric',
179
+ timeZone: getTimezone()
180
+ })
181
+ handleInputChange(e, formattedDate)
186
182
  }
187
183
  onRequestValidateDate?.(value, isInputValid)
188
184
  onBlur?.(e)
@@ -191,7 +187,6 @@ const DateInput2 = ({
191
187
  return (
192
188
  <TextInput
193
189
  {...passthroughProps(rest)}
194
- // margin={'large'} TODO add this prop to TextInput
195
190
  renderLabel={renderLabel}
196
191
  onChange={handleInputChange}
197
192
  onBlur={handleBlur}
@@ -230,8 +225,8 @@ const DateInput2 = ({
230
225
  onDateSelected={handleDateSelected}
231
226
  selectedDate={selectedDate}
232
227
  visibleMonth={selectedDate}
233
- locale={getLocale()}
234
- timezone={getTimezone()}
228
+ locale={locale}
229
+ timezone={timezone}
235
230
  role="listbox"
236
231
  renderNextMonthButton={
237
232
  <IconButton
@@ -23,18 +23,14 @@
23
23
  */
24
24
 
25
25
  import PropTypes from 'prop-types'
26
- import type { SyntheticEvent, InputHTMLAttributes } from 'react'
26
+ import type { SyntheticEvent } from 'react'
27
27
 
28
28
  import { controllable } from '@instructure/ui-prop-types'
29
29
  import { FormPropTypes } from '@instructure/ui-form-field'
30
30
  import type { FormMessage } from '@instructure/ui-form-field'
31
- import type {
32
- OtherHTMLAttributes,
33
- Renderable,
34
- PropValidators
35
- } from '@instructure/shared-types'
31
+ import type { Renderable, PropValidators } from '@instructure/shared-types'
36
32
 
37
- type DateInput2OwnProps = {
33
+ type DateInput2Props = {
38
34
  /**
39
35
  * Specifies the input label.
40
36
  */
@@ -60,11 +56,7 @@ type DateInput2OwnProps = {
60
56
  /**
61
57
  * Callback fired when the input changes.
62
58
  */
63
- onChange?: (
64
- event: React.SyntheticEvent,
65
- inputValue: string,
66
- dateString: string
67
- ) => void
59
+ onChange?: (event: React.SyntheticEvent, value: string) => void
68
60
  /**
69
61
  * Callback executed when the input fires a blur event.
70
62
  */
@@ -165,27 +157,9 @@ type DateInput2OwnProps = {
165
157
  startYear: number
166
158
  endYear: number
167
159
  }
168
-
169
- /**
170
- * Formatting function for how the date should be displayed inside the input field. It will be applied if the user clicks on a date in the date picker of after blur event from the input field.
171
- */
172
- formatDate?: (isoDate: string, locale: string, timezone: string) => string
173
-
174
- /**
175
- * Valid values are `0`, `none`, `auto`, `xxx-small`, `xx-small`, `x-small`,
176
- * `small`, `medium`, `large`, `x-large`, `xx-large`. Apply these values via
177
- * familiar CSS-like shorthand. For example: `margin="small auto large"`.
178
- */
179
- // margin?: Spacing TODO enable this prop
180
160
  }
181
161
 
182
- type PropKeys = keyof DateInput2OwnProps
183
-
184
- type DateInput2Props = DateInput2OwnProps &
185
- OtherHTMLAttributes<
186
- DateInput2OwnProps,
187
- InputHTMLAttributes<DateInput2OwnProps & Element>
188
- >
162
+ type PropKeys = keyof DateInput2Props
189
163
 
190
164
  const propTypes: PropValidators<PropKeys> = {
191
165
  renderLabel: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired,
@@ -209,8 +183,7 @@ const propTypes: PropValidators<PropKeys> = {
209
183
  ]),
210
184
  locale: PropTypes.string,
211
185
  timezone: PropTypes.string,
212
- withYearPicker: PropTypes.object,
213
- formatDate: PropTypes.func
186
+ withYearPicker: PropTypes.object
214
187
  }
215
188
 
216
189
  export type { DateInput2Props }
package/src/index.ts CHANGED
@@ -25,4 +25,3 @@
25
25
  export { DateInput } from './DateInput'
26
26
  export { DateInput2 } from './DateInput2'
27
27
  export type { DateInputProps } from './DateInput/props'
28
- export type { DateInput2Props } from './DateInput2/props'