返回 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={<div className="project-tree__empty project-tree__empty--subtle" role="status">{t("projectTree.loadingProjects")}</div>}>
49 <ProjectTree {...props.projectTree} />
50 </Suspense>
51 </section>
52 <nav className="sidebar__nav sidebar__nav--footer">
53 <div className="sidebar__utility-row" aria-label={t("sidebar.utilityActions")}>
54 <UtilityButton
55 label={t("shortcuts.action.commandPalette")}
56 tooltip={`${t("shortcuts.action.commandPalette")} ${props.paletteShortcut}`}
57 icon={<Search size={16} />}
58 onClick={props.onOpenPalette}
59 />
60 <UtilityButton label={t("sidebar.trash")} icon={<Trash2 size={16} />} onClick={props.onOpenTrash} />
61 <UtilityButton label={t("heartbeat.scheduler")} icon={<AlarmClock size={16} />} onClick={props.onOpenAutomation} />
62 <UtilityButton label={t("topbar.settings")} icon={<Settings size={16} />} onClick={() => props.onOpenSettings("general")} />
63 </div>
64 </nav>
65 </aside>
66 <button className="sidebar-resizer" type="button" role="separator" aria-orientation="vertical" aria-label={t("sidebar.resize")}
67 aria-valuemin={props.resize.min} aria-valuemax={props.resize.max} aria-valuenow={props.resize.value}
68 onPointerDown={props.resize.onPointerDown} onKeyDown={props.resize.onKeyDown} onDoubleClick={props.resize.onReset} />
69 </>
70 );
71 }
72
73 function UtilityButton({ icon, label, tooltip = label, onClick }: { icon: ReactNode; label: string; tooltip?: string; onClick: () => void }) {
74 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>;
75 }
76
76 lines Plain Text