@lobehub/ui
Version:
Lobe UI is an open-source UI component library for building AIGC web apps
188 lines (187 loc) • 7.36 kB
JavaScript
"use client";
import Icon from "../../Icon/Icon.mjs";
import { useTranslation } from "../../i18n/useTranslation.mjs";
import { panelStyles } from "../panelStyles.mjs";
import datePicker_default from "../../i18n/resources/en/datePicker.mjs";
import { buildMonthGrid, getWeekStart, getWeekdayLabels, isDayDisabled, isMonthDisabled, isSameDay, isYearDisabled, moveDayFocus, orderRange } from "./calendar.mjs";
import { styles } from "./style.mjs";
import { memo, useEffect, useRef, useState } from "react";
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
import { cx } from "antd-style";
import { ChevronLeft, ChevronRight } from "lucide-react";
import dayjs from "dayjs";
//#region src/base-ui/DatePicker/CalendarPanel.tsx
const MONTHS = Array.from({ length: 12 }, (_, index) => index);
const CalendarPanel = memo(({ bounds, hovered, mode, month, onHover, onMonthChange, onSelect, range, selected, showNav = true }) => {
const { t } = useTranslation(datePicker_default);
const [view, setView] = useState(mode);
const [focused, setFocused] = useState(selected ?? month);
const gridRef = useRef(null);
const weekStart = getWeekStart();
const today = /* @__PURE__ */ new Date();
const current = dayjs(month);
const decadeStart = Math.floor(current.year() / 12) * 12;
const days = buildMonthGrid(month, weekStart);
const focusDay = dayjs(focused).isSame(month, "month") && !isDayDisabled(focused, bounds) ? focused : days.find((day) => dayjs(day).isSame(month, "month") && !isDayDisabled(day, bounds)) ?? current.startOf("month").toDate();
const keyboardMoveRef = useRef(false);
useEffect(() => {
if (!keyboardMoveRef.current) return;
keyboardMoveRef.current = false;
gridRef.current?.querySelector("[data-focus-target]")?.focus();
}, [focused, month]);
const step = (direction) => {
const unit = view === "date" ? "month" : "year";
const amount = view === "year" ? 12 * direction : direction;
onMonthChange(current.add(amount, unit).toDate());
};
const drillUp = () => setView(view === "date" ? "month" : "year");
const handleDayKey = (event) => {
let next = moveDayFocus(focusDay, event.key, weekStart);
if (!next) return;
event.preventDefault();
for (let step = 0; next && isDayDisabled(next, bounds) && step < 366; step++) next = moveDayFocus(next, event.key, weekStart);
if (!next || isDayDisabled(next, bounds)) return;
keyboardMoveRef.current = true;
setFocused(next);
if (!dayjs(next).isSame(month, "month")) onMonthChange(next);
};
const band = (() => {
if (!range?.[0]) return null;
const end = range[1] ?? hovered;
return end ? orderRange(range[0], end) : null;
})();
const renderDays = () => {
return /* @__PURE__ */ jsxs("div", {
"aria-label": current.format("MMMM YYYY"),
className: styles.grid,
ref: gridRef,
role: "grid",
onMouseLeave: () => onHover?.(null),
children: [getWeekdayLabels(weekStart).map((label) => /* @__PURE__ */ jsx("span", {
className: cx(styles.weekday, panelStyles.label),
role: "columnheader",
children: label
}, label)), days.map((date) => {
const outside = !dayjs(date).isSame(month, "month");
const isStart = !outside && !!band && isSameDay(date, band[0]);
const isEnd = !outside && !!band && isSameDay(date, band[1]);
const inBand = !outside && !!band && dayjs(date).isAfter(band[0], "day") && dayjs(date).isBefore(band[1], "day");
const pressed = !outside && (isSameDay(date, selected) || isStart || isEnd);
const focusTarget = isSameDay(date, focusDay) && !outside;
return /* @__PURE__ */ jsx("span", {
"aria-selected": pressed,
role: "gridcell",
className: cx(styles.cell, inBand && styles.band, isStart && !isEnd && styles.bandStart, isEnd && !isStart && styles.bandEnd),
children: /* @__PURE__ */ jsx("button", {
"aria-label": dayjs(date).format("MMMM D, YYYY"),
"aria-pressed": pressed,
className: styles.day,
"data-focus-target": focusTarget ? "" : void 0,
"data-outside": outside ? "" : void 0,
"data-today": isSameDay(date, today) ? "" : void 0,
disabled: isDayDisabled(date, bounds),
tabIndex: focusTarget ? 0 : -1,
type: "button",
onClick: () => onSelect(dayjs(date).startOf("day").toDate()),
onKeyDown: handleDayKey,
onMouseEnter: () => onHover?.(date),
children: date.getDate()
})
}, date.toISOString());
})]
});
};
const renderMonths = () => /* @__PURE__ */ jsx("div", {
className: styles.tiles,
children: MONTHS.map((index) => {
const date = current.month(index).startOf("month").toDate();
return /* @__PURE__ */ jsx("button", {
"aria-pressed": !!selected && dayjs(selected).isSame(date, "month"),
className: styles.tile,
"data-today": dayjs(today).isSame(date, "month") ? "" : void 0,
disabled: isMonthDisabled(date, bounds),
type: "button",
onClick: () => {
if (mode === "month") return onSelect(date);
onMonthChange(date);
setView("date");
},
children: dayjs(date).format("MMM")
}, index);
})
});
const renderYears = () => /* @__PURE__ */ jsx("div", {
className: styles.tiles,
children: MONTHS.map((index) => {
const date = dayjs(new Date(decadeStart + index, 0, 1)).toDate();
return /* @__PURE__ */ jsx("button", {
"aria-pressed": !!selected && dayjs(selected).isSame(date, "year"),
className: styles.tile,
"data-today": dayjs(today).isSame(date, "year") ? "" : void 0,
disabled: isYearDisabled(date, bounds),
type: "button",
onClick: () => {
if (mode === "year") return onSelect(date);
onMonthChange(current.year(decadeStart + index).toDate());
setView("month");
},
children: decadeStart + index
}, index);
})
});
const title = (() => {
if (view === "date") return /* @__PURE__ */ jsxs(Fragment$1, { children: [
current.format("MMMM"),
" ",
/* @__PURE__ */ jsx("span", {
className: panelStyles.titleMuted,
children: current.format("YYYY")
})
] });
if (view === "month") return current.format("YYYY");
return `${decadeStart} – ${decadeStart + 11}`;
})();
return /* @__PURE__ */ jsxs("div", {
className: styles.calendar,
children: [
/* @__PURE__ */ jsxs("div", {
className: styles.header,
children: [/* @__PURE__ */ jsx("button", {
className: panelStyles.title,
disabled: view === "year",
type: "button",
onClick: drillUp,
children: title
}), showNav && /* @__PURE__ */ jsxs("div", {
className: styles.navGroup,
children: [/* @__PURE__ */ jsx("button", {
"aria-label": t("datePicker.previous"),
className: panelStyles.nav,
type: "button",
onClick: () => step(-1),
children: /* @__PURE__ */ jsx(Icon, {
icon: ChevronLeft,
size: 14
})
}), /* @__PURE__ */ jsx("button", {
"aria-label": t("datePicker.next"),
className: panelStyles.nav,
type: "button",
onClick: () => step(1),
children: /* @__PURE__ */ jsx(Icon, {
icon: ChevronRight,
size: 14
})
})]
})]
}),
view === "date" && renderDays(),
view === "month" && renderMonths(),
view === "year" && renderYears()
]
});
});
CalendarPanel.displayName = "CalendarPanel";
//#endregion
export { CalendarPanel as default };
//# sourceMappingURL=CalendarPanel.mjs.map