Ant Design is one of the most popular design frameworks that help designers and developers to build beautiful products and reusable components.
Here is the figma design that I’ve been tasked to build.
As I look at this design, I see multiple components.
- Popover
- Button that will trigger the popover with button states
- Flexible tab items
- Calendar with date range picker
- Buttons that will confirm or cancel the user selection
There are a lot of components involved. But for this article, we’ll focus on the range picker.
There are a couple of challenges that I encountered in this ticket:
1. Based on the design, how do we keep it open in a popover?
AntD date picker is by default closed
and showing the input
box for your date selection. The challenge is to keep it open in the popover. Checking the documentation, we have an open
option. Easy. But how about the popover? Out of curiosity, I asked the designer why do we need to keep it open? Her answer was it helps in the user experience that they can select already. It also doesn’t feel right that we’ll have another popup for it.
The good thing about AntD is its extensive community, with developers helping one another. One of the answers I got was to attach it to the parent node. Makes sense!
<RangePicker
getPopupContainer={node => node.parentNode}
inputReadOnly
open
onChange={handleChange}
/>
2. Based on the design, how do we customize it?
The thing about AntD is that we have to follow its design language. We can set the class name above it but in order for us to override the design of the AntD component, we must use the same selector name they used.
.status-updated-at-filter {
&__date-container {
height: 350px;
margin-top: 24px;
padding-left: 16px;
padding-right: 16px;
width: 595px;
.ant-picker .ant-picker-input > input {
padding-bottom: 8px;
padding-top: 8px;
}
}
}
3. Based on the design, how do we set the default date so when the user re-selects the existing selection is still there?
By default, AntD will show a blank input box so there is no bias as to what date to choose. But in reality, we allow our users to re-select or change once an option has been chosen. To do this, we use the defaultValue
property. When a user selects dates, the result will be an array of dates with string property. In setting a default value, it will not accept it as is. We need to use a library to accept the date. This is where Day.js
comes in. It is a very lightweight (2KB) library that is an alternative to Moment.js
. Let’s lose the extra baggage that we don’t need!
const DATE_FORMAT = 'YYYY-MM-DD';
const handleDefaultValue = () => {
if (selectedDates) {
return [dayjs(defaultValue[0], DATE_FORMAT), dayjs(defaultValue[1], DATE_FORMAT)];
}
return [dayjs(), dayjs()];
};
If we have a previous selection, we’ll use that, otherwise, we’ll return today’s date.
I thought this is good already, but when I ran the code, it showed me an error: clone.weekday is not a function
. We need to add extra dayjs
plugin to make it work. Good thing the community is helpful, it saved me tons of time to debug and shared what works for them.
Here’s what it looks like after the change:
import React, { useState } from 'react';
import T from 'prop-types';
import { DatePicker } from 'antd';
import dayjs from 'dayjs';
import localeData from 'dayjs/plugin/localeData';
import weekday from 'dayjs/plugin/weekday';
import TabFilterActions from './TabFilterActions';
import './StatusUpdatedAtFilter.scss';
dayjs.extend(weekday);
dayjs.extend(localeData);
const { RangePicker } = DatePicker;
const DATE_FORMAT = 'YYYY-MM-DD';
const StatusUpdatedAtFilter = props => {
const { defaultValue, onCloseFilter, onApplyFilter } = props;
const [selectedDates, setSelectedDates] = useState(defaultValue);
const handleDefaultValue = () => {
if (selectedDates) {
return [dayjs(defaultValue[0], DATE_FORMAT), dayjs(defaultValue[1], DATE_FORMAT)];
}
return [dayjs(), dayjs()];
};
const handleChange = (date, dateArray) => {
setSelectedDates(dateArray);
};
const handleApplyFilter = () => {
onApplyFilter(selectedDates);
onCloseFilter();
};
return (
<>
<div className="status-updated-at-filter__date-container">
<RangePicker
defaultValue={handleDefaultValue}
format={DATE_FORMAT}
getPopupContainer={node => node.parentNode}
inputReadOnly
open
placement="bottomLeft"
popupClassName="status-updated-at-filter__date-container-picker"
size="small"
onChange={handleChange}
/>
</div>
<TabFilterActions onApplyFilter={handleApplyFilter} onCloseFilter={onCloseFilter} />
</>
);
};
StatusUpdatedAtFilter.propTypes = {
defaultValue: T.arrayOf(T.string),
onCloseFilter: T.func.isRequired,
onApplyFilter: T.func.isRequired,
};
StatusUpdatedAtFilter.defaultProps = {
defaultValue: undefined,
};
export default StatusUpdatedAtFilter;
Thanks to AntD for the great community and flexible components that we can use in our projects.