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

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 { DatePicker } from '@pluralsight/ps-design-system-datepicker'
<DatePicker onSelect={ props.onSelect} />
Name
Type
Description
onSelect(evt: SyntheticEvent, selectedDate: DateObj) => voidcallback that return DateObj

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'
const { getDateProps, ...dayzedData } = useDayzed({
/** basic config **/
})
return (
<Calendar {...dayzedData}>
/** CalendarDates or something custom **/
</Calendar>
)

CalendarDates

CalendarDates is a headless UI component. It takes the getDateProps from the useDayzed output as props. It applies appropriate styles and event handlers to a UI of our choice. In this example, these props are applied to a <button />, passed through renderProps.

import { useDayzed } from 'dayzed'
import { CalendarDates } from '@pluralsight/ps-design-system-datepicker'
const { getDateProps, ...dayzedData } = useDayzed({
/** See dayzed documentation section of props**/
})
return (
// <Calendar {...dayzedData}>
<CalendarDates getDateProps={getDateProps}>
{renderProps => {
return <button {...renderProps} />
}}
</CalendarDates>
// </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
Record<string, unknown>styles to apply to `button`
import { useDayzed } from 'dayzed'
import {
Calendar,
CalendarDates,
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}>
<CalendarDates getDateProps={getDateProps}>
{(renderProps, dateObj) => {
return (
<button
{...renderProps}
{...isInRange(dateObj.date)}
onMouseEnter={() => onMouseEnter(dateObj.date)}
/>
)
}}
</CalendarDates>
</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,
CalendarDates,
useIsInRange,
onRangeDateSelected
} from '@pluralsight/ps-design-system-datepicker'
const [selected, setSelected] = React.useState<Date[] | undefined>()
const { getDateProps, ...dayzedData } = useDayzed({
selected,
onDateSelected: onRangeDateSelected({
selected,
setSelected,
// onSelect (optional)
}),
// ...rest of config
})
const { onMouseLeave, onMouseEnter, isInRange } = useIsInRange(selected)
return (
<Calendar {...dayzedData} onMouseLeave={onMouseLeave}>
<CalendarDates getDateProps={getDateProps}>
{(renderProps, dateObj) => {
return (
<button
{...renderProps}
{...isInRange(dateObj.date)}
onMouseEnter={() => onMouseEnter(dateObj.date)}
/>
)
}}
</CalendarDates>
</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,
CalendarDates,
onMultiDateSelected
} from '@pluralsight/ps-design-system-datepicker'
const [selected, setSelected] = React.useState<Date[]>([])
const { getDateProps, ...dayzedData } = useDayzed({
selected,
onDateSelected: onMultiDateSelected({
selected,
setSelected,
// onSelect (optional)
})
// ...rest of config
})
return (
<Calendar {...dayzedData}>
<CalendarDates getDateProps={getDateProps}>
{renderProps => {
return <button {...renderProps} />
}}
</CalendarDates>
</Calendar>
)

useDateSelectChange

useDateSelectChange 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
selected
Required
Datethe selected date
setSelected
Required
React.Dispatch<React.SetStateAction<Date>>the set state action for the selected date
setSelected
Required
React.Dispatch<React.SetStateAction<ValueOf<typeof slides>>>the set state action for the selected date

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

slides, as referenced above, is an enum of directions in which the calendar UI can change: 'forward' | 'backward' | 'undefined'.

import { useDayzed, DateObj } from 'dayzed'
import { ValueOf } from '@pluralsight/ps-design-system-util'
import TextInput from '@pluralsight/ps-design-system-textinput'
import {Calendar, CalendarDates, slides} from '@pluralsight/ps-design-system-datepicker'
import React from 'react'
const [selected, setSelected] = React.useState<Date | undefined>()
const { getDateProps, ...dayzedData } = useDayzed({
// ... rest of config
})
const [slide, setSlide] = React.useState<ValueOf<typeof slides>>()
const [value, onChange] = useDateSelectChange({
selected,
setSlide,
setSelected
})
return (
<>
<TextInput
ref={inputRef as RefFor<'input'>}
onChange={onChange}
value={value}
/>
<Calendar
{...dayzedData}
slide={slide}
>
<CalendarDates getDateProps={getDateProps}>
{renderProps => {
return <button {...renderProps} />
}}
</CalendarDates>
</Calendar>
</>
)

useRangeSelectChange

