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><DatePickerlabel="Party time"onSelect={(evt, dateObj) => (setDate(dateObj.date))}value={date}/></div></div>)}export default Example
Name | Type | Description |
---|---|---|
onSelect | (evt: SyntheticEvent, selectedDate: DateObj) => void | callback that return DateObj |
value | Date | date 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 => void | event handler to attach to the <button /> |
onMouseLeave Required | React.MouseEvent => void | event handler to attach to Calendar |
isInRange Required | string | className(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 } = propsreturn (<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): 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,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 } = propsreturn (<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): 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,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 |
---|---|---|
dateFormat | string | a <a href='https://date-fns.org/v2.16.1/docs/format'>date-fns compatible format string</a> |
selected Required | Date | the selected date |
setSelected Required | React.Dispatch<React.SetStateAction<Date>> | the set state action for the selected date |
value Required | string | formatted date value as string |
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 |
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.valuesetValue(nextValue)handleDateSelectChange({selected,setSelected,value: nextValue,dateFormat})}return (<><TextInputref={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 |
---|---|---|
dateFormat | string | a <a href='https://date-fns.org/v2.16.1/docs/format'>date-fns compatible format string</a> |
selected Required | Date | the selected date |
setSelected Required | React.Dispatch<React.SetStateAction<Date>> | the set state action for the selected date |
start Required | boolean | distinguishes 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 | 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,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.valuesetStartValue(nextValue)handleRangeSelectChange({dateFormat,selected,setSelected,start: true,value: nextValue})}const onEndChange: React.ChangeEventHandler<HTMLInputElement> = evt => {const nextValue = evt.target.valuesetEndValue(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 } = propsreturn (<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.
- 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.
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.
- 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. 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
,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 } = propsreturn (<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 } = propsreturn (<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.valuesetStartValue(nextValue)handleRangeSelectChange({dateFormat,selected,setSelected,start: true,value: nextValue})}const onEndChange: React.ChangeEventHandler<HTMLInputElement> = evt => {const nextValue = evt.target.valuesetEndValue(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 } = propsreturn (<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.
- 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. - 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<boolean>>
. - Use the
handleChange
callback and pass itdateFormat
,selected
,setSelected
, andvalue
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.valuesetValue(nextValue)handleDateSelectChange({selected,setSelected,value: nextValue,dateFormat})}return (<div style={{ display: 'inline-block', position: 'relative' }}><TextInputonChange={handleChange}value={value}placeholder='mm/dd/yyyy'icon={<CalendarIcononClick={handleIconClick}style={{ cursor: 'pointer' }}/>}/><br />{open && (<Calendar{...dayzedData}style={{ position: 'absolute', zIndex: 1, marginTop: 4 }}>{(props: CalendarDayProps) => <CalendarDay {...props} />}</Calendar>)}</div>)}export default Example