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
JavaScript
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
});
;