diff --git a/CLAUDE.md b/CLAUDE.md index b88c050..7914839 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -74,13 +74,21 @@ pnpm test # 跑 Vitest(真实 MongoDB 集成测试,见下) ## 样式(CSS)约定 -全局样式拆分在 `src/renderer/src/styles/`,由 `index.css` 按固定顺序 `@import` 11 个分区文件汇总(`main.tsx` 只引 `./styles/index.css`)。分区:`tokens`(设计令牌)、`base`(reset/按钮/输入/滚动条)、`app-shell`、`explorer`、`work-area`、`results`、`modals-forms`、`phase2`、`phase3`、`theme-polish`、`base-ui`。改某一区样式只需改对应分区文件。 +视觉层正从「纯手写 CSS」迁移到 **Tailwind v4 + shadcn 风原语(基于 `@base-ui/react`)**,当前是**混合态**:共享表单/弹窗原语(`components/ui/*` + `common/Button`)与连接弹窗已 Tailwind 化;其余功能分区仍是手写 CSS(已被令牌重新上色)。设计令牌体系见 `DESIGN.md`——**Zinc + Blue**(中性 zinc 表面 + 单一蓝强调,亮暗平等;数据/代码字体 **JetBrains Mono**)。 -- **`@import` 顺序是层叠契约,别打乱。** `tokens.css` 必须最先;`theme-polish.css`(Slate 后置修饰)必须排在所有功能分区**之后**——它靠源码顺序在**同等权重**下覆盖前面的基础规则(`button`/`input`/`.conn-item`/`.tree-node`/`.view-switch`/`.modal*`/`.tbl-head` 等约 29 个选择器在文件里出现两次:基础在前、polish 在后);`base-ui.css` 收尾。 -- **何时全局 vs CSS Module:** - - **永远全局**:`tokens.css` 的主题令牌(module 与全局共享的底座)、第三方选择器(CodeMirror `.cm-*`、Base UI `[data-*]`)、跨组件复用的共享基类(`.v-*` value 颜色、`.kv-row`、`.vrow`)。 - - **用 `Foo.module.css`**(放组件旁,作用域隔离):全新、**自包含**的组件(独立面板/弹窗/小部件)。Vite 开箱支持,无需新依赖。**范本见 `components/shell/pipeline/pipeline.module.css`**(项目首个 module):类名 camelCase,本地修饰态直接写,复用 §「永远全局」的共享词汇用 `:global(.muted)`——本地化 vs `:global()` 的取舍细节见该文件头注释。 - - 复用既有 result/tree/table/explorer 词汇的组件 → **留全局**。判断标准是「组件是否自包含」,**不是「新不新」**;不强制回迁老组件。 +**Tailwind 接入(CSS-first,无 `tailwind.config`):** +- `@tailwindcss/vite` 挂在 `electron.vite.config.ts` 的 `renderer.plugins`;`@` 别名指向 `src/renderer/src`(`tsconfig.web.json` + vite 对称,shadcn 约定)。 +- `styles/index.css` 顶部**只引 Tailwind 的 theme + utilities 层、不引 preflight**(迁移期避免全局 reset 扰动未迁移的手写 CSS);末尾 `@theme inline` 把 shadcn 色名映射到我们的语义令牌。**命名坑:** 我们的 `--accent`=品牌蓝 → 映射成 shadcn `primary`;shadcn 的 `accent`(hover 底)→ `--bg-3`。 +- **关键层叠规则:** 未分层的手写 CSS 优先级**高于** `@layer utilities`。所以裸元素默认规则(`base.css` 全文 + `theme-polish.css` 的通用 `button/input/select/textarea` 块)都收进 **`@layer base`**,迁移组件的 Tailwind 工具类才能盖过它们;原生元素仍取默认。**新组件用 Tailwind 即可生效;若被某条手写规则压住,多半是那条裸元素规则没进 base 层。** +- shadcn 风原语 = `@base-ui/react` 无样式原语 + Tailwind 工具类 + `cva` 变体(范本 `common/Button.tsx`);class 合并用 `lib/utils.ts` 的 `cn()`(clsx + tailwind-merge)。门面在 `components/ui/*`(对外 props 不变,业务**不**直接 import Base UI)。 + +**仍在用的手写分区(`styles/`,`index.css` 固定顺序 @import):** `tokens`(令牌底座) → `base`(@layer base) → `app-shell`/`explorer`/`work-area`/`results`/`modals-forms`/`phase2`/`phase3` → `theme-polish`(后置修饰) → `base-ui`(收尾)。**@import 顺序仍是层叠契约,别打乱**(`tokens` 最先、`theme-polish` 在功能分区之后);这些分区随迁移逐步清退到 Tailwind。 + +- **何时 Tailwind vs 全局 vs CSS Module:** + - **新组件 / 重做组件 → Tailwind 工具类 + `cva`**(首选,见 `components/ui/*`)。 + - **永远全局**:`tokens.css` 令牌、第三方选择器(CodeMirror `.cm-*`、Base UI `[data-*]`)、跨组件复用的共享数据词汇(`.v-*` 类型色、`.kv-row`、`.vrow`)。 + - **CSS Module**(`Foo.module.css`,作用域隔离):自包含组件,范本 `components/shell/pipeline/pipeline.module.css`。 + - 复用既有 result/tree/table/explorer 词汇的老组件 → 暂留全局,随迁移再说;不强制回迁。 ## 性能铁律(ADR-0004 —— 不可妥协,每个功能都必须遵守) diff --git a/DESIGN.md b/DESIGN.md index 0060b56..eef10fa 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -1,237 +1,174 @@ # AMDM — 设计系统 -**代号:Slate(石墨)。** 一套克制、中性、专业的数据库工具外观,对齐 TablePlus / Navicat / TinyRDM 的路线。亮色优先——纯白内容区坐落在**无色调倾向**的中性浅灰 chrome 上,近黑中性墨色文字,单一**石墨强调色**(仅用于主操作、当前选中、焦点环);并有一套同样中性的暗色。目标是数据工作的清晰可读:层面分明、chrome 安静、颜色只留给真正承载意义的东西(主按钮 + 文档语法着色)。 +**代号:Zinc(中性锌)+ Blue。** 一套现代、克制、数据密集的桌面数据库工具外观,路线对齐 Outerbase Studio:真中性 **zinc** 表面(无暖/绿色偏),单一**蓝/靛强调色**只用于主操作 / 选中 / 焦点环;语法与数据着色保留语义彩(绿字符串、蓝数字、橙红 ObjectId…)。亮色与暗色**同等对待**。 -> 本主题刻意**弃用**了旧版 Compass 那套带绿调的"灰色水泥"中间灰。换肤是纯 token 级别的——见 §6 与 CLAUDE.md「跨文件关键机制」。 +> 视觉层建立在 **Tailwind v4 + shadcn 风原语(基于 `@base-ui/react`)** 之上,**正在从旧的纯手写 CSS 迁移**——见 `CLAUDE.md` 的「样式约定」。当前是混合态:共享表单/弹窗原语与连接弹窗已 Tailwind 化,其余分区仍是手写 CSS(已被令牌重新上色)。本文件描述目标体系与现行约定。 +> +> 前身是「Slate 石墨」近单色体系(已弃用)。换肤是纯令牌级的——见 §1 与 §6。 三条原则贯穿每个决策: -1. **纯白内容,中性灰 chrome。** 内容面在亮色是 `#ffffff`、暗色是中性深灰 `#1c1c1f`;chrome(窗口框、侧栏、表头、凸起控件)逐级降到中性灰。**无任何色调倾向**——不偏绿、不偏蓝、不偏暖,内容永远待在最亮的层面,文字与数据读起来最锐利。 -2. **结构化排版。** 靠字重、颜色、细发丝线(标题栏与表头下方)建立层级,而非粗黑分隔条。圆角偏锐(5px),表面扁平,克制优先于装饰。 -3. **为数据工作而读。** 紧凑密度,所有数据/代码用真正的等宽字体,语法着色为「扫读」调校——不是彩虹。 +1. **中性 zinc 表面,单一蓝强调。** 内容面在亮色是 `#ffffff`、暗色是近黑 zinc `#101012`;chrome(窗口框、侧栏、表头、凸起控件)逐级降到中性 zinc 灰。**无色调倾向**。蓝强调是**结构性**的,只标主操作 / 当前选中 / 焦点环,绝不大面积铺。 +2. **结构化排版 + 现代细节。** 靠字重、颜色、细发丝线建立层级;偏锐圆角(5px)+ 圆角内嵌「药丸」hover(侧栏/菜单)+ 克制的短过渡。 +3. **为数据工作而读。** 紧凑密度,所有数据/代码用 **JetBrains Mono**,语法着色为「扫读」调校——不是彩虹。 --- ## 1. 颜色 token -所有颜色都是 CSS 自定义属性,定义在 `styles.css` 的 `:root`(亮,默认)与 `[data-theme='dark']`(暗)。**组件里绝不硬编码 hex——一律引用 token。** 暗色块只重指向调色板,所以切回亮色会自然落回 `:root`。 +所有颜色都是 CSS 自定义属性,定义在 `styles/tokens.css` 的 `:root`(亮,默认)与 `[data-theme='dark']`(暗)。**组件里绝不硬编码 hex——一律引用 token**(Tailwind 侧用 `bg-secondary`/`text-foreground` 等语义类,或 `[var(--token)]` 任意值)。`styles/index.css` 末尾的 `@theme inline` 把 shadcn 色名映射到这些令牌(**命名坑**:我们的 `--accent`=品牌蓝→映射成 shadcn `primary`;shadcn 的 `accent`=hover 底→`--bg-3`)。 -### 强调色 — 石墨 +### 强调色 — 蓝 | Token | Light | Dark | 用途 | |---|---|---|---| -| `--accent` | `#3f4754` | `#aeb7c6` | 主操作(Run)、激活态、选中、链接 | -| `--accent-hover` | `#2c333d` | `#9aa4b6` | 强调面 hover | -| `--accent-soft` | `rgba(63,71,84,.10)` | `rgba(174,183,198,.14)` | 激活行/标签背景 | -| `--accent-soft-strong` | `rgba(63,71,84,.16)` | `rgba(174,183,198,.24)` | 选中高亮、焦点环 | -| `--accent-fg` | `#ffffff` | `#1b1d22` | 强调填充上的文字/图标 | +| `--accent` | `#2f6bff` | `#4f7fff` | 主操作(Run/Save)、激活、选中、焦点环、链接 | +| `--accent-hover` | `#1f5bef` | `#6690ff` | 强调面 hover | +| `--accent-soft` | `rgba(47,107,255,.10)` | `rgba(79,127,255,.16)` | 焦点环、激活行/标签底 | +| `--accent-soft-strong` | `rgba(47,107,255,.16)` | `rgba(79,127,255,.26)` | 选中高亮 | +| `--accent-fg` | `#ffffff` | `#ffffff` | 蓝填充上的文字/图标 | -> 亮色用深石墨 `#3f4754` + **白字**;暗色翻为浅石墨 `#aeb7c6` + **深字**(所以 `--accent-fg` 本身随主题变)。石墨是最克制的一档:选中/主按钮/焦点环之外几乎不出现彩色,整体近单色。 - -### 表面(Surfaces) +### 表面(Surfaces,zinc) | Token | Light | Dark | 用途 | |---|---|---|---| -| `--bg-app` | `#ececee` | `#161618` | 窗口 chrome / 面板背后 | -| `--bg-0` | `#ffffff` | `#1c1c1f` | 主内容面(工作区、列表、结果) | -| `--bg-1` | `#f6f6f8` | `#232327` | 侧栏、头部 chrome、底栏 | -| `--bg-2` | `#eeeef1` | `#2a2a2f` | 凸起/内凹(输入框、表头、分段控件) | -| `--bg-3` | `#e6e6ea` | `#34343b` | 行/按钮 hover | -| `--bg-elevated` | `#ffffff` | `#2a2a2f` | 弹窗、菜单、激活分段 | -| `--bg-sel` | `rgba(63,71,84,.10)` | `rgba(174,183,198,.16)` | 选中行底色 | -| `--bg-editor` | `#ffffff` | `#19191c` | 查询编辑器(另见 §6) | - -> 亮色**内容**刻意纯 `#ffffff`:最亮层面给数据最高对比;chrome 逐级降到中性灰把面板干净分开。暗色是中性深灰 `#1c1c1f` 内容坐在 `#232327/#2a2a2f` chrome 上——**绝不带蓝/绿/棕调**。 +| `--bg-app` | `#f4f4f5` | `#09090b` | 窗口 chrome / 面板背后 | +| `--bg-0` | `#ffffff` | `#101012` | 主内容面(工作区、列表、结果) | +| `--bg-1` | `#fafafa` | `#18181b` | 侧栏、头部 chrome、底栏 | +| `--bg-2` | `#f4f4f5` | `#27272a` | 凸起/内凹(输入、表头、分段) | +| `--bg-3` | `#e4e4e7` | `#323238` | 行/按钮 hover(= shadcn `accent`) | +| `--bg-elevated` | `#ffffff` | `#1c1c20` | 弹窗、菜单、popover | +| `--bg-sel` | `rgba(47,107,255,.10)` | `rgba(79,127,255,.18)` | 选中行底(蓝) | +| `--bg-editor` | `#ffffff` | `#0d0d0f` | 查询编辑器(CodeMirror,另见 §6) | -### 边框与发丝线 +### 边框 / 文字 / 状态 | Token | Light | Dark | 用途 | |---|---|---|---| -| `--border` | `#e6e6ea` | `#303036` | 默认分隔线 | -| `--border-strong` | `#cfcfd6` | `#45454d` | 控件描边 | -| `--rule` | `#d6d6dc` | `#3a3a42` | 标题栏与表头下方的发丝线(比 `--border` 略重) | - -Slate 是**通透**的——分隔用浅发丝线而非粗黑条。`--rule` 只比 `--border` 略强,用来界定标题栏底边与表头下划线;真正承载层级的手势是石墨的选中/激活强调,而不是黑线。 - -### 文字 - -| Token | Light | Dark | 用途 | -|---|---|---|---| -| `--fg-0` | `#1d1d20` | `#ececee` | 主文字(近黑中性,无色调) | -| `--fg-1` | `#3a3a40` | `#c4c4cb` | 次级标签、正文 | -| `--fg-2` | `#6a6a73` | `#9a9aa3` | 三级、说明 | -| `--fg-3` | `#9a9aa3` | `#6c6c75` | 禁用、行号、占位、计数 | - -### 状态色 - -| Token | Light | Dark | -|---|---|---| -| `--ok` | `#1a8f4c` | `#3ec98a` | -| `--warn` | `#b5701a` | `#e0a23a` | -| `--err` | `#d23b3b` | `#ff6f5c` | -| `--err-bg` | `rgba(210,59,59,.08)` | `rgba(255,111,92,.13)` | +| `--border` | `#e4e4e7` | `#27272a` | 默认分隔线(= shadcn `border`) | +| `--border-strong` | `#d4d4d8` | `#3f3f46` | 控件描边(= shadcn `input`) | +| `--rule` | `#e4e4e7` | `#232327` | 标题栏 / 表头下发丝线 | +| `--fg-0` | `#18181b` | `#fafafa` | 主文字(zinc,无色调) | +| `--fg-1` | `#3f3f46` | `#d4d4d8` | 次级标签、正文 | +| `--fg-2` | `#71717a` | `#a1a1aa` | 三级、说明(= shadcn `muted-foreground`) | +| `--fg-3` | `#a1a1aa` | `#71717a` | 禁用、占位、计数 | +| `--ok` | `#16a34a` | `#34d399` | 成功 / 在线 | +| `--warn` | `#b45309` | `#e0a23a` | 警告 / 截断 | +| `--err` | `#dc2626` | `#ff6f5c` | 错误(= shadcn `destructive`) | ### 语法 / 数据着色(`--t-*`) -查询编辑器与结果单元格共用同一套类型色。**新增任何 BSON 类型,都要同时改这里、`lib/ejson.ts` 与序列化 core**(见 CLAUDE.md)。 +查询编辑器与结果单元格共用同一套类型色。**新增任何 BSON 类型,都要同时改这里、`lib/ejson.ts`、序列化 core,并同步 `lib/pineEditorTheme.ts` 的解析 hex**(编辑器另持一份;改 `--t-*` 务必两边同步)。 | Token | Light | Dark | 应用于 | |---|---|---|---| -| `--t-key` | `#1d1d20` | `#ececee` | 对象键(近黑墨,与正文同色) | +| `--t-key` | `#18181b` | `#fafafa` | 对象键(同正文色) | | `--t-string` | `#1a8f4c` | `#5fd39a` | 字符串(绿) | -| `--t-number` | `#2563eb` | `#74a8ff` | 数字(蓝) | -| `--t-date` | `#2563eb` | `#74a8ff` | ISODate / Timestamp(蓝,同数字) | -| `--t-boolean` | `#8a3fd0` | `#c79bff` | 布尔(紫) | -| `--t-objectId` | `#c0481f` | `#ff8a5c` | ObjectId(橙红) | -| `--t-binary` | `#c0481f` | `#ff8a5c` | BinData / UUID(橙红) | -| `--t-regex` | `#8a3fd0` | `#c79bff` | 正则(紫) | -| `--t-special` | `#c0481f` | `#ff8a5c` | MinKey/MaxKey/Code/DBRef 等 | -| `--t-null` | `#9a9aa3` | `#6c6c75` | null / undefined(灰) | +| `--t-number` / `--t-date` | `#2563eb` | `#74a8ff` | 数字 / ISODate / Timestamp(蓝) | +| `--t-boolean` / `--t-regex` | `#8a3fd0` | `#c79bff` | 布尔 / 正则(紫) | +| `--t-objectId` / `--t-binary` / `--t-special` | `#c0481f` | `#ff8a5c` | ObjectId / BinData / MinKey 等(橙红) | +| `--t-null` | `#a1a1aa` | `#71717a` | null / undefined(灰) | -> 即便强调色是单色石墨,数据着色仍保留可辨识的语义彩:**近黑键、绿字符串、蓝数字/日期、橙红 ObjectId、紫布尔、灰 null**。编辑器(CodeMirror)复用同一套指派,方法调用 `db.coll.find` 走近黑墨(克制,不抢色)。 +### 阴影 -### 阴影与光晕 - -`--shadow-sm/md/lg` 为**中性**(纯黑 rgba,无色调):小浮起(分段激活态、主按钮)/ 卡片 / 弹窗与浮层。多数表面是扁平的、靠边框定义;阴影只留给真正悬浮的东西(菜单、弹窗)。`--halo-ok/warn/err` 是状态点的柔光圈(按需使用,默认克制)。 +`--shadow-sm/md/lg` 为中性纯黑 rgba(无色调)。多数表面扁平、靠边框定义;阴影只留给真正悬浮的东西(菜单、弹窗)。 --- ## 2. 排版 ``` ---font-ui: 'Euclid Circular A', 'Helvetica Neue', Helvetica, Arial, sans-serif; ---font-mono: 'Source Code Pro', ui-monospace, 'SF Mono', Menlo, Monaco, monospace; ---fs: 12px; /* 基准 */ ---fs-sm: 11px; /* 小号:说明、计数、表头 */ +--font-ui: 'Euclid Circular A', 'Helvetica Neue', Helvetica, Arial, sans-serif; /* UI chrome */ +--font-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, Monaco, monospace; /* 数据 / 代码 */ +--fs: 12px; --fs-sm: 11px; +/* 排版尺度令牌(替代散落字面量):*/ +--fw-normal/medium/semibold/bold: 400/500/600/700; +--lh-tight/snug/normal/relaxed: 1.2/1.35/1.45/1.6; +--ls-tight/normal/wide: -0.01em/0/0.01em; ``` -- **UI sans** — 所有 chrome(标签、按钮、标题、连接名)。Euclid Circular A 为专有字体不内置,macOS 回退 Helvetica Neue。 -- **Source Code Pro** — *所有数据与代码*(编辑器、结果表、ObjectId、host、索引规格、库名 chip)。侧栏连接名也走等宽。 - -### 字号与处理 - -| 角色 | 字号 | 字重 | 备注 | -|---|---|---|---| -| 弹窗 / 空状态标题 | 16–22px | 700 | `letter-spacing: -0.01em` | -| 正文 / 标签 | 12–13px | 500–600 | | -| 区块标签(Connections 等) | 11px | 700 | 大驼峰,**不全大写**,`letter-spacing ~.01em` | -| 表头 | 11px | 700 | 大驼峰,发丝线下划线,**不全大写** | -| 数据 / 代码 | 11–12px | 400–500 | mono | -| 说明 / meta | 11px | 500 | mono,`--fg-2/3` | - -> **不使用纯英文大写**(`text-transform: uppercase` 已从全站清除,见 SPEC.md 多语言条目)。层级靠字重 + 颜色 + 细微字距,标签一律**大驼峰**(遵循全局与本项目 CLAUDE.md 的命名规则)。 +- **UI sans(Euclid Circular A)** — 所有 chrome(标签、按钮、标题、连接名)。专有字体不内置,回退 Helvetica Neue。 +- **JetBrains Mono(OFL-1.1,可商用,`@fontsource` 离线打包)** — *所有数据与代码*(编辑器、结果表、ObjectId、host、库名 chip)。侧栏连接名也走等宽。 +- **不使用纯英文大写**(`text-transform: uppercase` 全站清除);层级靠字重 + 颜色 + 细微字距,标签一律**大驼峰**。 --- -## 3. 形状、间距、层级 +## 3. 形状、间距、密度 ``` ---radius: 5px; /* 默认 — 按钮、输入、行、分段轨道 */ ---radius-sm: 4px; /* 密集控件、分段按钮、菜单项 */ +--radius: 5px; /* 默认 — 按钮、输入、行、分段 */ +--radius-sm: 4px; /* 密集控件、菜单项、树行药丸 */ --radius-lg: 9px; /* 弹窗 */ --row-h: 24px; /* 目录树 / 结果行高 */ ``` -圆角**偏锐**——存在但克制,不是 pill UI。 - -- **密度**:紧凑优先(这是给想在一屏看大量数据的人用的工具)。行内边距大致 `4–8px` 纵向 / `7–12px` 横向;面板 `6–14px`;弹窗 `12–18px`,遵循松散的 4px 节奏。 -- **可拖拽尺寸**:`--sidebar-width`(默认 300px)与 `--editor-height`(默认 160px)由 JS 从持久化设置覆写;拖拽分隔条见 `.resize-handle`。 +- 圆角**偏锐**——存在但克制,不是 pill UI。 +- **密度**:数据区紧凑优先(给想一屏看大量数据的人);**弹窗/表单则舒展**——Modal 三档宽度 `sm 480 / md 660(默认) / lg 760`,内边距 `px-6 py-5`,字段 16px 节奏。这是一个宽应用,弹窗应显著宽。 +- **可拖拽尺寸**:`--sidebar-width`(300px) / `--editor-height`(160px) 由 JS 从持久化设置覆写。 --- -## 4. 组件 +## 4. 架构(Tailwind + shadcn 风原语) + +详见 `CLAUDE.md`「样式约定」。要点: -### 按钮(Buttons) -- **组件 ` diff --git a/src/renderer/src/components/common/Modal.tsx b/src/renderer/src/components/common/Modal.tsx index 854ac3a..711a441 100644 --- a/src/renderer/src/components/common/Modal.tsx +++ b/src/renderer/src/components/common/Modal.tsx @@ -1,6 +1,7 @@ import { useRef, type ReactNode } from 'react' import { useTranslation } from 'react-i18next' import { Dialog, DialogClose, DialogTitle } from '@renderer/components/ui/Dialog' +import { cn } from '@renderer/lib/utils' // Focusable controls we want to land initial focus on (scoped to the body, which // excludes the header ✕). Covers native fields plus the ui/* primitives, whose @@ -12,39 +13,53 @@ interface ModalProps { onClose: () => void children: ReactNode footer?: ReactNode + /** Width preset. `small` is kept for back-compat (= 'sm'). Default 'md'. */ small?: boolean + size?: 'sm' | 'md' | 'lg' } /** * Minimal accessible modal. Public API unchanged (consumers conditionally mount - * it, so mount = open); internally backed by Base UI Dialog, which provides Esc / - * outside-press dismissal, focus trap+restore, and auto aria-labelledby wiring - * from `Dialog.Title` — so the old keydown listener and backdrop handler are gone. + * it, so mount = open); internally backed by Base UI Dialog (Esc / outside-press + * dismissal, focus trap+restore, aria wiring). shadcn-style Tailwind shell: a + * roomy elevated card on a dimmed backdrop. Positioning + backdrop come from + * ui/Dialog. Three width presets keep dense dialogs tight and forms spacious. */ -export function Modal({ title, onClose, children, footer, small }: ModalProps): JSX.Element { +export function Modal({ title, onClose, children, footer, small, size }: ModalProps): JSX.Element { const { t } = useTranslation() const bodyRef = useRef(null) + const width = small ? 'sm' : (size ?? 'md') return ( { if (!open) onClose() }} - className={small ? 'modal small' : 'modal'} + className={cn( + 'flex max-h-[88vh] max-w-[92vw] flex-col overflow-hidden rounded-xl border border-[var(--border-strong)] bg-card text-foreground shadow-[0_24px_64px_rgba(0,0,0,0.5)]', + width === 'sm' && 'w-[480px]', + width === 'md' && 'w-[660px]', + width === 'lg' && 'w-[760px]' + )} // Focus the first field in the body on open (preserving the old per-input // autoFocus); fall back to Base UI's default if the body has no control. initialFocus={() => bodyRef.current?.querySelector(FOCUSABLE) ?? true} > -
+
}>{title} - +
-
+
{children}
- {footer &&
{footer}
} + {footer && ( +
{footer}
+ )}
) } diff --git a/src/renderer/src/components/settings/SettingsModal.tsx b/src/renderer/src/components/settings/SettingsModal.tsx index 4ebff66..67af4b0 100644 --- a/src/renderer/src/components/settings/SettingsModal.tsx +++ b/src/renderer/src/components/settings/SettingsModal.tsx @@ -25,7 +25,6 @@ export function SettingsModal({ onClose }: { onClose: () => void }): JSX.Element return ( diff --git a/src/renderer/src/components/shell/ShellWorkspace.tsx b/src/renderer/src/components/shell/ShellWorkspace.tsx index 08823fe..5d7b285 100644 --- a/src/renderer/src/components/shell/ShellWorkspace.tsx +++ b/src/renderer/src/components/shell/ShellWorkspace.tsx @@ -66,6 +66,7 @@ export function ShellWorkspace(): JSX.Element { /> - - {running ? ( // Swap Run → Stop while a query is in flight, so a runaway // find/aggregate can be cancelled server-side (driver AbortSignal). - ) : ( - )} diff --git a/src/renderer/src/components/sidebar/ConnectionForm.tsx b/src/renderer/src/components/sidebar/ConnectionForm.tsx index 6751f66..002a0d3 100644 --- a/src/renderer/src/components/sidebar/ConnectionForm.tsx +++ b/src/renderer/src/components/sidebar/ConnectionForm.tsx @@ -18,6 +18,7 @@ import { Field } from '@renderer/components/ui/Field' import { Input } from '@renderer/components/ui/Input' import { Select } from '@renderer/components/ui/Select' import { Checkbox } from '@renderer/components/ui/Checkbox' +import { cn } from '@renderer/lib/utils' import { parseMongoUri, PRESET_COLORS } from '@renderer/lib/connectionUri' type Tab = 'general' | 'auth' | 'ssh' | 'tls' @@ -35,17 +36,19 @@ interface ConnectionFormProps { function DiagnoseResult({ stages }: { stages: DiagnoseStage[] }): JSX.Element { const { t } = useTranslation() return ( -
+
{stages.map((s) => { - const color = s.status === 'ok' ? '#4ade80' : s.status === 'fail' ? '#f87171' : '#9ca3af' + const color = s.status === 'ok' ? 'var(--ok)' : s.status === 'fail' ? 'var(--err)' : 'var(--fg-3)' const icon = s.status === 'ok' ? '✓' : s.status === 'fail' ? '✗' : '○' return ( -
- {icon} +
+ + {icon} + {t(`connection.ssh.stage.${s.key}`)} - {s.target && {s.target}} - {s.ms != null && {s.ms}ms} - {s.detail &&
{s.detail}
} + {s.target && {s.target}} + {s.ms != null && {s.ms}ms} + {s.detail &&
{s.detail}
}
) })} @@ -75,13 +78,13 @@ function DiagnoseControl({ const allOk = stages != null && stages.every((s) => s.status === 'ok') return ( <> -
+
{stages != null && !busy && ( <> - + {allOk ? '✓' : '✗'} @@ -446,6 +444,7 @@ export function ConnectionForm({ editing, onClose }: ConnectionFormProps): JSX.E {test && ( {test.ok ? [ @@ -466,11 +469,11 @@ export function ConnectionForm({ editing, onClose }: ConnectionFormProps): JSX.E )} {sshError && ( - + {sshError} )} - + @@ -488,10 +491,10 @@ export function ConnectionForm({ editing, onClose }: ConnectionFormProps): JSX.E {/* From URL / To URL: two independent one-way helpers, each in its own popup. From URL parses a pasted string INTO the fields; To URL exports the current fields OUT as a connection string. */} -
+
- - {parseNote && {parseNote}} + {parseNote && {parseNote}}
value={tab} onChange={setTab} + className="mb-5" items={[ { value: 'general', label: tFn('connection.tab.general') }, { value: 'auth', label: tFn('connection.tab.auth') }, @@ -524,21 +532,34 @@ export function ConnectionForm({ editing, onClose }: ConnectionFormProps): JSX.E setName(e.target.value)} placeholder="My MongoDB" /> -
- -
+
+ +
{PRESET_COLORS.map((c) => (