diff --git a/packages/components/src/date-time/date.tsx b/packages/components/src/date-time/date.tsx index fffba036a3a8c..9399d6afbab4a 100644 --- a/packages/components/src/date-time/date.tsx +++ b/packages/components/src/date-time/date.tsx @@ -80,7 +80,26 @@ function DatePickerDay( { day, events = [] }: DatePickerDayProps ) { ); } -function DatePicker( { +/** + * DatePicker is a React component that renders a calendar for date selection. + * + * ```jsx + * import { DatePicker } from '@wordpress/components'; + * import { useState } from '@wordpress/element'; + * + * const MyDatePicker = () => { + * const [ date, setDate ] = useState( new Date() ); + * + * return ( + * setDate( newDate ) } + * /> + * ); + * }; + * ``` + */ +export function DatePicker( { currentDate, onChange, events, diff --git a/packages/components/src/date-time/index.tsx b/packages/components/src/date-time/index.tsx index 9801d4c9407fa..85d9bdaf218e4 100644 --- a/packages/components/src/date-time/index.tsx +++ b/packages/components/src/date-time/index.tsx @@ -174,7 +174,6 @@ function UnforwardedDateTimePicker( * date and time selection. The calendar and clock components can be accessed * individually using the `DatePicker` and `TimePicker` components respectively. * - * @example * ```jsx * import { DateTimePicker } from '@wordpress/components'; * import { useState } from '@wordpress/element'; @@ -186,7 +185,7 @@ function UnforwardedDateTimePicker( * setDate( newDate ) } - * is12Hour={ true } + * is12Hour * /> * ); * }; diff --git a/packages/components/src/date-time/stories/date.js b/packages/components/src/date-time/stories/date.js deleted file mode 100644 index 6859eec76bd71..0000000000000 --- a/packages/components/src/date-time/stories/date.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Internal dependencies - */ -import DatePicker from '../date'; - -/** - * WordPress dependencies - */ -import { useState } from '@wordpress/element'; - -export default { title: 'Components/DatePicker', component: DatePicker }; - -export const _default = () => { - const [ date, setDate ] = useState(); - - return ; -}; diff --git a/packages/components/src/date-time/stories/date.tsx b/packages/components/src/date-time/stories/date.tsx new file mode 100644 index 0000000000000..b469635fe3e3d --- /dev/null +++ b/packages/components/src/date-time/stories/date.tsx @@ -0,0 +1,73 @@ +/** + * External dependencies + */ +import type { ComponentMeta, ComponentStory } from '@storybook/react'; + +/** + * WordPress dependencies + */ +import { useState, useEffect } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import DatePicker from '../date'; +import { daysFromNow, isWeekend } from './utils'; + +const meta: ComponentMeta< typeof DatePicker > = { + title: 'Components/DatePicker', + component: DatePicker, + argTypes: { + currentDate: { control: 'date' }, + onChange: { action: 'onChange', control: { type: null } }, + }, + parameters: { + controls: { expanded: true }, + docs: { source: { state: 'open' } }, + }, +}; +export default meta; + +const Template: ComponentStory< typeof DatePicker > = ( { + currentDate, + onChange, + ...args +} ) => { + const [ date, setDate ] = useState( currentDate ); + useEffect( () => { + setDate( currentDate ); + }, [ currentDate ] ); + return ( + { + setDate( newDate ); + onChange?.( newDate ); + } } + /> + ); +}; + +export const Default: ComponentStory< typeof DatePicker > = Template.bind( {} ); + +export const WithEvents: ComponentStory< typeof DatePicker > = Template.bind( + {} +); +WithEvents.args = { + currentDate: new Date(), + events: [ + { date: daysFromNow( 2 ) }, + { date: daysFromNow( 4 ) }, + { date: daysFromNow( 6 ) }, + { date: daysFromNow( 8 ) }, + ], +}; + +export const WithInvalidDates: ComponentStory< + typeof DatePicker +> = Template.bind( {} ); +WithInvalidDates.args = { + currentDate: new Date(), + isInvalidDate: isWeekend, +}; diff --git a/packages/components/src/date-time/stories/index.js b/packages/components/src/date-time/stories/index.js deleted file mode 100644 index 9c3aa2607764a..0000000000000 --- a/packages/components/src/date-time/stories/index.js +++ /dev/null @@ -1,91 +0,0 @@ -/** - * External dependencies - */ -import { boolean, button } from '@storybook/addon-knobs'; - -/** - * WordPress dependencies - */ -import { useState } from '@wordpress/element'; - -/** - * Internal dependencies - */ -import DateTimePicker from '../'; - -export default { - title: 'Components/DateTimePicker', - component: DateTimePicker, - parameters: { - knobs: { disable: false }, - }, -}; - -const DateTimePickerWithState = ( { is12Hour } ) => { - const [ date, setDate ] = useState(); - - return ( - - ); -}; - -export const _default = () => { - const is12Hour = boolean( 'Is 12 hour (shows AM/PM)', false ); - return ; -}; - -// Date utils, for demo purposes. -const DAY_IN_MS = 24 * 60 * 60 * 1000; -const aFewDaysAfter = ( date ) => { - // eslint-disable-next-line no-restricted-syntax - return new Date( date.getTime() + ( 1 + Math.random() * 5 ) * DAY_IN_MS ); -}; - -const now = new Date(); - -export const WithDaysHighlighted = () => { - const [ date, setDate ] = useState( now ); - - const [ highlights, setHighlights ] = useState( [ - { date: aFewDaysAfter( now ) }, - ] ); - - button( 'Add random highlight', () => { - const lastHighlight = highlights[ highlights.length - 1 ]; - setHighlights( [ - ...highlights, - { date: aFewDaysAfter( lastHighlight.date ) }, - ] ); - } ); - - return ( - - ); -}; - -/** - * You can mark particular dates as invalid using the `isInvalidDate` prop. This - * prevents the user from being able to select it. - */ -export const WithInvalidDates = () => { - const [ currentDate, setCurrentDate ] = useState( now ); - - return ( - - // Mark Saturdays and Sundays as invalid. - date.getDay() === 0 || date.getDay() === 6 - } - /> - ); -}; diff --git a/packages/components/src/date-time/stories/index.tsx b/packages/components/src/date-time/stories/index.tsx new file mode 100644 index 0000000000000..fb126edb81f4e --- /dev/null +++ b/packages/components/src/date-time/stories/index.tsx @@ -0,0 +1,75 @@ +/** + * External dependencies + */ +import type { ComponentMeta, ComponentStory } from '@storybook/react'; + +/** + * WordPress dependencies + */ +import { useState, useEffect } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import DateTimePicker from '..'; +import { daysFromNow, isWeekend } from './utils'; + +const meta: ComponentMeta< typeof DateTimePicker > = { + title: 'Components/DateTimePicker', + component: DateTimePicker, + argTypes: { + currentDate: { control: 'date' }, + onChange: { action: 'onChange', control: { type: null } }, + }, + parameters: { + controls: { expanded: true }, + docs: { source: { state: 'open' } }, + }, +}; +export default meta; + +const Template: ComponentStory< typeof DateTimePicker > = ( { + currentDate, + onChange, + ...args +} ) => { + const [ date, setDate ] = useState( currentDate ); + useEffect( () => { + setDate( currentDate ); + }, [ currentDate ] ); + return ( + { + setDate( newDate ); + onChange?.( newDate ); + } } + /> + ); +}; + +export const Default: ComponentStory< typeof DateTimePicker > = Template.bind( + {} +); + +export const WithEvents: ComponentStory< + typeof DateTimePicker +> = Template.bind( {} ); +WithEvents.args = { + currentDate: new Date(), + events: [ + { date: daysFromNow( 2 ) }, + { date: daysFromNow( 4 ) }, + { date: daysFromNow( 6 ) }, + { date: daysFromNow( 8 ) }, + ], +}; + +export const WithInvalidDates: ComponentStory< + typeof DateTimePicker +> = Template.bind( {} ); +WithInvalidDates.args = { + currentDate: new Date(), + isInvalidDate: isWeekend, +}; diff --git a/packages/components/src/date-time/stories/time.js b/packages/components/src/date-time/stories/time.js deleted file mode 100644 index 9a184c1940ee9..0000000000000 --- a/packages/components/src/date-time/stories/time.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Internal dependencies - */ -import TimePicker from '../time'; - -/** - * External dependencies - */ -import { date, boolean } from '@storybook/addon-knobs'; -import { noop } from 'lodash'; - -export default { - title: 'Components/TimePicker', - component: TimePicker, - parameters: { - knobs: { disable: false }, - }, -}; - -export const _default = () => { - return ( - - ); -}; diff --git a/packages/components/src/date-time/stories/time.tsx b/packages/components/src/date-time/stories/time.tsx new file mode 100644 index 0000000000000..9fc72086075f7 --- /dev/null +++ b/packages/components/src/date-time/stories/time.tsx @@ -0,0 +1,51 @@ +/** + * External dependencies + */ +import type { ComponentMeta, ComponentStory } from '@storybook/react'; + +/** + * WordPress dependencies + */ +import { useState, useEffect } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import TimePicker from '../time'; + +const meta: ComponentMeta< typeof TimePicker > = { + title: 'Components/TimePicker', + component: TimePicker, + argTypes: { + currentTime: { control: 'date' }, + onChange: { action: 'onChange', control: { type: null } }, + }, + parameters: { + controls: { expanded: true }, + docs: { source: { state: 'open' } }, + }, +}; +export default meta; + +const Template: ComponentStory< typeof TimePicker > = ( { + currentTime, + onChange, + ...args +} ) => { + const [ time, setTime ] = useState( currentTime ); + useEffect( () => { + setTime( currentTime ); + }, [ currentTime ] ); + return ( + { + setTime( newTime ); + onChange?.( newTime ); + } } + /> + ); +}; + +export const Default: ComponentStory< typeof TimePicker > = Template.bind( {} ); diff --git a/packages/components/src/date-time/stories/utils.ts b/packages/components/src/date-time/stories/utils.ts new file mode 100644 index 0000000000000..ccdac56c38135 --- /dev/null +++ b/packages/components/src/date-time/stories/utils.ts @@ -0,0 +1,9 @@ +export function daysFromNow( days: number ) { + const date = new Date(); + date.setDate( date.getDate() + days ); + return date; +} + +export function isWeekend( date: Date ) { + return date.getDay() === 0 || date.getDay() === 6; +} diff --git a/packages/components/src/date-time/time.tsx b/packages/components/src/date-time/time.tsx index 10b673b7c83d8..9d0c1cfa4c394 100644 --- a/packages/components/src/date-time/time.tsx +++ b/packages/components/src/date-time/time.tsx @@ -80,6 +80,26 @@ function UpdateOnBlurAsIntegerField( { } ); } +/** + * TimePicker is a React component that renders a clock for time selection. + * + * ```jsx + * import { TimePicker } from '@wordpress/components'; + * import { useState } from '@wordpress/element'; + * + * const MyTimePicker = () => { + * const [ time, setTime ] = useState( new Date() ); + * + * return ( + * setTime( newTime ) } + * is12Hour + * /> + * ); + * }; + * ``` + */ export function TimePicker( { is12Hour, currentTime, diff --git a/packages/components/src/date-time/types.ts b/packages/components/src/date-time/types.ts index 7ceddca3e70d2..1293721f3e27d 100644 --- a/packages/components/src/date-time/types.ts +++ b/packages/components/src/date-time/types.ts @@ -21,15 +21,11 @@ export type UpdateOnBlurAsIntegerFieldProps = { children?: ReactNode; }; -export type DateTimeString = string; - -export type ValidDateTimeInput = Date | string | number | null; - export type TimePickerProps = { /** * The initial current time the time picker should render. */ - currentTime?: ValidDateTimeInput; + currentTime?: Date | string | number | null; /** * Whether we use a 12-hour clock. With a 12-hour clock, an AM/PM widget is @@ -42,7 +38,7 @@ export type TimePickerProps = { * The function called when a new time has been selected. It is passed the * time as an argument. */ - onChange?: ( time: DateTimeString ) => void; + onChange?: ( time: string ) => void; }; export type DatePickerEvent = { @@ -71,13 +67,13 @@ export type DatePickerProps = { * The current date and time at initialization. Optionally pass in a `null` * value to specify no date is currently selected. */ - currentDate?: ValidDateTimeInput; + currentDate?: Date | string | number | null; /** * The function called when a new date has been selected. It is passed the * date as an argument. */ - onChange?: ( date: DateTimeString ) => void; + onChange?: ( date: string ) => void; /** * A callback function which receives a Date object representing a day as an @@ -91,7 +87,7 @@ export type DatePickerProps = { * picker. The callback receives the new month date in the ISO format as an * argument. */ - onMonthPreviewed?: ( date: DateTimeString ) => void; + onMonthPreviewed?: ( date: string ) => void; /** * List of events to show in the date picker. Each event will appear as a @@ -100,11 +96,11 @@ export type DatePickerProps = { events?: DatePickerEvent[]; }; -export type DateTimePickerProps = DatePickerProps & - TimePickerProps & { +export type DateTimePickerProps = Omit< DatePickerProps, 'onChange' > & + Omit< TimePickerProps, 'currentTime' | 'onChange' > & { /** * The function called when a new date or time has been selected. It is * passed the date and time as an argument. */ - onChange?: ( date: DateTimeString | null ) => void; + onChange?: ( date: string | null ) => void; }; diff --git a/packages/components/src/date-time/utils.ts b/packages/components/src/date-time/utils.ts index 6c4f4fd217cb7..25bb63a84d012 100644 --- a/packages/components/src/date-time/utils.ts +++ b/packages/components/src/date-time/utils.ts @@ -3,21 +3,16 @@ */ import moment from 'moment'; -/** - * Internal dependencies - */ -import type { ValidDateTimeInput } from './types'; - /** * Create a Moment object from a date string. With no date supplied, default to * a Moment object representing now. If a null value is passed, return a null * value. * - * @param {ValidDateTimeInput} [date] Date representing the currently selected - * date or null to signify no selection. - * @return {?moment.Moment} Moment object for selected date or null. + * @param [date] Date representing the currently selected + * date or null to signify no selection. + * @return Moment object for selected date or null. */ -export const getMomentDate = ( date?: ValidDateTimeInput ) => { +export const getMomentDate = ( date?: Date | string | number | null ) => { if ( null === date ) { return null; }