Skip to content

Commit a52eb8f

Browse files
Show upcoming meeting chrome (#5610)
Render the imminent meeting chip and remove the calendar sync status chip from the sidebar timeline chrome.
1 parent a8aa2ed commit a52eb8f

7 files changed

Lines changed: 467 additions & 361 deletions

File tree

apps/desktop/src/main/body.tsx

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { useShell } from "~/contexts/shell";
2525
import { GlobalLiveTranscriptAccessory } from "~/session/components/bottom-accessory/global-live";
2626
import { useOpenNoteDialog } from "~/shared/open-note-dialog";
2727
import { useNewNote } from "~/shared/useNewNote";
28+
import { useSidebarUpcomingMeetingStatus } from "~/sidebar/timeline/upcoming-meeting";
2829
import {
2930
hasCustomSidebarTab,
3031
hasLeftSurfaceCustomSidebarTab,
@@ -65,6 +66,7 @@ export function ClassicMainBody() {
6566
});
6667
const mainAreaTopDrag = useMainAreaTopWindowDrag(enableMainAreaTopDrag);
6768
const update = useDesktopUpdateControl();
69+
const upcomingMeetingStatus = useSidebarUpcomingMeetingStatus();
6870
const createNewNote = useNewNote();
6971
const openNoteDialog = useOpenNoteDialog();
7072
const handleOpenNoteDialog = useCallback(() => {
@@ -91,6 +93,7 @@ export function ClassicMainBody() {
9193
onNewNote={createNewNote}
9294
onSearch={handleOpenNoteDialog}
9395
onToggleSidebar={leftsidebar.toggleExpanded}
96+
hasUpcomingMeeting={Boolean(upcomingMeetingStatus)}
9497
update={update}
9598
/>
9699
</div>
@@ -285,12 +288,14 @@ function isMainAreaWindowDrag(
285288
}
286289

287290
function SidebarTimelineChrome({
291+
hasUpcomingMeeting,
288292
onNewNote,
289293
onSearch,
290294
onToggleSidebar,
291295
sidebarExpanded,
292296
update,
293297
}: {
298+
hasUpcomingMeeting: boolean;
294299
onNewNote: () => void;
295300
onSearch: () => void;
296301
onToggleSidebar: () => void;
@@ -299,6 +304,13 @@ function SidebarTimelineChrome({
299304
}) {
300305
const updateVisible = Boolean(update.status && update.version);
301306
const showUpdateButton = sidebarExpanded && updateVisible;
307+
const collapsedBadge = !sidebarExpanded
308+
? hasUpcomingMeeting
309+
? "upcomingMeeting"
310+
: updateVisible
311+
? "update"
312+
: null
313+
: null;
302314

303315
return (
304316
<div
@@ -308,7 +320,7 @@ function SidebarTimelineChrome({
308320
<div data-tauri-drag-region className="flex items-center gap-0">
309321
<LeftSurfaceChromeButton
310322
ariaLabel={sidebarExpanded ? "Hide sidebar" : "Show sidebar"}
311-
badge={!sidebarExpanded && updateVisible}
323+
badge={collapsedBadge}
312324
onClick={onToggleSidebar}
313325
>
314326
{sidebarExpanded ? (
@@ -335,15 +347,17 @@ function SidebarTimelineChrome({
335347
);
336348
}
337349

350+
type LeftSurfaceChromeBadge = "update" | "upcomingMeeting";
351+
338352
function LeftSurfaceChromeButton({
339353
ariaLabel,
340-
badge = false,
354+
badge = null,
341355
children,
342356
disabled = false,
343357
onClick,
344358
}: {
345359
ariaLabel: string;
346-
badge?: boolean;
360+
badge?: LeftSurfaceChromeBadge | null;
347361
children: React.ReactNode;
348362
disabled?: boolean;
349363
onClick: () => void;
@@ -366,8 +380,15 @@ function LeftSurfaceChromeButton({
366380
{badge ? (
367381
<span
368382
aria-hidden="true"
369-
data-testid="collapsed-sidebar-update-badge"
370-
className="ring-background pointer-events-none absolute top-1 right-1 size-1.5 rounded-full bg-blue-500 ring-2"
383+
data-testid={
384+
badge === "upcomingMeeting"
385+
? "collapsed-sidebar-upcoming-meeting-badge"
386+
: "collapsed-sidebar-update-badge"
387+
}
388+
className={cn([
389+
"ring-background pointer-events-none absolute top-1 right-1 size-1.5 rounded-full ring-2",
390+
badge === "upcomingMeeting" ? "bg-red-500" : "bg-blue-500",
391+
])}
371392
/>
372393
) : null}
373394
</button>

apps/desktop/src/shared/main/body.test.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const mocks = vi.hoisted(() => ({
1919
startDragging: vi.fn().mockResolvedValue(undefined),
2020
canGoBack: false,
2121
canGoNext: false,
22+
upcomingMeetingStatus: null as null | { label: string; title: string },
2223
leftSidebarExpanded: true,
2324
sidebarUpdateControl: {
2425
status: null as null | "available" | "downloading" | "ready" | "failed",
@@ -77,6 +78,10 @@ vi.mock("~/main/update-banner", () => ({
7778
useDesktopUpdateControl: () => mocks.sidebarUpdateControl,
7879
}));
7980

81+
vi.mock("~/sidebar/timeline/upcoming-meeting", () => ({
82+
useSidebarUpcomingMeetingStatus: () => mocks.upcomingMeetingStatus,
83+
}));
84+
8085
vi.mock("~/main/shell-sidebar", () => ({
8186
ClassicMainSidebar: () => <div data-testid="main-sidebar" />,
8287
}));
@@ -151,6 +156,7 @@ describe("ClassicMainBody", () => {
151156
mocks.startDragging.mockClear();
152157
mocks.canGoBack = false;
153158
mocks.canGoNext = false;
159+
mocks.upcomingMeetingStatus = null;
154160
mocks.leftSidebarExpanded = true;
155161
mocks.sidebarUpdateControl.status = null;
156162
mocks.sidebarUpdateControl.version = null;
@@ -322,6 +328,30 @@ describe("ClassicMainBody", () => {
322328
expect(mocks.toggleLeftSidebar).toHaveBeenCalledTimes(1);
323329
});
324330

331+
it("shows a red upcoming meeting badge on the collapsed sidebar toggle", () => {
332+
mocks.leftSidebarExpanded = false;
333+
mocks.sidebarUpdateControl.status = "available";
334+
mocks.sidebarUpdateControl.version = "1.0.34";
335+
mocks.upcomingMeetingStatus = {
336+
label: "Starts in 3m",
337+
title: "Devtool design sync",
338+
};
339+
340+
render(<ClassicMainBody />);
341+
342+
const sidebarToggle = screen.getByRole("button", { name: "Show sidebar" });
343+
const badge = within(sidebarToggle).getByTestId(
344+
"collapsed-sidebar-upcoming-meeting-badge",
345+
);
346+
347+
expect(badge).toBeTruthy();
348+
expect(badge.className.split(" ")).toContain("bg-red-500");
349+
expect(badge.className.split(" ")).not.toContain("bg-blue-500");
350+
expect(
351+
within(sidebarToggle).queryByTestId("collapsed-sidebar-update-badge"),
352+
).toBeNull();
353+
});
354+
325355
it("keeps sidebar chrome for changelog tabs", () => {
326356
mocks.currentTab = {
327357
active: true,

0 commit comments

Comments
 (0)