Date picker

DatePicker allows us to build date picker components of varying patterns using the `useDayzed` hook from dayzed. These components allow us to make single date, multi-date or date range selectors.

Components

  • Install
    npm install @pluralsight/ps-design-system-datepicker
  • Import
    import { DatePicker, /* ... */ } from '@pluralsight/ps-design-system-datepicker'

DatePicker

This is a very similar descendant of the prevous DatePicker. It is a convenience wrapper around the common case. The API is familiar but has some differences. It is a controlled component with value. It takes an onSelect prop that returns a dayzed.DateObj each time a valid date is selected.

import Button from '@pluralsight/ps-design-system-button'
import { DatePicker } from '@pluralsight/ps-design-system-datepicker'
import React from 'react'
const Example = () => {
const [date, setDate] = React.useState(new Date(1999, 11, 31))
return (
<div>
<div>
<Button onClick={() => setDate(new Date(2000, 0, 1))}>Go Y2K!</Button>
</div>
<div>
<DatePicker
label="Party time"
onSelect={(evt, dateObj) => (setDate(dateObj.date))}
value={date}
/>
</div>
</div>
)
}
export default Example
Name
Type
Description
onSelect(evt: SyntheticEvent, selectedDate: DateObj) => voidcallback that return DateObj
valueDatedate to set picker to

Calendar

Calendar is a top-level component for creating a calendar. When used in concert with the useDayzed hook, it takes all returned props except getDateProps

import { useDayzed } from 'dayzed'
import { Calendar } from '@pluralsight/ps-design-system-datepicker'
return (
<Calendar {...useDayzed({
/** basic config **/
})}>
/** renderProp of CalendarDay or custom CalendarDay **/
</Calendar>
)

CalendarDay

CalendarDay is to be used as a renderProp to pass through date props to each Calendar grid button

import { useDayzed } from 'dayzed'
import { CalendarDayProps, CalendarDay, Calendar} from '@pluralsight/ps-design-system-datepicker'
const { getDateProps, ...dayzedData } = useDayzed({
/** See dayzed documentation section of props**/
})
return (
// <Calendar {...useDayzed({
/** basic config **/
})}>
{(props: CalendarDayProps) => <CalendarDay {...props} />}
// </Calendar>
)

Functions

useIsInRange

The useIsInRange hook is used for every date picker.

Parameters: an object

Name
Type
Description
selected
Required
Date[]selected date on the picker

The hook will return an object with these properties: Returns: an object

Name
Type
Description
onMouseEnter
Required
React.MouseEvent => voidevent handler to attach to the <button />
onMouseLeave
Required
React.MouseEvent => voidevent handler to attach to Calendar
isInRange
Required
stringclassName(s) to merge into `button` className
import { useDayzed } from 'dayzed'
import {
Calendar,
CalendarDay,
CalendarDayProps,
useIsInRange
} from '@pluralsight/ps-design-system-datepicker'
const [selected, setSelected] = React.useState<Date[] | undefined>()
const { getDateProps, ...dayzedData } = useDayzed({
/** config props**/
})
const { onMouseLeave, onMouseEnter, isInRange } = useIsInRange(selected)
return (
<Calendar {...dayzedData} onMouseLeave={onMouseLeave}>
{(props: CalendarDayProps) => {
const { dateObj } = props
return (
<CalendarDay
{...props}
className={isInRange(dateObj.date)}
onMouseEnter={() => onMouseEnter(dateObj.date)}
/>
)
}}
</Calendar>
)

onRangeDateSelected

The onRangeDateSelected hook is a helper which returns a function to pass to the useDayzed hook in the onDateSelected parameter.

Parameters: an object

Name
Type
Description
onSelect(event: React.SyntheticEvent, selectedDate: dazyed.DateObj): voidcallback for side effects when a date is selected
selected
Required
Date[]the selected date
setSelected
Required
React.Dispatch<React.SetStateAction<Date[]>>the set state action for the selected date

Pass the return value directly to onDateSelect as seen in the example below.

Please refer to the dayzed typings for information on the shape of the dayzed.DateObj interface.

import { useDayzed, DateObj } from 'dayzed'
import {
Calendar,
CalendarDay,
CalendarDayProps,
useIsInRange,
onRangeDateSelected
} from '@pluralsight/ps-design-system-datepicker'
const [selected, setSelected] = React.useState<Date[] | undefined>()
const { onMouseLeave, onMouseEnter, isInRange } = useIsInRange(selected)
return (
<Calendar
{... useDayzed({
selected,
onDateSelected: onRangeDateSelected({
selected,
setSelected,
// onSelect (optional)
}),
// ...rest of config
})}
onMouseLeave={onMouseLeave}
>
{(props: CalendarDayProps) => {
const { dateObj } = props
return (
<CalendarDay
{...props}
className={isInRange(dateObj.date)}
onMouseEnter={() => onMouseEnter(dateObj.date)}
/>
)
}}
</Calendar>
)

