feat(calendar): add drag-create, resize, recurring scope, quick-create, and duplication

- Click-drag on empty time slots to create events with pre-filled time range
- Resize events by dragging bottom edge (15-min snap, optimistic JMAP update)
- Recurring event edit/delete scope dialog (this/following/all occurrences)
- Double-click quick event creation with inline title input
- Event duplication button in modal (+1 day offset)
- Shared interaction hook for pointer-based calendar interactions

Fixes #13
This commit is contained in:
Matthieu MALVACHE
2026-02-22 17:32:10 +01:00
committed by Matthieu MALVACHE
parent fb7cfe1634
commit ccbab654f3
22 changed files with 2011 additions and 322 deletions
+5 -4
View File
@@ -12,7 +12,7 @@ interface EventCardProps {
event: CalendarEvent;
calendar?: Calendar;
variant: "chip" | "block";
onClick?: () => void;
onClick?: (anchorRect: DOMRect) => void;
isSelected?: boolean;
draggable?: boolean;
}
@@ -100,7 +100,7 @@ export function EventCard({ event, calendar, variant, onClick, isSelected, dragg
if (variant === "chip") {
return (
<button
onClick={(e) => { e.stopPropagation(); onClick?.(); }}
onClick={(e) => { e.stopPropagation(); onClick?.(e.currentTarget.getBoundingClientRect()); }}
aria-label={ariaLabel}
{...dragProps}
className={cn(
@@ -123,11 +123,12 @@ export function EventCard({ event, calendar, variant, onClick, isSelected, dragg
return (
<button
onClick={(e) => { e.stopPropagation(); onClick?.(); }}
onClick={(e) => { e.stopPropagation(); onClick?.(e.currentTarget.getBoundingClientRect()); }}
aria-label={ariaLabel}
{...dragProps}
data-calendar-event
className={cn(
"w-full text-left rounded px-1.5 py-0.5 text-xs overflow-hidden",
"w-full h-full text-left rounded px-1.5 py-0.5 text-xs overflow-hidden",
"hover:opacity-90 transition-opacity cursor-pointer",
isSelected && "ring-2 ring-primary",
isBeingDragged && "opacity-50"