diff --git a/apps/desktop/src/components/editor/QueryEditor.vue b/apps/desktop/src/components/editor/QueryEditor.vue index fb54bb1c0..2b8c97d9a 100644 --- a/apps/desktop/src/components/editor/QueryEditor.vue +++ b/apps/desktop/src/components/editor/QueryEditor.vue @@ -719,7 +719,7 @@ onMounted(async () => { if (!editorRef.value) return; const [ - { EditorView, keymap, rectangularSelection, hoverTooltip, showTooltip, Decoration }, + { EditorView, keymap, rectangularSelection, hoverTooltip, showTooltip, Decoration, tooltips }, { EditorState, Compartment, Prec, StateEffect, StateField }, { sql, MSSQL, MySQL, PostgreSQL, SQLDialect }, { basicSetup }, @@ -825,6 +825,7 @@ onMounted(async () => { }), basicSetup, sql({ dialect }), + tooltips({ parent: document.body }), autocompletion({ activateOnTyping: true, override: [ diff --git a/apps/desktop/src/lib/editorThemes.ts b/apps/desktop/src/lib/editorThemes.ts index a75aa457c..be8993750 100644 --- a/apps/desktop/src/lib/editorThemes.ts +++ b/apps/desktop/src/lib/editorThemes.ts @@ -91,40 +91,41 @@ export function editorFontTheme( export function buildSqlCompletionThemeRules(): CodeMirrorStyleSpec { return { ".cm-tooltip.cm-tooltip-autocomplete": { - background: "#24272c", - border: "1px solid rgba(10, 12, 16, 0.95)", - borderRadius: "10px", - boxShadow: - "0 18px 42px rgba(0, 0, 0, 0.46), 0 0 0 1px rgba(255, 255, 255, 0.08) inset, 0 1px 0 rgba(255, 255, 255, 0.06) inset", - color: "rgba(202, 207, 217, 0.95)", - fontFamily: "var(--font-mono, 'JetBrains Mono', 'SF Mono', monospace)", - minWidth: "420px", + background: "var(--popover)", + border: "1px solid color-mix(in oklch, var(--border) 82%, var(--foreground) 18%)", + borderRadius: "8px", + boxShadow: "0 8px 18px rgb(0 0 0 / 0.14)", + color: "var(--popover-foreground)", + fontFamily: `var(${EDITOR_FONT_FAMILY_CSS_VAR}, var(--font-mono, monospace))`, + maxWidth: "min(520px, calc(100vw - 24px))", + minWidth: "min(280px, calc(100vw - 24px))", overflow: "hidden", - padding: "6px 0", + padding: "4px 0", }, ".cm-tooltip.cm-tooltip-autocomplete > ul": { - maxHeight: "340px", - minWidth: "420px", - padding: "0 7px 0 !important", - scrollbarColor: "rgba(148, 153, 162, 0.42) transparent", + maxHeight: "min(280px, calc(100vh - 32px))", + minWidth: "min(280px, calc(100vw - 24px))", + padding: "0 4px 0 !important", + scrollbarColor: "color-mix(in oklch, var(--muted-foreground) 44%, transparent) transparent", scrollbarWidth: "thin", }, ".cm-tooltip.cm-tooltip-autocomplete > ul > li": { alignItems: "center", borderRadius: "6px", - color: "rgba(199, 204, 214, 0.92)", + color: "var(--popover-foreground)", display: "flex", - fontSize: "16px", - fontWeight: "760", - height: "34px", + fontSize: `clamp(12px, var(${EDITOR_FONT_SIZE_CSS_VAR}, 13px), 14px)`, + fontWeight: "520", + height: "28px", letterSpacing: "0", - lineHeight: "34px", - padding: "0 18px !important", + lineHeight: "28px", + padding: "0 10px !important", transition: "background-color 90ms ease, color 90ms ease", }, ".cm-tooltip.cm-tooltip-autocomplete > ul > li[aria-selected]": { - background: "rgba(70, 75, 84, 0.86) !important", - color: "rgba(231, 235, 243, 0.98) !important", + background: "color-mix(in oklch, var(--primary) 14%, var(--popover)) !important", + color: "var(--popover-foreground) !important", + outline: "1px solid color-mix(in oklch, var(--primary) 22%, transparent)", }, ".cm-completionIcon": { display: "none !important", @@ -135,22 +136,22 @@ export function buildSqlCompletionThemeRules(): CodeMirrorStyleSpec { }, ".cm-completionLabel": { color: "inherit", - fontFamily: "var(--font-mono, 'JetBrains Mono', 'SF Mono', monospace)", - fontSize: "16px", - fontWeight: "760", + fontFamily: `var(${EDITOR_FONT_FAMILY_CSS_VAR}, var(--font-mono, monospace))`, + fontSize: `clamp(12px, var(${EDITOR_FONT_SIZE_CSS_VAR}, 13px), 14px)`, + fontWeight: "520", letterSpacing: "0", }, ".cm-completionMatchedText": { - color: "#5794f9", - fontWeight: "860", + color: "oklch(0.62 0.19 255)", + fontWeight: "700", textDecoration: "none", }, ".cm-completionDetail": { - color: "rgba(184, 188, 198, 0.92)", - fontSize: "16px", - fontWeight: "760", + color: "color-mix(in oklch, var(--popover-foreground) 68%, var(--popover))", + fontSize: `clamp(11px, calc(var(${EDITOR_FONT_SIZE_CSS_VAR}, 13px) - 1px), 13px)`, + fontWeight: "500", fontStyle: "normal", - marginLeft: "12px", + marginLeft: "10px", opacity: "1", }, }; diff --git a/packages/app-tests/editorCompletionTheme.test.ts b/packages/app-tests/editorCompletionTheme.test.ts index 80f9b66b7..16ff7681b 100644 --- a/packages/app-tests/editorCompletionTheme.test.ts +++ b/packages/app-tests/editorCompletionTheme.test.ts @@ -1,4 +1,5 @@ import { strict as assert } from "node:assert"; +import { readFileSync } from "node:fs"; import test from "node:test"; import { buildEditorFontThemeRules, @@ -10,7 +11,18 @@ import { test("sql completion theme styles the autocomplete popup", () => { const rules = buildSqlCompletionThemeRules(); - assert.ok(rules[".cm-tooltip.cm-tooltip-autocomplete"]); + assert.deepEqual(rules[".cm-tooltip.cm-tooltip-autocomplete"], { + background: "var(--popover)", + border: "1px solid color-mix(in oklch, var(--border) 82%, var(--foreground) 18%)", + borderRadius: "8px", + boxShadow: "0 8px 18px rgb(0 0 0 / 0.14)", + color: "var(--popover-foreground)", + fontFamily: `var(${EDITOR_FONT_FAMILY_CSS_VAR}, var(--font-mono, monospace))`, + maxWidth: "min(520px, calc(100vw - 24px))", + minWidth: "min(280px, calc(100vw - 24px))", + overflow: "hidden", + padding: "4px 0", + }); assert.deepEqual(rules[".cm-completionIcon"], { display: "none !important", height: "0", @@ -20,18 +32,25 @@ test("sql completion theme styles the autocomplete popup", () => { }); assert.deepEqual(rules[".cm-completionLabel"], { color: "inherit", - fontFamily: "var(--font-mono, 'JetBrains Mono', 'SF Mono', monospace)", - fontSize: "16px", - fontWeight: "760", + fontFamily: `var(${EDITOR_FONT_FAMILY_CSS_VAR}, var(--font-mono, monospace))`, + fontSize: `clamp(12px, var(${EDITOR_FONT_SIZE_CSS_VAR}, 13px), 14px)`, + fontWeight: "520", letterSpacing: "0", }); - assert.equal(rules[".cm-completionMatchedText"]?.color, "#5794f9"); + assert.equal(rules[".cm-completionMatchedText"]?.color, "oklch(0.62 0.19 255)"); assert.equal( rules[".cm-tooltip.cm-tooltip-autocomplete > ul > li[aria-selected]"]?.background, - "rgba(70, 75, 84, 0.86) !important", + "color-mix(in oklch, var(--primary) 14%, var(--popover)) !important", ); }); +test("query editor portals CodeMirror tooltips outside clipped editor panes", () => { + const source = readFileSync("apps/desktop/src/components/editor/QueryEditor.vue", "utf8"); + + assert.match(source, /tooltips/); + assert.match(source, /tooltips\(\{\s*parent:\s*document\.body\s*\}\)/s); +}); + test("editor font theme reads size and family from CSS variables", () => { const rules = buildEditorFontThemeRules({ fixedHeight: true, scrollable: true });