From c36c6906bc85950481950cc800d2419909c1b871 Mon Sep 17 00:00:00 2001 From: Michael Hurhangee Date: Wed, 1 Jul 2026 14:03:25 +0100 Subject: [PATCH] refactor(editor): split the stylesheets by concern (tokens, revisions) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Carve the two entry stylesheets into concern files, wired via @import aggregators so consumers are unchanged (frontend still imports editor.css; hidden-prose-mirror still imports prosemirror.css): - tokens.css — the --docx-* palette (light + dark), out of editor.css. 'Where do I change an editor colour' now has one obvious home. - revisions.css — the tracked-change structural cues, out of prosemirror.css. editor.css @imports tokens.css; prosemirror.css @imports revisions.css. Verified behaviour-preserving by diffing sorted non-comment lines against the pre-split files (no rule lost or added). --- living-docs/editor-styling-consolidation.md | 11 +- .../docx-editor-react/src/styles/editor.css | 201 +----------------- .../src/styles/prosemirror.css | 138 +----------- .../src/styles/revisions.css | 141 ++++++++++++ .../docx-editor-react/src/styles/tokens.css | 198 +++++++++++++++++ 5 files changed, 355 insertions(+), 334 deletions(-) create mode 100644 packages/docx-editor-react/src/styles/revisions.css create mode 100644 packages/docx-editor-react/src/styles/tokens.css diff --git a/living-docs/editor-styling-consolidation.md b/living-docs/editor-styling-consolidation.md index f7b788ba..9d1c8b10 100644 --- a/living-docs/editor-styling-consolidation.md +++ b/living-docs/editor-styling-consolidation.md @@ -158,8 +158,15 @@ the app `--doc-paper`. End: `grep -r "--doc-"` returns nothing. Diff is pure ren copy of the find/AI/selection rules; static `editor.css` was the live source) + its barrel re-export. Block-SDT + section-break rules deduped: removed from `styles/editor.css` (chrome), kept in `prosemirror/editor.css` (painter/PM DOM — the semantically-correct home). -- **Next:** Phase 2 (relocate CSS into `docx-editor-react/src/styles/`) → Phase 3 (move `@theme` - mappings off the host) → Phase 4 (de-inline static thematic styles). +- **Phase 2 — DONE** (PR #163): both stylesheets relocated into `docx-editor-react/src/styles/` + (editor.css chrome, prosemirror.css painter); contract-path + `CLAUDE.md` updated; frontend's + now-unused core dep dropped. +- **Phase 2b — DONE** (branch `refactor/editor-styling-split`): concern-split via `@import` + aggregators (consumers unchanged) — `tokens.css` (the `--docx-*` palette) out of editor.css; + `revisions.css` (the redline cues) out of prosemirror.css. Verified no rule lost by diffing + sorted non-comment lines against the pre-split files. +- **Next:** Phase 3 (move `@theme` `--color-docx-*` mappings off the host into an editor CSS the + host `@import`s) → Phase 4 (de-inline static thematic styles). ## Smell ledger (found, not yet fixed — separate passes) - `styles/editor.css` `.paged-editor__decoration-overlay` + the `.ProseMirror-yjs-cursor` diff --git a/packages/docx-editor-react/src/styles/editor.css b/packages/docx-editor-react/src/styles/editor.css index 77b244b6..ae11645f 100644 --- a/packages/docx-editor-react/src/styles/editor.css +++ b/packages/docx-editor-react/src/styles/editor.css @@ -2,209 +2,18 @@ * DOCX Editor Styles — the editor's chrome stylesheet. * * Plain CSS, imported once where the editor mounts (apps/frontend's - * docx-viewer.tsx). Contains: - * - The `--docx-*` chrome token palette (light) + the `.ep-root.dark` overrides - * - Cursor/caret, selection, content-control, popup/menu chrome, etc. + * docx-viewer.tsx). Imports the `--docx-*` palette from tokens.css, then the + * dark-canvas smart-invert + cursor/caret, selection, content-control, + * popup/menu chrome, etc. * * The Tailwind UTILITIES the editor components use are generated by the host * app's own Tailwind, which `@source`s the editor source (see the consuming * app's index.css) — the editor ships no built CSS of its own. The shadcn * tokens (--background, --primary, --border, …) are inherited from the host - * app's theme; only the `--docx-*` tokens below are editor-owned. + * app's theme; only the `--docx-*` tokens (in tokens.css) are editor-owned. */ -/* ============================================================================ - * CSS CUSTOM PROPERTIES (shadcn theme) - * ============================================================================ */ - -/* The editor inherits the host app's shadcn/Tailwind tokens (--background, - * --primary, --border, --radius, …) from packages/ui/src/theme.css — it no - * longer defines its own, so the chrome shares Patrick's palette and dark mode. - * The app's Tailwind generates the editor's utility classes (it @sources the - * editor src); only the `--docx-*` chrome tokens below remain editor-owned. */ -.ep-root { - /* ====================================================================== - * Document-editor chrome palette — the single source of truth for UI - * colors. Components reference these via var(--docx-*); component styles - * carry no raw hex/rgba. Override the whole set under .ep-root.dark to - * theme the UI. The document canvas is painted from the .docx and is - * intentionally NOT themed here (it stays Word-faithful, like a page in - * Word/Google Docs that stays white regardless of app chrome). - * ==================================================================== */ - - /* Surfaces */ - --docx-surface: #ffffff; /* panels, menus, dropdowns, cards */ - --docx-card: #f8fbff; /* collapsed comment / suggestion cards (near-white) */ - --docx-bg: #f8f9fa; /* app background behind the page */ - --docx-bg-subtle: #f5f5f5; /* subtle section backgrounds */ - --docx-bg-hover: #f1f3f4; /* hover states */ - --docx-bg-input: #f8f9fa; /* input backgrounds */ - - /* Primary action color — Patrick's emerald (inherited, theme-aware). */ - --docx-primary: var(--primary); - --docx-primary-hover: color-mix(in oklab, var(--primary), black 12%); - --docx-primary-light: color-mix(in oklab, var(--primary) 14%, transparent); /* selected/active backgrounds */ - - /* Accent (indigo — agent / assistant UI) */ - --docx-accent: #6366f1; - --docx-accent-bg: #eef2ff; - - /* Foreground for text/icons sitting on a colored fill (primary/accent/ - * dark buttons, badges, tooltips). */ - --docx-on-primary: var(--primary-foreground); - - /* Active toolbar toggle (bold/italic/comments/...): a soft emerald tint with - * an emerald icon — consistent across light/dark, matching Patrick's accent. */ - --docx-toggle-active-bg: color-mix(in oklab, var(--primary) 14%, transparent); - --docx-toggle-active-fg: var(--primary); - - /* Text */ - --docx-text: #202124; /* primary text */ - --docx-text-muted: #5f6368; /* secondary text */ - --docx-text-subtle: #9aa0a6; /* tertiary/disabled text */ - --docx-text-placeholder: #999; /* placeholder text */ - - /* Borders */ - --docx-border: #e0e0e0; /* standard borders */ - --docx-border-light: #dadce0; /* lighter borders */ - --docx-border-dark: #d0d0d0; /* darker borders (tables) */ - --docx-border-input: #ccc; /* input borders */ - - /* Status */ - --docx-error: #c5221f; /* error states */ - --docx-error-bg: #fce8e6; /* error backgrounds */ - --docx-success: #34a853; /* success states */ - --docx-success-bg: #e8f5e9; /* success backgrounds */ - --docx-warning: #f9a825; /* warning states (icon/accent/border) */ - --docx-warning-bg: #fff8e1; /* warning backgrounds */ - --docx-warning-text: #856404; /* readable warning text on warning-bg */ - - /* Link */ - --docx-link: var(--primary); /* hyperlinks */ - - /* Tracked changes & comments — the single source of truth for the redline - * palette. Referenced by the painter, the toDOM marks, the structural - * revision CSS, and the focus-highlight overlay (no hardcoded copies). - * One value each, not overridden in dark — the `.layout-page` smart-invert - * filter recolours them for dark mode, exactly as the old literals were. */ - --docx-revision-ins: #2e7d32; /* insertion border/glyph/outline */ - --docx-revision-ins-bg: rgba(52, 168, 83, 0.08); /* insertion run background */ - --docx-revision-ins-bg-focus: rgba(52, 168, 83, 0.2); /* focused in the sidebar */ - --docx-revision-del: #c62828; /* deletion text/border/glyph/outline */ - --docx-revision-del-bg: rgba(211, 47, 47, 0.08); /* deletion run background */ - --docx-revision-del-bg-focus: rgba(211, 47, 47, 0.2); /* focused in the sidebar */ - --docx-comment-bg: rgba(255, 212, 0, 0.15); /* painted comment run */ - --docx-comment-border: rgba(255, 212, 0, 0.4); - --docx-comment-bg-focus: rgba(255, 212, 0, 0.35); /* focused comment */ - --docx-comment-border-focus: rgba(255, 212, 0, 0.7); - --docx-comment-bg-mark: rgba(255, 212, 0, 0.25); /* hidden-PM toDOM mark */ - --docx-comment-border-mark: rgba(255, 212, 0, 0.6); - - /* Shadows & overlays */ - --docx-shadow: rgba(0, 0, 0, 0.15); - --docx-shadow-strong: rgba(0, 0, 0, 0.3); - --docx-shadow-subtle: rgba(0, 0, 0, 0.06); - --docx-overlay: rgba(0, 0, 0, 0.5); /* modal backdrops */ - - /* Sidebar card elevation (Google-grey, compound) — shared with cardStyles.ts. */ - --docx-card-shadow: 0 1px 3px rgba(60, 64, 67, 0.2), 0 2px 6px rgba(60, 64, 67, 0.08); - --docx-card-shadow-strong: 0 1px 3px rgba(60, 64, 67, 0.3), 0 4px 8px 3px rgba(60, 64, 67, 0.15); - /* Elevated dropdown/popover — mirrors the Radix Select content (Tailwind shadow-lg). */ - --docx-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1); - - /* Focus ring & text selection (derived from primary) */ - --docx-focus-ring: color-mix(in oklab, var(--primary) 40%, transparent); - --docx-selection: color-mix(in oklab, var(--primary) 25%, transparent); - /* Stronger variant for selecting over highlighted/colored text. */ - --docx-selection-strong: color-mix(in oklab, var(--primary) 42%, transparent); - - /* Document scroll container scrollbar */ - --docx-scrollbar-size: 14px; - --docx-scrollbar-thumb: color-mix(in srgb, var(--docx-text-muted) 45%, transparent); - --docx-scrollbar-thumb-hover: color-mix(in srgb, var(--docx-text-muted) 65%, transparent); - --docx-scrollbar-track: transparent; -} - -/* ======================================================================== - * Dark theme. The `.ep-root` element gets the `dark` class when the app is in - * dark mode (DocxEditor's colorMode prop → resolveIsDark). Theming is driven by - * the `--docx-*` token overrides below — every chrome token is re-pointed here, - * so all chrome re-themes automatically. The shadcn tokens come from the host - * app's `.dark` theme, so chrome and app re-theme in lockstep. The document - * canvas is lightness-inverted to a comfortable dark page (the smart-invert - * trick further down); the saved DOCX is untouched and print reverts to white. - * ====================================================================== */ -/* Also targets nested `.ep-root` elements (e.g. the paged-editor root, Radix - * portals) so the base `.ep-root` light tokens don't shadow these inside a - * dark tree. */ -.ep-root.dark, -.ep-root.dark .ep-root { - /* --docx-* chrome palette (Word Online dark). The shadcn tokens are inherited - * from the host app's `.dark` theme, so chrome components re-theme in lockstep. */ - --docx-surface: #1f1f1f; /* ribbon / toolbar / title bar */ - --docx-card: #2d2d2d; /* collapsed comment / suggestion cards */ - --docx-bg: #262626; /* workspace behind the page */ - --docx-bg-subtle: #2d2d2d; /* dropdowns, dialogs, panels */ - --docx-bg-hover: #3a3a3a; /* hover states */ - --docx-bg-input: #333333; /* input fields */ - /* Accent tints are re-derived for dark mode. The host's dark --primary is - * actually DARKER (oklch 0.432 vs 0.508 light), so the light-block translucent - * tints would vanish over the #1f1f1f chrome — brighten the emerald and raise - * the opacities so active/selected/focus states stay legible on dark. */ - --docx-primary: color-mix(in oklab, var(--primary), white 26%); - --docx-primary-hover: color-mix(in oklab, var(--primary), white 12%); - --docx-primary-light: color-mix(in oklab, var(--primary) 30%, transparent); - --docx-on-primary: oklch(0.18 0.02 165); - --docx-toggle-active-bg: color-mix(in oklab, var(--primary) 32%, transparent); - --docx-toggle-active-fg: color-mix(in oklab, var(--primary), white 45%); - --docx-link: color-mix(in oklab, var(--primary), white 35%); - --docx-focus-ring: color-mix(in oklab, var(--primary) 60%, transparent); - --docx-selection: color-mix(in oklab, var(--primary) 32%, transparent); - --docx-selection-strong: color-mix(in oklab, var(--primary) 50%, transparent); - --docx-accent: #4e9dff; - --docx-accent-bg: #1f3047; - --docx-text: #e8e8e8; /* primary text */ - --docx-text-muted: #a0a0a0; /* secondary text */ - --docx-text-subtle: #6e6e6e; /* tertiary/disabled text */ - --docx-text-placeholder: #6e6e6e; - --docx-border: #3c3c3c; /* standard borders */ - --docx-border-light: #353535; /* lighter borders */ - --docx-border-dark: #555555; /* darker borders (tables) */ - --docx-border-input: #4a4a4a; /* input borders */ - --docx-error: #f28b82; - --docx-error-bg: #3c1f1d; - --docx-success: #81c995; - --docx-success-bg: #1f2e22; - --docx-warning: #fdd663; - --docx-warning-bg: #332a1a; - --docx-warning-text: #fdd663; - --docx-shadow: rgba(0, 0, 0, 0.4); - --docx-shadow-strong: rgba(0, 0, 0, 0.6); - --docx-shadow-subtle: rgba(0, 0, 0, 0.2); - --docx-card-shadow: 0 1px 3px rgba(0, 0, 0, 0.4), 0 2px 6px rgba(0, 0, 0, 0.2); - --docx-card-shadow-strong: 0 1px 3px rgba(0, 0, 0, 0.5), 0 4px 8px 3px rgba(0, 0, 0, 0.3); - --docx-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.5), 0 4px 6px -4px rgba(0, 0, 0, 0.5); - --docx-overlay: rgba(0, 0, 0, 0.7); - - /* Document canvas in dark mode (a VIEW transform — the saved DOCX never - * changes). Word doesn't just swap two colors; it perceptually inverts the - * LIGHTNESS of every document color while keeping its hue, so black text → - * white, dark-navy headings → light blue, dark-red → light red, etc. We get - * that for free by rendering the page in normal (light) colors and applying - * `invert(1) hue-rotate(180deg)` to the whole page below — hue-rotate undoes - * the hue flip that invert() introduces, leaving only the lightness flip. - * - * So the page is painted light here: pre-filter #ccc inverts to the ~#333 - * canvas Word uses; text stays #000 (→ near-white). Images are inverted back - * to normal by the counter-filter below. */ - --docx-page-bg: #cccccc; - /* --docx-page-text stays #000000 (light fallback) so it inverts to white. */ - - /* Caret on the dark canvas. The selection overlay sits OUTSIDE the inverted - * page (it's a sibling, not filtered), so a black caret would be invisible — - * point it at a light colour in dark mode. */ - --docx-caret: #e8e8e8; -} +@import './tokens.css'; /* Smart lightness inversion of the document canvas — the heart of dark mode. * invert(1) hue-rotate(180deg) flips lightness but preserves hue for ALL diff --git a/packages/docx-editor-react/src/styles/prosemirror.css b/packages/docx-editor-react/src/styles/prosemirror.css index 2bf5cce3..fc878611 100644 --- a/packages/docx-editor-react/src/styles/prosemirror.css +++ b/packages/docx-editor-react/src/styles/prosemirror.css @@ -4,6 +4,8 @@ * Provides page-like layout similar to Word/Google Docs */ +@import './revisions.css'; + /* Outer wrapper - handles scrolling and background */ .prosemirror-editor-wrapper { min-height: 100%; @@ -648,142 +650,6 @@ pointer-events: none; } -/* ============================================================================= - * Tracked structural revisions — painter + ProseMirror DOM cues - * - * The painter + ProseMirror DOM emit the same class set via toDOM and - * renderParagraph. Colours come from the shared --docx-revision-ins (green) / - * --docx-revision-del (red) tokens, so the whole redline palette has one - * source of truth. - * ============================================================================= */ - -/* Paragraph mark — painter fragment (renderParagraph.ts) */ -.layout-revision-pmark { - position: relative; -} -.layout-revision-pmark::before { - content: ''; - position: absolute; - left: -10px; - top: 0; - bottom: 0; - width: 2px; - pointer-events: none; -} -.layout-revision-ins.layout-revision-pmark::before { - background-color: var(--docx-revision-ins); -} -.layout-revision-del.layout-revision-pmark::before { - background-color: var(--docx-revision-del); -} -/* Pilcrow glyph — appended into the last line element by the painter so it - sits inline with the text instead of as a ::after pseudo on the fragment - (which would render as its own block row below the line). */ -.layout-revision-pmark-glyph { - display: inline; - margin-left: 2px; - font-weight: normal; - opacity: 0.75; - pointer-events: none; -} -.layout-revision-pmark-glyph.layout-revision-ins { - color: var(--docx-revision-ins); -} -.layout-revision-pmark-glyph.layout-revision-del { - color: var(--docx-revision-del); - text-decoration: line-through; -} - -/* Paragraph mark — hidden ProseMirror DOM (ParagraphExtension toDOM) */ -.ep-revision-pmark { - padding-left: 6px; - box-shadow: inset 2px 0 0 currentColor; -} -.ep-revision-pmark.ep-revision-ins { - color: var(--docx-revision-ins); -} -.ep-revision-pmark.ep-revision-del { - color: var(--docx-revision-del); -} - -/* Tracked row — colored left margin bar. Hidden ProseMirror table uses - normal block flow so we paint on the per-cell child (one bar per row). - The painted layout uses absolutely-positioned cells inside a relative - `.layout-table-row`, so a single ::before pseudo on the row draws one - continuous bar on the left edge — much cleaner than stamping a bar on - every cell child. */ -.ep-revision-row { - position: relative; -} -.layout-table-row.ep-revision-row::before { - content: ''; - position: absolute; - left: -10px; - top: 0; - bottom: 0; - width: 2px; - pointer-events: none; -} -.layout-table-row.ep-revision-row.ep-revision-ins::before { - background-color: var(--docx-revision-ins); -} -.layout-table-row.ep-revision-row.ep-revision-del::before { - background-color: var(--docx-revision-del); -} - -/* Whole-table tracked insertion / deletion — one tall bar in the page - margin to the LEFT of the table. Matches the paragraph change-bar - visual (`.layout-revision-pmark::before`) so the user sees a - consistent margin indicator for any tracked block. The painter sets - `overflow: visible` on tracked tables so this pseudo can extend - beyond the table's own bounds into the page padding area. */ -.layout-table.ep-revision-table::before { - content: ''; - position: absolute; - left: -10px; - top: 0; - bottom: 0; - width: 2px; - pointer-events: none; -} -.layout-table.ep-revision-table.ep-revision-ins::before { - background-color: var(--docx-revision-ins); -} -.layout-table.ep-revision-table.ep-revision-del::before { - background-color: var(--docx-revision-del); -} -/* Hidden PM DOM path — kept for the off-screen editing surface. */ -.ep-revision-row:not(.layout-table-row).ep-revision-ins > * { - box-shadow: inset 3px 0 0 var(--docx-revision-ins); -} -.ep-revision-row:not(.layout-table-row).ep-revision-del > * { - box-shadow: inset 3px 0 0 var(--docx-revision-del); - text-decoration: line-through; - text-decoration-color: color-mix(in srgb, var(--docx-revision-del) 60%, transparent); -} - -/* Tracked cell — colored top border + faint background tint */ -.ep-revision-cell.ep-revision-ins { - box-shadow: inset 0 3px 0 var(--docx-revision-ins); - background-color: color-mix(in srgb, var(--docx-revision-ins) 5%, transparent); -} -.ep-revision-cell.ep-revision-del { - box-shadow: inset 0 3px 0 var(--docx-revision-del); - background-color: color-mix(in srgb, var(--docx-revision-del) 5%, transparent); - text-decoration: line-through; - text-decoration-color: color-mix(in srgb, var(--docx-revision-del) 60%, transparent); -} -.ep-revision-cell.ep-revision-merge { - box-shadow: inset 0 3px 0 #5f6368; - background-color: rgba(95, 99, 104, 0.05); -} - -/* Property-change paragraphs/blocks — quiet left margin bar in a neutral - color so the change is visible without being loud. */ -.ep-revision-prop-change { - box-shadow: inset 2px 0 0 #5f6368; - padding-left: 6px; -} /* Block-level Structured Document Tag (content control) boundary. The painter lays body fragments as flat absolutely-positioned siblings, so diff --git a/packages/docx-editor-react/src/styles/revisions.css b/packages/docx-editor-react/src/styles/revisions.css new file mode 100644 index 00000000..ad565c96 --- /dev/null +++ b/packages/docx-editor-react/src/styles/revisions.css @@ -0,0 +1,141 @@ +/** + * Tracked-change structural revision cues (painter + ProseMirror DOM). Colours + * come from the shared --docx-revision-* tokens. Imported by prosemirror.css. + */ + +/* ============================================================================= + * Tracked structural revisions — painter + ProseMirror DOM cues + * + * The painter + ProseMirror DOM emit the same class set via toDOM and + * renderParagraph. Colours come from the shared --docx-revision-ins (green) / + * --docx-revision-del (red) tokens, so the whole redline palette has one + * source of truth. + * ============================================================================= */ + +/* Paragraph mark — painter fragment (renderParagraph.ts) */ +.layout-revision-pmark { + position: relative; +} +.layout-revision-pmark::before { + content: ''; + position: absolute; + left: -10px; + top: 0; + bottom: 0; + width: 2px; + pointer-events: none; +} +.layout-revision-ins.layout-revision-pmark::before { + background-color: var(--docx-revision-ins); +} +.layout-revision-del.layout-revision-pmark::before { + background-color: var(--docx-revision-del); +} +/* Pilcrow glyph — appended into the last line element by the painter so it + sits inline with the text instead of as a ::after pseudo on the fragment + (which would render as its own block row below the line). */ +.layout-revision-pmark-glyph { + display: inline; + margin-left: 2px; + font-weight: normal; + opacity: 0.75; + pointer-events: none; +} +.layout-revision-pmark-glyph.layout-revision-ins { + color: var(--docx-revision-ins); +} +.layout-revision-pmark-glyph.layout-revision-del { + color: var(--docx-revision-del); + text-decoration: line-through; +} + +/* Paragraph mark — hidden ProseMirror DOM (ParagraphExtension toDOM) */ +.ep-revision-pmark { + padding-left: 6px; + box-shadow: inset 2px 0 0 currentColor; +} +.ep-revision-pmark.ep-revision-ins { + color: var(--docx-revision-ins); +} +.ep-revision-pmark.ep-revision-del { + color: var(--docx-revision-del); +} + +/* Tracked row — colored left margin bar. Hidden ProseMirror table uses + normal block flow so we paint on the per-cell child (one bar per row). + The painted layout uses absolutely-positioned cells inside a relative + `.layout-table-row`, so a single ::before pseudo on the row draws one + continuous bar on the left edge — much cleaner than stamping a bar on + every cell child. */ +.ep-revision-row { + position: relative; +} +.layout-table-row.ep-revision-row::before { + content: ''; + position: absolute; + left: -10px; + top: 0; + bottom: 0; + width: 2px; + pointer-events: none; +} +.layout-table-row.ep-revision-row.ep-revision-ins::before { + background-color: var(--docx-revision-ins); +} +.layout-table-row.ep-revision-row.ep-revision-del::before { + background-color: var(--docx-revision-del); +} + +/* Whole-table tracked insertion / deletion — one tall bar in the page + margin to the LEFT of the table. Matches the paragraph change-bar + visual (`.layout-revision-pmark::before`) so the user sees a + consistent margin indicator for any tracked block. The painter sets + `overflow: visible` on tracked tables so this pseudo can extend + beyond the table's own bounds into the page padding area. */ +.layout-table.ep-revision-table::before { + content: ''; + position: absolute; + left: -10px; + top: 0; + bottom: 0; + width: 2px; + pointer-events: none; +} +.layout-table.ep-revision-table.ep-revision-ins::before { + background-color: var(--docx-revision-ins); +} +.layout-table.ep-revision-table.ep-revision-del::before { + background-color: var(--docx-revision-del); +} +/* Hidden PM DOM path — kept for the off-screen editing surface. */ +.ep-revision-row:not(.layout-table-row).ep-revision-ins > * { + box-shadow: inset 3px 0 0 var(--docx-revision-ins); +} +.ep-revision-row:not(.layout-table-row).ep-revision-del > * { + box-shadow: inset 3px 0 0 var(--docx-revision-del); + text-decoration: line-through; + text-decoration-color: color-mix(in srgb, var(--docx-revision-del) 60%, transparent); +} + +/* Tracked cell — colored top border + faint background tint */ +.ep-revision-cell.ep-revision-ins { + box-shadow: inset 0 3px 0 var(--docx-revision-ins); + background-color: color-mix(in srgb, var(--docx-revision-ins) 5%, transparent); +} +.ep-revision-cell.ep-revision-del { + box-shadow: inset 0 3px 0 var(--docx-revision-del); + background-color: color-mix(in srgb, var(--docx-revision-del) 5%, transparent); + text-decoration: line-through; + text-decoration-color: color-mix(in srgb, var(--docx-revision-del) 60%, transparent); +} +.ep-revision-cell.ep-revision-merge { + box-shadow: inset 0 3px 0 #5f6368; + background-color: rgba(95, 99, 104, 0.05); +} + +/* Property-change paragraphs/blocks — quiet left margin bar in a neutral + color so the change is visible without being loud. */ +.ep-revision-prop-change { + box-shadow: inset 2px 0 0 #5f6368; + padding-left: 6px; +} diff --git a/packages/docx-editor-react/src/styles/tokens.css b/packages/docx-editor-react/src/styles/tokens.css new file mode 100644 index 00000000..d27040e4 --- /dev/null +++ b/packages/docx-editor-react/src/styles/tokens.css @@ -0,0 +1,198 @@ +/** + * Editor design tokens — the --docx-* palette (light + dark). The single source + * of truth for editor colours; sits on .ep-root and cascades to all editor DOM + * (chrome + painter). Imported by editor.css. + */ + +/* ============================================================================ + * CSS CUSTOM PROPERTIES (shadcn theme) + * ============================================================================ */ + +/* The editor inherits the host app's shadcn/Tailwind tokens (--background, + * --primary, --border, --radius, …) from packages/ui/src/theme.css — it no + * longer defines its own, so the chrome shares Patrick's palette and dark mode. + * The app's Tailwind generates the editor's utility classes (it @sources the + * editor src); only the `--docx-*` chrome tokens below remain editor-owned. */ +.ep-root { + /* ====================================================================== + * Document-editor chrome palette — the single source of truth for UI + * colors. Components reference these via var(--docx-*); component styles + * carry no raw hex/rgba. Override the whole set under .ep-root.dark to + * theme the UI. The document canvas is painted from the .docx and is + * intentionally NOT themed here (it stays Word-faithful, like a page in + * Word/Google Docs that stays white regardless of app chrome). + * ==================================================================== */ + + /* Surfaces */ + --docx-surface: #ffffff; /* panels, menus, dropdowns, cards */ + --docx-card: #f8fbff; /* collapsed comment / suggestion cards (near-white) */ + --docx-bg: #f8f9fa; /* app background behind the page */ + --docx-bg-subtle: #f5f5f5; /* subtle section backgrounds */ + --docx-bg-hover: #f1f3f4; /* hover states */ + --docx-bg-input: #f8f9fa; /* input backgrounds */ + + /* Primary action color — Patrick's emerald (inherited, theme-aware). */ + --docx-primary: var(--primary); + --docx-primary-hover: color-mix(in oklab, var(--primary), black 12%); + --docx-primary-light: color-mix(in oklab, var(--primary) 14%, transparent); /* selected/active backgrounds */ + + /* Accent (indigo — agent / assistant UI) */ + --docx-accent: #6366f1; + --docx-accent-bg: #eef2ff; + + /* Foreground for text/icons sitting on a colored fill (primary/accent/ + * dark buttons, badges, tooltips). */ + --docx-on-primary: var(--primary-foreground); + + /* Active toolbar toggle (bold/italic/comments/...): a soft emerald tint with + * an emerald icon — consistent across light/dark, matching Patrick's accent. */ + --docx-toggle-active-bg: color-mix(in oklab, var(--primary) 14%, transparent); + --docx-toggle-active-fg: var(--primary); + + /* Text */ + --docx-text: #202124; /* primary text */ + --docx-text-muted: #5f6368; /* secondary text */ + --docx-text-subtle: #9aa0a6; /* tertiary/disabled text */ + --docx-text-placeholder: #999; /* placeholder text */ + + /* Borders */ + --docx-border: #e0e0e0; /* standard borders */ + --docx-border-light: #dadce0; /* lighter borders */ + --docx-border-dark: #d0d0d0; /* darker borders (tables) */ + --docx-border-input: #ccc; /* input borders */ + + /* Status */ + --docx-error: #c5221f; /* error states */ + --docx-error-bg: #fce8e6; /* error backgrounds */ + --docx-success: #34a853; /* success states */ + --docx-success-bg: #e8f5e9; /* success backgrounds */ + --docx-warning: #f9a825; /* warning states (icon/accent/border) */ + --docx-warning-bg: #fff8e1; /* warning backgrounds */ + --docx-warning-text: #856404; /* readable warning text on warning-bg */ + + /* Link */ + --docx-link: var(--primary); /* hyperlinks */ + + /* Tracked changes & comments — the single source of truth for the redline + * palette. Referenced by the painter, the toDOM marks, the structural + * revision CSS, and the focus-highlight overlay (no hardcoded copies). + * One value each, not overridden in dark — the `.layout-page` smart-invert + * filter recolours them for dark mode, exactly as the old literals were. */ + --docx-revision-ins: #2e7d32; /* insertion border/glyph/outline */ + --docx-revision-ins-bg: rgba(52, 168, 83, 0.08); /* insertion run background */ + --docx-revision-ins-bg-focus: rgba(52, 168, 83, 0.2); /* focused in the sidebar */ + --docx-revision-del: #c62828; /* deletion text/border/glyph/outline */ + --docx-revision-del-bg: rgba(211, 47, 47, 0.08); /* deletion run background */ + --docx-revision-del-bg-focus: rgba(211, 47, 47, 0.2); /* focused in the sidebar */ + --docx-comment-bg: rgba(255, 212, 0, 0.15); /* painted comment run */ + --docx-comment-border: rgba(255, 212, 0, 0.4); + --docx-comment-bg-focus: rgba(255, 212, 0, 0.35); /* focused comment */ + --docx-comment-border-focus: rgba(255, 212, 0, 0.7); + --docx-comment-bg-mark: rgba(255, 212, 0, 0.25); /* hidden-PM toDOM mark */ + --docx-comment-border-mark: rgba(255, 212, 0, 0.6); + + /* Shadows & overlays */ + --docx-shadow: rgba(0, 0, 0, 0.15); + --docx-shadow-strong: rgba(0, 0, 0, 0.3); + --docx-shadow-subtle: rgba(0, 0, 0, 0.06); + --docx-overlay: rgba(0, 0, 0, 0.5); /* modal backdrops */ + + /* Sidebar card elevation (Google-grey, compound) — shared with cardStyles.ts. */ + --docx-card-shadow: 0 1px 3px rgba(60, 64, 67, 0.2), 0 2px 6px rgba(60, 64, 67, 0.08); + --docx-card-shadow-strong: 0 1px 3px rgba(60, 64, 67, 0.3), 0 4px 8px 3px rgba(60, 64, 67, 0.15); + /* Elevated dropdown/popover — mirrors the Radix Select content (Tailwind shadow-lg). */ + --docx-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1); + + /* Focus ring & text selection (derived from primary) */ + --docx-focus-ring: color-mix(in oklab, var(--primary) 40%, transparent); + --docx-selection: color-mix(in oklab, var(--primary) 25%, transparent); + /* Stronger variant for selecting over highlighted/colored text. */ + --docx-selection-strong: color-mix(in oklab, var(--primary) 42%, transparent); + + /* Document scroll container scrollbar */ + --docx-scrollbar-size: 14px; + --docx-scrollbar-thumb: color-mix(in srgb, var(--docx-text-muted) 45%, transparent); + --docx-scrollbar-thumb-hover: color-mix(in srgb, var(--docx-text-muted) 65%, transparent); + --docx-scrollbar-track: transparent; +} + +/* ======================================================================== + * Dark theme. The `.ep-root` element gets the `dark` class when the app is in + * dark mode (DocxEditor's colorMode prop → resolveIsDark). Theming is driven by + * the `--docx-*` token overrides below — every chrome token is re-pointed here, + * so all chrome re-themes automatically. The shadcn tokens come from the host + * app's `.dark` theme, so chrome and app re-theme in lockstep. The document + * canvas is lightness-inverted to a comfortable dark page (the smart-invert + * trick further down); the saved DOCX is untouched and print reverts to white. + * ====================================================================== */ +/* Also targets nested `.ep-root` elements (e.g. the paged-editor root, Radix + * portals) so the base `.ep-root` light tokens don't shadow these inside a + * dark tree. */ +.ep-root.dark, +.ep-root.dark .ep-root { + /* --docx-* chrome palette (Word Online dark). The shadcn tokens are inherited + * from the host app's `.dark` theme, so chrome components re-theme in lockstep. */ + --docx-surface: #1f1f1f; /* ribbon / toolbar / title bar */ + --docx-card: #2d2d2d; /* collapsed comment / suggestion cards */ + --docx-bg: #262626; /* workspace behind the page */ + --docx-bg-subtle: #2d2d2d; /* dropdowns, dialogs, panels */ + --docx-bg-hover: #3a3a3a; /* hover states */ + --docx-bg-input: #333333; /* input fields */ + /* Accent tints are re-derived for dark mode. The host's dark --primary is + * actually DARKER (oklch 0.432 vs 0.508 light), so the light-block translucent + * tints would vanish over the #1f1f1f chrome — brighten the emerald and raise + * the opacities so active/selected/focus states stay legible on dark. */ + --docx-primary: color-mix(in oklab, var(--primary), white 26%); + --docx-primary-hover: color-mix(in oklab, var(--primary), white 12%); + --docx-primary-light: color-mix(in oklab, var(--primary) 30%, transparent); + --docx-on-primary: oklch(0.18 0.02 165); + --docx-toggle-active-bg: color-mix(in oklab, var(--primary) 32%, transparent); + --docx-toggle-active-fg: color-mix(in oklab, var(--primary), white 45%); + --docx-link: color-mix(in oklab, var(--primary), white 35%); + --docx-focus-ring: color-mix(in oklab, var(--primary) 60%, transparent); + --docx-selection: color-mix(in oklab, var(--primary) 32%, transparent); + --docx-selection-strong: color-mix(in oklab, var(--primary) 50%, transparent); + --docx-accent: #4e9dff; + --docx-accent-bg: #1f3047; + --docx-text: #e8e8e8; /* primary text */ + --docx-text-muted: #a0a0a0; /* secondary text */ + --docx-text-subtle: #6e6e6e; /* tertiary/disabled text */ + --docx-text-placeholder: #6e6e6e; + --docx-border: #3c3c3c; /* standard borders */ + --docx-border-light: #353535; /* lighter borders */ + --docx-border-dark: #555555; /* darker borders (tables) */ + --docx-border-input: #4a4a4a; /* input borders */ + --docx-error: #f28b82; + --docx-error-bg: #3c1f1d; + --docx-success: #81c995; + --docx-success-bg: #1f2e22; + --docx-warning: #fdd663; + --docx-warning-bg: #332a1a; + --docx-warning-text: #fdd663; + --docx-shadow: rgba(0, 0, 0, 0.4); + --docx-shadow-strong: rgba(0, 0, 0, 0.6); + --docx-shadow-subtle: rgba(0, 0, 0, 0.2); + --docx-card-shadow: 0 1px 3px rgba(0, 0, 0, 0.4), 0 2px 6px rgba(0, 0, 0, 0.2); + --docx-card-shadow-strong: 0 1px 3px rgba(0, 0, 0, 0.5), 0 4px 8px 3px rgba(0, 0, 0, 0.3); + --docx-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.5), 0 4px 6px -4px rgba(0, 0, 0, 0.5); + --docx-overlay: rgba(0, 0, 0, 0.7); + + /* Document canvas in dark mode (a VIEW transform — the saved DOCX never + * changes). Word doesn't just swap two colors; it perceptually inverts the + * LIGHTNESS of every document color while keeping its hue, so black text → + * white, dark-navy headings → light blue, dark-red → light red, etc. We get + * that for free by rendering the page in normal (light) colors and applying + * `invert(1) hue-rotate(180deg)` to the whole page below — hue-rotate undoes + * the hue flip that invert() introduces, leaving only the lightness flip. + * + * So the page is painted light here: pre-filter #ccc inverts to the ~#333 + * canvas Word uses; text stays #000 (→ near-white). Images are inverted back + * to normal by the counter-filter below. */ + --docx-page-bg: #cccccc; + /* --docx-page-text stays #000000 (light fallback) so it inverts to white. */ + + /* Caret on the dark canvas. The selection overlay sits OUTSIDE the inverted + * page (it's a sibling, not filtered), so a black caret would be invisible — + * point it at a light colour in dark mode. */ + --docx-caret: #e8e8e8; +}