From f94f4fc605905816a1fb64916f3ef122dfe6f657 Mon Sep 17 00:00:00 2001
From: Linus Rath <139418639+rathlinus@users.noreply.github.com>
Date: Sat, 10 Oct 2026 23:46:37 +0200
Subject: [PATCH] feat(calendar): match the calendar layout to the rest of the
app
The calendar bar spanned both the sidebar and the grid, and the sidebar
opened with a full-width primary "Create event" button. Neither exists
in mail or contacts, so the calendar looked like a different app.
- the sidebar runs full height with a title row and a "+" button, like
the contacts sidebar
- the view bar sits over the grid only and uses the density header
padding, so its border lines up with the sidebar header
- calendar rows and section headers follow the contacts sidebar: full
width 32px rows, chevron on the left
---
components/calendar/calendar-app.tsx | 26 +++++-------
.../calendar/calendar-sidebar-header.tsx | 39 ++++++++++++++++++
.../calendar/calendar-sidebar-panel.tsx | 40 ++++++++++---------
components/calendar/calendar-toolbar.tsx | 2 +-
4 files changed, 72 insertions(+), 35 deletions(-)
create mode 100644 components/calendar/calendar-sidebar-header.tsx
diff --git a/components/calendar/calendar-app.tsx b/components/calendar/calendar-app.tsx
index e7f982bab..8edb59548 100644
--- a/components/calendar/calendar-app.tsx
+++ b/components/calendar/calendar-app.tsx
@@ -75,6 +75,7 @@ import { ConfirmDialog } from "@/components/ui/confirm-dialog";
import { useConfirmDialog } from "@/hooks/use-confirm-dialog";
import { SchedulingDeniedError } from "@/lib/jmap/scheduling-error";
import { CreateCalendarModal } from "@/components/calendar/create-calendar-modal";
+import { CalendarSidebarHeader } from "@/components/calendar/calendar-sidebar-header";
import { getUserParticipantId, collectUserCalendarAddresses } from "@/lib/calendar-participants";
import { generateBirthdayEvents, createBirthdayCalendar, BIRTHDAY_CALENDAR_ID } from "@/lib/birthday-calendar";
import { sharedCalendarColorKey, pickUnusedCalendarColor } from "@/lib/shared-calendar-colors";
@@ -1774,15 +1775,13 @@ export function CalendarApp({ linkSegments: routeSegments }: CalendarAppProps =
{!inlineApp && (
- {/* Desktop: the bar spans the sidebar and the grid, as in Google Calendar. */}
- {!isMobile && calendarToolbar}
{/* Sidebar - in-flow when desktop pane, overlay when narrow */}
{(isNarrow || !sidebarCollapsed) && (
<>
{!isNarrow && (
- {isMobile && calendarToolbar}
+ {calendarToolbar}
{/* isolate keeps the views' sticky headers (z-50) below the toolbar's
import dropdown, which overlaps this area (#1049). */}
diff --git a/components/calendar/calendar-sidebar-header.tsx b/components/calendar/calendar-sidebar-header.tsx
new file mode 100644
index 000000000..79272d9aa
--- /dev/null
+++ b/components/calendar/calendar-sidebar-header.tsx
@@ -0,0 +1,39 @@
+"use client";
+
+import { useTranslations } from "next-intl";
+import { Button } from "@/components/ui/button";
+import { Plus } from "@/components/icons";
+
+interface CalendarSidebarHeaderProps {
+ onCreateEvent: () => void;
+ /** Mark the button for the guided tour; off while the toolbar's own "+" is the visible one. */
+ tourTarget?: boolean;
+}
+
+/**
+ * Title row at the top of the calendar sidebar, the same shape as the
+ * contacts sidebar header: the app name on the left and "+" on the right.
+ */
+export function CalendarSidebarHeader({ onCreateEvent, tourTarget = true }: CalendarSidebarHeaderProps) {
+ const t = useTranslations("calendar");
+
+ return (
+