-
Notifications
You must be signed in to change notification settings - Fork 87
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[WNMGDS-1627] Single-input date field (#1876)
* Pull date field changes from original branch * Hook up the onChange function * Working on refining the styles * Implement grey border between that disappears when focusing so our focus styles are 👌 * Style the border, shadow, position, and spacing for the calendar picker * Add some default translations * Handle picker changes * Add some commented code borrowed from the Tooltip It would be nicer to be able to use the NativeDialog component for this so we don't have to create a listener for clicking outside the component. * Handle clicking outside and pressing escape with hooks * Remove old DateField files * Refactor useLabelMask to take input props instead of input element for consistency and simplicity * Validate date string before passing to DayPicker and remember the month In order to make it open to the currently selected month, we have to set the defaultMonth to the selected date. DayPicker uses internal state to track the current month, but we clear all that state when we stop rendering the picker (because we hide it) * Fix focus bug in useLabelMask * Don't export this new component yet * Add doc comments for the `useClickOutsideHandler` * Make `onChange` required because we treat it thusly
- Loading branch information
Showing
22 changed files
with
775 additions
and
81 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
6 changes: 3 additions & 3 deletions
6
...c/components/DateField/DateField.test.tsx → ...ts/DateField/MultiInputDateField.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
38 changes: 38 additions & 0 deletions
38
packages/design-system/src/components/DateField/SingleInputDateField.stories.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import React, { useState } from 'react'; | ||
import SingleInputDateField from './SingleInputDateField'; | ||
|
||
export default { | ||
title: 'Components/SingleInputDateField', | ||
component: SingleInputDateField, | ||
argTypes: { | ||
errorMessage: { | ||
control: { type: 'text' }, | ||
}, | ||
hint: { | ||
control: { type: 'text' }, | ||
}, | ||
label: { | ||
control: { type: 'text' }, | ||
}, | ||
requirementLabel: { | ||
control: { type: 'text' }, | ||
}, | ||
}, | ||
args: { | ||
label: 'Birthday', | ||
hint: 'Please enter your birthday', | ||
name: 'single-input-date-field', | ||
}, | ||
}; | ||
|
||
const Template = ({ ...args }) => { | ||
const [dateString, setDateString] = useState(''); | ||
return <SingleInputDateField {...args} value={dateString} onChange={setDateString} />; | ||
}; | ||
|
||
export const Default = Template.bind({}); | ||
|
||
export const WithPicker = Template.bind({}); | ||
WithPicker.args = { | ||
fromYear: new Date().getFullYear(), | ||
}; |
115 changes: 115 additions & 0 deletions
115
packages/design-system/src/components/DateField/SingleInputDateField.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import React, { useRef, useState } from 'react'; | ||
import CalendarIcon from '../Icons/CalendarIcon'; | ||
import classNames from 'classnames'; | ||
import isMatch from 'date-fns/isMatch'; | ||
import useLabelMask from '../TextField/useLabelMask'; | ||
import useClickOutsideHandler from '../utilities/useClickOutsideHandler'; | ||
import usePressEscapeHandler from '../utilities/usePressEscapeHandler'; | ||
import { DayPicker } from 'react-day-picker'; | ||
import { DATE_MASK, RE_DATE } from '../TextField/useLabelMask'; | ||
import { FormFieldProps, FormLabel, useFormLabel } from '../FormLabel'; | ||
import { TextInput } from '../TextField'; | ||
|
||
export interface SingleInputDateFieldProps extends FormFieldProps { | ||
onBlur?: (event: React.FocusEvent<HTMLInputElement>) => any; | ||
onChange: (updatedValue: string, maskedValue: string) => any; | ||
value?: string; | ||
name: string; | ||
|
||
// From DayPicker | ||
// ------------------------- | ||
defaultMonth?: Date; | ||
fromDate?: Date; | ||
fromMonth?: Date; | ||
fromYear?: number; | ||
toDate?: Date; | ||
toMonth?: Date; | ||
toYear?: number; | ||
} | ||
|
||
const SingleInputDateField = (props: SingleInputDateFieldProps) => { | ||
const { | ||
className, | ||
onChange, | ||
defaultMonth, | ||
fromDate, | ||
fromMonth, | ||
fromYear, | ||
toDate, | ||
toMonth, | ||
toYear, | ||
...remainingProps | ||
} = props; | ||
const withPicker = fromDate || fromMonth || fromYear; | ||
const [pickerVisible, setPickerVisible] = useState(false); | ||
|
||
function handleInputChange(event) { | ||
const updatedValue = event.currentTarget.value; | ||
onChange(updatedValue, DATE_MASK(updatedValue, true)); | ||
} | ||
|
||
const { labelProps, fieldProps, wrapperProps, bottomError } = useFormLabel({ | ||
...remainingProps, | ||
className: classNames( | ||
'ds-c-single-input-date-field', | ||
{ 'ds-c-single-input-date-field--with-picker': withPicker }, | ||
className | ||
), | ||
labelComponent: 'label', | ||
wrapperIsFieldset: false, | ||
}); | ||
const { labelMask, inputProps } = useLabelMask(DATE_MASK, { | ||
...fieldProps, | ||
onChange: handleInputChange, | ||
type: 'text', | ||
}); | ||
|
||
function handlePickerChange(date: Date) { | ||
const updatedValue = `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}`; | ||
onChange(DATE_MASK(updatedValue), DATE_MASK(updatedValue, true)); | ||
setPickerVisible(false); | ||
} | ||
|
||
const dayPickerRef = useRef(); | ||
const calendarButtonRef = useRef(); | ||
useClickOutsideHandler([dayPickerRef, calendarButtonRef], () => setPickerVisible(false)); | ||
usePressEscapeHandler(dayPickerRef, () => setPickerVisible(false)); | ||
|
||
// Validate the date string (value) and make date null if it's invalid. We don't want to pass | ||
// a bizarre date to DayPicker like `new Date('01/02')`, which is interpreted as `Jan 02, 2001` | ||
const dateString = DATE_MASK(props.value, true); | ||
const validDateString = isMatch(dateString, 'MM/dd/yyyy'); | ||
const date = validDateString ? new Date(dateString) : null; | ||
|
||
return ( | ||
<div {...wrapperProps}> | ||
<FormLabel {...labelProps} /> | ||
{labelMask} | ||
<div className="ds-c-single-input-date-field__field-wrapper"> | ||
<TextInput {...inputProps} /> | ||
{withPicker && ( | ||
<button | ||
className="ds-c-single-input-date-field__button" | ||
onClick={() => setPickerVisible(!pickerVisible)} | ||
ref={calendarButtonRef} | ||
> | ||
<CalendarIcon ariaHidden={false} /> | ||
</button> | ||
)} | ||
</div> | ||
{pickerVisible && ( | ||
<div ref={dayPickerRef}> | ||
<DayPicker | ||
mode="single" | ||
selected={date} | ||
defaultMonth={date} | ||
onSelect={handlePickerChange} | ||
/> | ||
</div> | ||
)} | ||
{bottomError} | ||
</div> | ||
); | ||
}; | ||
|
||
export default SingleInputDateField; |
2 changes: 1 addition & 1 deletion
2
...eld/__snapshots__/DateField.test.tsx.snap → ...shots__/MultiInputDateField.test.tsx.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,5 @@ | ||
export { default as DateField } from './DateField'; | ||
export * from './MultiInputDateField'; | ||
// export * from './SingleInputDateField'; | ||
// Alias the MultiInputDateField as its old name | ||
export { default as DateField } from './MultiInputDateField'; | ||
export { default as DateInput } from './DateInput'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.