diff --git a/components/calendar/__tests__/calendar-app.test.tsx b/components/calendar/__tests__/calendar-app.test.tsx index 87de6b7da..bb1fce2ce 100644 --- a/components/calendar/__tests__/calendar-app.test.tsx +++ b/components/calendar/__tests__/calendar-app.test.tsx @@ -580,3 +580,14 @@ describe('CalendarApp in the demo', () => { } }); }); + +describe('CalendarApp view menu', () => { + it('opens as a menu the keyboard can use and switches the view', async () => { + renderApp(); + fireEvent.click(screen.getByTestId('calendar-view-menu'), { detail: 0 }); + const day = await screen.findByRole('menuitemradio', { name: /views\.day/ }); + await waitFor(() => expect(document.activeElement?.getAttribute('role')).toBe('menuitemradio')); + fireEvent.click(day); + expect(useCalendarStore.getState().viewMode).toBe('day'); + }); +}); diff --git a/components/calendar/calendar-app.tsx b/components/calendar/calendar-app.tsx index d02991ab5..3dce4c269 100644 --- a/components/calendar/calendar-app.tsx +++ b/components/calendar/calendar-app.tsx @@ -20,6 +20,7 @@ import { toast } from "@/stores/toast-store"; import { useIsDesktop, useIsMobile } from "@/hooks/use-media-query"; import { useAccountPrincipalEmails } from "@/hooks/use-account-principal-emails"; import { Button } from "@/components/ui/button"; +import { PaneSpinner, Spinner } from "@/components/ui/spinner"; import { CalendarToolbar } from "@/components/calendar/calendar-toolbar"; import { CalendarMonthView } from "@/components/calendar/calendar-month-view"; import { CalendarWeekView } from "@/components/calendar/calendar-week-view"; @@ -1719,11 +1720,7 @@ export function CalendarApp({ linkSegments: routeSegments }: CalendarAppProps = const renderView = () => { if (isLoading && calendars.length === 0) { - return ( -
-

{t("status.loading_calendars")}

-
- ); + return ; } const viewContent = (() => { @@ -1867,7 +1864,7 @@ export function CalendarApp({ linkSegments: routeSegments }: CalendarAppProps = {viewContent} {isLoadingEvents && calendars.length > 0 && events.length === 0 && (
-
+
)}
diff --git a/components/calendar/calendar-sidebar-panel.tsx b/components/calendar/calendar-sidebar-panel.tsx index ae5f09512..b837980c2 100644 --- a/components/calendar/calendar-sidebar-panel.tsx +++ b/components/calendar/calendar-sidebar-panel.tsx @@ -416,7 +416,7 @@ export function CalendarSidebarPanel({ )} {canShare && ( { closeContextMenu(); onShareCalendar(cal); }} /> diff --git a/components/calendar/calendar-toolbar.tsx b/components/calendar/calendar-toolbar.tsx index b5b40bad0..543c938e9 100644 --- a/components/calendar/calendar-toolbar.tsx +++ b/components/calendar/calendar-toolbar.tsx @@ -101,8 +101,12 @@ export function CalendarToolbar({ const menuViews: CalendarViewMode[] = enableCalendarTasks ? ["day", "week", "month", "agenda", "tasks"] : ["day", "week", "month", "agenda"]; - const [showViewMenu, setShowViewMenu] = useState(false); - const viewMenuRef = useRef(null); + const { + contextMenu: viewMenu, + openContextMenuFrom: openViewMenuFrom, + closeContextMenu: closeViewMenu, + menuRef: viewMenuRef, + } = useContextMenu(); const [showCalendarDropdown, setShowCalendarDropdown] = useState(false); const dropdownRef = useRef(null); @@ -182,25 +186,6 @@ export function CalendarToolbar({ ) : null; - useEffect(() => { - if (!showViewMenu) return; - function handleClickOutside(e: MouseEvent) { - if (viewMenuRef.current && !viewMenuRef.current.contains(e.target as Node)) { - setShowViewMenu(false); - } - } - function handleKey(e: KeyboardEvent) { - if (e.key === "Escape") { - setShowViewMenu(false); - } - } - document.addEventListener("mousedown", handleClickOutside); - document.addEventListener("keydown", handleKey); - return () => { - document.removeEventListener("mousedown", handleClickOutside); - document.removeEventListener("keydown", handleKey); - }; - }, [showViewMenu]); @@ -429,42 +414,39 @@ export function CalendarToolbar({ {moreButton} -
+
- {showViewMenu && ( -
- {menuViews.map((v) => ( - - ))} -
- )} + + {menuViews.map((v) => ( + + ))} +
); diff --git a/components/calendar/event-context-menu.tsx b/components/calendar/event-context-menu.tsx index 39e0d3213..81786575b 100644 --- a/components/calendar/event-context-menu.tsx +++ b/components/calendar/event-context-menu.tsx @@ -8,11 +8,11 @@ import { } from "@/components/ui/context-menu"; import { Pencil, + CopyPlus, Copy, Download, - ClipboardCopy, + Video, Link as LinkIcon, - CalendarArrowUp as CalendarLinkIcon, Trash2, } from "@/components/icons"; import type { CalendarEvent } from "@/lib/jmap/types"; @@ -69,7 +69,7 @@ export function EventContextMenu({ return ( {editable && } - + {hasMeetingLink && onCopyMeetingLink && ( @@ -91,7 +91,7 @@ export function EventContextMenu({ {/* Permalink into Bulwark (#733) - distinct from the meeting URL above, which points at whatever conferencing tool the organiser used. */} { void copyLink(buildCalendarPath({ diff --git a/components/calendar/event-detail-popover.tsx b/components/calendar/event-detail-popover.tsx index 42a90c0db..cec55f9c6 100644 --- a/components/calendar/event-detail-popover.tsx +++ b/components/calendar/event-detail-popover.tsx @@ -7,7 +7,7 @@ import { Button } from "@/components/ui/button"; import { LinkifiedText } from "@/components/ui/linkified-text"; import { X, Clock, MapPin, Video, Users, Repeat, Bell, AlignLeft, - Pencil, Trash2, Copy, Send, Check, MoreVertical, Calendar as CalendarIcon, + Pencil, Trash2, Copy, CopyPlus, Send, Check, MoreVertical, Calendar as CalendarIcon, } from "@/components/icons"; import { format, isSameDay } from "date-fns"; import { cn } from "@/lib/utils"; @@ -383,7 +383,7 @@ export function EventDetailPopover({ aria-label={onMore ? t("toolbar.more") : t("events.duplicate")} title={onMore ? t("toolbar.more") : t("events.duplicate")} > - {onMore ? : } + {onMore ? : } )} @@ -1621,7 +1621,7 @@ export function EventModal({ aria-label={t("events.duplicate")} className="w-full" > - + {t("events.duplicate")} )}