Files
opsxshell/src/components/common/Modal.tsx
T
OpsXShell Dev e9a0d21758 feat(theme): 专业主题系统重构 — Warp/Linear 设计参考,深色/浅色完整适配
参考设计系统:
- 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
2026-04-25 19:31:51 +08:00

118 lines
2.8 KiB
TypeScript

/**
* 模态框组件
*
* 统一的对话框 UI
*/
import { type ReactNode, useEffect } from 'react'
import { X } from 'lucide-react'
import Button from './Button'
interface ModalProps {
/** 是否显示 */
isOpen: boolean
/** 标题 */
title: string
/** 内容 */
children: ReactNode
/** 确认按钮文本 */
confirmText?: string
/** 取消按钮文本 */
cancelText?: string
/** 确认回调 */
onConfirm?: () => void
/** 关闭回调 */
onClose: () => void
/** 是否显示确认按钮 */
showConfirm?: boolean
/** 是否危险操作 */
isDanger?: boolean
/** 加载状态 */
loading?: boolean
}
/**
* 模态框组件
*
* @example
* <Modal
* isOpen={isOpen}
* title="确认删除"
* onConfirm={handleDelete}
* onClose={() => setIsOpen(false)}
* isDanger
* >
* 确定要删除此会话吗?
* </Modal>
*/
export default function Modal({
isOpen,
title,
children,
confirmText = '确认',
cancelText = '取消',
onConfirm,
onClose,
showConfirm = true,
isDanger = false,
loading = false,
}: ModalProps) {
// ESC 键关闭
useEffect(() => {
const handleEsc = (e: KeyboardEvent) => {
if (e.key === 'Escape' && isOpen) {
onClose()
}
}
window.addEventListener('keydown', handleEsc)
return () => window.removeEventListener('keydown', handleEsc)
}, [isOpen, onClose])
if (!isOpen) return null
return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
{/* 遮罩 */}
<div
className="absolute inset-0 bg-surface-baselack/60 backdrop-blur-sm"
onClick={onClose}
/>
{/* 内容 */}
<div className="relative bg-dark-800 rounded-xl border border-dark-700 shadow-2xl w-full max-w-md">
{/* 头部 */}
<div className="flex items-center justify-between px-6 py-4 border-b border-dark-700">
<h3 className="text-lg font-semibold text-content-primary">{title}</h3>
<button
onClick={onClose}
className="p-1 hover:bg-dark-700 rounded-lg transition-colors"
>
<X className="w-5 h-5 text-txt-muted" />
</button>
</div>
{/* 内容 */}
<div className="px-6 py-4">
{children}
</div>
{/* 底部按钮 */}
<div className="flex justify-end gap-3 px-6 py-4 border-t border-dark-700">
<Button variant="ghost" onClick={onClose}>
{cancelText}
</Button>
{showConfirm && (
<Button
variant={isDanger ? 'danger' : 'primary'}
onClick={onConfirm}
loading={loading}
>
{confirmText}
</Button>
)}
</div>
</div>
</div>
)
}