Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions living-docs/editor-styling-consolidation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
201 changes: 5 additions & 196 deletions packages/docx-editor-react/src/styles/editor.css
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
138 changes: 2 additions & 136 deletions packages/docx-editor-react/src/styles/prosemirror.css
Original file line number Diff line number Diff line change
Expand Up @@ -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%;
Expand Down Expand Up @@ -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
Expand Down
Loading