Skip to content

Commit

Permalink
add month picker
Browse files Browse the repository at this point in the history
  • Loading branch information
PixeledCode committed Jan 12, 2024
1 parent cbdcfba commit 915d33c
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export const CalendarRange: RangeStory = {
args: {},
}

export const YearRange: any = {
export const Year: any = {
render: ({ ...args }) => {
return <YearCalendar {...args} />
},
Expand Down
2 changes: 2 additions & 0 deletions packages/opub-ui/src/components/DateField/DateField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,12 @@ const DateField = (props: any) => {
} = props
let { locale } = useLocale()
let ref = React.useRef(null)

let state = useDateFieldState({
...others,
locale,
createCalendar,
// granularity: 'month',
})

let { labelProps, fieldProps } = useDateField(others, state, ref)
Expand Down
75 changes: 42 additions & 33 deletions packages/opub-ui/src/components/DatePicker/DatePicker.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,58 @@
import { DatePicker } from './DatePicker';
import { DateRangePicker } from './DateRangePicker';
import { getLocalTimeZone, today, parseDate } from '@internationalized/date';
import { Meta, StoryObj } from '@storybook/react';
import { DatePicker } from './DatePicker'
import { DateRangePicker } from './DateRangePicker'
import { MonthPicker } from './MonthPicker'
import { getLocalTimeZone, today, parseDate } from '@internationalized/date'
import { Meta, StoryObj } from '@storybook/react'

/**
* A date picker combines a DateField and a Calendar popover to allow users to enter or select a date and time value.
*
* Reference: https://react-spectrum.adobe.com/react-aria/useDatePicker.html
*/
const meta = {
title: 'Components/DatePicker',
component: DatePicker,
} satisfies Meta<typeof DatePicker>;
title: 'Components/DatePicker',
component: DatePicker,
} satisfies Meta<typeof DatePicker>

export default meta;
type Story = StoryObj<typeof meta>;
export default meta
type Story = StoryObj<typeof meta>

const metaRange = {
component: DateRangePicker,
} satisfies Meta<typeof DateRangePicker>;
type StoryRange = StoryObj<typeof metaRange>;
component: DateRangePicker,
} satisfies Meta<typeof DateRangePicker>
type StoryRange = StoryObj<typeof metaRange>

export const Default: Story = {
args: {
label: 'Date Picker',
},
};
args: {
label: 'Date Picker',
},
}

export const Range: StoryRange = {
render: ({ ...args }) => <DateRangePicker {...args} />,
args: {
label: 'Date Range Picker',
onChange: (val) => console.log(val),
},
};
render: ({ ...args }) => <DateRangePicker {...args} />,
args: {
label: 'Date Range Picker',
onChange: (val) => console.log(val),
},
}

export const Month: Story = {
render: ({ ...args }) => <MonthPicker {...args} />,
args: {
label: 'Month Picker',
onChange: (val) => console.log(val),
},
}

export const DisabledDates: StoryRange = {
...Range,
args: {
label: 'Date Picker',
minValue: today(getLocalTimeZone()),
defaultValue: {
start: parseDate('2022-02-03'),
end: parseDate('2022-05-03'),
},
errorMessage: 'Date must be in the future',
},
};
...Range,
args: {
label: 'Date Picker',
minValue: today(getLocalTimeZone()),
defaultValue: {
start: parseDate('2022-02-03'),
end: parseDate('2022-05-03'),
},
errorMessage: 'Date must be in the future',
},
}
102 changes: 102 additions & 0 deletions packages/opub-ui/src/components/DatePicker/MonthPicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
'use client'

import { YearCalendar } from '../Calendar/YearCalendar'
import { DateField } from '../DateField'
import { IconButton } from '../IconButton'
import inputStyles from '../Input/Input.module.scss'
import { Labelled, LabelledProps } from '../Labelled'
import { Popover } from '../Popover'
import styles from './DatePicker.module.scss'
import { DateValue } from '@react-types/calendar'
import { IconCalendar } from '@tabler/icons-react'
import cx from 'classnames'
import React from 'react'
import { AriaDatePickerProps, useDatePicker } from 'react-aria'
import { DatePickerState, useDatePickerState } from 'react-stately'

export type DatePickerProps = {
/** Label for the field */
label: string
/** Error to display beneath the label */
error?: any
/** Adds an action to the label */
labelAction?: LabelledProps['action']
/** Visually hide the label */
labelHidden?: boolean
/** Visual required indicator, add an asterisk to label */
requiredIndicator?: boolean
/** Additional text to aide in use */
helpText?: React.ReactNode
} & (DatePickerState | AriaDatePickerProps<DateValue>)

const MonthPicker = React.forwardRef(
(
{
error,
labelAction,
labelHidden,
helpText,
requiredIndicator,
...props
}: DatePickerProps,
ref: any
) => {
let state = useDatePickerState(props)

let { labelProps, fieldProps, buttonProps, dialogProps, calendarProps } =
useDatePicker(props, state, ref)
const themeClass = cx(styles.DatePicker)

const {
onPress: onPressPrev,
isDisabled: disabledPrev,
...othersProps
} = buttonProps

return (
<div className={`opub-MonthPicker ${themeClass}`}>
<Labelled
label={props.label}
{...labelProps}
error={error}
action={labelAction}
labelHidden={labelHidden}
helpText={helpText}
requiredIndicator={requiredIndicator}
>
<div ref={ref} className={styles.Wrapper}>
<DateField
{...fieldProps}
errorMessage={error}
isPicker
granularity="month"
/>
<Popover
onOpenChange={() =>
!state.isOpen ? state.open() : state.close()
}
open={state.isOpen}
{...dialogProps}
>
<Popover.Trigger>
<IconButton
{...othersProps}
icon={IconCalendar}
withTooltip={false}
>
trigger calendar
</IconButton>
</Popover.Trigger>
<Popover.Content>
<YearCalendar {...calendarProps} />
</Popover.Content>
</Popover>
<div className={inputStyles.Backdrop} />
</div>
</Labelled>
</div>
)
}
)

export { MonthPicker }

0 comments on commit 915d33c

Please sign in to comment.