参考设计系统: - Warp: 温暖暗色调、自然层次、克制配色 - Linear: 亮度阶梯层次、半透明边框、精确排版 核心改进: - app-theme.ts: 完整重写深色/浅色调色板 (+surface-hover/active, border-subtle/focus, feedback-bg, placeholder) - useTheme.ts: 修复硬编码 return 'dark',支持 dark/light/system 完整切换 - index.css: 消除硬编码颜色,全部使用 CSS 变量 - tailwind.config.js: 新增 txt/content/surface/border/ph/feedback 语义 token - 44 文件批量迁移: 292 处 text-gray/bg-gray/text-white/placeholder-gray → 主题感知语义 token | 44 files +611/-482 | tsc零错误 | 96/96 tests passing
90 lines
2.1 KiB
TypeScript
90 lines
2.1 KiB
TypeScript
/**
|
|
* TabContextMenu - 标签右键菜单
|
|
*/
|
|
|
|
import { useRef, useEffect } from 'react'
|
|
|
|
interface MenuItem {
|
|
label: string
|
|
action: () => void
|
|
danger?: boolean
|
|
separator?: boolean
|
|
}
|
|
|
|
interface TabContextMenuProps {
|
|
isOpen: boolean
|
|
position: { x: number; y: number }
|
|
onClose: () => void
|
|
onCloseTab: () => void
|
|
onCloseOthers: () => void
|
|
onCloseRight: () => void
|
|
onCloseLeft: () => void
|
|
onCloseAll: () => void
|
|
onClone?: () => void
|
|
}
|
|
|
|
export function TabContextMenu({
|
|
isOpen,
|
|
position,
|
|
onClose,
|
|
onCloseTab,
|
|
onCloseOthers,
|
|
onCloseRight,
|
|
onCloseLeft,
|
|
onCloseAll,
|
|
onClone,
|
|
}: TabContextMenuProps) {
|
|
const menuRef = useRef<HTMLDivElement>(null)
|
|
|
|
useEffect(() => {
|
|
const handleClickOutside = (e: MouseEvent) => {
|
|
if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
|
|
onClose()
|
|
}
|
|
}
|
|
document.addEventListener('mousedown', handleClickOutside)
|
|
return () => document.removeEventListener('mousedown', handleClickOutside)
|
|
}, [onClose])
|
|
|
|
if (!isOpen) return null
|
|
|
|
const items: MenuItem[] = [
|
|
{ label: '关闭', action: onCloseTab },
|
|
{ label: '关闭其他', action: onCloseOthers },
|
|
{ label: '关闭右侧标签', action: onCloseRight },
|
|
{ label: '关闭左侧标签', action: onCloseLeft },
|
|
{ label: '关闭全部', action: onCloseAll, danger: true },
|
|
]
|
|
|
|
if (onClone) {
|
|
items.unshift({ label: '克隆标签', action: onClone })
|
|
}
|
|
|
|
return (
|
|
<div
|
|
ref={menuRef}
|
|
style={{ left: position.x, top: position.y }}
|
|
className="fixed z-50 bg-dark-800 border border-dark-700 rounded-lg shadow-xl py-1 min-w-[160px]"
|
|
>
|
|
{items.map((item, index) => (
|
|
<button
|
|
key={index}
|
|
onClick={() => {
|
|
item.action()
|
|
onClose()
|
|
}}
|
|
className={`
|
|
w-full px-4 py-2 text-left text-sm transition-colors
|
|
${item.danger
|
|
? 'text-red-400 hover:bg-red-500/20'
|
|
: 'text-content-secondary hover:bg-dark-700'
|
|
}
|
|
`}
|
|
>
|
|
{item.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
)
|
|
}
|