onMultiDateSelected

The onMultiDateSelected hook is similar to the onRangeDateSelected hook, except that it is used for date pickers where discrete dates are being selected. It is a helper which returns a function to pass to the useDayzed hook in the onDateSelected parameter.

Parameters: an object

Name
Type
Description
onSelect(event: React.SyntheticEvent, selectedDate: dazyed.DateObj): voidcallback for side effects when a date is selected
selected
Required
Date[]the selected date
setSelected
Required
React.Dispatch<React.SetStateAction<Date[]>>the set state action for the selected date

Pass the return value directly to onDateSelected as seen in the example below.

import { useDayzed, DateObj } from 'dayzed'
import {
Calendar,
CalendarDay,
CalendarDayProps,
onMultiDateSelected
} from '@pluralsight/ps-design-system-datepicker'
const [selected, setSelected] = React.useState<Date[]>([])
return (
<Calendar {...useDayzed({
selected,
onDateSelected: onMultiDateSelected({
selected,
setSelected,
// onSelect (optional)
})
// ...rest of config
})}>
{(props: CalendarDayProps) => <CalendarDay {...props} />}
</Calendar>
)

handleDateSelectChange

handleDateSelectChange is a convenience hook for creating the React state management that supports bi-directional updates between a date picker internal state and any other UI that displays the selected date, such as a TextInput.

Parameters: an object

Name
Type
Description
dateFormatstringa <a href='https://date-fns.org/v2.16.1/docs/format'>date-fns compatible format string</a>
selected
Required
Datethe selected date
setSelected
Required
React.Dispatch<React.SetStateAction<Date>>the set state action for the selected date
value
Required
stringformatted date value as string

Returns: an array

Name
Type
Description
[0]
Required
stringthe selected date as a string
[1]
Required
React.ChangeEventHandler<HTMLInputElement>the handle change callback to be placed on a text input
import { useDayzed, DateObj } from 'dayzed'
import { ValueOf } from '@pluralsight/ps-design-system-util'
import TextInput from '@pluralsight/ps-design-system-textinput'
import {Calendar, CalendarDay, CalendarDayProps,} from '@pluralsight/ps-design-system-datepicker'
import React from 'react'
const [selected, setSelected] = React.useState<Date | undefined>()
const [value, setValue] = React.useState<string>('')
const dateFormat = 'MM/dd/yyyy'
const handleChange: React.ChangeEventHandler<HTMLInputElement> = evt => {
const nextValue = evt.target.value
setValue(nextValue)
handleDateSelectChange({
selected,
setSelected,
value: nextValue,
dateFormat
})
}
return (
<>
<TextInput
ref={inputRef as React.RefObject<HTMLInputElement>}
onChange={handleChange}
value={value}
/>
<Calendar
{...useDayzed({
// ... rest of config
})}
>
{(props: CalendarDayProps) => <CalendarDay {...props} />}
</Calendar>
</>
)

handleRangeSelectChange

handleRangeSelectChange is like the handleDateSelectChange hook except it's designed to work with two inputs representing the start and end of a date range.

Parameters: an object

/>
Name
Type
Description
dateFormatstringa <a href='https://date-fns.org/v2.16.1/docs/format'>date-fns compatible format string</a>
selected
Required
Datethe selected date
setSelected
Required
React.Dispatch<React.SetStateAction<Date>>the set state action for the selected date
start
Required
booleandistinguishes the first date in the range from the end date
value
Required
Date[]Array of formatted date strings

Returns: an array

Name
Type
Description
[0]
Required
stringthe selected date as a string
[1]
Required
React.ChangeEventHandler<HTMLInputElement>the handle change callback to be placed on a text input

Two calls to the hook as usual, with one being used for an input for the start date and another for the input of the end date.

