From ddbd56a0ac4050be5e15e38fd6bff85cd326dc24 Mon Sep 17 00:00:00 2001 From: kurihada Date: Wed, 17 Dec 2025 17:52:20 +0800 Subject: [PATCH] =?UTF-8?q?feat(ui):=20=E6=B7=BB=E5=8A=A0=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E6=A0=8F=E6=BA=A2=E5=87=BA=E8=8F=9C=E5=8D=95=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E5=93=8D=E5=BA=94=E5=BC=8F=E5=B8=83=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 DropdownMenu 基础组件(基于 Radix UI) - 新增 ToolbarOverflowMenu 组件(齿轮图标设置菜单) - 将 Header 次要按钮收入溢出菜单 - 保留 Diagnostics 和 Sessions 按钮始终可见 - 溢出菜单放置在最右侧 --- .../ui/src/components/ToolbarOverflowMenu.tsx | 66 ++++++++++++++ packages/ui/src/index.ts | 1 + packages/ui/src/primitives/DropdownMenu.tsx | 77 ++++++++++++++++ packages/ui/src/primitives/index.ts | 10 ++ packages/web/src/pages/Chat.tsx | 91 +++---------------- 5 files changed, 167 insertions(+), 78 deletions(-) create mode 100644 packages/ui/src/components/ToolbarOverflowMenu.tsx create mode 100644 packages/ui/src/primitives/DropdownMenu.tsx diff --git a/packages/ui/src/components/ToolbarOverflowMenu.tsx b/packages/ui/src/components/ToolbarOverflowMenu.tsx new file mode 100644 index 0000000..10d502d --- /dev/null +++ b/packages/ui/src/components/ToolbarOverflowMenu.tsx @@ -0,0 +1,66 @@ +/** + * ToolbarOverflowMenu Component + * + * 工具栏溢出菜单,用于在空间不足时收纳次要按钮 + */ + +import { Settings, type LucideIcon } from 'lucide-react'; +import { motion } from 'framer-motion'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '../primitives/DropdownMenu'; +import { cn } from '../utils/cn'; + +export interface ToolbarMenuItem { + /** 菜单项图标 */ + icon: LucideIcon; + /** 菜单项标签 */ + label: string; + /** 点击回调 */ + onClick?: () => void; +} + +interface ToolbarOverflowMenuProps { + /** 菜单项列表 */ + items: ToolbarMenuItem[]; + /** 自定义类名 */ + className?: string; +} + +export function ToolbarOverflowMenu({ items, className }: ToolbarOverflowMenuProps) { + // 过滤掉没有 onClick 的项 + const validItems = items.filter((item) => item.onClick); + + if (validItems.length === 0) { + return null; + } + + return ( + + + + + + + + {validItems.map((item) => ( + + + {item.label} + + ))} + + + ); +} diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index e7d2323..4164fc3 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -255,6 +255,7 @@ export { CodeEditor, getLanguageFromFilename, type EditorTab } from './component export { IDE } from './components/IDE.js'; export { StatusBar } from './components/StatusBar.js'; export { Resizer } from './components/Resizer.js'; +export { ToolbarOverflowMenu, type ToolbarMenuItem } from './components/ToolbarOverflowMenu.js'; // Toast function (re-export from sonner) export { toast } from 'sonner'; diff --git a/packages/ui/src/primitives/DropdownMenu.tsx b/packages/ui/src/primitives/DropdownMenu.tsx new file mode 100644 index 0000000..7c04e98 --- /dev/null +++ b/packages/ui/src/primitives/DropdownMenu.tsx @@ -0,0 +1,77 @@ +import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'; +import { forwardRef } from 'react'; +import { cn } from '../utils/cn'; + +export const DropdownMenu = DropdownMenuPrimitive.Root; +export const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; +export const DropdownMenuGroup = DropdownMenuPrimitive.Group; +export const DropdownMenuPortal = DropdownMenuPrimitive.Portal; + +export const DropdownMenuContent = forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)); +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName; + +export const DropdownMenuItem = forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } +>(({ className, inset, ...props }, ref) => ( + +)); +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName; + +export const DropdownMenuLabel = forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } +>(({ className, inset, ...props }, ref) => ( + +)); +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName; + +export const DropdownMenuSeparator = forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName; diff --git a/packages/ui/src/primitives/index.ts b/packages/ui/src/primitives/index.ts index 2dcdcab..b589a12 100644 --- a/packages/ui/src/primitives/index.ts +++ b/packages/ui/src/primitives/index.ts @@ -32,3 +32,13 @@ export { TooltipTrigger, TooltipContent, } from './Tooltip'; +export { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuGroup, + DropdownMenuPortal, +} from './DropdownMenu'; diff --git a/packages/web/src/pages/Chat.tsx b/packages/web/src/pages/Chat.tsx index 684fba3..7c1c2d1 100644 --- a/packages/web/src/pages/Chat.tsx +++ b/packages/web/src/pages/Chat.tsx @@ -15,6 +15,7 @@ import { ContextUsage, SubagentProgress, DiagnosticsIndicator, + ToolbarOverflowMenu, } from '@ai-assistant/ui'; interface ChatPageProps { @@ -157,84 +158,6 @@ export function ChatPage({ /> )} - {/* Checkpoints 按钮 */} - {onOpenCheckpoints && ( - - - - )} - - {/* Providers 按钮 */} - {onOpenProviders && ( - - - - )} - - {/* Agents 按钮 */} - {onOpenAgents && ( - - - - )} - - {/* Hooks 按钮 */} - {onOpenHooks && ( - - - - )} - - {/* MCP 按钮 */} - {onOpenMCP && ( - - - - )} - - {/* 命令按钮 */} - {onOpenCommands && ( - - - - )} - {/* Sessions 按钮 */} {onOpenSessions && ( )} + + {/* 设置菜单 - 齿轮图标,放在最右侧 */} + )}