From dbb3342434ff7bb60ae88c90127a63ca8458eff4 Mon Sep 17 00:00:00 2001 From: Wojciech Maj Date: Mon, 25 Sep 2023 10:27:16 +0200 Subject: [PATCH] Add support for tileAs prop Closes #876 --- packages/react-calendar/README.md | 3 ++ packages/react-calendar/src/Calendar.tsx | 28 ++++++++++++------- packages/react-calendar/src/Tile.spec.tsx | 14 +++++++++- packages/react-calendar/src/Tile.tsx | 15 ++++++---- .../react-calendar/src/shared/propTypes.ts | 1 + packages/react-calendar/src/shared/types.ts | 8 +++++- 6 files changed, 52 insertions(+), 17 deletions(-) diff --git a/packages/react-calendar/README.md b/packages/react-calendar/README.md index 887d3c4d..47b1f5c6 100644 --- a/packages/react-calendar/README.md +++ b/packages/react-calendar/README.md @@ -144,6 +144,7 @@ Displays a complete, interactive calendar. | showNeighboringMonth | Whether days from previous or next month shall be rendered if the month doesn't start on the first day of the week or doesn't end on the last day of the week, respectively. | `true` | `false` | | selectRange | Whether the user shall select two dates forming a range instead of just one. **Note**: This feature will make react-calendar return array with two dates regardless of returnValue setting. | `false` | `true` | | showWeekNumbers | Whether week numbers shall be shown at the left of MonthView or not. | `false` | `true` | +| tileAs | Component used for rendering calendar tiles. | `"button"` | | | tileClassName | Class name(s) that will be applied to a given calendar item (day on month view, month on year view and so on). | n/a | | | tileContent | Allows to render custom content within a given calendar item (day on month view, month on year view and so on). | n/a | | | tileDisabled | Pass a function to determine if a certain day should be displayed as disabled. | n/a | | @@ -163,8 +164,10 @@ Displays a given month, year, decade and a century, respectively. | maxDate | Maximum date that the user can select. Periods partially overlapped by maxDate will also be selectable, although react-calendar will ensure that no later date is selected. | n/a | Date: `new Date()` | | minDate | Minimum date that the user can select. Periods partially overlapped by minDate will also be selectable, although react-calendar will ensure that no earlier date is selected. | n/a | Date: `new Date()` | | onClick | Function called when the user clicks an item (day on month view, month on year view and so on). | n/a | `(value) => alert('New date is: ', value)` | +| tileAs | Component used for rendering calendar tiles. | `"button"` | | | tileClassName | Class name(s) that will be applied to a given calendar item (day on month view, month on year view and so on). | n/a | | | tileContent | Allows to render custom content within a given item (day on month view, month on year view and so on). **Note**: For tiles with custom content you might want to set fixed height of `react-calendar__tile` to ensure consistent layout. | n/a | `({ date, view }) => view === 'month' && date.getDay() === 0 ?

It's Sunday!

