Skip to content

Commit

Permalink
feat: datepicker improvements (#1224)
Browse files Browse the repository at this point in the history
In this change, we update the Datepicker
* to accept and set a `defaultValue` when `enableRangeSelection` is set. For example, `defaultValue='01/01/2019 - 01/01/2020'`
* add story for above use case
* to allow spreading props into the `<InputGroup.Addon>`, footer, and footer `<Button>`
  • Loading branch information
prsdthkr committed Sep 28, 2020
1 parent dc456f5 commit 5d71ff0
Show file tree
Hide file tree
Showing 5 changed files with 241 additions and 83 deletions.
99 changes: 60 additions & 39 deletions src/DatePicker/DatePicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,22 @@ const dateRangeSeparator = ' - ';
class DatePicker extends Component {
constructor(props) {
super(props);
const formattedDate = props.defaultValue.length > 0 ? this.getFormattedDateStr(props.defaultValue) : '';
const isoFormattedDate = props.defaultValue.length > 0
? moment(props.defaultValue, props.dateFormat).format(ISO_DATE_FORMAT)
: '';

this.state = {
isExpanded: false,
selectedDate: formattedDate.length === 0 ? null : this.getMomentDateObj(formattedDate),
startAndEndDates: [],
formattedDate,
isoFormattedDate
formattedDate: this.props?.defaultValue || '',
isExpanded: false
};

this.calendarRef = React.createRef();
this.popoverRef = React.createRef();
}


toISOFormat = (dateStr) => {
if (!dateStr || !dateStr?.trim().length) return '';
return moment(dateStr, this.resolveFormat()).format(ISO_DATE_FORMAT);
}

/**
* Function tries to format any date string into the format specified by dateFormat.
* It will use format derived from locale if dateFormat is not specified.
Expand Down Expand Up @@ -122,22 +122,20 @@ class DatePicker extends Component {
this.setState({ formattedDate: e.target.value });
}

componentDidMount() {
this.validateDates();
}

componentDidUpdate(prevProps) {
if (prevProps.defaultValue !== this.props.defaultValue) {
this.handleNewDefault();
}
}

handleNewDefault = () => {
const { dateFormat, defaultValue } = this.props;
const formattedNewDefault = defaultValue && defaultValue.length > 0 ? this.getFormattedDateStr(defaultValue) : '';
const { defaultValue } = this.props;
this.setState({
selectedDate: formattedNewDefault.length === 0 ? null : this.getMomentDateObj(formattedNewDefault),
isoFormattedDate: defaultValue && defaultValue.length > 0
? moment(defaultValue, dateFormat).format(ISO_DATE_FORMAT)
: '',
formattedDate: formattedNewDefault,
startAndEndDates: []
formattedDate: defaultValue
}, () => {
this.validateDates();
});
Expand All @@ -147,9 +145,7 @@ class DatePicker extends Component {
e.stopPropagation();
this.setState({
formattedDate: e.target.value,
isoFormattedDate: e.target.value
? moment(e.target.value, this.props.dateFormat).format(ISO_DATE_FORMAT)
: ''
isoFormattedDate: this.toISOFormat(e.target?.value)
}, () => {
this.props.onChange(this.getCallbackData(), 'inputChange');
});
Expand Down Expand Up @@ -183,7 +179,9 @@ class DatePicker extends Component {
validateDates = (postValidationCallback) => {
const { formattedDate } = this.state;

if (this.props.enableRangeSelection) {
if (!formattedDate || !formattedDate?.trim().length) {
this.resetState(postValidationCallback);
} else if (this.props.enableRangeSelection) {
const dateRange = formattedDate.split(dateRangeSeparator);
const firstDate = this.getMomentDateObj(dateRange[0]);
const secondDate = this.getMomentDateObj(dateRange[1]);
Expand All @@ -195,9 +193,10 @@ class DatePicker extends Component {
: (arrSelected = [firstDate, secondDate]);
const newFormattedDateRangeStr = this.getFormattedDateRangeStr(arrSelected);
this.setState({
formattedDate: newFormattedDateRangeStr,
isoFormatDate: arrSelected[0].format(ISO_DATE_FORMAT) + dateRangeSeparator + arrSelected[1].format(ISO_DATE_FORMAT),
selectedDate: null,
startAndEndDates: arrSelected,
formattedDate: newFormattedDateRangeStr
startAndEndDates: arrSelected
}, () => {
if (formattedDate !== newFormattedDateRangeStr) {
this.executeCallback(this.props.onChange, 'autoFormatDateRange');
Expand All @@ -214,9 +213,7 @@ class DatePicker extends Component {
this.setState({
selectedDate: newDate,
formattedDate: newFormattedDateStr,
isoFormattedDate: formattedDate
? moment(formattedDate, this.props.dateFormat).format(ISO_DATE_FORMAT)
: '',
isoFormattedDate: newDate.format(ISO_DATE_FORMAT),
startAndEndDates: []
}, () => {
if (formattedDate !== newFormattedDateStr) {
Expand Down Expand Up @@ -267,16 +264,17 @@ class DatePicker extends Component {
const { formattedDate } = this.state;

if (this.props.enableRangeSelection) {
let isoFormatDate = date[0].format(ISO_DATE_FORMAT);
let isoFormatDateRange = date[0].format(ISO_DATE_FORMAT);
if (!!date[1]) {
isoFormatDate += dateRangeSeparator + date[1].format(ISO_DATE_FORMAT);
isoFormatDateRange += dateRangeSeparator + date[1].format(ISO_DATE_FORMAT);
closeCalendar = true;
}
const newFormattedDateRangeStr = this.getFormattedDateRangeStr(date);
this.setState({
startAndEndDates: date,
formattedDate: newFormattedDateRangeStr,
isoFormattedDate: isoFormatDate
isoFormattedDate: isoFormatDateRange,
selectedDate: null,
startAndEndDates: date
}, () => {
if (formattedDate !== newFormattedDateRangeStr) {
this.props.onChange(this.getCallbackData(), reason);
Expand All @@ -286,9 +284,9 @@ class DatePicker extends Component {
closeCalendar = true;
const newFormattedDate = this.getFormattedDateStr(date);
this.setState({
selectedDate: date,
formattedDate: newFormattedDate,
isoFormattedDate: date.format(ISO_DATE_FORMAT),
isoFormattedDate: this.toISOFormat(newFormattedDate),
selectedDate: date,
startAndEndDates: []
}, () => {
if (formattedDate !== newFormattedDate) {
Expand Down Expand Up @@ -341,6 +339,7 @@ class DatePicker extends Component {

render() {
const {
addonProps,
blockedDates,
buttonLabel,
buttonProps,
Expand All @@ -356,6 +355,8 @@ class DatePicker extends Component {
disableWeekday,
disableWeekends,
enableRangeSelection,
footerButtonProps,
footerClasses,
inputProps,
inputGroupProps,
locale,
Expand All @@ -380,7 +381,8 @@ class DatePicker extends Component {
'fd-input-group--control',
{
[`is-${validationState?.state}`]: validationState?.state
}
},
inputGroupProps?.className
);

const datepickerFooterClassName = classnames(
Expand All @@ -390,7 +392,13 @@ class DatePicker extends Component {
{
'fd-bar--cozy': !compact,
'fd-bar--compact': compact
}
},
footerClasses
);

const footerButtonClassnames = classnames(
'fd-dialog__decisive-button',
footerButtonProps?.className
);

const disableButton = disabled || readOnly;
Expand All @@ -414,7 +422,9 @@ class DatePicker extends Component {
placeholder={this.getPlaceHolder(dateFormat)}
readOnly={readOnly}
value={this.state.formattedDate} />
<InputGroup.Addon isButton>
<InputGroup.Addon
{...addonProps}
isButton>
<Button {...buttonProps}
aria-label={buttonLabel}
disabled={disableButton}
Expand Down Expand Up @@ -471,8 +481,8 @@ class DatePicker extends Component {
...localizedText,
todayLabel: todayAction.label
}}
onChange={ (e, todayNavigated) => {
this.updateDate(e, todayNavigated, todayNavigated ? 'todayNavigated' : 'calendarDateClicked');
onChange={ (date, todayNavigated) => {
this.updateDate(date, todayNavigated, todayNavigated ? 'todayNavigated' : 'calendarDateClicked');
}}
openToDate={openToDate}
ref={this.calendarRef}
Expand All @@ -484,7 +494,8 @@ class DatePicker extends Component {
<div className='fd-bar__right'>
<div className='fd-bar__element'>
<Button
className='fd-dialog__decisive-button'
{...footerButtonProps}
className={footerButtonClassnames}
compact={compact}
onClick={this.setTodayDate}>
{todayAction.label}
Expand Down Expand Up @@ -513,6 +524,8 @@ DatePicker.displayName = 'DatePicker';

DatePicker.propTypes = {
...Calendar.PropTypes,
/** Additional props to be spread to the input group addon button container i.e. `<InputGroup.Addon {...addonProps} >` */
addonProps: PropTypes.object,
/** aria-label for datepicker button */
buttonLabel: PropTypes.string,
/** Additional props to be spread to the `<button>` element */
Expand All @@ -529,12 +542,20 @@ DatePicker.propTypes = {
/** Format to use for displaying the inputted or selected date. E.g. "YYYY.M.D", "DD-MM-YYYY", "MM/DD/YYYY" etc.
* This overrides the date format derived from any set locale. */
dateFormat: PropTypes.string,
/** Default value to be shown in the Datepicker */
/**
* Default value to be shown in the Datepicker
* for example, `defaultValue='12/04/1993'`
* or when range selection is enabled `defaultValue='12/04/1993 - 12/30/1992'` (will auto sort chronologically)
* */
defaultValue: PropTypes.string,
/** Set to **true** to mark component as disabled and make it non-interactive */
disabled: PropTypes.bool,
/** Set to **true** to enable the selection of a date range (begin and end) */
enableRangeSelection: PropTypes.bool,
/** Additional props to to apply to calendar footer button*/
footerButtonProps: PropTypes.string,
/** Classnames to apply to calendar footer that will contain the 'Today' action */
footerClasses: PropTypes.string,
/** Additional props to be spread to the `InputGroup` component */
inputGroupProps: PropTypes.object,
/** Additional props to be spread to the `<input>` element */
Expand Down
24 changes: 23 additions & 1 deletion src/DatePicker/DatePicker.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -249,10 +249,32 @@ describe('<DatePicker />', () => {
defaultValue='3.16.20'
locale='hi' />
);
wrapper = mount(compToTest);

act(() => {
wrapper = mount(compToTest);
});

expect(wrapper.state('formattedDate')).toEqual('०३/१६/२०२०');
});

describe('Date range', () => {
test('default value with dateFormat and locale set', ()=>{
const compToTest = (
<DatePicker
dateFormat='MM/DD/YYYY'
defaultValue='3.20.20 - 3.16.20'
enableRangeSelection
locale='hi' />
);
act(()=>{
wrapper = mount(compToTest);
});

expect(wrapper.state('formattedDate')).toEqual('०३/१६/२०२० - ०३/२०/२०२०');
});
});


test('set defaultDate, unset dateFormat, set locale', ()=>{
const compToTest = (
<DatePicker
Expand Down
38 changes: 34 additions & 4 deletions src/DatePicker/__stories__/DatePicker.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,37 @@ export const localized = () => (
localized.storyName = 'Localized DatePicker';

export const rangeSelection = () => (
<DatePicker enableRangeSelection />
<Container>
<Row>
<Column>
<div>
<FormLabel
htmlFor='rangeSelection'>
Select a date range
</FormLabel>
<DatePicker
enableRangeSelection
inputProps={{
id: 'rangeSelection'
}} />
</div>
</Column>
<Column>
<div>
<FormLabel
htmlFor='rangeSelectionDefaultValue'>
Select a date range (default set)
</FormLabel>
<DatePicker
defaultValue='12/04/1993 - 12/30/1992'
enableRangeSelection
inputProps={{
id: 'rangeSelectionDefaultValue'
}} />
</div>
</Column>
</Row>
</Container>
);

rangeSelection.storyName = 'Enabled Range Selection';
Expand Down Expand Up @@ -411,7 +441,7 @@ export const dev = () => (
dateFormat={
select(dateFormatOptionsLabel, dateFormatOptions, 'DD/MM/YYYY')
}
defaultValue={text('Default Value', '20/06/2020')}
defaultValue={text('Default Value', '20/06/2020 - 18/06/2020')}
disableAfterDate={dateKnobToDate('disable after date', afterDateDefault)}
disableBeforeDate={dateKnobToDate('disable before date', beforeDateDefault)}
disableFutureDates={boolean('disable future dates', false)}
Expand All @@ -420,8 +450,8 @@ export const dev = () => (
disableWeekends={boolean('disable weekends', false)}
disabledDates={[dateKnobToDate('disable between dates (1)', disabledDateFirstDefault),
dateKnobToDate('disable between dates (2)', disabledDateSecondDefault)]}
enableRangeSelection={boolean('enableRangeSelection', false)}
locale={text('locale', 'en')}
enableRangeSelection={boolean('enableRangeSelection', true)}
locale={text('locale', 'hi')}
onChange={action('on-change')}
onDatePickerClose={action('on-date-picker-close')}
onInputBlur={action('on-input-blur')}
Expand Down
Loading

0 comments on commit 5d71ff0

Please sign in to comment.