import { useDayzed, DateObj } from 'dayzed'
import { ValueOf } from '@pluralsight/ps-design-system-util'
import TextInput from '@pluralsight/ps-design-system-textinput'
import {
Calendar,
CalendarDay,
CalendarDayProps,
useIsInRange,
onRangeDateSelected,
handleRangeSelectChange
} from '@pluralsight/ps-design-system-datepicker'
const [selected, setSelected] = React.useState<Date[] | undefined>()
const [startValue, setStartValue] = React.useState<string>('')
const [endValue, setEndValue] = React.useState<string>('')
const { onMouseLeave, onMouseEnter, isInRange } = useIsInRange(selected)
const dateFormat = 'MM/dd/yyyy'
const onStartChange: React.ChangeEventHandler<HTMLInputElement> = evt => {
const nextValue = evt.target.value
setStartValue(nextValue)
handleRangeSelectChange({
dateFormat,
selected,
setSelected,
start: true,
value: nextValue
})
}
const onEndChange: React.ChangeEventHandler<HTMLInputElement> = evt => {
const nextValue = evt.target.value
setEndValue(nextValue)
handleRangeSelectChange({
dateFormat,
selected,
setSelected,
start: false,
value: nextValue
})
}
return (
<>
<TextInput onChange={onStartChange} value={startValue} />
<TextInput onChange={onEndChange} value={endValue} />
<br />
<Calendar
{... useDayzed({
// ...rest of config
})}
onMouseLeave={onMouseLeave}
>
{(props: CalendarDayProps) => {
const { dateObj } = props
return (
<CalendarDay
{...props}
className={isInRange(dateObj.date)}
onMouseEnter={() => onMouseEnter(dateObj.date)}
/>
)
}}
</Calendar>
</>
)

Examples

Single date

For this first example, here are steps that to be considered when determining how to configure a date picker. Most other examples don't have these steps but should be similar. Learn this example well first in order to understand the rest more easily.

  1. Create a new state object & setter selected: Date and setSelected: React.Dispatch<React.SetStateAction<Date | undefined>> then optionally set the intial date.
  2. Create an onDateSelected callback that will update the selected state when a calendar date is clicked along with any side effects desired.
  3. Pass selected, setSelected, and onDateSelected to the useDayzed hook from the dayzed library along with any other dayzed props desired.
import { useDayzed, DateObj } from 'dayzed'
import { format } from 'date-fns'
import { colorsBackgroundLight, type, colorsTextIcon, colorsBackgroundDark, layout } from '@pluralsight/ps-design-system-core'
import {Calendar, CalendarDay, CalendarDayProps} from '@pluralsight/ps-design-system-datepicker'
import React from 'react'
function Example() {
const [selected, setSelected] = React.useState<Date | undefined>(
new Date('05/13/2020')
)
const [value, setValue] = React.useState<string | undefined>('05/13/2020')
const onDateSelected = (dateObj: DateObj, evt: React.SyntheticEvent) => {
setSelected(dateObj.date)
setValue(format(dateObj.date, 'MM/dd/yyyy'))
}
return (
<Calendar {...useDayzed({
date: selected || new Date('05/30/2020'),
selected,
onDateSelected
})}>
{(props: CalendarDayProps) => <CalendarDay {...props} />}
</Calendar>
)
}
export default Example

Single date controlled by external state

Calendar updates can also be triggered by setting the shared state object selected via an onClick handler passed to a button.

import { useDayzed, DateObj } from 'dayzed'
import { format } from 'date-fns'
import { colorsBackgroundLight, type, colorsTextIcon, colorsBackgroundDark, layout } from '@pluralsight/ps-design-system-core'
import Button from '@pluralsight/ps-design-system-button'
import {Calendar, CalendarDay, CalendarDayProps} from '@pluralsight/ps-design-system-datepicker'
import React from 'react'
const Example = () => {
const [selected, setSelected] = React.useState<Date | undefined>()
const onDateSelected = (dateObj: DateObj, evt: React.SyntheticEvent) => {
setSelected(dateObj.date)
}
const handleClick = () => {
setSelected(new Date('05/13/2020'))
}
return (
<>
<Button onClick={handleClick}>5/13/2020</Button>
<br />
<Calendar {...useDayzed({
date: selected || new Date('05/30/2020'),
selected,
onDateSelected
})}>
{(props: CalendarDayProps) => <CalendarDay {...props} />}
</Calendar>
</>
)
}
export default Example

Range

Range selection setup is a little different from date selection cases, so here's a short tutorial to help learn the concepts.

  1. Create a new state object & setter selected: Date[] and setSelected: React.Dispatch<React.SetStateAction<Date[] | undefined>> then optionally set the intial date.
  2. Import an onRangeDateSelected callback that will update the selected state when a calendar date is clicked along with any side effects desired using the onSelect argument.
  3. Pass selected, setSelected, and onRangeDateSelected to the useDayzed hook from the dayzed library along with any other dayzed props desired. pattern to apply styles and handlers to buttons that represent individual dates in a month.
  4. Import and setup useIsInRange hook and pass the return to Calendar, CalendarDay as shown below
