Skip to content

Commit

Permalink
Merge pull request #28 from hernansartorio/time-picking
Browse files Browse the repository at this point in the history
Add basic time-picking support
  • Loading branch information
hernansartorio authored May 7, 2020
2 parents 67dceed + 020521e commit 54a78f6
Show file tree
Hide file tree
Showing 10 changed files with 184 additions and 22 deletions.
8 changes: 6 additions & 2 deletions src/DatePickerCalendar.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react'
import { instanceOf, func, object, objectOf, string } from 'prop-types'
import { isSameDay, startOfMonth } from 'date-fns'
import { mergeModifiers, isSelectable } from './utils'
import { isSelectable, mergeModifiers, setTime } from './utils'
import useControllableState from './useControllableState'
import Calendar from './Calendar'

Expand All @@ -21,12 +21,16 @@ export default function DatePickerCalendar({
const modifiers = mergeModifiers({ selected: isSelected, disabled: isSelected }, receivedModifiers)
const [month, setMonth] = useControllableState(receivedMonth, onMonthChange, startOfMonth(selectedDate || new Date()))

const handleDateChange = date => {
onDateChange(selectedDate ? setTime(date, selectedDate) : date)
}

return (
<Calendar
locale={locale}
month={month}
onMonthChange={setMonth}
onDayClick={onDateChange}
onDayClick={handleDateChange}
minimumDate={minimumDate}
maximumDate={maximumDate}
modifiers={modifiers}
Expand Down
12 changes: 6 additions & 6 deletions src/DateRangePickerCalendar.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useState } from 'react'
import { func, instanceOf, object, objectOf, oneOf, string } from 'prop-types'
import { isSameDay, isAfter, isBefore, startOfMonth } from 'date-fns'
import { mergeModifiers, isSelectable } from './utils'
import { isSameDay, isAfter, isBefore, startOfMonth, startOfDay } from 'date-fns'
import { isSelectable, mergeModifiers, setTime } from './utils'
import { START_DATE, END_DATE } from './constants'
import useControllableState from './useControllableState'
import Calendar from './Calendar'
Expand All @@ -28,12 +28,12 @@ export default function DateRangePickerCalendar({
const displayedStartDate =
focus === START_DATE && !startDate && endDate && hoveredDate && !isSameDay(hoveredDate, endDate)
? hoveredDate
: startDate
: startOfDay(startDate)

const displayedEndDate =
focus === END_DATE && !endDate && startDate && hoveredDate && !isSameDay(hoveredDate, startDate)
? hoveredDate
: endDate
: startOfDay(endDate)

const isStartDate = date => isSameDay(date, displayedStartDate) && isBefore(date, displayedEndDate)
const isMiddleDate = date => isAfter(date, displayedStartDate) && isBefore(date, displayedEndDate)
Expand Down Expand Up @@ -63,7 +63,7 @@ export default function DateRangePickerCalendar({
onEndDateChange(null)
}

onStartDateChange(date)
onStartDateChange(startDate ? setTime(date, startDate) : date)
onFocusChange(END_DATE)
} else if (focus === END_DATE) {
const invalidStartDate = startDate && !isBefore(startDate, date)
Expand All @@ -72,7 +72,7 @@ export default function DateRangePickerCalendar({
onStartDateChange(null)
}

onEndDateChange(date)
onEndDateChange(endDate ? setTime(date, endDate) : date)
onFocusChange(invalidStartDate ? START_DATE : null)
}
}
Expand Down
5 changes: 4 additions & 1 deletion src/utils.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isAfter, isBefore, startOfDay } from 'date-fns'
import { isAfter, isBefore, startOfDay, set } from 'date-fns'

export const isSelectable = (date, { minimumDate, maximumDate }) =>
!isBefore(date, startOfDay(minimumDate)) && !isAfter(date, maximumDate)
Expand All @@ -18,3 +18,6 @@ export const mergeModifiers = (baseModifiers, newModifiers) => {

return modifiers
}

export const setTime = (date, dateWithTime) =>
set(date, { hours: dateWithTime.getHours(), minutes: dateWithTime.getMinutes(), seconds: dateWithTime.getSeconds() })
4 changes: 2 additions & 2 deletions test/Calendar.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ describe('Calendar', () => {
const monthShortName = format(today, 'MMM', { locale })

expect(getByText(dayShortName)).toBeInTheDocument()
expect(getByText(monthName)).toBeInTheDocument()
expect(getByText(monthShortName)).toBeInTheDocument()
expect(getAllByText(monthName).length).toBeGreaterThan(0)
expect(getAllByText(monthShortName).length).toBeGreaterThan(0)
expect(getAllByText('1').length).toBeGreaterThan(0)
})

Expand Down
11 changes: 11 additions & 0 deletions test/DatePickerCalendar.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,15 @@ describe('DatePickerCalendar', () => {

expect(getByText(monthName, { exact: false })).toBeInTheDocument()
})

it('should maintain the selected date’s time when selecting a new date', () => {
const handleDateChange = jest.fn()
const date = new Date(2020, 1, 24, 18, 30)

const { getByText } = render(<DatePickerCalendar locale={locale} date={date} onDateChange={handleDateChange} />)

fireEvent.click(getByText('25'))

expect(handleDateChange).toHaveBeenCalledWith(new Date(2020, 1, 25, 18, 30))
})
})
47 changes: 36 additions & 11 deletions test/DateRangePickerCalendar.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,22 +64,13 @@ describe('DateRangePickerCalendar', () => {
const endDate = addDays(startDate, 2)

const { container, getAllByText, rerender } = render(
<DateRangePickerCalendar
locale={locale}
startDate={startDate}
/>
<DateRangePickerCalendar locale={locale} startDate={startDate} />
)

expect(getAllByText('1')[0].parentElement).toHaveClass('-selected')
expect(container.querySelectorAll('.-selected').length).toBe(1)

rerender(
<DateRangePickerCalendar
locale={locale}
startDate={startDate}
endDate={endDate}
/>
)
rerender(<DateRangePickerCalendar locale={locale} startDate={startDate} endDate={endDate} />)

expect(getAllByText('1')[0].parentElement).toHaveClass('-selected -selected-start')
expect(getAllByText('2')[0].parentElement).toHaveClass('-selected -selected-middle')
Expand All @@ -105,4 +96,38 @@ describe('DateRangePickerCalendar', () => {

expect(getByText(monthName, { exact: false })).toBeInTheDocument()
})

it('should maintain the selected start date’s time when selecting a new date', () => {
const handleStartDateChange = jest.fn()

const { getByText } = render(
<DateRangePickerCalendar
locale={locale}
focus={START_DATE}
startDate={new Date(2020, 1, 24, 18, 30)}
onStartDateChange={handleStartDateChange}
/>
)

fireEvent.click(getByText('25'))

expect(handleStartDateChange).toHaveBeenCalledWith(new Date(2020, 1, 25, 18, 30))
})

it('should maintain the selected end date’s time when selecting a new date', () => {
const handleEndDateChange = jest.fn()

const { getByText } = render(
<DateRangePickerCalendar
locale={locale}
focus={END_DATE}
endDate={new Date(2020, 1, 24, 18, 30)}
onEndDateChange={handleEndDateChange}
/>
)

fireEvent.click(getByText('25'))

expect(handleEndDateChange).toHaveBeenCalledWith(new Date(2020, 1, 25, 18, 30))
})
})
10 changes: 10 additions & 0 deletions test/utils.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { setTime } from '../src/utils'

describe('setTime', () => {
test('should return first date with the time of the second date', () => {
const dateWithNoTime = new Date(2021, 2, 13)
const dateWithTime = new Date(2020, 1, 24, 18, 30)

expect(setTime(dateWithNoTime, dateWithTime)).toEqual(new Date(2021, 2, 13, 18, 30))
})
})
33 changes: 33 additions & 0 deletions website/examples/DatePickerWithTimeExample.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React, { useState } from 'react'
import { enGB } from 'date-fns/locale'
import { DatePicker } from '../../src'
import Example from './Example'

const code = `
import React, { useState } from 'react'
import { enGB } from 'date-fns/locale'
import { DatePicker } from 'react-nice-dates'
import 'react-nice-dates/build/style.css'
function DatePickerWithTimeExample() {
const [date, setDate] = useState(new Date(2020, 1, 24, 18, 15))
return (
<DatePicker date={date} onDateChange={setDate} locale={enGB} format='dd/MM/yyyy HH:mm'>
{({ inputProps, focused }) => <input className={'input' + (focused ? ' -focused' : '')} {...inputProps} />}
</DatePicker>
)
}
`

export default function DatePickerWithTimeExample() {
const [date, setDate] = useState(new Date(2020, 1, 24, 18, 15))

return (
<Example code={code}>
<DatePicker date={date} onDateChange={setDate} locale={enGB} format='dd/MM/yyyy HH:mm'>
{({ inputProps, focused }) => <input className={'input' + (focused ? ' -focused' : '')} {...inputProps} />}
</DatePicker>
</Example>
)
}
59 changes: 59 additions & 0 deletions website/examples/DatePickerWithTimeInputExample.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import React, { useState } from 'react'
import { enGB } from 'date-fns/locale'
import { DatePicker, useDateInput } from '../../src'
import Example from './Example'

const code = `
import React, { useState } from 'react'
import { enGB } from 'date-fns/locale'
import { DatePicker } from 'react-nice-dates'
import 'react-nice-dates/build/style.css'
function DatePickerWithTimeInputExample() {
const [date, setDate] = useState(new Date(2020, 1, 24, 18, 15))
const timeInputProps = useDateInput({
date,
format: 'HH:mm',
locale: enGB,
onDateChange: setDate
})
return (
<div style={{ display: 'flex' }}>
<DatePicker date={date} onDateChange={setDate} locale={enGB} format='dd/MM/yyyy'>
{({ inputProps, focused }) => (
<input className={'input' + (focused ? ' -focused' : '')} style={{ width: 150 }} {...inputProps} />
)}
</DatePicker>
<input className='input' style={{ marginLeft: 16, width: 80 }} {...timeInputProps} />
</div>
)
}
`

export default function DatePickerWithTimeInputExample() {
const [date, setDate] = useState(new Date(2020, 1, 24, 18, 15))

const timeInputProps = useDateInput({
date,
format: 'HH:mm',
locale: enGB,
onDateChange: setDate
})

return (
<Example code={code}>
<div style={{ display: 'flex' }}>
<DatePicker date={date} onDateChange={setDate} locale={enGB} format='dd/MM/yyyy'>
{({ inputProps, focused }) => (
<input className={'input' + (focused ? ' -focused' : '')} style={{ width: 150 }} {...inputProps} />
)}
</DatePicker>

<input className='input' style={{ marginLeft: 16, width: 80 }} {...timeInputProps} />
</div>
</Example>
)
}
17 changes: 17 additions & 0 deletions website/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import ModifiersExample from './examples/ModifiersExample'
import LocalesExample from './examples/LocalesExample'
import CalendarExample from './examples/CalendarExample'
import CodeBlock from './CodeBlock'
import DatePickerWithTimeInputExample from './examples/DatePickerWithTimeInputExample'
import DatePickerWithTimeExample from './examples/DatePickerWithTimeExample'

function App() {
return (
Expand Down Expand Up @@ -123,6 +125,21 @@ function App() {

<DatePickerCalendarWithInputExample />

<h3>Time-picking</h3>

<p>
While there’s no custom time-picking user interface, you can use an input format that includes the time, and the
selected time will get carried over when the selected date changes.
</p>

<DatePickerWithTimeExample />

<p>
You can also use separate input for the time of the date, using the <code>useDateInput</code> hook.
</p>

<DatePickerWithTimeInputExample />

<h3>Localization</h3>

<p>
Expand Down

0 comments on commit 54a78f6

Please sign in to comment.