diff --git a/apps/web/package.json b/apps/web/package.json index 61bbb0d14..6bd361a9c 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -34,6 +34,7 @@ "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", "@fontsource-variable/manrope": "^5.3.0", + "@lezer/highlight": "^1.2.4", "@lingui/core": "^6.8.0", "@lingui/react": "^6.8.0", "@orpc/client": "^1.15.4", diff --git a/apps/web/src/features/resume/stylesheet/editor.tsx b/apps/web/src/features/resume/stylesheet/editor.tsx index c8830710a..2d55afd11 100644 --- a/apps/web/src/features/resume/stylesheet/editor.tsx +++ b/apps/web/src/features/resume/stylesheet/editor.tsx @@ -6,7 +6,7 @@ import type { SemanticCssColorToken } from "./color-tokens"; import type { SemanticCssEditorMetadata } from "./protocol"; import { defaultKeymap, indentWithTab } from "@codemirror/commands"; import { css } from "@codemirror/lang-css"; -import { defaultHighlightStyle, syntaxHighlighting } from "@codemirror/language"; +import { HighlightStyle, syntaxHighlighting } from "@codemirror/language"; import { Annotation, Compartment, EditorState, Prec, Transaction } from "@codemirror/state"; import { drawSelection, @@ -16,6 +16,7 @@ import { keymap, lineNumbers, } from "@codemirror/view"; +import { tags } from "@lezer/highlight"; import { t } from "@lingui/core/macro"; import { Trans } from "@lingui/react/macro"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; @@ -54,6 +55,18 @@ type EditorCompartments = { intelligence: Compartment; }; +/** CSS in the app's own inks, so it reads the same in light and dark. */ +const highlightStyle = HighlightStyle.define([ + { tag: [tags.comment, tags.meta], color: "var(--ink-3)", fontStyle: "italic" }, + { tag: [tags.tagName, tags.className, tags.labelName], color: "var(--accent-text)" }, + { tag: [tags.propertyName, tags.attributeName], color: "var(--info-text)" }, + { tag: [tags.string, tags.number, tags.unit, tags.color, tags.atom], color: "var(--warn-text)" }, + { tag: [tags.keyword, tags.modifier, tags.definitionKeyword, tags.controlKeyword], color: "var(--danger-text)" }, + { tag: [tags.variableName, tags.function(tags.variableName)], color: "var(--ink)", fontWeight: "500" }, + { tag: [tags.punctuation, tags.operator, tags.bracket], color: "var(--ink-2)" }, + { tag: tags.invalid, color: "var(--danger-text)", textDecoration: "underline wavy" }, +]); + const editorTheme = (dark: boolean): Extension => EditorView.theme( { @@ -65,7 +78,7 @@ const editorTheme = (dark: boolean): Extension => }, ".cm-scroller": { overflow: "auto", - fontFamily: '"JetBrains Mono Variable", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace', + fontFamily: "var(--font-mono)", lineHeight: "1.5", }, ".cm-content": { minHeight: "100%", padding: "0.75rem 0" }, @@ -162,7 +175,7 @@ export function StylesheetCodeEditor({ drawSelection(), highlightActiveLine(), css(), - syntaxHighlighting(defaultHighlightStyle, { fallback: true }), + syntaxHighlighting(highlightStyle), EditorView.editorAttributes.of({ dir: "ltr" }), EditorView.contentAttributes.of({ "aria-label": initial.label, dir: "ltr", spellcheck: "false" }), Prec.high( diff --git a/apps/web/src/features/resume/stylesheet/legacy-banner.tsx b/apps/web/src/features/resume/stylesheet/legacy-banner.tsx index 8934da52c..e5160df5d 100644 --- a/apps/web/src/features/resume/stylesheet/legacy-banner.tsx +++ b/apps/web/src/features/resume/stylesheet/legacy-banner.tsx @@ -21,7 +21,7 @@ export function LegacyStylesheetBanner({ disabled, onActivate }: LegacyStyleshee

diff --git a/apps/web/src/features/resume/stylesheet/status.tsx b/apps/web/src/features/resume/stylesheet/status.tsx index 70acafc6b..11e93d3e0 100644 --- a/apps/web/src/features/resume/stylesheet/status.tsx +++ b/apps/web/src/features/resume/stylesheet/status.tsx @@ -23,19 +23,19 @@ export function StylesheetStatus({ mode, status, diagnostics }: StylesheetStatus
{hasFatalErrors ? ( - + Fatal error ) : isPending ? ( {mode === "legacy" ? Checking draft : Checking} ) : hasRecoverableErrors ? ( - + {mode === "legacy" ? Ready to activate with errors : Valid with errors} ) : warnings.length > 0 ? ( - + {mode === "legacy" ? Ready to activate with warnings : Valid with warnings} ) : ( diff --git a/apps/web/src/features/resume/stylesheet/toolbar.tsx b/apps/web/src/features/resume/stylesheet/toolbar.tsx index 90b73022f..fe734b66f 100644 --- a/apps/web/src/features/resume/stylesheet/toolbar.tsx +++ b/apps/web/src/features/resume/stylesheet/toolbar.tsx @@ -52,23 +52,19 @@ export function StylesheetToolbar({ return (
- + - + void copySourceToClipboard(source)}> - + - + - {focused ? ( - - ) : ( - - )} + {focused ? : }
); diff --git a/packages/ui/src/components/icon.test.tsx b/packages/ui/src/components/icon.test.tsx index 56d212a9b..42c2c4e6c 100644 --- a/packages/ui/src/components/icon.test.tsx +++ b/packages/ui/src/components/icon.test.tsx @@ -19,6 +19,11 @@ describe("Icon", () => { expect(container.querySelector("button")).toHaveTextContent(/^Search$/); }); + it("keeps its glyph when a caller passes its own data-icon", () => { + const { container } = render(); + expect(container.querySelector('[data-slot="icon"]')).toHaveAttribute("data-icon", "undo"); + }); + it("uses the filled glyph only when asked", () => { const { container } = render(); expect(container.querySelector('[data-slot="icon"]')?.getAttribute("style")).toContain('"FILL" 1'); diff --git a/packages/ui/src/components/icon.tsx b/packages/ui/src/components/icon.tsx index 0102ffbd3..8e2398fef 100644 --- a/packages/ui/src/components/icon.tsx +++ b/packages/ui/src/components/icon.tsx @@ -29,6 +29,8 @@ function Icon({ name, filled = false, size = 20, className, style, ...props }: I