import { useDayzed, DateObj } from 'dayzed'
import { format } from 'date-fns'
import { colorsBackgroundLight, type, colorsTextIcon, colorsBackgroundDark, layout } from '@pluralsight/ps-design-system-core'
import {Calendar, CalendarDay, CalendarDayProps, useIsInRange, onRangeDateSelected} from '@pluralsight/ps-design-system-datepicker'
import React from 'react'
const Example = () => {
const [selected, setSelected] = React.useState<Date[] | undefined>()
const { onMouseLeave, onMouseEnter, isInRange } = useIsInRange(selected)
return (
<Calendar
{...useDayzed({
selected,
onDateSelected: onRangeDateSelected({
selected,
setSelected,
// onSelect: (event: React.SyntheticEvent<Element, Event>, selectedDate: DateObj): void
}),
date: new Date('05/30/2020')
})}
onMouseLeave={onMouseLeave}
>
{(props: CalendarDayProps) => {
const { dateObj } = props
return (
<CalendarDay
{...props}
className={isInRange(dateObj.date)}
onMouseEnter={() => onMouseEnter(dateObj.date)}
/>
)
}}
</Calendar>
)
}
export default Example

Range controlled by external state

import { useDayzed, DateObj } from 'dayzed'
import { format } from 'date-fns'
import { colorsBackgroundLight, type, colorsTextIcon, colorsBackgroundDark, layout } from '@pluralsight/ps-design-system-core'
import Button from '@pluralsight/ps-design-system-button'
import {Calendar, CalendarDay, CalendarDayProps, useIsInRange, onRangeDateSelected} from '@pluralsight/ps-design-system-datepicker'
import React from 'react'
const Example = () => {
const [selected, setSelected] = React.useState<Date[]>([])
const { onMouseLeave, onMouseEnter, isInRange } = useIsInRange(selected)
const handleClick = () => {
setSelected([new Date('05/13/2020'), new Date('05/30/2020')])
}
return (
<>
<Button onClick={handleClick}>5/13/2020 - 05/30/2020</Button>
<br/>
<Calendar
{...useDayzed({
monthsToDisplay: 2,
selected,
onDateSelected: onRangeDateSelected({
selected,
setSelected,
// onSelect: (event: React.SyntheticEvent<Element, Event>, selectedDate: DateObj): void
}),
date: new Date('05/30/2020')
})}
onMouseLeave={onMouseLeave}
>
{(props: CalendarDayProps) => {
const { dateObj } = props
return (
<CalendarDay
{...props}
className={isInRange(dateObj.date)}
onMouseEnter={() => onMouseEnter(dateObj.date)}
/>
)
}}
</Calendar>
</>
)
}
export default Example

Range with bi-directional updates

External TextInput with range selection can be used to update the Calendar and vice versa.

import { useDayzed, DateObj } from 'dayzed'
import { format } from 'date-fns'
import { colorsBackgroundLight, type, colorsTextIcon, colorsBackgroundDark, layout } from '@pluralsight/ps-design-system-core'
import TextInput from '@pluralsight/ps-design-system-textinput'
import {Calendar, CalendarDay, CalendarDayProps, useIsInRange, onRangeDateSelected, handleRangeSelectChange} from '@pluralsight/ps-design-system-datepicker'
import React from 'react'
const Example = () => {
const [selected, setSelected] = React.useState<Date[] | undefined>()
const [startValue, setStartValue] = React.useState<string>('')
const [endValue, setEndValue] = React.useState<string>('')
const { onMouseLeave, onMouseEnter, isInRange } = useIsInRange(selected)
const dateFormat = 'MM/dd/yyyy'
const onStartChange: React.ChangeEventHandler<HTMLInputElement> = evt => {
const nextValue = evt.target.value
setStartValue(nextValue)
handleRangeSelectChange({
dateFormat,
selected,
setSelected,
start: true,
value: nextValue
})
}
const onEndChange: React.ChangeEventHandler<HTMLInputElement> = evt => {
const nextValue = evt.target.value
setEndValue(nextValue)
handleRangeSelectChange({
dateFormat,
selected,
setSelected,
start: false,
value: nextValue
})
}
return (
<>
<div>
<TextInput onChange={onStartChange} value={startValue} placeholder='mm/dd/yyyy' />
<TextInput onChange={onEndChange} value={endValue} placeholder='mm/dd/yyyy' />
</div>
<br />
<Calendar
{...useDayzed({
monthsToDisplay: 2,
selected,
onDateSelected: onRangeDateSelected({
selected,
setSelected,
// onSelect: (event: React.SyntheticEvent<Element, Event>, selectedDate: DateObj): void
}),
date: new Date('05/30/2020')
})}
onMouseLeave={onMouseLeave}
>
{(props: CalendarDayProps) => {
const { dateObj } = props
return (
<CalendarDay
{...props}
className={isInRange(dateObj.date)}
onMouseEnter={() => onMouseEnter(dateObj.date)}
/>
)
}}
</Calendar>
</>
)
}
export default Example

