From c6dd3695e553e771afa9c133601895431c82773d Mon Sep 17 00:00:00 2001 From: kurihada Date: Wed, 17 Dec 2025 21:35:34 +0800 Subject: [PATCH] =?UTF-8?q?style(ui):=20=E7=BE=8E=E5=8C=96=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E5=99=A8=E4=BB=A3=E7=A0=81=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 CodeMirror 编辑器全局样式(字体、行号、高亮、搜索面板等) - 添加 Diff 编辑器样式(删除/新增行高亮、行内变更等) - CodeEditor 优化:Tab 栏动画、文件图标着色、底部状态栏 - DiffEditor 优化:头部布局、变更统计、操作类型标签 --- packages/ui/src/components/CodeEditor.tsx | 143 +++++++++---- packages/ui/src/components/DiffEditor.tsx | 101 ++++++--- packages/ui/src/styles/index.css | 236 ++++++++++++++++++++++ 3 files changed, 411 insertions(+), 69 deletions(-) diff --git a/packages/ui/src/components/CodeEditor.tsx b/packages/ui/src/components/CodeEditor.tsx index 1002daf..caee401 100644 --- a/packages/ui/src/components/CodeEditor.tsx +++ b/packages/ui/src/components/CodeEditor.tsx @@ -13,7 +13,7 @@ import { html } from '@codemirror/lang-html'; import { css } from '@codemirror/lang-css'; import { markdown } from '@codemirror/lang-markdown'; import { oneDark } from '@codemirror/theme-one-dark'; -import { X, Save, Circle, FileCode, MousePointerClick } from 'lucide-react'; +import { X, Save, Circle, FileCode, MousePointerClick, Code2 } from 'lucide-react'; import { motion, AnimatePresence } from 'framer-motion'; import { toast } from 'sonner'; import { cn } from '../utils/cn.js'; @@ -154,6 +154,32 @@ export function CodeEditor({ return [getLanguageExtension(activeTab.language)].flat(); }, [activeTab]); + // 获取文件图标颜色 + const getFileIconColor = (language: string) => { + switch (language) { + case 'typescript': + case 'tsx': + return 'text-blue-500'; + case 'javascript': + case 'jsx': + return 'text-yellow-500'; + case 'python': + return 'text-green-500'; + case 'json': + return 'text-orange-500'; + case 'html': + return 'text-red-500'; + case 'css': + case 'scss': + case 'less': + return 'text-purple-500'; + case 'markdown': + return 'text-cyan-500'; + default: + return 'text-fg-muted'; + } + }; + if (tabs.length === 0) { return (
@@ -163,14 +189,14 @@ export function CodeEditor({ transition={{ duration: 0.3 }} className="text-center max-w-xs" > -
- +
+
-

No files open

-

+

No files open

+

Select a file from the explorer to view and edit its contents

-
+
Click a file to open
@@ -182,61 +208,77 @@ export function CodeEditor({ return (
{/* Tab Bar */} -
+
{tabs.map((tab) => { const isActive = tab.id === activeTabId; const isModified = hasUnsavedChanges(tab); const isSaving = saving === tab.id; + const iconColor = getFileIconColor(tab.language); return ( onTabChange(tab.id)} > - {/* 修改指示器 */} - {isModified && !isSaving && ( - - )} - {isSaving && ( -
+ {/* 活动标签顶部指示条 */} + {isActive && ( + )} + {/* 文件图标 */} + + {/* 文件名 */} {tab.name} - {/* 关闭按钮 */} - + {/* 修改指示器 / 保存中 / 关闭按钮 */} +
+ {isSaving ? ( +
+ ) : isModified ? ( + + ) : null} + + {/* 关闭按钮 */} + +
); })} @@ -251,10 +293,11 @@ export function CodeEditor({ whileTap={{ scale: 0.95 }} onClick={() => handleSave(activeTab)} disabled={saving === activeTab.id} - className="ml-auto mr-2 p-1.5 rounded hover:bg-surface-muted transition-colors" + className="ml-auto mr-3 px-3 py-1.5 rounded-md bg-primary-500/10 hover:bg-primary-500/20 transition-colors flex items-center gap-1.5" title="Save (Cmd+S)" > - + + Save )}
@@ -294,10 +337,32 @@ export function CodeEditor({ completionKeymap: true, lintKeymap: true, }} - className="h-full text-sm" + className="h-full" /> )}
+ + {/* Status Bar */} + {activeTab && ( +
+
+ {/* 语言 */} + {activeTab.language} + {/* 行数 */} + + {activeTab.content.split('\n').length} lines + +
+
+ {/* 编码 */} + UTF-8 + {/* 路径 */} + + {activeTab.path} + +
+
+ )}
); } diff --git a/packages/ui/src/components/DiffEditor.tsx b/packages/ui/src/components/DiffEditor.tsx index 6e09013..4528e09 100644 --- a/packages/ui/src/components/DiffEditor.tsx +++ b/packages/ui/src/components/DiffEditor.tsx @@ -16,7 +16,7 @@ import { html } from '@codemirror/lang-html'; import { css } from '@codemirror/lang-css'; import { markdown } from '@codemirror/lang-markdown'; import { oneDark } from '@codemirror/theme-one-dark'; -import { X, FileCode, GitCompare } from 'lucide-react'; +import { X, FileCode, GitCompare, ChevronLeft, ChevronRight, FileEdit, FilePlus } from 'lucide-react'; import { motion } from 'framer-motion'; import { cn } from '../utils/cn.js'; import { useTheme } from '../hooks/useTheme.js'; @@ -159,51 +159,86 @@ export function DiffEditor({ diff, onClose, className }: DiffEditorProps) { }; }, [diff.originalContent, diff.newContent, languageExtension, resolvedTheme]); + // 获取操作类型的图标和颜色 + const operationInfo = diff.operation === 'write' + ? { icon: FilePlus, label: 'New File', color: 'text-green-500', bgColor: 'bg-green-500/10' } + : { icon: FileEdit, label: 'Modified', color: 'text-blue-500', bgColor: 'bg-blue-500/10' }; + + const OperationIcon = operationInfo.icon; + return (
{/* Header */} -
-
- {/* 文件图标和名称 */} -
- - {diff.name} - ({diff.operation === 'write' ? 'Write' : 'Edit'}) +
+
+ {/* Diff 图标 */} +
+
- {/* 变更统计 */} -
- +{stats.additions} - -{stats.deletions} + {/* 文件名和操作类型 */} +
+
+ {diff.name} + + + {operationInfo.label} + +
+ + {diff.path} +
- {/* 操作按钮 */} -
+ {/* 变更统计和操作按钮 */} +
+ {/* 变更统计 */} +
+
+ + +{stats.additions} +
+
+
+ + -{stats.deletions} +
+
+ {/* 关闭按钮 */} {onClose && ( - + )}
- {/* 标签栏 */} -
-
- - Original + {/* 列标签栏 */} +
+
+ + + Original + {diff.operation === 'write' && ( + (empty) + )}
-
- - Modified +
+ + + Modified + ({stats.totalLines} lines)
@@ -214,9 +249,15 @@ export function DiffEditor({ diff, onClose, className }: DiffEditorProps) { style={{ minHeight: 0 }} /> - {/* 路径提示 */} -
- {diff.path} + {/* 底部状态栏 */} +
+
+ ESC + to close +
+
+ Side-by-side comparison +
); diff --git a/packages/ui/src/styles/index.css b/packages/ui/src/styles/index.css index 52d66d6..dcdda5e 100644 --- a/packages/ui/src/styles/index.css +++ b/packages/ui/src/styles/index.css @@ -93,6 +93,16 @@ html { background: var(--scrollbar-thumb-hover); } +/* 隐藏滚动条(保留滚动功能) */ +.scrollbar-hide { + -ms-overflow-style: none; + scrollbar-width: none; +} + +.scrollbar-hide::-webkit-scrollbar { + display: none; +} + /* ============ Message content ============ */ .message-content { @@ -119,6 +129,232 @@ html { padding: 0; } +/* ============ CodeMirror Editor Styles ============ */ + +/* 编辑器容器 */ +.cm-editor { + font-family: 'JetBrains Mono', 'Fira Code', 'SF Mono', 'Menlo', 'Monaco', 'Consolas', monospace !important; + font-size: 13px !important; + line-height: 1.6 !important; + font-feature-settings: 'liga' 1, 'calt' 1; /* 启用连字 */ +} + +/* 编辑器聚焦时的边框 */ +.cm-editor.cm-focused { + outline: none !important; +} + +/* 内容区域 */ +.cm-scroller { + overflow: auto !important; + font-family: inherit !important; +} + +/* 行号栏 */ +.cm-gutters { + border-right: 1px solid rgb(var(--color-border-default)) !important; + background: rgb(var(--color-bg-subtle)) !important; +} + +.dark .cm-gutters { + background: rgba(0, 0, 0, 0.2) !important; + border-right-color: rgb(var(--color-border-default)) !important; +} + +/* 行号 */ +.cm-lineNumbers .cm-gutterElement { + padding: 0 12px 0 8px !important; + min-width: 40px !important; + color: rgb(var(--color-text-subtle)) !important; + font-size: 12px !important; +} + +/* 活动行高亮 */ +.cm-activeLine { + background: rgba(var(--color-bg-emphasis), 0.5) !important; +} + +.dark .cm-activeLine { + background: rgba(255, 255, 255, 0.04) !important; +} + +/* 活动行号高亮 */ +.cm-activeLineGutter { + background: rgba(var(--color-bg-emphasis), 0.5) !important; +} + +.dark .cm-activeLineGutter { + background: rgba(255, 255, 255, 0.06) !important; +} + +/* 选区样式 */ +.cm-selectionBackground { + background: rgba(59, 130, 246, 0.2) !important; +} + +.dark .cm-selectionBackground { + background: rgba(59, 130, 246, 0.3) !important; +} + +/* 匹配的括号 */ +.cm-matchingBracket { + background: rgba(255, 215, 0, 0.3) !important; + outline: 1px solid rgba(255, 215, 0, 0.5) !important; +} + +/* 折叠占位符 */ +.cm-foldPlaceholder { + background: rgb(var(--color-bg-muted)) !important; + border: 1px solid rgb(var(--color-border-default)) !important; + border-radius: 4px !important; + padding: 0 4px !important; + margin: 0 2px !important; + color: rgb(var(--color-text-muted)) !important; +} + +/* 搜索面板 */ +.cm-panel.cm-search { + background: rgb(var(--color-bg-subtle)) !important; + border-bottom: 1px solid rgb(var(--color-border-default)) !important; + padding: 8px !important; +} + +.cm-panel.cm-search input { + background: rgb(var(--color-bg-base)) !important; + border: 1px solid rgb(var(--color-border-default)) !important; + border-radius: 4px !important; + padding: 4px 8px !important; + color: rgb(var(--color-text-primary)) !important; +} + +.cm-panel.cm-search button { + background: rgb(var(--color-bg-muted)) !important; + border: 1px solid rgb(var(--color-border-default)) !important; + border-radius: 4px !important; + padding: 4px 8px !important; + color: rgb(var(--color-text-secondary)) !important; + cursor: pointer !important; +} + +.cm-panel.cm-search button:hover { + background: rgb(var(--color-bg-emphasis)) !important; +} + +/* 自动补全下拉框 */ +.cm-tooltip-autocomplete { + background: rgb(var(--color-bg-base)) !important; + border: 1px solid rgb(var(--color-border-default)) !important; + border-radius: 6px !important; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important; + overflow: hidden !important; +} + +.dark .cm-tooltip-autocomplete { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4) !important; +} + +.cm-tooltip-autocomplete > ul { + font-family: inherit !important; + max-height: 200px !important; +} + +.cm-tooltip-autocomplete > ul > li { + padding: 4px 10px !important; + color: rgb(var(--color-text-primary)) !important; +} + +.cm-tooltip-autocomplete > ul > li[aria-selected] { + background: rgb(var(--color-bg-emphasis)) !important; + color: rgb(var(--color-text-primary)) !important; +} + +.dark .cm-tooltip-autocomplete > ul > li[aria-selected] { + background: rgba(59, 130, 246, 0.2) !important; +} + +/* 光标样式 */ +.cm-cursor { + border-left: 2px solid rgb(var(--color-text-primary)) !important; +} + +.dark .cm-cursor { + border-left-color: #fff !important; +} + +/* 行内容 */ +.cm-line { + padding: 0 8px !important; +} + +/* ============ Diff Editor Styles ============ */ + +/* Merge View 容器 */ +.cm-mergeView { + height: 100% !important; +} + +/* Diff 视图中的变更高亮 */ +.cm-changedLine { + background: rgba(255, 215, 0, 0.1) !important; +} + +.cm-deletedChunk { + background: rgba(239, 68, 68, 0.15) !important; +} + +.dark .cm-deletedChunk { + background: rgba(239, 68, 68, 0.2) !important; +} + +.cm-insertedChunk { + background: rgba(34, 197, 94, 0.15) !important; +} + +.dark .cm-insertedChunk { + background: rgba(34, 197, 94, 0.2) !important; +} + +/* Diff 行内变更 */ +.cm-changedText { + background: rgba(255, 215, 0, 0.3) !important; + border-radius: 2px !important; +} + +.cm-deletedText { + background: rgba(239, 68, 68, 0.3) !important; + text-decoration: line-through !important; + border-radius: 2px !important; +} + +.cm-insertedText { + background: rgba(34, 197, 94, 0.3) !important; + border-radius: 2px !important; +} + +/* Gutter 中的变更标记 */ +.cm-changeGutter { + width: 4px !important; +} + +.cm-changeGutter .cm-gutterElement { + padding: 0 !important; +} + +/* 折叠的未变更区域 */ +.cm-collapsedLines { + background: rgb(var(--color-bg-subtle)) !important; + color: rgb(var(--color-text-muted)) !important; + font-size: 12px !important; + padding: 4px 12px !important; + margin: 4px 0 !important; + border-radius: 4px !important; + cursor: pointer !important; +} + +.cm-collapsedLines:hover { + background: rgb(var(--color-bg-muted)) !important; +} + /* ============ Typing indicator ============ */ .typing-indicator {