Skip to content
Merged
Changes from all commits
Commits
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
116 changes: 74 additions & 42 deletions apps/web/app/shared/ui/calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import * as React from "react";
import {
DayPicker,
getDefaultClassNames,
useDayPicker,
type CustomComponents,
type DayButton,
type Locale,
} from "react-day-picker";
Expand All @@ -14,6 +16,74 @@ import {
ChevronDownIcon,
} from "lucide-react";

/**
* The `components` overrides live out here rather than inline in `Calendar`'s
* render, which is where shadcn's generator puts them.
*
* An arrow function written inside the render body is a new component type on
* every pass, and DayPicker keys its subtree on these identities: re-rendering
* the calendar unmounts and remounts the whole grid rather than updating it.
* That is not only wasted work — `CalendarDayButton` holds a ref and an effect
* that focuses the day matching `modifiers.focused`, and a remount drops the
* focus it had just moved. The range picker re-renders on every click, so this
* ran on the path a keyboard user is actually using.
*/
const CalendarRoot: CustomComponents["Root"] = ({
className,
rootRef,
...props
}) => {
return (
<div
data-slot="calendar"
ref={rootRef}
className={cn(className)}
{...props}
/>
);
};

const CalendarChevron: CustomComponents["Chevron"] = ({
className,
orientation,
...props
}) => {
if (orientation === "left") {
return <ChevronLeftIcon className={cn("size-4", className)} {...props} />;
}

if (orientation === "right") {
return <ChevronRightIcon className={cn("size-4", className)} {...props} />;
}

return <ChevronDownIcon className={cn("size-4", className)} {...props} />;
};

/**
* Reads the locale back out of DayPicker's own context instead of closing over
* `Calendar`'s prop, which is what forced this one to be defined inline. It is
* the same value: `Calendar` hands `locale` to `DayPicker`, and this renders
* inside it.
*/
const CalendarDayButtonSlot: CustomComponents["DayButton"] = (props) => {
const { dayPickerProps } = useDayPicker();

return <CalendarDayButton locale={dayPickerProps.locale} {...props} />;
};

const CalendarWeekNumber: CustomComponents["WeekNumber"] = ({
children,
...props
}) => {
return (
<td {...props}>
<div className="flex size-(--cell-size) items-center justify-center text-center">
{children}
</div>
</td>
);
};

function Calendar({
className,
classNames,
Expand Down Expand Up @@ -136,48 +206,10 @@ function Calendar({
...classNames,
}}
components={{
Root: ({ className, rootRef, ...props }) => {
return (
<div
data-slot="calendar"
ref={rootRef}
className={cn(className)}
{...props}
/>
);
},
Chevron: ({ className, orientation, ...props }) => {
if (orientation === "left") {
return (
<ChevronLeftIcon className={cn("size-4", className)} {...props} />
);
}

if (orientation === "right") {
return (
<ChevronRightIcon
className={cn("size-4", className)}
{...props}
/>
);
}

return (
<ChevronDownIcon className={cn("size-4", className)} {...props} />
);
},
DayButton: ({ ...props }) => (
<CalendarDayButton locale={locale} {...props} />
),
WeekNumber: ({ children, ...props }) => {
return (
<td {...props}>
<div className="flex size-(--cell-size) items-center justify-center text-center">
{children}
</div>
</td>
);
},
Root: CalendarRoot,
Chevron: CalendarChevron,
DayButton: CalendarDayButtonSlot,
WeekNumber: CalendarWeekNumber,
...components,
}}
{...props}
Expand Down
Loading