: null` | +| tileDisabled | Pass a function to determine if a certain day should be displayed as disabled. | n/a | | | value | Calendar value. Can be either one value or an array of two values. | n/a | | ## Useful links diff --git a/packages/react-calendar/src/Calendar.tsx b/packages/react-calendar/src/Calendar.tsx index ffdd6227..dee907d2 100644 --- a/packages/react-calendar/src/Calendar.tsx +++ b/packages/react-calendar/src/Calendar.tsx @@ -31,6 +31,7 @@ import type { LooseValue, NavigationLabelFunc, OnArgs, + OnClickEventType, OnClickFunc, OnClickWeekNumberFunc, Range, @@ -60,7 +61,7 @@ defaultMinDate.setFullYear(1, 0, 1); defaultMinDate.setHours(0, 0, 0, 0); const defaultMaxDate = new Date(8.64e15); -export type CalendarProps = { +export type CalendarProps = { activeStartDate?: Date; allowPartialRange?: boolean; calendarType?: CalendarType | DeprecatedCalendarType; @@ -90,12 +91,12 @@ export type CalendarProps = { nextAriaLabel?: string; nextLabel?: React.ReactNode; onActiveStartDateChange?: ({ action, activeStartDate, value, view }: OnArgs) => void; - onChange?: (value: Value, event: React.MouseEvent) => void; - onClickDay?: OnClickFunc; - onClickDecade?: OnClickFunc; - onClickMonth?: OnClickFunc; + onChange?: (value: Value, event: OnClickEventType) => void; + onClickDay?: OnClickFunc; + onClickDecade?: OnClickFunc; + onClickMonth?: OnClickFunc; onClickWeekNumber?: OnClickWeekNumberFunc; - onClickYear?: OnClickFunc; + onClickYear?: OnClickFunc; onDrillDown?: ({ action, activeStartDate, value, view }: OnArgs) => void; onDrillUp?: ({ action, activeStartDate, value, view }: OnArgs) => void; onViewChange?: ({ action, activeStartDate, value, view }: OnArgs) => void; @@ -110,6 +111,7 @@ export type CalendarProps = { showNavigation?: boolean; showNeighboringMonth?: boolean; showWeekNumbers?: boolean; + tileAs?: T; tileClassName?: TileClassNameFunc | ClassName; tileContent?: TileContentFunc | React.ReactNode; tileDisabled?: TileDisabledFunc; @@ -293,7 +295,10 @@ function areDatesEqual(date1?: Date | null, date2?: Date | null) { return date1 instanceof Date && date2 instanceof Date && date1.getTime() === date2.getTime(); } -const Calendar = forwardRef(function Calendar(props: CalendarProps, ref) { +const Calendar = forwardRef(function Calendar( + props: CalendarProps, + ref, +) { const { activeStartDate: activeStartDateProps, allowPartialRange, @@ -344,6 +349,7 @@ const Calendar = forwardRef(function Calendar(props: CalendarProps, ref) { showNavigation = true, showNeighboringMonth = true, showWeekNumbers, + tileAs, tileClassName, tileContent, tileDisabled, @@ -457,7 +463,7 @@ const Calendar = forwardRef(function Calendar(props: CalendarProps, ref) { ); const onClickTile = useCallback( - (value: Date, event: React.MouseEvent) => { + (value: Date, event: OnClickEventType) => { const callback = (() => { switch (view) { case 'century': @@ -479,7 +485,7 @@ const Calendar = forwardRef(function Calendar(props: CalendarProps, ref) { ); const drillDown = useCallback( - (nextActiveStartDate: Date, event: React.MouseEvent) => { + (nextActiveStartDate: Date, event: OnClickEventType) => { if (!drillDownAvailable) { return; } @@ -573,7 +579,7 @@ const Calendar = forwardRef(function Calendar(props: CalendarProps, ref) { ]); const onChange = useCallback( - (rawNextValue: Date, event: React.MouseEvent) => { + (rawNextValue: Date, event: OnClickEventType) => { const previousValue = value; onClickTile(rawNextValue, event); @@ -712,6 +718,7 @@ const Calendar = forwardRef(function Calendar(props: CalendarProps, ref) { minDate, onClick, onMouseOver: selectRange ? onMouseOver : undefined, + tileAs, tileClassName, tileContent, tileDisabled, @@ -871,6 +878,7 @@ Calendar.propTypes = { showNavigation: PropTypes.bool, showNeighboringMonth: PropTypes.bool, showWeekNumbers: PropTypes.bool, + tileAs: PropTypes.oneOfType([PropTypes.func, PropTypes.oneOf(['button', 'div'] as const)]), tileClassName: PropTypes.oneOfType([PropTypes.func, isClassName]), tileContent: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), tileDisabled: PropTypes.func, diff --git a/packages/react-calendar/src/Tile.spec.tsx b/packages/react-calendar/src/Tile.spec.tsx index 1d44595a..f452e0c9 100644 --- a/packages/react-calendar/src/Tile.spec.tsx +++ b/packages/react-calendar/src/Tile.spec.tsx @@ -15,12 +15,24 @@ describe(' component', () => { view: 'month', } satisfies React.ComponentProps; - it('renders button properly', () => { + it('renders button properly by default', () => { const { container } = render(); expect(container.querySelector('button')).toBeInTheDocument(); }); + it('renders button given tileAs="button"', () => { + const { container } = render(); + + expect(container.querySelector('button')).toBeInTheDocument(); + }); + + it('renders div given tileAs="div"', () => { + const { container } = render(); + + expect(container.querySelector('div')).toBeInTheDocument(); + }); + it('passes onClick to button', () => { const onClick = vi.fn(); diff --git a/packages/react-calendar/src/Tile.tsx b/packages/react-calendar/src/Tile.tsx index 3e51f728..a2099ae0 100644 --- a/packages/react-calendar/src/Tile.tsx +++ b/packages/react-calendar/src/Tile.tsx @@ -3,13 +3,14 @@ import clsx from 'clsx'; import type { ClassName, + OnClickEventType, TileClassNameFunc, TileContentFunc, TileDisabledFunc, View, } from './shared/types.js'; -type TileProps = { +type TileProps = { activeStartDate: Date; children: React.ReactNode; classes?: string[]; @@ -20,16 +21,17 @@ type TileProps = { maxDateTransform: (date: Date) => Date; minDate?: Date; minDateTransform: (date: Date) => Date; - onClick?: (date: Date, event: React.MouseEvent) => void; + onClick?: (date: Date, event: OnClickEventType) => void; onMouseOver?: (date: Date) => void; style?: React.CSSProperties; + tileAs?: T; tileClassName?: TileClassNameFunc | ClassName; tileContent?: TileContentFunc | React.ReactNode; tileDisabled?: TileDisabledFunc; view: View; }; -export default function Tile(props: TileProps) { +export default function Tile(props: TileProps) { const { activeStartDate, children, @@ -44,6 +46,7 @@ export default function Tile(props: TileProps) { onClick, onMouseOver, style, + tileAs, tileClassName: tileClassNameProps, tileContent: tileContentProps, tileDisabled, @@ -62,8 +65,10 @@ export default function Tile(props: TileProps) { return typeof tileContentProps === 'function' ? tileContentProps(args) : tileContentProps; }, [activeStartDate, date, tileContentProps, view]); + const TileComponent = tileAs || 'button'; + return ( - + ); } diff --git a/packages/react-calendar/src/shared/propTypes.ts b/packages/react-calendar/src/shared/propTypes.ts index 87a15a85..3f8b65f5 100644 --- a/packages/react-calendar/src/shared/propTypes.ts +++ b/packages/react-calendar/src/shared/propTypes.ts @@ -155,6 +155,7 @@ export const tileProps = { onClick: PropTypes.func, onMouseOver: PropTypes.func, style: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])), + tileAs: PropTypes.oneOfType([PropTypes.func, PropTypes.oneOf(['button', 'div'] as const)]), tileClassName: PropTypes.oneOfType([PropTypes.func, isClassName]), tileContent: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), tileDisabled: PropTypes.func, diff --git a/packages/react-calendar/src/shared/types.ts b/packages/react-calendar/src/shared/types.ts index 4d5fb24d..d36af8e0 100644 --- a/packages/react-calendar/src/shared/types.ts +++ b/packages/react-calendar/src/shared/types.ts @@ -45,8 +45,14 @@ export type OnArgs = { value: Value; view: View; }; +export type OnClickType = React.ComponentPropsWithoutRef['onClick']; -export type OnClickFunc = (value: Date, event: React.MouseEvent) => void; +export type OnClickEventType = Parameters>[1]; + +export type OnClickFunc = ( + value: Date, + event: OnClickEventType, +) => void; export type OnClickWeekNumberFunc = ( weekNumber: number,