Skip to content

Commit

Permalink
WIP(ui-date-input): wip
Browse files Browse the repository at this point in the history
  • Loading branch information
balzss committed Aug 15, 2024
1 parent 53da40c commit a7b3b8c
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 59 deletions.
13 changes: 8 additions & 5 deletions packages/ui-date-input/src/DateInput2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
describes: DateInput2
---

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.
`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. `DateInput` will be deprecated in the future, but for now, developers can start using `DateInput2` and provide feedback.

### Minimal config

Expand All @@ -21,7 +21,10 @@ This component is an updated version of [`DateInput`](/#DateInput) that's easier
}}
value={this.state.value}
width="20rem"
onChange={(e, value) => this.setState({ value })}
onChange={(e, dateString, inputValue) => {
console.log({ dateString, inputValue })
this.setState({ value: dateString })
}}
invalidDateErrorMessage="Invalid date"
/>
)
Expand Down Expand Up @@ -115,9 +118,9 @@ This component is an updated version of [`DateInput`](/#DateInput) that's easier

### Date validation

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.
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 triggered on the blur event of the input field.
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:
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:
```js
---
Expand Down Expand Up @@ -170,7 +173,7 @@ render(<Example />)
### Date formatting
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.
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.
Something to pay attention to is that the date string passed back in the callback function **is in UTC timezone**.
```js
Expand Down
63 changes: 28 additions & 35 deletions packages/ui-date-input/src/DateInput2/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ function defaultDateFormatter(
---
category: components
---
@module experimental
**/
const DateInput2 = ({
renderLabel,
Expand All @@ -89,37 +91,25 @@ const DateInput2 = ({
// margin, TODO enable this prop
...rest
}: DateInput2Props) => {
const [selectedDate, setSelectedDate] = useState<string>('')
const [inputValue, setInputValue] = useState<string>('')
const [inputMessages, setInputMessages] = useState<FormMessage[]>(
messages || []
)
const [showPopover, setShowPopover] = useState<boolean>(false)
const localeContext = useContext(ApplyLocaleContext)

useEffect(() => {
// when `value` is changed, validation removes the error message if passes
// but it's NOT adding error message if validation fails for better UX
validateInput(true)
}, [value])

useEffect(() => {
setInputMessages(messages || [])
}, [messages])

useEffect(() => {
setSelectedDate(parseDate(value || ''))
}, [])
validateInput(true)
}, [inputValue])

const handleInputChange = (
e: SyntheticEvent,
newValue: string,
parsedDate: string = ''
) => {
// blur event formats the input which shouldn't trigger parsing
if (e.type !== 'blur') {
setSelectedDate(parseDate(newValue))
}
onChange?.(e, newValue, parsedDate)
const handleInputChange = (e: SyntheticEvent, newValue: string) => {
setInputValue(newValue)
const parsedInput = parseDate(newValue)
onChange?.(e, parsedInput, newValue)
}

const handleDateSelected = (
Expand All @@ -129,16 +119,16 @@ const DateInput2 = ({
) => {
const formattedDate = formatDate(dateString, getLocale(), getTimezone())
const parsedDate = parseDate(dateString)
setSelectedDate(parsedDate)
handleInputChange(e, formattedDate, parsedDate)
setInputValue(formattedDate)
setShowPopover(false)
onChange?.(e, parsedDate, formattedDate)
onRequestValidateDate?.(dateString, true)
}

// onlyRemoveError is used to remove the error msg immediately when the user inputs a valid date (and don't wait for blur event)
const validateInput = (onlyRemoveError = false): boolean => {
// don't validate empty input
if (!value || parseDate(value) || selectedDate) {
if (!inputValue || parseDate(inputValue) || value) {
setInputMessages(messages || [])
return true
}
Expand All @@ -159,12 +149,25 @@ const DateInput2 = ({
return false
}

const handleBlur = (e: SyntheticEvent) => {
const parsedDate = parseDate(inputValue)
if (parsedDate) {
const formattedDate = formatDate(parsedDate, getLocale(), getTimezone())
setInputValue(formattedDate)
onChange?.(e, parsedDate, formattedDate)
}
validateInput(false)
onRequestValidateDate?.(value, !!parsedDate)
onBlur?.(e)
}

const getLocale = () => {
if (locale) {
return locale
} else if (localeContext.locale) {
return localeContext.locale
}
// default to the system's locale
return Locale.browserLocale()
}

Expand All @@ -178,16 +181,6 @@ const DateInput2 = ({
return Intl.DateTimeFormat().resolvedOptions().timeZone
}

const handleBlur = (e: SyntheticEvent) => {
const isInputValid = validateInput(false)
if (isInputValid && selectedDate) {
const formattedDate = formatDate(selectedDate, getLocale(), getTimezone())
handleInputChange(e, formattedDate, selectedDate)
}
onRequestValidateDate?.(value, isInputValid)
onBlur?.(e)
}

return (
<TextInput
{...passthroughProps(rest)}
Expand All @@ -196,7 +189,7 @@ const DateInput2 = ({
onChange={handleInputChange}
onBlur={handleBlur}
isRequired={isRequired}
value={value}
value={inputValue}
placeholder={placeholder}
width={width}
size={size}
Expand Down Expand Up @@ -228,8 +221,8 @@ const DateInput2 = ({
<Calendar
withYearPicker={withYearPicker}
onDateSelected={handleDateSelected}
selectedDate={selectedDate}
visibleMonth={selectedDate}
selectedDate={value}
visibleMonth={value}
locale={getLocale()}
timezone={getTimezone()}
role="listbox"
Expand Down
27 changes: 8 additions & 19 deletions packages/ui-date-input/src/DateInput2/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ type DateInput2OwnProps = {
nextMonthButton: string
}
/**
* Specifies the input value.
* Specifies the input value *before* formatting. The `formatDate` will be applied to it before displaying. Should be a valid, parsable date.
*/
value?: string // TODO: controllable(PropTypes.string)
value?: string
/**
* Specifies the input size.
*/
Expand All @@ -62,8 +62,8 @@ type DateInput2OwnProps = {
*/
onChange?: (
event: React.SyntheticEvent,
inputValue: string,
dateString: string
isoDateString: string,
formattedValue: string
) => void
/**
* Callback executed when the input fires a blur event.
Expand Down Expand Up @@ -99,21 +99,12 @@ type DateInput2OwnProps = {
*/
messages?: FormMessage[]
/**
* Callback fired requesting the calendar be shown.
*/
onRequestShowCalendar?: (event: SyntheticEvent) => void
/**
* Callback fired requesting the calendar be hidden.
*/
onRequestHideCalendar?: (event: SyntheticEvent) => void
/**
* Callback fired when the input is blurred. Feedback should be provided
* to the user when this function is called if the selected date or input
* value is invalid. The component has an internal check whether the date can
* be parsed to a valid date.
* Callback fired when the input is blurred or a date is selected from the calendar.
* Feedback should be provided to the user when this function is called if the selected date or input
* value is invalid. The component has an internal check whether the date can be parsed to a valid date.
*/
onRequestValidateDate?: (
value?: string,
isoDateString?: string,
internalValidationPassed?: boolean
) => void | FormMessage[]
/**
Expand Down Expand Up @@ -200,8 +191,6 @@ const propTypes: PropValidators<PropKeys> = {
isInline: PropTypes.bool,
width: PropTypes.string,
messages: PropTypes.arrayOf(FormPropTypes.message),
onRequestShowCalendar: PropTypes.func,
onRequestHideCalendar: PropTypes.func,
onRequestValidateDate: PropTypes.func,
invalidDateErrorMessage: PropTypes.oneOfType([
PropTypes.func,
Expand Down

0 comments on commit a7b3b8c

Please sign in to comment.