UNPKG

react-calendar-plus

Version:

A flexible, themeable, and internationalized React calendar component with date and time selection. Includes month/week/day views, range selection, and custom event handling.

1,234 lines (1,222 loc) 45.5 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { Calendar: () => Calendar, CalendarDay: () => CalendarDay, CalendarHeader: () => CalendarHeader, CalendarInput: () => CalendarInput, DEFAULT_LOCALE: () => DEFAULT_LOCALE, TimePicker: () => TimePicker, YearMonthSelector: () => YearMonthSelector, combineDateTime: () => combineDateTime, formatDate: () => formatDate, formatDateForDisplay: () => formatDateForDisplay, getCalendarDays: () => getCalendarDays, getMonthNames: () => getMonthNames, getViewTitle: () => getViewTitle, getWeekDays: () => getWeekDays, getWeekNumber: () => getWeekNumber, getYearRange: () => getYearRange, isDateDisabled: () => isDateDisabled, isDateInRange: () => isDateInRange, isDateSelected: () => isDateSelected, navigateDate: () => navigateDate, navigateToMonth: () => navigateToMonth, navigateToYear: () => navigateToYear, navigateToYearMonth: () => navigateToYearMonth, parseDateTime: () => parseDateTime }); module.exports = __toCommonJS(index_exports); // src/components/Calendar.tsx var import_react3 = require("react"); var import_date_fns5 = require("date-fns"); var import_locale4 = require("date-fns/locale"); var import_clsx5 = __toESM(require("clsx")); // src/components/CalendarHeader.tsx var import_clsx2 = __toESM(require("clsx")); // src/utils/dateUtils.ts var import_date_fns = require("date-fns"); var import_locale = require("date-fns/locale"); var DEFAULT_LOCALE = { locale: "en-US", weekStartsOn: 0, // Sunday formatOptions: { weekday: "short", month: "long", day: "numeric" } }; var getCalendarDays = (date, locale = DEFAULT_LOCALE) => { const start = (0, import_date_fns.startOfWeek)((0, import_date_fns.startOfMonth)(date), { weekStartsOn: locale.weekStartsOn }); const end = (0, import_date_fns.endOfWeek)((0, import_date_fns.endOfMonth)(date), { weekStartsOn: locale.weekStartsOn }); return (0, import_date_fns.eachDayOfInterval)({ start, end }); }; var getWeekDays = (date, locale = DEFAULT_LOCALE) => { const start = (0, import_date_fns.startOfWeek)(date, { weekStartsOn: locale.weekStartsOn }); const end = (0, import_date_fns.endOfWeek)(date, { weekStartsOn: locale.weekStartsOn }); return (0, import_date_fns.eachDayOfInterval)({ start, end }); }; var formatDate = (date, formatString) => { return (0, import_date_fns.format)(date, formatString, { locale: import_locale.enUS }); }; var formatDateForDisplay = (date, locale = DEFAULT_LOCALE) => { return new Intl.DateTimeFormat(locale.locale, locale.formatOptions).format(date); }; var isDateInRange = (date, range) => { if (!range.start || !range.end) return false; return (0, import_date_fns.isWithinInterval)(date, { start: range.start, end: range.end }); }; var isDateSelected = (date, selectedValue) => { if (!selectedValue) return false; if (selectedValue instanceof Date) { return (0, import_date_fns.isSameDay)(date, selectedValue); } if (Array.isArray(selectedValue)) { return selectedValue.some((selectedDate) => (0, import_date_fns.isSameDay)(date, selectedDate)); } if (selectedValue.start && selectedValue.end) { return isDateInRange(date, selectedValue); } else if (selectedValue.start) { return (0, import_date_fns.isSameDay)(date, selectedValue.start); } return false; }; var isDateDisabled = (date, minDate, maxDate, disabledDates, disabledDaysOfWeek, disableWeekends) => { if (minDate && date < minDate) return true; if (maxDate && date > maxDate) return true; if (disabledDates && disabledDates.some((disabledDate) => (0, import_date_fns.isSameDay)(date, disabledDate))) { return true; } const dayOfWeek = (0, import_date_fns.getDay)(date); if (disabledDaysOfWeek && disabledDaysOfWeek.includes(dayOfWeek)) { return true; } if (disableWeekends && (dayOfWeek === 0 || dayOfWeek === 6)) { return true; } return false; }; var getViewTitle = (date, view) => { switch (view) { case "month": return (0, import_date_fns.format)(date, "MMMM yyyy", { locale: import_locale.enUS }); case "week": const weekStart = (0, import_date_fns.startOfWeek)(date); const weekEnd = (0, import_date_fns.endOfWeek)(date); if ((0, import_date_fns.isSameMonth)(weekStart, weekEnd)) { return `${(0, import_date_fns.format)(weekStart, "MMM d", { locale: import_locale.enUS })} - ${(0, import_date_fns.format)(weekEnd, "d, yyyy", { locale: import_locale.enUS })}`; } else { return `${(0, import_date_fns.format)(weekStart, "MMM d", { locale: import_locale.enUS })} - ${(0, import_date_fns.format)(weekEnd, "MMM d, yyyy", { locale: import_locale.enUS })}`; } case "day": return (0, import_date_fns.format)(date, "EEEE, MMMM d, yyyy", { locale: import_locale.enUS }); default: return (0, import_date_fns.format)(date, "MMMM yyyy", { locale: import_locale.enUS }); } }; var navigateDate = (date, direction, view) => { switch (view) { case "month": return direction === "previous" ? (0, import_date_fns.subMonths)(date, 1) : (0, import_date_fns.addMonths)(date, 1); case "week": return direction === "previous" ? (0, import_date_fns.subWeeks)(date, 1) : (0, import_date_fns.addWeeks)(date, 1); case "day": return direction === "previous" ? (0, import_date_fns.subDays)(date, 1) : (0, import_date_fns.addDays)(date, 1); default: return date; } }; var parseDateTime = (dateTimeString) => { try { const parsed = (0, import_date_fns.parseISO)(dateTimeString); return (0, import_date_fns.isValid)(parsed) ? parsed : null; } catch { return null; } }; var combineDateTime = (date, time) => { const newDate = new Date(date); newDate.setHours(time.getHours(), time.getMinutes(), time.getSeconds()); return newDate; }; var getWeekNumber = (date) => { return (0, import_date_fns.getWeek)(date); }; var navigateToYear = (date, year) => { return (0, import_date_fns.setYear)(date, year); }; var navigateToMonth = (date, month) => { return (0, import_date_fns.setMonth)(date, month); }; var navigateToYearMonth = (date, year, month) => { return (0, import_date_fns.setMonth)((0, import_date_fns.setYear)(date, year), month); }; var getYearRange = (centerYear, range = 50) => { return Array.from({ length: range * 2 + 1 }, (_, i) => centerYear - range + i); }; var getMonthNames = () => { return Array.from( { length: 12 }, (_, i) => (0, import_date_fns.format)(new Date(2024, i, 1), "MMMM", { locale: import_locale.enUS }) ); }; // src/components/YearMonthSelector.tsx var import_react = require("react"); var import_clsx = __toESM(require("clsx")); var import_date_fns2 = require("date-fns"); var import_locale2 = require("date-fns/locale"); var import_jsx_runtime = require("react/jsx-runtime"); var YearMonthSelector = ({ currentDate, onDateChange, minDate, maxDate, className }) => { const [isYearOpen, setIsYearOpen] = (0, import_react.useState)(false); const [isMonthOpen, setIsMonthOpen] = (0, import_react.useState)(false); const yearRef = (0, import_react.useRef)(null); const monthRef = (0, import_react.useRef)(null); const currentYear = (0, import_date_fns2.getYear)(currentDate); const currentMonth = (0, import_date_fns2.getMonth)(currentDate); const yearOptions = Array.from({ length: 101 }, (_, i) => currentYear - 50 + i); const monthOptions = Array.from({ length: 12 }, (_, i) => ({ value: i, label: (0, import_date_fns2.format)(new Date(2024, i, 1), "MMMM", { locale: import_locale2.enUS }) })); const filteredYearOptions = yearOptions.filter((year) => { if (minDate && year < (0, import_date_fns2.getYear)(minDate)) return false; if (maxDate && year > (0, import_date_fns2.getYear)(maxDate)) return false; return true; }); const filteredMonthOptions = monthOptions.filter(({ value }) => { const testDate = new Date(currentYear, value, 1); if (minDate && testDate < minDate) return false; if (maxDate && testDate > maxDate) return false; return true; }); (0, import_react.useEffect)(() => { const handleClickOutside = (event) => { if (yearRef.current && !yearRef.current.contains(event.target)) { setIsYearOpen(false); } if (monthRef.current && !monthRef.current.contains(event.target)) { setIsMonthOpen(false); } }; document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, []); const handleYearChange = (year) => { const newDate = (0, import_date_fns2.setYear)(currentDate, year); onDateChange(newDate); setIsYearOpen(false); }; const handleMonthChange = (month) => { const newDate = (0, import_date_fns2.setMonth)(currentDate, month); onDateChange(newDate); setIsMonthOpen(false); }; return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: (0, import_clsx.default)("rcp-year-month-selector", className), children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rcp-year-month-selector__group", ref: monthRef, children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)( "button", { type: "button", className: "rcp-year-month-selector__button", onClick: () => setIsMonthOpen(!isMonthOpen), "aria-label": "Select month", children: [ (0, import_date_fns2.format)(currentDate, "MMMM", { locale: import_locale2.enUS }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)( "svg", { className: (0, import_clsx.default)( "rcp-year-month-selector__arrow", isMonthOpen && "rcp-year-month-selector__arrow--open" ), width: "12", height: "12", viewBox: "0 0 12 12", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M3 4.5L6 7.5L9 4.5", stroke: "currentColor", strokeWidth: "1.5", fill: "none", strokeLinecap: "round", strokeLinejoin: "round" }) } ) ] } ), isMonthOpen && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rcp-year-month-selector__dropdown", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rcp-year-month-selector__dropdown-content", children: filteredMonthOptions.map(({ value, label }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)( "button", { type: "button", className: (0, import_clsx.default)( "rcp-year-month-selector__option", currentMonth === value && "rcp-year-month-selector__option--selected" ), onClick: () => handleMonthChange(value), children: label }, value )) }) }) ] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rcp-year-month-selector__group", ref: yearRef, children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)( "button", { type: "button", className: "rcp-year-month-selector__button", onClick: () => setIsYearOpen(!isYearOpen), "aria-label": "Select year", children: [ currentYear, /* @__PURE__ */ (0, import_jsx_runtime.jsx)( "svg", { className: (0, import_clsx.default)( "rcp-year-month-selector__arrow", isYearOpen && "rcp-year-month-selector__arrow--open" ), width: "12", height: "12", viewBox: "0 0 12 12", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M3 4.5L6 7.5L9 4.5", stroke: "currentColor", strokeWidth: "1.5", fill: "none", strokeLinecap: "round", strokeLinejoin: "round" }) } ) ] } ), isYearOpen && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rcp-year-month-selector__dropdown", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rcp-year-month-selector__dropdown-content rcp-year-month-selector__dropdown-content--years", children: filteredYearOptions.map((year) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)( "button", { type: "button", className: (0, import_clsx.default)( "rcp-year-month-selector__option", currentYear === year && "rcp-year-month-selector__option--selected" ), onClick: () => handleYearChange(year), children: year }, year )) }) }) ] }) ] }); }; // src/components/CalendarHeader.tsx var import_jsx_runtime2 = require("react/jsx-runtime"); var CalendarHeader = ({ currentDate, view, onPrevious, onNext, onViewChange, onDateChange, minDate, maxDate, className }) => { const viewOptions = [ { value: "month", label: "Month" }, { value: "week", label: "Week" }, { value: "day", label: "Day" } ]; return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: (0, import_clsx2.default)("rcp-calendar-header", className), children: [ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "rcp-calendar-header__navigation", children: [ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( "button", { type: "button", onClick: onPrevious, className: "rcp-calendar-header__nav-button", "aria-label": "Previous", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M10.5 13L5.5 8L10.5 3", stroke: "currentColor", strokeWidth: "2", fill: "none", strokeLinecap: "round", strokeLinejoin: "round" }) }) } ), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "rcp-calendar-header__title-section", children: view === "month" && onDateChange ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( YearMonthSelector, { currentDate, onDateChange, minDate, maxDate, className: "rcp-calendar-header__year-month-selector" } ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { className: "rcp-calendar-header__title", children: getViewTitle(currentDate, view) }) }), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( "button", { type: "button", onClick: onNext, className: "rcp-calendar-header__nav-button", "aria-label": "Next", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M5.5 3L10.5 8L5.5 13", stroke: "currentColor", strokeWidth: "2", fill: "none", strokeLinecap: "round", strokeLinejoin: "round" }) }) } ) ] }), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "rcp-calendar-header__view-selector", children: viewOptions.map(({ value, label }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( "button", { type: "button", onClick: () => onViewChange(value), className: (0, import_clsx2.default)( "rcp-calendar-header__view-button", view === value && "rcp-calendar-header__view-button--active" ), children: label }, value )) }) ] }); }; // src/components/CalendarDay.tsx var import_date_fns3 = require("date-fns"); var import_clsx3 = __toESM(require("clsx")); var import_jsx_runtime3 = require("react/jsx-runtime"); var CalendarDay = ({ date, currentMonth, selectedValue, events, onDateClick, dayRenderer, minDate, maxDate, disabledDates, disabledDaysOfWeek, disableWeekends, showOtherMonths = true, className }) => { const isCurrentDay = (0, import_date_fns3.isToday)(date); const isSelected = isDateSelected(date, selectedValue); const isDisabled = isDateDisabled( date, minDate, maxDate, disabledDates, disabledDaysOfWeek, disableWeekends ); const isOutOfMonth = !(0, import_date_fns3.isSameMonth)(date, currentMonth); const dayEvents = events.filter( (event) => event.start <= date && (!event.end || event.end >= date) ); const handleClick = (event) => { if (isDisabled) return; onDateClick(date, event); }; const dayProps = { date, isToday: isCurrentDay, isSelected, isInRange: false, // This will be handled by the parent for range selection isDisabled, isOutOfMonth, events: dayEvents, onClick: (date2) => onDateClick(date2, {}) }; if (dayRenderer) { return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: dayRenderer(dayProps) }); } if (isOutOfMonth && !showOtherMonths) { return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "rcp-calendar-day rcp-calendar-day--empty" }); } return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)( "div", { className: (0, import_clsx3.default)( "rcp-calendar-day", { "rcp-calendar-day--today": isCurrentDay, "rcp-calendar-day--selected": isSelected, "rcp-calendar-day--disabled": isDisabled, "rcp-calendar-day--out-of-month": isOutOfMonth, "rcp-calendar-day--has-events": dayEvents.length > 0 }, className ), onClick: handleClick, role: "button", tabIndex: isDisabled ? -1 : 0, "aria-label": `${date.getDate()} ${date.toLocaleDateString()}`, "aria-selected": isSelected, "aria-disabled": isDisabled, onKeyDown: (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); handleClick(e); } }, children: [ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "rcp-calendar-day__number", children: date.getDate() }), dayEvents.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "rcp-calendar-day__events", children: [ dayEvents.slice(0, 3).map((event, index) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)( "div", { className: "rcp-calendar-day__event", style: { backgroundColor: event.color, color: event.textColor }, title: event.title, children: event.title }, event.id || index )), dayEvents.length > 3 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "rcp-calendar-day__event-more", children: [ "+", dayEvents.length - 3, " more" ] }) ] }) ] } ); }; // src/components/TimePicker.tsx var import_react2 = require("react"); var import_date_fns4 = require("date-fns"); var import_locale3 = require("date-fns/locale"); var import_clsx4 = __toESM(require("clsx")); var import_jsx_runtime4 = require("react/jsx-runtime"); var TimePicker = ({ value, onChange, format: timeFormat = "24h", minuteStep = 1, disabled = false, className }) => { const [hours, setHours] = (0, import_react2.useState)(value ? value.getHours() : 0); const [minutes, setMinutes] = (0, import_react2.useState)(value ? value.getMinutes() : 0); const [period, setPeriod] = (0, import_react2.useState)("AM"); (0, import_react2.useEffect)(() => { if (value) { const date = new Date(value); setHours(date.getHours()); setMinutes(date.getMinutes()); setPeriod(date.getHours() >= 12 ? "PM" : "AM"); } }, [value]); const handleTimeChange = (newHours, newMinutes) => { if (disabled) return; const date = new Date(value || /* @__PURE__ */ new Date()); date.setHours(newHours, newMinutes, 0, 0); onChange?.(date); }; const handleHourChange = (newHours) => { let adjustedHours = newHours; if (timeFormat === "12h") { if (period === "PM" && newHours !== 12) { adjustedHours = newHours + 12; } else if (period === "AM" && newHours === 12) { adjustedHours = 0; } } setHours(adjustedHours); handleTimeChange(adjustedHours, minutes); }; const handleMinuteChange = (newMinutes) => { setMinutes(newMinutes); handleTimeChange(hours, newMinutes); }; const handlePeriodChange = (newPeriod) => { setPeriod(newPeriod); let adjustedHours = hours; if (newPeriod === "PM" && hours < 12) { adjustedHours = hours + 12; } else if (newPeriod === "AM" && hours >= 12) { adjustedHours = hours - 12; } setHours(adjustedHours); handleTimeChange(adjustedHours, minutes); }; const displayHours = timeFormat === "12h" ? hours === 0 ? 12 : hours > 12 ? hours - 12 : hours : hours; const hourOptions = Array.from( { length: timeFormat === "12h" ? 12 : 24 }, (_, i) => timeFormat === "12h" ? i + 1 : i ); const minuteOptions = Array.from( { length: Math.floor(60 / minuteStep) }, (_, i) => i * minuteStep ); return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: (0, import_clsx4.default)("rcp-time-picker", className), children: [ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "rcp-time-picker__container", children: [ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "rcp-time-picker__field", children: [ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { className: "rcp-time-picker__label", children: "Hour" }), /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( "select", { value: displayHours, onChange: (e) => handleHourChange(parseInt(e.target.value)), disabled, className: "rcp-time-picker__select", children: hourOptions.map((hour) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: hour, children: hour.toString().padStart(2, "0") }, hour)) } ) ] }), /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "rcp-time-picker__separator", children: ":" }), /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "rcp-time-picker__field", children: [ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { className: "rcp-time-picker__label", children: "Minute" }), /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( "select", { value: minutes, onChange: (e) => handleMinuteChange(parseInt(e.target.value)), disabled, className: "rcp-time-picker__select", children: minuteOptions.map((minute) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: minute, children: minute.toString().padStart(2, "0") }, minute)) } ) ] }), timeFormat === "12h" && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "rcp-time-picker__field", children: [ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { className: "rcp-time-picker__label", children: "Period" }), /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)( "select", { value: period, onChange: (e) => handlePeriodChange(e.target.value), disabled, className: "rcp-time-picker__select", children: [ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "AM", children: "AM" }), /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "PM", children: "PM" }) ] } ) ] }) ] }), /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "rcp-time-picker__display", children: value && (0, import_date_fns4.format)(value, timeFormat === "12h" ? "h:mm a" : "HH:mm", { locale: import_locale3.enUS }) }) ] }); }; // src/components/Calendar.tsx var import_jsx_runtime5 = require("react/jsx-runtime"); var Calendar = ({ value, defaultValue, onChange, view: controlledView, defaultView = "month", onViewChange, selectionMode = "single", showTimePicker = false, timeFormat = "24h", minuteStep = 1, locale = DEFAULT_LOCALE, className, theme, onDateClick, onRangeSelect, onMonthChange, onWeekChange, onDayChange, dayRenderer, headerRenderer, minDate, maxDate, disabledDates, disabledDaysOfWeek, disableWeekends, events = [], responsive = true, showWeekNumbers = false, showOtherMonths = true }) => { const [currentView, setCurrentView] = (0, import_react3.useState)(controlledView || defaultView); const [currentDate, setCurrentDate] = (0, import_react3.useState)(/* @__PURE__ */ new Date()); const [selectedValue, setSelectedValue] = (0, import_react3.useState)(value || defaultValue || null); const [rangeStart, setRangeStart] = (0, import_react3.useState)(null); const [hoveredDate, setHoveredDate] = (0, import_react3.useState)(null); (0, import_react3.useEffect)(() => { if (controlledView !== void 0) { setCurrentView(controlledView); } }, [controlledView]); (0, import_react3.useEffect)(() => { if (value !== void 0) { setSelectedValue(value); } }, [value]); const displayDays = (0, import_react3.useMemo)(() => { switch (currentView) { case "month": return getCalendarDays(currentDate, locale); case "week": return getWeekDays(currentDate, locale); case "day": return [currentDate]; default: return getCalendarDays(currentDate, locale); } }, [currentDate, currentView, locale]); const weekHeaders = (0, import_react3.useMemo)(() => { const startOfCurrentWeek = (0, import_date_fns5.startOfWeek)(/* @__PURE__ */ new Date(), { weekStartsOn: locale.weekStartsOn }); return getWeekDays(startOfCurrentWeek, locale); }, [locale]); const handleViewChange = (newView) => { setCurrentView(newView); onViewChange?.(newView); switch (newView) { case "month": onMonthChange?.(currentDate); break; case "week": onWeekChange?.((0, import_date_fns5.startOfWeek)(currentDate, { weekStartsOn: locale.weekStartsOn })); break; case "day": onDayChange?.(currentDate); break; } }; const handlePrevious = () => { const newDate = navigateDate(currentDate, "previous", currentView); setCurrentDate(newDate); switch (currentView) { case "month": onMonthChange?.(newDate); break; case "week": onWeekChange?.((0, import_date_fns5.startOfWeek)(newDate, { weekStartsOn: locale.weekStartsOn })); break; case "day": onDayChange?.(newDate); break; } }; const handleNext = () => { const newDate = navigateDate(currentDate, "next", currentView); setCurrentDate(newDate); switch (currentView) { case "month": onMonthChange?.(newDate); break; case "week": onWeekChange?.((0, import_date_fns5.startOfWeek)(newDate, { weekStartsOn: locale.weekStartsOn })); break; case "day": onDayChange?.(newDate); break; } }; const handleDateChange = (newDate) => { setCurrentDate(newDate); switch (currentView) { case "month": onMonthChange?.(newDate); break; case "week": onWeekChange?.((0, import_date_fns5.startOfWeek)(newDate, { weekStartsOn: locale.weekStartsOn })); break; case "day": onDayChange?.(newDate); break; } }; const handleDateClick = (date, event) => { onDateClick?.(date, event); let newValue = null; switch (selectionMode) { case "single": newValue = date; if (showTimePicker && selectedValue instanceof Date) { newValue = combineDateTime(date, selectedValue); } break; case "multiple": if (Array.isArray(selectedValue)) { const exists = selectedValue.find((d) => (0, import_date_fns5.isSameDay)(d, date)); if (exists) { newValue = selectedValue.filter((d) => !(0, import_date_fns5.isSameDay)(d, date)); } else { newValue = [...selectedValue, date]; } } else { newValue = [date]; } break; case "range": if (!rangeStart || rangeStart && hoveredDate) { setRangeStart(date); setHoveredDate(null); newValue = { start: date, end: null }; } else { const start = rangeStart < date ? rangeStart : date; const end = rangeStart < date ? date : rangeStart; newValue = { start, end }; setRangeStart(null); onRangeSelect?.({ start, end }); } break; } setSelectedValue(newValue); onChange?.(newValue); }; const handleTimeChange = (time) => { if (selectedValue instanceof Date) { const newValue = combineDateTime(selectedValue, time); setSelectedValue(newValue); onChange?.(newValue); } }; const isInRange = (date) => { if (selectionMode !== "range") return false; if (selectedValue && typeof selectedValue === "object" && "start" in selectedValue) { return isDateInRange(date, selectedValue); } if (rangeStart && hoveredDate) { const start = rangeStart < hoveredDate ? rangeStart : hoveredDate; const end = rangeStart < hoveredDate ? hoveredDate : rangeStart; return isDateInRange(date, { start, end }); } return false; }; const renderWeekNumber = (date) => { if (!showWeekNumbers) return null; return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "rcp-calendar__week-number", children: getWeekNumber(date) }); }; const renderCalendarGrid = () => { const weeks = []; let currentWeek = []; displayDays.forEach((day, index) => { currentWeek.push(day); if (currentWeek.length === 7 || index === displayDays.length - 1) { weeks.push([...currentWeek]); currentWeek = []; } }); return weeks.map((week, weekIndex) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "rcp-calendar__week", children: [ showWeekNumbers && renderWeekNumber(week[0]), week.map((day) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)( CalendarDay, { date: day, currentMonth: currentDate, selectedValue: Array.isArray(selectedValue) ? selectedValue : selectedValue instanceof Date ? selectedValue : null, events, onDateClick: handleDateClick, dayRenderer, minDate, maxDate, disabledDates, disabledDaysOfWeek, disableWeekends, showOtherMonths, className: (0, import_clsx5.default)( isInRange(day) && "rcp-calendar-day--in-range" ) }, day.toISOString() )) ] }, weekIndex)); }; const themeStyles = theme ? { "--rcp-primary": theme.primary, "--rcp-secondary": theme.secondary, "--rcp-background": theme.background, "--rcp-text": theme.text, "--rcp-text-secondary": theme.textSecondary, "--rcp-border": theme.border, "--rcp-hover": theme.hover, "--rcp-selected": theme.selected, "--rcp-disabled": theme.disabled, "--rcp-today": theme.today } : {}; return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)( "div", { className: (0, import_clsx5.default)( "rcp-calendar", `rcp-calendar--${currentView}`, responsive && "rcp-calendar--responsive", showTimePicker && "rcp-calendar--with-time", className ), style: themeStyles, children: [ headerRenderer ? headerRenderer({ date: currentDate, view: currentView, onPrevious: handlePrevious, onNext: handleNext, onViewChange: handleViewChange }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)( CalendarHeader, { currentDate, view: currentView, onPrevious: handlePrevious, onNext: handleNext, onViewChange: handleViewChange, onDateChange: handleDateChange, minDate, maxDate } ), /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "rcp-calendar__body", children: [ currentView !== "day" && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "rcp-calendar__weekdays", children: [ showWeekNumbers && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "rcp-calendar__week-number-header", children: "#" }), weekHeaders.map((day) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "rcp-calendar__weekday", children: (0, import_date_fns5.format)(day, "EEE", { locale: import_locale4.enUS }) }, day.toISOString())) ] }), /* @__PURE__ */ (0, import_jsx_runtime5.jsx)( "div", { className: "rcp-calendar__grid", onMouseLeave: () => setHoveredDate(null), children: renderCalendarGrid() } ) ] }), showTimePicker && selectedValue instanceof Date && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "rcp-calendar__time-picker", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)( TimePicker, { value: selectedValue, onChange: handleTimeChange, format: timeFormat, minuteStep } ) }) ] } ); }; // src/components/CalendarInput.tsx var import_react4 = require("react"); var import_date_fns6 = require("date-fns"); var import_clsx6 = __toESM(require("clsx")); var import_jsx_runtime6 = require("react/jsx-runtime"); var CalendarInput = ({ value, defaultValue, onChange, placeholder = "Select date...", inputClassName, calendarClassName, showInput = true, inputFormat = "MM/dd/yyyy", disabled = false, readOnly = false, clearable = false, onClear, dropdownPosition = "auto", dropdownOffset = 4, closeOnSelect = true, closeOnOutsideClick = true, onOpen, onClose, name, id, autoComplete = "off", required = false, selectionMode = "single", showTimePicker = false, ...calendarProps }) => { const [isOpen, setIsOpen] = (0, import_react4.useState)(false); const [inputValue, setInputValue] = (0, import_react4.useState)(""); const [dropdownStyle, setDropdownStyle] = (0, import_react4.useState)({}); const inputRef = (0, import_react4.useRef)(null); const calendarRef = (0, import_react4.useRef)(null); const containerRef = (0, import_react4.useRef)(null); const formatValue = (val) => { if (!val) return ""; if (val instanceof Date) { return (0, import_date_fns6.format)(val, inputFormat); } if (Array.isArray(val)) { return val.map((d) => (0, import_date_fns6.format)(d, inputFormat)).join(", "); } const start = val.start ? (0, import_date_fns6.format)(val.start, inputFormat) : ""; const end = val.end ? (0, import_date_fns6.format)(val.end, inputFormat) : ""; return start && end ? `${start} - ${end}` : start || end; }; (0, import_react4.useEffect)(() => { setInputValue(formatValue(value ?? defaultValue ?? null)); }, [value, defaultValue, inputFormat]); const handleInputChange = (e) => { if (readOnly) return; setInputValue(e.target.value); }; const handleInputClick = () => { if (disabled || readOnly) return; if (!isOpen) { setIsOpen(true); onOpen?.(); } }; const handleCalendarChange = (newValue) => { onChange?.(newValue); setInputValue(formatValue(newValue)); if (closeOnSelect && selectionMode === "single" && !showTimePicker) { setIsOpen(false); onClose?.(); } }; const handleClear = (e) => { e.stopPropagation(); onChange?.(null); setInputValue(""); onClear?.(); }; (0, import_react4.useEffect)(() => { if (isOpen && inputRef.current) { const inputRect = inputRef.current.getBoundingClientRect(); const viewportHeight = window.innerHeight; let top = inputRect.bottom + dropdownOffset; let position = "bottom"; if (dropdownPosition === "auto") { const spaceBelow = viewportHeight - inputRect.bottom; const estimatedCalendarHeight = 400; if (spaceBelow < estimatedCalendarHeight && inputRect.top > estimatedCalendarHeight) { position = "top"; } } else if (dropdownPosition === "top") { position = "top"; } setDropdownStyle({ position: "fixed", top: position === "top" ? "auto" : top, bottom: position === "top" ? viewportHeight - inputRect.top + dropdownOffset : "auto", left: inputRect.left, zIndex: 1e3, minWidth: inputRect.width }); } }, [isOpen, dropdownPosition, dropdownOffset]); (0, import_react4.useEffect)(() => { const handleClickOutside = (event) => { if (closeOnOutsideClick && isOpen && containerRef.current && !containerRef.current.contains(event.target)) { setIsOpen(false); onClose?.(); } }; if (isOpen) { document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); } }, [isOpen, closeOnOutsideClick, onClose]); (0, import_react4.useEffect)(() => { const handleEscape = (event) => { if (event.key === "Escape" && isOpen) { setIsOpen(false); onClose?.(); } }; if (isOpen) { document.addEventListener("keydown", handleEscape); return () => document.removeEventListener("keydown", handleEscape); } }, [isOpen, onClose]); (0, import_react4.useEffect)(() => { let timeoutId = null; const handleScroll = () => { if (isOpen && inputRef.current) { if (timeoutId) { clearTimeout(timeoutId); } timeoutId = setTimeout(() => { const inputRect = inputRef.current.getBoundingClientRect(); const viewportHeight = window.innerHeight; const viewportWidth = window.innerWidth; if (inputRect.bottom < 0 || inputRect.top > viewportHeight || inputRect.right < 0 || inputRect.left > viewportWidth) { setIsOpen(false); onClose?.(); return; } let top = inputRect.bottom + dropdownOffset; let position = "bottom"; if (dropdownPosition === "auto") { const spaceBelow = viewportHeight - inputRect.bottom; const estimatedCalendarHeight = 400; if (spaceBelow < estimatedCalendarHeight && inputRect.top > estimatedCalendarHeight) { position = "top"; } } else if (dropdownPosition === "top") { position = "top"; } setDropdownStyle({ position: "fixed", top: position === "top" ? "auto" : top, bottom: position === "top" ? viewportHeight - inputRect.top + dropdownOffset : "auto", left: inputRect.left, zIndex: 1e3, minWidth: inputRect.width }); }, 16); } }; if (isOpen) { window.addEventListener("scroll", handleScroll, true); window.addEventListener("resize", handleScroll); return () => { if (timeoutId) { clearTimeout(timeoutId); } window.removeEventListener("scroll", handleScroll, true); window.removeEventListener("resize", handleScroll); }; } }, [isOpen, dropdownPosition, dropdownOffset]); return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { ref: containerRef, className: "rcp-calendar-input", children: [ showInput && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "rcp-calendar-input__wrapper", children: [ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)( "input", { ref: inputRef, type: "text", value: inputValue, onChange: handleInputChange, onClick: handleInputClick, placeholder, disabled, readOnly, name, id, autoComplete, required, className: (0, import_clsx6.default)( "rcp-calendar-input__field", disabled && "rcp-calendar-input__field--disabled", readOnly && "rcp-calendar-input__field--readonly", isOpen && "rcp-calendar-input__field--open", inputClassName ) } ), clearable && inputValue && !disabled && !readOnly && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)( "button", { type: "button", onClick: handleClear, className: "rcp-calendar-input__clear", "aria-label": "Clear date", children: "\xD7" } ), /* @__PURE__ */ (0, import_jsx_runtime6.jsx)( "button", { type: "button", onClick: handleInputClick, disabled, className: (0, import_clsx6.default)( "rcp-calendar-input__toggle", disabled && "rcp-calendar-input__toggle--disabled", isOpen && "rcp-calendar-input__toggle--open" ), "aria-label": "Open calendar", children: "\u{1F4C5}" } ) ] }), isOpen && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)( "div", { ref: calendarRef, className: (0, import_clsx6.default)( "rcp-calendar-input__dropdown", calendarClassName ), style: dropdownStyle, children: [ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)( Calendar, { value: value || defaultValue, onChange: handleCalendarChange, selectionMode, showTimePicker, ...calendarProps } ), showTimePicker && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { padding: "12px 16px", borderTop: "1px solid var(--rcp-border)", backgroundColor: "var(--rcp-hover)", display: "flex", justifyContent: "flex-end", gap: "8px" }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)( "button", { type: "button", onClick: () => { setIsOpen(false); onClose?.(); }, style: { padding: "8px 16px", backgroundColor: "var(--rcp-primary)", color: "white", border: "none", borderRadius: "4px", cursor: "pointer", fontSize: "14px", fontWeight: "500" }, children: "Done" } ) }) ] } ) ] }); }; // src/index.ts var import_calendar = require("./calendar-5YM4I3IC.css"); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { Calendar, CalendarDay, CalendarHeader, CalendarInput, DEFAULT_LOCALE, TimePicker, YearMonthSelector, combineDateTime, formatDate, formatDateForDisplay, getCalendarDays, getMonthNames, getViewTitle, getWeekDays, getWeekNumber, getYearRange, isDateDisabled, isDateInRange, isDateSelected, navigateDate, navigateToMonth, navigateToYear, navigateToYearMonth, parseDateTime });