返回 DeepSeek-Reasonix
SidebarRegion.tsx
根目录 / desktop / frontend / src / app-shell / SidebarRegion.tsx
1 import { lazy, Suspense, type ComponentProps, type KeyboardEvent, type PointerEvent, type ReactNode } from "react";
2 import { AlarmClock, MessageSquare, Search, Settings, Trash2 } from "lucide-react";
3 import { Tooltip } from "../components/Tooltip";
4 import type { Translator } from "../lib/i18n";
5 import type { SettingsTab } from "../lib/types";
6 import logoWordmark from "../assets/logo-wordmark.svg";
7
8 const ProjectTree = lazy(() => import("../components/ProjectTree").then((module) => ({ default: module.ProjectTree })));
9
10 export type SidebarRegionProps = {
11 className: string;
12 collapsed: boolean;
13 resize: {
14 min: number;
15 max: number;
16 value: number;
17 onPointerDown: (event: PointerEvent<HTMLButtonElement>) => void;
18 onKeyDown: (event: KeyboardEvent<HTMLButtonElement>) => void;
19 onReset: () => void;
20 };
21 projectTree: ComponentProps<typeof ProjectTree>;
22 t: Translator;
23 onNewSession: () => void;
24 onOpenPalette: () => void;
25 paletteShortcut: string;
26 onOpenTrash: () => void;
27 onOpenAutomation: () => void;
28 onOpenSettings: (tab: SettingsTab) => void;
29 };
30
31 /** Workbench sidebar: brand, quick new-session, project tree and utility nav. */
32 export function SidebarRegion(props: SidebarRegionProps) {
33 const { t } = props;
34 return (
35 <>
36 <aside className={props.className} aria-label={t("sidebar.navigation")}>
37 <div className="sidebar__head" aria-hidden={props.collapsed}>
38 <div className="sidebar__brand sidebar__brand--workbench">
39 <img src={logoWordmark} alt="Reasonix" className="sidebar__brand-logo sidebar__brand-logo--workbench" draggable={false} />
40 </div>
41 </div>
42 <div className="sidebar__quick-actions">
43 <button className="sidebar__quick-action" type="button" onClick={props.onNewSession}>
44 <MessageSquare size={18} aria-hidden="true" /><span>{t("topbar.newSession")}</span>
45 </button>
46 </div>
47 <section className="sidebar__section sidebar__section--projects">
48 <Suspense fallback={null}><ProjectTree {...props.projectTree} /></Suspense>
49 </section>
50 <nav className="sidebar__nav sidebar__nav--footer">
51 <div className="sidebar__utility-row" aria-label={t("sidebar.utilityActions")}>
52 <UtilityButton
53 label={t("shortcuts.action.commandPalette")}
54 tooltip={`${t("shortcuts.action.commandPalette")} ${props.paletteShortcut}`}
55 icon={<Search size={16} />}
56 onClick={props.onOpenPalette}
57 />
58 <UtilityButton label={t("sidebar.trash")} icon={<Trash2 size={16} />} onClick={props.onOpenTrash} />
59 <UtilityButton label={t("heartbeat.scheduler")} icon={<AlarmClock size={16} />} onClick={props.onOpenAutomation} />
60 <UtilityButton label={t("topbar.settings")} icon={<Settings size={16} />} onClick={() => props.onOpenSettings("general")} />
61 </div>
62 </nav>
63 </aside>
64 <button className="sidebar-resizer" type="button" role="separator" aria-orientation="vertical" aria-label={t("sidebar.resize")}
65 aria-valuemin={props.resize.min} aria-valuemax={props.resize.max} aria-valuenow={props.resize.value}
66 onPointerDown={props.resize.onPointerDown} onKeyDown={props.resize.onKeyDown} onDoubleClick={props.resize.onReset} />
67 </>
68 );
69 }
70
71 function UtilityButton({ icon, label, tooltip = label, onClick }: { icon: ReactNode; label: string; tooltip?: string; onClick: () => void }) {
72 return <Tooltip label={tooltip} fill side="top"><button className="sidebar__utility-button" type="button" aria-label={label} onClick={onClick}>{icon}<span className="sr-only">{label}</span></button></Tooltip>;
73 }
74
74 lines Plain Text