Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(calendar): add custom cell content to Calendar #3554

Open
wants to merge 30 commits into
base: canary
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
a24b443
feat(calendar): add custom cell content
1amageek Jun 24, 2024
487be5e
refactor(calendar-cell): add useRef hook to improve performance
1amageek Jun 24, 2024
7a0fa5c
refactor: improve CalendarCell by destructuring props and adding type…
1amageek Jun 25, 2024
a449568
test(calendar): add test for custom cell content in Calendar and Rang…
1amageek Jun 25, 2024
0218de1
chore: add changeset for custom cell content feature in Calendar and …
1amageek Jun 25, 2024
4d40ddd
refactor: use shared template component for custom cell content in ca…
1amageek Jun 29, 2024
c0f2b34
feat: add calendar width and custom cell templates for calendar and r…
1amageek Jul 25, 2024
448ab55
feat(calendar): add renderCellContent prop for custom cell content
1amageek Jul 25, 2024
9445d2b
feat(calendar): move renderCellContent to context
1amageek Nov 22, 2024
930d4af
feat(calendar): add cell components and context management
1amageek Nov 23, 2024
9fc7840
feat(calendar): implement custom cell content rendering
1amageek Nov 23, 2024
b327e40
feat(docs): add custom cell content examples for calendar components
1amageek Nov 23, 2024
b2fcf12
fix(theme): correct typo in calendar cellBody height class
1amageek Nov 23, 2024
39dd82c
Merge branch 'canary' into canary
1amageek Nov 23, 2024
0f22c26
feat(calendar): add day of week calculation to CalendarCell component
1amageek Nov 23, 2024
2ae85a9
refactor(calendar): improve calendar cell components and remove unuse…
1amageek Nov 23, 2024
01b3ba2
feat(calendar): update components to use CalendarCellHeader
1amageek Nov 23, 2024
874af0b
refactor(theme): update gridBodyRow styles for better spacing
1amageek Nov 23, 2024
e814634
refactor(calendar): integrate CalendarCellHeader into calendar cell c…
1amageek Nov 23, 2024
24ccded
feat(calendar): add support for custom cell content in calendar compo…
1amageek Nov 23, 2024
1cd1d21
feat(calendar): add role and aria-label to birthday events
1amageek Nov 27, 2024
8902936
feat(calendar): add calendar cell components and context support
1amageek Dec 7, 2024
84a85d7
feat(calendar): add calendar cell components and context support
1amageek Dec 7, 2024
deb9dfc
ci(changesets): version packages
jrgarciadev Dec 7, 2024
7dd0840
Merge pull request #1 from 1amageek/changeset-release/canary
1amageek Dec 7, 2024
60892c4
Add support for custom cell content in Calendar components
1amageek Dec 7, 2024
5e2da7c
Merge branch 'canary' into canary
1amageek Dec 7, 2024
215eb51
Merge branch 'canary' of https://github.com/nextui-org/nextui into ca…
1amageek Dec 12, 2024
fdc5337
Merge branch 'canary' into canary
1amageek Dec 24, 2024
81d0422
Update packages/components/calendar/src/calendar-cell-header.tsx
1amageek Dec 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/popular-seals-appear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nextui-org/calendar": minor
---

Added the `renderCellContent` prop to the `Calendar` and `RangeCalendar` components, allowing developers to specify custom content for each calendar cell. Updated the existing tests and added new test cases to cover the custom cell content functionality.
24 changes: 24 additions & 0 deletions packages/components/calendar/__tests__/calendar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -464,4 +464,28 @@ describe("Calendar", () => {
expect(year).toHaveAttribute("data-value", "2567");
});
});

describe("Custom cell content", () => {
1amageek marked this conversation as resolved.
Show resolved Hide resolved
it("should render custom content in the calendar cells", () => {
const renderCellContent = (date: CalendarDate) => (
<div>
{date.day}
<span>*</span>
</div>
);

const wrapper = render(
<Calendar
defaultValue={new CalendarDate(2024, 3, 31)}
renderCellContent={renderCellContent}
/>,
);

const gridCells = wrapper.getAllByRole("gridcell");
const customContentCell = gridCells.find((cell) => cell.textContent === "31*");

expect(customContentCell).not.toBeNull();
expect(customContentCell).toHaveTextContent("31*");
});
});
});
27 changes: 27 additions & 0 deletions packages/components/calendar/__tests__/range-calendar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -748,4 +748,31 @@ describe("RangeCalendar", () => {
expect(end).toEqual(new CalendarDate(2019, 6, 25));
});
});

describe("Custom cell content", () => {
it("should render custom content in the range calendar cells", () => {
const renderCellContent = (date: CalendarDate) => (
<div>
{date.day}
<span>*</span>
</div>
);

const wrapper = render(
<RangeCalendar
defaultValue={{start: new CalendarDate(2024, 6, 25), end: new CalendarDate(2024, 6, 26)}}
renderCellContent={renderCellContent}
/>,
);

const gridCells = wrapper.getAllByRole("gridcell");
const customContentCellA = gridCells.find((cell) => cell.textContent === "25*");
const customContentCellB = gridCells.find((cell) => cell.textContent === "26*");

expect(customContentCellA).not.toBeNull();
expect(customContentCellA).toHaveTextContent("25*");
expect(customContentCellB).not.toBeNull();
expect(customContentCellB).toHaveTextContent("26*");
});
});
});
29 changes: 29 additions & 0 deletions packages/components/calendar/src/calendar-cell-body.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type {HTMLNextUIProps} from "@nextui-org/system";

import React from "react";

import {useCalendarContext} from "./calendar-context";

interface Props extends HTMLNextUIProps<"div"> {
children: React.ReactNode;
}

