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) => void | callback 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 => void | event handler to attach to the <button /> |
onMouseLeave Required | React.MouseEvent => void | event 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): void | callback 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): void | callback 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 | Date | the 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 | string | the 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 (<><TextInputref={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 | Date | the 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 | boolean | distinguishes the first date in the range from the end date |
Returns: an array
Name | Type | Description |
---|---|---|
[0] Required | string | the 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.
- Create a new state object & setter
selected: Date
andsetSelected: React.Dispatch<React.SetStateAction<Date | undefined>>
then optionally set the intial date. - Create an
onDateSelected
callback that will update the selected state when a calendar date is clicked along with any side effects desired. - Pass
selected
,setSelected
, andonDateSelected
to theuseDayzed
hook from the dayzed library along with any other dayzed props desired. - Destructure
getDateProps
, and pass it to theCalendarDates
component which uses a renderProp pattern to apply styles and handlers to buttons that represent individual dates in a month.
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.
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.
- Create a new state object & setter
selected: Date[]
andsetSelected: React.Dispatch<React.SetStateAction<Date[] | undefined>>
then optionally set the intial date. - Import an
onRangeDateSelected
callback that will update the selected state when a calendar date is clicked along with any side effects desired using theonSelect
argument. - Pass
selected
,setSelected
, andonRangeDateSelected
to theuseDayzed
hook from the dayzed library along with any other dayzed props desired. - Destructure
getDateProps
and pass it to theCalendarDates
component which uses a renderProp pattern to apply styles and handlers to buttons that represent individual dates in a month. - Import and setup
useIsInRange
hook and pass the return toCalendar
,CalendarDates
andbutton
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, 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
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.
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
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.
- Create a new state object & setter
selected: Date
andsetSelected: React.Dispatch<React.SetStateAction<Date | undefined>>
then optionally set the initial date. - Create a new state object & setter
open: boolean
andsetOpen: React.Dispatch<React.SetStateAction<boolean>>
then set it to false. - Create an
onDateSelected
callback that will update the selected and open states when a calendar date is clicked along with any side effects desired. - Destructure
getDateProps
and pass it to theCalendarDates
component which uses a renderProp pattern to apply styles and handlers to buttons that represent individual dates in a month. - Pass
selected
,setSelected
, andonDateSelected
to theuseDayzed
hook from the dayzed library along with any other dayzed props desired. - Create a handleIconClick callback to be passed to CalendarIcon and will toggle the open state when clicked.
- Create a new state object & setter
open: boolean
andsetOpen: React.Dispatch<React.SetStateAction<ValueOf<typeof slides>>>
and pass slide to the Calendar. - Use the
useDateSelectChange
hook and pass itselected
,setSlide
, andsetSelected
. 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' }}><TextInputonChange={onChange}value={value}placeholder='mm/dd/yyyy'icon={<CalendarIcononClick={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