From 053cc44f5ff316222f3615c96e4a3982e0df338f Mon Sep 17 00:00:00 2001 From: Caio Pizzol Date: Wed, 18 Mar 2026 10:54:51 -0300 Subject: [PATCH 01/11] fix: add backward compat aliases, fix hardcoded values, add theming docs - Add compat.css with aliases mapping old CSS variable names to new ones (prevents breaking change for customers using --sd-comment-*, --sd-track-*, etc.) - Fix hardcoded #ffffff in CommentDialog.vue input backgrounds (use token) - Fix hardcoded #dbdbdb in CommentHeader.vue hover (use token) - Add --sd-ui-comments-panel-input-background token to variables.css and neon-night theme - Extract cssToken() helper in renderer.ts to eliminate fallback duplication - Simplify applyDevTheme() to single template literal - Add theming overview page (getting-started/theming.mdx) - Add custom themes guide with full per-component variable reference - Add CSS variable migration guide with old-to-new mapping table --- apps/docs/docs.json | 7 +- apps/docs/getting-started/theming.mdx | 155 ++++++++++ apps/docs/guides/general/custom-themes.mdx | 292 ++++++++++++++++++ apps/docs/guides/migration/css-variables.mdx | 178 +++++++++++ .../painters/dom/src/renderer.ts | 51 ++- .../src/assets/styles/helpers/_all.css | 1 + .../src/assets/styles/helpers/compat.css | 81 +++++ .../src/assets/styles/helpers/variables.css | 1 + .../CommentsLayer/CommentDialog.vue | 4 +- .../CommentsLayer/CommentHeader.vue | 2 +- .../src/dev/components/SuperdocDev.vue | 5 +- .../superdoc/src/dev/themes/neon-night.css | 1 + 12 files changed, 737 insertions(+), 41 deletions(-) create mode 100644 apps/docs/getting-started/theming.mdx create mode 100644 apps/docs/guides/general/custom-themes.mdx create mode 100644 apps/docs/guides/migration/css-variables.mdx create mode 100644 packages/superdoc/src/assets/styles/helpers/compat.css diff --git a/apps/docs/docs.json b/apps/docs/docs.json index 0d442c4d47..a4f1d53f73 100644 --- a/apps/docs/docs.json +++ b/apps/docs/docs.json @@ -65,7 +65,8 @@ ] }, "getting-started/import-export", - "getting-started/fonts" + "getting-started/fonts", + "getting-started/theming" ] }, { @@ -235,6 +236,7 @@ "pages": [ "guides/general/storage", "guides/general/stable-navigation", + "guides/general/custom-themes", { "group": "Collaboration", "pages": [ @@ -248,7 +250,8 @@ "pages": [ "guides/migration/prosemirror", "guides/migration/breaking-changes-v1", - "guides/migration/typescript-migration" + "guides/migration/typescript-migration", + "guides/migration/css-variables" ] } ] diff --git a/apps/docs/getting-started/theming.mdx b/apps/docs/getting-started/theming.mdx new file mode 100644 index 0000000000..b31b849346 --- /dev/null +++ b/apps/docs/getting-started/theming.mdx @@ -0,0 +1,155 @@ +--- +title: Theming +sidebarTitle: Theming +keywords: "theming, css variables, custom theme, dark mode, design tokens, branding, css customization, styling" +--- + +Customize how SuperDoc looks using CSS variables. Change five variables and your brand colors flow through every component — toolbar, comments, dropdowns, everything. + +## Quick start + +Every SuperDoc component reads its colors, spacing, and typography from `--sd-*` CSS custom properties. Override them on any parent element and the changes cascade down. + +```css +/* Your custom theme — add to your stylesheet */ +.my-theme { + --sd-ui-action: #6366f1; /* buttons, active states */ + --sd-ui-action-hover: #4f46e5; + --sd-ui-toolbar-background: #f8fafc; /* toolbar surface */ + --sd-ui-surface: #ffffff; /* panels, cards */ + --sd-ui-text: #1e293b; /* body text */ +} +``` + +```html + + +
+ +``` + +That's it. No JavaScript config needed for visual customization. + +## Preset themes + +SuperDoc ships three preset themes you can use out of the box. Add the class to your `` or any wrapper element. + + + + Google Docs aesthetic. Clean blues, compact toolbar, subtle shadows. + ```html + + ``` + + + Microsoft Word aesthetic. Fluent-style surfaces and borders. + ```html + + ``` + + + High-contrast technical preset. Teal accents, structured layout. + ```html + + ``` + + + +Each preset overrides 30-50 variables across toolbar, dropdown, context menu, comments, and layout. You can layer your own overrides on top of a preset: + +```css +/* Start from the Docs preset, customize from there */ +.sd-theme-docs { + --sd-ui-action: #your-brand-color; + --sd-ui-comments-panel-card-background: #f0f0f0; +} +``` + +## Token architecture + +Variables are organized in three tiers. Higher tiers reference lower tiers, so changing a primitive cascades everywhere. + +``` +Primitives → UI semantic → Component-specific +--sd-color-* --sd-ui-* --sd-ui-toolbar-* +--sd-font-size-* --sd-ui-text --sd-ui-comments-panel-* +--sd-radius-* --sd-ui-action --sd-comments-highlight-* +``` + +### Tier 1: Primitives + +Raw color, sizing, and radius values. These are the building blocks. + +| Variable | Default | What it controls | +|----------|---------|-----------------| +| `--sd-color-blue-500` | `#1355ff` | Brand blue used throughout | +| `--sd-color-gray-*` | 50-900 scale | Neutral palette | +| `--sd-font-size-200` | `12px` | Small text (toolbar labels, timestamps) | +| `--sd-font-size-400` | `14px` | Body text (comments, inputs) | +| `--sd-radius-100` | `6px` | Default border radius | +| `--sd-radius-300` | `12px` | Larger radius (cards, panels) | + +### Tier 2: UI semantic + +Shared variables that many components reference. Change one of these and you affect the entire UI. + +| Variable | Default | What it controls | +|----------|---------|-----------------| +| `--sd-ui-font-family` | `Arial, Helvetica, sans-serif` | Font for all UI elements | +| `--sd-ui-text` | `#47484a` | Primary text color | +| `--sd-ui-text-muted` | `gray-700` | Secondary/muted text | +| `--sd-ui-surface` | `#ffffff` | Panel and card backgrounds | +| `--sd-ui-surface-hover` | `gray-400` | Hover states | +| `--sd-ui-border` | `gray-400` | Default borders | +| `--sd-ui-action` | `blue-500` | Action/accent color (buttons, links) | +| `--sd-ui-action-hover` | `blue-600` | Action hover state | +| `--sd-ui-shadow` | `0 4px 12px rgba(0,0,0,0.12)` | Default shadow | +| `--sd-ui-radius` | `6px` | Default border radius for UI elements | + +### Tier 3: Component-specific + +Fine-grained control over individual components. These override the semantic defaults for specific UI pieces. See the [custom themes guide](/guides/general/custom-themes) for the full reference. + +**Five groups to know:** + +1. **Toolbar** (`--sd-ui-toolbar-*`) — background, button colors, spacing +2. **Dropdowns** (`--sd-ui-dropdown-*`) — surfaces, borders, shadows for all dropdown menus +3. **Comments** (`--sd-ui-comments-panel-*`) — card backgrounds, text colors, input styling +4. **Comment highlights** (`--sd-comments-highlight-*`) — in-document highlight colors for internal/external comments +5. **Tracked changes** (`--sd-tracked-changes-*`) — insert/delete/format highlight colors + +## What about the JavaScript config? + +The `trackChangesConfig` and `commentsConfig` JavaScript options still work and take priority over CSS variables when set. Use the JS API for runtime changes, CSS variables for static theming. + +```javascript +// This still works — it overrides the CSS variable at runtime +new SuperDoc({ + selector: '#editor', + commentsConfig: { + highlightHoverColor: '#ff000055', + trackChangeHighlightColors: { + insertBorder: '#00ff00', + }, + }, +}); +``` + +## What's next + + + + Per-component variable reference with every token, default value, and which component it controls. + + + Mapping from old CSS variable names to the new system. + + diff --git a/apps/docs/guides/general/custom-themes.mdx b/apps/docs/guides/general/custom-themes.mdx new file mode 100644 index 0000000000..50cfb29419 --- /dev/null +++ b/apps/docs/guides/general/custom-themes.mdx @@ -0,0 +1,292 @@ +--- +title: Custom themes +sidebarTitle: Custom themes +keywords: "custom theme, css variables reference, design tokens, theming guide, component styling, dark theme" +--- + +A complete reference for every CSS variable you can override, organized by component. Start from the [theming overview](/getting-started/theming) if you haven't set up theming yet. + +## How to create a custom theme + +Define a CSS class with your overrides and apply it to any ancestor element. Most people apply it to ``. + +```css +.my-company-theme { + /* Start broad — these cascade to every component */ + --sd-ui-action: #8b5cf6; + --sd-ui-action-hover: #7c3aed; + --sd-ui-text: #1e293b; + --sd-ui-surface: #f8fafc; + --sd-ui-border: #e2e8f0; + + /* Then fine-tune specific components */ + --sd-ui-toolbar-background: #f1f5f9; + --sd-ui-comments-panel-card-background: #f0f0ff; +} +``` + + +You don't need to set every variable. Unset variables fall back to their defaults. Start with the 5-10 UI semantic variables and add component-specific ones only where the defaults don't look right. + + +## Building a dark theme + +For a dark theme, you'll need to override the core surface and text variables, plus set appropriate values for component backgrounds. Here's a starting point: + +```css +.dark-theme { + /* Core surface inversion */ + --sd-ui-surface: #1a1a2e; + --sd-ui-surface-hover: #2a2a3e; + --sd-ui-surface-active: #3a3a4e; + --sd-ui-text: #e2e8f0; + --sd-ui-text-muted: #94a3b8; + --sd-ui-border: #334155; + --sd-ui-action: #60a5fa; + --sd-ui-action-hover: #93c5fd; + + /* Toolbar */ + --sd-ui-toolbar-background: #0f172a; + --sd-ui-toolbar-button-text: #e2e8f0; + --sd-ui-toolbar-button-hover-surface: #1e293b; + --sd-ui-toolbar-button-active-surface: #334155; + + /* Dropdowns */ + --sd-ui-dropdown-surface: #1e293b; + --sd-ui-dropdown-border: #334155; + --sd-ui-dropdown-surface-hover: #2d3a4e; + --sd-ui-dropdown-shadow: 0 8px 24px rgba(0, 0, 0, 0.4); + + /* Comments */ + --sd-ui-comments-panel-card-background: #1e293b; + --sd-ui-comments-panel-card-active-background: #0f172a; + --sd-ui-comments-panel-input-background: #1e293b; + --sd-ui-comments-panel-input-border: #475569; + --sd-ui-comments-panel-author-text: #e2e8f0; + --sd-ui-comments-panel-body-text: #cbd5e1; + --sd-ui-comments-panel-timestamp-text: #64748b; + + /* Page */ + --sd-layout-page-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); +} +``` + +## Variable reference by component + +### General UI + +These are the top-level semantic variables. Most component-specific variables reference these as defaults, so changing them cascades broadly. + +| Variable | Default | Description | +|----------|---------|-------------| +| `--sd-ui-font-family` | `Arial, Helvetica, sans-serif` | Font for all UI elements | +| `--sd-ui-text` | `#47484a` | Primary text color | +| `--sd-ui-text-muted` | `gray-700` | Secondary text | +| `--sd-ui-text-disabled` | `gray-500` | Disabled state text | +| `--sd-ui-surface` | `#ffffff` | Default surface/background | +| `--sd-ui-surface-hover` | `gray-400` | Hover background | +| `--sd-ui-surface-active` | `#c8d0d8` | Active/pressed background | +| `--sd-ui-surface-disabled` | `gray-100` | Disabled surface | +| `--sd-ui-border` | `gray-400` | Default border color | +| `--sd-ui-action` | `blue-500` | Action/accent color | +| `--sd-ui-action-hover` | `blue-600` | Action hover | +| `--sd-ui-shadow` | `0 4px 12px rgba(0,0,0,0.12)` | Default shadow | +| `--sd-ui-radius` | `6px` | Default border radius | + +### Toolbar + +| Variable | Default | Description | +|----------|---------|-------------| +| `--sd-ui-toolbar-height` | `32px` | Toolbar row height | +| `--sd-ui-toolbar-padding-x` | `16px` | Horizontal padding | +| `--sd-ui-toolbar-padding-y` | `4px` | Vertical padding | +| `--sd-ui-toolbar-item-gap` | `2px` | Gap between buttons | +| `--sd-ui-toolbar-item-padding` | `5px` | Padding inside each button | +| `--sd-ui-toolbar-background` | `transparent` | Toolbar background | +| `--sd-ui-toolbar-button-text` | `var(--sd-ui-text)` | Button text/icon color | +| `--sd-ui-toolbar-button-hover-surface` | `var(--sd-ui-surface-hover)` | Button hover background | +| `--sd-ui-toolbar-button-active-surface` | `var(--sd-ui-surface-active)` | Button active background | + +### Dropdowns + +Applies to toolbar dropdowns (font picker, style picker, etc.) and the overflow menu. + +| Variable | Default | Description | +|----------|---------|-------------| +| `--sd-ui-dropdown-surface` | `#ffffff` | Dropdown panel background | +| `--sd-ui-dropdown-border` | `#e4e6eb` | Panel border | +| `--sd-ui-dropdown-text` | `var(--sd-ui-text)` | Option text | +| `--sd-ui-dropdown-hover-text` | `var(--sd-ui-dropdown-text)` | Hovered option text | +| `--sd-ui-dropdown-selected-text` | `var(--sd-ui-dropdown-text)` | Selected option text | +| `--sd-ui-dropdown-surface-hover` | `#d8dee5` | Option hover background | +| `--sd-ui-dropdown-surface-active` | `#d8dee5` | Option active/selected background | +| `--sd-ui-dropdown-option-radius` | `3px` | Option border radius | +| `--sd-ui-dropdown-shadow` | `0 8px 24px rgba(0,0,0,0.16)` | Panel shadow | + +### Context menu + +The right-click context menu. + +| Variable | Default | Description | +|----------|---------|-------------| +| `--sd-ui-context-menu-surface` | `#ffffff` | Menu background | +| `--sd-ui-context-menu-text` | `#47484a` | Menu text | +| `--sd-ui-context-menu-font-size` | `12px` | Menu text size | +| `--sd-ui-context-menu-radius` | `0` | Menu border radius | +| `--sd-ui-context-menu-border` | `#eee` | Menu border | +| `--sd-ui-context-menu-shadow` | complex | Menu shadow | +| `--sd-ui-context-menu-input-border` | `#ddd` | Search input border | +| `--sd-ui-context-menu-input-focus-border` | `#0096fd` | Search input focus border | +| `--sd-ui-context-menu-item-hover-surface` | `#f5f5f5` | Item hover background | +| `--sd-ui-context-menu-item-active-surface` | `#edf6ff` | Active item background | +| `--sd-ui-context-menu-item-active-text` | `#0096fd` | Active item text | + +### Tooltip + +| Variable | Default | Description | +|----------|---------|-------------| +| `--sd-ui-tooltip-surface` | `#262626` | Tooltip background | +| `--sd-ui-tooltip-text` | `#ffffff` | Tooltip text | +| `--sd-ui-tooltip-radius` | `6px` | Tooltip border radius | +| `--sd-ui-tooltip-shadow` | `0 3px 12px rgba(0,0,0,0.28)` | Tooltip shadow | + +### Comments panel + +Card styling, typography, and input elements for the comments sidebar. + +**Card appearance:** + +| Variable | Default | Description | +|----------|---------|-------------| +| `--sd-ui-comments-panel-card-background` | `#f3f6fd` | Default card background | +| `--sd-ui-comments-panel-card-hover-background` | `#f3f6fd` | Hovered card | +| `--sd-ui-comments-panel-card-active-background` | `#ffffff` | Selected/active card | +| `--sd-ui-comments-panel-card-resolved-background` | `#f0f0f0` | Resolved comment card | +| `--sd-ui-comments-panel-card-active-border` | `gray-300` | Active card border | +| `--sd-ui-comments-panel-card-radius` | `12px` | Card border radius | +| `--sd-ui-comments-panel-card-padding` | `16px` | Card inner padding | +| `--sd-ui-comments-panel-card-shadow` | complex | Card shadow | +| `--sd-ui-comments-panel-transition` | `all 200ms ease` | Card transition | +| `--sd-ui-comments-panel-separator` | `gray-300` | Divider between comments | + +**Typography:** + +| Variable | Default | Description | +|----------|---------|-------------| +| `--sd-ui-comments-panel-author-text` | `gray-900` | Author name color | +| `--sd-ui-comments-panel-author-size` | `14px` | Author name size | +| `--sd-ui-comments-panel-author-weight` | `600` | Author name weight | +| `--sd-ui-comments-panel-timestamp-text` | `gray-600` | Timestamp color | +| `--sd-ui-comments-panel-timestamp-size` | `12px` | Timestamp size | +| `--sd-ui-comments-panel-body-text` | `gray-900` | Comment body text | +| `--sd-ui-comments-panel-body-text-size` | `14px` | Comment body size | + +**Inputs:** + +| Variable | Default | Description | +|----------|---------|-------------| +| `--sd-ui-comments-panel-input-background` | `#ffffff` | Comment input background | +| `--sd-ui-comments-panel-input-border` | `gray-400` | Comment input border | + +**Status indicators:** + +| Variable | Default | Description | +|----------|---------|-------------| +| `--sd-ui-comments-panel-resolved-badge-text` | `green-500` | "Resolved" badge text | +| `--sd-ui-comments-panel-tc-insert-text` | `green-500` | Tracked change insert text | +| `--sd-ui-comments-panel-tc-delete-text` | `rose-500` | Tracked change delete text | + +**Dropdown (filter/sort menu):** + +| Variable | Default | Description | +|----------|---------|-------------| +| `--sd-ui-comments-panel-dropdown-background` | `#ffffff` | Dropdown background | +| `--sd-ui-comments-panel-dropdown-border` | `gray-400` | Dropdown border | +| `--sd-ui-comments-panel-dropdown-shadow` | complex | Dropdown shadow | +| `--sd-ui-comments-panel-dropdown-option-text` | body text | Option text | +| `--sd-ui-comments-panel-dropdown-option-hover-text` | option text | Hovered option text | +| `--sd-ui-comments-panel-dropdown-option-hover-background` | `#f3f3f5` | Hovered option background | +| `--sd-ui-comments-panel-dropdown-option-text-size` | `14px` | Option text size | + +**Visibility toggle (internal/external):** + +| Variable | Default | Description | +|----------|---------|-------------| +| `--sd-ui-comments-panel-visibility-internal-background` | `#cde6e6` | Internal badge background | +| `--sd-ui-comments-panel-visibility-external-background` | `#f5cfda` | External badge background | + +### Comment highlights (in-document) + +Colors applied to highlighted text in the document when comments are present. + +| Variable | Default | Description | +|----------|---------|-------------| +| `--sd-comments-highlight-external` | `#b1124b40` | External comment highlight | +| `--sd-comments-highlight-external-active` | `#b1124b66` | Active external highlight | +| `--sd-comments-highlight-external-faded` | `#b1124b20` | Faded external highlight | +| `--sd-comments-highlight-external-nested-border` | `#b1124b99` | Nested comment left border | +| `--sd-comments-highlight-internal` | `#07838340` | Internal comment highlight | +| `--sd-comments-highlight-internal-active` | `#07838366` | Active internal highlight | +| `--sd-comments-highlight-internal-faded` | `#07838320` | Faded internal highlight | +| `--sd-comments-highlight-internal-nested-border` | `#07838399` | Nested comment left border | +| `--sd-comments-highlight-hover` | `#1354ff55` | Hovered comment highlight | +| `--sd-comments-selection-background` | `#1354ff55` | Selected text highlight | + +### Tracked changes + +Colors for insert, delete, and format change decorations in the document. + +| Variable | Default | Description | +|----------|---------|-------------| +| `--sd-tracked-changes-insert-background` | `#399c7222` | Insert highlight | +| `--sd-tracked-changes-insert-border` | `#00853d` | Insert left border | +| `--sd-tracked-changes-delete-background` | `#cb0e4722` | Delete highlight | +| `--sd-tracked-changes-delete-border` | `#cb0e47` | Delete left border | +| `--sd-tracked-changes-format-border` | `gold` | Format change border | +| `--sd-tracked-changes-insert-background-focused` | `#399c7244` | Focused insert | +| `--sd-tracked-changes-delete-background-focused` | `#cb0e4744` | Focused delete | +| `--sd-tracked-changes-format-background-focused` | `#ffd70033` | Focused format change | + +### Content controls (structured content) + +Styling for DOCX content controls (SDTs) — form fields, dropdowns, date pickers. + +| Variable | Default | Description | +|----------|---------|-------------| +| `--sd-content-controls-block-border` | `#629be7` | Block control border | +| `--sd-content-controls-block-hover-background` | `#f2f2f2` | Block control hover | +| `--sd-content-controls-inline-border` | `#629be7` | Inline control border | +| `--sd-content-controls-inline-hover-background` | `#f2f2f2` | Inline control hover | +| `--sd-content-controls-label-border` | `#629be7` | Label border | +| `--sd-content-controls-label-background` | `#629be7ee` | Label background | +| `--sd-content-controls-label-text` | `#ffffff` | Label text | +| `--sd-content-controls-lock-hover-background` | `rgba(98,155,231,0.08)` | Locked control hover | + +### Layout + +Page-level appearance in presentation/layout mode. + +| Variable | Default | Description | +|----------|---------|-------------| +| `--sd-layout-page-background` | `#ffffff` | Page background | +| `--sd-layout-page-shadow` | `0 4px 20px rgba(15,23,42,0.08)` | Page shadow | + +### Tools (floating action toolbar) + +The floating tools toolbar that appears for AI/insert actions. + +| Variable | Default | Description | +|----------|---------|-------------| +| `--sd-ui-tools-gap` | `6px` | Gap between tool items | +| `--sd-ui-tools-item-size` | `50px` | Tool button size | +| `--sd-ui-tools-item-radius` | `12px` | Tool button radius | +| `--sd-ui-tools-item-surface` | `rgba(219,219,219,0.6)` | Tool button background | +| `--sd-ui-tools-icon-size` | `20px` | Icon size within buttons | + +## Source files + +The canonical token definitions live in your `node_modules/superdoc/` package: + +- **Default values:** `src/assets/styles/helpers/variables.css` +- **Preset themes:** `src/assets/styles/helpers/themes.css` +- **Backward-compat aliases:** `src/assets/styles/helpers/compat.css` diff --git a/apps/docs/guides/migration/css-variables.mdx b/apps/docs/guides/migration/css-variables.mdx new file mode 100644 index 0000000000..4fe4522493 --- /dev/null +++ b/apps/docs/guides/migration/css-variables.mdx @@ -0,0 +1,178 @@ +--- +title: CSS variable migration +sidebarTitle: CSS variables +keywords: "migration, css variables, renamed variables, breaking changes, backward compatibility, tokens" +--- + +SuperDoc's CSS variables were restructured into a three-tier token system (primitives, UI semantic, component-specific). If you're upgrading from a version that used the old flat variable names, this page maps every old name to its new equivalent. + + +**Backward compatibility is included.** The old variable names still work via aliases in `compat.css`, which is loaded automatically. You don't need to change your code immediately — but new code should use the new names. + + +## What changed + +The old system used flat, inconsistent names (`--sd-comment-bg`, `--sd-track-insert-border`, `--sd-surface-card`). The new system uses a structured hierarchy: + +``` +Old: --sd-comment-bg +New: --sd-ui-comments-panel-card-background + ───── ──────────── ──────────────────── + tier component property +``` + +The JavaScript config API (`trackChangesConfig`, `commentsConfig`) is unchanged. Only CSS variable names were restructured. + +## Full mapping + +### Semantic surfaces + +| Old variable | New variable | +|-------------|-------------| +| `--sd-surface-page` | `--sd-ui-surface` | +| `--sd-surface-canvas` | `--sd-color-gray-50` | +| `--sd-surface-card` | `--sd-ui-surface` | +| `--sd-surface-muted` | `--sd-ui-surface-disabled` | +| `--sd-surface-hover` | `--sd-ui-surface-hover` | +| `--sd-surface-selected` | `--sd-color-blue-100` | + +### Semantic text + +| Old variable | New variable | +|-------------|-------------| +| `--sd-text-primary` | `--sd-ui-text` | +| `--sd-text-secondary` | `--sd-color-gray-700` | +| `--sd-text-muted` | `--sd-ui-text-muted` | +| `--sd-text-placeholder` | `--sd-ui-text-disabled` | + +### Semantic borders + +| Old variable | New variable | +|-------------|-------------| +| `--sd-border-default` | `--sd-ui-border` | +| `--sd-border-subtle` | `--sd-ui-comments-panel-separator` | +| `--sd-border-focus` | `--sd-ui-action` | + +### Semantic actions + +| Old variable | New variable | +|-------------|-------------| +| `--sd-action-primary` | `--sd-ui-action` | +| `--sd-action-primary-hover` | `--sd-ui-action-hover` | + +### Typography + +| Old variable | New variable | +|-------------|-------------| +| `--sd-font-family` | `--sd-ui-font-family` | + +### Radius + +| Old variable | New variable | +|-------------|-------------| +| `--sd-radius-sm` | `--sd-radius-50` | +| `--sd-radius-md` | `--sd-radius-200` | +| `--sd-radius-lg` | `--sd-radius-300` | + +### Comment dialog + +| Old variable | New variable | +|-------------|-------------| +| `--sd-comment-bg` | `--sd-ui-comments-panel-card-background` | +| `--sd-comment-bg-hover` | `--sd-ui-comments-panel-card-hover-background` | +| `--sd-comment-bg-active` | `--sd-ui-comments-panel-card-active-background` | +| `--sd-comment-bg-resolved` | `--sd-ui-comments-panel-card-resolved-background` | +| `--sd-comment-border-active` | `--sd-ui-comments-panel-card-active-border` | +| `--sd-comment-radius` | `--sd-ui-comments-panel-card-radius` | +| `--sd-comment-padding` | `--sd-ui-comments-panel-card-padding` | +| `--sd-comment-shadow` | `--sd-ui-comments-panel-card-shadow` | +| `--sd-comment-separator` | `--sd-ui-comments-panel-separator` | +| `--sd-comment-transition` | `--sd-ui-comments-panel-transition` | + +### Comment typography + +| Old variable | New variable | +|-------------|-------------| +| `--sd-comment-author-color` | `--sd-ui-comments-panel-author-text` | +| `--sd-comment-author-size` | `--sd-ui-comments-panel-author-size` | +| `--sd-comment-author-weight` | `--sd-ui-comments-panel-author-weight` | +| `--sd-comment-time-color` | `--sd-ui-comments-panel-timestamp-text` | +| `--sd-comment-time-size` | `--sd-ui-comments-panel-timestamp-size` | +| `--sd-comment-body-size` | `--sd-ui-comments-panel-body-text-size` | + +### Comment tracked change colors + +| Old variable | New variable | +|-------------|-------------| +| `--sd-comment-tc-insert-color` | `--sd-ui-comments-panel-tc-insert-text` | +| `--sd-comment-tc-delete-color` | `--sd-ui-comments-panel-tc-delete-text` | + +### Comment highlights + +| Old variable | New variable | +|-------------|-------------| +| `--sd-comment-highlight-internal` | `--sd-comments-highlight-internal` | +| `--sd-comment-highlight-external` | `--sd-comments-highlight-external` | + + +`--sd-comment-highlight-opacity` and `--sd-comment-highlight-opacity-active` were removed. The new system uses pre-computed RGBA colors (e.g., `--sd-comments-highlight-external: #b1124b40`) instead of separate base color + opacity. Override the full RGBA value directly. + + +### Comment visibility toggle + +| Old variable | New variable | +|-------------|-------------| +| `--sd-comment-internal-bg` | `--sd-ui-comments-panel-visibility-internal-background` | +| `--sd-comment-external-bg` | `--sd-ui-comments-panel-visibility-external-background` | + +### Tracked changes + +| Old variable | New variable | +|-------------|-------------| +| `--sd-track-insert-border` | `--sd-tracked-changes-insert-border` | +| `--sd-track-insert-bg` | `--sd-tracked-changes-insert-background` | +| `--sd-track-delete-border` | `--sd-tracked-changes-delete-border` | +| `--sd-track-delete-bg` | `--sd-tracked-changes-delete-background` | +| `--sd-track-format-border` | `--sd-tracked-changes-format-border` | + +### Removed variables + +These variables were removed with no direct replacement: + +| Removed variable | Reason | +|-----------------|--------| +| `--sd-comment-highlight-opacity` | Replaced by pre-computed RGBA values | +| `--sd-comment-highlight-opacity-active` | Replaced by pre-computed RGBA values | +| `--sd-comment-max-width` | Sizing is now managed internally | +| `--sd-comment-min-width` | Sizing is now managed internally | +| `--sd-comment-group-bg` | Group bubble styling removed | +| `--sd-comment-group-color` | Group bubble styling removed | +| `--sd-comment-group-size` | Group bubble styling removed | +| `--sd-comment-group-expanded-size` | Group bubble styling removed | +| `--sd-font-mono` | No longer exposed as a token | + +## How backward compatibility works + +The `compat.css` file defines aliases that point old names to new ones: + +```css +/* Loaded automatically — you don't need to import this */ +:root { + --sd-comment-bg: var(--sd-ui-comments-panel-card-background); + --sd-track-insert-border: var(--sd-tracked-changes-insert-border); + /* ... etc */ +} +``` + +If you have existing CSS like: +```css +.my-overrides { + --sd-comment-bg: #f0f0f0; +} +``` + +It will continue to work. The alias in `:root` is overridden by your more-specific selector. + + +The compat aliases will be removed in the next major version. Plan to migrate your CSS to the new variable names when convenient. + diff --git a/packages/layout-engine/painters/dom/src/renderer.ts b/packages/layout-engine/painters/dom/src/renderer.ts index 0019cf8100..6547f8d4c9 100644 --- a/packages/layout-engine/painters/dom/src/renderer.ts +++ b/packages/layout-engine/painters/dom/src/renderer.ts @@ -596,38 +596,25 @@ type CommentHighlightToken = { fallback: string; }; -const COMMENT_HIGHLIGHT_EXTERNAL: CommentHighlightToken = { - css: 'var(--sd-comments-highlight-external, #B1124B40)', - fallback: '#B1124B40', -}; -const COMMENT_HIGHLIGHT_EXTERNAL_ACTIVE: CommentHighlightToken = { - css: 'var(--sd-comments-highlight-external-active, #B1124B66)', - fallback: '#B1124B66', -}; -const COMMENT_HIGHLIGHT_EXTERNAL_FADED: CommentHighlightToken = { - css: 'var(--sd-comments-highlight-external-faded, #B1124B20)', - fallback: '#B1124B20', -}; -const COMMENT_HIGHLIGHT_INTERNAL: CommentHighlightToken = { - css: 'var(--sd-comments-highlight-internal, #07838340)', - fallback: '#07838340', -}; -const COMMENT_HIGHLIGHT_INTERNAL_ACTIVE: CommentHighlightToken = { - css: 'var(--sd-comments-highlight-internal-active, #07838366)', - fallback: '#07838366', -}; -const COMMENT_HIGHLIGHT_INTERNAL_FADED: CommentHighlightToken = { - css: 'var(--sd-comments-highlight-internal-faded, #07838320)', - fallback: '#07838320', -}; -const COMMENT_HIGHLIGHT_EXTERNAL_NESTED_BORDER: CommentHighlightToken = { - css: 'var(--sd-comments-highlight-external-nested-border, #B1124B99)', - fallback: '#B1124B99', -}; -const COMMENT_HIGHLIGHT_INTERNAL_NESTED_BORDER: CommentHighlightToken = { - css: 'var(--sd-comments-highlight-internal-nested-border, #07838399)', - fallback: '#07838399', -}; +const cssToken = (varName: string, fallback: string): CommentHighlightToken => ({ + css: `var(${varName}, ${fallback})`, + fallback, +}); + +const COMMENT_HIGHLIGHT_EXTERNAL = cssToken('--sd-comments-highlight-external', '#B1124B40'); +const COMMENT_HIGHLIGHT_EXTERNAL_ACTIVE = cssToken('--sd-comments-highlight-external-active', '#B1124B66'); +const COMMENT_HIGHLIGHT_EXTERNAL_FADED = cssToken('--sd-comments-highlight-external-faded', '#B1124B20'); +const COMMENT_HIGHLIGHT_INTERNAL = cssToken('--sd-comments-highlight-internal', '#07838340'); +const COMMENT_HIGHLIGHT_INTERNAL_ACTIVE = cssToken('--sd-comments-highlight-internal-active', '#07838366'); +const COMMENT_HIGHLIGHT_INTERNAL_FADED = cssToken('--sd-comments-highlight-internal-faded', '#07838320'); +const COMMENT_HIGHLIGHT_EXTERNAL_NESTED_BORDER = cssToken( + '--sd-comments-highlight-external-nested-border', + '#B1124B99', +); +const COMMENT_HIGHLIGHT_INTERNAL_NESTED_BORDER = cssToken( + '--sd-comments-highlight-internal-nested-border', + '#07838399', +); type LinkRenderData = { href?: string; diff --git a/packages/superdoc/src/assets/styles/helpers/_all.css b/packages/superdoc/src/assets/styles/helpers/_all.css index 9ebb66bcde..1f70dc6a9b 100644 --- a/packages/superdoc/src/assets/styles/helpers/_all.css +++ b/packages/superdoc/src/assets/styles/helpers/_all.css @@ -1,2 +1,3 @@ @import './variables.css'; +@import './compat.css'; @import './themes.css'; diff --git a/packages/superdoc/src/assets/styles/helpers/compat.css b/packages/superdoc/src/assets/styles/helpers/compat.css new file mode 100644 index 0000000000..0e2ba4a72d --- /dev/null +++ b/packages/superdoc/src/assets/styles/helpers/compat.css @@ -0,0 +1,81 @@ +/* + * Backward-compatible aliases for renamed CSS variables. + * + * SuperDoc's theming system was restructured to use a three-tier token + * hierarchy (primitive → UI → component). The old flat variable names + * still work via these aliases, but new code should use the new names. + * + * These aliases will be removed in the next major version. + */ + +:root { + /* ─── Semantic surfaces → UI tokens ─── */ + --sd-surface-page: var(--sd-ui-surface); + --sd-surface-canvas: var(--sd-color-gray-50); + --sd-surface-card: var(--sd-ui-surface); + --sd-surface-muted: var(--sd-ui-surface-disabled); + --sd-surface-hover: var(--sd-ui-surface-hover); + --sd-surface-selected: var(--sd-color-blue-100); + + /* ─── Semantic text → UI tokens ─── */ + --sd-text-primary: var(--sd-ui-text); + --sd-text-secondary: var(--sd-color-gray-700); + --sd-text-muted: var(--sd-ui-text-muted); + --sd-text-placeholder: var(--sd-ui-text-disabled); + + /* ─── Semantic borders → UI tokens ─── */ + --sd-border-default: var(--sd-ui-border); + --sd-border-subtle: var(--sd-ui-comments-panel-separator); + --sd-border-focus: var(--sd-ui-action); + + /* ─── Semantic actions → UI tokens ─── */ + --sd-action-primary: var(--sd-ui-action); + --sd-action-primary-hover: var(--sd-ui-action-hover); + + /* ─── Typography ─── */ + --sd-font-family: var(--sd-ui-font-family); + + /* ─── Radius ─── */ + --sd-radius-sm: var(--sd-radius-50); + --sd-radius-md: var(--sd-radius-200); + --sd-radius-lg: var(--sd-radius-300); + + /* ─── Comment dialog ─── */ + --sd-comment-bg: var(--sd-ui-comments-panel-card-background); + --sd-comment-bg-hover: var(--sd-ui-comments-panel-card-hover-background); + --sd-comment-bg-active: var(--sd-ui-comments-panel-card-active-background); + --sd-comment-bg-resolved: var(--sd-ui-comments-panel-card-resolved-background); + --sd-comment-border-active: var(--sd-ui-comments-panel-card-active-border); + --sd-comment-radius: var(--sd-ui-comments-panel-card-radius); + --sd-comment-padding: var(--sd-ui-comments-panel-card-padding); + --sd-comment-shadow: var(--sd-ui-comments-panel-card-shadow); + --sd-comment-separator: var(--sd-ui-comments-panel-separator); + --sd-comment-transition: var(--sd-ui-comments-panel-transition); + + /* Comment: author & timestamp */ + --sd-comment-author-color: var(--sd-ui-comments-panel-author-text); + --sd-comment-author-size: var(--sd-ui-comments-panel-author-size); + --sd-comment-author-weight: var(--sd-ui-comments-panel-author-weight); + --sd-comment-time-color: var(--sd-ui-comments-panel-timestamp-text); + --sd-comment-time-size: var(--sd-ui-comments-panel-timestamp-size); + --sd-comment-body-size: var(--sd-ui-comments-panel-body-text-size); + + /* Comment: tracked change text colors */ + --sd-comment-tc-insert-color: var(--sd-ui-comments-panel-tc-insert-text); + --sd-comment-tc-delete-color: var(--sd-ui-comments-panel-tc-delete-text); + + /* Comment: document highlights */ + --sd-comment-highlight-internal: var(--sd-comments-highlight-internal); + --sd-comment-highlight-external: var(--sd-comments-highlight-external); + + /* Comment: internal/external toggle */ + --sd-comment-internal-bg: var(--sd-ui-comments-panel-visibility-internal-background); + --sd-comment-external-bg: var(--sd-ui-comments-panel-visibility-external-background); + + /* ─── Tracked changes (old short names) ─── */ + --sd-track-insert-border: var(--sd-tracked-changes-insert-border); + --sd-track-insert-bg: var(--sd-tracked-changes-insert-background); + --sd-track-delete-border: var(--sd-tracked-changes-delete-border); + --sd-track-delete-bg: var(--sd-tracked-changes-delete-background); + --sd-track-format-border: var(--sd-tracked-changes-format-border); +} diff --git a/packages/superdoc/src/assets/styles/helpers/variables.css b/packages/superdoc/src/assets/styles/helpers/variables.css index df25ae97e6..23d620ca06 100644 --- a/packages/superdoc/src/assets/styles/helpers/variables.css +++ b/packages/superdoc/src/assets/styles/helpers/variables.css @@ -145,6 +145,7 @@ --sd-ui-comments-panel-dropdown-option-hover-text: var(--sd-ui-comments-panel-dropdown-option-text); --sd-ui-comments-panel-dropdown-option-selected-text: var(--sd-ui-comments-panel-dropdown-option-text); --sd-ui-comments-panel-dropdown-option-hover-background: #f3f3f5; + --sd-ui-comments-panel-input-background: #ffffff; --sd-ui-comments-panel-input-border: var(--sd-color-gray-400); --sd-ui-comments-panel-visibility-internal-background: #cde6e6; --sd-ui-comments-panel-visibility-external-background: #f5cfda; diff --git a/packages/superdoc/src/components/CommentsLayer/CommentDialog.vue b/packages/superdoc/src/components/CommentsLayer/CommentDialog.vue index 00ab571237..f2521b8516 100644 --- a/packages/superdoc/src/components/CommentsLayer/CommentDialog.vue +++ b/packages/superdoc/src/components/CommentsLayer/CommentDialog.vue @@ -894,7 +894,7 @@ watch(editingCommentId, (commentId) => { border: 1.5px solid var(--sd-ui-comments-panel-input-border, #dbdbdb); border-radius: 12px; padding: 8.5px 10.5px; - background: #ffffff; + background: var(--sd-ui-comments-panel-input-background, #ffffff); margin-top: 4px; max-height: 150px; overflow-y: auto; @@ -941,7 +941,7 @@ watch(editingCommentId, (commentId) => { border: 1.5px solid var(--sd-ui-comments-panel-input-border, #dbdbdb); border-radius: 12px; padding: 8.5px 10.5px; - background: #ffffff; + background: var(--sd-ui-comments-panel-input-background, #ffffff); max-height: 150px; overflow-y: auto; } diff --git a/packages/superdoc/src/components/CommentsLayer/CommentHeader.vue b/packages/superdoc/src/components/CommentsLayer/CommentHeader.vue index 06253b3dea..b31cc3e665 100644 --- a/packages/superdoc/src/components/CommentsLayer/CommentHeader.vue +++ b/packages/superdoc/src/components/CommentsLayer/CommentHeader.vue @@ -259,7 +259,7 @@ const getCurrentUser = computed(() => { transition: all 250ms ease; } .overflow-menu__icon:hover { - background-color: #dbdbdb; + background-color: var(--sd-ui-comments-panel-separator, #dbdbdb); } .overflow-menu__icon :deep(svg) { width: 100%; diff --git a/packages/superdoc/src/dev/components/SuperdocDev.vue b/packages/superdoc/src/dev/components/SuperdocDev.vue index 1918e53234..c2dc34e6ed 100644 --- a/packages/superdoc/src/dev/components/SuperdocDev.vue +++ b/packages/superdoc/src/dev/components/SuperdocDev.vue @@ -81,10 +81,7 @@ const DEV_THEME_CLASSES = ['sd-theme-docs', 'sd-theme-word', 'sd-theme-blueprint const applyDevTheme = (theme) => { const html = document.documentElement; DEV_THEME_CLASSES.forEach((cls) => html.classList.remove(cls)); - if (theme === 'docs') html.classList.add('sd-theme-docs'); - if (theme === 'word') html.classList.add('sd-theme-word'); - if (theme === 'blueprint') html.classList.add('sd-theme-blueprint'); - if (theme === 'neon-night') html.classList.add('sd-theme-neon-night'); + if (theme !== 'default') html.classList.add(`sd-theme-${theme}`); }; // URL loading diff --git a/packages/superdoc/src/dev/themes/neon-night.css b/packages/superdoc/src/dev/themes/neon-night.css index d09c53fc9b..07a02a1390 100644 --- a/packages/superdoc/src/dev/themes/neon-night.css +++ b/packages/superdoc/src/dev/themes/neon-night.css @@ -50,6 +50,7 @@ --sd-ui-comments-panel-author-text: #e7f7ff; --sd-ui-comments-panel-timestamp-text: #8fc4ef; --sd-ui-comments-panel-body-text: #d6ecff; + --sd-ui-comments-panel-input-background: #0e1d33; --sd-ui-comments-panel-input-border: #4a82be; --sd-ui-comments-panel-dropdown-background: #0a1a30; --sd-ui-comments-panel-dropdown-border: #36659a; From 80169643b3fdd06acaf6cc027b9775b46397d00a Mon Sep 17 00:00:00 2001 From: Caio Pizzol Date: Wed, 18 Mar 2026 11:04:17 -0300 Subject: [PATCH 02/11] fix: wire CSS variable cascade so semantic tier changes propagate to all components Component-specific variables (dropdown, context menu, comments panel, content controls, layout) now reference semantic-tier variables instead of hardcoding hex values. This means setting --sd-ui-surface, --sd-ui-text, --sd-ui-border, --sd-ui-action, and --sd-ui-font-family cascades through every component automatically. Shadows and intentional deviations (tooltip inverse palette, comment card tint, highlight colors) remain component-specific. --- .../src/assets/styles/helpers/variables.css | 83 ++++++++++--------- 1 file changed, 44 insertions(+), 39 deletions(-) diff --git a/packages/superdoc/src/assets/styles/helpers/variables.css b/packages/superdoc/src/assets/styles/helpers/variables.css index 23d620ca06..80ddfb1d7c 100644 --- a/packages/superdoc/src/assets/styles/helpers/variables.css +++ b/packages/superdoc/src/assets/styles/helpers/variables.css @@ -43,7 +43,12 @@ --sd-radius-200: 8px; --sd-radius-300: 12px; - /* UI: general */ + /* + * UI: general (semantic tier) + * + * These are the "5-variable theme" — change these and the entire UI updates. + * Component-specific variables below cascade from these defaults. + */ --sd-ui-font-family: Arial, Helvetica, sans-serif; --sd-ui-text: #47484a; --sd-ui-text-muted: var(--sd-color-gray-700); @@ -66,24 +71,24 @@ --sd-ui-font-size-500: var(--sd-font-size-500); --sd-ui-font-size-600: var(--sd-font-size-600); - /* UI: dropdown */ - --sd-ui-dropdown-border: #e4e6eb; - --sd-ui-dropdown-surface: #ffffff; + /* UI: dropdown — cascades surface/text/border from semantic tier */ + --sd-ui-dropdown-border: var(--sd-ui-border); + --sd-ui-dropdown-surface: var(--sd-ui-surface); --sd-ui-dropdown-option-radius: 3px; --sd-ui-dropdown-text: var(--sd-ui-text); --sd-ui-dropdown-hover-text: var(--sd-ui-dropdown-text); --sd-ui-dropdown-selected-text: var(--sd-ui-dropdown-text); - --sd-ui-dropdown-surface-hover: #d8dee5; - --sd-ui-dropdown-surface-active: #d8dee5; + --sd-ui-dropdown-surface-hover: var(--sd-ui-surface-hover); + --sd-ui-dropdown-surface-active: var(--sd-ui-surface-active); --sd-ui-dropdown-shadow: 0 8px 24px rgba(0, 0, 0, 0.16); - /* UI: tooltip */ + /* UI: tooltip — intentionally inverted palette (dark bg, light text) */ --sd-ui-tooltip-surface: #262626; --sd-ui-tooltip-text: #ffffff; --sd-ui-tooltip-radius: var(--sd-radius-100); --sd-ui-tooltip-shadow: 0 3px 12px rgba(0, 0, 0, 0.28); - /* UI: toolbar */ + /* UI: toolbar — cascades text/hover from semantic tier */ --sd-ui-toolbar-height: 32px; --sd-ui-toolbar-padding-x: 16px; --sd-ui-toolbar-padding-y: 4px; @@ -94,18 +99,18 @@ --sd-ui-toolbar-button-hover-surface: var(--sd-ui-surface-hover); --sd-ui-toolbar-button-active-surface: var(--sd-ui-surface-active); - /* UI: context menu */ - --sd-ui-context-menu-surface: #ffffff; - --sd-ui-context-menu-text: #47484a; + /* UI: context menu — cascades surface/text/border/action from semantic tier */ + --sd-ui-context-menu-surface: var(--sd-ui-surface); + --sd-ui-context-menu-text: var(--sd-ui-text); --sd-ui-context-menu-font-size: var(--sd-font-size-200); --sd-ui-context-menu-radius: 0; --sd-ui-context-menu-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05), 0px 10px 20px rgba(0, 0, 0, 0.1); - --sd-ui-context-menu-border: #eee; - --sd-ui-context-menu-input-border: #ddd; - --sd-ui-context-menu-input-focus-border: #0096fd; - --sd-ui-context-menu-item-hover-surface: #f5f5f5; - --sd-ui-context-menu-item-active-surface: #edf6ff; - --sd-ui-context-menu-item-active-text: #0096fd; + --sd-ui-context-menu-border: var(--sd-ui-border); + --sd-ui-context-menu-input-border: var(--sd-ui-border); + --sd-ui-context-menu-input-focus-border: var(--sd-ui-action); + --sd-ui-context-menu-item-hover-surface: var(--sd-ui-surface-hover); + --sd-ui-context-menu-item-active-surface: var(--sd-ui-surface-active); + --sd-ui-context-menu-item-active-text: var(--sd-ui-action); /* UI: tools */ --sd-ui-tools-gap: 6px; @@ -114,43 +119,43 @@ --sd-ui-tools-item-surface: rgba(219, 219, 219, 0.6); --sd-ui-tools-icon-size: 20px; - /* UI: comments panel */ + /* UI: comments panel — cascades surface/text/border from semantic tier */ --sd-ui-comments-panel-card-background: #f3f6fd; - --sd-ui-comments-panel-card-hover-background: #f3f6fd; - --sd-ui-comments-panel-card-active-background: #ffffff; - --sd-ui-comments-panel-card-resolved-background: #f0f0f0; - --sd-ui-comments-panel-card-active-border: var(--sd-color-gray-300); + --sd-ui-comments-panel-card-hover-background: var(--sd-ui-comments-panel-card-background); + --sd-ui-comments-panel-card-active-background: var(--sd-ui-surface); + --sd-ui-comments-panel-card-resolved-background: var(--sd-ui-surface-disabled); + --sd-ui-comments-panel-card-active-border: var(--sd-ui-border); --sd-ui-comments-panel-card-radius: var(--sd-radius-300); --sd-ui-comments-panel-card-padding: 16px; --sd-ui-comments-panel-card-shadow: 0px 4px 12px 0px rgba(50, 50, 50, 0.15); - --sd-ui-comments-panel-separator: var(--sd-color-gray-300); + --sd-ui-comments-panel-separator: var(--sd-ui-border); --sd-ui-comments-panel-transition: all 200ms ease; - --sd-ui-comments-panel-author-text: var(--sd-color-gray-900); + --sd-ui-comments-panel-author-text: var(--sd-ui-text); --sd-ui-comments-panel-author-size: var(--sd-font-size-400); --sd-ui-comments-panel-author-weight: 600; - --sd-ui-comments-panel-imported-tag-text: var(--sd-color-gray-600); + --sd-ui-comments-panel-imported-tag-text: var(--sd-ui-text-muted); --sd-ui-comments-panel-imported-tag-background: var(--sd-color-gray-200); - --sd-ui-comments-panel-timestamp-text: var(--sd-color-gray-600); + --sd-ui-comments-panel-timestamp-text: var(--sd-ui-text-muted); --sd-ui-comments-panel-timestamp-size: var(--sd-font-size-200); - --sd-ui-comments-panel-body-text: var(--sd-color-gray-900); + --sd-ui-comments-panel-body-text: var(--sd-ui-text); --sd-ui-comments-panel-body-text-size: var(--sd-font-size-400); --sd-ui-comments-panel-resolved-badge-text: var(--sd-color-green-500); --sd-ui-comments-panel-tc-insert-text: var(--sd-color-green-500); --sd-ui-comments-panel-tc-delete-text: var(--sd-color-rose-500); - --sd-ui-comments-panel-dropdown-border: var(--sd-color-gray-400); - --sd-ui-comments-panel-dropdown-background: #ffffff; + --sd-ui-comments-panel-dropdown-border: var(--sd-ui-border); + --sd-ui-comments-panel-dropdown-background: var(--sd-ui-surface); --sd-ui-comments-panel-dropdown-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); --sd-ui-comments-panel-dropdown-option-text-size: var(--sd-font-size-400); --sd-ui-comments-panel-dropdown-option-text: var(--sd-ui-comments-panel-body-text); --sd-ui-comments-panel-dropdown-option-hover-text: var(--sd-ui-comments-panel-dropdown-option-text); --sd-ui-comments-panel-dropdown-option-selected-text: var(--sd-ui-comments-panel-dropdown-option-text); - --sd-ui-comments-panel-dropdown-option-hover-background: #f3f3f5; - --sd-ui-comments-panel-input-background: #ffffff; - --sd-ui-comments-panel-input-border: var(--sd-color-gray-400); + --sd-ui-comments-panel-dropdown-option-hover-background: var(--sd-ui-surface-hover); + --sd-ui-comments-panel-input-background: var(--sd-ui-surface); + --sd-ui-comments-panel-input-border: var(--sd-ui-border); --sd-ui-comments-panel-visibility-internal-background: #cde6e6; --sd-ui-comments-panel-visibility-external-background: #f5cfda; - /* Styles: comments */ + /* Styles: comments — document-level highlight colors, intentionally standalone */ --sd-comments-highlight-external-base: #b1124b; --sd-comments-highlight-external: #b1124b40; --sd-comments-highlight-external-active: #b1124b66; @@ -164,7 +169,7 @@ --sd-comments-highlight-hover: #1354ff55; --sd-comments-selection-background: #1354ff55; - /* Styles: tracked changes */ + /* Styles: tracked changes — document-level decoration colors, intentionally standalone */ --sd-tracked-changes-insert-background: #399c7222; --sd-tracked-changes-insert-border: #00853d; --sd-tracked-changes-delete-background: #cb0e4722; @@ -174,17 +179,17 @@ --sd-tracked-changes-delete-background-focused: #cb0e4744; --sd-tracked-changes-format-background-focused: #ffd70033; - /* Styles: content controls (SDT) */ + /* Styles: content controls (SDT) — blue accent, intentionally standalone */ --sd-content-controls-block-border: #629be7; - --sd-content-controls-block-hover-background: #f2f2f2; + --sd-content-controls-block-hover-background: var(--sd-ui-surface-hover); --sd-content-controls-inline-border: #629be7; - --sd-content-controls-inline-hover-background: #f2f2f2; + --sd-content-controls-inline-hover-background: var(--sd-ui-surface-hover); --sd-content-controls-label-border: #629be7; --sd-content-controls-label-background: #629be7ee; --sd-content-controls-label-text: #ffffff; --sd-content-controls-lock-hover-background: rgba(98, 155, 231, 0.08); - /* Styles: layout */ - --sd-layout-page-background: #ffffff; + /* Styles: layout — cascades surface from semantic tier */ + --sd-layout-page-background: var(--sd-ui-surface); --sd-layout-page-shadow: 0 4px 20px rgba(15, 23, 42, 0.08); } From f1a947e529b7c4a60f1a192adf8972529342651b Mon Sep 17 00:00:00 2001 From: Caio Pizzol Date: Wed, 18 Mar 2026 11:09:59 -0300 Subject: [PATCH 03/11] refactor: rename CSS variables for consistency and DX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Standardize naming convention across all CSS variables: - surface/background → bg (consistent property naming) - context-menu → menu (shorter, no disambiguation needed) - comments-panel → comments (drop redundant -panel-) - State placement: standardize to {state}-{property} pattern (e.g., dropdown-hover-bg not dropdown-surface-hover) Average variable name length drops from ~42 to ~28 chars. 62 variables renamed across 29 files. Backward compat aliases in compat.css already point old (pre-PR) names to new names. --- apps/docs/getting-started/theming.mdx | 14 +- apps/docs/guides/general/custom-themes.mdx | 154 +++++++++--------- apps/docs/guides/migration/css-variables.mdx | 54 +++--- .../painters/dom/src/renderer.ts | 2 +- .../layout-engine/painters/dom/src/styles.ts | 14 +- .../styles/extensions/structured-content.css | 6 +- .../components/context-menu/ContextMenu.vue | 32 ++-- .../components/toolbar/AlignmentButtons.vue | 2 +- .../src/components/toolbar/DocumentMode.vue | 4 +- .../src/components/toolbar/IconGrid.vue | 2 +- .../src/components/toolbar/IconGridRow.vue | 2 +- .../src/components/toolbar/LinkInput.vue | 10 +- .../src/components/toolbar/LinkedStyle.vue | 2 +- .../src/components/toolbar/OverflowMenu.vue | 2 +- .../src/components/toolbar/SdTooltip.vue | 4 +- .../src/components/toolbar/TableActions.vue | 4 +- .../src/components/toolbar/TableGrid.vue | 2 +- .../src/components/toolbar/Toolbar.vue | 2 +- .../src/components/toolbar/ToolbarButton.vue | 4 +- .../components/toolbar/ToolbarDropdown.vue | 6 +- packages/superdoc/src/SuperDoc.vue | 2 +- .../src/assets/styles/helpers/compat.css | 50 +++--- .../src/assets/styles/helpers/themes.css | 128 +++++++-------- .../src/assets/styles/helpers/variables.css | 124 +++++++------- .../CommentsLayer/CommentDialog.vue | 50 +++--- .../CommentsLayer/CommentHeader.vue | 16 +- .../CommentsLayer/CommentsDropdown.vue | 14 +- .../CommentsLayer/InternalDropdown.vue | 4 +- .../superdoc/src/dev/themes/neon-night.css | 74 ++++----- 29 files changed, 392 insertions(+), 392 deletions(-) diff --git a/apps/docs/getting-started/theming.mdx b/apps/docs/getting-started/theming.mdx index b31b849346..8d523282a5 100644 --- a/apps/docs/getting-started/theming.mdx +++ b/apps/docs/getting-started/theming.mdx @@ -15,8 +15,8 @@ Every SuperDoc component reads its colors, spacing, and typography from `--sd-*` .my-theme { --sd-ui-action: #6366f1; /* buttons, active states */ --sd-ui-action-hover: #4f46e5; - --sd-ui-toolbar-background: #f8fafc; /* toolbar surface */ - --sd-ui-surface: #ffffff; /* panels, cards */ + --sd-ui-toolbar-bg: #f8fafc; /* toolbar surface */ + --sd-ui-bg: #ffffff; /* panels, cards */ --sd-ui-text: #1e293b; /* body text */ } ``` @@ -61,7 +61,7 @@ Each preset overrides 30-50 variables across toolbar, dropdown, context menu, co /* Start from the Docs preset, customize from there */ .sd-theme-docs { --sd-ui-action: #your-brand-color; - --sd-ui-comments-panel-card-background: #f0f0f0; + --sd-ui-comments-card-bg: #f0f0f0; } ``` @@ -72,7 +72,7 @@ Variables are organized in three tiers. Higher tiers reference lower tiers, so c ``` Primitives → UI semantic → Component-specific --sd-color-* --sd-ui-* --sd-ui-toolbar-* ---sd-font-size-* --sd-ui-text --sd-ui-comments-panel-* +--sd-font-size-* --sd-ui-text --sd-ui-comments-* --sd-radius-* --sd-ui-action --sd-comments-highlight-* ``` @@ -98,8 +98,8 @@ Shared variables that many components reference. Change one of these and you aff | `--sd-ui-font-family` | `Arial, Helvetica, sans-serif` | Font for all UI elements | | `--sd-ui-text` | `#47484a` | Primary text color | | `--sd-ui-text-muted` | `gray-700` | Secondary/muted text | -| `--sd-ui-surface` | `#ffffff` | Panel and card backgrounds | -| `--sd-ui-surface-hover` | `gray-400` | Hover states | +| `--sd-ui-bg` | `#ffffff` | Panel and card backgrounds | +| `--sd-ui-hover-bg` | `gray-400` | Hover states | | `--sd-ui-border` | `gray-400` | Default borders | | `--sd-ui-action` | `blue-500` | Action/accent color (buttons, links) | | `--sd-ui-action-hover` | `blue-600` | Action hover state | @@ -114,7 +114,7 @@ Fine-grained control over individual components. These override the semantic def 1. **Toolbar** (`--sd-ui-toolbar-*`) — background, button colors, spacing 2. **Dropdowns** (`--sd-ui-dropdown-*`) — surfaces, borders, shadows for all dropdown menus -3. **Comments** (`--sd-ui-comments-panel-*`) — card backgrounds, text colors, input styling +3. **Comments** (`--sd-ui-comments-*`) — card backgrounds, text colors, input styling 4. **Comment highlights** (`--sd-comments-highlight-*`) — in-document highlight colors for internal/external comments 5. **Tracked changes** (`--sd-tracked-changes-*`) — insert/delete/format highlight colors diff --git a/apps/docs/guides/general/custom-themes.mdx b/apps/docs/guides/general/custom-themes.mdx index 50cfb29419..cfafa27584 100644 --- a/apps/docs/guides/general/custom-themes.mdx +++ b/apps/docs/guides/general/custom-themes.mdx @@ -16,12 +16,12 @@ Define a CSS class with your overrides and apply it to any ancestor element. Mos --sd-ui-action: #8b5cf6; --sd-ui-action-hover: #7c3aed; --sd-ui-text: #1e293b; - --sd-ui-surface: #f8fafc; + --sd-ui-bg: #f8fafc; --sd-ui-border: #e2e8f0; /* Then fine-tune specific components */ - --sd-ui-toolbar-background: #f1f5f9; - --sd-ui-comments-panel-card-background: #f0f0ff; + --sd-ui-toolbar-bg: #f1f5f9; + --sd-ui-comments-card-bg: #f0f0ff; } ``` @@ -36,9 +36,9 @@ For a dark theme, you'll need to override the core surface and text variables, p ```css .dark-theme { /* Core surface inversion */ - --sd-ui-surface: #1a1a2e; - --sd-ui-surface-hover: #2a2a3e; - --sd-ui-surface-active: #3a3a4e; + --sd-ui-bg: #1a1a2e; + --sd-ui-hover-bg: #2a2a3e; + --sd-ui-active-bg: #3a3a4e; --sd-ui-text: #e2e8f0; --sd-ui-text-muted: #94a3b8; --sd-ui-border: #334155; @@ -46,25 +46,25 @@ For a dark theme, you'll need to override the core surface and text variables, p --sd-ui-action-hover: #93c5fd; /* Toolbar */ - --sd-ui-toolbar-background: #0f172a; + --sd-ui-toolbar-bg: #0f172a; --sd-ui-toolbar-button-text: #e2e8f0; - --sd-ui-toolbar-button-hover-surface: #1e293b; - --sd-ui-toolbar-button-active-surface: #334155; + --sd-ui-toolbar-button-hover-bg: #1e293b; + --sd-ui-toolbar-button-active-bg: #334155; /* Dropdowns */ - --sd-ui-dropdown-surface: #1e293b; + --sd-ui-dropdown-bg: #1e293b; --sd-ui-dropdown-border: #334155; - --sd-ui-dropdown-surface-hover: #2d3a4e; + --sd-ui-dropdown-hover-bg: #2d3a4e; --sd-ui-dropdown-shadow: 0 8px 24px rgba(0, 0, 0, 0.4); /* Comments */ - --sd-ui-comments-panel-card-background: #1e293b; - --sd-ui-comments-panel-card-active-background: #0f172a; - --sd-ui-comments-panel-input-background: #1e293b; - --sd-ui-comments-panel-input-border: #475569; - --sd-ui-comments-panel-author-text: #e2e8f0; - --sd-ui-comments-panel-body-text: #cbd5e1; - --sd-ui-comments-panel-timestamp-text: #64748b; + --sd-ui-comments-card-bg: #1e293b; + --sd-ui-comments-card-active-bg: #0f172a; + --sd-ui-comments-input-bg: #1e293b; + --sd-ui-comments-input-border: #475569; + --sd-ui-comments-author-text: #e2e8f0; + --sd-ui-comments-body-text: #cbd5e1; + --sd-ui-comments-timestamp-text: #64748b; /* Page */ --sd-layout-page-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); @@ -83,10 +83,10 @@ These are the top-level semantic variables. Most component-specific variables re | `--sd-ui-text` | `#47484a` | Primary text color | | `--sd-ui-text-muted` | `gray-700` | Secondary text | | `--sd-ui-text-disabled` | `gray-500` | Disabled state text | -| `--sd-ui-surface` | `#ffffff` | Default surface/background | -| `--sd-ui-surface-hover` | `gray-400` | Hover background | -| `--sd-ui-surface-active` | `#c8d0d8` | Active/pressed background | -| `--sd-ui-surface-disabled` | `gray-100` | Disabled surface | +| `--sd-ui-bg` | `#ffffff` | Default surface/background | +| `--sd-ui-hover-bg` | `gray-400` | Hover background | +| `--sd-ui-active-bg` | `#c8d0d8` | Active/pressed background | +| `--sd-ui-disabled-bg` | `gray-100` | Disabled surface | | `--sd-ui-border` | `gray-400` | Default border color | | `--sd-ui-action` | `blue-500` | Action/accent color | | `--sd-ui-action-hover` | `blue-600` | Action hover | @@ -102,10 +102,10 @@ These are the top-level semantic variables. Most component-specific variables re | `--sd-ui-toolbar-padding-y` | `4px` | Vertical padding | | `--sd-ui-toolbar-item-gap` | `2px` | Gap between buttons | | `--sd-ui-toolbar-item-padding` | `5px` | Padding inside each button | -| `--sd-ui-toolbar-background` | `transparent` | Toolbar background | +| `--sd-ui-toolbar-bg` | `transparent` | Toolbar background | | `--sd-ui-toolbar-button-text` | `var(--sd-ui-text)` | Button text/icon color | -| `--sd-ui-toolbar-button-hover-surface` | `var(--sd-ui-surface-hover)` | Button hover background | -| `--sd-ui-toolbar-button-active-surface` | `var(--sd-ui-surface-active)` | Button active background | +| `--sd-ui-toolbar-button-hover-bg` | `var(--sd-ui-hover-bg)` | Button hover background | +| `--sd-ui-toolbar-button-active-bg` | `var(--sd-ui-active-bg)` | Button active background | ### Dropdowns @@ -113,13 +113,13 @@ Applies to toolbar dropdowns (font picker, style picker, etc.) and the overflow | Variable | Default | Description | |----------|---------|-------------| -| `--sd-ui-dropdown-surface` | `#ffffff` | Dropdown panel background | +| `--sd-ui-dropdown-bg` | `#ffffff` | Dropdown panel background | | `--sd-ui-dropdown-border` | `#e4e6eb` | Panel border | | `--sd-ui-dropdown-text` | `var(--sd-ui-text)` | Option text | | `--sd-ui-dropdown-hover-text` | `var(--sd-ui-dropdown-text)` | Hovered option text | | `--sd-ui-dropdown-selected-text` | `var(--sd-ui-dropdown-text)` | Selected option text | -| `--sd-ui-dropdown-surface-hover` | `#d8dee5` | Option hover background | -| `--sd-ui-dropdown-surface-active` | `#d8dee5` | Option active/selected background | +| `--sd-ui-dropdown-hover-bg` | `#d8dee5` | Option hover background | +| `--sd-ui-dropdown-active-bg` | `#d8dee5` | Option active/selected background | | `--sd-ui-dropdown-option-radius` | `3px` | Option border radius | | `--sd-ui-dropdown-shadow` | `0 8px 24px rgba(0,0,0,0.16)` | Panel shadow | @@ -129,23 +129,23 @@ The right-click context menu. | Variable | Default | Description | |----------|---------|-------------| -| `--sd-ui-context-menu-surface` | `#ffffff` | Menu background | -| `--sd-ui-context-menu-text` | `#47484a` | Menu text | -| `--sd-ui-context-menu-font-size` | `12px` | Menu text size | -| `--sd-ui-context-menu-radius` | `0` | Menu border radius | -| `--sd-ui-context-menu-border` | `#eee` | Menu border | -| `--sd-ui-context-menu-shadow` | complex | Menu shadow | -| `--sd-ui-context-menu-input-border` | `#ddd` | Search input border | -| `--sd-ui-context-menu-input-focus-border` | `#0096fd` | Search input focus border | -| `--sd-ui-context-menu-item-hover-surface` | `#f5f5f5` | Item hover background | -| `--sd-ui-context-menu-item-active-surface` | `#edf6ff` | Active item background | -| `--sd-ui-context-menu-item-active-text` | `#0096fd` | Active item text | +| `--sd-ui-menu-bg` | `#ffffff` | Menu background | +| `--sd-ui-menu-text` | `#47484a` | Menu text | +| `--sd-ui-menu-font-size` | `12px` | Menu text size | +| `--sd-ui-menu-radius` | `0` | Menu border radius | +| `--sd-ui-menu-border` | `#eee` | Menu border | +| `--sd-ui-menu-shadow` | complex | Menu shadow | +| `--sd-ui-menu-input-border` | `#ddd` | Search input border | +| `--sd-ui-menu-input-focus-border` | `#0096fd` | Search input focus border | +| `--sd-ui-menu-item-hover-bg` | `#f5f5f5` | Item hover background | +| `--sd-ui-menu-item-active-bg` | `#edf6ff` | Active item background | +| `--sd-ui-menu-item-active-text` | `#0096fd` | Active item text | ### Tooltip | Variable | Default | Description | |----------|---------|-------------| -| `--sd-ui-tooltip-surface` | `#262626` | Tooltip background | +| `--sd-ui-tooltip-bg` | `#262626` | Tooltip background | | `--sd-ui-tooltip-text` | `#ffffff` | Tooltip text | | `--sd-ui-tooltip-radius` | `6px` | Tooltip border radius | | `--sd-ui-tooltip-shadow` | `0 3px 12px rgba(0,0,0,0.28)` | Tooltip shadow | @@ -158,62 +158,62 @@ Card styling, typography, and input elements for the comments sidebar. | Variable | Default | Description | |----------|---------|-------------| -| `--sd-ui-comments-panel-card-background` | `#f3f6fd` | Default card background | -| `--sd-ui-comments-panel-card-hover-background` | `#f3f6fd` | Hovered card | -| `--sd-ui-comments-panel-card-active-background` | `#ffffff` | Selected/active card | -| `--sd-ui-comments-panel-card-resolved-background` | `#f0f0f0` | Resolved comment card | -| `--sd-ui-comments-panel-card-active-border` | `gray-300` | Active card border | -| `--sd-ui-comments-panel-card-radius` | `12px` | Card border radius | -| `--sd-ui-comments-panel-card-padding` | `16px` | Card inner padding | -| `--sd-ui-comments-panel-card-shadow` | complex | Card shadow | -| `--sd-ui-comments-panel-transition` | `all 200ms ease` | Card transition | -| `--sd-ui-comments-panel-separator` | `gray-300` | Divider between comments | +| `--sd-ui-comments-card-bg` | `#f3f6fd` | Default card background | +| `--sd-ui-comments-card-hover-bg` | `#f3f6fd` | Hovered card | +| `--sd-ui-comments-card-active-bg` | `#ffffff` | Selected/active card | +| `--sd-ui-comments-card-resolved-bg` | `#f0f0f0` | Resolved comment card | +| `--sd-ui-comments-card-active-border` | `gray-300` | Active card border | +| `--sd-ui-comments-card-radius` | `12px` | Card border radius | +| `--sd-ui-comments-card-padding` | `16px` | Card inner padding | +| `--sd-ui-comments-card-shadow` | complex | Card shadow | +| `--sd-ui-comments-transition` | `all 200ms ease` | Card transition | +| `--sd-ui-comments-separator` | `gray-300` | Divider between comments | **Typography:** | Variable | Default | Description | |----------|---------|-------------| -| `--sd-ui-comments-panel-author-text` | `gray-900` | Author name color | -| `--sd-ui-comments-panel-author-size` | `14px` | Author name size | -| `--sd-ui-comments-panel-author-weight` | `600` | Author name weight | -| `--sd-ui-comments-panel-timestamp-text` | `gray-600` | Timestamp color | -| `--sd-ui-comments-panel-timestamp-size` | `12px` | Timestamp size | -| `--sd-ui-comments-panel-body-text` | `gray-900` | Comment body text | -| `--sd-ui-comments-panel-body-text-size` | `14px` | Comment body size | +| `--sd-ui-comments-author-text` | `gray-900` | Author name color | +| `--sd-ui-comments-author-size` | `14px` | Author name size | +| `--sd-ui-comments-author-weight` | `600` | Author name weight | +| `--sd-ui-comments-timestamp-text` | `gray-600` | Timestamp color | +| `--sd-ui-comments-timestamp-size` | `12px` | Timestamp size | +| `--sd-ui-comments-body-text` | `gray-900` | Comment body text | +| `--sd-ui-comments-body-size` | `14px` | Comment body size | **Inputs:** | Variable | Default | Description | |----------|---------|-------------| -| `--sd-ui-comments-panel-input-background` | `#ffffff` | Comment input background | -| `--sd-ui-comments-panel-input-border` | `gray-400` | Comment input border | +| `--sd-ui-comments-input-bg` | `#ffffff` | Comment input background | +| `--sd-ui-comments-input-border` | `gray-400` | Comment input border | **Status indicators:** | Variable | Default | Description | |----------|---------|-------------| -| `--sd-ui-comments-panel-resolved-badge-text` | `green-500` | "Resolved" badge text | -| `--sd-ui-comments-panel-tc-insert-text` | `green-500` | Tracked change insert text | -| `--sd-ui-comments-panel-tc-delete-text` | `rose-500` | Tracked change delete text | +| `--sd-ui-comments-resolved-text` | `green-500` | "Resolved" badge text | +| `--sd-ui-comments-insert-text` | `green-500` | Tracked change insert text | +| `--sd-ui-comments-delete-text` | `rose-500` | Tracked change delete text | **Dropdown (filter/sort menu):** | Variable | Default | Description | |----------|---------|-------------| -| `--sd-ui-comments-panel-dropdown-background` | `#ffffff` | Dropdown background | -| `--sd-ui-comments-panel-dropdown-border` | `gray-400` | Dropdown border | -| `--sd-ui-comments-panel-dropdown-shadow` | complex | Dropdown shadow | -| `--sd-ui-comments-panel-dropdown-option-text` | body text | Option text | -| `--sd-ui-comments-panel-dropdown-option-hover-text` | option text | Hovered option text | -| `--sd-ui-comments-panel-dropdown-option-hover-background` | `#f3f3f5` | Hovered option background | -| `--sd-ui-comments-panel-dropdown-option-text-size` | `14px` | Option text size | +| `--sd-ui-comments-dropdown-bg` | `#ffffff` | Dropdown background | +| `--sd-ui-comments-dropdown-border` | `gray-400` | Dropdown border | +| `--sd-ui-comments-dropdown-shadow` | complex | Dropdown shadow | +| `--sd-ui-comments-option-text` | body text | Option text | +| `--sd-ui-comments-option-hover-text` | option text | Hovered option text | +| `--sd-ui-comments-option-hover-bg` | `#f3f3f5` | Hovered option background | +| `--sd-ui-comments-option-size` | `14px` | Option text size | **Visibility toggle (internal/external):** | Variable | Default | Description | |----------|---------|-------------| -| `--sd-ui-comments-panel-visibility-internal-background` | `#cde6e6` | Internal badge background | -| `--sd-ui-comments-panel-visibility-external-background` | `#f5cfda` | External badge background | +| `--sd-ui-comments-internal-bg` | `#cde6e6` | Internal badge background | +| `--sd-ui-comments-external-bg` | `#f5cfda` | External badge background | ### Comment highlights (in-document) @@ -254,13 +254,13 @@ Styling for DOCX content controls (SDTs) — form fields, dropdowns, date picker | Variable | Default | Description | |----------|---------|-------------| | `--sd-content-controls-block-border` | `#629be7` | Block control border | -| `--sd-content-controls-block-hover-background` | `#f2f2f2` | Block control hover | +| `--sd-content-controls-block-hover-bg` | `#f2f2f2` | Block control hover | | `--sd-content-controls-inline-border` | `#629be7` | Inline control border | -| `--sd-content-controls-inline-hover-background` | `#f2f2f2` | Inline control hover | +| `--sd-content-controls-inline-hover-bg` | `#f2f2f2` | Inline control hover | | `--sd-content-controls-label-border` | `#629be7` | Label border | -| `--sd-content-controls-label-background` | `#629be7ee` | Label background | +| `--sd-content-controls-label-bg` | `#629be7ee` | Label background | | `--sd-content-controls-label-text` | `#ffffff` | Label text | -| `--sd-content-controls-lock-hover-background` | `rgba(98,155,231,0.08)` | Locked control hover | +| `--sd-content-controls-lock-hover-bg` | `rgba(98,155,231,0.08)` | Locked control hover | ### Layout @@ -268,7 +268,7 @@ Page-level appearance in presentation/layout mode. | Variable | Default | Description | |----------|---------|-------------| -| `--sd-layout-page-background` | `#ffffff` | Page background | +| `--sd-layout-page-bg` | `#ffffff` | Page background | | `--sd-layout-page-shadow` | `0 4px 20px rgba(15,23,42,0.08)` | Page shadow | ### Tools (floating action toolbar) @@ -280,7 +280,7 @@ The floating tools toolbar that appears for AI/insert actions. | `--sd-ui-tools-gap` | `6px` | Gap between tool items | | `--sd-ui-tools-item-size` | `50px` | Tool button size | | `--sd-ui-tools-item-radius` | `12px` | Tool button radius | -| `--sd-ui-tools-item-surface` | `rgba(219,219,219,0.6)` | Tool button background | +| `--sd-ui-tools-item-bg` | `rgba(219,219,219,0.6)` | Tool button background | | `--sd-ui-tools-icon-size` | `20px` | Icon size within buttons | ## Source files diff --git a/apps/docs/guides/migration/css-variables.mdx b/apps/docs/guides/migration/css-variables.mdx index 4fe4522493..6a88d8af8f 100644 --- a/apps/docs/guides/migration/css-variables.mdx +++ b/apps/docs/guides/migration/css-variables.mdx @@ -16,7 +16,7 @@ The old system used flat, inconsistent names (`--sd-comment-bg`, `--sd-track-ins ``` Old: --sd-comment-bg -New: --sd-ui-comments-panel-card-background +New: --sd-ui-comments-card-bg ───── ──────────── ──────────────────── tier component property ``` @@ -29,11 +29,11 @@ The JavaScript config API (`trackChangesConfig`, `commentsConfig`) is unchanged. | Old variable | New variable | |-------------|-------------| -| `--sd-surface-page` | `--sd-ui-surface` | +| `--sd-surface-page` | `--sd-ui-bg` | | `--sd-surface-canvas` | `--sd-color-gray-50` | -| `--sd-surface-card` | `--sd-ui-surface` | -| `--sd-surface-muted` | `--sd-ui-surface-disabled` | -| `--sd-surface-hover` | `--sd-ui-surface-hover` | +| `--sd-surface-card` | `--sd-ui-bg` | +| `--sd-surface-muted` | `--sd-ui-disabled-bg` | +| `--sd-surface-hover` | `--sd-ui-hover-bg` | | `--sd-surface-selected` | `--sd-color-blue-100` | ### Semantic text @@ -50,7 +50,7 @@ The JavaScript config API (`trackChangesConfig`, `commentsConfig`) is unchanged. | Old variable | New variable | |-------------|-------------| | `--sd-border-default` | `--sd-ui-border` | -| `--sd-border-subtle` | `--sd-ui-comments-panel-separator` | +| `--sd-border-subtle` | `--sd-ui-comments-separator` | | `--sd-border-focus` | `--sd-ui-action` | ### Semantic actions @@ -78,34 +78,34 @@ The JavaScript config API (`trackChangesConfig`, `commentsConfig`) is unchanged. | Old variable | New variable | |-------------|-------------| -| `--sd-comment-bg` | `--sd-ui-comments-panel-card-background` | -| `--sd-comment-bg-hover` | `--sd-ui-comments-panel-card-hover-background` | -| `--sd-comment-bg-active` | `--sd-ui-comments-panel-card-active-background` | -| `--sd-comment-bg-resolved` | `--sd-ui-comments-panel-card-resolved-background` | -| `--sd-comment-border-active` | `--sd-ui-comments-panel-card-active-border` | -| `--sd-comment-radius` | `--sd-ui-comments-panel-card-radius` | -| `--sd-comment-padding` | `--sd-ui-comments-panel-card-padding` | -| `--sd-comment-shadow` | `--sd-ui-comments-panel-card-shadow` | -| `--sd-comment-separator` | `--sd-ui-comments-panel-separator` | -| `--sd-comment-transition` | `--sd-ui-comments-panel-transition` | +| `--sd-comment-bg` | `--sd-ui-comments-card-bg` | +| `--sd-comment-bg-hover` | `--sd-ui-comments-card-hover-bg` | +| `--sd-comment-bg-active` | `--sd-ui-comments-card-active-bg` | +| `--sd-comment-bg-resolved` | `--sd-ui-comments-card-resolved-bg` | +| `--sd-comment-border-active` | `--sd-ui-comments-card-active-border` | +| `--sd-comment-radius` | `--sd-ui-comments-card-radius` | +| `--sd-comment-padding` | `--sd-ui-comments-card-padding` | +| `--sd-comment-shadow` | `--sd-ui-comments-card-shadow` | +| `--sd-comment-separator` | `--sd-ui-comments-separator` | +| `--sd-comment-transition` | `--sd-ui-comments-transition` | ### Comment typography | Old variable | New variable | |-------------|-------------| -| `--sd-comment-author-color` | `--sd-ui-comments-panel-author-text` | -| `--sd-comment-author-size` | `--sd-ui-comments-panel-author-size` | -| `--sd-comment-author-weight` | `--sd-ui-comments-panel-author-weight` | -| `--sd-comment-time-color` | `--sd-ui-comments-panel-timestamp-text` | -| `--sd-comment-time-size` | `--sd-ui-comments-panel-timestamp-size` | -| `--sd-comment-body-size` | `--sd-ui-comments-panel-body-text-size` | +| `--sd-comment-author-color` | `--sd-ui-comments-author-text` | +| `--sd-comment-author-size` | `--sd-ui-comments-author-size` | +| `--sd-comment-author-weight` | `--sd-ui-comments-author-weight` | +| `--sd-comment-time-color` | `--sd-ui-comments-timestamp-text` | +| `--sd-comment-time-size` | `--sd-ui-comments-timestamp-size` | +| `--sd-comment-body-size` | `--sd-ui-comments-body-size` | ### Comment tracked change colors | Old variable | New variable | |-------------|-------------| -| `--sd-comment-tc-insert-color` | `--sd-ui-comments-panel-tc-insert-text` | -| `--sd-comment-tc-delete-color` | `--sd-ui-comments-panel-tc-delete-text` | +| `--sd-comment-tc-insert-color` | `--sd-ui-comments-insert-text` | +| `--sd-comment-tc-delete-color` | `--sd-ui-comments-delete-text` | ### Comment highlights @@ -122,8 +122,8 @@ The JavaScript config API (`trackChangesConfig`, `commentsConfig`) is unchanged. | Old variable | New variable | |-------------|-------------| -| `--sd-comment-internal-bg` | `--sd-ui-comments-panel-visibility-internal-background` | -| `--sd-comment-external-bg` | `--sd-ui-comments-panel-visibility-external-background` | +| `--sd-comment-internal-bg` | `--sd-ui-comments-internal-bg` | +| `--sd-comment-external-bg` | `--sd-ui-comments-external-bg` | ### Tracked changes @@ -158,7 +158,7 @@ The `compat.css` file defines aliases that point old names to new ones: ```css /* Loaded automatically — you don't need to import this */ :root { - --sd-comment-bg: var(--sd-ui-comments-panel-card-background); + --sd-comment-bg: var(--sd-ui-comments-card-bg); --sd-track-insert-border: var(--sd-tracked-changes-insert-border); /* ... etc */ } diff --git a/packages/layout-engine/painters/dom/src/renderer.ts b/packages/layout-engine/painters/dom/src/renderer.ts index 6547f8d4c9..4e11df7718 100644 --- a/packages/layout-engine/painters/dom/src/renderer.ts +++ b/packages/layout-engine/painters/dom/src/renderer.ts @@ -2614,7 +2614,7 @@ export class DomPainter { const base = this.options.pageStyles ?? {}; return { ...base, - background: base.background ?? 'var(--sd-layout-page-background, #fff)', + background: base.background ?? 'var(--sd-layout-page-bg, #fff)', boxShadow: 'none', border: 'none', margin: '0', diff --git a/packages/layout-engine/painters/dom/src/styles.ts b/packages/layout-engine/painters/dom/src/styles.ts index 4895573b27..05ad697d52 100644 --- a/packages/layout-engine/painters/dom/src/styles.ts +++ b/packages/layout-engine/painters/dom/src/styles.ts @@ -18,7 +18,7 @@ export type PageStyles = { }; export const DEFAULT_PAGE_STYLES: Required = { - background: 'var(--sd-layout-page-background, #fff)', + background: 'var(--sd-layout-page-bg, #fff)', boxShadow: 'var(--sd-layout-page-shadow, 0 4px 20px rgba(15, 23, 42, 0.08))', border: '1px solid rgba(15, 23, 42, 0.08)', margin: '0 auto', @@ -380,14 +380,14 @@ const SDT_CONTAINER_STYLES = ` } .superdoc-structured-content-block:not(.ProseMirror-selectednode):hover { - background-color: var(--sd-content-controls-block-hover-background, #f2f2f2); + background-color: var(--sd-content-controls-block-hover-bg, #f2f2f2); border-color: transparent; } /* Group hover (JavaScript-coordinated) */ .superdoc-structured-content-block.sdt-group-hover:not(.ProseMirror-selectednode), .superdoc-structured-content-block.sdt-hover:not(.ProseMirror-selectednode) { - background-color: var(--sd-content-controls-block-hover-background, #f2f2f2); + background-color: var(--sd-content-controls-block-hover-bg, #f2f2f2); border-color: transparent; } @@ -412,7 +412,7 @@ const SDT_CONTAINER_STYLES = ` border: 1px solid var(--sd-content-controls-label-border, #629be7); border-bottom: none; border-radius: 6px 6px 0 0; - background-color: var(--sd-content-controls-label-background, #629be7ee); + background-color: var(--sd-content-controls-label-bg, #629be7ee); color: var(--sd-content-controls-label-text, #ffffff); box-sizing: border-box; z-index: 10; @@ -479,7 +479,7 @@ const SDT_CONTAINER_STYLES = ` /* Hover effect for inline structured content */ .superdoc-structured-content-inline:not(.ProseMirror-selectednode):hover { - background-color: var(--sd-content-controls-inline-hover-background, #f2f2f2); + background-color: var(--sd-content-controls-inline-hover-bg, #f2f2f2); border-color: transparent; } @@ -497,7 +497,7 @@ const SDT_CONTAINER_STYLES = ` font-size: 11px; padding: 0 4px; border: 1px solid var(--sd-content-controls-label-border, #629be7); - background-color: var(--sd-content-controls-label-background, #629be7ee); + background-color: var(--sd-content-controls-label-bg, #629be7ee); color: var(--sd-content-controls-label-text, #ffffff); border-radius: 4px; white-space: nowrap; @@ -523,7 +523,7 @@ const SDT_CONTAINER_STYLES = ` * Hover is suppressed when the node is selected (SD-1584). */ .superdoc-structured-content-block[data-lock-mode].sdt-hover:not(.ProseMirror-selectednode), .superdoc-structured-content-inline[data-lock-mode]:hover:not(.ProseMirror-selectednode) { - background-color: var(--sd-content-controls-lock-hover-background, rgba(98, 155, 231, 0.08)); + background-color: var(--sd-content-controls-lock-hover-bg, rgba(98, 155, 231, 0.08)); z-index: 9999999; } diff --git a/packages/super-editor/src/assets/styles/extensions/structured-content.css b/packages/super-editor/src/assets/styles/extensions/structured-content.css index 61376dc519..387e70a71b 100644 --- a/packages/super-editor/src/assets/styles/extensions/structured-content.css +++ b/packages/super-editor/src/assets/styles/extensions/structured-content.css @@ -14,11 +14,11 @@ /* Hover (not selected): light grey background, no handle */ .super-editor .sd-structured-content:not(.ProseMirror-selectednode):hover { - background-color: var(--sd-content-controls-inline-hover-background, #f2f2f2); + background-color: var(--sd-content-controls-inline-hover-bg, #f2f2f2); } .super-editor .sd-structured-content-block:not(.ProseMirror-selectednode):hover { - background-color: var(--sd-content-controls-block-hover-background, #f2f2f2); + background-color: var(--sd-content-controls-block-hover-bg, #f2f2f2); } /* Selected: border + handle visible */ @@ -45,7 +45,7 @@ border: 1px solid var(--sd-content-controls-label-border, #629be7); border-bottom: none; border-radius: 6px 6px 0 0; - background-color: var(--sd-content-controls-label-background, #629be7ee); + background-color: var(--sd-content-controls-label-bg, #629be7ee); color: var(--sd-content-controls-label-text, #ffffff); box-sizing: border-box; z-index: 10; diff --git a/packages/super-editor/src/components/context-menu/ContextMenu.vue b/packages/super-editor/src/components/context-menu/ContextMenu.vue index 80e489ba8a..53dd19da12 100644 --- a/packages/super-editor/src/components/context-menu/ContextMenu.vue +++ b/packages/super-editor/src/components/context-menu/ContextMenu.vue @@ -527,13 +527,13 @@ onBeforeUnmount(() => { position: fixed; z-index: 50; width: 180px; - color: var(--sd-ui-context-menu-text, #47484a); - background: var(--sd-ui-context-menu-surface, #ffffff); - border-radius: var(--sd-ui-context-menu-radius, 0); + color: var(--sd-ui-menu-text, #47484a); + background: var(--sd-ui-menu-bg, #ffffff); + border-radius: var(--sd-ui-menu-radius, 0); overflow: hidden; - box-shadow: var(--sd-ui-context-menu-shadow, 0 0 0 1px rgba(0, 0, 0, 0.05), 0px 10px 20px rgba(0, 0, 0, 0.1)); + box-shadow: var(--sd-ui-menu-shadow, 0 0 0 1px rgba(0, 0, 0, 0.05), 0px 10px 20px rgba(0, 0, 0, 0.1)); margin-top: 0.5rem; - font-size: var(--sd-ui-context-menu-font-size, 12px); + font-size: var(--sd-ui-menu-font-size, 12px); } /* Hide the input but keep it functional */ @@ -555,18 +555,18 @@ onBeforeUnmount(() => { .context-menu-search { padding: 0.5rem; - border-bottom: 1px solid var(--sd-ui-context-menu-border, #eee); + border-bottom: 1px solid var(--sd-ui-menu-border, #eee); } .context-menu-search input { width: 100%; padding: 0.25rem 0.5rem; - border: 1px solid var(--sd-ui-context-menu-input-border, #ddd); + border: 1px solid var(--sd-ui-menu-input-border, #ddd); outline: none; } .context-menu-search input:focus { - border-color: var(--sd-ui-context-menu-input-focus-border, #0096fd); + border-color: var(--sd-ui-menu-input-focus-border, #0096fd); } /* Remove unused group styles */ @@ -584,13 +584,13 @@ onBeforeUnmount(() => { } .context-menu-item:hover { - background: var(--sd-ui-context-menu-item-hover-surface, #f5f5f5); + background: var(--sd-ui-menu-item-hover-bg, #f5f5f5); } .context-menu-item.is-selected { - background: var(--sd-ui-context-menu-item-active-surface, #edf6ff); - color: var(--sd-ui-context-menu-item-active-text, #0096fd); - fill: var(--sd-ui-context-menu-item-active-text, #0096fd); + background: var(--sd-ui-menu-item-active-bg, #edf6ff); + color: var(--sd-ui-menu-item-active-text, #0096fd); + fill: var(--sd-ui-menu-item-active-text, #0096fd); } .context-menu-item-icon { @@ -617,15 +617,15 @@ onBeforeUnmount(() => { } .popover { - background: var(--sd-ui-context-menu-surface, #ffffff); - border-radius: var(--sd-ui-context-menu-radius, 0); - box-shadow: var(--sd-ui-context-menu-shadow, 0 0 0 1px rgba(0, 0, 0, 0.05), 0px 10px 20px rgba(0, 0, 0, 0.1)); + background: var(--sd-ui-menu-bg, #ffffff); + border-radius: var(--sd-ui-menu-radius, 0); + box-shadow: var(--sd-ui-menu-shadow, 0 0 0 1px rgba(0, 0, 0, 0.05), 0px 10px 20px rgba(0, 0, 0, 0.1)); z-index: 100; } .context-menu-divider { height: 1px; - background: var(--sd-ui-context-menu-border, #eee); + background: var(--sd-ui-menu-border, #eee); margin: 4px 0; } diff --git a/packages/super-editor/src/components/toolbar/AlignmentButtons.vue b/packages/super-editor/src/components/toolbar/AlignmentButtons.vue index 752a058979..7f90757fa5 100644 --- a/packages/super-editor/src/components/toolbar/AlignmentButtons.vue +++ b/packages/super-editor/src/components/toolbar/AlignmentButtons.vue @@ -116,7 +116,7 @@ onMounted(() => { box-sizing: border-box; &:hover { - background-color: var(--sd-ui-dropdown-surface-hover, #d8dee5); + background-color: var(--sd-ui-dropdown-hover-bg, #d8dee5); color: var(--sd-ui-dropdown-hover-text, #47484a); } diff --git a/packages/super-editor/src/components/toolbar/DocumentMode.vue b/packages/super-editor/src/components/toolbar/DocumentMode.vue index c2022de4e5..aa2dbcaca6 100644 --- a/packages/super-editor/src/components/toolbar/DocumentMode.vue +++ b/packages/super-editor/src/components/toolbar/DocumentMode.vue @@ -103,14 +103,14 @@ onMounted(() => { .option-item { display: flex; flex-direction: row; - background-color: var(--sd-ui-dropdown-surface, #ffffff); + background-color: var(--sd-ui-dropdown-bg, #ffffff); padding: 10px; border-radius: var(--sd-ui-dropdown-option-radius, 3px); cursor: pointer; box-sizing: border-box; &:hover { - background-color: var(--sd-ui-dropdown-surface-hover, #d8dee5); + background-color: var(--sd-ui-dropdown-hover-bg, #d8dee5); } } diff --git a/packages/super-editor/src/components/toolbar/IconGrid.vue b/packages/super-editor/src/components/toolbar/IconGrid.vue index 221d7e3faf..82dcde6846 100644 --- a/packages/super-editor/src/components/toolbar/IconGrid.vue +++ b/packages/super-editor/src/components/toolbar/IconGrid.vue @@ -70,7 +70,7 @@ const handleSelect = (option) => { .option-grid-ctn { display: flex; flex-direction: column; - background-color: var(--sd-ui-dropdown-surface, #ffffff); + background-color: var(--sd-ui-dropdown-bg, #ffffff); z-index: 3; box-sizing: border-box; &__subtitle { diff --git a/packages/super-editor/src/components/toolbar/IconGridRow.vue b/packages/super-editor/src/components/toolbar/IconGridRow.vue index 9e2aafd517..60dc7852d6 100644 --- a/packages/super-editor/src/components/toolbar/IconGridRow.vue +++ b/packages/super-editor/src/components/toolbar/IconGridRow.vue @@ -147,7 +147,7 @@ const handleKeyDown = (event, rowIndex, optionIndex, option) => { } .option:hover { - background-color: var(--sd-ui-dropdown-surface-hover, #d8dee5); + background-color: var(--sd-ui-dropdown-hover-bg, #d8dee5); } .option__icon { diff --git a/packages/super-editor/src/components/toolbar/LinkInput.vue b/packages/super-editor/src/components/toolbar/LinkInput.vue index 800855ab58..a46a88a773 100644 --- a/packages/super-editor/src/components/toolbar/LinkInput.vue +++ b/packages/super-editor/src/components/toolbar/LinkInput.vue @@ -289,7 +289,7 @@ const handleRemove = () => { flex-direction: column; padding: 1em; border-radius: var(--sd-ui-radius, 6px); - background-color: var(--sd-ui-dropdown-surface, #ffffff); + background-color: var(--sd-ui-dropdown-bg, #ffffff); box-sizing: border-box; :deep(svg) { @@ -323,7 +323,7 @@ const handleRemove = () => { } &[readonly] { - background-color: var(--sd-ui-surface-disabled, #f5f5f5); + background-color: var(--sd-ui-disabled-bg, #f5f5f5); cursor: default; color: var(--sd-ui-text-disabled, #888); border-color: var(--sd-ui-border, #e0e0e0); @@ -376,7 +376,7 @@ const handleRemove = () => { .open-link-icon:hover { color: var(--sd-ui-action, #1355ff); - background-color: var(--sd-ui-surface, #ffffff); + background-color: var(--sd-ui-bg, #ffffff); border: 1px solid var(--sd-ui-border, #dbdbdb); } @@ -441,7 +441,7 @@ const handleRemove = () => { padding: 10px 16px; border-radius: var(--sd-ui-radius, 6px); outline: none; - background-color: var(--sd-ui-surface, #ffffff); + background-color: var(--sd-ui-bg, #ffffff); color: var(--sd-ui-text, #47484a); font-weight: 400; font-size: var(--sd-ui-font-size-300, 13px); @@ -452,7 +452,7 @@ const handleRemove = () => { } .remove-btn:hover { - background-color: var(--sd-ui-surface-hover, #dbdbdb); + background-color: var(--sd-ui-hover-bg, #dbdbdb); } .submit-btn { diff --git a/packages/super-editor/src/components/toolbar/LinkedStyle.vue b/packages/super-editor/src/components/toolbar/LinkedStyle.vue index b316dff12b..8487d98906 100644 --- a/packages/super-editor/src/components/toolbar/LinkedStyle.vue +++ b/packages/super-editor/src/components/toolbar/LinkedStyle.vue @@ -89,7 +89,7 @@ onMounted(() => { } .style-name:hover { - background-color: var(--sd-ui-dropdown-surface-hover, #d8dee5); + background-color: var(--sd-ui-dropdown-hover-bg, #d8dee5); color: var(--sd-ui-dropdown-hover-text, #47484a); } diff --git a/packages/super-editor/src/components/toolbar/OverflowMenu.vue b/packages/super-editor/src/components/toolbar/OverflowMenu.vue index caa3a2d5eb..912871a6bc 100644 --- a/packages/super-editor/src/components/toolbar/OverflowMenu.vue +++ b/packages/super-editor/src/components/toolbar/OverflowMenu.vue @@ -79,7 +79,7 @@ onBeforeUnmount(() => { top: calc(100% + 3px); right: 0; padding: 4px 8px; - background-color: var(--sd-ui-dropdown-surface, #fff); + background-color: var(--sd-ui-dropdown-bg, #fff); border-radius: var(--sd-ui-radius, 6px); z-index: 100; box-shadow: var(--sd-ui-dropdown-shadow, 0 8px 24px rgba(0, 0, 0, 0.16)); diff --git a/packages/super-editor/src/components/toolbar/SdTooltip.vue b/packages/super-editor/src/components/toolbar/SdTooltip.vue index db59b4fc5d..9de7a96cd2 100644 --- a/packages/super-editor/src/components/toolbar/SdTooltip.vue +++ b/packages/super-editor/src/components/toolbar/SdTooltip.vue @@ -210,7 +210,7 @@ onBeforeUnmount(() => { } .sd-tooltip-content { - background-color: var(--sd-ui-tooltip-surface, #262626); + background-color: var(--sd-ui-tooltip-bg, #262626); color: var(--sd-ui-tooltip-text, #fff); font-size: var(--sd-ui-font-size-400, 14px); line-height: 1.3; @@ -227,7 +227,7 @@ onBeforeUnmount(() => { bottom: -5px; width: 10px; height: 10px; - background-color: var(--sd-ui-tooltip-surface, #262626); + background-color: var(--sd-ui-tooltip-bg, #262626); transform: translateX(-50%) rotate(45deg); } diff --git a/packages/super-editor/src/components/toolbar/TableActions.vue b/packages/super-editor/src/components/toolbar/TableActions.vue index c7620418e9..92c1ef73a3 100644 --- a/packages/super-editor/src/components/toolbar/TableActions.vue +++ b/packages/super-editor/src/components/toolbar/TableActions.vue @@ -52,7 +52,7 @@ const handleClick = (item) => { .toolbar-table-actions__item { display: flex; gap: 5px; - background-color: var(--sd-ui-dropdown-surface, #ffffff); + background-color: var(--sd-ui-dropdown-bg, #ffffff); padding: 4px 10px; border-radius: var(--sd-ui-dropdown-option-radius, 3px); cursor: pointer; @@ -61,7 +61,7 @@ const handleClick = (item) => { } .toolbar-table-actions__item:hover { - background-color: var(--sd-ui-dropdown-surface-hover, #d8dee5); + background-color: var(--sd-ui-dropdown-hover-bg, #d8dee5); } .toolbar-table-actions__item--border:after { diff --git a/packages/super-editor/src/components/toolbar/TableGrid.vue b/packages/super-editor/src/components/toolbar/TableGrid.vue index fb92815bb3..5aa1b591be 100644 --- a/packages/super-editor/src/components/toolbar/TableGrid.vue +++ b/packages/super-editor/src/components/toolbar/TableGrid.vue @@ -163,7 +163,7 @@ onMounted(() => { } .toolbar-table-grid__item.selected { - background-color: var(--sd-ui-dropdown-surface-hover, #d8dee5); + background-color: var(--sd-ui-dropdown-hover-bg, #d8dee5); } &.high-contrast { diff --git a/packages/super-editor/src/components/toolbar/Toolbar.vue b/packages/super-editor/src/components/toolbar/Toolbar.vue index 21fa4d4141..835969f7e6 100644 --- a/packages/super-editor/src/components/toolbar/Toolbar.vue +++ b/packages/super-editor/src/components/toolbar/Toolbar.vue @@ -118,7 +118,7 @@ const restoreSelection = () => { display: flex; width: 100%; justify-content: space-between; - background: var(--sd-ui-toolbar-background, transparent); + background: var(--sd-ui-toolbar-bg, transparent); padding: var(--sd-ui-toolbar-padding-y, 4px) var(--sd-ui-toolbar-padding-x, 16px); box-sizing: border-box; font-family: var(--sd-ui-font-family, Arial, Helvetica, sans-serif); diff --git a/packages/super-editor/src/components/toolbar/ToolbarButton.vue b/packages/super-editor/src/components/toolbar/ToolbarButton.vue index 7dadb82476..6747576dcb 100644 --- a/packages/super-editor/src/components/toolbar/ToolbarButton.vue +++ b/packages/super-editor/src/components/toolbar/ToolbarButton.vue @@ -187,7 +187,7 @@ const caretIcon = computed(() => { } .toolbar-button:hover { - background-color: var(--sd-ui-toolbar-button-hover-surface, var(--sd-ui-surface-hover, #dbdbdb)); + background-color: var(--sd-ui-toolbar-button-hover-bg, var(--sd-ui-hover-bg, #dbdbdb)); .toolbar-icon { &.high-contrast { @@ -203,7 +203,7 @@ const caretIcon = computed(() => { .toolbar-button:active, .active { - background-color: var(--sd-ui-toolbar-button-active-surface, var(--sd-ui-surface-active, #c8d0d8)); + background-color: var(--sd-ui-toolbar-button-active-bg, var(--sd-ui-active-bg, #c8d0d8)); } .button-label { diff --git a/packages/super-editor/src/components/toolbar/ToolbarDropdown.vue b/packages/super-editor/src/components/toolbar/ToolbarDropdown.vue index fb69f2a92b..65cb02b409 100644 --- a/packages/super-editor/src/components/toolbar/ToolbarDropdown.vue +++ b/packages/super-editor/src/components/toolbar/ToolbarDropdown.vue @@ -374,7 +374,7 @@ onBeforeUnmount(() => { min-width: 80px; padding: 4px; border-radius: var(--sd-ui-radius, 6px); - background: var(--sd-ui-dropdown-surface, #fff); + background: var(--sd-ui-dropdown-bg, #fff); border: 1px solid var(--sd-ui-dropdown-border, #e4e6eb); box-shadow: var(--sd-ui-dropdown-shadow, 0 8px 24px rgba(0, 0, 0, 0.16)); box-sizing: border-box; @@ -414,12 +414,12 @@ onBeforeUnmount(() => { } .toolbar-dropdown-option:hover { - background: var(--sd-ui-dropdown-surface-hover, #d8dee5); + background: var(--sd-ui-dropdown-hover-bg, #d8dee5); color: var(--sd-ui-dropdown-hover-text, #47484a); } .toolbar-dropdown-option.selected { - background: var(--sd-ui-dropdown-surface-active, #d8dee5); + background: var(--sd-ui-dropdown-active-bg, #d8dee5); color: var(--sd-ui-dropdown-selected-text, #47484a); } diff --git a/packages/superdoc/src/SuperDoc.vue b/packages/superdoc/src/SuperDoc.vue index f70d32f393..faa3bb8e33 100644 --- a/packages/superdoc/src/SuperDoc.vue +++ b/packages/superdoc/src/SuperDoc.vue @@ -1557,7 +1557,7 @@ const getPDFViewer = () => { justify-content: center; width: var(--sd-ui-tools-item-size, 50px); height: var(--sd-ui-tools-item-size, 50px); - background-color: var(--sd-ui-tools-item-surface, rgba(219, 219, 219, 0.6)); + background-color: var(--sd-ui-tools-item-bg, rgba(219, 219, 219, 0.6)); border-radius: var(--sd-ui-tools-item-radius, 12px); cursor: pointer; position: relative; diff --git a/packages/superdoc/src/assets/styles/helpers/compat.css b/packages/superdoc/src/assets/styles/helpers/compat.css index 0e2ba4a72d..17420ddca6 100644 --- a/packages/superdoc/src/assets/styles/helpers/compat.css +++ b/packages/superdoc/src/assets/styles/helpers/compat.css @@ -10,11 +10,11 @@ :root { /* ─── Semantic surfaces → UI tokens ─── */ - --sd-surface-page: var(--sd-ui-surface); + --sd-surface-page: var(--sd-ui-bg); --sd-surface-canvas: var(--sd-color-gray-50); - --sd-surface-card: var(--sd-ui-surface); - --sd-surface-muted: var(--sd-ui-surface-disabled); - --sd-surface-hover: var(--sd-ui-surface-hover); + --sd-surface-card: var(--sd-ui-bg); + --sd-surface-muted: var(--sd-ui-disabled-bg); + --sd-surface-hover: var(--sd-ui-hover-bg); --sd-surface-selected: var(--sd-color-blue-100); /* ─── Semantic text → UI tokens ─── */ @@ -25,7 +25,7 @@ /* ─── Semantic borders → UI tokens ─── */ --sd-border-default: var(--sd-ui-border); - --sd-border-subtle: var(--sd-ui-comments-panel-separator); + --sd-border-subtle: var(--sd-ui-comments-separator); --sd-border-focus: var(--sd-ui-action); /* ─── Semantic actions → UI tokens ─── */ @@ -41,36 +41,36 @@ --sd-radius-lg: var(--sd-radius-300); /* ─── Comment dialog ─── */ - --sd-comment-bg: var(--sd-ui-comments-panel-card-background); - --sd-comment-bg-hover: var(--sd-ui-comments-panel-card-hover-background); - --sd-comment-bg-active: var(--sd-ui-comments-panel-card-active-background); - --sd-comment-bg-resolved: var(--sd-ui-comments-panel-card-resolved-background); - --sd-comment-border-active: var(--sd-ui-comments-panel-card-active-border); - --sd-comment-radius: var(--sd-ui-comments-panel-card-radius); - --sd-comment-padding: var(--sd-ui-comments-panel-card-padding); - --sd-comment-shadow: var(--sd-ui-comments-panel-card-shadow); - --sd-comment-separator: var(--sd-ui-comments-panel-separator); - --sd-comment-transition: var(--sd-ui-comments-panel-transition); + --sd-comment-bg: var(--sd-ui-comments-card-bg); + --sd-comment-bg-hover: var(--sd-ui-comments-card-hover-bg); + --sd-comment-bg-active: var(--sd-ui-comments-card-active-bg); + --sd-comment-bg-resolved: var(--sd-ui-comments-card-resolved-bg); + --sd-comment-border-active: var(--sd-ui-comments-card-active-border); + --sd-comment-radius: var(--sd-ui-comments-card-radius); + --sd-comment-padding: var(--sd-ui-comments-card-padding); + --sd-comment-shadow: var(--sd-ui-comments-card-shadow); + --sd-comment-separator: var(--sd-ui-comments-separator); + --sd-comment-transition: var(--sd-ui-comments-transition); /* Comment: author & timestamp */ - --sd-comment-author-color: var(--sd-ui-comments-panel-author-text); - --sd-comment-author-size: var(--sd-ui-comments-panel-author-size); - --sd-comment-author-weight: var(--sd-ui-comments-panel-author-weight); - --sd-comment-time-color: var(--sd-ui-comments-panel-timestamp-text); - --sd-comment-time-size: var(--sd-ui-comments-panel-timestamp-size); - --sd-comment-body-size: var(--sd-ui-comments-panel-body-text-size); + --sd-comment-author-color: var(--sd-ui-comments-author-text); + --sd-comment-author-size: var(--sd-ui-comments-author-size); + --sd-comment-author-weight: var(--sd-ui-comments-author-weight); + --sd-comment-time-color: var(--sd-ui-comments-timestamp-text); + --sd-comment-time-size: var(--sd-ui-comments-timestamp-size); + --sd-comment-body-size: var(--sd-ui-comments-body-size); /* Comment: tracked change text colors */ - --sd-comment-tc-insert-color: var(--sd-ui-comments-panel-tc-insert-text); - --sd-comment-tc-delete-color: var(--sd-ui-comments-panel-tc-delete-text); + --sd-comment-tc-insert-color: var(--sd-ui-comments-insert-text); + --sd-comment-tc-delete-color: var(--sd-ui-comments-delete-text); /* Comment: document highlights */ --sd-comment-highlight-internal: var(--sd-comments-highlight-internal); --sd-comment-highlight-external: var(--sd-comments-highlight-external); /* Comment: internal/external toggle */ - --sd-comment-internal-bg: var(--sd-ui-comments-panel-visibility-internal-background); - --sd-comment-external-bg: var(--sd-ui-comments-panel-visibility-external-background); + --sd-comment-internal-bg: var(--sd-ui-comments-internal-bg); + --sd-comment-external-bg: var(--sd-ui-comments-external-bg); /* ─── Tracked changes (old short names) ─── */ --sd-track-insert-border: var(--sd-tracked-changes-insert-border); diff --git a/packages/superdoc/src/assets/styles/helpers/themes.css b/packages/superdoc/src/assets/styles/helpers/themes.css index 82124fa25b..4b3a29ba82 100644 --- a/packages/superdoc/src/assets/styles/helpers/themes.css +++ b/packages/superdoc/src/assets/styles/helpers/themes.css @@ -3,39 +3,39 @@ /* UI base */ --sd-ui-font-family: Arial, sans-serif; --sd-ui-text: #202124; - --sd-ui-surface: #f1f3f4; + --sd-ui-bg: #f1f3f4; --sd-ui-border: #dadce0; --sd-ui-action: #1a73e8; --sd-ui-action-hover: #185abc; /* UI: toolbar/dropdown/context */ - --sd-ui-toolbar-background: #f0f4f9; + --sd-ui-toolbar-bg: #f0f4f9; --sd-ui-toolbar-button-text: #3c4043; - --sd-ui-toolbar-button-hover-surface: #e8eaed; - --sd-ui-toolbar-button-active-surface: #d2e3fc; + --sd-ui-toolbar-button-hover-bg: #e8eaed; + --sd-ui-toolbar-button-active-bg: #d2e3fc; --sd-ui-toolbar-item-padding: 4px; --sd-ui-dropdown-border: #dadce0; - --sd-ui-dropdown-surface: #ffffff; - --sd-ui-dropdown-surface-hover: #e8eaed; - --sd-ui-dropdown-surface-active: #d2e3fc; + --sd-ui-dropdown-bg: #ffffff; + --sd-ui-dropdown-hover-bg: #e8eaed; + --sd-ui-dropdown-active-bg: #d2e3fc; --sd-ui-dropdown-shadow: 0 1px 2px rgba(60, 64, 67, 0.3), 0 1px 3px 1px rgba(60, 64, 67, 0.15); - --sd-ui-context-menu-surface: #ffffff; - --sd-ui-context-menu-border: #dadce0; - --sd-ui-context-menu-shadow: 0 1px 2px rgba(60, 64, 67, 0.25), 0 3px 8px rgba(60, 64, 67, 0.18); - --sd-ui-context-menu-item-hover-surface: #e8eaed; - --sd-ui-context-menu-item-active-surface: #d2e3fc; + --sd-ui-menu-bg: #ffffff; + --sd-ui-menu-border: #dadce0; + --sd-ui-menu-shadow: 0 1px 2px rgba(60, 64, 67, 0.25), 0 3px 8px rgba(60, 64, 67, 0.18); + --sd-ui-menu-item-hover-bg: #e8eaed; + --sd-ui-menu-item-active-bg: #d2e3fc; /* Comments */ - --sd-ui-comments-panel-card-background: #d7dde8; - --sd-ui-comments-panel-card-active-background: #ffffff; - --sd-ui-comments-panel-card-active-border: #c7cdd6; - --sd-ui-comments-panel-card-shadow: 0 1px 2px rgba(60, 64, 67, 0.2), 0 2px 6px rgba(60, 64, 67, 0.18); - --sd-ui-comments-panel-separator: #c7cdd6; - --sd-ui-comments-panel-timestamp-text: #5f6368; - --sd-ui-comments-panel-dropdown-option-hover-background: #e8eaed; - --sd-ui-comments-panel-input-border: #80868b; + --sd-ui-comments-card-bg: #d7dde8; + --sd-ui-comments-card-active-bg: #ffffff; + --sd-ui-comments-card-active-border: #c7cdd6; + --sd-ui-comments-card-shadow: 0 1px 2px rgba(60, 64, 67, 0.2), 0 2px 6px rgba(60, 64, 67, 0.18); + --sd-ui-comments-separator: #c7cdd6; + --sd-ui-comments-timestamp-text: #5f6368; + --sd-ui-comments-option-hover-bg: #e8eaed; + --sd-ui-comments-input-border: #80868b; --sd-comments-highlight-external-base: #f9ab00; --sd-comments-highlight-external: #f9ab0033; @@ -49,7 +49,7 @@ --sd-comments-highlight-internal-nested-border: #18803888; /* Layout */ - --sd-layout-page-background: #ffffff; + --sd-layout-page-bg: #ffffff; --sd-layout-page-shadow: 0 1px 2px rgba(60, 64, 67, 0.2), 0 2px 6px rgba(60, 64, 67, 0.12); } @@ -58,39 +58,39 @@ /* UI base */ --sd-ui-font-family: 'Segoe UI', Arial, sans-serif; --sd-ui-text: #323130; - --sd-ui-surface: #f3f2f1; + --sd-ui-bg: #f3f2f1; --sd-ui-border: #d2d0ce; --sd-ui-action: #185abd; --sd-ui-action-hover: #134a9e; /* UI: toolbar/dropdown/context */ - --sd-ui-toolbar-background: #f3f2f1; + --sd-ui-toolbar-bg: #f3f2f1; --sd-ui-toolbar-button-text: #201f1e; - --sd-ui-toolbar-button-hover-surface: #e8e6e4; - --sd-ui-toolbar-button-active-surface: #d2d0ce; + --sd-ui-toolbar-button-hover-bg: #e8e6e4; + --sd-ui-toolbar-button-active-bg: #d2d0ce; --sd-ui-toolbar-item-padding: 4px; --sd-ui-dropdown-border: #d2d0ce; - --sd-ui-dropdown-surface: #ffffff; - --sd-ui-dropdown-surface-hover: #e8e6e4; - --sd-ui-dropdown-surface-active: #d2d0ce; + --sd-ui-dropdown-bg: #ffffff; + --sd-ui-dropdown-hover-bg: #e8e6e4; + --sd-ui-dropdown-active-bg: #d2d0ce; --sd-ui-dropdown-shadow: 0 1px 2px rgba(0, 0, 0, 0.14), 0 4px 8px rgba(0, 0, 0, 0.08); - --sd-ui-context-menu-surface: #ffffff; - --sd-ui-context-menu-border: #d2d0ce; - --sd-ui-context-menu-shadow: 0 2px 6px rgba(0, 0, 0, 0.14); - --sd-ui-context-menu-item-hover-surface: #f3f2f1; - --sd-ui-context-menu-item-active-surface: #edebe9; + --sd-ui-menu-bg: #ffffff; + --sd-ui-menu-border: #d2d0ce; + --sd-ui-menu-shadow: 0 2px 6px rgba(0, 0, 0, 0.14); + --sd-ui-menu-item-hover-bg: #f3f2f1; + --sd-ui-menu-item-active-bg: #edebe9; /* Comments */ - --sd-ui-comments-panel-card-background: #ebebeb; - --sd-ui-comments-panel-card-active-background: #ffffff; - --sd-ui-comments-panel-card-active-border: #d2d0ce; - --sd-ui-comments-panel-card-shadow: 0 2px 4px rgba(0, 0, 0, 0.16), 0 10px 20px rgba(0, 0, 0, 0.1); - --sd-ui-comments-panel-separator: #c8c6c4; - --sd-ui-comments-panel-timestamp-text: #605e5c; - --sd-ui-comments-panel-dropdown-option-hover-background: #f3f2f1; - --sd-ui-comments-panel-input-border: #8a8886; + --sd-ui-comments-card-bg: #ebebeb; + --sd-ui-comments-card-active-bg: #ffffff; + --sd-ui-comments-card-active-border: #d2d0ce; + --sd-ui-comments-card-shadow: 0 2px 4px rgba(0, 0, 0, 0.16), 0 10px 20px rgba(0, 0, 0, 0.1); + --sd-ui-comments-separator: #c8c6c4; + --sd-ui-comments-timestamp-text: #605e5c; + --sd-ui-comments-option-hover-bg: #f3f2f1; + --sd-ui-comments-input-border: #8a8886; --sd-comments-highlight-external-base: #8a8886; --sd-comments-highlight-external: #8a888633; @@ -104,7 +104,7 @@ --sd-comments-highlight-internal-nested-border: #0f6cbd88; /* Layout */ - --sd-layout-page-background: #ffffff; + --sd-layout-page-bg: #ffffff; --sd-layout-page-shadow: 0 1px 2px rgba(0, 0, 0, 0.16), 0 6px 12px rgba(0, 0, 0, 0.08); } @@ -115,42 +115,42 @@ --sd-ui-text: #0f172a; --sd-ui-text-muted: #334155; --sd-ui-text-disabled: #64748b; - --sd-ui-surface: #e8eff5; - --sd-ui-surface-hover: #d3e2ef; - --sd-ui-surface-active: #bfd4e6; + --sd-ui-bg: #e8eff5; + --sd-ui-hover-bg: #d3e2ef; + --sd-ui-active-bg: #bfd4e6; --sd-ui-border: #9fb4c7; --sd-ui-action: #0f766e; --sd-ui-action-hover: #115e59; --sd-ui-shadow: 0 2px 8px rgba(15, 23, 42, 0.18); /* Toolbar / dropdown / context */ - --sd-ui-toolbar-background: #dbe6f0; + --sd-ui-toolbar-bg: #dbe6f0; --sd-ui-toolbar-button-text: #0f172a; - --sd-ui-toolbar-button-hover-surface: #c9dced; - --sd-ui-toolbar-button-active-surface: #b7cee3; - --sd-ui-dropdown-surface: #ffffff; + --sd-ui-toolbar-button-hover-bg: #c9dced; + --sd-ui-toolbar-button-active-bg: #b7cee3; + --sd-ui-dropdown-bg: #ffffff; --sd-ui-dropdown-border: #8ea5b8; - --sd-ui-dropdown-surface-hover: #e7f0f8; - --sd-ui-dropdown-surface-active: #cde2f3; + --sd-ui-dropdown-hover-bg: #e7f0f8; + --sd-ui-dropdown-active-bg: #cde2f3; --sd-ui-dropdown-shadow: 0 8px 24px rgba(15, 23, 42, 0.22); - --sd-ui-context-menu-surface: #ffffff; - --sd-ui-context-menu-border: #8ea5b8; - --sd-ui-context-menu-item-hover-surface: #e2eef8; - --sd-ui-context-menu-item-active-surface: #cde2f3; - --sd-ui-context-menu-shadow: 0 8px 24px rgba(15, 23, 42, 0.22); + --sd-ui-menu-bg: #ffffff; + --sd-ui-menu-border: #8ea5b8; + --sd-ui-menu-item-hover-bg: #e2eef8; + --sd-ui-menu-item-active-bg: #cde2f3; + --sd-ui-menu-shadow: 0 8px 24px rgba(15, 23, 42, 0.22); /* Layout */ - --sd-layout-page-background: #ffffff; + --sd-layout-page-bg: #ffffff; --sd-layout-page-shadow: 0 8px 24px rgba(15, 23, 42, 0.16); /* Comments */ - --sd-ui-comments-panel-card-background: #dbe7f2; - --sd-ui-comments-panel-card-active-background: #ffffff; - --sd-ui-comments-panel-card-active-border: #8ea5b8; - --sd-ui-comments-panel-card-shadow: 0 10px 24px rgba(15, 23, 42, 0.18); - --sd-ui-comments-panel-input-border: #7f96aa; - --sd-ui-comments-panel-separator: #9fb4c7; - --sd-ui-comments-panel-timestamp-text: #475569; + --sd-ui-comments-card-bg: #dbe7f2; + --sd-ui-comments-card-active-bg: #ffffff; + --sd-ui-comments-card-active-border: #8ea5b8; + --sd-ui-comments-card-shadow: 0 10px 24px rgba(15, 23, 42, 0.18); + --sd-ui-comments-input-border: #7f96aa; + --sd-ui-comments-separator: #9fb4c7; + --sd-ui-comments-timestamp-text: #475569; /* Highlights */ --sd-comments-highlight-external-base: #b45309; diff --git a/packages/superdoc/src/assets/styles/helpers/variables.css b/packages/superdoc/src/assets/styles/helpers/variables.css index 80ddfb1d7c..eb1e471966 100644 --- a/packages/superdoc/src/assets/styles/helpers/variables.css +++ b/packages/superdoc/src/assets/styles/helpers/variables.css @@ -53,10 +53,10 @@ --sd-ui-text: #47484a; --sd-ui-text-muted: var(--sd-color-gray-700); --sd-ui-text-disabled: var(--sd-color-gray-500); - --sd-ui-surface: #ffffff; - --sd-ui-surface-hover: var(--sd-color-gray-400); - --sd-ui-surface-active: #c8d0d8; - --sd-ui-surface-disabled: var(--sd-color-gray-100); + --sd-ui-bg: #ffffff; + --sd-ui-hover-bg: var(--sd-color-gray-400); + --sd-ui-active-bg: #c8d0d8; + --sd-ui-disabled-bg: var(--sd-color-gray-100); --sd-ui-border: var(--sd-color-gray-400); --sd-ui-action: var(--sd-color-blue-500); --sd-ui-action-hover: var(--sd-color-blue-600); @@ -73,17 +73,17 @@ /* UI: dropdown — cascades surface/text/border from semantic tier */ --sd-ui-dropdown-border: var(--sd-ui-border); - --sd-ui-dropdown-surface: var(--sd-ui-surface); + --sd-ui-dropdown-bg: var(--sd-ui-bg); --sd-ui-dropdown-option-radius: 3px; --sd-ui-dropdown-text: var(--sd-ui-text); --sd-ui-dropdown-hover-text: var(--sd-ui-dropdown-text); --sd-ui-dropdown-selected-text: var(--sd-ui-dropdown-text); - --sd-ui-dropdown-surface-hover: var(--sd-ui-surface-hover); - --sd-ui-dropdown-surface-active: var(--sd-ui-surface-active); + --sd-ui-dropdown-hover-bg: var(--sd-ui-hover-bg); + --sd-ui-dropdown-active-bg: var(--sd-ui-active-bg); --sd-ui-dropdown-shadow: 0 8px 24px rgba(0, 0, 0, 0.16); /* UI: tooltip — intentionally inverted palette (dark bg, light text) */ - --sd-ui-tooltip-surface: #262626; + --sd-ui-tooltip-bg: #262626; --sd-ui-tooltip-text: #ffffff; --sd-ui-tooltip-radius: var(--sd-radius-100); --sd-ui-tooltip-shadow: 0 3px 12px rgba(0, 0, 0, 0.28); @@ -94,66 +94,66 @@ --sd-ui-toolbar-padding-y: 4px; --sd-ui-toolbar-item-gap: 2px; --sd-ui-toolbar-item-padding: 5px; - --sd-ui-toolbar-background: transparent; + --sd-ui-toolbar-bg: transparent; --sd-ui-toolbar-button-text: var(--sd-ui-text); - --sd-ui-toolbar-button-hover-surface: var(--sd-ui-surface-hover); - --sd-ui-toolbar-button-active-surface: var(--sd-ui-surface-active); + --sd-ui-toolbar-button-hover-bg: var(--sd-ui-hover-bg); + --sd-ui-toolbar-button-active-bg: var(--sd-ui-active-bg); /* UI: context menu — cascades surface/text/border/action from semantic tier */ - --sd-ui-context-menu-surface: var(--sd-ui-surface); - --sd-ui-context-menu-text: var(--sd-ui-text); - --sd-ui-context-menu-font-size: var(--sd-font-size-200); - --sd-ui-context-menu-radius: 0; - --sd-ui-context-menu-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05), 0px 10px 20px rgba(0, 0, 0, 0.1); - --sd-ui-context-menu-border: var(--sd-ui-border); - --sd-ui-context-menu-input-border: var(--sd-ui-border); - --sd-ui-context-menu-input-focus-border: var(--sd-ui-action); - --sd-ui-context-menu-item-hover-surface: var(--sd-ui-surface-hover); - --sd-ui-context-menu-item-active-surface: var(--sd-ui-surface-active); - --sd-ui-context-menu-item-active-text: var(--sd-ui-action); + --sd-ui-menu-bg: var(--sd-ui-bg); + --sd-ui-menu-text: var(--sd-ui-text); + --sd-ui-menu-font-size: var(--sd-font-size-200); + --sd-ui-menu-radius: 0; + --sd-ui-menu-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05), 0px 10px 20px rgba(0, 0, 0, 0.1); + --sd-ui-menu-border: var(--sd-ui-border); + --sd-ui-menu-input-border: var(--sd-ui-border); + --sd-ui-menu-input-focus-border: var(--sd-ui-action); + --sd-ui-menu-item-hover-bg: var(--sd-ui-hover-bg); + --sd-ui-menu-item-active-bg: var(--sd-ui-active-bg); + --sd-ui-menu-item-active-text: var(--sd-ui-action); /* UI: tools */ --sd-ui-tools-gap: 6px; --sd-ui-tools-item-size: 50px; --sd-ui-tools-item-radius: var(--sd-radius-300); - --sd-ui-tools-item-surface: rgba(219, 219, 219, 0.6); + --sd-ui-tools-item-bg: rgba(219, 219, 219, 0.6); --sd-ui-tools-icon-size: 20px; /* UI: comments panel — cascades surface/text/border from semantic tier */ - --sd-ui-comments-panel-card-background: #f3f6fd; - --sd-ui-comments-panel-card-hover-background: var(--sd-ui-comments-panel-card-background); - --sd-ui-comments-panel-card-active-background: var(--sd-ui-surface); - --sd-ui-comments-panel-card-resolved-background: var(--sd-ui-surface-disabled); - --sd-ui-comments-panel-card-active-border: var(--sd-ui-border); - --sd-ui-comments-panel-card-radius: var(--sd-radius-300); - --sd-ui-comments-panel-card-padding: 16px; - --sd-ui-comments-panel-card-shadow: 0px 4px 12px 0px rgba(50, 50, 50, 0.15); - --sd-ui-comments-panel-separator: var(--sd-ui-border); - --sd-ui-comments-panel-transition: all 200ms ease; - --sd-ui-comments-panel-author-text: var(--sd-ui-text); - --sd-ui-comments-panel-author-size: var(--sd-font-size-400); - --sd-ui-comments-panel-author-weight: 600; - --sd-ui-comments-panel-imported-tag-text: var(--sd-ui-text-muted); - --sd-ui-comments-panel-imported-tag-background: var(--sd-color-gray-200); - --sd-ui-comments-panel-timestamp-text: var(--sd-ui-text-muted); - --sd-ui-comments-panel-timestamp-size: var(--sd-font-size-200); - --sd-ui-comments-panel-body-text: var(--sd-ui-text); - --sd-ui-comments-panel-body-text-size: var(--sd-font-size-400); - --sd-ui-comments-panel-resolved-badge-text: var(--sd-color-green-500); - --sd-ui-comments-panel-tc-insert-text: var(--sd-color-green-500); - --sd-ui-comments-panel-tc-delete-text: var(--sd-color-rose-500); - --sd-ui-comments-panel-dropdown-border: var(--sd-ui-border); - --sd-ui-comments-panel-dropdown-background: var(--sd-ui-surface); - --sd-ui-comments-panel-dropdown-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); - --sd-ui-comments-panel-dropdown-option-text-size: var(--sd-font-size-400); - --sd-ui-comments-panel-dropdown-option-text: var(--sd-ui-comments-panel-body-text); - --sd-ui-comments-panel-dropdown-option-hover-text: var(--sd-ui-comments-panel-dropdown-option-text); - --sd-ui-comments-panel-dropdown-option-selected-text: var(--sd-ui-comments-panel-dropdown-option-text); - --sd-ui-comments-panel-dropdown-option-hover-background: var(--sd-ui-surface-hover); - --sd-ui-comments-panel-input-background: var(--sd-ui-surface); - --sd-ui-comments-panel-input-border: var(--sd-ui-border); - --sd-ui-comments-panel-visibility-internal-background: #cde6e6; - --sd-ui-comments-panel-visibility-external-background: #f5cfda; + --sd-ui-comments-card-bg: #f3f6fd; + --sd-ui-comments-card-hover-bg: var(--sd-ui-comments-card-bg); + --sd-ui-comments-card-active-bg: var(--sd-ui-bg); + --sd-ui-comments-card-resolved-bg: var(--sd-ui-disabled-bg); + --sd-ui-comments-card-active-border: var(--sd-ui-border); + --sd-ui-comments-card-radius: var(--sd-radius-300); + --sd-ui-comments-card-padding: 16px; + --sd-ui-comments-card-shadow: 0px 4px 12px 0px rgba(50, 50, 50, 0.15); + --sd-ui-comments-separator: var(--sd-ui-border); + --sd-ui-comments-transition: all 200ms ease; + --sd-ui-comments-author-text: var(--sd-ui-text); + --sd-ui-comments-author-size: var(--sd-font-size-400); + --sd-ui-comments-author-weight: 600; + --sd-ui-comments-tag-text: var(--sd-ui-text-muted); + --sd-ui-comments-tag-bg: var(--sd-color-gray-200); + --sd-ui-comments-timestamp-text: var(--sd-ui-text-muted); + --sd-ui-comments-timestamp-size: var(--sd-font-size-200); + --sd-ui-comments-body-text: var(--sd-ui-text); + --sd-ui-comments-body-size: var(--sd-font-size-400); + --sd-ui-comments-resolved-text: var(--sd-color-green-500); + --sd-ui-comments-insert-text: var(--sd-color-green-500); + --sd-ui-comments-delete-text: var(--sd-color-rose-500); + --sd-ui-comments-dropdown-border: var(--sd-ui-border); + --sd-ui-comments-dropdown-bg: var(--sd-ui-bg); + --sd-ui-comments-dropdown-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); + --sd-ui-comments-option-size: var(--sd-font-size-400); + --sd-ui-comments-option-text: var(--sd-ui-comments-body-text); + --sd-ui-comments-option-hover-text: var(--sd-ui-comments-option-text); + --sd-ui-comments-option-selected-text: var(--sd-ui-comments-option-text); + --sd-ui-comments-option-hover-bg: var(--sd-ui-hover-bg); + --sd-ui-comments-input-bg: var(--sd-ui-bg); + --sd-ui-comments-input-border: var(--sd-ui-border); + --sd-ui-comments-internal-bg: #cde6e6; + --sd-ui-comments-external-bg: #f5cfda; /* Styles: comments — document-level highlight colors, intentionally standalone */ --sd-comments-highlight-external-base: #b1124b; @@ -181,15 +181,15 @@ /* Styles: content controls (SDT) — blue accent, intentionally standalone */ --sd-content-controls-block-border: #629be7; - --sd-content-controls-block-hover-background: var(--sd-ui-surface-hover); + --sd-content-controls-block-hover-bg: var(--sd-ui-hover-bg); --sd-content-controls-inline-border: #629be7; - --sd-content-controls-inline-hover-background: var(--sd-ui-surface-hover); + --sd-content-controls-inline-hover-bg: var(--sd-ui-hover-bg); --sd-content-controls-label-border: #629be7; - --sd-content-controls-label-background: #629be7ee; + --sd-content-controls-label-bg: #629be7ee; --sd-content-controls-label-text: #ffffff; - --sd-content-controls-lock-hover-background: rgba(98, 155, 231, 0.08); + --sd-content-controls-lock-hover-bg: rgba(98, 155, 231, 0.08); /* Styles: layout — cascades surface from semantic tier */ - --sd-layout-page-background: var(--sd-ui-surface); + --sd-layout-page-bg: var(--sd-ui-bg); --sd-layout-page-shadow: 0 4px 20px rgba(15, 23, 42, 0.08); } diff --git a/packages/superdoc/src/components/CommentsLayer/CommentDialog.vue b/packages/superdoc/src/components/CommentsLayer/CommentDialog.vue index f2521b8516..8be3d58145 100644 --- a/packages/superdoc/src/components/CommentsLayer/CommentDialog.vue +++ b/packages/superdoc/src/components/CommentsLayer/CommentDialog.vue @@ -749,14 +749,14 @@ watch(editingCommentId, (commentId) => { .comments-dialog { display: flex; flex-direction: column; - padding: var(--sd-ui-comments-panel-card-padding, 16px); - border-radius: var(--sd-ui-comments-panel-card-radius, 12px); - background-color: var(--sd-ui-comments-panel-card-background, #f3f6fd); + padding: var(--sd-ui-comments-card-padding, 16px); + border-radius: var(--sd-ui-comments-card-radius, 12px); + background-color: var(--sd-ui-comments-card-bg, #f3f6fd); border: 1px solid transparent; font-family: var(--sd-ui-font-family, Arial, Helvetica, sans-serif); - font-size: var(--sd-ui-comments-panel-body-text-size, 14px); + font-size: var(--sd-ui-comments-body-size, 14px); line-height: 1.5; - transition: var(--sd-ui-comments-panel-transition, all 200ms ease); + transition: var(--sd-ui-comments-transition, all 200ms ease); box-shadow: none; z-index: 5; max-width: 300px; @@ -767,33 +767,33 @@ watch(editingCommentId, (commentId) => { cursor: pointer; } .comments-dialog:not(.is-active):not(.is-resolved):hover { - background-color: var(--sd-ui-comments-panel-card-hover-background, #f3f6fd); + background-color: var(--sd-ui-comments-card-hover-bg, #f3f6fd); } .comments-dialog:not(.is-resolved):hover :deep(.overflow-menu) { opacity: 1; pointer-events: auto; } .comments-dialog.is-active { - background-color: var(--sd-ui-comments-panel-card-active-background, #ffffff); - border-color: var(--sd-ui-comments-panel-card-active-border, #e0e0e0); - box-shadow: var(--sd-ui-comments-panel-card-shadow, 0px 4px 12px 0px rgba(50, 50, 50, 0.15)); + background-color: var(--sd-ui-comments-card-active-bg, #ffffff); + border-color: var(--sd-ui-comments-card-active-border, #e0e0e0); + box-shadow: var(--sd-ui-comments-card-shadow, 0px 4px 12px 0px rgba(50, 50, 50, 0.15)); z-index: 10; } .comments-dialog.is-resolved { - background-color: var(--sd-ui-comments-panel-card-resolved-background, #f0f0f0); + background-color: var(--sd-ui-comments-card-resolved-bg, #f0f0f0); } .comment-separator { - background-color: var(--sd-ui-comments-panel-separator, #e0e0e0); + background-color: var(--sd-ui-comments-separator, #e0e0e0); height: 1px; width: 100%; margin: 10px 0; } .comment { - font-size: var(--sd-ui-comments-panel-body-text-size, 14px); + font-size: var(--sd-ui-comments-body-size, 14px); line-height: 1.5; - color: var(--sd-ui-comments-panel-body-text, #212121); + color: var(--sd-ui-comments-body-text, #212121); margin: 4px 0 0 0; } .comment :deep(p) { @@ -801,22 +801,22 @@ watch(editingCommentId, (commentId) => { } .tracked-change { - font-size: var(--sd-ui-comments-panel-body-text-size, 14px); + font-size: var(--sd-ui-comments-body-size, 14px); line-height: 1.5; - color: var(--sd-ui-comments-panel-body-text, #212121); + color: var(--sd-ui-comments-body-text, #212121); margin: 4px 0 0 0; } .change-type { - color: var(--sd-ui-comments-panel-body-text, #212121); + color: var(--sd-ui-comments-body-text, #212121); } .tracked-change-text { - color: var(--sd-ui-comments-panel-body-text, #212121); + color: var(--sd-ui-comments-body-text, #212121); } .tracked-change-text.is-deleted { - color: var(--sd-ui-comments-panel-tc-delete-text, #cb0e47); + color: var(--sd-ui-comments-delete-text, #cb0e47); } .tracked-change-text.is-inserted { - color: var(--sd-ui-comments-panel-tc-insert-text, #00853d); + color: var(--sd-ui-comments-insert-text, #00853d); font-weight: 500; } @@ -827,7 +827,7 @@ watch(editingCommentId, (commentId) => { gap: 4px; font-size: 11px; font-weight: 500; - color: var(--sd-ui-comments-panel-resolved-badge-text, #00853d); + color: var(--sd-ui-comments-resolved-text, #00853d); margin-bottom: 4px; } .resolved-badge__icon { @@ -883,7 +883,7 @@ watch(editingCommentId, (commentId) => { --sd-comment-avatar-size: 20px; --sd-comment-avatar-font-size: 8px; margin-left: -4px; - border: 2px solid var(--sd-ui-comments-panel-card-active-background, #ffffff); + border: 2px solid var(--sd-ui-comments-card-active-bg, #ffffff); } .collapsed-avatars .mini-avatar:first-child { margin-left: 0; @@ -891,10 +891,10 @@ watch(editingCommentId, (commentId) => { /* ── New comment input ── */ .new-comment-input-wrapper { - border: 1.5px solid var(--sd-ui-comments-panel-input-border, #dbdbdb); + border: 1.5px solid var(--sd-ui-comments-input-border, #dbdbdb); border-radius: 12px; padding: 8.5px 10.5px; - background: var(--sd-ui-comments-panel-input-background, #ffffff); + background: var(--sd-ui-comments-input-bg, #ffffff); margin-top: 4px; max-height: 150px; overflow-y: auto; @@ -938,10 +938,10 @@ watch(editingCommentId, (commentId) => { margin-top: 10px; } .reply-input-wrapper { - border: 1.5px solid var(--sd-ui-comments-panel-input-border, #dbdbdb); + border: 1.5px solid var(--sd-ui-comments-input-border, #dbdbdb); border-radius: 12px; padding: 8.5px 10.5px; - background: var(--sd-ui-comments-panel-input-background, #ffffff); + background: var(--sd-ui-comments-input-bg, #ffffff); max-height: 150px; overflow-y: auto; } diff --git a/packages/superdoc/src/components/CommentsLayer/CommentHeader.vue b/packages/superdoc/src/components/CommentsLayer/CommentHeader.vue index b31cc3e665..77bd5084b3 100644 --- a/packages/superdoc/src/components/CommentsLayer/CommentHeader.vue +++ b/packages/superdoc/src/components/CommentsLayer/CommentHeader.vue @@ -209,9 +209,9 @@ const getCurrentUser = computed(() => { flex-direction: column; } .user-name { - font-size: var(--sd-ui-comments-panel-author-size, 14px); - font-weight: var(--sd-ui-comments-panel-author-weight, 600); - color: var(--sd-ui-comments-panel-author-text, #212121); + font-size: var(--sd-ui-comments-author-size, 14px); + font-weight: var(--sd-ui-comments-author-weight, 600); + color: var(--sd-ui-comments-author-text, #212121); line-height: 1.2em; } .imported-tag { @@ -220,8 +220,8 @@ const getCurrentUser = computed(() => { font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; - color: var(--sd-ui-comments-panel-imported-tag-text, #888888); - background: var(--sd-ui-comments-panel-imported-tag-background, #f2f2f2); + color: var(--sd-ui-comments-tag-text, #888888); + background: var(--sd-ui-comments-tag-bg, #f2f2f2); border-radius: 3px; padding: 1px 4px; margin-left: 6px; @@ -230,8 +230,8 @@ const getCurrentUser = computed(() => { } .user-timestamp { line-height: 1.2em; - font-size: var(--sd-ui-comments-panel-timestamp-size, 12px); - color: var(--sd-ui-comments-panel-timestamp-text, #888888); + font-size: var(--sd-ui-comments-timestamp-size, 12px); + color: var(--sd-ui-comments-timestamp-text, #888888); } .overflow-menu { flex-shrink: 1; @@ -259,7 +259,7 @@ const getCurrentUser = computed(() => { transition: all 250ms ease; } .overflow-menu__icon:hover { - background-color: var(--sd-ui-comments-panel-separator, #dbdbdb); + background-color: var(--sd-ui-comments-separator, #dbdbdb); } .overflow-menu__icon :deep(svg) { width: 100%; diff --git a/packages/superdoc/src/components/CommentsLayer/CommentsDropdown.vue b/packages/superdoc/src/components/CommentsLayer/CommentsDropdown.vue index 150dbb7bcb..a6bbd3d911 100644 --- a/packages/superdoc/src/components/CommentsLayer/CommentsDropdown.vue +++ b/packages/superdoc/src/components/CommentsLayer/CommentsDropdown.vue @@ -176,16 +176,16 @@ onBeforeUnmount(() => { .comments-dropdown__menu { min-width: 120px; border-radius: 8px; - border: 1px solid var(--sd-ui-comments-panel-dropdown-border, #dbdbdb); - background: var(--sd-ui-comments-panel-dropdown-background, #fff); - box-shadow: var(--sd-ui-comments-panel-dropdown-shadow, 0 8px 24px rgba(0, 0, 0, 0.12)); + border: 1px solid var(--sd-ui-comments-dropdown-border, #dbdbdb); + background: var(--sd-ui-comments-dropdown-bg, #fff); + box-shadow: var(--sd-ui-comments-dropdown-shadow, 0 8px 24px rgba(0, 0, 0, 0.12)); padding: 4px; box-sizing: border-box; } .comments-dropdown__option { - font-size: var(--sd-ui-comments-panel-dropdown-option-text-size, 14px); - color: var(--sd-ui-comments-panel-dropdown-option-text, #212121); + font-size: var(--sd-ui-comments-option-size, 14px); + color: var(--sd-ui-comments-option-text, #212121); display: flex; align-items: center; gap: 8px; @@ -197,8 +197,8 @@ onBeforeUnmount(() => { } .comments-dropdown__option:hover { - background-color: var(--sd-ui-comments-panel-dropdown-option-hover-background, #f3f3f5); - color: var(--sd-ui-comments-panel-dropdown-option-hover-text, #212121); + background-color: var(--sd-ui-comments-option-hover-bg, #f3f3f5); + color: var(--sd-ui-comments-option-hover-text, #212121); } .comments-dropdown__option.disabled { diff --git a/packages/superdoc/src/components/CommentsLayer/InternalDropdown.vue b/packages/superdoc/src/components/CommentsLayer/InternalDropdown.vue index ac8a256332..38ecc5c3f3 100644 --- a/packages/superdoc/src/components/CommentsLayer/InternalDropdown.vue +++ b/packages/superdoc/src/components/CommentsLayer/InternalDropdown.vue @@ -23,13 +23,13 @@ const options = [ label: 'Internal', key: 'internal', iconString: superdocIcons.internal, - backgroundColor: 'var(--sd-ui-comments-panel-visibility-internal-background, #CDE6E6)', + backgroundColor: 'var(--sd-ui-comments-internal-bg, #CDE6E6)', }, { label: 'External', key: 'external', iconString: superdocIcons.external, - backgroundColor: 'var(--sd-ui-comments-panel-visibility-external-background, #F5CFDA)', + backgroundColor: 'var(--sd-ui-comments-external-bg, #F5CFDA)', }, ]; diff --git a/packages/superdoc/src/dev/themes/neon-night.css b/packages/superdoc/src/dev/themes/neon-night.css index 07a02a1390..316e143328 100644 --- a/packages/superdoc/src/dev/themes/neon-night.css +++ b/packages/superdoc/src/dev/themes/neon-night.css @@ -4,61 +4,61 @@ --sd-ui-text: #e7f7ff; --sd-ui-text-muted: #9bd8ff; --sd-ui-text-disabled: #5a89a8; - --sd-ui-surface: #07111f; - --sd-ui-surface-hover: #0e1d33; - --sd-ui-surface-active: #152949; + --sd-ui-bg: #07111f; + --sd-ui-hover-bg: #0e1d33; + --sd-ui-active-bg: #152949; --sd-ui-border: #2d4e76; --sd-ui-action: #15e3ff; --sd-ui-action-hover: #7cf7ff; --sd-ui-shadow: 0 0 0 1px #1f3a5a, 0 10px 28px rgba(0, 0, 0, 0.5); /* Toolbar / dropdown / context */ - --sd-ui-toolbar-background: #061327; + --sd-ui-toolbar-bg: #061327; --sd-ui-toolbar-button-text: #d8eeff; - --sd-ui-toolbar-button-hover-surface: #12325a; - --sd-ui-toolbar-button-active-surface: #1a4a84; - --sd-ui-dropdown-surface: #0a1a30; + --sd-ui-toolbar-button-hover-bg: #12325a; + --sd-ui-toolbar-button-active-bg: #1a4a84; + --sd-ui-dropdown-bg: #0a1a30; --sd-ui-dropdown-border: #36659a; --sd-ui-dropdown-text: #d8eeff; --sd-ui-dropdown-hover-text: #e9fbff; --sd-ui-dropdown-selected-text: #e9fbff; - --sd-ui-dropdown-surface-hover: #12325a; - --sd-ui-dropdown-surface-active: #1a4a84; + --sd-ui-dropdown-hover-bg: #12325a; + --sd-ui-dropdown-active-bg: #1a4a84; --sd-ui-dropdown-shadow: 0 0 0 1px #2e547f, 0 14px 34px rgba(0, 0, 0, 0.58); - --sd-ui-context-menu-surface: #0a1a30; - --sd-ui-context-menu-text: #d8eeff; - --sd-ui-context-menu-border: #36659a; - --sd-ui-context-menu-item-hover-surface: #133862; - --sd-ui-context-menu-item-active-surface: #1a4a84; - --sd-ui-context-menu-item-active-text: #e9fbff; - --sd-ui-context-menu-shadow: 0 0 0 1px #2e547f, 0 14px 34px rgba(0, 0, 0, 0.58); - --sd-ui-tooltip-surface: #001018; + --sd-ui-menu-bg: #0a1a30; + --sd-ui-menu-text: #d8eeff; + --sd-ui-menu-border: #36659a; + --sd-ui-menu-item-hover-bg: #133862; + --sd-ui-menu-item-active-bg: #1a4a84; + --sd-ui-menu-item-active-text: #e9fbff; + --sd-ui-menu-shadow: 0 0 0 1px #2e547f, 0 14px 34px rgba(0, 0, 0, 0.58); + --sd-ui-tooltip-bg: #001018; --sd-ui-tooltip-text: #cfffff; /* Layout */ - --sd-layout-page-background: #ffffff; + --sd-layout-page-bg: #ffffff; --sd-layout-page-shadow: 0 0 0 1px #1c3758, 0 20px 50px rgba(0, 0, 0, 0.46); /* Comments panel */ - --sd-ui-comments-panel-card-background: #0b1f39; - --sd-ui-comments-panel-card-hover-background: #10274a; - --sd-ui-comments-panel-card-active-background: #15315a; - --sd-ui-comments-panel-card-resolved-background: #0c2441; - --sd-ui-comments-panel-card-active-border: #4a82be; - --sd-ui-comments-panel-card-shadow: 0 0 0 1px #2f5f96, 0 14px 30px rgba(0, 0, 0, 0.54); - --sd-ui-comments-panel-separator: #2f5f96; - --sd-ui-comments-panel-author-text: #e7f7ff; - --sd-ui-comments-panel-timestamp-text: #8fc4ef; - --sd-ui-comments-panel-body-text: #d6ecff; - --sd-ui-comments-panel-input-background: #0e1d33; - --sd-ui-comments-panel-input-border: #4a82be; - --sd-ui-comments-panel-dropdown-background: #0a1a30; - --sd-ui-comments-panel-dropdown-border: #36659a; - --sd-ui-comments-panel-dropdown-shadow: 0 0 0 1px #2e547f, 0 14px 34px rgba(0, 0, 0, 0.58); - --sd-ui-comments-panel-dropdown-option-text: #d6ecff; - --sd-ui-comments-panel-dropdown-option-hover-text: #e9fbff; - --sd-ui-comments-panel-dropdown-option-selected-text: #e9fbff; - --sd-ui-comments-panel-dropdown-option-hover-background: #12325a; + --sd-ui-comments-card-bg: #0b1f39; + --sd-ui-comments-card-hover-bg: #10274a; + --sd-ui-comments-card-active-bg: #15315a; + --sd-ui-comments-card-resolved-bg: #0c2441; + --sd-ui-comments-card-active-border: #4a82be; + --sd-ui-comments-card-shadow: 0 0 0 1px #2f5f96, 0 14px 30px rgba(0, 0, 0, 0.54); + --sd-ui-comments-separator: #2f5f96; + --sd-ui-comments-author-text: #e7f7ff; + --sd-ui-comments-timestamp-text: #8fc4ef; + --sd-ui-comments-body-text: #d6ecff; + --sd-ui-comments-input-bg: #0e1d33; + --sd-ui-comments-input-border: #4a82be; + --sd-ui-comments-dropdown-bg: #0a1a30; + --sd-ui-comments-dropdown-border: #36659a; + --sd-ui-comments-dropdown-shadow: 0 0 0 1px #2e547f, 0 14px 34px rgba(0, 0, 0, 0.58); + --sd-ui-comments-option-text: #d6ecff; + --sd-ui-comments-option-hover-text: #e9fbff; + --sd-ui-comments-option-selected-text: #e9fbff; + --sd-ui-comments-option-hover-bg: #12325a; /* Comments highlights */ --sd-comments-highlight-external-base: #ff2fa3; From 49278fde233d13b9db2209568d0206f7d235a350 Mon Sep 17 00:00:00 2001 From: Caio Pizzol Date: Wed, 18 Mar 2026 11:21:21 -0300 Subject: [PATCH 04/11] docs: rewrite theming docs to match CLAUDE.md voice and style guidelines - Sentence case headings throughout - Developer register: show code first, explain after - "You" framing, short sentences, no buzzwords - Getting Started page is high-level overview, links to detail - Custom themes guide: resolved default values (not var() refs), "inherits X" for cascading vars, scannable tables - Migration guide: tighter tables, less prose - Remove Tips/Warnings from Getting Started page per page depth rules --- apps/docs/getting-started/theming.mdx | 88 ++---- apps/docs/guides/general/custom-themes.mdx | 316 +++++++++---------- apps/docs/guides/migration/css-variables.mdx | 112 +++---- 3 files changed, 229 insertions(+), 287 deletions(-) diff --git a/apps/docs/getting-started/theming.mdx b/apps/docs/getting-started/theming.mdx index 8d523282a5..e14c3e4b80 100644 --- a/apps/docs/getting-started/theming.mdx +++ b/apps/docs/getting-started/theming.mdx @@ -4,35 +4,29 @@ sidebarTitle: Theming keywords: "theming, css variables, custom theme, dark mode, design tokens, branding, css customization, styling" --- -Customize how SuperDoc looks using CSS variables. Change five variables and your brand colors flow through every component — toolbar, comments, dropdowns, everything. - -## Quick start - -Every SuperDoc component reads its colors, spacing, and typography from `--sd-*` CSS custom properties. Override them on any parent element and the changes cascade down. +Change five CSS variables and your brand colors flow through every SuperDoc component — toolbar, comments, dropdowns, everything. No JavaScript needed. ```css -/* Your custom theme — add to your stylesheet */ .my-theme { - --sd-ui-action: #6366f1; /* buttons, active states */ + --sd-ui-action: #6366f1; --sd-ui-action-hover: #4f46e5; - --sd-ui-toolbar-bg: #f8fafc; /* toolbar surface */ - --sd-ui-bg: #ffffff; /* panels, cards */ - --sd-ui-text: #1e293b; /* body text */ + --sd-ui-toolbar-bg: #f8fafc; + --sd-ui-bg: #ffffff; + --sd-ui-text: #1e293b; } ``` ```html -
``` -That's it. No JavaScript config needed for visual customization. +Every `--sd-*` CSS custom property can be overridden on any parent element. The changes cascade down to all child components. ## Preset themes -SuperDoc ships three preset themes you can use out of the box. Add the class to your `` or any wrapper element. +Three preset themes ship out of the box. Add the class to `` or any wrapper element. @@ -42,7 +36,7 @@ SuperDoc ships three preset themes you can use out of the box. Add the class to ``` - Microsoft Word aesthetic. Fluent-style surfaces and borders. + Microsoft Word aesthetic. Fluent-style borders and surfaces. ```html ``` @@ -55,75 +49,45 @@ SuperDoc ships three preset themes you can use out of the box. Add the class to -Each preset overrides 30-50 variables across toolbar, dropdown, context menu, comments, and layout. You can layer your own overrides on top of a preset: +You can layer your own overrides on top of a preset: ```css -/* Start from the Docs preset, customize from there */ .sd-theme-docs { --sd-ui-action: #your-brand-color; --sd-ui-comments-card-bg: #f0f0f0; } ``` -## Token architecture +## How the token system works Variables are organized in three tiers. Higher tiers reference lower tiers, so changing a primitive cascades everywhere. ``` -Primitives → UI semantic → Component-specific ---sd-color-* --sd-ui-* --sd-ui-toolbar-* ---sd-font-size-* --sd-ui-text --sd-ui-comments-* ---sd-radius-* --sd-ui-action --sd-comments-highlight-* +Primitives UI semantic Component-specific +--sd-color-* --sd-ui-text --sd-ui-toolbar-* +--sd-font-size-* --sd-ui-bg --sd-ui-comments-* +--sd-radius-* --sd-ui-action --sd-ui-dropdown-* ``` -### Tier 1: Primitives - -Raw color, sizing, and radius values. These are the building blocks. - -| Variable | Default | What it controls | -|----------|---------|-----------------| -| `--sd-color-blue-500` | `#1355ff` | Brand blue used throughout | -| `--sd-color-gray-*` | 50-900 scale | Neutral palette | -| `--sd-font-size-200` | `12px` | Small text (toolbar labels, timestamps) | -| `--sd-font-size-400` | `14px` | Body text (comments, inputs) | -| `--sd-radius-100` | `6px` | Default border radius | -| `--sd-radius-300` | `12px` | Larger radius (cards, panels) | - -### Tier 2: UI semantic +**Tier 1 — Primitives.** Raw color scales, font sizes, and radii. You rarely need to touch these directly. -Shared variables that many components reference. Change one of these and you affect the entire UI. +**Tier 2 — UI semantic.** The "5-variable theme." Change these and every component updates: | Variable | Default | What it controls | |----------|---------|-----------------| -| `--sd-ui-font-family` | `Arial, Helvetica, sans-serif` | Font for all UI elements | +| `--sd-ui-font-family` | `Arial, Helvetica, sans-serif` | Font for all UI chrome | | `--sd-ui-text` | `#47484a` | Primary text color | -| `--sd-ui-text-muted` | `gray-700` | Secondary/muted text | -| `--sd-ui-bg` | `#ffffff` | Panel and card backgrounds | -| `--sd-ui-hover-bg` | `gray-400` | Hover states | -| `--sd-ui-border` | `gray-400` | Default borders | -| `--sd-ui-action` | `blue-500` | Action/accent color (buttons, links) | -| `--sd-ui-action-hover` | `blue-600` | Action hover state | -| `--sd-ui-shadow` | `0 4px 12px rgba(0,0,0,0.12)` | Default shadow | -| `--sd-ui-radius` | `6px` | Default border radius for UI elements | - -### Tier 3: Component-specific - -Fine-grained control over individual components. These override the semantic defaults for specific UI pieces. See the [custom themes guide](/guides/general/custom-themes) for the full reference. - -**Five groups to know:** +| `--sd-ui-bg` | `#ffffff` | Panels, cards, dropdowns | +| `--sd-ui-border` | `#dbdbdb` | All borders | +| `--sd-ui-action` | `#1355ff` | Buttons, links, active states | -1. **Toolbar** (`--sd-ui-toolbar-*`) — background, button colors, spacing -2. **Dropdowns** (`--sd-ui-dropdown-*`) — surfaces, borders, shadows for all dropdown menus -3. **Comments** (`--sd-ui-comments-*`) — card backgrounds, text colors, input styling -4. **Comment highlights** (`--sd-comments-highlight-*`) — in-document highlight colors for internal/external comments -5. **Tracked changes** (`--sd-tracked-changes-*`) — insert/delete/format highlight colors +**Tier 3 — Component-specific.** Fine-grained overrides for individual components. These default to the semantic tier, so you only set them when a component needs to look different from the rest. See the [full reference](/guides/general/custom-themes). -## What about the JavaScript config? +## JavaScript config -The `trackChangesConfig` and `commentsConfig` JavaScript options still work and take priority over CSS variables when set. Use the JS API for runtime changes, CSS variables for static theming. +The `trackChangesConfig` and `commentsConfig` JavaScript options still work. They take priority over CSS variables when set. Use the JS API for runtime changes, CSS variables for static theming. ```javascript -// This still works — it overrides the CSS variable at runtime new SuperDoc({ selector: '#editor', commentsConfig: { @@ -135,7 +99,7 @@ new SuperDoc({ }); ``` -## What's next +## Next steps - Per-component variable reference with every token, default value, and which component it controls. + Full variable reference by component. Dark theme starter. How to build your own preset. - Mapping from old CSS variable names to the new system. + Old-to-new variable name mapping. Backward compatibility details. diff --git a/apps/docs/guides/general/custom-themes.mdx b/apps/docs/guides/general/custom-themes.mdx index cfafa27584..893e33a48e 100644 --- a/apps/docs/guides/general/custom-themes.mdx +++ b/apps/docs/guides/general/custom-themes.mdx @@ -4,38 +4,37 @@ sidebarTitle: Custom themes keywords: "custom theme, css variables reference, design tokens, theming guide, component styling, dark theme" --- -A complete reference for every CSS variable you can override, organized by component. Start from the [theming overview](/getting-started/theming) if you haven't set up theming yet. +Every CSS variable you can override, organized by component. If you haven't set up theming yet, start with the [theming overview](/getting-started/theming). -## How to create a custom theme +## Creating a theme -Define a CSS class with your overrides and apply it to any ancestor element. Most people apply it to ``. +Define a CSS class with your overrides. Apply it to any ancestor element. ```css .my-company-theme { - /* Start broad — these cascade to every component */ + /* These cascade to every component */ --sd-ui-action: #8b5cf6; --sd-ui-action-hover: #7c3aed; --sd-ui-text: #1e293b; --sd-ui-bg: #f8fafc; --sd-ui-border: #e2e8f0; - /* Then fine-tune specific components */ + /* Fine-tune where needed */ --sd-ui-toolbar-bg: #f1f5f9; --sd-ui-comments-card-bg: #f0f0ff; } ``` -You don't need to set every variable. Unset variables fall back to their defaults. Start with the 5-10 UI semantic variables and add component-specific ones only where the defaults don't look right. +Start with the 5-10 semantic variables. Add component-specific ones only where the defaults don't look right. -## Building a dark theme +## Dark theme starter -For a dark theme, you'll need to override the core surface and text variables, plus set appropriate values for component backgrounds. Here's a starting point: +Override the core surface and text variables. Component backgrounds cascade from `--sd-ui-bg` automatically. ```css .dark-theme { - /* Core surface inversion */ --sd-ui-bg: #1a1a2e; --sd-ui-hover-bg: #2a2a3e; --sd-ui-active-bg: #3a3a4e; @@ -55,189 +54,184 @@ For a dark theme, you'll need to override the core surface and text variables, p --sd-ui-dropdown-bg: #1e293b; --sd-ui-dropdown-border: #334155; --sd-ui-dropdown-hover-bg: #2d3a4e; - --sd-ui-dropdown-shadow: 0 8px 24px rgba(0, 0, 0, 0.4); /* Comments */ --sd-ui-comments-card-bg: #1e293b; --sd-ui-comments-card-active-bg: #0f172a; --sd-ui-comments-input-bg: #1e293b; - --sd-ui-comments-input-border: #475569; --sd-ui-comments-author-text: #e2e8f0; --sd-ui-comments-body-text: #cbd5e1; --sd-ui-comments-timestamp-text: #64748b; - - /* Page */ - --sd-layout-page-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); } ``` -## Variable reference by component +## Variable reference -### General UI +### Semantic (UI-wide) -These are the top-level semantic variables. Most component-specific variables reference these as defaults, so changing them cascades broadly. +These cascade to every component. Changing one updates the entire UI. -| Variable | Default | Description | -|----------|---------|-------------| -| `--sd-ui-font-family` | `Arial, Helvetica, sans-serif` | Font for all UI elements | -| `--sd-ui-text` | `#47484a` | Primary text color | -| `--sd-ui-text-muted` | `gray-700` | Secondary text | -| `--sd-ui-text-disabled` | `gray-500` | Disabled state text | -| `--sd-ui-bg` | `#ffffff` | Default surface/background | -| `--sd-ui-hover-bg` | `gray-400` | Hover background | +| Variable | Default | Controls | +|----------|---------|----------| +| `--sd-ui-font-family` | `Arial, Helvetica, sans-serif` | UI font | +| `--sd-ui-text` | `#47484a` | Primary text | +| `--sd-ui-text-muted` | `#666666` | Secondary text | +| `--sd-ui-text-disabled` | `#ababab` | Disabled text | +| `--sd-ui-bg` | `#ffffff` | Default background | +| `--sd-ui-hover-bg` | `#dbdbdb` | Hover background | | `--sd-ui-active-bg` | `#c8d0d8` | Active/pressed background | -| `--sd-ui-disabled-bg` | `gray-100` | Disabled surface | -| `--sd-ui-border` | `gray-400` | Default border color | -| `--sd-ui-action` | `blue-500` | Action/accent color | -| `--sd-ui-action-hover` | `blue-600` | Action hover | +| `--sd-ui-disabled-bg` | `#f5f5f5` | Disabled background | +| `--sd-ui-border` | `#dbdbdb` | Default border | +| `--sd-ui-action` | `#1355ff` | Action/accent color | +| `--sd-ui-action-hover` | `#0f44cc` | Action hover | | `--sd-ui-shadow` | `0 4px 12px rgba(0,0,0,0.12)` | Default shadow | | `--sd-ui-radius` | `6px` | Default border radius | ### Toolbar -| Variable | Default | Description | -|----------|---------|-------------| -| `--sd-ui-toolbar-height` | `32px` | Toolbar row height | +| Variable | Default | Controls | +|----------|---------|----------| +| `--sd-ui-toolbar-height` | `32px` | Row height | | `--sd-ui-toolbar-padding-x` | `16px` | Horizontal padding | | `--sd-ui-toolbar-padding-y` | `4px` | Vertical padding | | `--sd-ui-toolbar-item-gap` | `2px` | Gap between buttons | -| `--sd-ui-toolbar-item-padding` | `5px` | Padding inside each button | -| `--sd-ui-toolbar-bg` | `transparent` | Toolbar background | -| `--sd-ui-toolbar-button-text` | `var(--sd-ui-text)` | Button text/icon color | -| `--sd-ui-toolbar-button-hover-bg` | `var(--sd-ui-hover-bg)` | Button hover background | -| `--sd-ui-toolbar-button-active-bg` | `var(--sd-ui-active-bg)` | Button active background | +| `--sd-ui-toolbar-item-padding` | `5px` | Button padding | +| `--sd-ui-toolbar-bg` | `transparent` | Background | +| `--sd-ui-toolbar-button-text` | inherits `--sd-ui-text` | Button text/icon color | +| `--sd-ui-toolbar-button-hover-bg` | inherits `--sd-ui-hover-bg` | Button hover | +| `--sd-ui-toolbar-button-active-bg` | inherits `--sd-ui-active-bg` | Button active | ### Dropdowns -Applies to toolbar dropdowns (font picker, style picker, etc.) and the overflow menu. - -| Variable | Default | Description | -|----------|---------|-------------| -| `--sd-ui-dropdown-bg` | `#ffffff` | Dropdown panel background | -| `--sd-ui-dropdown-border` | `#e4e6eb` | Panel border | -| `--sd-ui-dropdown-text` | `var(--sd-ui-text)` | Option text | -| `--sd-ui-dropdown-hover-text` | `var(--sd-ui-dropdown-text)` | Hovered option text | -| `--sd-ui-dropdown-selected-text` | `var(--sd-ui-dropdown-text)` | Selected option text | -| `--sd-ui-dropdown-hover-bg` | `#d8dee5` | Option hover background | -| `--sd-ui-dropdown-active-bg` | `#d8dee5` | Option active/selected background | +Toolbar dropdowns (font picker, style picker) and the overflow menu. + +| Variable | Default | Controls | +|----------|---------|----------| +| `--sd-ui-dropdown-bg` | inherits `--sd-ui-bg` | Panel background | +| `--sd-ui-dropdown-border` | inherits `--sd-ui-border` | Panel border | +| `--sd-ui-dropdown-text` | inherits `--sd-ui-text` | Option text | +| `--sd-ui-dropdown-hover-text` | inherits dropdown text | Hovered option text | +| `--sd-ui-dropdown-selected-text` | inherits dropdown text | Selected option text | +| `--sd-ui-dropdown-hover-bg` | inherits `--sd-ui-hover-bg` | Option hover | +| `--sd-ui-dropdown-active-bg` | inherits `--sd-ui-active-bg` | Option active | | `--sd-ui-dropdown-option-radius` | `3px` | Option border radius | | `--sd-ui-dropdown-shadow` | `0 8px 24px rgba(0,0,0,0.16)` | Panel shadow | ### Context menu -The right-click context menu. - -| Variable | Default | Description | -|----------|---------|-------------| -| `--sd-ui-menu-bg` | `#ffffff` | Menu background | -| `--sd-ui-menu-text` | `#47484a` | Menu text | -| `--sd-ui-menu-font-size` | `12px` | Menu text size | -| `--sd-ui-menu-radius` | `0` | Menu border radius | -| `--sd-ui-menu-border` | `#eee` | Menu border | -| `--sd-ui-menu-shadow` | complex | Menu shadow | -| `--sd-ui-menu-input-border` | `#ddd` | Search input border | -| `--sd-ui-menu-input-focus-border` | `#0096fd` | Search input focus border | -| `--sd-ui-menu-item-hover-bg` | `#f5f5f5` | Item hover background | -| `--sd-ui-menu-item-active-bg` | `#edf6ff` | Active item background | -| `--sd-ui-menu-item-active-text` | `#0096fd` | Active item text | +The right-click menu. + +| Variable | Default | Controls | +|----------|---------|----------| +| `--sd-ui-menu-bg` | inherits `--sd-ui-bg` | Background | +| `--sd-ui-menu-text` | inherits `--sd-ui-text` | Text | +| `--sd-ui-menu-font-size` | `12px` | Text size | +| `--sd-ui-menu-radius` | `0` | Border radius | +| `--sd-ui-menu-border` | inherits `--sd-ui-border` | Border | +| `--sd-ui-menu-shadow` | `0 0 0 1px rgba(0,0,0,0.05), 0 10px 20px rgba(0,0,0,0.1)` | Shadow | +| `--sd-ui-menu-input-border` | inherits `--sd-ui-border` | Search input border | +| `--sd-ui-menu-input-focus-border` | inherits `--sd-ui-action` | Search input focus | +| `--sd-ui-menu-item-hover-bg` | inherits `--sd-ui-hover-bg` | Item hover | +| `--sd-ui-menu-item-active-bg` | inherits `--sd-ui-active-bg` | Active item | +| `--sd-ui-menu-item-active-text` | inherits `--sd-ui-action` | Active item text | ### Tooltip -| Variable | Default | Description | -|----------|---------|-------------| -| `--sd-ui-tooltip-bg` | `#262626` | Tooltip background | -| `--sd-ui-tooltip-text` | `#ffffff` | Tooltip text | -| `--sd-ui-tooltip-radius` | `6px` | Tooltip border radius | -| `--sd-ui-tooltip-shadow` | `0 3px 12px rgba(0,0,0,0.28)` | Tooltip shadow | +| Variable | Default | Controls | +|----------|---------|----------| +| `--sd-ui-tooltip-bg` | `#262626` | Background (intentionally dark) | +| `--sd-ui-tooltip-text` | `#ffffff` | Text (intentionally light) | +| `--sd-ui-tooltip-radius` | `6px` | Border radius | +| `--sd-ui-tooltip-shadow` | `0 3px 12px rgba(0,0,0,0.28)` | Shadow | -### Comments panel +### Comments Card styling, typography, and input elements for the comments sidebar. -**Card appearance:** - -| Variable | Default | Description | -|----------|---------|-------------| -| `--sd-ui-comments-card-bg` | `#f3f6fd` | Default card background | -| `--sd-ui-comments-card-hover-bg` | `#f3f6fd` | Hovered card | -| `--sd-ui-comments-card-active-bg` | `#ffffff` | Selected/active card | -| `--sd-ui-comments-card-resolved-bg` | `#f0f0f0` | Resolved comment card | -| `--sd-ui-comments-card-active-border` | `gray-300` | Active card border | -| `--sd-ui-comments-card-radius` | `12px` | Card border radius | -| `--sd-ui-comments-card-padding` | `16px` | Card inner padding | -| `--sd-ui-comments-card-shadow` | complex | Card shadow | -| `--sd-ui-comments-transition` | `all 200ms ease` | Card transition | -| `--sd-ui-comments-separator` | `gray-300` | Divider between comments | - -**Typography:** - -| Variable | Default | Description | -|----------|---------|-------------| -| `--sd-ui-comments-author-text` | `gray-900` | Author name color | -| `--sd-ui-comments-author-size` | `14px` | Author name size | -| `--sd-ui-comments-author-weight` | `600` | Author name weight | -| `--sd-ui-comments-timestamp-text` | `gray-600` | Timestamp color | +**Card:** + +| Variable | Default | Controls | +|----------|---------|----------| +| `--sd-ui-comments-card-bg` | `#f3f6fd` | Default card | +| `--sd-ui-comments-card-hover-bg` | inherits card bg | Hovered card | +| `--sd-ui-comments-card-active-bg` | inherits `--sd-ui-bg` | Selected card | +| `--sd-ui-comments-card-resolved-bg` | inherits `--sd-ui-disabled-bg` | Resolved card | +| `--sd-ui-comments-card-active-border` | inherits `--sd-ui-border` | Selected card border | +| `--sd-ui-comments-card-radius` | `12px` | Card radius | +| `--sd-ui-comments-card-padding` | `16px` | Card padding | +| `--sd-ui-comments-card-shadow` | `0 4px 12px rgba(50,50,50,0.15)` | Card shadow | +| `--sd-ui-comments-transition` | `all 200ms ease` | Animation | +| `--sd-ui-comments-separator` | inherits `--sd-ui-border` | Divider line | + +**Text:** + +| Variable | Default | Controls | +|----------|---------|----------| +| `--sd-ui-comments-author-text` | inherits `--sd-ui-text` | Author name | +| `--sd-ui-comments-author-size` | `14px` | Author size | +| `--sd-ui-comments-author-weight` | `600` | Author weight | +| `--sd-ui-comments-timestamp-text` | inherits `--sd-ui-text-muted` | Timestamp | | `--sd-ui-comments-timestamp-size` | `12px` | Timestamp size | -| `--sd-ui-comments-body-text` | `gray-900` | Comment body text | -| `--sd-ui-comments-body-size` | `14px` | Comment body size | +| `--sd-ui-comments-body-text` | inherits `--sd-ui-text` | Body text | +| `--sd-ui-comments-body-size` | `14px` | Body size | -**Inputs:** +**Input:** -| Variable | Default | Description | -|----------|---------|-------------| -| `--sd-ui-comments-input-bg` | `#ffffff` | Comment input background | -| `--sd-ui-comments-input-border` | `gray-400` | Comment input border | +| Variable | Default | Controls | +|----------|---------|----------| +| `--sd-ui-comments-input-bg` | inherits `--sd-ui-bg` | Input background | +| `--sd-ui-comments-input-border` | inherits `--sd-ui-border` | Input border | -**Status indicators:** +**Status:** -| Variable | Default | Description | -|----------|---------|-------------| -| `--sd-ui-comments-resolved-text` | `green-500` | "Resolved" badge text | -| `--sd-ui-comments-insert-text` | `green-500` | Tracked change insert text | -| `--sd-ui-comments-delete-text` | `rose-500` | Tracked change delete text | +| Variable | Default | Controls | +|----------|---------|----------| +| `--sd-ui-comments-resolved-text` | `#00853d` | "Resolved" badge | +| `--sd-ui-comments-insert-text` | `#00853d` | Tracked change insert | +| `--sd-ui-comments-delete-text` | `#cb0e47` | Tracked change delete | -**Dropdown (filter/sort menu):** +**Filter dropdown:** -| Variable | Default | Description | -|----------|---------|-------------| -| `--sd-ui-comments-dropdown-bg` | `#ffffff` | Dropdown background | -| `--sd-ui-comments-dropdown-border` | `gray-400` | Dropdown border | -| `--sd-ui-comments-dropdown-shadow` | complex | Dropdown shadow | -| `--sd-ui-comments-option-text` | body text | Option text | -| `--sd-ui-comments-option-hover-text` | option text | Hovered option text | -| `--sd-ui-comments-option-hover-bg` | `#f3f3f5` | Hovered option background | -| `--sd-ui-comments-option-size` | `14px` | Option text size | +| Variable | Default | Controls | +|----------|---------|----------| +| `--sd-ui-comments-dropdown-bg` | inherits `--sd-ui-bg` | Dropdown background | +| `--sd-ui-comments-dropdown-border` | inherits `--sd-ui-border` | Dropdown border | +| `--sd-ui-comments-dropdown-shadow` | `0 8px 24px rgba(0,0,0,0.12)` | Dropdown shadow | +| `--sd-ui-comments-option-text` | inherits body text | Option text | +| `--sd-ui-comments-option-hover-text` | inherits option text | Hovered option | +| `--sd-ui-comments-option-hover-bg` | inherits `--sd-ui-hover-bg` | Option hover | +| `--sd-ui-comments-option-size` | `14px` | Option size | -**Visibility toggle (internal/external):** +**Visibility badges:** -| Variable | Default | Description | -|----------|---------|-------------| -| `--sd-ui-comments-internal-bg` | `#cde6e6` | Internal badge background | -| `--sd-ui-comments-external-bg` | `#f5cfda` | External badge background | +| Variable | Default | Controls | +|----------|---------|----------| +| `--sd-ui-comments-internal-bg` | `#cde6e6` | Internal badge | +| `--sd-ui-comments-external-bg` | `#f5cfda` | External badge | ### Comment highlights (in-document) -Colors applied to highlighted text in the document when comments are present. - -| Variable | Default | Description | -|----------|---------|-------------| -| `--sd-comments-highlight-external` | `#b1124b40` | External comment highlight | -| `--sd-comments-highlight-external-active` | `#b1124b66` | Active external highlight | -| `--sd-comments-highlight-external-faded` | `#b1124b20` | Faded external highlight | -| `--sd-comments-highlight-external-nested-border` | `#b1124b99` | Nested comment left border | -| `--sd-comments-highlight-internal` | `#07838340` | Internal comment highlight | -| `--sd-comments-highlight-internal-active` | `#07838366` | Active internal highlight | -| `--sd-comments-highlight-internal-faded` | `#07838320` | Faded internal highlight | -| `--sd-comments-highlight-internal-nested-border` | `#07838399` | Nested comment left border | -| `--sd-comments-highlight-hover` | `#1354ff55` | Hovered comment highlight | -| `--sd-comments-selection-background` | `#1354ff55` | Selected text highlight | +Colors applied to highlighted text when comments are present. These are document-level, not UI chrome. + +| Variable | Default | Controls | +|----------|---------|----------| +| `--sd-comments-highlight-external` | `#b1124b40` | External highlight | +| `--sd-comments-highlight-external-active` | `#b1124b66` | Active external | +| `--sd-comments-highlight-external-faded` | `#b1124b20` | Faded external | +| `--sd-comments-highlight-external-nested-border` | `#b1124b99` | Nested left border | +| `--sd-comments-highlight-internal` | `#07838340` | Internal highlight | +| `--sd-comments-highlight-internal-active` | `#07838366` | Active internal | +| `--sd-comments-highlight-internal-faded` | `#07838320` | Faded internal | +| `--sd-comments-highlight-internal-nested-border` | `#07838399` | Nested left border | +| `--sd-comments-highlight-hover` | `#1354ff55` | Hover highlight | +| `--sd-comments-selection-background` | `#1354ff55` | Selection highlight | ### Tracked changes -Colors for insert, delete, and format change decorations in the document. +Insert, delete, and format change decorations in the document. -| Variable | Default | Description | -|----------|---------|-------------| +| Variable | Default | Controls | +|----------|---------|----------| | `--sd-tracked-changes-insert-background` | `#399c7222` | Insert highlight | | `--sd-tracked-changes-insert-border` | `#00853d` | Insert left border | | `--sd-tracked-changes-delete-background` | `#cb0e4722` | Delete highlight | @@ -247,16 +241,16 @@ Colors for insert, delete, and format change decorations in the document. | `--sd-tracked-changes-delete-background-focused` | `#cb0e4744` | Focused delete | | `--sd-tracked-changes-format-background-focused` | `#ffd70033` | Focused format change | -### Content controls (structured content) +### Content controls -Styling for DOCX content controls (SDTs) — form fields, dropdowns, date pickers. +DOCX content controls (SDTs) — form fields, dropdowns, date pickers. -| Variable | Default | Description | -|----------|---------|-------------| +| Variable | Default | Controls | +|----------|---------|----------| | `--sd-content-controls-block-border` | `#629be7` | Block control border | -| `--sd-content-controls-block-hover-bg` | `#f2f2f2` | Block control hover | +| `--sd-content-controls-block-hover-bg` | inherits `--sd-ui-hover-bg` | Block hover | | `--sd-content-controls-inline-border` | `#629be7` | Inline control border | -| `--sd-content-controls-inline-hover-bg` | `#f2f2f2` | Inline control hover | +| `--sd-content-controls-inline-hover-bg` | inherits `--sd-ui-hover-bg` | Inline hover | | `--sd-content-controls-label-border` | `#629be7` | Label border | | `--sd-content-controls-label-bg` | `#629be7ee` | Label background | | `--sd-content-controls-label-text` | `#ffffff` | Label text | @@ -264,29 +258,29 @@ Styling for DOCX content controls (SDTs) — form fields, dropdowns, date picker ### Layout -Page-level appearance in presentation/layout mode. +Page appearance in presentation mode. -| Variable | Default | Description | -|----------|---------|-------------| -| `--sd-layout-page-bg` | `#ffffff` | Page background | +| Variable | Default | Controls | +|----------|---------|----------| +| `--sd-layout-page-bg` | inherits `--sd-ui-bg` | Page background | | `--sd-layout-page-shadow` | `0 4px 20px rgba(15,23,42,0.08)` | Page shadow | -### Tools (floating action toolbar) +### Tools -The floating tools toolbar that appears for AI/insert actions. +The floating action toolbar for AI and insert actions. -| Variable | Default | Description | -|----------|---------|-------------| -| `--sd-ui-tools-gap` | `6px` | Gap between tool items | -| `--sd-ui-tools-item-size` | `50px` | Tool button size | -| `--sd-ui-tools-item-radius` | `12px` | Tool button radius | -| `--sd-ui-tools-item-bg` | `rgba(219,219,219,0.6)` | Tool button background | -| `--sd-ui-tools-icon-size` | `20px` | Icon size within buttons | +| Variable | Default | Controls | +|----------|---------|----------| +| `--sd-ui-tools-gap` | `6px` | Gap between items | +| `--sd-ui-tools-item-size` | `50px` | Button size | +| `--sd-ui-tools-item-radius` | `12px` | Button radius | +| `--sd-ui-tools-item-bg` | `rgba(219,219,219,0.6)` | Button background | +| `--sd-ui-tools-icon-size` | `20px` | Icon size | ## Source files -The canonical token definitions live in your `node_modules/superdoc/` package: +Token definitions live in your `node_modules/superdoc/` package: -- **Default values:** `src/assets/styles/helpers/variables.css` -- **Preset themes:** `src/assets/styles/helpers/themes.css` -- **Backward-compat aliases:** `src/assets/styles/helpers/compat.css` +- `src/assets/styles/helpers/variables.css` — defaults +- `src/assets/styles/helpers/themes.css` — preset themes +- `src/assets/styles/helpers/compat.css` — backward-compat aliases diff --git a/apps/docs/guides/migration/css-variables.mdx b/apps/docs/guides/migration/css-variables.mdx index 6a88d8af8f..4f7f041ef7 100644 --- a/apps/docs/guides/migration/css-variables.mdx +++ b/apps/docs/guides/migration/css-variables.mdx @@ -4,31 +4,31 @@ sidebarTitle: CSS variables keywords: "migration, css variables, renamed variables, breaking changes, backward compatibility, tokens" --- -SuperDoc's CSS variables were restructured into a three-tier token system (primitives, UI semantic, component-specific). If you're upgrading from a version that used the old flat variable names, this page maps every old name to its new equivalent. +SuperDoc's CSS variables were restructured into a three-tier token system. This page maps every old name to its new equivalent. -**Backward compatibility is included.** The old variable names still work via aliases in `compat.css`, which is loaded automatically. You don't need to change your code immediately — but new code should use the new names. +**Old names still work.** Aliases in `compat.css` (loaded automatically) point old names to new ones. You don't need to change your code immediately. ## What changed -The old system used flat, inconsistent names (`--sd-comment-bg`, `--sd-track-insert-border`, `--sd-surface-card`). The new system uses a structured hierarchy: +The old system used flat names (`--sd-comment-bg`, `--sd-track-insert-border`). The new system uses a structured pattern: ``` +--sd-{scope}-{component}-{element?}-{state?}-{property} + Old: --sd-comment-bg New: --sd-ui-comments-card-bg - ───── ──────────── ──────────────────── - tier component property ``` -The JavaScript config API (`trackChangesConfig`, `commentsConfig`) is unchanged. Only CSS variable names were restructured. +The JavaScript config API is unchanged. Only CSS variable names moved. ## Full mapping -### Semantic surfaces +### Surfaces -| Old variable | New variable | -|-------------|-------------| +| Old | New | +|-----|-----| | `--sd-surface-page` | `--sd-ui-bg` | | `--sd-surface-canvas` | `--sd-color-gray-50` | | `--sd-surface-card` | `--sd-ui-bg` | @@ -36,48 +36,43 @@ The JavaScript config API (`trackChangesConfig`, `commentsConfig`) is unchanged. | `--sd-surface-hover` | `--sd-ui-hover-bg` | | `--sd-surface-selected` | `--sd-color-blue-100` | -### Semantic text +### Text -| Old variable | New variable | -|-------------|-------------| +| Old | New | +|-----|-----| | `--sd-text-primary` | `--sd-ui-text` | | `--sd-text-secondary` | `--sd-color-gray-700` | | `--sd-text-muted` | `--sd-ui-text-muted` | | `--sd-text-placeholder` | `--sd-ui-text-disabled` | -### Semantic borders +### Borders -| Old variable | New variable | -|-------------|-------------| +| Old | New | +|-----|-----| | `--sd-border-default` | `--sd-ui-border` | | `--sd-border-subtle` | `--sd-ui-comments-separator` | | `--sd-border-focus` | `--sd-ui-action` | -### Semantic actions +### Actions -| Old variable | New variable | -|-------------|-------------| +| Old | New | +|-----|-----| | `--sd-action-primary` | `--sd-ui-action` | | `--sd-action-primary-hover` | `--sd-ui-action-hover` | -### Typography +### Typography and radius -| Old variable | New variable | -|-------------|-------------| +| Old | New | +|-----|-----| | `--sd-font-family` | `--sd-ui-font-family` | - -### Radius - -| Old variable | New variable | -|-------------|-------------| | `--sd-radius-sm` | `--sd-radius-50` | | `--sd-radius-md` | `--sd-radius-200` | | `--sd-radius-lg` | `--sd-radius-300` | -### Comment dialog +### Comment card -| Old variable | New variable | -|-------------|-------------| +| Old | New | +|-----|-----| | `--sd-comment-bg` | `--sd-ui-comments-card-bg` | | `--sd-comment-bg-hover` | `--sd-ui-comments-card-hover-bg` | | `--sd-comment-bg-active` | `--sd-ui-comments-card-active-bg` | @@ -89,90 +84,79 @@ The JavaScript config API (`trackChangesConfig`, `commentsConfig`) is unchanged. | `--sd-comment-separator` | `--sd-ui-comments-separator` | | `--sd-comment-transition` | `--sd-ui-comments-transition` | -### Comment typography +### Comment text -| Old variable | New variable | -|-------------|-------------| +| Old | New | +|-----|-----| | `--sd-comment-author-color` | `--sd-ui-comments-author-text` | | `--sd-comment-author-size` | `--sd-ui-comments-author-size` | | `--sd-comment-author-weight` | `--sd-ui-comments-author-weight` | | `--sd-comment-time-color` | `--sd-ui-comments-timestamp-text` | | `--sd-comment-time-size` | `--sd-ui-comments-timestamp-size` | | `--sd-comment-body-size` | `--sd-ui-comments-body-size` | - -### Comment tracked change colors - -| Old variable | New variable | -|-------------|-------------| | `--sd-comment-tc-insert-color` | `--sd-ui-comments-insert-text` | | `--sd-comment-tc-delete-color` | `--sd-ui-comments-delete-text` | ### Comment highlights -| Old variable | New variable | -|-------------|-------------| +| Old | New | +|-----|-----| | `--sd-comment-highlight-internal` | `--sd-comments-highlight-internal` | | `--sd-comment-highlight-external` | `--sd-comments-highlight-external` | +| `--sd-comment-internal-bg` | `--sd-ui-comments-internal-bg` | +| `--sd-comment-external-bg` | `--sd-ui-comments-external-bg` | -`--sd-comment-highlight-opacity` and `--sd-comment-highlight-opacity-active` were removed. The new system uses pre-computed RGBA colors (e.g., `--sd-comments-highlight-external: #b1124b40`) instead of separate base color + opacity. Override the full RGBA value directly. +`--sd-comment-highlight-opacity` and `--sd-comment-highlight-opacity-active` were removed. The new system uses pre-computed RGBA colors (`--sd-comments-highlight-external: #b1124b40`). Override the full value directly. -### Comment visibility toggle - -| Old variable | New variable | -|-------------|-------------| -| `--sd-comment-internal-bg` | `--sd-ui-comments-internal-bg` | -| `--sd-comment-external-bg` | `--sd-ui-comments-external-bg` | - ### Tracked changes -| Old variable | New variable | -|-------------|-------------| +| Old | New | +|-----|-----| | `--sd-track-insert-border` | `--sd-tracked-changes-insert-border` | | `--sd-track-insert-bg` | `--sd-tracked-changes-insert-background` | | `--sd-track-delete-border` | `--sd-tracked-changes-delete-border` | | `--sd-track-delete-bg` | `--sd-tracked-changes-delete-background` | | `--sd-track-format-border` | `--sd-tracked-changes-format-border` | -### Removed variables +### Removed -These variables were removed with no direct replacement: +These have no replacement: -| Removed variable | Reason | -|-----------------|--------| +| Variable | Why | +|----------|-----| | `--sd-comment-highlight-opacity` | Replaced by pre-computed RGBA values | | `--sd-comment-highlight-opacity-active` | Replaced by pre-computed RGBA values | -| `--sd-comment-max-width` | Sizing is now managed internally | -| `--sd-comment-min-width` | Sizing is now managed internally | +| `--sd-comment-max-width` | Managed internally | +| `--sd-comment-min-width` | Managed internally | | `--sd-comment-group-bg` | Group bubble styling removed | | `--sd-comment-group-color` | Group bubble styling removed | | `--sd-comment-group-size` | Group bubble styling removed | | `--sd-comment-group-expanded-size` | Group bubble styling removed | -| `--sd-font-mono` | No longer exposed as a token | +| `--sd-font-mono` | No longer exposed | ## How backward compatibility works -The `compat.css` file defines aliases that point old names to new ones: +`compat.css` defines aliases from old names to new: ```css -/* Loaded automatically — you don't need to import this */ +/* Loaded automatically */ :root { --sd-comment-bg: var(--sd-ui-comments-card-bg); --sd-track-insert-border: var(--sd-tracked-changes-insert-border); - /* ... etc */ + /* ... */ } ``` -If you have existing CSS like: +Your existing overrides keep working because your selector is more specific than `:root`: + ```css .my-overrides { - --sd-comment-bg: #f0f0f0; + --sd-comment-bg: #f0f0f0; /* still works */ } ``` -It will continue to work. The alias in `:root` is overridden by your more-specific selector. - -The compat aliases will be removed in the next major version. Plan to migrate your CSS to the new variable names when convenient. +Compat aliases will be removed in the next major version. Migrate to the new names when convenient. From 31d0039d1d9199c6c842611c0247178dc3ea6349 Mon Sep 17 00:00:00 2001 From: Caio Pizzol Date: Wed, 18 Mar 2026 12:32:37 -0300 Subject: [PATCH 05/11] fix: add tokens to link popover and comment action buttons - LinkInput: title text, input icon, high-contrast mode, and submit button now use --sd-ui-* tokens instead of hardcoded colors - CommentDialog: reply button text and cancel button use tokens --- .../super-editor/src/components/toolbar/LinkInput.vue | 11 ++++++----- .../src/components/CommentsLayer/CommentDialog.vue | 6 +++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/super-editor/src/components/toolbar/LinkInput.vue b/packages/super-editor/src/components/toolbar/LinkInput.vue index a46a88a773..b0a2085b0c 100644 --- a/packages/super-editor/src/components/toolbar/LinkInput.vue +++ b/packages/super-editor/src/components/toolbar/LinkInput.vue @@ -340,7 +340,7 @@ const handleRemove = () => { position: absolute; left: 25px; width: auto; - color: #999; + color: var(--sd-ui-text-disabled, #ababab); pointer-events: none; } @@ -351,12 +351,12 @@ const handleRemove = () => { &.high-contrast { .input-icon { - color: #000; + color: var(--sd-ui-text, #47484a); } .input-row input { - color: #000; - border-color: #000; + color: var(--sd-ui-text, #47484a); + border-color: var(--sd-ui-text, #47484a); } } } @@ -427,6 +427,7 @@ const handleRemove = () => { .link-title { font-size: var(--sd-ui-font-size-400, 14px); font-weight: 600; + color: var(--sd-ui-text, #47484a); margin-bottom: 10px; } @@ -464,7 +465,7 @@ const handleRemove = () => { outline: none; border: none; background-color: var(--sd-ui-action, #1355ff); - color: white; + color: var(--sd-ui-bg, #ffffff); font-weight: 400; font-size: var(--sd-ui-font-size-300, 13px); cursor: pointer; diff --git a/packages/superdoc/src/components/CommentsLayer/CommentDialog.vue b/packages/superdoc/src/components/CommentsLayer/CommentDialog.vue index 8be3d58145..95e752fd6d 100644 --- a/packages/superdoc/src/components/CommentsLayer/CommentDialog.vue +++ b/packages/superdoc/src/components/CommentsLayer/CommentDialog.vue @@ -976,21 +976,21 @@ watch(editingCommentId, (commentId) => { border: none; font-size: 13px; font-weight: 500; - color: var(--sd-color-gray-700, #666666); + color: var(--sd-ui-text-muted, #666666); cursor: pointer; padding: 0; font-family: inherit; transition: color 150ms; } .reply-btn-cancel:hover { - color: var(--sd-color-gray-900, #212121); + color: var(--sd-ui-text, #212121); } .reply-btn-primary { background: var(--sd-ui-action, #1355ff); border: none; font-size: 13px; font-weight: 600; - color: #ffffff; + color: var(--sd-ui-bg, #ffffff); cursor: pointer; padding: 6px 16px; border-radius: 9999px; From 07567a697514231271f46e43028fd5a6e2c8f8e1 Mon Sep 17 00:00:00 2001 From: Caio Pizzol Date: Wed, 18 Mar 2026 12:45:20 -0300 Subject: [PATCH 06/11] test: add tests for cssToken helper and compat alias integrity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Extract cssToken() to css-token.ts for testability - Test that cssToken builds correct var() strings with synced fallbacks - Test that every compat alias points to a variable defined in variables.css - Test that compat doesn't re-declare any variable from variables.css - Test key old→new mappings (comment-bg, surface-card, track-insert, etc.) --- .../painters/dom/src/css-token.test.ts | 25 +++++++ .../painters/dom/src/css-token.ts | 14 ++++ .../painters/dom/src/renderer.ts | 11 +-- .../src/assets/styles/helpers/compat.test.ts | 73 +++++++++++++++++++ 4 files changed, 115 insertions(+), 8 deletions(-) create mode 100644 packages/layout-engine/painters/dom/src/css-token.test.ts create mode 100644 packages/layout-engine/painters/dom/src/css-token.ts create mode 100644 packages/superdoc/src/assets/styles/helpers/compat.test.ts diff --git a/packages/layout-engine/painters/dom/src/css-token.test.ts b/packages/layout-engine/painters/dom/src/css-token.test.ts new file mode 100644 index 0000000000..4107087363 --- /dev/null +++ b/packages/layout-engine/painters/dom/src/css-token.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, it } from 'vitest'; +import { cssToken } from './css-token.js'; + +describe('cssToken', () => { + it('builds a var() string with the fallback embedded', () => { + const token = cssToken('--sd-ui-bg', '#ffffff'); + expect(token.css).toBe('var(--sd-ui-bg, #ffffff)'); + }); + + it('stores the fallback separately for jsdom environments', () => { + const token = cssToken('--sd-ui-bg', '#ffffff'); + expect(token.fallback).toBe('#ffffff'); + }); + + it('keeps css and fallback in sync', () => { + const token = cssToken('--sd-comments-highlight-external', '#B1124B40'); + expect(token.css).toContain(token.fallback); + }); + + it('works with rgba values', () => { + const token = cssToken('--sd-ui-tools-item-bg', 'rgba(219, 219, 219, 0.6)'); + expect(token.css).toBe('var(--sd-ui-tools-item-bg, rgba(219, 219, 219, 0.6))'); + expect(token.fallback).toBe('rgba(219, 219, 219, 0.6)'); + }); +}); diff --git a/packages/layout-engine/painters/dom/src/css-token.ts b/packages/layout-engine/painters/dom/src/css-token.ts new file mode 100644 index 0000000000..5d15e3ba4a --- /dev/null +++ b/packages/layout-engine/painters/dom/src/css-token.ts @@ -0,0 +1,14 @@ +export type CssToken = { + css: string; + fallback: string; +}; + +/** + * Creates a CSS variable token with a fallback value. + * The `css` string is used in real browsers (supports var()). + * The `fallback` is used in environments that don't (e.g., jsdom in tests). + */ +export const cssToken = (varName: string, fallback: string): CssToken => ({ + css: `var(${varName}, ${fallback})`, + fallback, +}); diff --git a/packages/layout-engine/painters/dom/src/renderer.ts b/packages/layout-engine/painters/dom/src/renderer.ts index 4e11df7718..a589982cd7 100644 --- a/packages/layout-engine/painters/dom/src/renderer.ts +++ b/packages/layout-engine/painters/dom/src/renderer.ts @@ -591,15 +591,10 @@ const LIST_MARKER_GAP = 8; const DEFAULT_PAGE_HEIGHT_PX = 1056; /** Default gap used when virtualization is enabled (kept in sync with PresentationEditor layout defaults). */ const DEFAULT_VIRTUALIZED_PAGE_GAP = 72; -type CommentHighlightToken = { - css: string; - fallback: string; -}; +import { cssToken } from './css-token.js'; +import type { CssToken } from './css-token.js'; -const cssToken = (varName: string, fallback: string): CommentHighlightToken => ({ - css: `var(${varName}, ${fallback})`, - fallback, -}); +type CommentHighlightToken = CssToken; const COMMENT_HIGHLIGHT_EXTERNAL = cssToken('--sd-comments-highlight-external', '#B1124B40'); const COMMENT_HIGHLIGHT_EXTERNAL_ACTIVE = cssToken('--sd-comments-highlight-external-active', '#B1124B66'); diff --git a/packages/superdoc/src/assets/styles/helpers/compat.test.ts b/packages/superdoc/src/assets/styles/helpers/compat.test.ts new file mode 100644 index 0000000000..042e7d45a6 --- /dev/null +++ b/packages/superdoc/src/assets/styles/helpers/compat.test.ts @@ -0,0 +1,73 @@ +import { describe, expect, it } from 'vitest'; +import { readFileSync } from 'node:fs'; +import { resolve, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const variablesCss = readFileSync(resolve(__dirname, 'variables.css'), 'utf-8'); +const compatCss = readFileSync(resolve(__dirname, 'compat.css'), 'utf-8'); + +/** Extract all --sd-* variable declarations from a CSS string. */ +const extractDeclaredVars = (css: string): Set => { + const vars = new Set(); + for (const match of css.matchAll(/(--sd-[\w-]+)\s*:/g)) { + vars.add(match[1]); + } + return vars; +}; + +/** Extract all var(--sd-*) references from a CSS string. */ +const extractReferencedVars = (css: string): Set => { + const vars = new Set(); + for (const match of css.matchAll(/var\((--sd-[\w-]+)/g)) { + vars.add(match[1]); + } + return vars; +}; + +describe('compat.css backward-compatibility aliases', () => { + const declaredInVariables = extractDeclaredVars(variablesCss); + const declaredInCompat = extractDeclaredVars(compatCss); + const referencedByCompat = extractReferencedVars(compatCss); + + it('every compat alias points to a variable defined in variables.css', () => { + const broken: string[] = []; + for (const ref of referencedByCompat) { + if (!declaredInVariables.has(ref)) { + broken.push(ref); + } + } + expect(broken, `Compat aliases reference undefined variables: ${broken.join(', ')}`).toEqual([]); + }); + + it('compat does not re-declare any variable from variables.css', () => { + const collisions: string[] = []; + for (const name of declaredInCompat) { + if (declaredInVariables.has(name)) { + collisions.push(name); + } + } + expect(collisions, `Compat re-declares variables from variables.css: ${collisions.join(', ')}`).toEqual([]); + }); + + it('maps key old names to the expected new names', () => { + const expected: Record = { + '--sd-comment-bg': '--sd-ui-comments-card-bg', + '--sd-surface-card': '--sd-ui-bg', + '--sd-action-primary': '--sd-ui-action', + '--sd-border-default': '--sd-ui-border', + '--sd-track-insert-border': '--sd-tracked-changes-insert-border', + '--sd-track-delete-bg': '--sd-tracked-changes-delete-background', + '--sd-comment-highlight-internal': '--sd-comments-highlight-internal', + '--sd-text-primary': '--sd-ui-text', + '--sd-radius-sm': '--sd-radius-50', + }; + + for (const [oldName, newName] of Object.entries(expected)) { + const pattern = new RegExp( + `${oldName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*:\\s*var\\(${newName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, + ); + expect(compatCss, `Expected ${oldName} → ${newName}`).toMatch(pattern); + } + }); +}); From 6a4c717618b1401387dd6f2ce606f54b75ba6d41 Mon Sep 17 00:00:00 2001 From: Caio Pizzol Date: Wed, 18 Mar 2026 12:46:52 -0300 Subject: [PATCH 07/11] fix(docs): theme class must be on , not any wrapper element Some SuperDoc elements (popovers, dropdowns) are appended to , so they only inherit CSS variables from . --- apps/docs/getting-started/theming.mdx | 4 ++-- apps/docs/guides/general/custom-themes.mdx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/docs/getting-started/theming.mdx b/apps/docs/getting-started/theming.mdx index e14c3e4b80..3a46855e8e 100644 --- a/apps/docs/getting-started/theming.mdx +++ b/apps/docs/getting-started/theming.mdx @@ -22,11 +22,11 @@ Change five CSS variables and your brand colors flow through every SuperDoc comp ``` -Every `--sd-*` CSS custom property can be overridden on any parent element. The changes cascade down to all child components. +Override any `--sd-*` CSS custom property on the `` element and the changes cascade to all components. ## Preset themes -Three preset themes ship out of the box. Add the class to `` or any wrapper element. +Three preset themes ship out of the box. Add the class to `` — some SuperDoc elements (popovers, dropdowns) are appended to ``, so they need to inherit from `` to pick up the theme. diff --git a/apps/docs/guides/general/custom-themes.mdx b/apps/docs/guides/general/custom-themes.mdx index 893e33a48e..44dc054efa 100644 --- a/apps/docs/guides/general/custom-themes.mdx +++ b/apps/docs/guides/general/custom-themes.mdx @@ -8,7 +8,7 @@ Every CSS variable you can override, organized by component. If you haven't set ## Creating a theme -Define a CSS class with your overrides. Apply it to any ancestor element. +Define a CSS class with your overrides. Apply it to the `` element. ```css .my-company-theme { From e7fc58f78d0eec588cceebe278280434b9d1ecf0 Mon Sep 17 00:00:00 2001 From: Caio Pizzol Date: Wed, 18 Mar 2026 12:51:30 -0300 Subject: [PATCH 08/11] fix: make old CSS variable overrides actually work (two-way compat) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The compat aliases were one-way: setting --sd-comment-bg on a wrapper had no effect because components read --sd-ui-comments-card-bg directly. Fix: new tokens in variables.css now fall back to old names via var(): --sd-ui-comments-card-bg: var(--sd-comment-bg, #f3f6fd); CSS resolves var() at computed-value time, so customer overrides of old names propagate automatically. Setting the new name still works too — it overrides the whole expression. compat.css is trimmed to only aliases where the old name maps to a different concept (no direct fallback possible). All 1:1 renames are handled by the fallbacks in variables.css. --- .../src/assets/styles/helpers/compat.css | 73 +--------- .../src/assets/styles/helpers/compat.test.ts | 128 +++++++++++++----- .../src/assets/styles/helpers/variables.css | 100 +++++++------- 3 files changed, 153 insertions(+), 148 deletions(-) diff --git a/packages/superdoc/src/assets/styles/helpers/compat.css b/packages/superdoc/src/assets/styles/helpers/compat.css index 17420ddca6..c6107d1729 100644 --- a/packages/superdoc/src/assets/styles/helpers/compat.css +++ b/packages/superdoc/src/assets/styles/helpers/compat.css @@ -1,81 +1,22 @@ /* * Backward-compatible aliases for renamed CSS variables. * - * SuperDoc's theming system was restructured to use a three-tier token - * hierarchy (primitive → UI → component). The old flat variable names - * still work via these aliases, but new code should use the new names. + * Most old names are honored via var() fallbacks in variables.css — if a + * customer sets --sd-comment-bg, the new --sd-ui-comments-card-bg picks it up + * automatically. This file only covers aliases where the old name maps to a + * different concept (no direct 1:1 fallback possible). * * These aliases will be removed in the next major version. */ :root { - /* ─── Semantic surfaces → UI tokens ─── */ - --sd-surface-page: var(--sd-ui-bg); + /* Old semantic names that map to primitives (no new-token equivalent) */ --sd-surface-canvas: var(--sd-color-gray-50); - --sd-surface-card: var(--sd-ui-bg); - --sd-surface-muted: var(--sd-ui-disabled-bg); - --sd-surface-hover: var(--sd-ui-hover-bg); --sd-surface-selected: var(--sd-color-blue-100); - - /* ─── Semantic text → UI tokens ─── */ - --sd-text-primary: var(--sd-ui-text); --sd-text-secondary: var(--sd-color-gray-700); - --sd-text-muted: var(--sd-ui-text-muted); - --sd-text-placeholder: var(--sd-ui-text-disabled); - /* ─── Semantic borders → UI tokens ─── */ - --sd-border-default: var(--sd-ui-border); + /* Old names that map to a different concept in the new system */ + --sd-surface-page: var(--sd-ui-bg); --sd-border-subtle: var(--sd-ui-comments-separator); --sd-border-focus: var(--sd-ui-action); - - /* ─── Semantic actions → UI tokens ─── */ - --sd-action-primary: var(--sd-ui-action); - --sd-action-primary-hover: var(--sd-ui-action-hover); - - /* ─── Typography ─── */ - --sd-font-family: var(--sd-ui-font-family); - - /* ─── Radius ─── */ - --sd-radius-sm: var(--sd-radius-50); - --sd-radius-md: var(--sd-radius-200); - --sd-radius-lg: var(--sd-radius-300); - - /* ─── Comment dialog ─── */ - --sd-comment-bg: var(--sd-ui-comments-card-bg); - --sd-comment-bg-hover: var(--sd-ui-comments-card-hover-bg); - --sd-comment-bg-active: var(--sd-ui-comments-card-active-bg); - --sd-comment-bg-resolved: var(--sd-ui-comments-card-resolved-bg); - --sd-comment-border-active: var(--sd-ui-comments-card-active-border); - --sd-comment-radius: var(--sd-ui-comments-card-radius); - --sd-comment-padding: var(--sd-ui-comments-card-padding); - --sd-comment-shadow: var(--sd-ui-comments-card-shadow); - --sd-comment-separator: var(--sd-ui-comments-separator); - --sd-comment-transition: var(--sd-ui-comments-transition); - - /* Comment: author & timestamp */ - --sd-comment-author-color: var(--sd-ui-comments-author-text); - --sd-comment-author-size: var(--sd-ui-comments-author-size); - --sd-comment-author-weight: var(--sd-ui-comments-author-weight); - --sd-comment-time-color: var(--sd-ui-comments-timestamp-text); - --sd-comment-time-size: var(--sd-ui-comments-timestamp-size); - --sd-comment-body-size: var(--sd-ui-comments-body-size); - - /* Comment: tracked change text colors */ - --sd-comment-tc-insert-color: var(--sd-ui-comments-insert-text); - --sd-comment-tc-delete-color: var(--sd-ui-comments-delete-text); - - /* Comment: document highlights */ - --sd-comment-highlight-internal: var(--sd-comments-highlight-internal); - --sd-comment-highlight-external: var(--sd-comments-highlight-external); - - /* Comment: internal/external toggle */ - --sd-comment-internal-bg: var(--sd-ui-comments-internal-bg); - --sd-comment-external-bg: var(--sd-ui-comments-external-bg); - - /* ─── Tracked changes (old short names) ─── */ - --sd-track-insert-border: var(--sd-tracked-changes-insert-border); - --sd-track-insert-bg: var(--sd-tracked-changes-insert-background); - --sd-track-delete-border: var(--sd-tracked-changes-delete-border); - --sd-track-delete-bg: var(--sd-tracked-changes-delete-background); - --sd-track-format-border: var(--sd-tracked-changes-format-border); } diff --git a/packages/superdoc/src/assets/styles/helpers/compat.test.ts b/packages/superdoc/src/assets/styles/helpers/compat.test.ts index 042e7d45a6..46238e5163 100644 --- a/packages/superdoc/src/assets/styles/helpers/compat.test.ts +++ b/packages/superdoc/src/assets/styles/helpers/compat.test.ts @@ -25,49 +25,109 @@ const extractReferencedVars = (css: string): Set => { return vars; }; -describe('compat.css backward-compatibility aliases', () => { +describe('backward compatibility', () => { const declaredInVariables = extractDeclaredVars(variablesCss); const declaredInCompat = extractDeclaredVars(compatCss); const referencedByCompat = extractReferencedVars(compatCss); + const referencedByVariables = extractReferencedVars(variablesCss); - it('every compat alias points to a variable defined in variables.css', () => { - const broken: string[] = []; - for (const ref of referencedByCompat) { - if (!declaredInVariables.has(ref)) { - broken.push(ref); + describe('compat.css aliases', () => { + it('every alias points to a variable defined in variables.css', () => { + const broken: string[] = []; + for (const ref of referencedByCompat) { + if (!declaredInVariables.has(ref)) { + broken.push(ref); + } } - } - expect(broken, `Compat aliases reference undefined variables: ${broken.join(', ')}`).toEqual([]); - }); + expect(broken, `Compat aliases reference undefined variables: ${broken.join(', ')}`).toEqual([]); + }); + + it('does not re-declare any variable from variables.css', () => { + const collisions: string[] = []; + for (const name of declaredInCompat) { + if (declaredInVariables.has(name)) { + collisions.push(name); + } + } + expect(collisions, `Compat re-declares variables from variables.css: ${collisions.join(', ')}`).toEqual([]); + }); - it('compat does not re-declare any variable from variables.css', () => { - const collisions: string[] = []; - for (const name of declaredInCompat) { - if (declaredInVariables.has(name)) { - collisions.push(name); + it('has no circular references with variables.css', () => { + // A circular reference: compat declares --old pointing to --new, + // AND variables.css declares --new pointing back to --old. + const circular: string[] = []; + for (const compatVar of declaredInCompat) { + // Find what this compat alias points to + const match = compatCss.match( + new RegExp(`${compatVar.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*:\\s*var\\((--sd-[\\w-]+)`), + ); + if (!match) continue; + const target = match[1]; + // Check if variables.css references the compat var name + const targetDecl = variablesCss.match( + new RegExp( + `${target.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*:[^;]*var\\(${compatVar.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, + ), + ); + if (targetDecl) { + circular.push(`${compatVar} ↔ ${target}`); + } } - } - expect(collisions, `Compat re-declares variables from variables.css: ${collisions.join(', ')}`).toEqual([]); + expect(circular, `Circular references found: ${circular.join(', ')}`).toEqual([]); + }); }); - it('maps key old names to the expected new names', () => { - const expected: Record = { - '--sd-comment-bg': '--sd-ui-comments-card-bg', - '--sd-surface-card': '--sd-ui-bg', - '--sd-action-primary': '--sd-ui-action', - '--sd-border-default': '--sd-ui-border', - '--sd-track-insert-border': '--sd-tracked-changes-insert-border', - '--sd-track-delete-bg': '--sd-tracked-changes-delete-background', - '--sd-comment-highlight-internal': '--sd-comments-highlight-internal', - '--sd-text-primary': '--sd-ui-text', - '--sd-radius-sm': '--sd-radius-50', - }; + describe('old-name fallbacks in variables.css', () => { + it('honors old comment variable names', () => { + const expected: [string, string][] = [ + ['--sd-ui-comments-card-bg', '--sd-comment-bg'], + ['--sd-ui-comments-card-hover-bg', '--sd-comment-bg-hover'], + ['--sd-ui-comments-card-active-bg', '--sd-comment-bg-active'], + ['--sd-ui-comments-separator', '--sd-comment-separator'], + ['--sd-ui-comments-author-text', '--sd-comment-author-color'], + ['--sd-ui-comments-timestamp-text', '--sd-comment-time-color'], + ['--sd-ui-comments-internal-bg', '--sd-comment-internal-bg'], + ['--sd-ui-comments-external-bg', '--sd-comment-external-bg'], + ]; + for (const [newName, oldName] of expected) { + const pattern = new RegExp( + `${newName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*:\\s*var\\(${oldName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, + ); + expect(variablesCss, `Expected ${newName} to fall back to ${oldName}`).toMatch(pattern); + } + }); - for (const [oldName, newName] of Object.entries(expected)) { - const pattern = new RegExp( - `${oldName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*:\\s*var\\(${newName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, - ); - expect(compatCss, `Expected ${oldName} → ${newName}`).toMatch(pattern); - } + it('honors old tracked change variable names', () => { + const expected: [string, string][] = [ + ['--sd-tracked-changes-insert-border', '--sd-track-insert-border'], + ['--sd-tracked-changes-insert-background', '--sd-track-insert-bg'], + ['--sd-tracked-changes-delete-border', '--sd-track-delete-border'], + ['--sd-tracked-changes-delete-background', '--sd-track-delete-bg'], + ['--sd-tracked-changes-format-border', '--sd-track-format-border'], + ]; + for (const [newName, oldName] of expected) { + const pattern = new RegExp( + `${newName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*:\\s*var\\(${oldName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, + ); + expect(variablesCss, `Expected ${newName} to fall back to ${oldName}`).toMatch(pattern); + } + }); + + it('honors old semantic variable names', () => { + const expected: [string, string][] = [ + ['--sd-ui-bg', '--sd-surface-card'], + ['--sd-ui-text', '--sd-text-primary'], + ['--sd-ui-border', '--sd-border-default'], + ['--sd-ui-action', '--sd-action-primary'], + ['--sd-ui-font-family', '--sd-font-family'], + ['--sd-ui-hover-bg', '--sd-surface-hover'], + ]; + for (const [newName, oldName] of expected) { + const pattern = new RegExp( + `${newName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*:\\s*var\\(${oldName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, + ); + expect(variablesCss, `Expected ${newName} to fall back to ${oldName}`).toMatch(pattern); + } + }); }); }); diff --git a/packages/superdoc/src/assets/styles/helpers/variables.css b/packages/superdoc/src/assets/styles/helpers/variables.css index eb1e471966..a6237ab8f1 100644 --- a/packages/superdoc/src/assets/styles/helpers/variables.css +++ b/packages/superdoc/src/assets/styles/helpers/variables.css @@ -37,29 +37,33 @@ --sd-font-size-500: 15px; --sd-font-size-600: 16px; - /* Primitive: radius */ - --sd-radius-50: 4px; + /* Primitive: radius — old names (--sd-radius-sm/md/lg) honored via fallback */ + --sd-radius-50: var(--sd-radius-sm, 4px); --sd-radius-100: 6px; - --sd-radius-200: 8px; - --sd-radius-300: 12px; + --sd-radius-200: var(--sd-radius-md, 8px); + --sd-radius-300: var(--sd-radius-lg, 12px); /* * UI: general (semantic tier) * * These are the "5-variable theme" — change these and the entire UI updates. * Component-specific variables below cascade from these defaults. + * + * Old-name fallbacks (var(--sd-old-name, ...)) honor pre-existing customer + * overrides. They resolve at computed-value time, so setting the old name on + * any element still works. These fallbacks will be removed in the next major. */ - --sd-ui-font-family: Arial, Helvetica, sans-serif; - --sd-ui-text: #47484a; - --sd-ui-text-muted: var(--sd-color-gray-700); - --sd-ui-text-disabled: var(--sd-color-gray-500); - --sd-ui-bg: #ffffff; - --sd-ui-hover-bg: var(--sd-color-gray-400); + --sd-ui-font-family: var(--sd-font-family, Arial, Helvetica, sans-serif); + --sd-ui-text: var(--sd-text-primary, #47484a); + --sd-ui-text-muted: var(--sd-text-muted, var(--sd-color-gray-700)); + --sd-ui-text-disabled: var(--sd-text-placeholder, var(--sd-color-gray-500)); + --sd-ui-bg: var(--sd-surface-card, #ffffff); + --sd-ui-hover-bg: var(--sd-surface-hover, var(--sd-color-gray-400)); --sd-ui-active-bg: #c8d0d8; - --sd-ui-disabled-bg: var(--sd-color-gray-100); - --sd-ui-border: var(--sd-color-gray-400); - --sd-ui-action: var(--sd-color-blue-500); - --sd-ui-action-hover: var(--sd-color-blue-600); + --sd-ui-disabled-bg: var(--sd-surface-muted, var(--sd-color-gray-100)); + --sd-ui-border: var(--sd-border-default, var(--sd-color-gray-400)); + --sd-ui-action: var(--sd-action-primary, var(--sd-color-blue-500)); + --sd-ui-action-hover: var(--sd-action-primary-hover, var(--sd-color-blue-600)); --sd-ui-shadow: 0 4px 12px rgba(0, 0, 0, 0.12); --sd-ui-radius: var(--sd-radius-100); @@ -71,7 +75,7 @@ --sd-ui-font-size-500: var(--sd-font-size-500); --sd-ui-font-size-600: var(--sd-font-size-600); - /* UI: dropdown — cascades surface/text/border from semantic tier */ + /* UI: dropdown — cascades from semantic tier */ --sd-ui-dropdown-border: var(--sd-ui-border); --sd-ui-dropdown-bg: var(--sd-ui-bg); --sd-ui-dropdown-option-radius: 3px; @@ -88,7 +92,7 @@ --sd-ui-tooltip-radius: var(--sd-radius-100); --sd-ui-tooltip-shadow: 0 3px 12px rgba(0, 0, 0, 0.28); - /* UI: toolbar — cascades text/hover from semantic tier */ + /* UI: toolbar — cascades from semantic tier */ --sd-ui-toolbar-height: 32px; --sd-ui-toolbar-padding-x: 16px; --sd-ui-toolbar-padding-y: 4px; @@ -99,7 +103,7 @@ --sd-ui-toolbar-button-hover-bg: var(--sd-ui-hover-bg); --sd-ui-toolbar-button-active-bg: var(--sd-ui-active-bg); - /* UI: context menu — cascades surface/text/border/action from semantic tier */ + /* UI: context menu — cascades from semantic tier */ --sd-ui-menu-bg: var(--sd-ui-bg); --sd-ui-menu-text: var(--sd-ui-text); --sd-ui-menu-font-size: var(--sd-font-size-200); @@ -119,29 +123,29 @@ --sd-ui-tools-item-bg: rgba(219, 219, 219, 0.6); --sd-ui-tools-icon-size: 20px; - /* UI: comments panel — cascades surface/text/border from semantic tier */ - --sd-ui-comments-card-bg: #f3f6fd; - --sd-ui-comments-card-hover-bg: var(--sd-ui-comments-card-bg); - --sd-ui-comments-card-active-bg: var(--sd-ui-bg); - --sd-ui-comments-card-resolved-bg: var(--sd-ui-disabled-bg); - --sd-ui-comments-card-active-border: var(--sd-ui-border); - --sd-ui-comments-card-radius: var(--sd-radius-300); - --sd-ui-comments-card-padding: 16px; - --sd-ui-comments-card-shadow: 0px 4px 12px 0px rgba(50, 50, 50, 0.15); - --sd-ui-comments-separator: var(--sd-ui-border); - --sd-ui-comments-transition: all 200ms ease; - --sd-ui-comments-author-text: var(--sd-ui-text); - --sd-ui-comments-author-size: var(--sd-font-size-400); - --sd-ui-comments-author-weight: 600; + /* UI: comments — cascades from semantic tier, old names honored via fallback */ + --sd-ui-comments-card-bg: var(--sd-comment-bg, #f3f6fd); + --sd-ui-comments-card-hover-bg: var(--sd-comment-bg-hover, var(--sd-ui-comments-card-bg)); + --sd-ui-comments-card-active-bg: var(--sd-comment-bg-active, var(--sd-ui-bg)); + --sd-ui-comments-card-resolved-bg: var(--sd-comment-bg-resolved, var(--sd-ui-disabled-bg)); + --sd-ui-comments-card-active-border: var(--sd-comment-border-active, var(--sd-ui-border)); + --sd-ui-comments-card-radius: var(--sd-comment-radius, var(--sd-radius-300)); + --sd-ui-comments-card-padding: var(--sd-comment-padding, 16px); + --sd-ui-comments-card-shadow: var(--sd-comment-shadow, 0px 4px 12px 0px rgba(50, 50, 50, 0.15)); + --sd-ui-comments-separator: var(--sd-comment-separator, var(--sd-ui-border)); + --sd-ui-comments-transition: var(--sd-comment-transition, all 200ms ease); + --sd-ui-comments-author-text: var(--sd-comment-author-color, var(--sd-ui-text)); + --sd-ui-comments-author-size: var(--sd-comment-author-size, var(--sd-font-size-400)); + --sd-ui-comments-author-weight: var(--sd-comment-author-weight, 600); --sd-ui-comments-tag-text: var(--sd-ui-text-muted); --sd-ui-comments-tag-bg: var(--sd-color-gray-200); - --sd-ui-comments-timestamp-text: var(--sd-ui-text-muted); - --sd-ui-comments-timestamp-size: var(--sd-font-size-200); + --sd-ui-comments-timestamp-text: var(--sd-comment-time-color, var(--sd-ui-text-muted)); + --sd-ui-comments-timestamp-size: var(--sd-comment-time-size, var(--sd-font-size-200)); --sd-ui-comments-body-text: var(--sd-ui-text); - --sd-ui-comments-body-size: var(--sd-font-size-400); + --sd-ui-comments-body-size: var(--sd-comment-body-size, var(--sd-font-size-400)); --sd-ui-comments-resolved-text: var(--sd-color-green-500); - --sd-ui-comments-insert-text: var(--sd-color-green-500); - --sd-ui-comments-delete-text: var(--sd-color-rose-500); + --sd-ui-comments-insert-text: var(--sd-comment-tc-insert-color, var(--sd-color-green-500)); + --sd-ui-comments-delete-text: var(--sd-comment-tc-delete-color, var(--sd-color-rose-500)); --sd-ui-comments-dropdown-border: var(--sd-ui-border); --sd-ui-comments-dropdown-bg: var(--sd-ui-bg); --sd-ui-comments-dropdown-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); @@ -152,16 +156,16 @@ --sd-ui-comments-option-hover-bg: var(--sd-ui-hover-bg); --sd-ui-comments-input-bg: var(--sd-ui-bg); --sd-ui-comments-input-border: var(--sd-ui-border); - --sd-ui-comments-internal-bg: #cde6e6; - --sd-ui-comments-external-bg: #f5cfda; + --sd-ui-comments-internal-bg: var(--sd-comment-internal-bg, #cde6e6); + --sd-ui-comments-external-bg: var(--sd-comment-external-bg, #f5cfda); - /* Styles: comments — document-level highlight colors, intentionally standalone */ + /* Styles: comments — old names honored via fallback */ --sd-comments-highlight-external-base: #b1124b; - --sd-comments-highlight-external: #b1124b40; + --sd-comments-highlight-external: var(--sd-comment-highlight-external, #b1124b40); --sd-comments-highlight-external-active: #b1124b66; --sd-comments-highlight-external-faded: #b1124b20; --sd-comments-highlight-internal-base: #078383; - --sd-comments-highlight-internal: #07838340; + --sd-comments-highlight-internal: var(--sd-comment-highlight-internal, #07838340); --sd-comments-highlight-internal-active: #07838366; --sd-comments-highlight-internal-faded: #07838320; --sd-comments-highlight-external-nested-border: #b1124b99; @@ -169,12 +173,12 @@ --sd-comments-highlight-hover: #1354ff55; --sd-comments-selection-background: #1354ff55; - /* Styles: tracked changes — document-level decoration colors, intentionally standalone */ - --sd-tracked-changes-insert-background: #399c7222; - --sd-tracked-changes-insert-border: #00853d; - --sd-tracked-changes-delete-background: #cb0e4722; - --sd-tracked-changes-delete-border: #cb0e47; - --sd-tracked-changes-format-border: gold; + /* Styles: tracked changes — old names honored via fallback */ + --sd-tracked-changes-insert-background: var(--sd-track-insert-bg, #399c7222); + --sd-tracked-changes-insert-border: var(--sd-track-insert-border, #00853d); + --sd-tracked-changes-delete-background: var(--sd-track-delete-bg, #cb0e4722); + --sd-tracked-changes-delete-border: var(--sd-track-delete-border, #cb0e47); + --sd-tracked-changes-format-border: var(--sd-track-format-border, gold); --sd-tracked-changes-insert-background-focused: #399c7244; --sd-tracked-changes-delete-background-focused: #cb0e4744; --sd-tracked-changes-format-background-focused: #ffd70033; @@ -189,7 +193,7 @@ --sd-content-controls-label-text: #ffffff; --sd-content-controls-lock-hover-bg: rgba(98, 155, 231, 0.08); - /* Styles: layout — cascades surface from semantic tier */ + /* Styles: layout — cascades from semantic tier */ --sd-layout-page-bg: var(--sd-ui-bg); --sd-layout-page-shadow: 0 4px 20px rgba(15, 23, 42, 0.08); } From 0d93e8d196255567e6eb7dc3584aa66d2f508556 Mon Sep 17 00:00:00 2001 From: Caio Pizzol Date: Wed, 18 Mar 2026 12:52:52 -0300 Subject: [PATCH 09/11] docs: update migration guide to reflect two-way compat mechanism The compat approach changed from one-way aliases in compat.css to var() fallbacks in variables.css. Updated the explanation and code examples. --- apps/docs/guides/general/custom-themes.mdx | 2 +- apps/docs/guides/migration/css-variables.mdx | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/apps/docs/guides/general/custom-themes.mdx b/apps/docs/guides/general/custom-themes.mdx index 44dc054efa..29df1171b9 100644 --- a/apps/docs/guides/general/custom-themes.mdx +++ b/apps/docs/guides/general/custom-themes.mdx @@ -283,4 +283,4 @@ Token definitions live in your `node_modules/superdoc/` package: - `src/assets/styles/helpers/variables.css` — defaults - `src/assets/styles/helpers/themes.css` — preset themes -- `src/assets/styles/helpers/compat.css` — backward-compat aliases +- `src/assets/styles/helpers/compat.css` — aliases for old names that map to a different concept diff --git a/apps/docs/guides/migration/css-variables.mdx b/apps/docs/guides/migration/css-variables.mdx index 4f7f041ef7..043bb52e7f 100644 --- a/apps/docs/guides/migration/css-variables.mdx +++ b/apps/docs/guides/migration/css-variables.mdx @@ -7,7 +7,7 @@ keywords: "migration, css variables, renamed variables, breaking changes, backwa SuperDoc's CSS variables were restructured into a three-tier token system. This page maps every old name to its new equivalent. -**Old names still work.** Aliases in `compat.css` (loaded automatically) point old names to new ones. You don't need to change your code immediately. +**Old names still work.** New tokens fall back to old names automatically. You don't need to change your code immediately. ## What changed @@ -138,25 +138,26 @@ These have no replacement: ## How backward compatibility works -`compat.css` defines aliases from old names to new: +New tokens in `variables.css` fall back to old names via `var()`: ```css -/* Loaded automatically */ +/* Inside variables.css */ :root { - --sd-comment-bg: var(--sd-ui-comments-card-bg); - --sd-track-insert-border: var(--sd-tracked-changes-insert-border); - /* ... */ + --sd-ui-comments-card-bg: var(--sd-comment-bg, #f3f6fd); + --sd-tracked-changes-insert-border: var(--sd-track-insert-border, #00853d); } ``` -Your existing overrides keep working because your selector is more specific than `:root`: +CSS resolves `var()` at computed-value time. If you set `--sd-comment-bg` on any element, the new token picks it up in that scope: ```css .my-overrides { - --sd-comment-bg: #f0f0f0; /* still works */ + --sd-comment-bg: #f0f0f0; /* works — --sd-ui-comments-card-bg resolves to #f0f0f0 */ } ``` +Setting the new name directly works too — it overrides the whole fallback expression. + -Compat aliases will be removed in the next major version. Migrate to the new names when convenient. +Old-name fallbacks will be removed in the next major version. Migrate to the new names when convenient. From 2bf71ffdb5e5a0eef3653b9ac4e72d4a778a64c7 Mon Sep 17 00:00:00 2001 From: Caio Pizzol Date: Wed, 18 Mar 2026 13:05:34 -0300 Subject: [PATCH 10/11] feat: add createTheme() and buildTheme() helpers for JS-based theming createTheme() takes a typed config object and returns a CSS class name. Apply it to to theme the entire UI. Maps a small set of high-level options (colors, font, radius) to the full CSS variable system. - colors: action, bg, text, border cascade to every component - Component overrides: toolbar, dropdown, menu, comments, highlights, etc. - buildTheme() returns { className, css } for SSR - Style element auto-injected, idempotent on re-call with same name - Exported from superdoc package: import { createTheme } from 'superdoc' - 11 unit tests - Docs updated to lead with createTheme(), CSS variables as advanced option --- apps/docs/getting-started/theming.mdx | 122 ++++---- apps/docs/guides/general/custom-themes.mdx | 120 ++++---- .../superdoc/src/core/theme/create-theme.js | 267 ++++++++++++++++++ .../src/core/theme/create-theme.test.js | 105 +++++++ packages/superdoc/src/index.js | 1 + 5 files changed, 510 insertions(+), 105 deletions(-) create mode 100644 packages/superdoc/src/core/theme/create-theme.js create mode 100644 packages/superdoc/src/core/theme/create-theme.test.js diff --git a/apps/docs/getting-started/theming.mdx b/apps/docs/getting-started/theming.mdx index 3a46855e8e..796f39fdeb 100644 --- a/apps/docs/getting-started/theming.mdx +++ b/apps/docs/getting-started/theming.mdx @@ -1,32 +1,58 @@ --- title: Theming sidebarTitle: Theming -keywords: "theming, css variables, custom theme, dark mode, design tokens, branding, css customization, styling" +keywords: "theming, css variables, custom theme, dark mode, design tokens, branding, css customization, createTheme, styling" --- -Change five CSS variables and your brand colors flow through every SuperDoc component — toolbar, comments, dropdowns, everything. No JavaScript needed. +Set your brand colors in JavaScript and every SuperDoc component updates — toolbar, comments, dropdowns, everything. -```css -.my-theme { - --sd-ui-action: #6366f1; - --sd-ui-action-hover: #4f46e5; - --sd-ui-toolbar-bg: #f8fafc; - --sd-ui-bg: #ffffff; - --sd-ui-text: #1e293b; -} +```javascript +import { createTheme } from 'superdoc'; + +const theme = createTheme({ + colors: { action: '#6366f1', bg: '#ffffff', text: '#1e293b', border: '#e2e8f0' }, + font: 'Inter, sans-serif', +}); + +document.documentElement.classList.add(theme); ``` -```html - -
- +That's it. Five properties theme the entire UI. + +## `createTheme()` API + +Pass a config object, get back a CSS class name. Apply it to ``. + +```javascript +import { createTheme } from 'superdoc'; + +const theme = createTheme({ + name: 'my-brand', // optional — generates sd-theme-my-brand + font: 'Inter, sans-serif', + radius: '8px', + colors: { + action: '#6366f1', // buttons, links, active states + actionHover: '#4f46e5', + bg: '#ffffff', // panels, cards, dropdowns + text: '#1e293b', // primary text + textMuted: '#64748b', // secondary text + border: '#e2e8f0', // all borders + }, + // Fine-tune individual components (optional) + toolbar: { bg: '#f8fafc' }, + comments: { cardBg: '#f0f0ff' }, +}); + +document.documentElement.classList.add(theme); ``` -Override any `--sd-*` CSS custom property on the `` element and the changes cascade to all components. +The `colors` object controls the semantic tier — every component inherits from it. Component-level overrides (`toolbar`, `comments`, `dropdown`, `menu`, etc.) are optional and only needed when a specific component should look different. + +See the [full API reference](/guides/general/custom-themes) for all available options. ## Preset themes -Three preset themes ship out of the box. Add the class to `` — some SuperDoc elements (popovers, dropdowns) are appended to ``, so they need to inherit from `` to pick up the theme. +Three presets ship out of the box. Add the class to `` — some SuperDoc elements (popovers, dropdowns) are appended to ``, so they need to inherit from ``. @@ -49,54 +75,40 @@ Three preset themes ship out of the box. Add the class to `` — some Supe -You can layer your own overrides on top of a preset: +## CSS variables (advanced) + +`createTheme()` generates CSS variables under the hood. You can also set them directly if you prefer raw CSS: ```css -.sd-theme-docs { - --sd-ui-action: #your-brand-color; - --sd-ui-comments-card-bg: #f0f0f0; +:root { + --sd-ui-action: #6366f1; + --sd-ui-bg: #ffffff; + --sd-ui-text: #1e293b; + --sd-ui-border: #e2e8f0; + --sd-ui-font-family: Inter, sans-serif; } ``` -## How the token system works - -Variables are organized in three tiers. Higher tiers reference lower tiers, so changing a primitive cascades everywhere. - -``` -Primitives UI semantic Component-specific ---sd-color-* --sd-ui-text --sd-ui-toolbar-* ---sd-font-size-* --sd-ui-bg --sd-ui-comments-* ---sd-radius-* --sd-ui-action --sd-ui-dropdown-* -``` - -**Tier 1 — Primitives.** Raw color scales, font sizes, and radii. You rarely need to touch these directly. - -**Tier 2 — UI semantic.** The "5-variable theme." Change these and every component updates: - -| Variable | Default | What it controls | -|----------|---------|-----------------| -| `--sd-ui-font-family` | `Arial, Helvetica, sans-serif` | Font for all UI chrome | -| `--sd-ui-text` | `#47484a` | Primary text color | -| `--sd-ui-bg` | `#ffffff` | Panels, cards, dropdowns | -| `--sd-ui-border` | `#dbdbdb` | All borders | -| `--sd-ui-action` | `#1355ff` | Buttons, links, active states | +See the [full variable reference](/guides/general/custom-themes#variable-reference) for every token. -**Tier 3 — Component-specific.** Fine-grained overrides for individual components. These default to the semantic tier, so you only set them when a component needs to look different from the rest. See the [full reference](/guides/general/custom-themes). +## SSR support -## JavaScript config - -The `trackChangesConfig` and `commentsConfig` JavaScript options still work. They take priority over CSS variables when set. Use the JS API for runtime changes, CSS variables for static theming. +For server-side rendering, use `buildTheme()` to get the raw CSS string: ```javascript -new SuperDoc({ - selector: '#editor', - commentsConfig: { - highlightHoverColor: '#ff000055', - trackChangeHighlightColors: { - insertBorder: '#00ff00', - }, - }, +import { buildTheme } from 'superdoc'; + +const { className, css } = buildTheme({ + colors: { action: '#6366f1', bg: '#ffffff', text: '#1e293b' }, }); + +// Inject into your HTML template +const html = ` + + +
+ +`; ``` ## Next steps @@ -107,7 +119,7 @@ new SuperDoc({ icon="palette" href="/guides/general/custom-themes" > - Full variable reference by component. Dark theme starter. How to build your own preset. + Full createTheme() API reference. Dark theme starter. All CSS variables by component.
` element. - -```css -.my-company-theme { - /* These cascade to every component */ - --sd-ui-action: #8b5cf6; - --sd-ui-action-hover: #7c3aed; - --sd-ui-text: #1e293b; - --sd-ui-bg: #f8fafc; - --sd-ui-border: #e2e8f0; - - /* Fine-tune where needed */ - --sd-ui-toolbar-bg: #f1f5f9; - --sd-ui-comments-card-bg: #f0f0ff; -} +Full `createTheme()` API reference and CSS variable catalog. Start with the [theming overview](/getting-started/theming) if you're new to theming. + +## `createTheme()` options + +```javascript +import { createTheme } from 'superdoc'; + +const theme = createTheme({ + name: 'my-brand', // optional, used in generated class name + font: 'Inter, sans-serif', + radius: '8px', + shadow: '0 2px 8px rgba(0,0,0,0.1)', + + colors: { + action: '#6366f1', // buttons, links, active states + actionHover: '#4f46e5', + bg: '#ffffff', // panels, cards, dropdowns + hoverBg: '#f1f5f9', + activeBg: '#e2e8f0', + text: '#1e293b', + textMuted: '#64748b', + border: '#e2e8f0', + }, + + toolbar: { bg, buttonText, buttonHoverBg, buttonActiveBg, itemPadding }, + dropdown: { bg, border, text, hoverBg, activeBg, shadow }, + menu: { bg, text, border, itemHoverBg, itemActiveBg, itemActiveText, shadow }, + comments: { cardBg, cardHoverBg, cardActiveBg, cardResolvedBg, cardActiveBorder, + cardRadius, cardShadow, separator, authorText, timestampText, + bodyText, inputBg, inputBorder }, + highlights: { external, externalActive, externalFaded, + internal, internalActive, internalFaded }, + trackedChanges: { insertBg, insertBorder, deleteBg, deleteBorder, formatBorder }, + layout: { pageBg, pageShadow }, +}); + +document.documentElement.classList.add(theme); ``` -Start with the 5-10 semantic variables. Add component-specific ones only where the defaults don't look right. +Start with `colors` only. Add component overrides when a specific component needs to look different from the rest. -## Dark theme starter +## Dark theme example + +```javascript +import { createTheme } from 'superdoc'; + +const dark = createTheme({ + name: 'dark', + colors: { + bg: '#1a1a2e', + hoverBg: '#2a2a3e', + activeBg: '#3a3a4e', + text: '#e2e8f0', + textMuted: '#94a3b8', + border: '#334155', + action: '#60a5fa', + actionHover: '#93c5fd', + }, + toolbar: { bg: '#0f172a', buttonText: '#e2e8f0' }, + comments: { cardBg: '#1e293b', bodyText: '#cbd5e1' }, +}); + +document.documentElement.classList.add(dark); +``` + +## Raw CSS alternative -Override the core surface and text variables. Component backgrounds cascade from `--sd-ui-bg` automatically. +If you prefer writing CSS directly, set `--sd-*` variables on ``: ```css -.dark-theme { - --sd-ui-bg: #1a1a2e; - --sd-ui-hover-bg: #2a2a3e; - --sd-ui-active-bg: #3a3a4e; - --sd-ui-text: #e2e8f0; - --sd-ui-text-muted: #94a3b8; - --sd-ui-border: #334155; - --sd-ui-action: #60a5fa; - --sd-ui-action-hover: #93c5fd; - - /* Toolbar */ - --sd-ui-toolbar-bg: #0f172a; - --sd-ui-toolbar-button-text: #e2e8f0; - --sd-ui-toolbar-button-hover-bg: #1e293b; - --sd-ui-toolbar-button-active-bg: #334155; - - /* Dropdowns */ - --sd-ui-dropdown-bg: #1e293b; - --sd-ui-dropdown-border: #334155; - --sd-ui-dropdown-hover-bg: #2d3a4e; - - /* Comments */ - --sd-ui-comments-card-bg: #1e293b; - --sd-ui-comments-card-active-bg: #0f172a; - --sd-ui-comments-input-bg: #1e293b; - --sd-ui-comments-author-text: #e2e8f0; - --sd-ui-comments-body-text: #cbd5e1; - --sd-ui-comments-timestamp-text: #64748b; +:root { + --sd-ui-action: #6366f1; + --sd-ui-bg: #ffffff; + --sd-ui-text: #1e293b; + --sd-ui-border: #e2e8f0; + --sd-ui-font-family: Inter, sans-serif; } ``` diff --git a/packages/superdoc/src/core/theme/create-theme.js b/packages/superdoc/src/core/theme/create-theme.js new file mode 100644 index 0000000000..e7e3a309b2 --- /dev/null +++ b/packages/superdoc/src/core/theme/create-theme.js @@ -0,0 +1,267 @@ +/** + * @typedef {Object} ThemeColors + * @property {string} [action] Action/accent color (buttons, links). Default: #1355ff + * @property {string} [actionHover] Action hover state. Default: derived from action + * @property {string} [bg] Default surface/background. Default: #ffffff + * @property {string} [hoverBg] Hover background. Default: #dbdbdb + * @property {string} [activeBg] Active/pressed background. Default: #c8d0d8 + * @property {string} [text] Primary text color. Default: #47484a + * @property {string} [textMuted] Secondary text. Default: #666666 + * @property {string} [border] Default border color. Default: #dbdbdb + */ + +/** + * @typedef {Object} ThemeToolbar + * @property {string} [bg] Toolbar background. Default: transparent + * @property {string} [buttonText] Button text/icon color + * @property {string} [buttonHoverBg] Button hover background + * @property {string} [buttonActiveBg] Button active background + * @property {string} [itemPadding] Padding inside each button + */ + +/** + * @typedef {Object} ThemeDropdown + * @property {string} [bg] Dropdown panel background + * @property {string} [border] Panel border + * @property {string} [text] Option text + * @property {string} [hoverBg] Option hover background + * @property {string} [activeBg] Option active background + * @property {string} [shadow] Panel shadow + */ + +/** + * @typedef {Object} ThemeMenu + * @property {string} [bg] Menu background + * @property {string} [text] Menu text + * @property {string} [border] Menu border + * @property {string} [itemHoverBg] Item hover background + * @property {string} [itemActiveBg] Active item background + * @property {string} [itemActiveText] Active item text + * @property {string} [shadow] Menu shadow + */ + +/** + * @typedef {Object} ThemeComments + * @property {string} [cardBg] Default card background. Default: #f3f6fd + * @property {string} [cardHoverBg] Hovered card background + * @property {string} [cardActiveBg] Selected/active card background + * @property {string} [cardResolvedBg] Resolved card background + * @property {string} [cardActiveBorder] Active card border + * @property {string} [cardRadius] Card border radius + * @property {string} [cardShadow] Card shadow + * @property {string} [separator] Divider between comments + * @property {string} [authorText] Author name color + * @property {string} [timestampText] Timestamp color + * @property {string} [bodyText] Comment body text color + * @property {string} [inputBg] Comment input background + * @property {string} [inputBorder] Comment input border + */ + +/** + * @typedef {Object} ThemeHighlights + * @property {string} [external] External comment highlight + * @property {string} [externalActive] Active external highlight + * @property {string} [externalFaded] Faded external highlight + * @property {string} [internal] Internal comment highlight + * @property {string} [internalActive] Active internal highlight + * @property {string} [internalFaded] Faded internal highlight + */ + +/** + * @typedef {Object} ThemeTrackedChanges + * @property {string} [insertBg] Insert highlight background + * @property {string} [insertBorder] Insert left border + * @property {string} [deleteBg] Delete highlight background + * @property {string} [deleteBorder] Delete left border + * @property {string} [formatBorder] Format change border + */ + +/** + * @typedef {Object} ThemeLayout + * @property {string} [pageBg] Page background + * @property {string} [pageShadow] Page shadow + */ + +/** + * @typedef {Object} ThemeConfig + * @property {string} [name] Optional theme name (used in the generated class name) + * @property {string} [font] UI font family + * @property {string} [radius] Default border radius + * @property {string} [shadow] Default shadow + * @property {ThemeColors} [colors] Core color palette + * @property {ThemeToolbar} [toolbar] Toolbar overrides + * @property {ThemeDropdown} [dropdown] Dropdown overrides + * @property {ThemeMenu} [menu] Context menu overrides + * @property {ThemeComments} [comments] Comments panel overrides + * @property {ThemeHighlights} [highlights] In-document comment highlight overrides + * @property {ThemeTrackedChanges} [trackedChanges] Tracked change decoration overrides + * @property {ThemeLayout} [layout] Page layout overrides + */ + +/** @type {Map} */ +const CONFIG_TO_VAR = new Map([ + // Colors (semantic tier) + ['colors.action', '--sd-ui-action'], + ['colors.actionHover', '--sd-ui-action-hover'], + ['colors.bg', '--sd-ui-bg'], + ['colors.hoverBg', '--sd-ui-hover-bg'], + ['colors.activeBg', '--sd-ui-active-bg'], + ['colors.text', '--sd-ui-text'], + ['colors.textMuted', '--sd-ui-text-muted'], + ['colors.border', '--sd-ui-border'], + + // Top-level shortcuts + ['font', '--sd-ui-font-family'], + ['radius', '--sd-ui-radius'], + ['shadow', '--sd-ui-shadow'], + + // Toolbar + ['toolbar.bg', '--sd-ui-toolbar-bg'], + ['toolbar.buttonText', '--sd-ui-toolbar-button-text'], + ['toolbar.buttonHoverBg', '--sd-ui-toolbar-button-hover-bg'], + ['toolbar.buttonActiveBg', '--sd-ui-toolbar-button-active-bg'], + ['toolbar.itemPadding', '--sd-ui-toolbar-item-padding'], + + // Dropdown + ['dropdown.bg', '--sd-ui-dropdown-bg'], + ['dropdown.border', '--sd-ui-dropdown-border'], + ['dropdown.text', '--sd-ui-dropdown-text'], + ['dropdown.hoverBg', '--sd-ui-dropdown-hover-bg'], + ['dropdown.activeBg', '--sd-ui-dropdown-active-bg'], + ['dropdown.shadow', '--sd-ui-dropdown-shadow'], + + // Menu + ['menu.bg', '--sd-ui-menu-bg'], + ['menu.text', '--sd-ui-menu-text'], + ['menu.border', '--sd-ui-menu-border'], + ['menu.itemHoverBg', '--sd-ui-menu-item-hover-bg'], + ['menu.itemActiveBg', '--sd-ui-menu-item-active-bg'], + ['menu.itemActiveText', '--sd-ui-menu-item-active-text'], + ['menu.shadow', '--sd-ui-menu-shadow'], + + // Comments + ['comments.cardBg', '--sd-ui-comments-card-bg'], + ['comments.cardHoverBg', '--sd-ui-comments-card-hover-bg'], + ['comments.cardActiveBg', '--sd-ui-comments-card-active-bg'], + ['comments.cardResolvedBg', '--sd-ui-comments-card-resolved-bg'], + ['comments.cardActiveBorder', '--sd-ui-comments-card-active-border'], + ['comments.cardRadius', '--sd-ui-comments-card-radius'], + ['comments.cardShadow', '--sd-ui-comments-card-shadow'], + ['comments.separator', '--sd-ui-comments-separator'], + ['comments.authorText', '--sd-ui-comments-author-text'], + ['comments.timestampText', '--sd-ui-comments-timestamp-text'], + ['comments.bodyText', '--sd-ui-comments-body-text'], + ['comments.inputBg', '--sd-ui-comments-input-bg'], + ['comments.inputBorder', '--sd-ui-comments-input-border'], + + // Highlights + ['highlights.external', '--sd-comments-highlight-external'], + ['highlights.externalActive', '--sd-comments-highlight-external-active'], + ['highlights.externalFaded', '--sd-comments-highlight-external-faded'], + ['highlights.internal', '--sd-comments-highlight-internal'], + ['highlights.internalActive', '--sd-comments-highlight-internal-active'], + ['highlights.internalFaded', '--sd-comments-highlight-internal-faded'], + + // Tracked changes + ['trackedChanges.insertBg', '--sd-tracked-changes-insert-background'], + ['trackedChanges.insertBorder', '--sd-tracked-changes-insert-border'], + ['trackedChanges.deleteBg', '--sd-tracked-changes-delete-background'], + ['trackedChanges.deleteBorder', '--sd-tracked-changes-delete-border'], + ['trackedChanges.formatBorder', '--sd-tracked-changes-format-border'], + + // Layout + ['layout.pageBg', '--sd-layout-page-bg'], + ['layout.pageShadow', '--sd-layout-page-shadow'], +]); + +let themeCounter = 0; + +/** + * Flatten a nested config object into dot-separated paths. + * @param {Record} obj + * @param {string} [prefix] + * @returns {Array<[string, string]>} + */ +function flattenConfig(obj, prefix = '') { + /** @type {Array<[string, string]>} */ + const entries = []; + for (const [key, value] of Object.entries(obj)) { + const path = prefix ? `${prefix}.${key}` : key; + if (value != null && typeof value === 'object' && !Array.isArray(value)) { + entries.push(...flattenConfig(value, path)); + } else if (value != null) { + entries.push([path, String(value)]); + } + } + return entries; +} + +/** + * Create a SuperDoc theme from a config object. + * + * Returns a CSS class name. Apply it to `` to activate the theme. + * The class is injected into the document automatically. + * + * @param {ThemeConfig} config + * @returns {string} The generated CSS class name + * + * @example + * ```js + * import { createTheme } from 'superdoc'; + * + * const theme = createTheme({ + * colors: { action: '#6366f1', bg: '#ffffff', text: '#1e293b' }, + * font: 'Inter, sans-serif', + * toolbar: { bg: '#f8fafc' }, + * }); + * + * document.documentElement.classList.add(theme); + * ``` + */ +export function createTheme(config) { + const { name, ...rest } = config; + const className = `sd-theme-${name || `custom-${++themeCounter}`}`; + + const flat = flattenConfig(rest); + const declarations = []; + + for (const [path, value] of flat) { + const varName = CONFIG_TO_VAR.get(path); + if (varName) { + declarations.push(` ${varName}: ${value};`); + } + } + + if (declarations.length === 0) return className; + + const css = `.${className} {\n${declarations.join('\n')}\n}`; + + // Inject into document if available (SSR-safe) + if (typeof document !== 'undefined') { + let style = document.querySelector(`[data-sd-theme="${className}"]`); + if (!style) { + style = document.createElement('style'); + style.setAttribute('data-sd-theme', className); + document.head.appendChild(style); + } + style.textContent = css; + } + + // Attach the raw CSS for SSR or manual injection + /** @type {string} */ + createTheme._lastCss = css; + + return className; +} + +/** + * Get the raw CSS string from the last createTheme() call. + * Useful for SSR where you need to inject styles into the HTML template. + * + * @param {ThemeConfig} config + * @returns {{ className: string, css: string }} + */ +export function buildTheme(config) { + const className = createTheme(config); + return { className, css: createTheme._lastCss || '' }; +} diff --git a/packages/superdoc/src/core/theme/create-theme.test.js b/packages/superdoc/src/core/theme/create-theme.test.js new file mode 100644 index 0000000000..30582e2c65 --- /dev/null +++ b/packages/superdoc/src/core/theme/create-theme.test.js @@ -0,0 +1,105 @@ +import { describe, expect, it, beforeEach } from 'vitest'; +import { createTheme, buildTheme } from './create-theme.js'; + +describe('createTheme', () => { + beforeEach(() => { + // Clean up injected styles between tests + document.querySelectorAll('[data-sd-theme]').forEach((el) => el.remove()); + }); + + it('returns a class name', () => { + const className = createTheme({ colors: { action: '#ff0000' } }); + expect(className).toMatch(/^sd-theme-/); + }); + + it('uses the provided name', () => { + const className = createTheme({ name: 'dark', colors: { bg: '#000' } }); + expect(className).toBe('sd-theme-dark'); + }); + + it('maps colors to CSS variables', () => { + const { css } = buildTheme({ + name: 'test-colors', + colors: { + action: '#6366f1', + bg: '#ffffff', + text: '#1e293b', + border: '#e2e8f0', + }, + }); + expect(css).toContain('--sd-ui-action: #6366f1'); + expect(css).toContain('--sd-ui-bg: #ffffff'); + expect(css).toContain('--sd-ui-text: #1e293b'); + expect(css).toContain('--sd-ui-border: #e2e8f0'); + }); + + it('maps top-level font and radius', () => { + const { css } = buildTheme({ + name: 'test-font', + font: 'Inter, sans-serif', + radius: '8px', + }); + expect(css).toContain('--sd-ui-font-family: Inter, sans-serif'); + expect(css).toContain('--sd-ui-radius: 8px'); + }); + + it('maps component-level toolbar overrides', () => { + const { css } = buildTheme({ + name: 'test-toolbar', + toolbar: { bg: '#f1f5f9', buttonText: '#333' }, + }); + expect(css).toContain('--sd-ui-toolbar-bg: #f1f5f9'); + expect(css).toContain('--sd-ui-toolbar-button-text: #333'); + }); + + it('maps component-level comments overrides', () => { + const { css } = buildTheme({ + name: 'test-comments', + comments: { cardBg: '#f0f0ff', inputBg: '#fff', separator: '#ddd' }, + }); + expect(css).toContain('--sd-ui-comments-card-bg: #f0f0ff'); + expect(css).toContain('--sd-ui-comments-input-bg: #fff'); + expect(css).toContain('--sd-ui-comments-separator: #ddd'); + }); + + it('maps tracked changes overrides', () => { + const { css } = buildTheme({ + name: 'test-tc', + trackedChanges: { insertBorder: '#00ff00', deleteBorder: '#ff0000' }, + }); + expect(css).toContain('--sd-tracked-changes-insert-border: #00ff00'); + expect(css).toContain('--sd-tracked-changes-delete-border: #ff0000'); + }); + + it('ignores null and undefined values', () => { + const { css } = buildTheme({ + name: 'test-null', + colors: { action: '#ff0000', bg: undefined, text: null }, + }); + expect(css).toContain('--sd-ui-action: #ff0000'); + expect(css).not.toContain('--sd-ui-bg'); + expect(css).not.toContain('--sd-ui-text'); + }); + + it('injects a style element into the document', () => { + const className = createTheme({ name: 'inject-test', colors: { action: '#abc' } }); + const style = document.querySelector(`[data-sd-theme="${className}"]`); + expect(style).not.toBeNull(); + expect(style.textContent).toContain('--sd-ui-action: #abc'); + }); + + it('updates existing style element on re-call with same name', () => { + createTheme({ name: 'reuse', colors: { action: '#111' } }); + createTheme({ name: 'reuse', colors: { action: '#222' } }); + const styles = document.querySelectorAll('[data-sd-theme="sd-theme-reuse"]'); + expect(styles.length).toBe(1); + expect(styles[0].textContent).toContain('#222'); + }); + + it('buildTheme returns both className and css', () => { + const result = buildTheme({ name: 'build-test', colors: { action: '#f00' } }); + expect(result.className).toBe('sd-theme-build-test'); + expect(result.css).toContain('.sd-theme-build-test'); + expect(result.css).toContain('--sd-ui-action: #f00'); + }); +}); diff --git a/packages/superdoc/src/index.js b/packages/superdoc/src/index.js index 34047a9504..19bad9bd5d 100644 --- a/packages/superdoc/src/index.js +++ b/packages/superdoc/src/index.js @@ -17,6 +17,7 @@ import { getSchemaIntrospection } from './helpers/schema-introspection.js'; // Public exports export { SuperDoc } from './core/SuperDoc.js'; +export { createTheme, buildTheme } from './core/theme/create-theme.js'; export { BlankDOCX, getFileObject, From 4ba147d4b744389dbf1d405c94db4241f67a272c Mon Sep 17 00:00:00 2001 From: Caio Pizzol Date: Wed, 18 Mar 2026 13:12:54 -0300 Subject: [PATCH 11/11] Revert "feat: add createTheme() and buildTheme() helpers for JS-based theming" This reverts commit 2bf71ffdb5e5a0eef3653b9ac4e72d4a778a64c7. --- apps/docs/getting-started/theming.mdx | 122 ++++---- apps/docs/guides/general/custom-themes.mdx | 120 ++++---- .../superdoc/src/core/theme/create-theme.js | 267 ------------------ .../src/core/theme/create-theme.test.js | 105 ------- packages/superdoc/src/index.js | 1 - 5 files changed, 105 insertions(+), 510 deletions(-) delete mode 100644 packages/superdoc/src/core/theme/create-theme.js delete mode 100644 packages/superdoc/src/core/theme/create-theme.test.js diff --git a/apps/docs/getting-started/theming.mdx b/apps/docs/getting-started/theming.mdx index 796f39fdeb..3a46855e8e 100644 --- a/apps/docs/getting-started/theming.mdx +++ b/apps/docs/getting-started/theming.mdx @@ -1,58 +1,32 @@ --- title: Theming sidebarTitle: Theming -keywords: "theming, css variables, custom theme, dark mode, design tokens, branding, css customization, createTheme, styling" +keywords: "theming, css variables, custom theme, dark mode, design tokens, branding, css customization, styling" --- -Set your brand colors in JavaScript and every SuperDoc component updates — toolbar, comments, dropdowns, everything. +Change five CSS variables and your brand colors flow through every SuperDoc component — toolbar, comments, dropdowns, everything. No JavaScript needed. -```javascript -import { createTheme } from 'superdoc'; - -const theme = createTheme({ - colors: { action: '#6366f1', bg: '#ffffff', text: '#1e293b', border: '#e2e8f0' }, - font: 'Inter, sans-serif', -}); - -document.documentElement.classList.add(theme); +```css +.my-theme { + --sd-ui-action: #6366f1; + --sd-ui-action-hover: #4f46e5; + --sd-ui-toolbar-bg: #f8fafc; + --sd-ui-bg: #ffffff; + --sd-ui-text: #1e293b; +} ``` -That's it. Five properties theme the entire UI. - -## `createTheme()` API - -Pass a config object, get back a CSS class name. Apply it to ``. - -```javascript -import { createTheme } from 'superdoc'; - -const theme = createTheme({ - name: 'my-brand', // optional — generates sd-theme-my-brand - font: 'Inter, sans-serif', - radius: '8px', - colors: { - action: '#6366f1', // buttons, links, active states - actionHover: '#4f46e5', - bg: '#ffffff', // panels, cards, dropdowns - text: '#1e293b', // primary text - textMuted: '#64748b', // secondary text - border: '#e2e8f0', // all borders - }, - // Fine-tune individual components (optional) - toolbar: { bg: '#f8fafc' }, - comments: { cardBg: '#f0f0ff' }, -}); - -document.documentElement.classList.add(theme); +```html + +
+ ``` -The `colors` object controls the semantic tier — every component inherits from it. Component-level overrides (`toolbar`, `comments`, `dropdown`, `menu`, etc.) are optional and only needed when a specific component should look different. - -See the [full API reference](/guides/general/custom-themes) for all available options. +Override any `--sd-*` CSS custom property on the `` element and the changes cascade to all components. ## Preset themes -Three presets ship out of the box. Add the class to `` — some SuperDoc elements (popovers, dropdowns) are appended to ``, so they need to inherit from ``. +Three preset themes ship out of the box. Add the class to `` — some SuperDoc elements (popovers, dropdowns) are appended to ``, so they need to inherit from `` to pick up the theme. @@ -75,40 +49,54 @@ Three presets ship out of the box. Add the class to `` — some SuperDoc e -## CSS variables (advanced) - -`createTheme()` generates CSS variables under the hood. You can also set them directly if you prefer raw CSS: +You can layer your own overrides on top of a preset: ```css -:root { - --sd-ui-action: #6366f1; - --sd-ui-bg: #ffffff; - --sd-ui-text: #1e293b; - --sd-ui-border: #e2e8f0; - --sd-ui-font-family: Inter, sans-serif; +.sd-theme-docs { + --sd-ui-action: #your-brand-color; + --sd-ui-comments-card-bg: #f0f0f0; } ``` -See the [full variable reference](/guides/general/custom-themes#variable-reference) for every token. +## How the token system works -## SSR support +Variables are organized in three tiers. Higher tiers reference lower tiers, so changing a primitive cascades everywhere. -For server-side rendering, use `buildTheme()` to get the raw CSS string: +``` +Primitives UI semantic Component-specific +--sd-color-* --sd-ui-text --sd-ui-toolbar-* +--sd-font-size-* --sd-ui-bg --sd-ui-comments-* +--sd-radius-* --sd-ui-action --sd-ui-dropdown-* +``` -```javascript -import { buildTheme } from 'superdoc'; +**Tier 1 — Primitives.** Raw color scales, font sizes, and radii. You rarely need to touch these directly. -const { className, css } = buildTheme({ - colors: { action: '#6366f1', bg: '#ffffff', text: '#1e293b' }, -}); +**Tier 2 — UI semantic.** The "5-variable theme." Change these and every component updates: + +| Variable | Default | What it controls | +|----------|---------|-----------------| +| `--sd-ui-font-family` | `Arial, Helvetica, sans-serif` | Font for all UI chrome | +| `--sd-ui-text` | `#47484a` | Primary text color | +| `--sd-ui-bg` | `#ffffff` | Panels, cards, dropdowns | +| `--sd-ui-border` | `#dbdbdb` | All borders | +| `--sd-ui-action` | `#1355ff` | Buttons, links, active states | + +**Tier 3 — Component-specific.** Fine-grained overrides for individual components. These default to the semantic tier, so you only set them when a component needs to look different from the rest. See the [full reference](/guides/general/custom-themes). + +## JavaScript config -// Inject into your HTML template -const html = ` - - -
- -`; +The `trackChangesConfig` and `commentsConfig` JavaScript options still work. They take priority over CSS variables when set. Use the JS API for runtime changes, CSS variables for static theming. + +```javascript +new SuperDoc({ + selector: '#editor', + commentsConfig: { + highlightHoverColor: '#ff000055', + trackChangeHighlightColors: { + insertBorder: '#00ff00', + }, + }, +}); ``` ## Next steps @@ -119,7 +107,7 @@ const html = ` icon="palette" href="/guides/general/custom-themes" > - Full createTheme() API reference. Dark theme starter. All CSS variables by component. + Full variable reference by component. Dark theme starter. How to build your own preset.
` element. + +```css +.my-company-theme { + /* These cascade to every component */ + --sd-ui-action: #8b5cf6; + --sd-ui-action-hover: #7c3aed; + --sd-ui-text: #1e293b; + --sd-ui-bg: #f8fafc; + --sd-ui-border: #e2e8f0; + + /* Fine-tune where needed */ + --sd-ui-toolbar-bg: #f1f5f9; + --sd-ui-comments-card-bg: #f0f0ff; +} ``` -Start with `colors` only. Add component overrides when a specific component needs to look different from the rest. +Start with the 5-10 semantic variables. Add component-specific ones only where the defaults don't look right. -## Dark theme example - -```javascript -import { createTheme } from 'superdoc'; - -const dark = createTheme({ - name: 'dark', - colors: { - bg: '#1a1a2e', - hoverBg: '#2a2a3e', - activeBg: '#3a3a4e', - text: '#e2e8f0', - textMuted: '#94a3b8', - border: '#334155', - action: '#60a5fa', - actionHover: '#93c5fd', - }, - toolbar: { bg: '#0f172a', buttonText: '#e2e8f0' }, - comments: { cardBg: '#1e293b', bodyText: '#cbd5e1' }, -}); - -document.documentElement.classList.add(dark); -``` - -## Raw CSS alternative +## Dark theme starter -If you prefer writing CSS directly, set `--sd-*` variables on ``: +Override the core surface and text variables. Component backgrounds cascade from `--sd-ui-bg` automatically. ```css -:root { - --sd-ui-action: #6366f1; - --sd-ui-bg: #ffffff; - --sd-ui-text: #1e293b; - --sd-ui-border: #e2e8f0; - --sd-ui-font-family: Inter, sans-serif; +.dark-theme { + --sd-ui-bg: #1a1a2e; + --sd-ui-hover-bg: #2a2a3e; + --sd-ui-active-bg: #3a3a4e; + --sd-ui-text: #e2e8f0; + --sd-ui-text-muted: #94a3b8; + --sd-ui-border: #334155; + --sd-ui-action: #60a5fa; + --sd-ui-action-hover: #93c5fd; + + /* Toolbar */ + --sd-ui-toolbar-bg: #0f172a; + --sd-ui-toolbar-button-text: #e2e8f0; + --sd-ui-toolbar-button-hover-bg: #1e293b; + --sd-ui-toolbar-button-active-bg: #334155; + + /* Dropdowns */ + --sd-ui-dropdown-bg: #1e293b; + --sd-ui-dropdown-border: #334155; + --sd-ui-dropdown-hover-bg: #2d3a4e; + + /* Comments */ + --sd-ui-comments-card-bg: #1e293b; + --sd-ui-comments-card-active-bg: #0f172a; + --sd-ui-comments-input-bg: #1e293b; + --sd-ui-comments-author-text: #e2e8f0; + --sd-ui-comments-body-text: #cbd5e1; + --sd-ui-comments-timestamp-text: #64748b; } ``` diff --git a/packages/superdoc/src/core/theme/create-theme.js b/packages/superdoc/src/core/theme/create-theme.js deleted file mode 100644 index e7e3a309b2..0000000000 --- a/packages/superdoc/src/core/theme/create-theme.js +++ /dev/null @@ -1,267 +0,0 @@ -/** - * @typedef {Object} ThemeColors - * @property {string} [action] Action/accent color (buttons, links). Default: #1355ff - * @property {string} [actionHover] Action hover state. Default: derived from action - * @property {string} [bg] Default surface/background. Default: #ffffff - * @property {string} [hoverBg] Hover background. Default: #dbdbdb - * @property {string} [activeBg] Active/pressed background. Default: #c8d0d8 - * @property {string} [text] Primary text color. Default: #47484a - * @property {string} [textMuted] Secondary text. Default: #666666 - * @property {string} [border] Default border color. Default: #dbdbdb - */ - -/** - * @typedef {Object} ThemeToolbar - * @property {string} [bg] Toolbar background. Default: transparent - * @property {string} [buttonText] Button text/icon color - * @property {string} [buttonHoverBg] Button hover background - * @property {string} [buttonActiveBg] Button active background - * @property {string} [itemPadding] Padding inside each button - */ - -/** - * @typedef {Object} ThemeDropdown - * @property {string} [bg] Dropdown panel background - * @property {string} [border] Panel border - * @property {string} [text] Option text - * @property {string} [hoverBg] Option hover background - * @property {string} [activeBg] Option active background - * @property {string} [shadow] Panel shadow - */ - -/** - * @typedef {Object} ThemeMenu - * @property {string} [bg] Menu background - * @property {string} [text] Menu text - * @property {string} [border] Menu border - * @property {string} [itemHoverBg] Item hover background - * @property {string} [itemActiveBg] Active item background - * @property {string} [itemActiveText] Active item text - * @property {string} [shadow] Menu shadow - */ - -/** - * @typedef {Object} ThemeComments - * @property {string} [cardBg] Default card background. Default: #f3f6fd - * @property {string} [cardHoverBg] Hovered card background - * @property {string} [cardActiveBg] Selected/active card background - * @property {string} [cardResolvedBg] Resolved card background - * @property {string} [cardActiveBorder] Active card border - * @property {string} [cardRadius] Card border radius - * @property {string} [cardShadow] Card shadow - * @property {string} [separator] Divider between comments - * @property {string} [authorText] Author name color - * @property {string} [timestampText] Timestamp color - * @property {string} [bodyText] Comment body text color - * @property {string} [inputBg] Comment input background - * @property {string} [inputBorder] Comment input border - */ - -/** - * @typedef {Object} ThemeHighlights - * @property {string} [external] External comment highlight - * @property {string} [externalActive] Active external highlight - * @property {string} [externalFaded] Faded external highlight - * @property {string} [internal] Internal comment highlight - * @property {string} [internalActive] Active internal highlight - * @property {string} [internalFaded] Faded internal highlight - */ - -/** - * @typedef {Object} ThemeTrackedChanges - * @property {string} [insertBg] Insert highlight background - * @property {string} [insertBorder] Insert left border - * @property {string} [deleteBg] Delete highlight background - * @property {string} [deleteBorder] Delete left border - * @property {string} [formatBorder] Format change border - */ - -/** - * @typedef {Object} ThemeLayout - * @property {string} [pageBg] Page background - * @property {string} [pageShadow] Page shadow - */ - -/** - * @typedef {Object} ThemeConfig - * @property {string} [name] Optional theme name (used in the generated class name) - * @property {string} [font] UI font family - * @property {string} [radius] Default border radius - * @property {string} [shadow] Default shadow - * @property {ThemeColors} [colors] Core color palette - * @property {ThemeToolbar} [toolbar] Toolbar overrides - * @property {ThemeDropdown} [dropdown] Dropdown overrides - * @property {ThemeMenu} [menu] Context menu overrides - * @property {ThemeComments} [comments] Comments panel overrides - * @property {ThemeHighlights} [highlights] In-document comment highlight overrides - * @property {ThemeTrackedChanges} [trackedChanges] Tracked change decoration overrides - * @property {ThemeLayout} [layout] Page layout overrides - */ - -/** @type {Map} */ -const CONFIG_TO_VAR = new Map([ - // Colors (semantic tier) - ['colors.action', '--sd-ui-action'], - ['colors.actionHover', '--sd-ui-action-hover'], - ['colors.bg', '--sd-ui-bg'], - ['colors.hoverBg', '--sd-ui-hover-bg'], - ['colors.activeBg', '--sd-ui-active-bg'], - ['colors.text', '--sd-ui-text'], - ['colors.textMuted', '--sd-ui-text-muted'], - ['colors.border', '--sd-ui-border'], - - // Top-level shortcuts - ['font', '--sd-ui-font-family'], - ['radius', '--sd-ui-radius'], - ['shadow', '--sd-ui-shadow'], - - // Toolbar - ['toolbar.bg', '--sd-ui-toolbar-bg'], - ['toolbar.buttonText', '--sd-ui-toolbar-button-text'], - ['toolbar.buttonHoverBg', '--sd-ui-toolbar-button-hover-bg'], - ['toolbar.buttonActiveBg', '--sd-ui-toolbar-button-active-bg'], - ['toolbar.itemPadding', '--sd-ui-toolbar-item-padding'], - - // Dropdown - ['dropdown.bg', '--sd-ui-dropdown-bg'], - ['dropdown.border', '--sd-ui-dropdown-border'], - ['dropdown.text', '--sd-ui-dropdown-text'], - ['dropdown.hoverBg', '--sd-ui-dropdown-hover-bg'], - ['dropdown.activeBg', '--sd-ui-dropdown-active-bg'], - ['dropdown.shadow', '--sd-ui-dropdown-shadow'], - - // Menu - ['menu.bg', '--sd-ui-menu-bg'], - ['menu.text', '--sd-ui-menu-text'], - ['menu.border', '--sd-ui-menu-border'], - ['menu.itemHoverBg', '--sd-ui-menu-item-hover-bg'], - ['menu.itemActiveBg', '--sd-ui-menu-item-active-bg'], - ['menu.itemActiveText', '--sd-ui-menu-item-active-text'], - ['menu.shadow', '--sd-ui-menu-shadow'], - - // Comments - ['comments.cardBg', '--sd-ui-comments-card-bg'], - ['comments.cardHoverBg', '--sd-ui-comments-card-hover-bg'], - ['comments.cardActiveBg', '--sd-ui-comments-card-active-bg'], - ['comments.cardResolvedBg', '--sd-ui-comments-card-resolved-bg'], - ['comments.cardActiveBorder', '--sd-ui-comments-card-active-border'], - ['comments.cardRadius', '--sd-ui-comments-card-radius'], - ['comments.cardShadow', '--sd-ui-comments-card-shadow'], - ['comments.separator', '--sd-ui-comments-separator'], - ['comments.authorText', '--sd-ui-comments-author-text'], - ['comments.timestampText', '--sd-ui-comments-timestamp-text'], - ['comments.bodyText', '--sd-ui-comments-body-text'], - ['comments.inputBg', '--sd-ui-comments-input-bg'], - ['comments.inputBorder', '--sd-ui-comments-input-border'], - - // Highlights - ['highlights.external', '--sd-comments-highlight-external'], - ['highlights.externalActive', '--sd-comments-highlight-external-active'], - ['highlights.externalFaded', '--sd-comments-highlight-external-faded'], - ['highlights.internal', '--sd-comments-highlight-internal'], - ['highlights.internalActive', '--sd-comments-highlight-internal-active'], - ['highlights.internalFaded', '--sd-comments-highlight-internal-faded'], - - // Tracked changes - ['trackedChanges.insertBg', '--sd-tracked-changes-insert-background'], - ['trackedChanges.insertBorder', '--sd-tracked-changes-insert-border'], - ['trackedChanges.deleteBg', '--sd-tracked-changes-delete-background'], - ['trackedChanges.deleteBorder', '--sd-tracked-changes-delete-border'], - ['trackedChanges.formatBorder', '--sd-tracked-changes-format-border'], - - // Layout - ['layout.pageBg', '--sd-layout-page-bg'], - ['layout.pageShadow', '--sd-layout-page-shadow'], -]); - -let themeCounter = 0; - -/** - * Flatten a nested config object into dot-separated paths. - * @param {Record} obj - * @param {string} [prefix] - * @returns {Array<[string, string]>} - */ -function flattenConfig(obj, prefix = '') { - /** @type {Array<[string, string]>} */ - const entries = []; - for (const [key, value] of Object.entries(obj)) { - const path = prefix ? `${prefix}.${key}` : key; - if (value != null && typeof value === 'object' && !Array.isArray(value)) { - entries.push(...flattenConfig(value, path)); - } else if (value != null) { - entries.push([path, String(value)]); - } - } - return entries; -} - -/** - * Create a SuperDoc theme from a config object. - * - * Returns a CSS class name. Apply it to `` to activate the theme. - * The class is injected into the document automatically. - * - * @param {ThemeConfig} config - * @returns {string} The generated CSS class name - * - * @example - * ```js - * import { createTheme } from 'superdoc'; - * - * const theme = createTheme({ - * colors: { action: '#6366f1', bg: '#ffffff', text: '#1e293b' }, - * font: 'Inter, sans-serif', - * toolbar: { bg: '#f8fafc' }, - * }); - * - * document.documentElement.classList.add(theme); - * ``` - */ -export function createTheme(config) { - const { name, ...rest } = config; - const className = `sd-theme-${name || `custom-${++themeCounter}`}`; - - const flat = flattenConfig(rest); - const declarations = []; - - for (const [path, value] of flat) { - const varName = CONFIG_TO_VAR.get(path); - if (varName) { - declarations.push(` ${varName}: ${value};`); - } - } - - if (declarations.length === 0) return className; - - const css = `.${className} {\n${declarations.join('\n')}\n}`; - - // Inject into document if available (SSR-safe) - if (typeof document !== 'undefined') { - let style = document.querySelector(`[data-sd-theme="${className}"]`); - if (!style) { - style = document.createElement('style'); - style.setAttribute('data-sd-theme', className); - document.head.appendChild(style); - } - style.textContent = css; - } - - // Attach the raw CSS for SSR or manual injection - /** @type {string} */ - createTheme._lastCss = css; - - return className; -} - -/** - * Get the raw CSS string from the last createTheme() call. - * Useful for SSR where you need to inject styles into the HTML template. - * - * @param {ThemeConfig} config - * @returns {{ className: string, css: string }} - */ -export function buildTheme(config) { - const className = createTheme(config); - return { className, css: createTheme._lastCss || '' }; -} diff --git a/packages/superdoc/src/core/theme/create-theme.test.js b/packages/superdoc/src/core/theme/create-theme.test.js deleted file mode 100644 index 30582e2c65..0000000000 --- a/packages/superdoc/src/core/theme/create-theme.test.js +++ /dev/null @@ -1,105 +0,0 @@ -import { describe, expect, it, beforeEach } from 'vitest'; -import { createTheme, buildTheme } from './create-theme.js'; - -describe('createTheme', () => { - beforeEach(() => { - // Clean up injected styles between tests - document.querySelectorAll('[data-sd-theme]').forEach((el) => el.remove()); - }); - - it('returns a class name', () => { - const className = createTheme({ colors: { action: '#ff0000' } }); - expect(className).toMatch(/^sd-theme-/); - }); - - it('uses the provided name', () => { - const className = createTheme({ name: 'dark', colors: { bg: '#000' } }); - expect(className).toBe('sd-theme-dark'); - }); - - it('maps colors to CSS variables', () => { - const { css } = buildTheme({ - name: 'test-colors', - colors: { - action: '#6366f1', - bg: '#ffffff', - text: '#1e293b', - border: '#e2e8f0', - }, - }); - expect(css).toContain('--sd-ui-action: #6366f1'); - expect(css).toContain('--sd-ui-bg: #ffffff'); - expect(css).toContain('--sd-ui-text: #1e293b'); - expect(css).toContain('--sd-ui-border: #e2e8f0'); - }); - - it('maps top-level font and radius', () => { - const { css } = buildTheme({ - name: 'test-font', - font: 'Inter, sans-serif', - radius: '8px', - }); - expect(css).toContain('--sd-ui-font-family: Inter, sans-serif'); - expect(css).toContain('--sd-ui-radius: 8px'); - }); - - it('maps component-level toolbar overrides', () => { - const { css } = buildTheme({ - name: 'test-toolbar', - toolbar: { bg: '#f1f5f9', buttonText: '#333' }, - }); - expect(css).toContain('--sd-ui-toolbar-bg: #f1f5f9'); - expect(css).toContain('--sd-ui-toolbar-button-text: #333'); - }); - - it('maps component-level comments overrides', () => { - const { css } = buildTheme({ - name: 'test-comments', - comments: { cardBg: '#f0f0ff', inputBg: '#fff', separator: '#ddd' }, - }); - expect(css).toContain('--sd-ui-comments-card-bg: #f0f0ff'); - expect(css).toContain('--sd-ui-comments-input-bg: #fff'); - expect(css).toContain('--sd-ui-comments-separator: #ddd'); - }); - - it('maps tracked changes overrides', () => { - const { css } = buildTheme({ - name: 'test-tc', - trackedChanges: { insertBorder: '#00ff00', deleteBorder: '#ff0000' }, - }); - expect(css).toContain('--sd-tracked-changes-insert-border: #00ff00'); - expect(css).toContain('--sd-tracked-changes-delete-border: #ff0000'); - }); - - it('ignores null and undefined values', () => { - const { css } = buildTheme({ - name: 'test-null', - colors: { action: '#ff0000', bg: undefined, text: null }, - }); - expect(css).toContain('--sd-ui-action: #ff0000'); - expect(css).not.toContain('--sd-ui-bg'); - expect(css).not.toContain('--sd-ui-text'); - }); - - it('injects a style element into the document', () => { - const className = createTheme({ name: 'inject-test', colors: { action: '#abc' } }); - const style = document.querySelector(`[data-sd-theme="${className}"]`); - expect(style).not.toBeNull(); - expect(style.textContent).toContain('--sd-ui-action: #abc'); - }); - - it('updates existing style element on re-call with same name', () => { - createTheme({ name: 'reuse', colors: { action: '#111' } }); - createTheme({ name: 'reuse', colors: { action: '#222' } }); - const styles = document.querySelectorAll('[data-sd-theme="sd-theme-reuse"]'); - expect(styles.length).toBe(1); - expect(styles[0].textContent).toContain('#222'); - }); - - it('buildTheme returns both className and css', () => { - const result = buildTheme({ name: 'build-test', colors: { action: '#f00' } }); - expect(result.className).toBe('sd-theme-build-test'); - expect(result.css).toContain('.sd-theme-build-test'); - expect(result.css).toContain('--sd-ui-action: #f00'); - }); -}); diff --git a/packages/superdoc/src/index.js b/packages/superdoc/src/index.js index 19bad9bd5d..34047a9504 100644 --- a/packages/superdoc/src/index.js +++ b/packages/superdoc/src/index.js @@ -17,7 +17,6 @@ import { getSchemaIntrospection } from './helpers/schema-introspection.js'; // Public exports export { SuperDoc } from './core/SuperDoc.js'; -export { createTheme, buildTheme } from './core/theme/create-theme.js'; export { BlankDOCX, getFileObject,