Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UX improvements date range picker #847

Merged
merged 9 commits into from
May 21, 2019
18 changes: 17 additions & 1 deletion apps/finance/app/src/components/DateRange/DatePicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,25 @@ class DatePicker extends React.PureComponent {
}

render() {
const { name } = this.props
const today = startOfDay(new Date())
const { value: selected = today } = this.state

return (
<Container overlay={this.props.overlay}>
{name && (
<Text
size="normal"
weight="bold"
css={`
text-align: center;
margin-bottom: 2px;
`}
>
{name}
</Text>
)}

{!this.props.hideYearSelector && (
<Selector>
<ArrowButton onClick={this.previousYear}>◀</ArrowButton>
Expand Down Expand Up @@ -128,6 +142,7 @@ class DatePicker extends React.PureComponent {

DatePicker.propTypes = {
currentDate: PropTypes.instanceOf(Date),
name: PropTypes.string,

// Events
onSelect: PropTypes.func,
Expand Down Expand Up @@ -227,11 +242,12 @@ const DayView = styled.li`
props.disabled &&
css`
pointer-events: none;
color: ${theme.disabled};
color: #fff;
`}

${props =>
props.selected &&
!props.disabled &&
css`
&&& {
background: ${mainColor};
Expand Down
209 changes: 158 additions & 51 deletions apps/finance/app/src/components/DateRange/DateRangeInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ import {
startOfDay,
endOfDay,
} from 'date-fns'
import { breakpoint, font, theme } from '@aragon/ui'
import { Button, breakpoint, font, theme, useViewport } from '@aragon/ui'
import IconCalendar from './Calendar'
import TextInput from './TextInput'
import DatePicker from './DatePicker'

const DATE_PLACEHOLDER = '--/--/----'
const START_DATE = 'Start date'
const END_DATE = 'End date'

class DateRangeInput extends React.PureComponent {
state = {
Expand Down Expand Up @@ -47,6 +48,14 @@ class DateRangeInput extends React.PureComponent {

componentDidUpdate(prevProps, prevState) {
if (this.state.showPicker !== prevState.showPicker) {
const { startDate, endDate, compactMode } = this.props
// unsetting selection for compact because it shows one calendar at a time
this.setState({
startDateSelected: !compactMode && !!startDate,
endDateSelected: !compactMode && !!endDate,
startDate: !compactMode ? startDate : null,
endDate: !compactMode ? endDate : null,
})
if (this.state.showPicker) {
document.addEventListener('mousedown', this.handleClickOutside)
} else {
Expand All @@ -62,15 +71,7 @@ class DateRangeInput extends React.PureComponent {

handleClickOutside = event => {
if (this.rootRef && !this.rootRef.contains(event.target)) {
this.setState({ showPicker: false }, () => {
const { startDate, endDate } = this.state
if (startDate && endDate) {
this.props.onChange({
start: startOfDay(startDate),
end: endOfDay(endDate),
})
}
})
this.setState({ showPicker: false })
}
}

Expand All @@ -92,58 +93,142 @@ class DateRangeInput extends React.PureComponent {
}
}

render() {
handleApply = e => {
e.preventDefault()
e.stopPropagation()
bpierre marked this conversation as resolved.
Show resolved Hide resolved
this.setState({ showPicker: false })
const { startDate, endDate } = this.state
if (startDate && endDate) {
this.props.onChange({
start: startOfDay(startDate),
end: endOfDay(endDate),
})
}
}

handleClear = e => {
e.preventDefault()
e.stopPropagation()
this.setState({ showPicker: false, startDate: null, endDate: null })
this.props.onChange({
start: null,
end: null,
})
}

getValueText = () => {
const {
compactMode,
format,
startDate: startDateProps,
endDate: endDateProps,
} = this.props
const { showPicker, startDateSelected, endDateSelected } = this.state

// closed
// shows props, if props null then placeholder
if (!showPicker) {
return startDateProps && endDateProps
? `${formatDate(startDateProps, format)} | ${formatDate(
endDateProps,
format
)}`
: ''
}

// opened
// shows constants, till dates selected
if (compactMode) {
return `${startDateSelected ? this.formattedStartDate : START_DATE} | ${
endDateSelected ? this.formattedEndDate : END_DATE
}`
}

// shows props, changes with selection
return `${
this.formattedStartDate ? this.formattedStartDate : START_DATE
} | ${this.formattedEndDate ? this.formattedEndDate : END_DATE}`
}

render() {
const {
startDate,
endDate,
startDateSelected,
endDateSelected,
showPicker,
} = this.state
const { compactMode } = this.props

const icon = this.state.showPicker ? (
<IconCalendarSelected />
) : (
<IconCalendar />
)
const value =
this.formattedStartDate && this.formattedEndDate
? `${this.formattedStartDate} - ${this.formattedEndDate}`
: ''
const placeholder = `${
this.formattedStartDate ? this.formattedStartDate : DATE_PLACEHOLDER
} - ${
this.formattedEndDate
? this.formattedEndDate
: this.formattedStartDate
? DATE_PLACEHOLDER
: formatDate(new Date(), this.props.format)
}`

return (
<StyledContainer
ref={el => (this.rootRef = el)}
onClick={this.handleClick}
>
<StyledTextInput
value={value}
value={this.getValueText()}
readOnly
adornment={icon}
adornmentPosition="end"
height={39}
placeholder={placeholder}
placeholder={`${START_DATE} | ${END_DATE}`}
/>
{this.state.showPicker && (
<StyledDatePickersContainer>
<DatePicker
key={`start-picker-${startDate}`}
name="start-date-picker"
currentDate={startDate}
onSelect={this.handleSelectStartDate}
overlay={false}
/>
<DatePicker
key={`end-picker-${endDate}`}
name="end-date-picker"
currentDate={endDate}
onSelect={this.handleSelectEndDate}
overlay={false}
/>
</StyledDatePickersContainer>
<Container>
<Wrap>
{(!compactMode || !startDateSelected) && (
<DatePicker
key={`start-picker-${startDate}`}
name="Start date"
currentDate={startDate}
onSelect={this.handleSelectStartDate}
overlay={false}
/>
)}
{(!compactMode || startDateSelected) && (
<DatePicker
key={`end-picker-${endDate}`}
name="End date"
currentDate={endDate}
onSelect={this.handleSelectEndDate}
overlay={false}
/>
)}
</Wrap>

<Controls>
<Button
css="width: 124px"
mode="outline"
onClick={this.handleClear}
>
Clear
</Button>
<Button
css={`
width: 124px;

${breakpoint(
'medium',
`
margin-left: 19px;
`
)}
`}
mode="strong"
onClick={this.handleApply}
disabled={!startDateSelected || !endDateSelected}
>
Apply
</Button>
</Controls>
</Container>
)}
</StyledContainer>
)
Expand All @@ -162,6 +247,22 @@ DateRangeInput.defaultProps = {
onChange: () => {},
}

const Controls = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
margin: 16px 0;
padding: 0 8px;

${breakpoint(
'medium',
`
display: block;
text-align: right;
`
)}
`

const StyledContainer = styled.div`
position: relative;
`
Expand All @@ -171,13 +272,7 @@ const StyledTextInput = styled(TextInput)`
${font({ monospace: true })};
`

const StyledDatePickersContainer = styled.div`
position: absolute;
z-index: 10;
border: 1px solid ${theme.contentBorder};
border-radius: 3px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15);

const Wrap = styled.div`
> div {
border: 0;
box-shadow: none;
Expand All @@ -193,8 +288,20 @@ const StyledDatePickersContainer = styled.div`
)}
`

const Container = styled.div`
position: absolute;
z-index: 10;
border: 1px solid ${theme.contentBorder};
border-radius: 3px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15);
background: #fff;
`

const IconCalendarSelected = styled(IconCalendar)`
color: ${theme.accent};
`

export default DateRangeInput
export default props => {
const { below } = useViewport()
return <DateRangeInput {...props} compactMode={below('medium')} />
}