Playbook's date picker is built using flatpickr, a vanilla js library. Common date picker use cases and features have been adapted into simple prop based configuration detailed in the docs below. You can implement additional features and functionality by accessing a flatpickr instance directly (demonstrated in the 'flatpickr methods' example below). This is done with the following code.
const fpInstance = document.querySelector('#pickerId')._flatpickr
pickerId
is a prop passed to the date picker kit. Flatpickr uses this id to target an input and attach a flatpickr instance to that input.
To learn more visit flatpickr's docs or see the hook doc section below for an applied example.
The Date Picker works best with Javascript Date Objects or ISO Date strings. If you're programming in js use Date Objects. If you're using rails convert your date object (with timezone) to UTC and then to an ISO Date string. For example, DateTime.now.utc.iso8601
. This ensures that the string passed to the Date Picker kit behaves as expected.
The Date Picker expects a date format of MM/DD/YYYY
by default. If a different date format (e.g. DD/MM/YYYY
, m/d/y
, etc.) is used, the component will not know how to handle it and use a default date instead. To change the date format used, read more here.
The defaultDate
/default_date
prop has a null or empty string value by default. You can pass an ISO date string (preferred rails method) or date object (preferred JS method) if you want a default value on page load. Use Ruby UTC DateTime objects and convert them to ISO date strings with DateTime.now.utc.iso8601
.
If you use a Date object without UTC time standardization the Date Picker kit may misinterpret that date as yesterdays date (consequence of timezone differentials and the Javascript Date Object constructor). See this GitHub issue for more information and the anti-pattern examples below.
import React from 'react' import DatePicker from 'playbook-ui' const DatePickerDefaultDate = (props) => ( <div> <DatePicker defaultDate="07/31/2020" label="Default Date String" pickerId="date-picker-default-date1" /> <DatePicker defaultDate={new Date().fp_incr(1)} label="Default Date Dynamic" pickerId="date-picker-default-date2" /> <DatePicker label="Default Behavior" pickerId="date-picker-default-date4" /> </div> ) export default DatePickerDefaultDate
Setting the allowInput
prop to true permits users to key values directly into the input. This prop is set to false by default.
The date picker is built with the text input kit. Text input props you pass to the date picker kit will be forwarded to the input, with a few exceptions. The value
attribute is automatically handled and bound to whatever date string is contained by the input field. You cannot pass a custom value prop. id
props passed to the date picker kit will be assigned to it's parent/wrapper div. The pickerId
prop is passed directly to the input and is required to instatiate the date picker.
You must use inputAria
or input_aria
and inputData
or input_data
props if you wish to pass data or aria attributes to the text input kit. If you use data
or aria
props they will be passed to the date picker kit itself instead. Also be aware the default behavior of text input aria and data props is to pass those props to attributes on the wrapping div not on the input itself.
The placeholder prop has a default string value: "Select Date". You can replace this with your own string or an empty string if you'd prefer it blank.
import React from 'react' import DatePicker from 'playbook-ui' const DatePickerInput = (props) => ( <div> <DatePicker inputAria={{ label: 'input-field' }} inputData={{ key: 'value', key2: 'value2' }} label="Aria, Name, and Data Attributes" name="date-field" pickerId="date-picker-input1" /> <DatePicker label="Custom Placeholder" pickerId="date-picker-input2" placeholder="custom-placeholder" /> <DatePicker label="Blank Placeholder" pickerId="date-picker-input3" placeholder="" /> <DatePicker disableInput label="Disable Input" pickerId="date-picker-input4" placeholder="Disabled Input" /> </div> ) export default DatePickerInput
Default label prop is "Date Picker"
. To remove the label set the hideLabel
prop in React or the hide_label
prop in Rails to true
.
Your change handler function has access to two arguments: dateStr
and selectedDates
.
The first, dateStr
, is a string of the chosen date. The second, selectedDates
, is an array of selected date objects. In many use cases selectedDates
will have only one value but you'll still need to access it from index 0.
NOTE: On Change does not account for manual input by users, so if your date picker sets allowInput
, you should use the onClose
method instead.
import React, { useState } from 'react' import { DatePicker, LabelValue } from 'playbook-ui' const DatePickerOnChange = (props) => { const today = new Date() const [dateString, setDateString] = useState(today.toLocaleDateString()) const [dateObj, setDateObj] = useState([today]) const changeHandler = (dateStr, selectedDates) => { setDateString(dateStr) setDateObj(selectedDates) } return ( <div> <DatePicker defaultDate={dateString} marginBottom="lg" onChange={changeHandler} pickerId="date-picker-onchange" /> <LabelValue label="Date Object" marginBottom="lg" value={dateObj[0] ? dateObj[0].toString() : ''} /> <LabelValue label="Date String" value={dateString} /> </div> ) } export default DatePickerOnChange
The onClose
handler function has access to two arguments: dateStr
and selectedDates
.
The first, dateStr
, is a string of the chosen date. The second, selectedDates
, is an array of selected date objects. In many use cases selectedDates
will have only one value but you'll still need to access it from index 0.
NOTE: onClose
is the ideal handler function to use when allowInput
is enabled.
/* eslint-disable react/no-multi-comp */ import React, { useState } from 'react' import { DatePicker,LabelValue } from 'playbook-ui' const DatePickerOnClose = (props) => { const today = new Date() const [dateString, setDateString] = useState(today.toLocaleDateString()) const [dateObj, setDateObj] = useState([today]) const handleOnClose = (selectedDates, dateStr) => { setDateString(dateStr) setDateObj(selectedDates) } return ( <div> <DatePicker defaultDate={dateString} enableTime marginBottom="lg" onClose={handleOnClose} pickerId="date-picker-on-close" showTimezone /> <LabelValue label="Date Object" marginBottom="lg" value={dateObj[0] ? dateObj[0].toString() : ''} /> <LabelValue label="Date String" value={dateString} /> </div> ) } export default DatePickerOnClose
Use the onChange
handler function to access the startDate and endDate values. Check the onChange
example for more information.
import React from "react" import DatePicker from 'playbook-ui' const DatePickerQuickPickReact = (props) => ( <> <DatePicker allowInput mode="range" pickerId="date-picker-quick-pick" placeholder="mm/dd/yyyy to mm/dd/yyyy" selectionType="quickpick" /> </> ) export default DatePickerQuickPickReact
Because the Quick Pick variant has allowInput
set to true
by default, use the onClose
handler function to access the startDate and endDate values. See the onClose
example for details.
import React from 'react' import DatePicker from 'playbook-ui' const DatePickerQuickPickRangeLimit = (props) => ( <> <DatePicker allowInput mode="range" pickerId="thisRangesEndToday" placeholder="mm/dd/yyyy to mm/dd/yyyy" selectionType="quickpick" thisRangesEndToday /> </> ) export default DatePickerQuickPickRangeLimit
The customQuickPickDates/custom_quick_pick_dates prop allows for the user/dev to define their own quick pick dates.
The prop accepts an object with two key/value pairs: dates & override (separate doc example below).
The dates property accepts an array of objects. Each object in the array has label and value properties. The label is what will be displayed in the UI of the dropdown menu. The value property is just the date that is going to be passed to the datepicker. The value property can be an array of two strings that represent a range, allowing for the dev to be extremely specific. Additionally, the dates array allows for a clean, simple API under that automatically converts dates in a common vernacular.
The timePeriod property accepts "days", "weeks", "months", "quarters" or "years", representing past time periods.
The amount property accepts any number.
import React from "react" import DatePicker from 'playbook-ui' const DatePickerQuickPickCustom = (props) => ( <> <DatePicker allowInput customQuickPickDates={{ dates: [ // Allow Playbook to handle the logic... { label: "Last 15 months", value: { timePeriod: "months", amount: 15, }, }, // Or, be explicit with an exact date range for more control... { label: "First Week of June 2022", value: ["06/01/2022", "06/07/2022"], }, ], }} mode='range' pickerId='date-picker-quick-pick-custom-override' placeholder='mm/dd/yyyy to mm/dd/yyyy' selectionType='quickpick' /> </> ) export default DatePickerQuickPickCustom
The customQuickPickDates/custom_quick_pick_dates prop allows for an override boolean. The override allows for the user to completely override the quick pick dates that ship with the component. Default of override
is set to true. If you would like to simply append your dates to the default quick pick dates, set this prop to false explicitly.
import React from "react" import DatePicker from 'playbook-ui' const DatePickerQuickPickCustomOverride = (props) => ( <> <DatePicker allowInput customQuickPickDates={{ override: false, dates: [ { label: "Last 15 months", value: { timePeriod: "months", amount: 15, }, }, { label: "First Week of June 2022", value: ["06/01/2022", "06/07/2022"], }, ], }} marginTop='lg' mode='range' pickerId='date-picker-quick-pick-custom' placeholder='mm/dd/yyyy to mm/dd/yyyy' selectionType='quickpick' /> </> ) export default DatePickerQuickPickCustomOverride
A full list of formatting tokens, i.e. "m/d/Y"
can be found here.
import React from 'react' import DatePicker from 'playbook-ui' const DatePickerFormat = (props) => ( <div> <DatePicker defaultDate={new Date()} format="m-d-Y" pickerId="date-picker-format1" /> <DatePicker defaultDate={new Date()} format="m/d/y" pickerId="date-picker-format2" /> <DatePicker defaultDate={new Date()} format="n-j-y" pickerId="date-picker-format3" /> <DatePicker defaultDate={new Date()} format="Y-d-m" pickerId="date-picker-format4" /> </div> ) export default DatePickerFormat
import React from 'react' import DatePicker from 'playbook-ui' const DatePickerDisabled = (props) => ( <div> <DatePicker disableDate={[new Date().fp_incr(1)]} label="Disable Single Date" pickerId="single-disabled-date" /> <DatePicker disableDate={[new Date().fp_incr(1), new Date().fp_incr(3)]} label="Disable Multiple Dates" pickerId="multiple-disabled-dates" /> <DatePicker disableRange={[ { from: new Date().fp_incr(1), to: new Date().fp_incr(7), }, ]} label="Disable Single Range" pickerId="single-date-range" /> <DatePicker disableRange={[ { from: new Date().fp_incr(1), to: new Date().fp_incr(3), }, { from: new Date().fp_incr(7), to: new Date().fp_incr(14), }, ]} label="Disable Multiple Ranges" pickerId="multiple-date-ranges" /> <DatePicker disableWeekdays={['Sunday', 'Saturday']} label="Disable Specific Weekdays" pickerId="disabled-weekdays" /> </div> ) export default DatePickerDisabled
import React from 'react' import DatePicker from 'playbook-ui' const DatePickerMinMax = (props) => ( <div> <DatePicker label="Dynamic dates using flatpickr increment function" maxDate={new Date().fp_incr(3)} minDate={new Date().fp_incr(-3)} pickerId="date-picker-min-max1" /> <DatePicker format="m/d/Y" label="Absolute formatted dates" maxDate="10/20/2020" minDate="10/10/2020" pickerId="date-picker-min-max2" /> </div> ) export default DatePickerMinMax
import React, { useEffect } from 'react' import DatePicker from 'playbook-ui' import Button from 'playbook-ui' const DatePickerFlatpickrMethods = () => { let fpInstance useEffect(() => { fpInstance = document.querySelector('#fp-methods')._flatpickr }, []) const clickHandlerClear = () => { fpInstance.clear() } const clickHandlerClose = () => { fpInstance.close() } const clickHandlerToday = () => { fpInstance.setDate(new Date(), true) } return ( <div> <Button marginRight="sm" onClick={clickHandlerClose} text="Close" /> <Button marginRight="sm" onClick={clickHandlerClear} text="Clear" /> <Button onClick={clickHandlerToday} text="Today" /> <DatePicker hideLabel marginTop="sm" pickerId="fp-methods" /> </div> ) } export default DatePickerFlatpickrMethods
You can find a full list of flatpickr events and hooks in their documentation.
import React from 'react' import DatePicker from 'playbook-ui' const DatePickerHooks = (props) => { // Define hooks const changeHook = () => { alert('date changed') } const openHook = () => { alert('calendar opened') } // Access flatpickr instances with picker ids and assign them variables window.addEventListener('DOMContentLoaded', () => { const fpChange = document.querySelector('#date-picker-hooks-onchange')._flatpickr const fpOpen = document.querySelector('#date-picker-hooks-onopen')._flatpickr // Push one or more hooks to flatpickr instance's Event config arrays fpChange.config.onChange.push(changeHook) fpOpen.config.onOpen.push(openHook) }) return ( <div> <DatePicker label="onChange" pickerId="date-picker-hooks-onchange" /> <DatePicker label="onOpen" pickerId="date-picker-hooks-onopen" /> </div> ) } export default DatePickerHooks
Defaults to [1900, 2100]
. Combine with min-max prop for best results.
import React from 'react' import DatePicker from 'playbook-ui' const DatePickerYearRange = (props) => ( <div> <DatePicker defaultDate="05/05/2015" maxDate="12/31/2018" minDate="01/01/2015" pickerId="date-picker-year-range" yearRange={[2015, 2018]} /> </div> ) export default DatePickerYearRange
import React from 'react' import DatePicker from 'playbook-ui' const DatePickerMarginBottom = (props) => ( <div> <DatePicker marginBottom="none" pickerId="date-picker-none" /> <DatePicker marginBottom="xs" pickerId="date-picker-xs" /> <DatePicker marginBottom="sm" pickerId="date-picker-sm" /> <DatePicker marginBottom="md" pickerId="date-picker-md" /> <DatePicker marginBottom="lg" pickerId="date-picker-lg" /> <DatePicker marginBottom="xl" pickerId="date-picker-xl" /> </div> ) export default DatePickerMarginBottom
import React from 'react' import DatePicker from 'playbook-ui' const DatePickerInline = (props) => { const showAngleDownHandler = (dateSelected) => { if (dateSelected) { document.querySelector('.inline-date-picker').classList.add('show-angle-down-icon') } } return ( <div> <DatePicker className="inline-date-picker" hideIcon inLine onChange={showAngleDownHandler} pickerId="date-picker-inline" /> </div> ) } export default DatePickerInline
By default selectType prop is disabled. To activate it set selectionType
prop in JSX/TSX to month
. To activate it set selection_type
prop in a rails file to month
.
By default selectType prop is disabled. To activate it set selectionType
prop in JSX/TSX to week
. To activate it set selection_type
prop in a rails file to week
.
To select time as well, you should pass the enableTime
boolean prop. You can also enable timezone display by passing showTimezone
.
/* eslint-disable react/no-multi-comp */ import React, { useMemo, useState } from 'react' import DatePicker from 'playbook-ui' import Body from 'playbook-ui' const DEFAULT_DATE = new Date() DEFAULT_DATE.setHours(12) DEFAULT_DATE.setMinutes(0) const DatePickerTime = (props) => { const [selectedDateTime, setSelectedDateTime] = useState(DEFAULT_DATE) const refExample = React.createRef() const handleOnInputChanged = (dateTime) => { setSelectedDateTime(dateTime) } return ( <div ref={refExample}> <Body marginBottom="md">{selectedDateTime.toString()}</Body> {useMemo(() => ( <DatePicker closeOnSelect={false} defaultDate={DEFAULT_DATE} enableTime onChange={handleOnInputChanged} pickerId="date-picker-time" showTimezone /> ), [props])} </div> ) } export default DatePickerTime
Datepicker supports position
options from Flatpickr Options Documentation. There are multiple positioning options to choose from.
Note: In order for the above prop to work properly, you must also send staticPosition={false}
to your Datepicker kit instance.
Upon adding static={false}
to the date picker, you will notice that the date picker detaches from the input field while scrolling. This is a known Flatpickr nuance. By adding the scrollContainer
prop, you can tell the date picker which DOM container it should watch for scroll events. In this example, you can see that scrollContainer=".pb--page--content--main"
is being passed in order to keep the date picker correctly positioned on page scroll.
Useage: scrollContainer: .validQuerySelectorHere
import React from 'react' import Flex from 'playbook-ui' import FlexItem from 'playbook-ui' import DatePicker from 'playbook-ui' const DatePickerPositions = (props) => ( <React.Fragment> <Flex> <FlexItem fixedSize="50%"> <DatePicker label="Datepicker (opens on the right)" pickerId="date-picker-positions1" position="auto right" scrollContainer=".pb--page--content--main" staticPosition={false} /> </FlexItem> </Flex> <Flex> <FlexItem fixedSize="50%"> <DatePicker label="Datepicker (opens on the left)" pickerId="date-picker-positions2" position="auto left" scrollContainer=".pb--page--content--main" staticPosition={false} /> </FlexItem> </Flex> <Flex> <FlexItem fixedSize="50%"> <DatePicker label="Datepicker (opens above on the left)" pickerId="date-picker-positions3" position="above left" scrollContainer=".pb--page--content--main" staticPosition={false} /> </FlexItem> </Flex> <Flex> <FlexItem fixedSize="50%"> <DatePicker label="Datepicker (opens below on the right)" pickerId="date-picker-positions4" position="below right" scrollContainer=".pb--page--content--main" staticPosition={false} /> </FlexItem> </Flex> </React.Fragment> ) export default DatePickerPositions
import React, { useRef, useEffect, useState } from 'react' import Card from 'playbook-ui' import Flex from 'playbook-ui' import FlexItem from 'playbook-ui' import DatePicker from 'playbook-ui' const DatePickerPositionsElement = (props) => { const cardRefTop = useRef(null), cardRefBtm = useRef(null) const [cardTop, setCardTop] = useState() const [cardBtm, setCardBtm] = useState() useEffect(() => { setCardTop(cardRefTop.current) setCardBtm(cardRefBtm.current) }, [cardTop, cardBtm]) return ( <React.Fragment> <div ref={cardRefTop}> <Card marginBottom="md" > {'π Datepicker will position from here based on ID.'} </Card> </div> <Flex> <FlexItem fixedSize="50%"> <DatePicker label="Datepicker (opens on the right)" pickerId="date-picker-position-element" position="auto right" positionElement={cardTop} scrollContainer=".pb--page--content--main" staticPosition={false} /> </FlexItem> </Flex> <div ref={cardRefBtm}> <Card marginBottom="md" > {'π Datepicker will position from here based on class name.'} </Card> </div> <Flex> <FlexItem fixedSize="50%"> <DatePicker label="Datepicker (opens on the right)" pickerId="date-picker-position-element2" position="auto right" positionElement={cardBtm} scrollContainer=".pb--page--content--main" staticPosition={false} /> </FlexItem> </Flex> </React.Fragment> ) } export default DatePickerPositionsElement