useRangeSelectChange is like the useDateSelectChange 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
selected
Required
Datethe selected date
setSelected
Required
React.Dispatch<React.SetStateAction<Date>>the set state action for the selected date
setSelected
Required
React.Dispatch<React.SetStateAction<ValueOf<typeof slides>>>the set state action for the selected date
start
Required
booleandistinguishes the first date in the range from the end date

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,
CalendarDates,
useIsInRange,
onRangeDateSelected,
useRangeSelectChange,
slides
} from '@pluralsight/ps-design-system-datepicker'
const [selected, setSelected] = React.useState<Date[] | undefined>()
const { getDateProps, ...dayzedData } = useDayzed({
// ...rest of config
})
const { onMouseLeave, onMouseEnter, isInRange } = useIsInRange(selected)
const [slide, setSlide] = React.useState<ValueOf<typeof slides>>()
const [startValue, onStartChange] = useRangeSelectChange({
start: true,
selected,
setSlide,
setSelected
})
const [endValue, onEndChange] = useRangeSelectChange({
start: false,
selected,
setSlide,
setSelected
})
return (
<>
<TextInput onChange={onStartChange} value={startValue} />
<TextInput onChange={onEndChange} value={endValue} />
<br />
<Calendar {...dayzedData} onMouseLeave={onMouseLeave} slide={slide}>
<CalendarDates getDateProps={getDateProps}>
{(renderProps, dateObj) => {
return (
<button
{...renderProps}
{...isInRange(dateObj.date)}
onMouseEnter={() => onMouseEnter(dateObj.date)}
/>
)
}}
</CalendarDates>
</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.
  4. Destructure getDateProps, and pass it to the CalendarDates component which uses a renderProp pattern to apply styles and handlers to buttons that represent individual dates in a month.
May 2020
Sun
Mon
Tue
Wed
Thu
Fri
Sat
import { useDayzed, DateObj } from 'dayzed'
import { format } from 'date-fns'
import { colorsBackgroundLight, type, colorsTextIcon, colorsBackgroundDark, layout } from '@pluralsight/ps-design-system-core'
import {Calendar, CalendarDates} 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'))
}
const { getDateProps, ...dayzedData } = useDayzed({
date: selected || new Date('05/30/2020'),
selected,
onDateSelected
})
return (
<Calendar {...dayzedData}>
<CalendarDates getDateProps={getDateProps}>
{renderProps => {
return <button {...renderProps} />
}}
</CalendarDates>
</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.


May 2020
Sun
Mon
Tue
Wed
Thu
Fri
Sat
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, CalendarDates} 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 { getDateProps, ...dayzedData } = useDayzed({
date: selected || new Date('05/30/2020'),
selected,
onDateSelected
})
const handleClick = () => {
setSelected(new Date('05/13/2020'))
}
return (
<>
<Button onClick={handleClick}>5/13/2020</Button>
<br />
<Calendar {...dayzedData}>
<CalendarDates getDateProps={getDateProps}>
{renderProps => {
return <button {...renderProps} />
}}
</CalendarDates>
</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.
  4. Destructure getDateProps and pass it to the CalendarDates component which uses a renderProp pattern to apply styles and handlers to buttons that represent individual dates in a month.
  5. Import and setup useIsInRange hook and pass the return to Calendar, CalendarDates and button as shown below
May 2020
Sun
Mon
Tue
Wed
Thu
Fri
Sat
import { useDayzed, DateObj } from 'dayzed'
import { format } from 'date-fns'
import { colorsBackgroundLight, type, colorsTextIcon, colorsBackgroundDark, layout } from '@pluralsight/ps-design-system-core'
import {Calendar, CalendarDates, useIsInRange, onRangeDateSelected} from '@pluralsight/ps-design-system-datepicker'
import React from 'react'
const Example = () => {
const [selected, setSelected] = React.useState<Date[] | undefined>()
const { getDateProps, ...dayzedData } = useDayzed({
selected,
onDateSelected: onRangeDateSelected({
selected,
setSelected,
// onSelect: (event: React.SyntheticEvent<Element, Event>, selectedDate: DateObj): void
}),
date: new Date('05/30/2020')
})
const { onMouseLeave, onMouseEnter, isInRange } = useIsInRange(selected)
return (
<Calendar {...dayzedData} onMouseLeave={onMouseLeave}>
<CalendarDates getDateProps={getDateProps}>
{(renderProps, dateObj) => {
return (
<button
{...renderProps}
{...isInRange(dateObj.date)}
onMouseEnter={() => onMouseEnter(dateObj.date)}
/>
)
}}
</CalendarDates>
</Calendar>
)
}
export default Example

Range controlled by external state


May 2020
Sun
Mon
Tue
Wed
Thu
Fri
Sat
Jun 2020
Sun
Mon
Tue
Wed
Thu
Fri
Sat
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, CalendarDates, useIsInRange, onRangeDateSelected} from '@pluralsight/ps-design-system-datepicker'
import React from 'react'
const Example = () => {
const [selected, setSelected] = React.useState<Date[]>([])
const { getDateProps, ...dayzedData } = useDayzed({
monthsToDisplay: 2,
selected,
onDateSelected: onRangeDateSelected({
selected,
setSelected,
// onSelect: (event: React.SyntheticEvent<Element, Event>, selectedDate: DateObj): void
}),
date: new Date('05/30/2020')
})
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 {...dayzedData} onMouseLeave={onMouseLeave}>
<CalendarDates getDateProps={getDateProps}>
{(renderProps, dateObj) => {
return (
<button
{...renderProps}
{...isInRange(dateObj.date)}
onMouseEnter={() => onMouseEnter(dateObj.date)}
/>
)
}}
</CalendarDates>
</Calendar>
</>
)
}
export default Example

