@instructure/ui-date-input 9.5.2-snapshot-7 → 9.6.0
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 +16 -2
- package/es/DateInput2/index.js +42 -33
- package/es/DateInput2/props.js +2 -1
- package/lib/DateInput2/index.js +42 -33
- package/lib/DateInput2/props.js +2 -1
- package/package.json +20 -20
- package/src/DateInput2/README.md +103 -1
- package/src/DateInput2/index.tsx +53 -48
- package/src/DateInput2/props.ts +33 -6
- package/src/index.ts +1 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/types/DateInput2/index.d.ts +34 -2
- package/types/DateInput2/index.d.ts.map +1 -1
- package/types/DateInput2/props.d.ts +10 -5
- package/types/DateInput2/props.d.ts.map +1 -1
- package/types/index.d.ts +1 -0
- package/types/index.d.ts.map +1 -1
package/src/DateInput2/index.tsx
CHANGED
@@ -25,7 +25,6 @@
|
|
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'
|
29
28
|
import { Calendar } from '@instructure/ui-calendar'
|
30
29
|
import { IconButton } from '@instructure/ui-buttons'
|
31
30
|
import {
|
@@ -43,25 +42,24 @@ import { jsx } from '@instructure/emotion'
|
|
43
42
|
import { propTypes } from './props'
|
44
43
|
import type { DateInput2Props } from './props'
|
45
44
|
import type { FormMessage } from '@instructure/ui-form-field'
|
45
|
+
import type { Moment } from '@instructure/ui-i18n'
|
46
46
|
|
47
|
-
function
|
48
|
-
|
47
|
+
function parseDate(dateString: string): string {
|
48
|
+
const date = new Date(dateString)
|
49
|
+
return isNaN(date.getTime()) ? '' : date.toISOString()
|
49
50
|
}
|
50
51
|
|
51
|
-
function
|
52
|
+
function defaultDateFormatter(
|
52
53
|
dateString: string,
|
53
54
|
locale: string,
|
54
55
|
timezone: string
|
55
|
-
)
|
56
|
-
return
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
timezone
|
63
|
-
)
|
64
|
-
.isValid()
|
56
|
+
) {
|
57
|
+
return new Date(dateString).toLocaleDateString(locale, {
|
58
|
+
month: 'long',
|
59
|
+
year: 'numeric',
|
60
|
+
day: 'numeric',
|
61
|
+
timeZone: timezone
|
62
|
+
})
|
65
63
|
}
|
66
64
|
|
67
65
|
/**
|
@@ -87,6 +85,8 @@ const DateInput2 = ({
|
|
87
85
|
locale,
|
88
86
|
timezone,
|
89
87
|
placeholder,
|
88
|
+
formatDate = defaultDateFormatter,
|
89
|
+
// margin, TODO enable this prop
|
90
90
|
...rest
|
91
91
|
}: DateInput2Props) => {
|
92
92
|
const [selectedDate, setSelectedDate] = useState<string>('')
|
@@ -97,6 +97,8 @@ 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
|
100
102
|
validateInput(true)
|
101
103
|
}, [value])
|
102
104
|
|
@@ -104,46 +106,53 @@ const DateInput2 = ({
|
|
104
106
|
setInputMessages(messages || [])
|
105
107
|
}, [messages])
|
106
108
|
|
107
|
-
|
108
|
-
|
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)
|
109
123
|
}
|
110
124
|
|
111
125
|
const handleDateSelected = (
|
112
126
|
dateString: string,
|
113
|
-
_momentDate:
|
127
|
+
_momentDate: Moment,
|
114
128
|
e: SyntheticEvent
|
115
129
|
) => {
|
116
|
-
const formattedDate =
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
timeZone: getTimezone()
|
121
|
-
})
|
122
|
-
handleInputChange(e, formattedDate)
|
130
|
+
const formattedDate = formatDate(dateString, getLocale(), getTimezone())
|
131
|
+
const parsedDate = parseDate(dateString)
|
132
|
+
setSelectedDate(parsedDate)
|
133
|
+
handleInputChange(e, formattedDate, parsedDate)
|
123
134
|
setShowPopover(false)
|
124
|
-
onRequestValidateDate?.(
|
135
|
+
onRequestValidateDate?.(dateString, true)
|
125
136
|
}
|
126
137
|
|
138
|
+
// onlyRemoveError is used to remove the error msg immediately when the user inputs a valid date (and don't wait for blur event)
|
127
139
|
const validateInput = (onlyRemoveError = false): boolean => {
|
128
|
-
//
|
129
|
-
|
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 || '')
|
140
|
+
// don't validate empty input
|
141
|
+
if (!value || parseDate(value) || selectedDate) {
|
137
142
|
setInputMessages(messages || [])
|
138
143
|
return true
|
139
144
|
}
|
140
|
-
if
|
141
|
-
|
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([
|
142
152
|
{
|
143
153
|
type: 'error',
|
144
154
|
text: invalidDateErrorMessage
|
145
|
-
}
|
146
|
-
...messages
|
155
|
+
}
|
147
156
|
])
|
148
157
|
}
|
149
158
|
|
@@ -171,14 +180,9 @@ const DateInput2 = ({
|
|
171
180
|
|
172
181
|
const handleBlur = (e: SyntheticEvent) => {
|
173
182
|
const isInputValid = validateInput(false)
|
174
|
-
if (isInputValid &&
|
175
|
-
const formattedDate =
|
176
|
-
|
177
|
-
year: 'numeric',
|
178
|
-
day: 'numeric',
|
179
|
-
timeZone: getTimezone()
|
180
|
-
})
|
181
|
-
handleInputChange(e, formattedDate)
|
183
|
+
if (isInputValid && selectedDate) {
|
184
|
+
const formattedDate = formatDate(selectedDate, getLocale(), getTimezone())
|
185
|
+
handleInputChange(e, formattedDate, selectedDate)
|
182
186
|
}
|
183
187
|
onRequestValidateDate?.(value, isInputValid)
|
184
188
|
onBlur?.(e)
|
@@ -187,6 +191,7 @@ const DateInput2 = ({
|
|
187
191
|
return (
|
188
192
|
<TextInput
|
189
193
|
{...passthroughProps(rest)}
|
194
|
+
// margin={'large'} TODO add this prop to TextInput
|
190
195
|
renderLabel={renderLabel}
|
191
196
|
onChange={handleInputChange}
|
192
197
|
onBlur={handleBlur}
|
@@ -225,8 +230,8 @@ const DateInput2 = ({
|
|
225
230
|
onDateSelected={handleDateSelected}
|
226
231
|
selectedDate={selectedDate}
|
227
232
|
visibleMonth={selectedDate}
|
228
|
-
locale={
|
229
|
-
timezone={
|
233
|
+
locale={getLocale()}
|
234
|
+
timezone={getTimezone()}
|
230
235
|
role="listbox"
|
231
236
|
renderNextMonthButton={
|
232
237
|
<IconButton
|
package/src/DateInput2/props.ts
CHANGED
@@ -23,14 +23,18 @@
|
|
23
23
|
*/
|
24
24
|
|
25
25
|
import PropTypes from 'prop-types'
|
26
|
-
import type { SyntheticEvent } from 'react'
|
26
|
+
import type { SyntheticEvent, InputHTMLAttributes } 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 {
|
31
|
+
import type {
|
32
|
+
OtherHTMLAttributes,
|
33
|
+
Renderable,
|
34
|
+
PropValidators
|
35
|
+
} from '@instructure/shared-types'
|
32
36
|
|
33
|
-
type
|
37
|
+
type DateInput2OwnProps = {
|
34
38
|
/**
|
35
39
|
* Specifies the input label.
|
36
40
|
*/
|
@@ -56,7 +60,11 @@ type DateInput2Props = {
|
|
56
60
|
/**
|
57
61
|
* Callback fired when the input changes.
|
58
62
|
*/
|
59
|
-
onChange?: (
|
63
|
+
onChange?: (
|
64
|
+
event: React.SyntheticEvent,
|
65
|
+
inputValue: string,
|
66
|
+
dateString: string
|
67
|
+
) => void
|
60
68
|
/**
|
61
69
|
* Callback executed when the input fires a blur event.
|
62
70
|
*/
|
@@ -157,9 +165,27 @@ type DateInput2Props = {
|
|
157
165
|
startYear: number
|
158
166
|
endYear: number
|
159
167
|
}
|
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
|
160
180
|
}
|
161
181
|
|
162
|
-
type PropKeys = keyof
|
182
|
+
type PropKeys = keyof DateInput2OwnProps
|
183
|
+
|
184
|
+
type DateInput2Props = DateInput2OwnProps &
|
185
|
+
OtherHTMLAttributes<
|
186
|
+
DateInput2OwnProps,
|
187
|
+
InputHTMLAttributes<DateInput2OwnProps & Element>
|
188
|
+
>
|
163
189
|
|
164
190
|
const propTypes: PropValidators<PropKeys> = {
|
165
191
|
renderLabel: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired,
|
@@ -183,7 +209,8 @@ const propTypes: PropValidators<PropKeys> = {
|
|
183
209
|
]),
|
184
210
|
locale: PropTypes.string,
|
185
211
|
timezone: PropTypes.string,
|
186
|
-
withYearPicker: PropTypes.object
|
212
|
+
withYearPicker: PropTypes.object,
|
213
|
+
formatDate: PropTypes.func
|
187
214
|
}
|
188
215
|
|
189
216
|
export type { DateInput2Props }
|
package/src/index.ts
CHANGED