参考设计系统: - 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
118 lines
2.8 KiB
TypeScript
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>
|
|
)
|
|
}
|