83 lines
2.6 KiB
TypeScript
83 lines
2.6 KiB
TypeScript
import { memo } from "react";
|
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
|
import { cn } from "@/lib/utils";
|
|
import { DEFAULT_CELL_SIZE, SMALL_CELL_SIZE } from "./constants";
|
|
import type { CalendarDayCell, CalendarSize } from "./types";
|
|
import { getCalendarCellStateClass, getCellIntensityClass } from "./utils";
|
|
|
|
export interface CalendarCellProps {
|
|
day: CalendarDayCell;
|
|
maxCount: number;
|
|
tooltipText: string;
|
|
onClick?: (date: string) => void;
|
|
size?: CalendarSize;
|
|
disableTooltip?: boolean;
|
|
}
|
|
|
|
export const CalendarCell = memo((props: CalendarCellProps) => {
|
|
const { day, maxCount, tooltipText, onClick, size = "default", disableTooltip = false } = props;
|
|
|
|
const handleClick = () => {
|
|
if (onClick) {
|
|
onClick(day.date);
|
|
}
|
|
};
|
|
|
|
const sizeConfig = size === "small" ? SMALL_CELL_SIZE : DEFAULT_CELL_SIZE;
|
|
const smallExtraClasses = size === "small" ? `${SMALL_CELL_SIZE.dimensions} min-h-0` : "";
|
|
|
|
const baseClasses = cn(
|
|
"aspect-square w-full flex items-center justify-center text-center transition-all duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40 focus-visible:ring-offset-2 select-none border border-border/10 bg-muted/20",
|
|
sizeConfig.font,
|
|
sizeConfig.borderRadius,
|
|
smallExtraClasses,
|
|
);
|
|
const isInteractive = Boolean(onClick);
|
|
const ariaLabel = day.isSelected ? `${tooltipText} (selected)` : tooltipText;
|
|
|
|
if (!day.isCurrentMonth) {
|
|
return <div className={cn(baseClasses, "text-muted-foreground/30 bg-transparent border-transparent cursor-default")}>{day.label}</div>;
|
|
}
|
|
|
|
const intensityClass = getCellIntensityClass(day, maxCount);
|
|
|
|
const buttonClasses = cn(
|
|
baseClasses,
|
|
intensityClass,
|
|
getCalendarCellStateClass(day),
|
|
day.isToday && "underline decoration-1 underline-offset-4",
|
|
isInteractive ? "cursor-pointer hover:bg-muted/40 hover:border-border/30 hover:text-foreground" : "cursor-default",
|
|
);
|
|
|
|
const button = (
|
|
<button
|
|
type="button"
|
|
onClick={handleClick}
|
|
tabIndex={isInteractive ? 0 : -1}
|
|
aria-label={ariaLabel}
|
|
aria-current={day.isToday ? "date" : undefined}
|
|
aria-disabled={!isInteractive}
|
|
className={buttonClasses}
|
|
>
|
|
{day.label}
|
|
</button>
|
|
);
|
|
|
|
const shouldShowTooltip = tooltipText && !disableTooltip;
|
|
|
|
if (!shouldShowTooltip) {
|
|
return button;
|
|
}
|
|
|
|
return (
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>{button}</TooltipTrigger>
|
|
<TooltipContent side="top">
|
|
<p>{tooltipText}</p>
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
);
|
|
});
|
|
|
|
CalendarCell.displayName = "CalendarCell";
|