evOo-local/apps/web/src/pages/shell/sidebar-header.tsx
Claude 3c678519a6
refactor(web): extract ShellPage sidebar JSX into components
ShellPage's JSX carried the whole sidebar inline, including two near-copies
of the group header and of the main header's sidebar toggles, which made the
render hard to follow and easy to change in one place only.

Move-only: the sidebar header, group header (one component for the
conversation and project variants), the team and project roster rows, the
archived section, the user menu and the sidebar toggle buttons now live in
pages/shell. DOM, classNames, test ids, aria attributes, handlers and messages
are unchanged; the catalogs only update their file references, and the
desktop source checks for the header toggles now read the file they moved to.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-04 10:08:43 +00:00

119 lines
3.8 KiB
TypeScript

import { useLingui } from "@lingui/react/macro";
import type { Bot } from "@rakazo/contracts";
import { Popover, PopoverContent, PopoverTrigger, Wordmark } from "@rakazo/ui-web";
import { Bell } from "lucide-react";
import { WindowChrome } from "../WindowChrome";
import { BotCreatePicker } from "./bot-picker";
/** Window chrome, wordmark, the activity toggle and the create menu atop the sidebar. */
export function SidebarHeader({
activityMode,
onToggleActivityMode,
createMenuOpen,
onCreateMenuOpenChange,
onCloseMobileSidebar,
bots,
onCreateBot,
onOpenBot,
onCreateGroup,
onCreateSpace,
onShowGroupInfo,
onShowSpaceInfo,
}: {
activityMode: boolean;
onToggleActivityMode: () => void;
createMenuOpen: boolean;
onCreateMenuOpenChange: (open: boolean) => void;
onCloseMobileSidebar: () => void;
bots: Bot[];
onCreateBot: () => void;
onOpenBot: (id: string) => void;
onCreateGroup: () => void;
onCreateSpace: () => void;
onShowGroupInfo: () => void;
onShowSpaceInfo: () => void;
}) {
const { t } = useLingui();
return (
<div className="app-drag flex items-center justify-between px-4 lg:px-6 2xl:px-8 pb-3 pt-4">
<WindowChrome />
<div className="flex-1 px-1">
<Wordmark />
</div>
<div className="relative flex items-center gap-2.5">
<button
type="button"
aria-label={t`Activity`}
aria-pressed={activityMode}
title={t`Activity`}
data-activity-mode={activityMode ? "on" : "off"}
onClick={onToggleActivityMode}
className={`app-no-drag flex size-control-sm items-center justify-center rounded-full ${
activityMode
? "bg-primary text-primary-foreground"
: "rk-sidebar-muted hover:text-foreground/75"
}`}
>
<Bell
strokeWidth={1.8}
fill={activityMode ? "currentColor" : "none"}
aria-hidden="true"
className="size-icon-md"
/>
</button>
<Popover open={createMenuOpen} onOpenChange={onCreateMenuOpenChange}>
<PopoverTrigger
className="app-no-drag text-[21px] rk-sidebar-muted hover:text-foreground/75"
title={t`Create`}
data-testid="create-menu-trigger"
>
+
</PopoverTrigger>
{/* Unmount with the state change so the panel it opens never coexists with the menu. */}
{createMenuOpen ? (
<PopoverContent
align="end"
className="app-no-drag w-auto gap-0 overflow-hidden p-0 data-closed:animate-none"
>
<BotCreatePicker
bots={bots}
onCreateBot={() => {
onCreateMenuOpenChange(false);
onCloseMobileSidebar();
onCreateBot();
}}
onOpenBot={(id) => {
onCreateMenuOpenChange(false);
onCloseMobileSidebar();
onOpenBot(id);
}}
onCreateGroup={() => {
onCreateMenuOpenChange(false);
onCloseMobileSidebar();
onCreateGroup();
}}
onCreateSpace={() => {
onCreateMenuOpenChange(false);
onCloseMobileSidebar();
onCreateSpace();
}}
onShowGroupInfo={() => {
onCreateMenuOpenChange(false);
onCloseMobileSidebar();
onShowGroupInfo();
}}
onShowSpaceInfo={() => {
onCreateMenuOpenChange(false);
onCloseMobileSidebar();
onShowSpaceInfo();
}}
/>
</PopoverContent>
) : null}
</Popover>
</div>
</div>
);
}