feat(ui): 实现深色/浅色主题切换功能
- 添加 CSS 变量定义浅色和深色主题色板 - 扩展 Tailwind 配置支持语义化颜色 (surface-*, fg-*, line-*, code) - 创建 useTheme hook 管理主题状态和持久化 - 创建 ThemeToggle 组件支持三种模式 (light/dark/system) - 迁移所有组件从硬编码 gray-* 到语义化颜色 - 支持系统主题偏好检测 (prefers-color-scheme) - 添加主题初始化脚本防止闪烁 (FOUC)
This commit is contained in:
@@ -71,10 +71,10 @@ function getTriggerInfo(trigger: CheckpointTrigger) {
|
||||
case 'session_end':
|
||||
return { icon: '⏹️', label: 'Session End', color: 'text-cyan-400' };
|
||||
case 'pre_rollback':
|
||||
return { icon: '🔙', label: 'Pre-Rollback', color: 'text-gray-400' };
|
||||
return { icon: '🔙', label: 'Pre-Rollback', color: 'text-fg-muted' };
|
||||
case 'auto':
|
||||
default:
|
||||
return { icon: '⚪', label: 'Auto', color: 'text-gray-400' };
|
||||
return { icon: '⚪', label: 'Auto', color: 'text-fg-muted' };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,7 +295,7 @@ export function CheckpointPanel({
|
||||
const LoadingSkeleton = () => (
|
||||
<div className="space-y-3 p-4">
|
||||
{[1, 2, 3, 4].map((i) => (
|
||||
<div key={i} className="flex items-center gap-3 p-3 bg-gray-900/50 rounded-lg">
|
||||
<div key={i} className="flex items-center gap-3 p-3 bg-surface-base/50 rounded-lg">
|
||||
<Skeleton className="h-4 w-4 rounded" />
|
||||
<div className="flex-1 space-y-2">
|
||||
<Skeleton className="h-4 w-48" />
|
||||
@@ -329,7 +329,7 @@ export function CheckpointPanel({
|
||||
transition={smoothTransition}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className={cn(
|
||||
'bg-gray-800 max-h-[90vh] overflow-hidden flex flex-col',
|
||||
'bg-surface-subtle max-h-[90vh] overflow-hidden flex flex-col',
|
||||
responsive
|
||||
? 'w-full md:w-full md:max-w-2xl md:mx-4 rounded-t-2xl md:rounded-lg'
|
||||
: 'rounded-lg w-full max-w-2xl mx-4'
|
||||
@@ -338,19 +338,19 @@ export function CheckpointPanel({
|
||||
{/* Header */}
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center justify-between border-b border-gray-700',
|
||||
'flex items-center justify-between border-b border-line',
|
||||
responsive ? 'px-4 md:px-6 py-4' : 'px-6 py-4'
|
||||
)}
|
||||
>
|
||||
{responsive && (
|
||||
<div className="absolute top-2 left-1/2 -translate-x-1/2 w-10 h-1 bg-gray-600 rounded-full md:hidden" />
|
||||
<div className="absolute top-2 left-1/2 -translate-x-1/2 w-10 h-1 bg-surface-emphasis rounded-full md:hidden" />
|
||||
)}
|
||||
<div className={cn(responsive && 'mt-2 md:mt-0')}>
|
||||
<h2 className="text-lg font-semibold flex items-center gap-2">
|
||||
<History size={20} className="text-primary-400" />
|
||||
Checkpoints
|
||||
</h2>
|
||||
<p className="text-xs text-gray-500">
|
||||
<p className="text-xs text-fg-subtle">
|
||||
{stats ? `${stats.count} checkpoints` : 'Loading...'}
|
||||
{stats?.oldestTimestamp && (
|
||||
<> · Oldest: {formatTime(stats.oldestTimestamp)}</>
|
||||
@@ -431,10 +431,10 @@ export function CheckpointPanel({
|
||||
{loading ? (
|
||||
<LoadingSkeleton />
|
||||
) : checkpoints.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center py-12 text-gray-500">
|
||||
<div className="flex flex-col items-center justify-center py-12 text-fg-subtle">
|
||||
<History size={48} className="mb-4 opacity-50" />
|
||||
<p className="text-center">No checkpoints yet</p>
|
||||
<p className="text-xs text-gray-600 mt-2 text-center max-w-xs">
|
||||
<p className="text-xs text-fg-subtle mt-2 text-center max-w-xs">
|
||||
Checkpoints are created automatically when files are modified
|
||||
</p>
|
||||
<Button
|
||||
@@ -461,12 +461,12 @@ export function CheckpointPanel({
|
||||
<div key={group.label} className="space-y-1">
|
||||
{/* Group Header */}
|
||||
<button
|
||||
className="flex items-center gap-2 text-sm text-gray-400 hover:text-gray-300 transition-colors w-full"
|
||||
className="flex items-center gap-2 text-sm text-fg-muted hover:text-fg-secondary transition-colors w-full"
|
||||
onClick={() => toggleGroup(group.label)}
|
||||
>
|
||||
{isExpanded ? <ChevronDown size={14} /> : <ChevronRight size={14} />}
|
||||
<span className="font-medium">{group.label}</span>
|
||||
<span className="text-xs text-gray-500">({group.items.length})</span>
|
||||
<span className="text-xs text-fg-subtle">({group.items.length})</span>
|
||||
</button>
|
||||
|
||||
{/* Group Items */}
|
||||
@@ -488,7 +488,7 @@ export function CheckpointPanel({
|
||||
key={cp.id}
|
||||
initial={{ opacity: 0, x: -10 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
className="bg-gray-900/50 rounded-lg p-3 hover:bg-gray-900/80 transition-colors"
|
||||
className="bg-surface-base/50 rounded-lg p-3 hover:bg-surface-base/80 transition-colors"
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
{/* Trigger Icon */}
|
||||
@@ -502,16 +502,16 @@ export function CheckpointPanel({
|
||||
<span className={cn('text-sm font-medium', triggerInfo.color)}>
|
||||
{triggerInfo.label}
|
||||
</span>
|
||||
<span className="text-xs text-gray-500">
|
||||
<span className="text-xs text-fg-subtle">
|
||||
{formatTime(cp.timestamp)}
|
||||
</span>
|
||||
</div>
|
||||
{cp.description && (
|
||||
<p className="text-xs text-gray-400 mt-0.5 truncate">
|
||||
<p className="text-xs text-fg-muted mt-0.5 truncate">
|
||||
{cp.description}
|
||||
</p>
|
||||
)}
|
||||
<div className="flex items-center gap-3 mt-1 text-xs text-gray-500">
|
||||
<div className="flex items-center gap-3 mt-1 text-xs text-fg-subtle">
|
||||
<span className="flex items-center gap-1">
|
||||
<FileText size={10} />
|
||||
{cp.filesChanged} files
|
||||
@@ -580,11 +580,11 @@ export function CheckpointPanel({
|
||||
{/* Footer */}
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center justify-between border-t border-gray-700',
|
||||
'flex items-center justify-between border-t border-line',
|
||||
responsive ? 'px-4 py-3 safe-area-pb' : 'px-6 py-3'
|
||||
)}
|
||||
>
|
||||
<span className="text-xs text-gray-500">
|
||||
<span className="text-xs text-fg-subtle">
|
||||
Auto-cleanup enabled (7 days / 100 max)
|
||||
</span>
|
||||
<Button
|
||||
@@ -592,10 +592,10 @@ export function CheckpointPanel({
|
||||
size="sm"
|
||||
onClick={handleCleanup}
|
||||
disabled={cleaningUp}
|
||||
className="text-gray-400 hover:text-gray-300"
|
||||
className="text-fg-muted hover:text-fg-secondary"
|
||||
>
|
||||
{cleaningUp ? (
|
||||
<div className="animate-spin rounded-full h-3 w-3 border-t-2 border-b-2 border-gray-500 mr-1" />
|
||||
<div className="animate-spin rounded-full h-3 w-3 border-t-2 border-b-2 border-fg-subtle mr-1" />
|
||||
) : (
|
||||
<Trash2 size={12} className="mr-1" />
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user