@ilamy/calendar
Version:
A full-featured React calendar component library built with Shadcn-Ui, Tailwind CSS, and TypeScript.
1,321 lines (1,303 loc) • 581 kB
JavaScript
"use client";
// src/features/month-view/components/view/month-view.tsx
import { AnimatePresence as AnimatePresence3, motion as motion3 } from "motion/react";
import React5, { useMemo as useMemo2 } from "react";
// src/contexts/calendar-context/context.ts
import { createContext, useContext } from "react";
var CalendarContext = createContext(undefined);
var useCalendarContext = () => {
const context = useContext(CalendarContext);
if (context === undefined) {
throw new Error("useCalendarContext must be used within a CalendarProvider");
}
return context;
};
var useIlamyCalendarContext = () => {
const context = useContext(CalendarContext);
if (context === undefined) {
throw new Error("useIlamyCalendarContext must be used within a CalendarProvider");
}
return {
currentDate: context.currentDate,
view: context.view,
events: context.events,
isEventFormOpen: context.isEventFormOpen,
selectedEvent: context.selectedEvent,
selectedDate: context.selectedDate,
firstDayOfWeek: context.firstDayOfWeek,
setCurrentDate: context.setCurrentDate,
selectDate: context.selectDate,
setView: context.setView,
nextPeriod: context.nextPeriod,
prevPeriod: context.prevPeriod,
today: context.today,
addEvent: context.addEvent,
updateEvent: context.updateEvent,
deleteEvent: context.deleteEvent,
openEventForm: context.openEventForm,
closeEventForm: context.closeEventForm
};
};
// src/components/ui/button.tsx
import { Slot } from "@radix-ui/react-slot";
import { cva } from "class-variance-authority";
// src/lib/utils.ts
import { clsx } from "clsx";
// src/lib/dayjs-config.ts
import dayjs from "dayjs";
import weekday from "dayjs/plugin/weekday.js";
import weekOfYear from "dayjs/plugin/weekOfYear.js";
import isSameOrAfter from "dayjs/plugin/isSameOrAfter.js";
import isSameOrBefore from "dayjs/plugin/isSameOrBefore.js";
import isBetween from "dayjs/plugin/isBetween.js";
import minMax from "dayjs/plugin/minMax.js";
import timezone from "dayjs/plugin/timezone.js";
import utc from "dayjs/plugin/utc.js";
import localeData from "dayjs/plugin/localeData.js";
import"dayjs/locale/af.js";
import"dayjs/locale/am.js";
import"dayjs/locale/ar-dz.js";
import"dayjs/locale/ar-iq.js";
import"dayjs/locale/ar-kw.js";
import"dayjs/locale/ar-ly.js";
import"dayjs/locale/ar-ma.js";
import"dayjs/locale/ar-sa.js";
import"dayjs/locale/ar-tn.js";
import"dayjs/locale/ar.js";
import"dayjs/locale/az.js";
import"dayjs/locale/be.js";
import"dayjs/locale/bg.js";
import"dayjs/locale/bi.js";
import"dayjs/locale/bm.js";
import"dayjs/locale/bn-bd.js";
import"dayjs/locale/bn.js";
import"dayjs/locale/bo.js";
import"dayjs/locale/br.js";
import"dayjs/locale/bs.js";
import"dayjs/locale/ca.js";
import"dayjs/locale/cs.js";
import"dayjs/locale/cv.js";
import"dayjs/locale/cy.js";
import"dayjs/locale/da.js";
import"dayjs/locale/de-at.js";
import"dayjs/locale/de-ch.js";
import"dayjs/locale/de.js";
import"dayjs/locale/dv.js";
import"dayjs/locale/el.js";
import"dayjs/locale/en-au.js";
import"dayjs/locale/en-ca.js";
import"dayjs/locale/en-gb.js";
import"dayjs/locale/en-ie.js";
import"dayjs/locale/en-il.js";
import"dayjs/locale/en-in.js";
import"dayjs/locale/en-nz.js";
import"dayjs/locale/en-sg.js";
import"dayjs/locale/en-tt.js";
import"dayjs/locale/en.js";
import"dayjs/locale/eo.js";
import"dayjs/locale/es-do.js";
import"dayjs/locale/es-mx.js";
import"dayjs/locale/es-pr.js";
import"dayjs/locale/es-us.js";
import"dayjs/locale/es.js";
import"dayjs/locale/et.js";
import"dayjs/locale/eu.js";
import"dayjs/locale/fa.js";
import"dayjs/locale/fi.js";
import"dayjs/locale/fo.js";
import"dayjs/locale/fr-ca.js";
import"dayjs/locale/fr-ch.js";
import"dayjs/locale/fr.js";
import"dayjs/locale/fy.js";
import"dayjs/locale/ga.js";
import"dayjs/locale/gd.js";
import"dayjs/locale/gl.js";
import"dayjs/locale/gom-latn.js";
import"dayjs/locale/gu.js";
import"dayjs/locale/he.js";
import"dayjs/locale/hi.js";
import"dayjs/locale/hr.js";
import"dayjs/locale/ht.js";
import"dayjs/locale/hu.js";
import"dayjs/locale/hy-am.js";
import"dayjs/locale/id.js";
import"dayjs/locale/is.js";
import"dayjs/locale/it-ch.js";
import"dayjs/locale/it.js";
import"dayjs/locale/ja.js";
import"dayjs/locale/jv.js";
import"dayjs/locale/ka.js";
import"dayjs/locale/kk.js";
import"dayjs/locale/km.js";
import"dayjs/locale/kn.js";
import"dayjs/locale/ko.js";
import"dayjs/locale/ku.js";
import"dayjs/locale/ky.js";
import"dayjs/locale/lb.js";
import"dayjs/locale/lo.js";
import"dayjs/locale/lt.js";
import"dayjs/locale/lv.js";
import"dayjs/locale/me.js";
import"dayjs/locale/mi.js";
import"dayjs/locale/mk.js";
import"dayjs/locale/ml.js";
import"dayjs/locale/mn.js";
import"dayjs/locale/mr.js";
import"dayjs/locale/ms-my.js";
import"dayjs/locale/ms.js";
import"dayjs/locale/mt.js";
import"dayjs/locale/my.js";
import"dayjs/locale/nb.js";
import"dayjs/locale/ne.js";
import"dayjs/locale/nl-be.js";
import"dayjs/locale/nl.js";
import"dayjs/locale/nn.js";
import"dayjs/locale/oc-lnc.js";
import"dayjs/locale/pa-in.js";
import"dayjs/locale/pl.js";
import"dayjs/locale/pt-br.js";
import"dayjs/locale/pt.js";
import"dayjs/locale/rn.js";
import"dayjs/locale/ro.js";
import"dayjs/locale/ru.js";
import"dayjs/locale/rw.js";
import"dayjs/locale/sd.js";
import"dayjs/locale/se.js";
import"dayjs/locale/si.js";
import"dayjs/locale/sk.js";
import"dayjs/locale/sl.js";
import"dayjs/locale/sq.js";
import"dayjs/locale/sr-cyrl.js";
import"dayjs/locale/sr.js";
import"dayjs/locale/ss.js";
import"dayjs/locale/sv-fi.js";
import"dayjs/locale/sv.js";
import"dayjs/locale/sw.js";
import"dayjs/locale/ta.js";
import"dayjs/locale/te.js";
import"dayjs/locale/tet.js";
import"dayjs/locale/tg.js";
import"dayjs/locale/th.js";
import"dayjs/locale/tk.js";
import"dayjs/locale/tl-ph.js";
import"dayjs/locale/tlh.js";
import"dayjs/locale/tr.js";
import"dayjs/locale/tzl.js";
import"dayjs/locale/tzm-latn.js";
import"dayjs/locale/tzm.js";
import"dayjs/locale/ug-cn.js";
import"dayjs/locale/uk.js";
import"dayjs/locale/ur.js";
import"dayjs/locale/uz-latn.js";
import"dayjs/locale/uz.js";
import"dayjs/locale/vi.js";
import"dayjs/locale/x-pseudo.js";
import"dayjs/locale/yo.js";
import"dayjs/locale/zh-cn.js";
import"dayjs/locale/zh-hk.js";
import"dayjs/locale/zh-tw.js";
import"dayjs/locale/zh.js";
dayjs.extend(weekday);
dayjs.extend(weekOfYear);
dayjs.extend(isSameOrAfter);
dayjs.extend(isSameOrBefore);
dayjs.extend(isBetween);
dayjs.extend(minMax);
dayjs.extend(timezone);
dayjs.extend(utc);
dayjs.extend(localeData);
var dayjs_config_default = dayjs;
// src/lib/utils.ts
import { twMerge } from "tailwind-merge";
function cn(...inputs) {
return twMerge(clsx(inputs));
}
function safeDate(date) {
if (dayjs_config_default.isDayjs(date)) {
return date;
}
const parsedDate = dayjs_config_default(date);
return parsedDate.isValid() ? parsedDate : dayjs_config_default();
}
var omitKeys = (obj, keys) => {
const result = { ...obj };
for (const key of keys) {
delete result[key];
}
return result;
};
function normalizePublicFacingCalendarEvent(events) {
if (!events || !events.length) {
return [];
}
return events.map((event) => {
return {
...event,
start: dayjs_config_default.isDayjs(event.start) ? event.start : dayjs_config_default(event.start),
end: dayjs_config_default.isDayjs(event.end) ? event.end : dayjs_config_default(event.end)
};
});
}
// src/components/ui/button.tsx
import { jsx } from "react/jsx-runtime";
var buttonVariants = cva("inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", {
variants: {
variant: {
default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
destructive: "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline"
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9"
}
},
defaultVariants: {
variant: "default",
size: "default"
}
});
function Button({
className,
variant,
size,
asChild = false,
...props
}) {
const Comp = asChild ? Slot : "button";
return /* @__PURE__ */ jsx(Comp, {
"data-slot": "button",
className: cn(buttonVariants({ variant, size, className })),
...props
});
}
// src/components/ui/calendar.tsx
import * as React from "react";
import {
ChevronDownIcon,
ChevronLeftIcon,
ChevronRightIcon
} from "lucide-react";
import { DayPicker, getDefaultClassNames } from "react-day-picker";
import { jsx as jsx2 } from "react/jsx-runtime";
function Calendar({
className,
classNames,
showOutsideDays = true,
captionLayout = "label",
buttonVariant = "ghost",
formatters,
components,
...props
}) {
const defaultClassNames = getDefaultClassNames();
return /* @__PURE__ */ jsx2(DayPicker, {
showOutsideDays,
className: cn("bg-background group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent", String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`, String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`, className),
captionLayout,
formatters: {
formatMonthDropdown: (date) => date.toLocaleString("default", { month: "short" }),
...formatters
},
classNames: {
root: cn("w-fit", defaultClassNames.root),
months: cn("flex gap-4 flex-col md:flex-row relative", defaultClassNames.months),
month: cn("flex flex-col w-full gap-4", defaultClassNames.month),
nav: cn("flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between", defaultClassNames.nav),
button_previous: cn(buttonVariants({ variant: buttonVariant }), "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none", defaultClassNames.button_previous),
button_next: cn(buttonVariants({ variant: buttonVariant }), "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none", defaultClassNames.button_next),
month_caption: cn("flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)", defaultClassNames.month_caption),
dropdowns: cn("w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5", defaultClassNames.dropdowns),
dropdown_root: cn("relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md", defaultClassNames.dropdown_root),
dropdown: cn("absolute bg-popover inset-0 opacity-0", defaultClassNames.dropdown),
caption_label: cn("select-none font-medium", captionLayout === "label" ? "text-sm" : "rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5", defaultClassNames.caption_label),
table: "w-full border-collapse",
weekdays: cn("flex", defaultClassNames.weekdays),
weekday: cn("text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none", defaultClassNames.weekday),
week: cn("flex w-full mt-2", defaultClassNames.week),
week_number_header: cn("select-none w-(--cell-size)", defaultClassNames.week_number_header),
week_number: cn("text-[0.8rem] select-none text-muted-foreground", defaultClassNames.week_number),
day: cn("relative w-full h-full p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none", defaultClassNames.day),
range_start: cn("rounded-l-md bg-accent", defaultClassNames.range_start),
range_middle: cn("rounded-none", defaultClassNames.range_middle),
range_end: cn("rounded-r-md bg-accent", defaultClassNames.range_end),
today: cn("bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none", defaultClassNames.today),
outside: cn("text-muted-foreground aria-selected:text-muted-foreground", defaultClassNames.outside),
disabled: cn("text-muted-foreground opacity-50", defaultClassNames.disabled),
hidden: cn("invisible", defaultClassNames.hidden),
...classNames
},
components: {
Root: ({ className: className2, rootRef, ...props2 }) => {
return /* @__PURE__ */ jsx2("div", {
"data-slot": "calendar",
ref: rootRef,
className: cn(className2),
...props2
});
},
Chevron: ({ className: className2, orientation, ...props2 }) => {
if (orientation === "left") {
return /* @__PURE__ */ jsx2(ChevronLeftIcon, {
className: cn("size-4", className2),
...props2
});
}
if (orientation === "right") {
return /* @__PURE__ */ jsx2(ChevronRightIcon, {
className: cn("size-4", className2),
...props2
});
}
return /* @__PURE__ */ jsx2(ChevronDownIcon, {
className: cn("size-4", className2),
...props2
});
},
DayButton: CalendarDayButton,
WeekNumber: ({ children, ...props2 }) => {
return /* @__PURE__ */ jsx2("td", {
...props2,
children: /* @__PURE__ */ jsx2("div", {
className: "flex size-(--cell-size) items-center justify-center text-center",
children
})
});
},
...components
},
...props
});
}
function CalendarDayButton({
className,
day,
modifiers,
...props
}) {
const defaultClassNames = getDefaultClassNames();
const ref = React.useRef(null);
React.useEffect(() => {
if (modifiers.focused) {
ref.current?.focus();
}
}, [modifiers.focused]);
return /* @__PURE__ */ jsx2(Button, {
ref,
variant: "ghost",
size: "icon",
"data-day": day.date.toLocaleDateString(),
"data-selected-single": modifiers.selected && !modifiers.range_start && !modifiers.range_end && !modifiers.range_middle,
"data-range-start": modifiers.range_start,
"data-range-end": modifiers.range_end,
"data-range-middle": modifiers.range_middle,
className: cn("data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 dark:hover:text-accent-foreground flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md [&>span]:text-xs [&>span]:opacity-70", defaultClassNames.day, className),
...props
});
}
// src/components/ui/checkbox.tsx
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
import { CheckIcon } from "lucide-react";
import { jsx as jsx3 } from "react/jsx-runtime";
function Checkbox({
className,
...props
}) {
return /* @__PURE__ */ jsx3(CheckboxPrimitive.Root, {
"data-slot": "checkbox",
className: cn("peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50", className),
...props,
children: /* @__PURE__ */ jsx3(CheckboxPrimitive.Indicator, {
"data-slot": "checkbox-indicator",
className: "flex items-center justify-center text-current transition-none",
children: /* @__PURE__ */ jsx3(CheckIcon, {
className: "size-3.5"
})
})
});
}
// src/components/ui/card.tsx
import { jsx as jsx4 } from "react/jsx-runtime";
function Card({ className, ...props }) {
return /* @__PURE__ */ jsx4("div", {
"data-slot": "card",
className: cn("text-card-foreground flex flex-col gap-6 rounded-xl border shadow-sm", className),
...props
});
}
function CardHeader({ className, ...props }) {
return /* @__PURE__ */ jsx4("div", {
"data-slot": "card-header",
className: cn("@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 p-4 pb-0 has-data-[slot=card-action]:grid-cols-[1fr_auto]", className),
...props
});
}
function CardTitle({ className, ...props }) {
return /* @__PURE__ */ jsx4("div", {
"data-slot": "card-title",
className: cn("leading-none font-semibold", className),
...props
});
}
function CardContent({ className, ...props }) {
return /* @__PURE__ */ jsx4("div", {
"data-slot": "card-content",
className: cn("p-4 pt-0", className),
...props
});
}
// src/components/ui/date-picker.tsx
import { Calendar as CalendarIcon } from "lucide-react";
// src/components/ui/popover.tsx
import * as PopoverPrimitive from "@radix-ui/react-popover";
import { jsx as jsx5 } from "react/jsx-runtime";
function Popover({
...props
}) {
return /* @__PURE__ */ jsx5(PopoverPrimitive.Root, {
"data-slot": "popover",
...props
});
}
function PopoverTrigger({
...props
}) {
return /* @__PURE__ */ jsx5(PopoverPrimitive.Trigger, {
"data-slot": "popover-trigger",
...props
});
}
function PopoverContent({
className,
align = "center",
sideOffset = 4,
...props
}) {
return /* @__PURE__ */ jsx5(PopoverPrimitive.Portal, {
children: /* @__PURE__ */ jsx5(PopoverPrimitive.Content, {
"data-slot": "popover-content",
align,
sideOffset,
className: cn("bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden", className),
...props
})
});
}
// src/components/ui/date-picker.tsx
import { useRef as useRef2 } from "react";
import { PopoverClose } from "@radix-ui/react-popover";
import { jsx as jsx6, jsxs } from "react/jsx-runtime";
function DatePicker({
date,
closeOnSelect,
setDate,
label = "Pick a date",
className
}) {
const popOverRef = useRef2(null);
const onSelect = (date2) => {
setDate(date2);
if (closeOnSelect) {
popOverRef.current?.click();
}
};
return /* @__PURE__ */ jsx6("div", {
className,
children: /* @__PURE__ */ jsxs(Popover, {
children: [
/* @__PURE__ */ jsx6(PopoverTrigger, {
asChild: true,
children: /* @__PURE__ */ jsxs(Button, {
variant: "outline",
className: cn("w-full justify-start text-left font-normal", !date && "text-muted-foreground"),
children: [
/* @__PURE__ */ jsx6(CalendarIcon, {
className: "mr-2 h-4 w-4"
}),
date ? dayjs_config_default(date).format("MMM D, YYYY") : /* @__PURE__ */ jsx6("span", {
children: label
})
]
})
}),
/* @__PURE__ */ jsxs(PopoverContent, {
className: "w-auto p-0",
align: "start",
children: [
/* @__PURE__ */ jsx6(PopoverClose, {
ref: popOverRef
}),
/* @__PURE__ */ jsx6(Calendar, {
mode: "single",
selected: date,
onSelect,
month: date,
initialFocus: true
})
]
})
]
})
});
}
// src/components/ui/dialog.tsx
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { XIcon } from "lucide-react";
import { jsx as jsx7, jsxs as jsxs2 } from "react/jsx-runtime";
function Dialog({
...props
}) {
return /* @__PURE__ */ jsx7(DialogPrimitive.Root, {
"data-slot": "dialog",
...props
});
}
function DialogPortal({
...props
}) {
return /* @__PURE__ */ jsx7(DialogPrimitive.Portal, {
"data-slot": "dialog-portal",
...props
});
}
function DialogOverlay({
className,
...props
}) {
return /* @__PURE__ */ jsx7(DialogPrimitive.Overlay, {
"data-slot": "dialog-overlay",
className: cn("data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50", className),
...props
});
}
function DialogContent({
className,
children,
...props
}) {
return /* @__PURE__ */ jsxs2(DialogPortal, {
"data-slot": "dialog-portal",
children: [
/* @__PURE__ */ jsx7(DialogOverlay, {}),
/* @__PURE__ */ jsxs2(DialogPrimitive.Content, {
"data-slot": "dialog-content",
className: cn("bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg max-h-[90%] overflow-y-scroll", className),
...props,
children: [
children,
/* @__PURE__ */ jsxs2(DialogPrimitive.Close, {
className: "ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
children: [
/* @__PURE__ */ jsx7(XIcon, {}),
/* @__PURE__ */ jsx7("span", {
className: "sr-only",
children: "Close"
})
]
})
]
})
]
});
}
function DialogHeader({ className, ...props }) {
return /* @__PURE__ */ jsx7("div", {
"data-slot": "dialog-header",
className: cn("flex flex-col gap-2 text-center sm:text-left", className),
...props
});
}
function DialogFooter({ className, ...props }) {
return /* @__PURE__ */ jsx7("div", {
"data-slot": "dialog-footer",
className: cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className),
...props
});
}
function DialogTitle({
className,
...props
}) {
return /* @__PURE__ */ jsx7(DialogPrimitive.Title, {
"data-slot": "dialog-title",
className: cn("text-lg leading-none font-semibold", className),
...props
});
}
function DialogDescription({
className,
...props
}) {
return /* @__PURE__ */ jsx7(DialogPrimitive.Description, {
"data-slot": "dialog-description",
className: cn("text-muted-foreground text-sm", className),
...props
});
}
// src/components/ui/input.tsx
import { jsx as jsx8 } from "react/jsx-runtime";
function Input({ className, type, ...props }) {
return /* @__PURE__ */ jsx8("input", {
type,
"data-slot": "input",
className: cn("file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]", "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", className),
...props
});
}
// src/components/ui/label.tsx
import * as LabelPrimitive from "@radix-ui/react-label";
import { jsx as jsx9 } from "react/jsx-runtime";
function Label({
className,
...props
}) {
return /* @__PURE__ */ jsx9(LabelPrimitive.Root, {
"data-slot": "label",
className: cn("flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50", className),
...props
});
}
// src/components/ui/scroll-area.tsx
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
import { jsx as jsx10, jsxs as jsxs3 } from "react/jsx-runtime";
function ScrollArea({ className, children, ...props }) {
return /* @__PURE__ */ jsxs3(ScrollAreaPrimitive.Root, {
"data-slot": "scroll-area",
className: cn("relative", className),
...props,
children: [
/* @__PURE__ */ jsx10(ScrollAreaPrimitive.Viewport, {
...props.viewPortProps,
"data-slot": "scroll-area-viewport",
className: cn("focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1", props.viewPortProps?.className),
children
}),
/* @__PURE__ */ jsx10(ScrollBar, {}),
/* @__PURE__ */ jsx10(ScrollAreaPrimitive.Corner, {})
]
});
}
function ScrollBar({
className,
orientation = "vertical",
...props
}) {
return /* @__PURE__ */ jsx10(ScrollAreaPrimitive.ScrollAreaScrollbar, {
"data-slot": "scroll-area-scrollbar",
orientation,
className: cn("flex touch-none p-px transition-colors select-none", orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent", orientation === "horizontal" && "h-2.5 flex-col border-t border-t-transparent", className),
...props,
children: /* @__PURE__ */ jsx10(ScrollAreaPrimitive.ScrollAreaThumb, {
"data-slot": "scroll-area-thumb",
className: "bg-border relative flex-1 rounded-full"
})
});
}
// src/components/ui/select.tsx
import * as SelectPrimitive from "@radix-ui/react-select";
import { CheckIcon as CheckIcon2, ChevronDownIcon as ChevronDownIcon2, ChevronUpIcon } from "lucide-react";
import { jsx as jsx11, jsxs as jsxs4 } from "react/jsx-runtime";
function Select({
...props
}) {
return /* @__PURE__ */ jsx11(SelectPrimitive.Root, {
"data-slot": "select",
...props
});
}
function SelectValue({
...props
}) {
return /* @__PURE__ */ jsx11(SelectPrimitive.Value, {
"data-slot": "select-value",
...props
});
}
function SelectTrigger({
className,
size = "default",
children,
...props
}) {
return /* @__PURE__ */ jsxs4(SelectPrimitive.Trigger, {
"data-slot": "select-trigger",
"data-size": size,
className: cn("border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className),
...props,
children: [
children,
/* @__PURE__ */ jsx11(SelectPrimitive.Icon, {
asChild: true,
children: /* @__PURE__ */ jsx11(ChevronDownIcon2, {
className: "size-4 opacity-50"
})
})
]
});
}
function SelectContent({
className,
children,
position = "popper",
...props
}) {
return /* @__PURE__ */ jsx11(SelectPrimitive.Portal, {
children: /* @__PURE__ */ jsxs4(SelectPrimitive.Content, {
"data-slot": "select-content",
className: cn("bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md", position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", className),
position,
...props,
children: [
/* @__PURE__ */ jsx11(SelectScrollUpButton, {}),
/* @__PURE__ */ jsx11(SelectPrimitive.Viewport, {
className: cn("p-1", position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"),
children
}),
/* @__PURE__ */ jsx11(SelectScrollDownButton, {})
]
})
});
}
function SelectItem({
className,
children,
...props
}) {
return /* @__PURE__ */ jsxs4(SelectPrimitive.Item, {
"data-slot": "select-item",
className: cn("focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2", className),
...props,
children: [
/* @__PURE__ */ jsx11("span", {
className: "absolute right-2 flex size-3.5 items-center justify-center",
children: /* @__PURE__ */ jsx11(SelectPrimitive.ItemIndicator, {
children: /* @__PURE__ */ jsx11(CheckIcon2, {
className: "size-4"
})
})
}),
/* @__PURE__ */ jsx11(SelectPrimitive.ItemText, {
children
})
]
});
}
function SelectScrollUpButton({
className,
...props
}) {
return /* @__PURE__ */ jsx11(SelectPrimitive.ScrollUpButton, {
"data-slot": "select-scroll-up-button",
className: cn("flex cursor-default items-center justify-center py-1", className),
...props,
children: /* @__PURE__ */ jsx11(ChevronUpIcon, {
className: "size-4"
})
});
}
function SelectScrollDownButton({
className,
...props
}) {
return /* @__PURE__ */ jsx11(SelectPrimitive.ScrollDownButton, {
"data-slot": "select-scroll-down-button",
className: cn("flex cursor-default items-center justify-center py-1", className),
...props,
children: /* @__PURE__ */ jsx11(ChevronDownIcon2, {
className: "size-4"
})
});
}
// src/components/ui/tabs.tsx
import * as TabsPrimitive from "@radix-ui/react-tabs";
import { jsx as jsx12 } from "react/jsx-runtime";
// src/components/ui/tooltip.tsx
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { jsx as jsx13, jsxs as jsxs5 } from "react/jsx-runtime";
function TooltipProvider({
delayDuration = 0,
...props
}) {
return /* @__PURE__ */ jsx13(TooltipPrimitive.Provider, {
"data-slot": "tooltip-provider",
delayDuration,
...props
});
}
function Tooltip({
...props
}) {
return /* @__PURE__ */ jsx13(TooltipProvider, {
children: /* @__PURE__ */ jsx13(TooltipPrimitive.Root, {
"data-slot": "tooltip",
...props
})
});
}
function TooltipTrigger({
...props
}) {
return /* @__PURE__ */ jsx13(TooltipPrimitive.Trigger, {
"data-slot": "tooltip-trigger",
...props
});
}
function TooltipContent({
className,
sideOffset = 0,
children,
...props
}) {
return /* @__PURE__ */ jsx13(TooltipPrimitive.Portal, {
children: /* @__PURE__ */ jsxs5(TooltipPrimitive.Content, {
"data-slot": "tooltip-content",
sideOffset,
className: cn("bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance", className),
...props,
children: [
children,
/* @__PURE__ */ jsx13(TooltipPrimitive.Arrow, {
className: "bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]"
})
]
})
});
}
// src/features/month-view/components/all-events-dialog.tsx
import { useImperativeHandle, useState } from "react";
// src/features/draggable-event/draggable-event.tsx
import { useDraggable } from "@dnd-kit/core";
import { AnimatePresence, motion } from "motion/react";
import { memo } from "react";
import { jsx as jsx14 } from "react/jsx-runtime";
function DraggableEventUnmemoized({
elementId,
event,
className,
style,
disableDrag = false
}) {
const { onEventClick, renderEvent, disableEventClick, disableDragAndDrop } = useCalendarContext();
const { attributes, listeners, setNodeRef, isDragging } = useDraggable({
id: elementId,
data: {
event,
type: "calendar-event"
},
disabled: disableDrag || disableDragAndDrop
});
const DefaultEventContent = () => /* @__PURE__ */ jsx14("div", {
className: cn(event.backgroundColor || "bg-blue-500", event.color || "text-white", "h-full w-full px-1 border-[1.5px] border-card rounded-md text-left overflow-clip"),
style: { backgroundColor: event.backgroundColor, color: event.color },
children: /* @__PURE__ */ jsx14("p", {
className: "text-[10px] font-semibold sm:text-xs mt-0.5",
children: event.title
})
});
return /* @__PURE__ */ jsx14(AnimatePresence, {
mode: "wait",
children: /* @__PURE__ */ jsx14(motion.div, {
ref: setNodeRef,
...attributes,
...listeners,
initial: { opacity: 0, y: -50 },
animate: { opacity: 1, y: 0 },
exit: { opacity: 0, y: -50 },
layout: true,
layoutId: elementId,
transition: { duration: 0.4, ease: "easeInOut" },
className: cn("truncate h-full w-full", disableDrag || disableDragAndDrop ? disableEventClick ? "cursor-default" : "cursor-pointer" : "cursor-grab", isDragging && !(disableDrag || disableDragAndDrop) && "cursor-grabbing shadow-lg", className),
style,
onClick: (e) => {
e.stopPropagation();
onEventClick(event);
},
children: renderEvent ? renderEvent(event) : /* @__PURE__ */ jsx14(DefaultEventContent, {})
}, elementId)
});
}
var DraggableEvent = memo(DraggableEventUnmemoized, (prevProps, nextProps) => {
return prevProps.elementId === nextProps.elementId && prevProps.disableDrag === nextProps.disableDrag && prevProps.className === nextProps.className && prevProps.event === nextProps.event;
});
// src/features/month-view/components/all-events-dialog.tsx
import { jsx as jsx15, jsxs as jsxs6 } from "react/jsx-runtime";
var AllEventDialog = ({ ref }) => {
const [dialogOpen, setDialogOpen] = useState(false);
const [selectedDayEvents, setSelectedDayEvents] = useState(null);
const { currentDate, firstDayOfWeek } = useCalendarContext();
useImperativeHandle(ref, () => ({
open: () => setDialogOpen(true),
close: () => setDialogOpen(false),
setSelectedDayEvents: (dayEvents) => setSelectedDayEvents(dayEvents)
}));
const firstDayOfMonth = currentDate.startOf("month");
let adjustedFirstDayOfCalendar = firstDayOfMonth.clone();
while (adjustedFirstDayOfCalendar.day() !== firstDayOfWeek) {
adjustedFirstDayOfCalendar = adjustedFirstDayOfCalendar.subtract(1, "day");
}
return /* @__PURE__ */ jsx15(Dialog, {
open: dialogOpen,
onOpenChange: setDialogOpen,
children: /* @__PURE__ */ jsxs6(DialogContent, {
className: "max-h-[80vh] max-w-md overflow-y-auto",
children: [
/* @__PURE__ */ jsx15(DialogHeader, {
children: /* @__PURE__ */ jsx15(DialogTitle, {
children: selectedDayEvents && selectedDayEvents.day.format("MMMM D, YYYY")
})
}),
/* @__PURE__ */ jsx15("div", {
className: "mt-4 space-y-3",
children: selectedDayEvents && selectedDayEvents.events.map((event) => {
return /* @__PURE__ */ jsx15(DraggableEvent, {
elementId: `all-events-dialog-event-$${event.id}`,
event,
className: "relative my-1 h-[30px]"
}, event.id);
})
})
]
})
});
};
// src/features/month-view/components/day-cell.tsx
import React3 from "react";
// src/features/droppable-cell/droppable-cell.tsx
import { useDroppable } from "@dnd-kit/core";
import { jsx as jsx16 } from "react/jsx-runtime";
function DroppableCell({
id,
type,
date,
hour,
minute,
children,
className,
style,
"data-testid": dataTestId
}) {
const { onCellClick, disableDragAndDrop, disableCellClick } = useCalendarContext();
const { isOver, setNodeRef } = useDroppable({
id,
data: {
type,
date,
hour,
minute
},
disabled: disableDragAndDrop
});
const handleCellClick = (e) => {
e.stopPropagation();
if (disableCellClick) {
return;
}
const startDate = date.hour(hour ?? 0).minute(minute ?? 0);
let endDate = startDate.clone();
if (hour !== undefined && minute !== undefined) {
endDate = endDate.hour(hour).minute(minute + 15);
} else if (hour !== undefined) {
endDate = endDate.hour(hour + 1).minute(0);
} else {
endDate = endDate.hour(23).minute(59);
}
onCellClick(startDate, endDate);
};
return /* @__PURE__ */ jsx16("div", {
ref: setNodeRef,
"data-testid": dataTestId,
className: cn(className, isOver && !disableDragAndDrop && "bg-accent", disableCellClick ? "cursor-default" : "cursor-pointer"),
onClick: handleCellClick,
style,
children
});
}
// src/features/month-view/components/day-cell.tsx
import { jsx as jsx17, jsxs as jsxs7, Fragment } from "react/jsx-runtime";
var DayCell = ({
index,
day,
className = ""
}) => {
const allEventsDialogRef = React3.useRef(null);
const {
currentLocale,
getEventsForDateRange,
currentDate,
firstDayOfWeek,
dayMaxEvents = 0
} = useCalendarContext();
const todayEvents = getEventsForDateRange(day.startOf("day"), day.endOf("day"));
const firstDayOfMonth = currentDate.startOf("month");
let adjustedFirstDayOfCalendar = firstDayOfMonth.clone();
while (adjustedFirstDayOfCalendar.day() !== firstDayOfWeek) {
adjustedFirstDayOfCalendar = adjustedFirstDayOfCalendar.subtract(1, "day");
}
const showAllEvents = (day2, events) => {
allEventsDialogRef.current?.setSelectedDayEvents({
day: day2,
events
});
allEventsDialogRef.current?.open();
};
const isToday = day.isSame(dayjs_config_default(), "day");
const isCurrentMonth = day.month() === currentDate.month();
const isLastColumn = index === 6;
const hiddenEventsCount = todayEvents.length - dayMaxEvents;
const hasHiddenEvents = hiddenEventsCount > 0;
return /* @__PURE__ */ jsxs7(Fragment, {
children: [
/* @__PURE__ */ jsx17(DroppableCell, {
id: `day-cell-${day.format("YYYY-MM-DD")}`,
type: "day-cell",
"data-testid": `day-cell-${day.format("YYYY-MM-DD")}`,
date: day,
className: cn("cursor-pointer overflow-clip p-1 hover:bg-accent min-h-[60px]", !isCurrentMonth && "bg-secondary text-muted-foreground", isLastColumn && "border-r-0", className),
children: /* @__PURE__ */ jsxs7("div", {
className: "flex flex-col gap-1",
children: [
/* @__PURE__ */ jsx17("div", {
className: cn("flex h-5 w-5 items-center justify-center rounded-full text-xs sm:h-6 sm:w-6", isToday && "bg-primary text-primary-foreground font-medium"),
children: Intl.DateTimeFormat(currentLocale, { day: "numeric" }).format(day.toDate())
}),
todayEvents.slice(0, dayMaxEvents).map((event, rowIndex) => /* @__PURE__ */ jsx17("div", {
className: "h-[20px] w-full",
"data-testid": event?.title
}, `empty-${rowIndex}`)),
hasHiddenEvents && /* @__PURE__ */ jsxs7("div", {
className: "text-muted-foreground hover:text-foreground cursor-pointer text-[10px] whitespace-nowrap sm:text-xs mt-1",
onClick: (e) => {
e.stopPropagation();
showAllEvents(day, todayEvents);
},
onKeyDown: (e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
e.stopPropagation();
showAllEvents(day, todayEvents);
}
},
tabIndex: 0,
role: "button",
children: [
"+",
hiddenEventsCount,
" more"
]
})
]
})
}),
/* @__PURE__ */ jsx17(AllEventDialog, {
ref: allEventsDialogRef
})
]
});
};
// src/features/month-view/components/month-header/month-header.tsx
import { AnimatePresence as AnimatePresence2, motion as motion2 } from "motion/react";
import { useMemo } from "react";
import { jsx as jsx18, jsxs as jsxs8 } from "react/jsx-runtime";
var MonthHeader = ({ className }) => {
const {
firstDayOfWeek,
currentLocale,
stickyViewHeader,
viewHeaderClassName
} = useCalendarContext();
const weekDays = useMemo(() => {
const days = dayjs_config_default.weekdays().map((day) => day.toLowerCase());
const shortDays = dayjs_config_default.weekdaysShort().map((day) => day.toLowerCase());
for (let i = 0;i < firstDayOfWeek; i++) {
const dayToMove = days.shift();
const shortDayToMove = shortDays.shift();
if (dayToMove) {
days.push(dayToMove);
}
if (shortDayToMove) {
shortDays.push(shortDayToMove);
}
}
return { days, shortDays };
}, [firstDayOfWeek, currentLocale]);
return /* @__PURE__ */ jsx18("div", {
className: cn("grid grid-cols-7 border-b", stickyViewHeader && "sticky top-0 z-100", viewHeaderClassName, className),
"data-testid": "month-header",
children: weekDays.days.map((weekDay, index) => /* @__PURE__ */ jsx18(AnimatePresence2, {
mode: "wait",
children: /* @__PURE__ */ jsxs8(motion2.div, {
initial: { opacity: 0, y: -10 },
animate: { opacity: 1, y: 0 },
exit: { opacity: 0, y: -10 },
transition: {
duration: 0.25,
ease: "easeInOut",
delay: index * 0.05
},
className: "py-2 text-center font-medium border-r first:border-l",
"data-testid": `weekday-header-${weekDay}`,
children: [
/* @__PURE__ */ jsx18("span", {
className: "hidden text-sm sm:inline",
children: weekDay
}),
/* @__PURE__ */ jsx18("span", {
className: "text-xs sm:hidden",
children: weekDays.shortDays[index]
})
]
}, weekDay)
}, weekDay))
});
};
// src/lib/constants.ts
var GAP_BETWEEN_ELEMENTS = 1;
var DAY_NUMBER_HEIGHT = 28;
var EVENT_BAR_HEIGHT = 24;
var WEEK_DAYS_NUMBER_MAP = {
sunday: 0,
monday: 1,
tuesday: 2,
wednesday: 3,
thursday: 4,
friday: 5,
saturday: 6
};
// src/hooks/useProcessedWeekEvents.ts
var useProcessedWeekEvents = ({
days
}) => {
const { getEventsForDateRange, dayMaxEvents } = useCalendarContext();
const weekStart = days[0].startOf("day");
const weekEnd = days[6].endOf("day");
const weekEvents = getEventsForDateRange(weekStart, weekEnd);
const multiDayEvents = weekEvents.filter((e) => e.end.diff(e.start, "day") > 0);
const singleDayEvents = weekEvents.filter((e) => e.end.diff(e.start, "day") === 0);
const sortedMultiDay = [...multiDayEvents].sort((a, b) => {
const startDiff = a.start.diff(b.start);
if (startDiff !== 0) {
return startDiff;
}
return b.end.diff(b.start) - a.end.diff(a.start);
});
const sortedSingleDay = [...singleDayEvents].sort((a, b) => a.start.diff(b.start));
const grid = [];
for (let row = 0;row < dayMaxEvents; row++) {
grid[row] = [];
for (let col = 0;col < 7; col++) {
grid[row][col] = { taken: false, event: null };
}
}
const processedEvents = [];
for (const event of sortedMultiDay) {
const eventStart = dayjs_config_default.max(event.start.startOf("day"), weekStart);
const eventEnd = dayjs_config_default.min(event.end.startOf("day"), weekEnd);
const startCol = Math.max(0, eventStart.diff(weekStart, "day"));
const endCol = Math.min(6, eventEnd.diff(weekStart, "day"));
let placedSuccessfully = false;
let assignedRow = -1;
for (let row = 0;row < dayMaxEvents; row++) {
let canPlace = true;
for (let col = startCol;col <= endCol; col++) {
if (grid[row][col].taken) {
canPlace = false;
break;
}
}
if (canPlace) {
assignedRow = row;
break;
}
}
if (assignedRow !== -1) {
for (let col = startCol;col <= endCol; col++) {
grid[assignedRow][col] = { taken: true, event };
}
const spanDays = endCol - startCol + 1;
processedEvents.push({
left: startCol / 7 * 100,
width: spanDays / 7 * 100,
top: DAY_NUMBER_HEIGHT + GAP_BETWEEN_ELEMENTS + assignedRow * (EVENT_BAR_HEIGHT + GAP_BETWEEN_ELEMENTS),
height: EVENT_BAR_HEIGHT,
position: assignedRow,
...event
});
placedSuccessfully = true;
}
if (!placedSuccessfully) {
for (let tryStartCol = startCol + 1;tryStartCol <= endCol; tryStartCol++) {
let truncatedAssignedRow = -1;
for (let row = 0;row < dayMaxEvents; row++) {
let canPlace = true;
for (let col = tryStartCol;col <= endCol; col++) {
if (grid[row][col].taken) {
canPlace = false;
break;
}
}
if (canPlace) {
truncatedAssignedRow = row;
break;
}
}
if (truncatedAssignedRow !== -1) {
for (let col = tryStartCol;col <= endCol; col++) {
grid[truncatedAssignedRow][col] = { taken: true, event };
}
const truncatedSpanDays = endCol - tryStartCol + 1;
processedEvents.push({
left: tryStartCol / 7 * 100,
width: truncatedSpanDays / 7 * 100,
top: DAY_NUMBER_HEIGHT + GAP_BETWEEN_ELEMENTS + truncatedAssignedRow * (EVENT_BAR_HEIGHT + GAP_BETWEEN_ELEMENTS),
height: EVENT_BAR_HEIGHT,
position: truncatedAssignedRow,
...event
});
placedSuccessfully = true;
break;
}
}
}
}
for (const event of sortedSingleDay) {
const eventStart = dayjs_config_default.max(event.start.startOf("day"), weekStart);
const col = Math.max(0, eventStart.diff(weekStart, "day"));
let assignedRow = -1;
for (let row = 0;row < dayMaxEvents; row++) {
if (!grid[row][col].taken) {
assignedRow = row;
break;
}
}
if (assignedRow !== -1) {
grid[assignedRow][col] = { taken: true, event };
processedEvents.push({
left: col / 7 * 100,
width: 1 / 7 * 100,
top: DAY_NUMBER_HEIGHT + GAP_BETWEEN_ELEMENTS + assignedRow * (EVENT_BAR_HEIGHT + GAP_BETWEEN_ELEMENTS),
height: EVENT_BAR_HEIGHT,
position: assignedRow,
...event
});
}
}
return processedEvents;
};
// src/features/month-view/components/week-events-layer/week-events-layer.tsx
import { jsx as jsx19 } from "react/jsx-runtime";
var WeekEventsLayer = ({ days }) => {
const weekStart = days[0];
const processedWeekEvents = useProcessedWeekEvents({ days });
return /* @__PURE__ */ jsx19("div", {
className: "relative w-full h-full pointer-events-none z-20 overflow-clip",
children: processedWeekEvents.map((event) => {
return /* @__PURE__ */ jsx19("div", {
className: "absolute z-10 pointer-events-auto overflow-clip",
style: {
left: `calc(${event.left}% + var(--spacing) * 0.25)`,
w