diff --git a/components/calendar/calendar-app.tsx b/components/calendar/calendar-app.tsx index cfbce5667..b19cfff9c 100644 --- a/components/calendar/calendar-app.tsx +++ b/components/calendar/calendar-app.tsx @@ -92,6 +92,8 @@ import { import { useLiteLinkSegments } from "@/hooks/use-lite-link-segments"; import { findMeetingLink } from "@/lib/event-links"; import { useDocumentTitle } from "@/hooks/use-document-title"; +import { MvTopBar } from "@/components/layout/mv-top-bar"; +import { useMountainViewShell } from "@/hooks/use-mountain-view-shell"; type PendingScopeAction = | { type: "edit"; event: CalendarEvent; updates: Partial; sendScheduling?: boolean } @@ -119,6 +121,8 @@ export function CalendarApp({ linkSegments: routeSegments }: CalendarAppProps = const isMobile = useIsMobile(); const isDesktop = useIsDesktop(); const isEmbedded = useIsEmbedded(); + // Mountain View's desktop chrome: its top bar in place of the left rail. + const mvShell = useMountainViewShell(isEmbedded); // When the pane (Pro shell) or window is narrower than `lg`, the sidebar // collapses into a burger-toggled overlay instead of taking inline space. const isNarrow = !isDesktop; @@ -1571,9 +1575,18 @@ export function CalendarApp({ linkSegments: routeSegments }: CalendarAppProps =
{refreshIndicator} + {mvShell && ( + + )}
{/* Left Navigation Rail (hidden when embedded in Pro shell) */} - {!isMobile && !isEmbedded && ( + {!isMobile && !isEmbedded && !mvShell && (
{refreshIndicator} + {mvShell && ( + + )}
{/* Navigation Rail - desktop only (hidden when embedded in Pro shell) */} - {!isMobile && !isEmbedded && ( + {!isMobile && !isEmbedded && !mvShell && (
(() => loadFilesSettings().folderLayout); // The account the files client was last initialised for (`undefined` = never). // Tracked so a Pro-shell account switch re-initialises instead of showing the @@ -573,8 +577,17 @@ export function FilesApp({ linkSegments: routeSegments }: FilesAppProps = {}) {
{refreshIndicator} + {mvShell && ( + + )}
- {!isMobile && !isEmbedded && ( + {!isMobile && !isEmbedded && !mvShell && (
void; - onSearchSubmit: (query: string) => void; - onSearchClear: () => void; +/** Mail search, which only the mail surface puts into the bar. */ +interface MvTopBarSearch { + query: string; + onQueryChange: (value: string) => void; + onSubmit: (query: string) => void; + onClear: () => void; onSelectContact: (contact: ContactSuggestion, field: ContactSearchField) => void; /** Opens the advanced-filter panel that lives under the message list. */ onToggleFilters: () => void; filtersOpen: boolean; activeFilterCount: number; - searchDisabled?: boolean; - onShowShortcuts?: () => void; - onManageApps?: () => void; - onInlineApp?: (appId: string, url: string, name: string) => void; - onCloseInlineApp?: () => void; - activeAppId?: string | null; - /** Opens the merged across-accounts inbox from the account popover. */ - onSelectAllInboxes?: () => void; - allInboxesSelected?: boolean; + disabled?: boolean; /** * Receives the node under the search field that the advanced-filter panel is * portalled into, so the panel drops from the field itself; without this @@ -58,23 +51,36 @@ interface MvTopBarProps { onFilterAnchorChange?: (node: HTMLDivElement | null) => void; } +interface MvTopBarProps { + /** + * The mail search field. Other surfaces leave it out and keep their own + * search where it was; the bar then carries the brand, the app grid and the + * account avatar only. + */ + search?: MvTopBarSearch; + /** The hamburger folds the mail navigation, so only mail shows it. */ + showMenuButton?: boolean; + onShowShortcuts?: () => void; + onManageApps?: () => void; + onInlineApp?: (appId: string, url: string, name: string) => void; + onCloseInlineApp?: () => void; + activeAppId?: string | null; + /** Opens the merged across-accounts inbox from the account popover. */ + onSelectAllInboxes?: () => void; + allInboxesSelected?: boolean; +} + /** * Mountain View's global header: one 64px row that owns the hamburger, the * brand, the search field and the account cluster. It only renders on - * desktop under `interfaceLayout === 'mountain-view'`; the default layout keeps - * search inside the list column and navigation in the left rail, and neither - * of those is touched here. + * desktop under `interfaceLayout === 'mountain-view'` (see + * useMountainViewShell), on every surface, in place of the left rail; the + * default layout keeps search inside the list column and navigation in the + * left rail, and neither of those is touched here. */ export function MvTopBar({ - searchQuery, - onSearchQueryChange, - onSearchSubmit, - onSearchClear, - onSelectContact, - onToggleFilters, - filtersOpen, - activeFilterCount, - searchDisabled = false, + search, + showMenuButton = true, onShowShortcuts, onManageApps, onInlineApp, @@ -82,7 +88,6 @@ export function MvTopBar({ activeAppId, onSelectAllInboxes, allInboxesSelected = false, - onFilterAnchorChange, }: MvTopBarProps) { const t = useTranslations("sidebar"); const tSearch = useTranslations("advanced_search"); @@ -96,6 +101,8 @@ export function MvTopBar({ const calendarEnabled = usePolicyStore((s) => s.isFeatureEnabled("calendarEnabled")); const contactsEnabled = usePolicyStore((s) => s.isFeatureEnabled("contactsEnabled")); const filesEnabled = usePolicyStore((s) => s.isFeatureEnabled("filesEnabled")); + // The same gate the navigation rail puts on its "add app" button. + const sidebarAppsEnabled = usePolicyStore((s) => s.isFeatureEnabled("sidebarAppsEnabled")); const sidebarApps = useResolvedSidebarApps(); const [appsOpen, setAppsOpen] = useState(false); @@ -147,15 +154,17 @@ export function MvTopBar({ data-mv-topbar="" > {/* Brand block: occupies the sidebar's own column width */} -
- +
+ {showMenuButton && ( + + )} {logoUrl ? : null} {appName} @@ -163,48 +172,50 @@ export function MvTopBar({
{/* Search: left-anchored and capped, not centred */} -
- - + {search && ( +
+ + - {/* Anchor for the advanced-filter panel: the search field's own box, - filter button included, so the panel shares the field's edges. It - only marks the spot; the panel itself is drawn at the end of . */} -
-
+ {/* Anchor for the advanced-filter panel: the search field's own box, + filter button included, so the panel shares the field's edges. It + only marks the spot; the panel itself is drawn at the end of . */} +
+
+ )} {/* Spacer keeps the right cluster pinned while search stays left */}
@@ -306,7 +317,7 @@ export function MvTopBar({ ); })}
- {onManageApps && ( + {sidebarAppsEnabled && onManageApps && (