From 95d21c49a9797da5d9e9ee097a00a3f34ae209dd Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Sat, 10 Oct 2026 22:38:59 +0200 Subject: [PATCH] feat(calendar): open a day from the week header and show a clearer now line The week and day headers now show a small weekday above a large date, with today's date in a filled circle and its weekday in the primary colour. Clicking a date in the week header opens that day in the day view. The all-day row moved below the dates, where it lines up with the columns it belongs to. The current time line is thicker with a larger dot at the start of today's column, and events shorter than 45 minutes show their start time next to the title instead of hiding it. --- components/calendar/calendar-app.tsx | 12 +++ components/calendar/calendar-day-view.tsx | 33 +++++-- components/calendar/calendar-week-view.tsx | 106 ++++++++++++--------- components/calendar/event-card.tsx | 10 +- 4 files changed, 102 insertions(+), 59 deletions(-) diff --git a/components/calendar/calendar-app.tsx b/components/calendar/calendar-app.tsx index 67d00fd61..53aa4d2b8 100644 --- a/components/calendar/calendar-app.tsx +++ b/components/calendar/calendar-app.tsx @@ -566,6 +566,17 @@ export function CalendarApp({ linkSegments: routeSegments }: CalendarAppProps = setNarrowSidebarOpen(false); }, [setSelectedDate, jumpTo, isMobile, normalizedViewMode, setViewMode]); + // Clicking a date number in the week or month header opens that day in + // the day view, as in Google Calendar. + const handleOpenDay = useCallback((date: Date) => { + setMobileReturnToMonth(isMobile && normalizedViewMode === "month"); + jumpTo(date); + setSelectedDate(date); + setMiniMonth(date); + setViewMode("day"); + setNarrowSidebarOpen(false); + }, [jumpTo, setSelectedDate, setViewMode, isMobile, normalizedViewMode]); + // The mini calendar is a navigation control: unlike a click in the week or // month grid, which marks a day that is already on screen, picking a day // here has to bring that day into view. @@ -1485,6 +1496,7 @@ export function CalendarApp({ linkSegments: routeSegments }: CalendarAppProps = events={visibleEvents} calendars={allCalendars} onSelectDate={handleSelectDate} + onOpenDay={handleOpenDay} onSelectEvent={handleSelectEvent} onHoverEvent={handleHoverEvent} onHoverLeave={handleHoverLeave} diff --git a/components/calendar/calendar-day-view.tsx b/components/calendar/calendar-day-view.tsx index 412fefd28..5911fe3c7 100644 --- a/components/calendar/calendar-day-view.tsx +++ b/components/calendar/calendar-day-view.tsx @@ -313,13 +313,26 @@ export function CalendarDayView({ const dayTasks = tasksByDay.get(key) ?? []; return (
-
-

- {isMobile - ? intlFormatter.dateTime(day, { weekday: "short", month: "short", day: "numeric" }) - : intlFormatter.dateTime(day, { weekday: "long", month: "long", day: "numeric", year: "numeric" }) - } -

+
+
+ {intlFormatter.dateTime(day, { weekday: "short" })} +
+
+ {format(day, "d")} +
{hasAllDayArea && ( @@ -539,9 +552,9 @@ export function CalendarDayView({ className="absolute left-0 right-0 z-20 pointer-events-none" style={{ top: ((nowMinutes - hours.startMinutes) / 60) * HOUR_HEIGHT }} > -
-
-
+
+
+
)} diff --git a/components/calendar/calendar-week-view.tsx b/components/calendar/calendar-week-view.tsx index 6c73464a2..44f4c7a18 100644 --- a/components/calendar/calendar-week-view.tsx +++ b/components/calendar/calendar-week-view.tsx @@ -28,6 +28,8 @@ interface CalendarWeekViewProps extends ScrollWindowViewProps { events: CalendarEvent[]; calendars: Calendar[]; onSelectDate: (date: Date) => void; + /** Open a day in the day view (clicking its date in the header). */ + onOpenDay?: (date: Date) => void; onSelectEvent: (event: CalendarEvent, anchorRect: DOMRect) => void; onHoverEvent?: (event: CalendarEvent, anchorRect: DOMRect) => void; onHoverLeave?: () => void; @@ -63,6 +65,7 @@ export function CalendarWeekView({ isLoading = false, onVisibleDateChange, onSelectDate, + onOpenDay, onSelectEvent, onHoverEvent, onHoverLeave, @@ -384,6 +387,59 @@ export function CalendarWeekView({
+
+
+ {canToggleHours && ( + + )} +
+
+ {days.map((day) => { + const todayCol = isDisplayToday(day); + const selected = isSameDay(day, selectedDate); + const fullLabel = intlFormatter.dateTime(day, { weekday: "long", month: "long", day: "numeric", year: "numeric" }); + return ( +
+
+ {intlFormatter.dateTime(day, { weekday: "short" })} +
+ {/* The date opens that day in the day view. */} + +
+ ); + })} +
+
{hasAllDay && (
)} -
-
- {canToggleHours && ( - - )} -
-
- {days.map((day) => { - const todayCol = isDisplayToday(day); - const selected = isSameDay(day, selectedDate); - const fullLabel = intlFormatter.dateTime(day, { weekday: "long", month: "long", day: "numeric", year: "numeric" }); - return ( - - ); - })} -
-
@@ -673,9 +685,9 @@ export function CalendarWeekView({ className="absolute left-0 right-0 z-20 pointer-events-none" style={{ top: ((nowMinutes - hours.startMinutes) / 60) * HOUR_HEIGHT }} > -
-
-
+
+
+
)} diff --git a/components/calendar/event-card.tsx b/components/calendar/event-card.tsx index 722d00d86..66a09c008 100644 --- a/components/calendar/event-card.tsx +++ b/components/calendar/event-card.tsx @@ -218,8 +218,14 @@ export function EventCard({ event, calendar, variant, onClick, onMouseEnter, onM )} style={{ ...eventFillStyle(color, { inactive: isInactive, separated: true, selected: isSelected }), ...style }} > -
{event.title || t("events.no_title")}
- {/* Shorter blocks have no room for a second line at the 60px hour. */} + {/* Shorter blocks have no room for a second line at the 60px hour, so + their start time follows the title on the same line. */} +
+ {event.title || t("events.no_title")} + {!event.showWithoutTime && durationMinutes < 45 && ( + {safeFormat(startDate, timeFmt)} + )} +
{!event.showWithoutTime && durationMinutes >= 45 && (
{timeString}