Multi-date

import { useDayzed, DateObj } from 'dayzed'
import { format } from 'date-fns'
import { colorsBackgroundLight, type, colorsTextIcon, colorsBackgroundDark, layout } from '@pluralsight/ps-design-system-core'
import {Calendar, CalendarDay, CalendarDayProps, onMultiDateSelected} from '@pluralsight/ps-design-system-datepicker'
import React from 'react'
const Example = () => {
const [selected, setSelected] = React.useState<Date[]>([])
return (
<Calendar {...useDayzed({
selected,
onDateSelected: onMultiDateSelected({
selected,
setSelected,
// onSelect: (event: React.SyntheticEvent<Element, Event>, selectedDate: DateObj): void
})
})}>
{(props: CalendarDayProps) => <CalendarDay {...props} />}
</Calendar>
)
}
export default Example

Migration guide from v11.x and before to v12.x

Custom

This guide is to migrate the old DatePicker to a single datepicker using the new components and functions using a custom setup. This is useful when one needs to grab ref or handle unique logic.

  1. Create a new state object & setter selected: Date and setSelected: React.Dispatch<React.SetStateAction<Date | undefined>> then optionally set the initial date.
  2. Create a new state object & setter open: boolean and setOpen: React.Dispatch<React.SetStateAction<boolean>> then set it to false.
  3. Create an onDateSelected callback that will update the selected and open states when a calendar date is clicked along with any side effects desired.
  4. Pass selected, setSelected, and onDateSelected to the useDayzed hook from the dayzed library along with any other dayzed props desired.
  5. Create a handleIconClick callback to be passed to CalendarIcon and will toggle the open state when clicked.
  6. Create a new state object & setter open: boolean and setOpen: React.Dispatch<React.SetStateAction<boolean>>.
  7. Use the handleChange callback and pass it dateFormat, selected, setSelected, and value to manipulate the calendar using an input as demonstrated below.
import { useDayzed, DateObj } from 'dayzed'
import { format } from 'date-fns'
import { colorsBackgroundLight, type, colorsTextIcon, colorsBackgroundDark, layout } from '@pluralsight/ps-design-system-core'
import { CalendarIcon } from '@pluralsight/ps-design-system-icon'
import TextInput from '@pluralsight/ps-design-system-textinput'
import {Calendar, CalendarDay, CalendarDayProps} from '@pluralsight/ps-design-system-datepicker'
import React from 'react'
const Example = () => {
const [selected, setSelected] = React.useState<Date | undefined>()
const [open, setOpen] = React.useState<boolean>(false)
const onDateSelected = (dateObj: DateObj, evt: React.SyntheticEvent) => {
setSelected(dateObj.date)
setOpen(false)
}
const { getDateProps, ...dayzedData } = useDayzed({
date: selected || new Date('05/30/2020'),
selected,
onDateSelected
})
const handleIconClick: React.MouseEventHandler<HTMLDivElement> = evt => {
setOpen(!open)
}
const [value, setValue] = React.useState<string>('')
const dateFormat = 'MM/dd/yyyy'
const handleChange: React.ChangeEventHandler<HTMLInputElement> = evt => {
const nextValue = evt.target.value
setValue(nextValue)
handleDateSelectChange({
selected,
setSelected,
value: nextValue,
dateFormat
})
}
return (
<div style={{ display: 'inline-block', position: 'relative' }}>
<TextInput
onChange={handleChange}
value={value}
placeholder='mm/dd/yyyy'
icon={
<CalendarIcon
onClick={handleIconClick}
style={{ cursor: 'pointer' }}
/>
}
/>
<br />
{open && (
<Calendar
{...dayzedData}
style={{ position: 'absolute', zIndex: 1, marginTop: 4 }}
>
{(props: CalendarDayProps) => <CalendarDay {...props} />}
</Calendar>
)}
</div>
)
}
export default Example