Files
tusk/src/lib/editor-theme.ts
Aleksey Shakhmatov da0001e77e feat(ui): "Graphite & Honey" redesign — warm dark, monospace-first
- new design system in globals.css: warm graphite surfaces, ivory text, honey
  accent; semantic status/data-type/syntax tokens replacing hardcoded colors
- IBM Plex Mono as the universal UI font (sans + mono), tabular numerals
- custom CodeMirror SQL theme (src/lib/editor-theme.ts) matching the palette
- data grid: zebra striping + honey row hover, stronger sticky header
- route status dots, JSON syntax, EXPLAIN cost, schema-tree icons and the
  read/write toggle through the new tokens
- TUSK wordmark in the toolbar
2026-05-23 15:02:19 +03:00

103 lines
4.2 KiB
TypeScript

import { EditorView } from "@codemirror/view";
import { HighlightStyle, syntaxHighlighting } from "@codemirror/language";
import { tags as t } from "@lezer/highlight";
import type { Extension } from "@codemirror/state";
/* ───────────────────────────────────────────────────────────
Tusk "Graphite & Honey" CodeMirror theme.
Palette mirrors the design tokens in styles/globals.css so the
SQL editor sits seamlessly inside the warm graphite workstation.
─────────────────────────────────────────────────────────── */
const c = {
bg: "oklch(0.186 0.006 75)",
surface: "oklch(0.205 0.007 75)",
fg: "oklch(0.9 0.013 80)",
faint: "oklch(0.52 0.012 75)",
gutter: "oklch(0.46 0.011 75)",
gutterActive: "oklch(0.74 0.012 80)",
cursor: "oklch(0.84 0.13 82)",
selection: "oklch(0.808 0.124 82 / 22%)",
activeLine: "oklch(0.808 0.124 82 / 5%)",
activeGutter: "oklch(0.808 0.124 82 / 9%)",
matchBg: "oklch(0.808 0.124 82 / 18%)",
// syntax
keyword: "oklch(0.82 0.125 82)", // honey — SELECT, FROM, WHERE
string: "oklch(0.76 0.13 152)", // green
number: "oklch(0.74 0.12 222)", // cyan-blue
func: "oklch(0.74 0.15 305)", // violet — built-ins
type: "oklch(0.74 0.12 200)", // teal-cyan — types
comment: "oklch(0.5 0.012 75)", // muted, italic
operator: "oklch(0.74 0.013 80)",
bracket: "oklch(0.66 0.012 78)",
invalid: "oklch(0.66 0.2 24)",
};
const tuskEditorTheme = EditorView.theme(
{
"&": {
color: c.fg,
backgroundColor: c.bg,
},
".cm-content": {
caretColor: c.cursor,
fontFamily: "var(--font-mono)",
padding: "8px 0",
},
".cm-cursor, .cm-dropCursor": { borderLeftColor: c.cursor, borderLeftWidth: "2px" },
"&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection":
{ backgroundColor: c.selection },
".cm-activeLine": { backgroundColor: c.activeLine },
".cm-gutters": {
backgroundColor: c.bg,
color: c.gutter,
border: "none",
borderRight: "1px solid oklch(0.34 0.011 75 / 50%)",
},
".cm-activeLineGutter": {
backgroundColor: c.activeGutter,
color: c.gutterActive,
},
".cm-foldGutter .cm-gutterElement": { color: c.faint },
".cm-selectionMatch": { backgroundColor: c.matchBg },
"&.cm-focused .cm-matchingBracket": {
backgroundColor: c.matchBg,
outline: "1px solid oklch(0.808 0.124 82 / 45%)",
borderRadius: "2px",
},
".cm-line": { padding: "0 4px 0 8px" },
".cm-scroller": { fontFamily: "var(--font-mono)" },
".cm-panels": { backgroundColor: c.surface, color: c.fg },
".cm-searchMatch": {
backgroundColor: "oklch(0.78 0.135 65 / 28%)",
outline: "1px solid oklch(0.78 0.135 65 / 50%)",
},
".cm-searchMatch.cm-searchMatch-selected": {
backgroundColor: "oklch(0.808 0.124 82 / 35%)",
},
},
{ dark: true }
);
const tuskHighlightStyle = HighlightStyle.define([
{ tag: [t.keyword, t.operatorKeyword, t.modifier], color: c.keyword, fontWeight: "500" },
{ tag: [t.string, t.special(t.string), t.character], color: c.string },
{ tag: [t.number, t.bool, t.null], color: c.number },
{ tag: [t.function(t.variableName), t.function(t.propertyName)], color: c.func },
{ tag: [t.typeName, t.className, t.namespace], color: c.type },
{ tag: [t.comment, t.lineComment, t.blockComment], color: c.comment, fontStyle: "italic" },
{ tag: [t.operator, t.compareOperator, t.arithmeticOperator, t.logicOperator], color: c.operator },
{ tag: [t.bracket, t.paren, t.squareBracket, t.brace, t.punctuation], color: c.bracket },
{ tag: [t.propertyName, t.attributeName], color: c.fg },
{ tag: [t.variableName, t.name], color: c.fg },
{ tag: [t.definitionKeyword], color: c.keyword, fontWeight: "500" },
{ tag: [t.invalid], color: c.invalid, textDecoration: "underline wavy" },
]);
/** Full Tusk editor theme: base UI styling + SQL syntax highlighting. */
export const tuskEditorExtensions: Extension = [
tuskEditorTheme,
syntaxHighlighting(tuskHighlightStyle),
];