Range with bi-directional updates

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


May 2020
Sun
Mon
Tue
Wed
Thu
Fri
Sat
Jun 2020
Sun
Mon
Tue
Wed
Thu
Fri
Sat
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, CalendarDates, useIsInRange, onRangeDateSelected, useRangeSelectChange, slides} from '@pluralsight/ps-design-system-datepicker'
import React from 'react'
const Example = () => {
const [selected, setSelected] = React.useState<Date[] | undefined>()
const { getDateProps, ...dayzedData } = useDayzed({
monthsToDisplay: 2,
selected,
onDateSelected: onRangeDateSelected({
selected,
setSelected,
// onSelect: (event: React.SyntheticEvent<Element, Event>, selectedDate: DateObj): void
}),
date: new Date('05/30/2020')
})
const { onMouseLeave, onMouseEnter, isInRange } = useIsInRange(selected)
const [slide, setSlide] = React.useState<ValueOf<typeof slides>>()
const [startValue, onStartChange] = useRangeSelectChange({
start: true,
selected,
setSlide,
setSelected
})
const [endValue, onEndChange] = useRangeSelectChange({
start: false,
selected,
setSlide,
setSelected
})
return (
<>
<div>
<TextInput onChange={onStartChange} value={startValue} placeholder='mm/dd/yyyy' />
<TextInput onChange={onEndChange} value={endValue} placeholder='mm/dd/yyyy' />
</div>
<br />
<Calendar {...dayzedData} onMouseLeave={onMouseLeave} slide={slide}>
<CalendarDates getDateProps={getDateProps}>
{(renderProps, dateObj) => {
return (
<button
{...renderProps}
{...isInRange(dateObj.date)}
onMouseEnter={() => onMouseEnter(dateObj.date)}
/>
)
}}
</CalendarDates>
</Calendar>
</>
)
}
export default Example

Multi-date

Apr 2021
Sun
Mon
Tue
Wed
Thu
Fri
Sat
import { useDayzed, DateObj } from 'dayzed'
import { format } from 'date-fns'
import { colorsBackgroundLight, type, colorsTextIcon, colorsBackgroundDark, layout } from '@pluralsight/ps-design-system-core'
import {Calendar, CalendarDates, onMultiDateSelected} from '@pluralsight/ps-design-system-datepicker'
import React from 'react'
const Example = () => {
const [selected, setSelected] = React.useState<Date[]>([])
const { getDateProps, ...dayzedData } = useDayzed({
selected,
onDateSelected: onMultiDateSelected({
selected,
setSelected,
// onSelect: (event: React.SyntheticEvent<Element, Event>, selectedDate: DateObj): void
})
})
return (
<Calendar {...dayzedData}>
<CalendarDates getDateProps={getDateProps}>
{renderProps => {
return <button {...renderProps} />
}}
</CalendarDates>
</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. Destructure getDateProps and pass it to the CalendarDates component which uses a renderProp pattern to apply styles and handlers to buttons that represent individual dates in a month.
  5. Pass selected, setSelected, and onDateSelected to the useDayzed hook from the dayzed library along with any other dayzed props desired.
  6. Create a handleIconClick callback to be passed to CalendarIcon and will toggle the open state when clicked.
  7. Create a new state object & setter open: boolean and setOpen: React.Dispatch<React.SetStateAction<ValueOf<typeof slides>>> and pass slide to the Calendar.
  8. Use the useDateSelectChange hook and pass it selected, setSlide, and setSelected. Destructure the returned value and onChange callback and pass them to the TextInput. To manipulate the calendar using an input will require a bit more setup. The DatePicker package provides us with a handy hook to reduce the amount of boilerplate necessary for such a composite component, useDateSelectChange.

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, CalendarDates, slides} 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 [slide, setSlide] = React.useState<ValueOf<typeof slides>>()
const [value, onChange] = useDateSelectChange({
selected,
setSlide,
setSelected
})
return (
<div style={{ display: 'inline-block', position: 'relative' }}>
<TextInput
onChange={onChange}
value={value}
placeholder='mm/dd/yyyy'
icon={
<CalendarIcon
onClick={handleIconClick}
style={{ cursor: 'pointer' }}
/>
}
/>
<br />
{open && (
<Calendar
{...dayzedData}
style={{ position: 'absolute', zIndex: 1, marginTop: 4 }}
slide={slide}
>
<CalendarDates getDateProps={getDateProps}>
{renderProps => {
return <button {...renderProps} />
}}
</CalendarDates>
</Calendar>
)}
</div>
)
}
export default Example