feat: ui v2 - Date Range Component (#1537)
* feat: Date Range wrapper component * fix yarn issues * add calendar icon * date range css styles * date range fixes * fix propTypes warning * fix clear method, defaultProps & onChange method * update inline styles to use tailwind classes * date range documentation page * minor refactor
This commit is contained in:
1 parent
0ec277dd56
commit
b183e78f47
13 files changed
+407
-38
No files matched your search
@@ -0,0 +1,98 @@
|
||||
.DateRangePicker {
|
||||
}
|
||||
|
||||
/** CONTAINER STYLES **/
|
||||
|
||||
.DateRangePickerInput {
|
||||
@apply bg-transparent border-0 flex;
|
||||
}
|
||||
|
||||
.DateRangePicker_picker {
|
||||
@apply -mt-1;
|
||||
}
|
||||
|
||||
/** INPUT DIV STYLES **/
|
||||
|
||||
.DateInput {
|
||||
background: transparent;
|
||||
@apply flex flex-1 w-auto;
|
||||
}
|
||||
|
||||
/** INPUT FIELD COMMON STYLES **/
|
||||
|
||||
.DateInput_input {
|
||||
/* used data:image as background-image because svg import with relative url didn't work */
|
||||
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="%236b6b6b" fill-rule="evenodd"><path d="M20 20h-4v-4h4v4zm-6-10h-4v4h4v-4zm6 0h-4v4h4v-4zm-12 6h-4v4h4v-4zm6 0h-4v4h4v-4zm-6-6h-4v4h4v-4zm16-8v22h-24v-22h3v1c0 1.103.897 2 2 2s2-.897 2-2v-1h10v1c0 1.103.897 2 2 2s2-.897 2-2v-1h3zm-2 6h-20v14h20v-14zm-2-7c0-.552-.447-1-1-1s-1 .448-1 1v2c0 .552.447 1 1 1s1-.448 1-1v-2zm-14 2c0 .552-.447 1-1 1s-1-.448-1-1v-2c0-.552.447-1 1-1s1 .448 1 1v2z"/></g></svg>');
|
||||
|
||||
background-size: 14px;
|
||||
background-position: 10px center;
|
||||
@apply bg-no-repeat;
|
||||
}
|
||||
.DateInput_input {
|
||||
@apply cursor-pointer border-custom-blue mt-2 bg-black shadow transition duration-300 appearance-none border-t border-l border-r border-b border-solid rounded w-full py-2 px-3 text-sm text-gray-700 leading-tight pl-8;
|
||||
}
|
||||
.DateInput_input:hover {
|
||||
@apply border-gray-500;
|
||||
}
|
||||
.DateInput_input:focus {
|
||||
@apply border-gray-500 outline-none;
|
||||
}
|
||||
/** FIRST INPUT STYLES **/
|
||||
.DateInput:first-child .DateInput_input {
|
||||
@apply rounded-r-none;
|
||||
}
|
||||
|
||||
.DateInput:first-child .DateInput_input:hover,
|
||||
.DateInput:first-child .DateInput_input:focus {
|
||||
@apply relative z-10;
|
||||
}
|
||||
|
||||
/** SECOND INPUT STYLES **/
|
||||
.DateInput:last-child .DateInput_input {
|
||||
@apply rounded-l-none;
|
||||
margin-left: -1px;
|
||||
}
|
||||
/** ARROW STYLES **/
|
||||
.DateRangePickerInput_arrow {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
/* SELECT MONTH PICKER */
|
||||
.DateRangePicker_select {
|
||||
@apply bg-white text-custom-dark border rounded border-custom-gray appearance-none cursor-pointer text-base pl-2 pr-5 py-1; /* NEEDED FOR ARROW DOWN */
|
||||
background-image: linear-gradient(45deg, transparent 50%, gray 50%),
|
||||
linear-gradient(135deg, gray 50%, transparent 50%);
|
||||
background-position: calc(100% - 11px) 11px, calc(100% - 6px) calc(11px);
|
||||
background-size: 5px 5px, 5px 5px;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
/* CALENDAR DAYS */
|
||||
.CalendarDay {
|
||||
@apply border-0 rounded-full;
|
||||
}
|
||||
|
||||
.CalendarDay:hover,
|
||||
.CalendarDay__selected,
|
||||
.CalendarDay__selected:active,
|
||||
.CalendarDay__selected:hover {
|
||||
@apply bg-custom-blue text-white border-0 border-custom-blue;
|
||||
}
|
||||
|
||||
.CalendarDay__blocked_out_of_range:hover {
|
||||
@apply border-0 bg-white cursor-not-allowed text-custom-gray;
|
||||
}
|
||||
|
||||
.CalendarDay__selected_span {
|
||||
@apply border-0 bg-custom-aquaBright;
|
||||
}
|
||||
|
||||
/* MONTH NAVIGATION BUTTONS */
|
||||
.DayPickerNavigation_button__horizontalDefault,
|
||||
.DayPickerNavigation_button__horizontalDefault:hover {
|
||||
@apply border-custom-gray text-custom-gray;
|
||||
top: 24px;
|
||||
padding: 3px 9px;
|
||||
}
|
||||
.DayPickerNavigation_svg__horizontal {
|
||||
@apply fill-current;
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
/** REACT DATES */
|
||||
import 'react-dates/initialize';
|
||||
import 'react-dates/lib/css/_datepicker.css';
|
||||
import { DateRangePicker, isInclusivelyBeforeDay } from 'react-dates';
|
||||
import './DateRange.css';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import moment from 'moment';
|
||||
|
||||
const DateRange = props => {
|
||||
const { onChange, startDate, endDate } = props;
|
||||
const [focusedInput, setFocusedInput] = useState(null);
|
||||
|
||||
const today = moment();
|
||||
const lastWeek = moment().subtract(7, 'day');
|
||||
const lastMonth = moment().subtract(1, 'month');
|
||||
|
||||
const studyDatePresets = [
|
||||
{
|
||||
text: 'Today',
|
||||
start: today,
|
||||
end: today,
|
||||
},
|
||||
{
|
||||
text: 'Last 7 days',
|
||||
start: lastWeek,
|
||||
end: today,
|
||||
},
|
||||
{
|
||||
text: 'Last 30 days',
|
||||
start: lastMonth,
|
||||
end: today,
|
||||
},
|
||||
];
|
||||
|
||||
const renderDatePresets = () => {
|
||||
return (
|
||||
<div className="PresetDateRangePicker_panel flex justify-between">
|
||||
{studyDatePresets.map(({ text, start, end }) => {
|
||||
return (
|
||||
<button
|
||||
key={text}
|
||||
type="button"
|
||||
className={`m-0 py-2 px-3 bg-custom-blue border-0 rounded text-white text-base transition duration-300 hover:opacity-80`}
|
||||
onClick={() =>
|
||||
onChange({
|
||||
startDate: start,
|
||||
endDate: end,
|
||||
preset: true,
|
||||
})
|
||||
}
|
||||
>
|
||||
{text}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const renderMonthElement = ({ month, onMonthSelect, onYearSelect }) => {
|
||||
const renderYearsOptions = () => {
|
||||
const yearsRange = 20;
|
||||
const options = [];
|
||||
|
||||
for (let i = 0; i < yearsRange; i++) {
|
||||
const year = moment().year() - i;
|
||||
options.push(
|
||||
<option key={year} value={year}>
|
||||
{year}
|
||||
</option>
|
||||
);
|
||||
}
|
||||
|
||||
return options;
|
||||
};
|
||||
|
||||
renderMonthElement.propTypes = {
|
||||
month: PropTypes.object,
|
||||
onMonthSelect: PropTypes.func,
|
||||
onYearSelect: PropTypes.func,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex justify-center">
|
||||
<div className="my-0 mx-1">
|
||||
<select
|
||||
className="DateRangePicker_select"
|
||||
value={month.month()}
|
||||
onChange={e => onMonthSelect(month, e.target.value)}
|
||||
>
|
||||
{moment.months().map((label, value) => (
|
||||
<option key={value} value={value}>
|
||||
{label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="my-0 mx-1">
|
||||
{}
|
||||
<select
|
||||
className="DateRangePicker_select"
|
||||
value={month.year()}
|
||||
onChange={e => onYearSelect(month, e.target.value)}
|
||||
>
|
||||
{renderYearsOptions()}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<DateRangePicker
|
||||
/** REQUIRED */
|
||||
startDate={startDate}
|
||||
startDateId={'startDateId'}
|
||||
endDate={endDate}
|
||||
endDateId={'endDateId'}
|
||||
onDatesChange={onChange}
|
||||
focusedInput={focusedInput}
|
||||
onFocusChange={updatedVal => setFocusedInput(updatedVal)}
|
||||
/** OPTIONAL */
|
||||
renderCalendarInfo={renderDatePresets}
|
||||
renderMonthElement={renderMonthElement}
|
||||
startDatePlaceholderText={'Start Date'}
|
||||
endDatePlaceholderText={'End Date'}
|
||||
phrases={{
|
||||
closeDatePicker: 'Close',
|
||||
clearDates: 'Clear dates',
|
||||
}}
|
||||
isOutsideRange={day => !isInclusivelyBeforeDay(day, moment())}
|
||||
hideKeyboardShortcutsPanel={true}
|
||||
numberOfMonths={1}
|
||||
showClearDates={false}
|
||||
anchorDirection="left"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
DateRange.defaultProps = {
|
||||
startDate: null,
|
||||
endDate: null,
|
||||
};
|
||||
|
||||
DateRange.propTypes = {
|
||||
/** Start date moment object */
|
||||
startDate: PropTypes.object, // moment date is an object
|
||||
/** End date moment object */
|
||||
endDate: PropTypes.object, // moment date is an object
|
||||
/** Callback that returns on object with selected dates */
|
||||
onChange: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default DateRange;
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
name: Date Range
|
||||
menu: Components
|
||||
route: components/date-range
|
||||
---
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Playground, Props } from 'docz';
|
||||
import DateRange from '../DateRange';
|
||||
|
||||
# Date Range
|
||||
|
||||
Date Range is used to select a range of dates.
|
||||
|
||||
## Import
|
||||
|
||||
```javascript
|
||||
import { DateRange } from '@ohfi/ui';
|
||||
```
|
||||
|
||||
## Date Range
|
||||
|
||||
<Playground>
|
||||
{() => {
|
||||
const [dates, setDates] = useState({
|
||||
startDate: null,
|
||||
endDate: null,
|
||||
});
|
||||
return (
|
||||
<div className="p-4 flex flex-col items-center">
|
||||
<DateRange
|
||||
startDate={dates.startDate}
|
||||
endDate={dates.endDate}
|
||||
onChange={({ startDate, endDate }) =>
|
||||
setDates({ startDate, endDate })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Playground>
|
||||
|
||||
## Properties
|
||||
|
||||
<Props of={DateRange} />
|
||||
@@ -0,0 +1,3 @@
|
||||
import DateRange from './DateRange';
|
||||
|
||||
export default DateRange;
|
||||
@@ -3,6 +3,7 @@ import React from 'react';
|
||||
import arrowDown from './../../assets/icons/arrow-down.svg';
|
||||
import seriesActive from './../../assets/icons/series-active.svg';
|
||||
import seriesInactive from './../../assets/icons/series-inactive.svg';
|
||||
import calendar from './../../assets/icons/calendar.svg';
|
||||
import cancel from './../../assets/icons/cancel.svg';
|
||||
import chevronDown from './../../assets/icons/chevron-down.svg';
|
||||
import chevronRight from './../../assets/icons/chevron-right.svg';
|
||||
@@ -21,6 +22,7 @@ const ICONS = {
|
||||
'arrow-down': arrowDown,
|
||||
'series-active': seriesActive,
|
||||
'series-inactive': seriesInactive,
|
||||
calendar: calendar,
|
||||
cancel: cancel,
|
||||
'chevron-down': chevronDown,
|
||||
'chevron-right': chevronRight,
|
||||
|
||||
@@ -20,6 +20,7 @@ const Input = ({
|
||||
type = 'text',
|
||||
value,
|
||||
onChange,
|
||||
onFocus,
|
||||
...otherProps
|
||||
}) => {
|
||||
return (
|
||||
@@ -34,6 +35,7 @@ const Input = ({
|
||||
type={type}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onFocus={onFocus}
|
||||
{...otherProps}
|
||||
/>
|
||||
</div>
|
||||
@@ -49,6 +51,7 @@ Input.propTypes = {
|
||||
type: PropTypes.string,
|
||||
value: PropTypes.any,
|
||||
onChange: PropTypes.func,
|
||||
onFocus: PropTypes.func,
|
||||
};
|
||||
|
||||
export default Input;
|
||||
@@ -1,5 +1,6 @@
|
||||
import Button from './Button';
|
||||
import ButtonGroup from './ButtonGroup';
|
||||
import DateRange from './DateRange';
|
||||
import Icon from './Icon';
|
||||
import IconButton from './IconButton';
|
||||
import Input from './Input';
|
||||
@@ -10,6 +11,7 @@ import Typography from './Typography';
|
||||
export {
|
||||
Button,
|
||||
ButtonGroup,
|
||||
DateRange,
|
||||
Icon,
|
||||
IconButton,
|
||||
Input,
|
||||
|
||||
Reference in new issue
Block a user