diff --git a/packages/blade/src/components/DatePicker/DatePicker.stories.tsx b/packages/blade/src/components/DatePicker/DatePicker.stories.tsx index 23c11bc4ed2..3f813988ac7 100644 --- a/packages/blade/src/components/DatePicker/DatePicker.stories.tsx +++ b/packages/blade/src/components/DatePicker/DatePicker.stories.tsx @@ -1,59 +1,388 @@ import type { StoryFn, Meta } from '@storybook/react'; import dayjs from 'dayjs'; import React from 'react'; -import type { CalendarProps, DatesRangeValue } from './types'; -import { DatePicker } from './'; +import { Title } from '@storybook/addon-docs'; +import { I18nProvider } from '@razorpay/i18nify-react'; +import type { DatePickerProps, DatesRangeValue } from './types'; +import { DatePicker as DatePickerComponent } from './'; import { Box } from '~components/Box'; +import StoryPageWrapper from '~utils/storybook/StoryPageWrapper'; +import { Sandbox } from '~utils/storybook/Sandbox'; +import { Code, Text } from '~components/Typography'; +import 'dayjs/locale/fr'; +const propsCategory = { + BASE_PROPS: 'DatePicker Props', + INPUT_PROPS: 'Input Props', +}; + +const baseProp = { + table: { + category: propsCategory.BASE_PROPS, + }, +} as const; +const inputProp = { + table: { + category: propsCategory.INPUT_PROPS, + }, +} as const; export default { title: 'Components/DatePicker', - component: DatePicker, + component: DatePickerComponent, tags: ['autodocs'], -} as Meta>; + argTypes: { + value: baseProp, + isOpen: baseProp, + onChange: baseProp, + selectionType: baseProp, + presets: baseProp, + minDate: baseProp, + maxDate: baseProp, + excludeDate: baseProp, + picker: baseProp, + onOpenChange: baseProp, + allowSingleDateInRange: baseProp, + defaultIsOpen: baseProp, + defaultPicker: baseProp, + defaultValue: baseProp, + firstDayOfWeek: baseProp, + onMonthSelect: baseProp, + onYearSelect: baseProp, + onNext: baseProp, + onNextDecade: baseProp, + onNextMonth: baseProp, + onNextYear: baseProp, + onPickerChange: baseProp, + onPrevious: baseProp, + onPreviousDecade: baseProp, + onPreviousMonth: baseProp, + onPreviousYear: baseProp, + locale: baseProp, + accessibilityLabel: inputProp, + errorText: inputProp, + helpText: inputProp, + isDisabled: inputProp, + isRequired: inputProp, + label: inputProp, + labelPosition: inputProp, + size: inputProp, + successText: inputProp, + validationState: inputProp, + name: inputProp, + autoFocus: inputProp, + necessityIndicator: inputProp, + }, + parameters: { + docs: { + page: () => ( + + Usage + + {` + import { DatePicker } from '@razorpay/blade/components'; -export const Calendar: StoryFn = () => { - const [isOpen, setIsOpen] = React.useState(true); - const [date, setDate] = React.useState([ - new Date(), - dayjs().add(3, 'day').toDate(), - ]); + function App(): React.ReactElement { + return ( + console.log(e)} + /> + ) + } + + export default App; + `} + + + ), + }, + }, +} as Meta>; + +const DatePickerTemplate: StoryFn = ({ ...args }) => { + if (args.selectionType === 'range' && typeof args.label === 'string') { + throw new Error( + '[Storybook Controls]: Cannot use string label for range selection, please switch to the RangeDatePicker story', + ); + } + if (args.selectionType === 'single' && typeof args.label === 'object') { + throw new Error( + '[Storybook Controls]: Cannot use {start,end} label for single selection, please switch to the SingleDatePicker story', + ); + } + return ( + { + console.log(date); + }} + {...args} + /> + ); +}; + +export const SingleDatePicker = DatePickerTemplate.bind({}); +SingleDatePicker.storyName = 'SingleDatePicker'; +SingleDatePicker.args = { + label: 'Select a date', + selectionType: 'single', +}; + +export const RangeDatePicker = DatePickerTemplate.bind({}); +RangeDatePicker.storyName = 'RangeDatePicker'; +RangeDatePicker.args = { + label: { start: 'Start Date', end: 'End Date' }, + selectionType: 'range', +}; + +export const DatePickerPresets: StoryFn = ({ ...args }) => { return ( - + In Range DatePicker you can pass presets which will render a + quick selection panel inside DatePicker for easy to use range selections + + + presets accepts an array of objects with label and{' '} + value properties. + + + Example: + + {` + [ { label: 'Past 7 days', value: (date) => [dayjs(date).subtract(7, 'days').toDate(), date]} ] + `} + + + + setIsOpen(isOpen)} - value={date} - labelPosition="top" onChange={(date) => { console.log(date); - setDate(date); }} - label={{ start: 'Start Date', end: 'End Date' }} presets={[ { - label: 'Past 3 days', - value: (date) => [dayjs(date).subtract(3, 'day').toDate(), date], + label: 'Past 7 days', + value: (date) => [dayjs(date).subtract(7, 'days').toDate(), date], }, { - label: 'Past 7 days', - value: (date) => [dayjs(date).subtract(7, 'day').toDate(), date], + label: 'Past 15 days', + value: (date) => [dayjs(date).subtract(15, 'days').toDate(), date], }, { - label: 'Past 30 days', - value: (date) => [dayjs(date).subtract(30, 'day').toDate(), date], + label: 'Past month', + value: (date) => [dayjs(date).subtract(1, 'month').toDate(), date], }, { - label: 'Last Year', + label: 'Past year', value: (date) => [dayjs(date).subtract(1, 'year').toDate(), date], }, ]} + {...args} + /> + + ); +}; + +DatePickerPresets.storyName = 'With Presets'; +DatePickerPresets.args = { + label: { start: 'Start Date', end: 'End Date' }, +}; + +export const DatePickerControlled: StoryFn = () => { + const [isOpen, setIsOpen] = React.useState(true); + const [date, setDate] = React.useState([ + new Date(), + dayjs().add(3, 'day').toDate(), + ]); + + return ( + + + With isOpen, value and associated + event handlers you can control the DatePicker. + + + + Selected: [{dayjs(date[0]).format('DD-MM-YYYY')}, {dayjs(date[1]).format('DD-MM-YYYY')}] + + IsOpen: {JSON.stringify(isOpen)} + + setIsOpen(isOpen)} + value={date} + onChange={(date) => { + setDate(date); + }} /> ); }; + +DatePickerControlled.storyName = 'Controlled DatePicker'; + +export const Validations: StoryFn = () => { + const [date, setDate] = React.useState([ + new Date(), + dayjs().add(3, 'day').toDate(), + ]); + const [hasError, setHasError] = React.useState(false); + + return ( + + + DatePicker supports all common Input props like validationState,{' '} + isRequired, isDisabled etc. + + { + setDate(date); + if (dayjs(date[1]).diff(date[0], 'day') > 3) { + setHasError(true); + } else { + setHasError(false); + } + }} + /> + + ); +}; + +Validations.storyName = 'Validations'; + +export const MinMaxDates: StoryFn = () => { + return ( + + + With minDate and maxDate props you can + set minimum and maximum dates that can be selected. + + + Example: + {`minDate={dayjs().subtract(1, 'week').toDate()}`} + {`maxDate={dayjs().add(1, 'week').toDate()}`} + + + + ); +}; + +MinMaxDates.storyName = 'MinMaxDates'; + +export const ExcludeDates: StoryFn = () => { + return ( + + + With excludeDate function you can exclude specific dates from + being selected. + + + Example, exclude weekends: + {`excludeDate={(date) => dayjs(date).day() === 0 || dayjs(date).day() === 6}`} + + dayjs(date).day() === 0 || dayjs(date).day() === 6} + /> + + ); +}; + +ExcludeDates.storyName = 'ExcludeDates'; + +export const LabelPositionLeft: StoryFn = () => { + return ( + + + The label prop accepts a string or an object{' '} + {`{start, end}`} depending on the + selectionType. When the{' '} + labelPositionLeft prop is set & selectionType is range, the label + will be rendered on the left with the {`{start}`} string. + + + + + + + ); +}; + +LabelPositionLeft.storyName = 'LabelPositionLeft'; + +export const MonthPicker: StoryFn = ({ ...args }) => { + return ( + + + By passing picker prop as month or{' '} + year you can render a month/year picker + + + You can also hook into onMonthSelect and onYearSelect events + + + Note: picker is only supported in single selection mode + + + + ); +}; + +MonthPicker.storyName = 'Month/Year Picker'; + +export const Localization: StoryFn = () => { + return ( + + + The DatePicker component supports localization using the i18nify-js{' '} + I18nProvider. You can pass the locale as a prop to the + I18nProvider to change the locale of the DatePicker. + + + + NOTE: Once you set the locale, you will also need to import the locale file from dayjs so + it takes effect. + + + import "dayjs/locale/fr"; + + + + + + + ); +}; + +Localization.storyName = 'Localization'; diff --git a/packages/blade/src/components/DatePicker/types.ts b/packages/blade/src/components/DatePicker/types.ts index 85f558c57ca..690c2976476 100644 --- a/packages/blade/src/components/DatePicker/types.ts +++ b/packages/blade/src/components/DatePicker/types.ts @@ -50,12 +50,26 @@ type CalendarProps = Pick< * @default 'date' */ picker?: PickerType; + /** + * Sets the default picker type + */ defaultPicker?: PickerType; + /** + * Callback which fires when picker type changes + */ onPickerChange?: (picker: PickerType) => void; - // Standard controlled/uncontrolled state props + /** + * Controlled isOpen state + */ isOpen?: boolean; + /** + * Uncontrolled isOpen state + */ defaultIsOpen?: boolean; + /** + * Callback which fires when isOpen state changes + */ onOpenChange?: ({ isOpen }: { isOpen: boolean }) => void; /** @@ -89,6 +103,9 @@ type CalendarProps = Pick< * Sets the maximum date that can be selected. */ maxDate?: Date; + /** + * Disables dates that do not pass the function. + */ excludeDate?: (date: Date) => boolean; /** * Determines whether single date can be selected as range, applicable only when type="range"