export type CalendarCellBodyProps = Props;

export const CalendarCellBody = React.forwardRef<HTMLDivElement, CalendarCellBodyProps>(
({children, ...props}, ref) => {
const {slots, classNames} = useCalendarContext();
const bodyProps = {
...props,
ref: ref,
className: slots?.cellBody({class: classNames?.cellBody}),
"data-slot": "cell-body",
};

return <div {...bodyProps}>{children}</div>;
},
);

CalendarCellBody.displayName = "NextUI.CalendarCellBody";

export default CalendarCellBody;
74 changes: 74 additions & 0 deletions packages/components/calendar/src/calendar-cell-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import type {HTMLNextUIProps} from "@nextui-org/system";

import {useRef} from "react";
import {useHover} from "@react-aria/interactions";
import {useFocusRing} from "@react-aria/focus";
import {mergeProps} from "@react-aria/utils";
import {dataAttr} from "@nextui-org/shared-utils";

import {useCalendarCell} from "./calendar-cell-context";
import {useCalendarContext} from "./calendar-context";

export interface CalendarCellButtonProps extends HTMLNextUIProps<"div"> {
children?: React.ReactNode;
}

export const CalendarCellButton = ({children, ...props}: CalendarCellButtonProps) => {
const ref = useRef<HTMLDivElement>(null);
const {slots, classNames} = useCalendarContext();
const {
state,
buttonProps,
isSelected,
isDisabled,
isInvalid,
isOutsideMonth,
isToday,
isUnavailable,
isRangeSelection,
isRangeStart,
isRangeEnd,
isSelectionStart,
isSelectionEnd,
} = useCalendarCell();

const {focusProps, isFocusVisible} = useFocusRing();
const {hoverProps, isHovered} = useHover({
isDisabled: isDisabled || isUnavailable || state.isReadOnly,
});

// Extract pressed from buttonProps using optional chaining
const isPressed = buttonProps["aria-pressed"] === true;

const {date} = useCalendarCell();

return (
<div
{...mergeProps(buttonProps, hoverProps, focusProps, props)}
ref={ref}
className={slots?.cellButton({class: classNames?.cellButton})}
data-disabled={dataAttr(isDisabled && !isInvalid)}
data-focus-visible={dataAttr(isFocusVisible)}
data-hover={dataAttr(isHovered)}
data-invalid={dataAttr(isInvalid)}
data-outside-month={dataAttr(isOutsideMonth)}
data-pressed={dataAttr(isPressed && !state.isReadOnly)}
data-range-end={dataAttr(isRangeEnd)}
data-range-selection={dataAttr(isRangeSelection)}
data-range-start={dataAttr(isRangeStart)}
data-readonly={dataAttr(state.isReadOnly)}
data-selected={dataAttr(isSelected)}
data-selection-end={dataAttr(isSelectionEnd)}
data-selection-start={dataAttr(isSelectionStart)}
data-slot="cell-button"
data-today={dataAttr(isToday)}
data-unavailable={dataAttr(isUnavailable)}
>
{children ? children : date.day}
1amageek marked this conversation as resolved.
Show resolved Hide resolved
</div>
);
};

CalendarCellButton.displayName = "NextUI.CalendarCellButton";

export default CalendarCellButton;
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from "react";
import {CalendarDate} from "@internationalized/date";

import {CalendarCellContent} from "./calendar-cell-content";
import {CalendarCellButton} from "./calendar-cell-button";

export interface CalendarCellContentDefaultProps {
date: CalendarDate;
}

export const CalendarCellContentDefault: React.FC<CalendarCellContentDefaultProps> = ({date}) => {
return (
<CalendarCellContent>
<CalendarCellButton>{date.day}</CalendarCellButton>
</CalendarCellContent>
);
};

CalendarCellContentDefault.displayName = "NextUI.CalendarCellContentDefault";
25 changes: 25 additions & 0 deletions packages/components/calendar/src/calendar-cell-content.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type {HTMLNextUIProps} from "@nextui-org/system";

import {useCalendarContext} from "./calendar-context";

export interface CalendarCellContentProps extends HTMLNextUIProps<"div"> {
children: React.ReactNode;
}

export const CalendarCellContent = ({children, ...props}: CalendarCellContentProps) => {
const {slots, classNames} = useCalendarContext();

return (
<div
className={slots?.cellContent({class: classNames?.cellContent})}
data-slot="cell-content"
{...props}
>
{children}
</div>
);
};

CalendarCellContent.displayName = "NextUI.CalendarCellContent";

export default CalendarCellContent;
31 changes: 31 additions & 0 deletions packages/components/calendar/src/calendar-cell-context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type {CalendarState, RangeCalendarState} from "@react-stately/calendar";
import type {CalendarDate} from "@internationalized/date";
import type {DOMAttributes} from "@react-types/shared";

import {createContext} from "@nextui-org/react-utils";

export interface CalendarCellContextType {
date: CalendarDate;
state: CalendarState | RangeCalendarState;
buttonProps: DOMAttributes;
isSelected: boolean;
isDisabled: boolean;
isInvalid: boolean;
isUnavailable: boolean;
isOutsideMonth: boolean;
isToday: boolean;
isPressable: boolean;
isRangeSelection: boolean;
isRangeStart: boolean;
isRangeEnd: boolean;
isSelectionStart: boolean;
isSelectionEnd: boolean;
formattedDate: string;
}

export const [CalendarCellProvider, useCalendarCell] = createContext<CalendarCellContextType>({
name: "CalendarCellContext",
strict: true,
errorMessage:
"useCalendarCell: `context` is undefined. Seems you forgot to wrap component within the CalendarCellProvider",
});
Loading