import "./index.css"; import { getLocalizedString } from "@olenbetong/appframe-core"; import clsx from "clsx"; import { useEffect, useRef, useState } from "react"; function getUiCulture() { return af?.userSession?.culture ?? "default"; } function Arrow({ direction, ...rest }: { direction: "left" | "right" } & React.ButtonHTMLAttributes) { return ( ); } const formatter = new Intl.DateTimeFormat(getUiCulture(), { weekday: "short", day: "numeric", month: "short", year: "numeric", timeZone: "utc", }); const oneDay = 24 * 60 * 60 * 1000; export type DateNavigatorProps = { date?: Date | string | number; defaultDate?: Date | string | number; // For default uncontrolled value disableTodayButton?: boolean; onChange?: (date: Date) => void; minDate?: Date | string | number; maxDate?: Date | string | number; }; function sanitizeDate(date: Date | string | number): Date { if (typeof date === "string" || typeof date === "number") { let parsedDate = new Date(date); if (Number.isNaN(parsedDate.getTime())) { throw new Error(`Invalid date value: ${date}`); } return parsedDate; } else if (date instanceof Date) { if (Number.isNaN(date.getTime())) { throw new Error(`Invalid date object: ${date}`); } return new Date(date.getTime()); // Return a copy of the date } else { throw new Error(`Unsupported date type: ${typeof date}`); } } export function DateNavigator({ date: dateProp, defaultDate, disableTodayButton, onChange, minDate: minDateProp, maxDate: maxDateProp, }: DateNavigatorProps) { let [internalDate, setInternalDate] = useState(() => (defaultDate ? sanitizeDate(defaultDate) : new Date())); let date = dateProp ? sanitizeDate(dateProp) : internalDate; let dateUtc = Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()); let today = new Date(); let todayUtc = Date.UTC(today.getFullYear(), today.getMonth(), today.getDate()); let liveRef = useRef(null); let text = (() => { let base = formatter.format(new Date(dateUtc)); if (todayUtc === dateUtc) return `${getLocalizedString("Today")}, ${base}`; if (todayUtc === dateUtc + oneDay) return `${getLocalizedString("Yesterday")}, ${base}`; if (todayUtc === dateUtc - oneDay) return `${getLocalizedString("Tomorrow")}, ${base}`; return base; })(); let minDate = minDateProp && sanitizeDate(minDateProp); let maxDate = maxDateProp && sanitizeDate(maxDateProp); let disablePrev = !!minDate && dateUtc <= Date.UTC(minDate.getFullYear(), minDate.getMonth(), minDate.getDate()); let disableNext = !!maxDate && dateUtc >= Date.UTC(maxDate.getFullYear(), maxDate.getMonth(), maxDate.getDate()); function updateDate(newDate: Date) { if (minDate && newDate < minDate) { newDate = new Date(minDate); // Ensure we don't go below minDate } else if (maxDate && newDate > maxDate) { newDate = new Date(maxDate); // Ensure we don't go above maxDate } onChange?.(newDate); setInternalDate(newDate); } function jumpToToday() { updateDate(new Date()); } useEffect(() => { if (liveRef.current) { liveRef.current.textContent = text; } }, [text]); function handleKeyDown(event: React.KeyboardEvent) { if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return; if (event.defaultPrevented) return; if (event.altKey || event.metaKey) return; // Ignore if alt or meta key is pressed if (disablePrev && event.key === "ArrowLeft") return; if (disableNext && event.key === "ArrowRight") return; if (event.ctrlKey && event.shiftKey) { // If both ctrl and shift are pressed, navigate by 1 year let newDate = new Date(dateUtc); if (event.key === "ArrowLeft") { newDate.setFullYear(newDate.getFullYear() - 1); } else if (event.key === "ArrowRight") { newDate.setFullYear(newDate.getFullYear() + 1); } updateDate(newDate); return; } if (event.ctrlKey) { // Navigate by month let newDate = new Date(dateUtc); if (event.key === "ArrowLeft") { newDate.setMonth(newDate.getMonth() - 1); } else if (event.key === "ArrowRight") { newDate.setMonth(newDate.getMonth() + 1); } updateDate(newDate); return; } let modifier = event.shiftKey ? 7 : 1; if (event.key === "ArrowLeft") { updateDate(new Date(dateUtc - oneDay * modifier)); } else if (event.key === "ArrowRight") { updateDate(new Date(dateUtc + oneDay * modifier)); } } return ( // oxlint-disable-next-line jsx-a11y/no-noninteractive-tabindex -- I hate this component // oxlint-disable-next-line jsx-a11y/no-static-element-interactions -- legacy component // oxlint-disable-next-line jsx-a11y/role-supports-aria-props -- legacy component
updateDate(new Date(dateUtc - oneDay))} disabled={disablePrev} />
{text}
updateDate(new Date(dateUtc + oneDay))} disabled={disableNext} /> {!disableTodayButton && ( )}
); }