Skip to content

Instantly share code, notes, and snippets.

@tanpld
Last active June 26, 2024 16:39
Show Gist options
  • Save tanpld/2698a98f4d99fe5e639b8d668c15633c to your computer and use it in GitHub Desktop.
Save tanpld/2698a98f4d99fe5e639b8d668c15633c to your computer and use it in GitHub Desktop.
import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/24/solid";
import clsx from "clsx";
import { useState, useMemo } from "react";
interface YearCalendarProps {
availableYears?: number[];
defaultYear?: number;
onChange: (year: number) => void;
}
const MIN_YEAR = 1900;
const MAX_YEAR = 2099;
const VIEW_LENGTH = 12;
export default function YearCalendar({
availableYears,
defaultYear,
onChange,
}: YearCalendarProps) {
const currentYear = new Date().getFullYear();
const initialStartYear = useMemo(() => {
const baseYear = defaultYear || currentYear;
return Math.min(
Math.max(baseYear - (baseYear % 12), MIN_YEAR),
MAX_YEAR - (MAX_YEAR % 12)
);
}, [defaultYear, currentYear]);
const [startYear, setStartYear] = useState(initialStartYear);
const [selectedYear, setSelectedYear] = useState<number | null>(
defaultYear || availableYears?.[0] || currentYear
);
const years = useMemo(
() => Array.from({ length: VIEW_LENGTH }, (_, i) => startYear + i),
[startYear]
);
const goToPrevView = () =>
setStartYear((prevStartYear) =>
Math.max(prevStartYear - VIEW_LENGTH, MIN_YEAR)
);
const goToNextView = () =>
setStartYear((prevStartYear) =>
Math.min(prevStartYear + VIEW_LENGTH, MAX_YEAR - VIEW_LENGTH + 1)
);
const handleOnClick = (year: number) => {
setSelectedYear(year);
onChange(year);
};
const isSelected = (year: number) => year === selectedYear;
const isAvailable = (year: number) =>
availableYears ? availableYears.includes(year) : true;
return (
<div className="bg-gray-800 rounded-3xl w-[192px] py-5 px-6">
<div className="flex items-center justify-between px-2">
<button onClick={goToPrevView} aria-label="Previous Decade">
<ChevronLeftIcon className="size-4" />
</button>
<span className="text-lg font-bold text-amber-400">{selectedYear}</span>
<button onClick={goToNextView} aria-label="Next Decade">
<ChevronRightIcon className="size-4" />
</button>
</div>
<div className="grid grid-cols-3 mt-4">
{years.map((year) => (
<button
key={year}
className={clsx(
"size-12 text-sm flex items-center justify-center rounded-full",
isSelected(year) && "bg-emerald-600 text-white",
isAvailable(year)
? "cursor-pointer hover:bg-gray-600 hover:text-white"
: "cursor-not-allowed opacity-50"
)}
onClick={() => isAvailable(year) && handleOnClick(year)}
aria-label={`Select Year ${year}`}
>
{year}
</button>
))}
</div>
</div>
);
}
@tanpld
Copy link
Author

tanpld commented Jun 26, 2024

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment