UNPKG

@lobehub/ui

Version:

Lobe UI is an open-source UI component library for building AIGC web apps

188 lines (187 loc) • 7.36 kB
"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