列表引导改为手动触发且不跳转创建页;历史 Tab 展示会商步骤/链条与深度研究模板;Step2 增加暂停与内容高度跟随滚动,并修复旧草稿恢复兜底。 Co-authored-by: Cursor <cursoragent@cursor.com>
143 lines
5.5 KiB
TypeScript
143 lines
5.5 KiB
TypeScript
"use client";
|
|
|
|
import { Wrench, X } from "lucide-react";
|
|
import { useEffect, useRef, useState } from "react";
|
|
import { useLocation, useNavigate } from "react-router-dom";
|
|
|
|
import { cn } from "@/lib/utils";
|
|
|
|
import { useWorkspaceCompactNavItems } from "./workspace-nav-chat-list";
|
|
|
|
const TOOLBOX_ICON_TONES = [
|
|
"bg-blue-500/12 text-blue-600 dark:text-blue-400",
|
|
"bg-violet-500/12 text-violet-600 dark:text-violet-400",
|
|
"bg-emerald-500/12 text-emerald-600 dark:text-emerald-400",
|
|
"bg-amber-500/14 text-amber-600 dark:text-amber-400",
|
|
] as const;
|
|
|
|
/**
|
|
* 简洁模式右侧工具箱。
|
|
*
|
|
* 菜单来源与左侧简洁菜单、设置和更多完全一致,由管理员在菜单管理中把项目
|
|
* 放到 ``toolbox`` 泳道。默认展开常显(不再藏进右侧按钮后面);点 X 或 Esc
|
|
* 收起后右侧出现竖排按钮,可再次展开。
|
|
*/
|
|
export function CompactToolbox() {
|
|
const { toolboxItems } = useWorkspaceCompactNavItems();
|
|
const { pathname } = useLocation();
|
|
const navigate = useNavigate();
|
|
const [open, setOpen] = useState(true);
|
|
const triggerRef = useRef<HTMLButtonElement>(null);
|
|
|
|
useEffect(() => {
|
|
if (!open) return;
|
|
|
|
const handleKeyDown = (event: KeyboardEvent) => {
|
|
if (event.key === "Escape") {
|
|
setOpen(false);
|
|
triggerRef.current?.focus();
|
|
}
|
|
};
|
|
|
|
document.addEventListener("keydown", handleKeyDown);
|
|
return () => {
|
|
document.removeEventListener("keydown", handleKeyDown);
|
|
};
|
|
}, [open]);
|
|
|
|
if (toolboxItems.length === 0) return null;
|
|
|
|
const closeDrawer = () => {
|
|
setOpen(false);
|
|
requestAnimationFrame(() => triggerRef.current?.focus());
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<button
|
|
ref={triggerRef}
|
|
type="button"
|
|
aria-label="打开工具箱"
|
|
aria-controls="compact-toolbox-panel"
|
|
aria-expanded={open}
|
|
title="打开工具箱"
|
|
onClick={() => setOpen(true)}
|
|
className={cn(
|
|
"fixed right-0 top-1/2 z-[80] flex size-16 -translate-y-1/2 flex-col items-center justify-center gap-1 rounded-l-2xl border border-r-0 border-primary/25 bg-primary text-primary-foreground shadow-xl transition-[transform,opacity,background-color] duration-200 hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
open && "pointer-events-none translate-x-full opacity-0",
|
|
)}
|
|
>
|
|
<Wrench className="size-6 shrink-0" />
|
|
<span className="text-xs font-semibold leading-4">工具箱</span>
|
|
</button>
|
|
|
|
<aside
|
|
id="compact-toolbox-panel"
|
|
aria-label="工具箱"
|
|
aria-hidden={!open}
|
|
inert={!open}
|
|
className={cn(
|
|
"fixed right-0 top-1/2 z-[100] flex max-h-[calc(100vh-32px)] w-32 max-w-[calc(100vw-48px)] -translate-y-1/2 flex-col overflow-hidden rounded-l-2xl bg-background text-foreground shadow-2xl transition-transform duration-300 ease-out",
|
|
open
|
|
? "pointer-events-auto translate-x-0"
|
|
: "pointer-events-none translate-x-full",
|
|
)}
|
|
>
|
|
<header className="relative flex h-12 shrink-0 items-center justify-center px-2">
|
|
<h2 className="text-sm font-semibold">工具箱</h2>
|
|
<button
|
|
type="button"
|
|
aria-label="收起工具箱"
|
|
title="收起"
|
|
onClick={closeDrawer}
|
|
className="absolute right-2 flex size-7 shrink-0 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
>
|
|
<X className="size-4" />
|
|
</button>
|
|
</header>
|
|
|
|
<nav
|
|
aria-label="工具箱菜单"
|
|
className="flex min-h-0 flex-col items-center gap-2.5 overflow-y-auto bg-muted/20 p-2"
|
|
>
|
|
{toolboxItems.map((item, index) => {
|
|
const Icon = item.icon;
|
|
const active = item.match(pathname);
|
|
return (
|
|
<button
|
|
key={item.id}
|
|
type="button"
|
|
aria-current={active ? "page" : undefined}
|
|
className={cn(
|
|
"group flex h-24 w-24 aspect-square shrink-0 flex-col items-center justify-center gap-1.5 rounded-xl p-2 text-center transition-[color,background-color,transform] hover:-translate-y-0.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
active
|
|
? "bg-primary/12 text-primary ring-1 ring-inset ring-primary/25"
|
|
: "bg-muted/55 text-card-foreground hover:bg-primary/8 hover:text-primary",
|
|
)}
|
|
onClick={() => navigate(item.path ?? "/page/workspace/chats/new")}
|
|
>
|
|
<span
|
|
className={cn(
|
|
"flex size-10 shrink-0 items-center justify-center rounded-xl transition-transform group-hover:scale-105",
|
|
active
|
|
? "bg-primary text-primary-foreground"
|
|
: TOOLBOX_ICON_TONES[index % TOOLBOX_ICON_TONES.length],
|
|
)}
|
|
>
|
|
<Icon className="size-6" strokeWidth={1.8} />
|
|
</span>
|
|
<span
|
|
className="line-clamp-2 w-full text-center text-xs font-medium leading-4"
|
|
title={item.label}
|
|
>
|
|
{item.label}
|
|
</span>
|
|
</button>
|
|
);
|
|
})}
|
|
</nav>
|
|
</aside>
|
|
</>
|
|
);
|
|
}
|