Skip to content

Commit

Permalink
Migrate from dayjs to @internationalized/date, change initial Calenda…
Browse files Browse the repository at this point in the history
…r props and fix React18 focus bugs (finos#320)

Signed-off-by: joshwooding <12938082+joshwooding@users.noreply.github.com>
  • Loading branch information
joshwooding authored and junaidzm13 committed Jan 5, 2024
1 parent c69f54c commit c6440b1
Show file tree
Hide file tree
Showing 13 changed files with 357 additions and 380 deletions.
14 changes: 13 additions & 1 deletion Calendar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { forwardRef } from "react";
import { forwardRef, useCallback } from "react";
import classnames from "classnames";
import { makePrefixer } from "@jpmorganchase/uitk-core";
import {
Expand Down Expand Up @@ -37,6 +37,16 @@ export const Calendar = forwardRef<HTMLDivElement, CalendarProps>(

const { state, helpers } = useCalendar({ ...rest });

const { setCalendarFocused } = helpers;

const handleFocus = useCallback(() => {
setCalendarFocused(true);
}, [setCalendarFocused]);

const handleBlur = useCallback(() => {
setCalendarFocused(false);
}, [setCalendarFocused]);

return (
<CalendarContext.Provider
value={{
Expand All @@ -52,6 +62,8 @@ export const Calendar = forwardRef<HTMLDivElement, CalendarProps>(
<CalendarNavigation hideYearDropdown={hideYearDropdown} />
<CalendarWeekHeader />
<CalendarCarousel
onFocus={handleFocus}
onBlur={handleBlur}
renderDayContents={renderDayContents}
TooltipProps={TooltipProps}
/>
Expand Down
58 changes: 27 additions & 31 deletions internal/CalendarCarousel.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { forwardRef, useRef, useEffect, useState } from "react";
import dayjs from "./dayjs";
import {
DateValue,
getLocalTimeZone,
isSameMonth,
today,
} from "@internationalized/date";
import { CalendarMonth, CalendarMonthProps } from "./CalendarMonth";
import {
makePrefixer,
Expand All @@ -9,19 +14,18 @@ import { usePrevious } from "../../utils";
import { useCalendarContext } from "./CalendarContext";

import "./CalendarCarousel.css";
import { formatDate, monthDiff } from "./utils";

export type CalendarCarouselProps = Omit<CalendarMonthProps, "date">;

function getMonths(month: Date | undefined) {
return [
dayjs(month)
.startOf("month")
.subtract(1, "month")
.startOf("month")
.format("L"),
dayjs(month).startOf("month").format("L"),
dayjs(month).startOf("month").add(1, "month").startOf("month").format("L"),
];
function getMonths(month: DateValue) {
return [month.subtract({ months: 1 }), month, month.add({ months: 1 })];
}

function usePreviousMonth(visibleMonth: DateValue) {
const previous = usePrevious(visibleMonth, [formatDate(visibleMonth)]);

return previous ?? today(getLocalTimeZone());
}

const withBaseName = makePrefixer("uitkCalendarCarousel");
Expand All @@ -36,21 +40,18 @@ export const CalendarCarousel = forwardRef<
state: { visibleMonth },
} = useCalendarContext();
const containerRef = useRef<HTMLDivElement>(null);
const diffIndex = (a: Date | undefined, b: Date | undefined) =>
dayjs(a).diff(dayjs(b), "month");
const diffIndex = (a: DateValue, b: DateValue) => monthDiff(a, b);

const previousMonth = usePrevious(visibleMonth, [
dayjs(visibleMonth).format("L"),
]);
const { current: baseIndex } = useRef(visibleMonth);
const previousVisibleMonth = usePreviousMonth(visibleMonth);

useIsomorphicLayoutEffect(() => {
if (Math.abs(diffIndex(visibleMonth, previousMonth)) > 1) {
if (Math.abs(diffIndex(visibleMonth, previousVisibleMonth)) > 1) {
containerRef.current?.classList.remove(withBaseName("shouldAnimate"));
} else {
containerRef.current?.classList.add(withBaseName("shouldAnimate"));
}
}, [dayjs(visibleMonth).format("L"), dayjs(previousMonth).format("L")]);
}, [formatDate(visibleMonth), formatDate(previousVisibleMonth)]);

useIsomorphicLayoutEffect(() => {
if (containerRef.current) {
Expand All @@ -64,10 +65,11 @@ export const CalendarCarousel = forwardRef<

useEffect(() => {
setMonths((oldMonths) => {
const newMonths = getMonths(visibleMonth);
const monthSet = new Set(oldMonths.concat(newMonths));
const newMonths = getMonths(visibleMonth).filter((month) => {
return !oldMonths.find((oldMonth) => isSameMonth(oldMonth, month));
});

return Array.from(monthSet);
return oldMonths.concat(newMonths);
});
const finishTransition = () => {
setMonths(getMonths(visibleMonth));
Expand All @@ -88,7 +90,7 @@ export const CalendarCarousel = forwardRef<
}

return undefined;
}, [dayjs(visibleMonth).format("L")]); // eslint-disable-line react-hooks/exhaustive-deps
}, [formatDate(visibleMonth)]); // eslint-disable-line react-hooks/exhaustive-deps

return (
<div
Expand All @@ -102,20 +104,14 @@ export const CalendarCarousel = forwardRef<
<div className={withBaseName("track")} ref={containerRef}>
{months.map((date, index) => (
<div
key={dayjs(date).format("L")}
key={formatDate(date)}
className={withBaseName("slide")}
style={{
transform: `translateX(${
diffIndex(dayjs(date).toDate(), baseIndex) * 100
}%)`,
transform: `translateX(${diffIndex(date, baseIndex) * 100}%)`,
}}
aria-hidden={index !== 1 ? "true" : undefined}
>
<CalendarMonth
isVisible={index === 1}
{...rest}
date={dayjs(date).toDate()}
/>
<CalendarMonth isVisible={index === 1} {...rest} date={date} />
</div>
))}
</div>
Expand Down
40 changes: 16 additions & 24 deletions internal/CalendarDay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,22 @@ import {
} from "@jpmorganchase/uitk-core";
import { CloseIcon } from "@jpmorganchase/uitk-icons";
import cx from "classnames";
import {
ComponentPropsWithRef,
forwardRef,
ReactElement,
useCallback,
} from "react";
import { DayStatus, useCalendarDay } from "../useCalendarDay";
import dayjs from "./dayjs";
import { ComponentPropsWithRef, forwardRef, ReactElement, useRef } from "react";
import { DateValue } from "@internationalized/date";

import { DayStatus, useCalendarDay } from "../useCalendarDay";
import "./CalendarDay.css";
import { formatDate } from "./utils";

export type DateFormatter = (day: Date) => string | undefined;

export interface CalendarDayProps
extends Omit<ComponentPropsWithRef<"button">, "children"> {
day: Date;
day: DateValue;
formatDate?: DateFormatter;
renderDayContents?: (date: Date, status: DayStatus) => ReactElement;
renderDayContents?: (date: DateValue, status: DayStatus) => ReactElement;
status?: DayStatus;
month: Date;
month: DateValue;
TooltipProps?: Partial<TooltipProps>;
}

Expand All @@ -37,20 +33,22 @@ export const CalendarDay = forwardRef<HTMLButtonElement, CalendarDayProps>(
const { className, day, renderDayContents, month, TooltipProps, ...rest } =
props;

const { status, dayProps, registerDay, unselectableReason } =
useCalendarDay({
const dayRef = useRef<HTMLButtonElement>(null);
const { status, dayProps, unselectableReason } = useCalendarDay(
{
date: day,
month,
});

},
dayRef
);
const { outOfRange, today, unselectable, hidden } = status;

const { getTriggerProps, getTooltipProps } = useTooltip({
disabled: !unselectableReason,
});

const { ref: triggerRef, ...triggerProps } = getTriggerProps<"button">({
"aria-label": dayjs(day).format("dddd, LL"),
"aria-label": formatDate(day),
...dayProps,
...rest,
className: cx(
Expand All @@ -67,13 +65,7 @@ export const CalendarDay = forwardRef<HTMLButtonElement, CalendarDayProps>(
),
});

const registerDayRef = useCallback(
(node: HTMLButtonElement) => {
registerDay(day, node);
},
[registerDay, day]
);
const handleTriggerRef = useForkRef(triggerRef, registerDayRef);
const handleTriggerRef = useForkRef(triggerRef, dayRef);
const handleRef = useForkRef(handleTriggerRef, ref);

return (
Expand All @@ -97,7 +89,7 @@ export const CalendarDay = forwardRef<HTMLButtonElement, CalendarDayProps>(

{renderDayContents
? renderDayContents(day, status)
: dayjs(day).format("D")}
: formatDate(day, { day: "numeric" })}
</button>
</>
);
Expand Down
15 changes: 6 additions & 9 deletions internal/CalendarMonth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ import {
SyntheticEvent,
} from "react";
import cx from "classnames";
import dayjs from "./dayjs";
import { CalendarDay, CalendarDayProps } from "./CalendarDay";
import { generateVisibleDays } from "./calendarUtils";
import { makePrefixer } from "@jpmorganchase/uitk-core";
import { DateValue } from "@internationalized/date";
import { CalendarDay, CalendarDayProps } from "./CalendarDay";
import { formatDate, generateVisibleDays } from "./utils";

import "./CalendarMonth.css";
import { useCalendarContext } from "./CalendarContext";

export interface CalendarMonthProps extends ComponentPropsWithRef<"div"> {
date: Date;
date: DateValue;
hideOutOfRangeDates?: boolean;
renderDayContents?: CalendarDayProps["renderDayContents"];
isVisible?: boolean;
Expand All @@ -36,10 +36,7 @@ export const CalendarMonth = forwardRef<HTMLDivElement, CalendarMonthProps>(
...rest
} = props;

const month = dayjs(date).month();
const year = dayjs(date).year();

const days = generateVisibleDays(year, month);
const days = generateVisibleDays(date);
const {
helpers: { setHoveredDate },
} = useCalendarContext();
Expand All @@ -63,7 +60,7 @@ export const CalendarMonth = forwardRef<HTMLDivElement, CalendarMonthProps>(
{days.map((day) => {
return (
<CalendarDay
key={dayjs(day.date).format("L")}
key={formatDate(date)}
day={day.date}
renderDayContents={renderDayContents}
month={date}
Expand Down
Loading

0 comments on commit c6440b1

Please sign in to comment.