// The rail's sections that surfaces fill in: the brand row with the workspace // switcher slot, and the primary navigation with "New session" plus the // surface's own entries. `SessionShell` composes them. import { ChevronsLeftIcon, ChevronsRightIcon, SquarePenIcon, XIcon } from "lucide-react"; import { createContext, useContext, type ReactNode } from "react"; import { BrandMark, Wordmark } from "../brand-mark"; import { useRail } from "./rail-context"; import { NewSessionLink } from "./session-list"; import { Button } from "../../../ui/button"; import { Tooltip, TooltipContent, TooltipTrigger } from "../../../ui/tooltip"; import { useActionShortcut } from "../../lib/actions/actions-provider"; import { TooltipWithKeys } from "../command-menu/keycaps"; import { Link, useRouterState } from "../../lib/navigation"; import { cn } from "../../lib/utils"; import { windowControlsInPage } from "../../lib/desktop"; import { useSessionUiFeature } from "../../lib/session-ui-features"; /** * The surface-owned sections of the session shell. Every slot is optional: a * surface without a workspace switcher, workspace navigation or footer simply * leaves them out and the shell keeps its layout. */ export type SessionShellSlots = Readonly<{ /** The workspace switcher under the brand row (or under the window controls). */ switcher?: ReactNode; /** Primary navigation after "New session" (inbox, workspace shortcuts). */ primaryNav?: ReactNode; /** * The phone drawer's "Workspace" tab. Without it, phones show the sessions * list alone, with no tabs. */ mobileWorkspacePanel?: ReactNode; /** Pinned under the rail's scroll viewport: account menu, offers. */ railFooter?: ReactNode; /** Notices across the top of the canvas, under the top strip. */ canvasBanners?: ReactNode; }>; export const SessionShellSlotsContext = createContext({}); /** Supplies the slots to a rail rendered outside `SessionShell` (previews, fixtures). */ export function SessionShellSlotsProvider({ slots, children, }: { slots: SessionShellSlots; children: ReactNode; }) { return ( {children} ); } /** * The top of the main rail: a brand row (the mark and the "Opengeni" wordmark) * and, under it, the workspace picker at full rail width. Its spacing matches * the settings rail's top (12px, 32px brand row, 12px gap) so switching * between the two rails doesn't jump. The collapse toggle sits at the right end * of the brand row; collapsed, only the mark stays, with the expand toggle * under it. In the macOS app the row holds just the toggle beside the window * controls. */ export function RailHeader({ showSwitcher = true }: { showSwitcher?: boolean }) { const rail = useRail(); const { switcher } = useContext(SessionShellSlotsContext); const sidebarKeys = useActionShortcut("view.sidebar"); // Without the brand (an embed, or the macOS app whose window controls sit // in the row), the row holds just the collapse toggle, at its end. const brand = useSessionUiFeature("brand"); const brandless = windowControlsInPage || !brand; const collapseToggle = !rail.isMobile ? ( ) : null; return (
{brandless ? null : ( {!rail.collapsed ? : null} )} {!rail.collapsed || brandless ? collapseToggle : null} {rail.isMobile ? ( ) : null}
{rail.collapsed && collapseToggle && !brandless ? (
{collapseToggle}
) : null} {showSwitcher && !rail.collapsed ? switcher : null}
); } /** "New session" followed by the surface's own primary navigation. */ export function PrimaryNav() { const rail = useRail(); const { primaryNav } = useContext(SessionShellSlotsContext); const pathname = useRouterState({ select: (state) => state.location.pathname }); const newSessionActive = pathname === `/workspaces/${rail.workspaceId}/sessions`; const newChatShortcut = useActionShortcut("chat.new"); return (
{primaryNav}
); }