diff --git a/CLAUDE.md b/CLAUDE.md index fcde499aa..06979efe1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -175,12 +175,15 @@ Pixel-level before/after comparison for documents that failed layout comparison. ## Brand & Design System -Brand guidelines, voice, and design tokens live in `brand/`. Token values are defined in `packages/superdoc/src/assets/styles/tokens.css`. +Brand guidelines, voice, and design tokens live in `brand/`. +Token contract source is `packages/superdoc/src/assets/styles/helpers/variables.css` (`:root` defaults). +Preset theme overrides are defined in `packages/superdoc/src/assets/styles/helpers/themes.css`. **When creating or modifying UI components:** -- Use `--sd-*` CSS custom properties — never hardcode hex values. See `tokens.css` for all available variables. -- Tokens follow three tiers: primitive (`--sd-color-blue-500`) → semantic (`--sd-action-primary`) → component (`--sd-comment-bg`). Components reference semantic or component-level variables. -- Expose component-specific variables as `--sd-{component}-*` so consumers can customize via CSS. -- Document component CSS variables in `apps/docs/ui-components/` (Mintlify docs). +- Use `--sd-*` CSS custom properties — never hardcode hex values. +- Treat `variables.css` as the canonical token contract; add new tokens there. +- Keep preset themes in `themes.css` (`.sd-theme-*`) and override only the tokens that need theme-specific values. +- Tokens are organized by layers: primitive (`--sd-color-blue-500`) → UI/document tokens (`--sd-ui-*`, `--sd-comments-*`, etc.) → component usage. +- Expose UI component-specific variables as `--sd-ui-{component}-*` so consumers can customize via CSS. **When writing copy or content:** see `brand/brand-guidelines.md` for voice, tone, and the dual-register pattern (developer vs. leader). Product name is always **SuperDoc** (capital S, capital D). diff --git a/brand/README.md b/brand/README.md index dae815d74..3a4ed2c80 100644 --- a/brand/README.md +++ b/brand/README.md @@ -14,17 +14,19 @@ brand/ ## Design tokens -Token values live in `packages/superdoc/src/assets/styles/tokens.css` as CSS custom properties (`--sd-*`). That file is the single source of truth. +Token defaults live in `packages/superdoc/src/assets/styles/helpers/variables.css` as CSS custom properties (`--sd-*`). +Preset theme overrides live in `packages/superdoc/src/assets/styles/helpers/themes.css`. +Together, these files are the design-token source of truth. -Tokens follow three tiers: -- **Primitive** (`--sd-color-blue-500`) — raw palette values -- **Semantic** (`--sd-action-primary`, `--sd-surface-card`) — UI roles that reference primitives -- **Component** (`--sd-comment-bg`) — component-specific overrides that reference semantic tokens +Tokens are organized by layers: +- **Primitive** (`--sd-color-blue-500`, `--sd-font-size-400`, `--sd-radius-100`) — raw design values +- **UI/Document semantic** (`--sd-ui-*`, `--sd-comments-*`, `--sd-tracked-changes-*`, `--sd-layout-*`) — role-based tokens used by components and rendering layers +- **Component-level (optional)** (`--sd-ui-{component}-*`) — local overrides for a specific UI component when cross-component tokens are not enough -Consumers customize SuperDoc by overriding `--sd-*` variables in their own CSS. Component customization is documented at `apps/docs/ui-components/`. +Consumers customize SuperDoc by overriding `--sd-*` variables in their own CSS. ## How to use -**For development**: Use semantic or component tokens in CSS — never hardcode hex values. When adding a new UI component, expose its visual properties as `--sd-{component}-*` variables in `tokens.css`. +**For development**: Use semantic or component tokens in CSS — never hardcode hex values. When adding a new UI component, expose its visual properties as `--sd-ui-{component}-*` variables in `variables.css`; add per-theme overrides in `themes.css` only when needed. **For marketing/content**: See `brand-guidelines.md` for voice, tone, and the dual-register pattern (developer vs. leader). diff --git a/brand/visual-identity.md b/brand/visual-identity.md index 85acb3cd5..20f2b3712 100644 --- a/brand/visual-identity.md +++ b/brand/visual-identity.md @@ -12,7 +12,7 @@ - **Don't place blue text on blue backgrounds** — maintain contrast ### Color scale -See `packages/superdoc/src/assets/styles/tokens.css` for the full blue scale (`--sd-color-blue-50` through `--sd-color-blue-900`). The scale moves from near-white (#EBF0FF) to near-black (#041133), with 500 being the canonical brand color. +See `packages/superdoc/src/assets/styles/helpers/variables.css` for the full blue scale (`--sd-color-blue-50` through `--sd-color-blue-900`). The scale moves from near-white (#EBF0FF) to near-black (#041133), with 500 being the canonical brand color. ## Logo @@ -82,7 +82,7 @@ No heavy shadows, no gradients on UI elements (gradients are reserved for market Dark mode is currently used on the homepage/marketing site. The product UI is light-only. -Dark mode token overrides are documented in `packages/superdoc/src/assets/styles/tokens.css`. +Theme overrides are defined in `packages/superdoc/src/assets/styles/helpers/themes.css`, while base token defaults are in `packages/superdoc/src/assets/styles/helpers/variables.css`. Key principle: dark backgrounds (#0B0C10) with reduced-brightness text (#E8E8E8), not pure white on pure black. diff --git a/packages/layout-engine/painters/dom/src/renderer.ts b/packages/layout-engine/painters/dom/src/renderer.ts index 16ae2cea5..0019cf810 100644 --- a/packages/layout-engine/painters/dom/src/renderer.ts +++ b/packages/layout-engine/painters/dom/src/renderer.ts @@ -591,11 +591,43 @@ 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; -const COMMENT_EXTERNAL_COLOR = '#B1124B'; -const COMMENT_INTERNAL_COLOR = '#078383'; -const COMMENT_INACTIVE_ALPHA = '40'; // ~25% for inactive -const COMMENT_ACTIVE_ALPHA = '66'; // ~40% for active/selected -const COMMENT_FADED_ALPHA = '20'; // ~12% for non-selected when another comment is active +type CommentHighlightToken = { + css: string; + 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', +}; type LinkRenderData = { href?: string; @@ -2595,7 +2627,7 @@ export class DomPainter { const base = this.options.pageStyles ?? {}; return { ...base, - background: base.background ?? '#fff', + background: base.background ?? 'var(--sd-layout-page-background, #fff)', boxShadow: 'none', border: 'none', margin: '0', @@ -4605,14 +4637,23 @@ export class DomPainter { const commentHighlight = getCommentHighlight(textRun, this.activeCommentId); if (commentHighlight.color && hasAnyComment) { - (elem as HTMLElement).style.backgroundColor = commentHighlight.color; - // Add thin visual indicator for nested comments when outer comment is selected - // Use box-shadow instead of border to avoid affecting text layout - if (commentHighlight.hasNestedComments && commentHighlight.baseColor) { - const borderColor = `${commentHighlight.baseColor}99`; // Semi-transparent for subtlety - (elem as HTMLElement).style.boxShadow = `inset 1px 0 0 ${borderColor}, inset -1px 0 0 ${borderColor}`; + const runElement = elem as HTMLElement; + const previousBackgroundColor = runElement.style.backgroundColor; + runElement.style.backgroundColor = commentHighlight.color.css; + // jsdom may drop var() values for inline style properties. + // Fall back to concrete color to keep rendering/tests stable. + if (!runElement.style.backgroundColor || runElement.style.backgroundColor === previousBackgroundColor) { + runElement.style.backgroundColor = commentHighlight.color.fallback; + } + // Add thin visual indicator for nested comments when outer comment is selected. + // Use box-shadow instead of border to avoid affecting text layout. + if (commentHighlight.hasNestedComments && commentHighlight.nestedBorderColor) { + runElement.style.boxShadow = `inset 1px 0 0 ${commentHighlight.nestedBorderColor.css}, inset -1px 0 0 ${commentHighlight.nestedBorderColor.css}`; + if (!runElement.style.boxShadow) { + runElement.style.boxShadow = `inset 1px 0 0 ${commentHighlight.nestedBorderColor.fallback}, inset -1px 0 0 ${commentHighlight.nestedBorderColor.fallback}`; + } } else { - (elem as HTMLElement).style.boxShadow = ''; + runElement.style.boxShadow = ''; } } // We still need to preserve the comment ids @@ -6958,8 +6999,8 @@ const applyRunStyles = (element: HTMLElement, run: Run, _isLink = false): void = }; interface CommentHighlightResult { - color?: string; - baseColor?: string; + color?: CommentHighlightToken; + nestedBorderColor?: CommentHighlightToken; hasNestedComments?: boolean; } @@ -7000,27 +7041,25 @@ const getCommentHighlight = (run: TextRun, activeCommentId: string | null): Comm matchesId(c as { commentId: string; importedId?: string }, activeCommentId), ); if (activeComment) { - const base = activeComment.internal ? COMMENT_INTERNAL_COLOR : COMMENT_EXTERNAL_COLOR; - // Check if there are OTHER comments besides the active one (nested comments) const nestedComments = comments.filter( (c) => !matchesId(c as { commentId: string; importedId?: string }, activeCommentId), ); return { - color: `${base}${COMMENT_ACTIVE_ALPHA}`, - baseColor: base, + color: activeComment.internal ? COMMENT_HIGHLIGHT_INTERNAL_ACTIVE : COMMENT_HIGHLIGHT_EXTERNAL_ACTIVE, + nestedBorderColor: activeComment.internal + ? COMMENT_HIGHLIGHT_INTERNAL_NESTED_BORDER + : COMMENT_HIGHLIGHT_EXTERNAL_NESTED_BORDER, hasNestedComments: nestedComments.length > 0, }; } // Active comment is set but this run does not belong to it - show faded highlight. const fadedPrimary = comments[0]; - const fadedBase = fadedPrimary.internal ? COMMENT_INTERNAL_COLOR : COMMENT_EXTERNAL_COLOR; - return { color: `${fadedBase}${COMMENT_FADED_ALPHA}` }; + return { color: fadedPrimary.internal ? COMMENT_HIGHLIGHT_INTERNAL_FADED : COMMENT_HIGHLIGHT_EXTERNAL_FADED }; } // No active comment - show uniform light highlight (like Word/Google Docs) const primary = comments[0]; - const base = primary.internal ? COMMENT_INTERNAL_COLOR : COMMENT_EXTERNAL_COLOR; - return { color: `${base}${COMMENT_INACTIVE_ALPHA}` }; + return { color: primary.internal ? COMMENT_HIGHLIGHT_INTERNAL : COMMENT_HIGHLIGHT_EXTERNAL }; }; /** diff --git a/packages/layout-engine/painters/dom/src/styles.ts b/packages/layout-engine/painters/dom/src/styles.ts index 3f9e39ba0..4895573b2 100644 --- a/packages/layout-engine/painters/dom/src/styles.ts +++ b/packages/layout-engine/painters/dom/src/styles.ts @@ -18,8 +18,8 @@ export type PageStyles = { }; export const DEFAULT_PAGE_STYLES: Required = { - background: '#fff', - boxShadow: '0 4px 20px rgba(15, 23, 42, 0.08)', + background: 'var(--sd-layout-page-background, #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', }; @@ -233,38 +233,38 @@ const TRACK_CHANGE_STYLES = ` } .superdoc-layout .track-insert-dec.highlighted { - border-top: 1px dashed #00853d; - border-bottom: 1px dashed #00853d; - background-color: #399c7222; + border-top: 1px dashed var(--sd-tracked-changes-insert-border, #00853d); + border-bottom: 1px dashed var(--sd-tracked-changes-insert-border, #00853d); + background-color: var(--sd-tracked-changes-insert-background, #399c7222); } .superdoc-layout .track-delete-dec.highlighted { - border-top: 1px dashed #cb0e47; - border-bottom: 1px dashed #cb0e47; - background-color: #cb0e4722; + border-top: 1px dashed var(--sd-tracked-changes-delete-border, #cb0e47); + border-bottom: 1px dashed var(--sd-tracked-changes-delete-border, #cb0e47); + background-color: var(--sd-tracked-changes-delete-background, #cb0e4722); text-decoration: line-through !important; text-decoration-thickness: 2px !important; } .superdoc-layout .track-format-dec.highlighted { - border-bottom: 2px solid gold; + border-bottom: 2px solid var(--sd-tracked-changes-format-border, gold); } .superdoc-layout .track-insert-dec.highlighted.track-change-focused { border-style: solid; border-width: 2px; - background-color: #399c7244; + background-color: var(--sd-tracked-changes-insert-background-focused, #399c7244); } .superdoc-layout .track-delete-dec.highlighted.track-change-focused { border-style: solid; border-width: 2px; - background-color: #cb0e4744; + background-color: var(--sd-tracked-changes-delete-background-focused, #cb0e4744); } .superdoc-layout .track-format-dec.highlighted.track-change-focused { border-bottom-width: 3px; - background-color: #ffd70033; + background-color: var(--sd-tracked-changes-format-background-focused, #ffd70033); } `; @@ -380,19 +380,19 @@ const SDT_CONTAINER_STYLES = ` } .superdoc-structured-content-block:not(.ProseMirror-selectednode):hover { - background-color: #f2f2f2; + background-color: var(--sd-content-controls-block-hover-background, #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: #f2f2f2; + background-color: var(--sd-content-controls-block-hover-background, #f2f2f2); border-color: transparent; } .superdoc-structured-content-block.ProseMirror-selectednode { - border-color: #629be7; + border-color: var(--sd-content-controls-block-border, #629be7); outline: none; } @@ -409,10 +409,11 @@ const SDT_CONTAINER_STYLES = ` min-width: 0; height: 18px; padding: 0 4px; - border: 1px solid #629be7; + border: 1px solid var(--sd-content-controls-label-border, #629be7); border-bottom: none; border-radius: 6px 6px 0 0; - background-color: #629be7ee; + background-color: var(--sd-content-controls-label-background, #629be7ee); + color: var(--sd-content-controls-label-text, #ffffff); box-sizing: border-box; z-index: 10; display: none; @@ -478,12 +479,12 @@ const SDT_CONTAINER_STYLES = ` /* Hover effect for inline structured content */ .superdoc-structured-content-inline:not(.ProseMirror-selectednode):hover { - background-color: #f2f2f2; + background-color: var(--sd-content-controls-inline-hover-background, #f2f2f2); border-color: transparent; } .superdoc-structured-content-inline.ProseMirror-selectednode { - border-color: #629be7; + border-color: var(--sd-content-controls-inline-border, #629be7); outline: none; background-color: transparent; } @@ -495,8 +496,9 @@ const SDT_CONTAINER_STYLES = ` transform: translateX(-50%); font-size: 11px; padding: 0 4px; - background-color: #629be7ee; - color: white; + border: 1px solid var(--sd-content-controls-label-border, #629be7); + background-color: var(--sd-content-controls-label-background, #629be7ee); + color: var(--sd-content-controls-label-text, #ffffff); border-radius: 4px; white-space: nowrap; z-index: 100; @@ -521,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: rgba(98, 155, 231, 0.08); + background-color: var(--sd-content-controls-lock-hover-background, rgba(98, 155, 231, 0.08)); z-index: 9999999; } diff --git a/packages/super-editor/src/assets/styles/elements/prosemirror.css b/packages/super-editor/src/assets/styles/elements/prosemirror.css index 855b69329..7f4b26248 100644 --- a/packages/super-editor/src/assets/styles/elements/prosemirror.css +++ b/packages/super-editor/src/assets/styles/elements/prosemirror.css @@ -241,21 +241,21 @@ https://github.com/ProseMirror/prosemirror-tables/blob/master/demo/index.html } .sd-editor-scoped .ProseMirror .track-insert-dec.highlighted { - border-top: 1px dashed var(--sd-track-insert-border, #00853d); - border-bottom: 1px dashed var(--sd-track-insert-border, #00853d); - background-color: var(--sd-track-insert-bg, #399c7222); + border-top: 1px dashed var(--sd-tracked-changes-insert-border, #00853d); + border-bottom: 1px dashed var(--sd-tracked-changes-insert-border, #00853d); + background-color: var(--sd-tracked-changes-insert-background, #399c7222); } .sd-editor-scoped .ProseMirror .track-delete-dec.highlighted { - border-top: 1px dashed var(--sd-track-delete-border, #cb0e47); - border-bottom: 1px dashed var(--sd-track-delete-border, #cb0e47); - background-color: var(--sd-track-delete-bg, #cb0e4722); + border-top: 1px dashed var(--sd-tracked-changes-delete-border, #cb0e47); + border-bottom: 1px dashed var(--sd-tracked-changes-delete-border, #cb0e47); + background-color: var(--sd-tracked-changes-delete-background, #cb0e4722); text-decoration: line-through !important; text-decoration-thickness: 2px !important; } .sd-editor-scoped .ProseMirror .track-format-dec.highlighted { - border-bottom: 2px solid var(--sd-track-format-border, gold); + border-bottom: 2px solid var(--sd-tracked-changes-format-border, gold); } .sd-editor-scoped .ProseMirror .track-delete-widget { diff --git a/packages/super-editor/src/assets/styles/extensions/comments.css b/packages/super-editor/src/assets/styles/extensions/comments.css index 9b4ab407e..3c75c319a 100644 --- a/packages/super-editor/src/assets/styles/extensions/comments.css +++ b/packages/super-editor/src/assets/styles/extensions/comments.css @@ -3,7 +3,7 @@ } .sd-editor-comment-highlight:hover { - background-color: var(--sd-comment-highlight-hover, #1354ff55); + background-color: var(--sd-comments-highlight-hover, #1354ff55); } .sd-editor-comment-highlight.sd-custom-selection { 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 8d80d5d43..61376dc51 100644 --- a/packages/super-editor/src/assets/styles/extensions/structured-content.css +++ b/packages/super-editor/src/assets/styles/extensions/structured-content.css @@ -13,15 +13,21 @@ } /* Hover (not selected): light grey background, no handle */ -.super-editor .sd-structured-content:not(.ProseMirror-selectednode):hover, +.super-editor .sd-structured-content:not(.ProseMirror-selectednode):hover { + background-color: var(--sd-content-controls-inline-hover-background, #f2f2f2); +} + .super-editor .sd-structured-content-block:not(.ProseMirror-selectednode):hover { - background-color: #f2f2f2; + background-color: var(--sd-content-controls-block-hover-background, #f2f2f2); } /* Selected: border + handle visible */ -.super-editor .sd-structured-content.ProseMirror-selectednode, +.super-editor .sd-structured-content.ProseMirror-selectednode { + border-color: var(--sd-content-controls-inline-border, #629be7); +} + .super-editor .sd-structured-content-block.ProseMirror-selectednode { - border-color: #629be7; + border-color: var(--sd-content-controls-block-border, #629be7); } .super-editor .sd-structured-content-draggable { @@ -36,10 +42,11 @@ min-width: 0; height: 18px; padding: 0 4px; - border: 1px solid #629be7; + border: 1px solid var(--sd-content-controls-label-border, #629be7); border-bottom: none; border-radius: 6px 6px 0 0; - background-color: #629be7dd; + background-color: var(--sd-content-controls-label-background, #629be7ee); + color: var(--sd-content-controls-label-text, #ffffff); box-sizing: border-box; z-index: 10; cursor: grab; diff --git a/packages/super-editor/src/components/context-menu/ContextMenu.vue b/packages/super-editor/src/components/context-menu/ContextMenu.vue index 4147d044b..80e489ba8 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: #47484a; - background: white; - box-shadow: - 0 0 0 1px rgba(0, 0, 0, 0.05), - 0px 10px 20px rgba(0, 0, 0, 0.1); + 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); + 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)); margin-top: 0.5rem; - font-size: 12px; + font-size: var(--sd-ui-context-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 #eee; + border-bottom: 1px solid var(--sd-ui-context-menu-border, #eee); } .context-menu-search input { width: 100%; padding: 0.25rem 0.5rem; - border: 1px solid #ddd; + border: 1px solid var(--sd-ui-context-menu-input-border, #ddd); outline: none; } .context-menu-search input:focus { - border-color: #0096fd; + border-color: var(--sd-ui-context-menu-input-focus-border, #0096fd); } /* Remove unused group styles */ @@ -584,13 +584,13 @@ onBeforeUnmount(() => { } .context-menu-item:hover { - background: #f5f5f5; + background: var(--sd-ui-context-menu-item-hover-surface, #f5f5f5); } .context-menu-item.is-selected { - background: #edf6ff; - color: #0096fd; - fill: #0096fd; + 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); } .context-menu-item-icon { @@ -617,17 +617,15 @@ onBeforeUnmount(() => { } .popover { - background: white; - border-radius: 6px; - box-shadow: - 0 0 0 1px rgba(0, 0, 0, 0.05), - 0px 10px 20px rgba(0, 0, 0, 0.1); + 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)); z-index: 100; } .context-menu-divider { height: 1px; - background: #eee; + background: var(--sd-ui-context-menu-border, #eee); margin: 4px 0; } diff --git a/packages/super-editor/src/components/rulers/Ruler.vue b/packages/super-editor/src/components/rulers/Ruler.vue index 517b312f5..8d1409860 100644 --- a/packages/super-editor/src/components/rulers/Ruler.vue +++ b/packages/super-editor/src/components/rulers/Ruler.vue @@ -527,7 +527,7 @@ onUnmounted(() => { position: absolute; top: -16px; left: -2px; - font-size: 10px; + font-size: var(--sd-ui-font-size-50, 10px); pointer-events: none; user-select: none; } diff --git a/packages/super-editor/src/components/toolbar/AIWriter.vue b/packages/super-editor/src/components/toolbar/AIWriter.vue index c75db9fbb..74280a9a2 100644 --- a/packages/super-editor/src/components/toolbar/AIWriter.vue +++ b/packages/super-editor/src/components/toolbar/AIWriter.vue @@ -419,7 +419,7 @@ const handleInput = (event) => { padding-left: 8px; width: 100%; color: #47484a; - font-size: 12px; + font-size: var(--sd-ui-font-size-200, 12px); border: none; background: transparent; outline: none; @@ -449,7 +449,7 @@ const handleInput = (event) => { .ai-textarea-icon { display: block; font-weight: 800; - font-size: 14px; + font-size: var(--sd-ui-font-size-400, 14px); width: 16px; height: 16px; } diff --git a/packages/super-editor/src/components/toolbar/AlignmentButtons.vue b/packages/super-editor/src/components/toolbar/AlignmentButtons.vue index a621308cc..db67cdb9e 100644 --- a/packages/super-editor/src/components/toolbar/AlignmentButtons.vue +++ b/packages/super-editor/src/components/toolbar/AlignmentButtons.vue @@ -105,17 +105,17 @@ onMounted(() => { .button-icon { cursor: pointer; padding: 5px; - font-size: 16px; + font-size: var(--sd-ui-font-size-600, 16px); width: 25px; height: 25px; - border-radius: 4px; + border-radius: var(--sd-ui-dropdown-option-radius, 3px); display: flex; justify-content: center; align-items: center; box-sizing: border-box; &:hover { - background-color: #d8dee5; + background-color: var(--sd-ui-dropdown-surface-hover, #d8dee5); } :deep(svg) { diff --git a/packages/super-editor/src/components/toolbar/DocumentMode.vue b/packages/super-editor/src/components/toolbar/DocumentMode.vue index 021e83597..c2022de4e 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: white; + background-color: var(--sd-ui-dropdown-surface, #ffffff); padding: 10px; - border-radius: 4px; + border-radius: var(--sd-ui-dropdown-option-radius, 3px); cursor: pointer; box-sizing: border-box; &:hover { - background-color: #c8d0d8; + background-color: var(--sd-ui-dropdown-surface-hover, #d8dee5); } } @@ -147,8 +147,8 @@ onMounted(() => { .document-mode-type { font-weight: 400; - font-size: 15px; - color: #222; + font-size: var(--sd-ui-font-size-500, 15px); + color: var(--sd-ui-text, #47484a); } .icon-column { @@ -156,7 +156,7 @@ onMounted(() => { justify-content: flex-start; align-items: center; padding: 0 5px; - color: black; + color: var(--sd-ui-text, #47484a); height: 100%; box-sizing: border-box; @@ -166,7 +166,7 @@ onMounted(() => { align-items: center; flex-shrink: 0; height: 18px; - color: #47484a; + color: var(--sd-ui-text, #47484a); } } @@ -177,7 +177,7 @@ onMounted(() => { } .document-mode-description { - font-size: 12px; - color: #666; + font-size: var(--sd-ui-font-size-200, 12px); + color: var(--sd-ui-text-muted, #666666); } diff --git a/packages/super-editor/src/components/toolbar/IconGrid.vue b/packages/super-editor/src/components/toolbar/IconGrid.vue index 946a5f865..221d7e3fa 100644 --- a/packages/super-editor/src/components/toolbar/IconGrid.vue +++ b/packages/super-editor/src/components/toolbar/IconGrid.vue @@ -53,7 +53,7 @@ const handleSelect = (option) => { diff --git a/packages/super-editor/src/components/toolbar/OverflowMenu.vue b/packages/super-editor/src/components/toolbar/OverflowMenu.vue index 9f668d40d..caa3a2d5e 100644 --- a/packages/super-editor/src/components/toolbar/OverflowMenu.vue +++ b/packages/super-editor/src/components/toolbar/OverflowMenu.vue @@ -72,24 +72,27 @@ onBeforeUnmount(() => { diff --git a/packages/super-editor/src/components/toolbar/TableGrid.vue b/packages/super-editor/src/components/toolbar/TableGrid.vue index 6c3c825cd..fb92815bb 100644 --- a/packages/super-editor/src/components/toolbar/TableGrid.vue +++ b/packages/super-editor/src/components/toolbar/TableGrid.vue @@ -157,13 +157,13 @@ onMounted(() => { .toolbar-table-grid__item { width: 20px; height: 20px; - border: 1px solid #d3d3d3; + border: 1px solid var(--sd-ui-border, #dbdbdb); cursor: pointer; transition: all 0.15s; } .toolbar-table-grid__item.selected { - background-color: #dbdbdb; + background-color: var(--sd-ui-dropdown-surface-hover, #d8dee5); } &.high-contrast { @@ -177,7 +177,7 @@ onMounted(() => { } .toolbar-table-grid-value { - font-size: 13px; + font-size: var(--sd-ui-font-size-300, 13px); line-height: 1.1; padding: 0px 8px 2px; } diff --git a/packages/super-editor/src/components/toolbar/Toolbar.vue b/packages/super-editor/src/components/toolbar/Toolbar.vue index ccd1b9d12..21fa4d414 100644 --- a/packages/super-editor/src/components/toolbar/Toolbar.vue +++ b/packages/super-editor/src/components/toolbar/Toolbar.vue @@ -118,7 +118,8 @@ const restoreSelection = () => { display: flex; width: 100%; justify-content: space-between; - padding: 4px 16px; + background: var(--sd-ui-toolbar-background, 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 26a6f4a27..7dadb8247 100644 --- a/packages/super-editor/src/components/toolbar/ToolbarButton.vue +++ b/packages/super-editor/src/components/toolbar/ToolbarButton.vue @@ -158,7 +158,7 @@ const caretIcon = computed(() => { position: relative; z-index: 1; min-width: 30px; - margin: 0 1px; + margin: 0 calc(var(--sd-ui-toolbar-item-gap, 2px) / 2); } .visually-hidden { @@ -170,16 +170,16 @@ const caretIcon = computed(() => { } .toolbar-button { - padding: 5px; - height: 32px; - max-height: 32px; - border-radius: 6px; + padding: var(--sd-ui-toolbar-item-padding, 5px); + height: var(--sd-ui-toolbar-height, 32px); + max-height: var(--sd-ui-toolbar-height, 32px); + border-radius: var(--sd-ui-radius, 6px); overflow-y: visible; display: flex; align-items: center; justify-content: center; cursor: pointer; - color: #47484a; + color: var(--sd-ui-toolbar-button-text, #47484a); transition: all 0.2s ease-out; user-select: none; position: relative; @@ -187,7 +187,7 @@ const caretIcon = computed(() => { } .toolbar-button:hover { - background-color: #dbdbdb; + background-color: var(--sd-ui-toolbar-button-hover-surface, var(--sd-ui-surface-hover, #dbdbdb)); .toolbar-icon { &.high-contrast { @@ -203,7 +203,7 @@ const caretIcon = computed(() => { .toolbar-button:active, .active { - background-color: #c8d0d8; + background-color: var(--sd-ui-toolbar-button-active-surface, var(--sd-ui-surface-active, #c8d0d8)); } .button-label { @@ -213,7 +213,7 @@ const caretIcon = computed(() => { text-overflow: ellipsis; white-space: nowrap; font-weight: 400; - font-size: 15px; + font-size: var(--sd-ui-font-size-500, 15px); margin: 5px; } @@ -255,17 +255,17 @@ const caretIcon = computed(() => { } .button-text-input { - color: #47484a; + color: var(--sd-ui-toolbar-button-text, #47484a); border-radius: 4px; text-align: center; width: 30px; - font-size: 14px; + font-size: var(--sd-ui-font-size-400, 14px); margin-right: 5px; font-weight: 400; background-color: transparent; padding: 2px 0; outline: none; - border: 1px solid #d8dee5; + border: 1px solid var(--sd-ui-border, #dbdbdb); box-sizing: border-box; &.high-contrast { @@ -278,7 +278,7 @@ const caretIcon = computed(() => { } .button-text-input::placeholder { - color: #47484a; + color: var(--sd-ui-toolbar-button-text, #47484a); } .dropdown-caret { diff --git a/packages/super-editor/src/components/toolbar/ToolbarButtonIcon.vue b/packages/super-editor/src/components/toolbar/ToolbarButtonIcon.vue index d91b98953..d0f673915 100644 --- a/packages/super-editor/src/components/toolbar/ToolbarButtonIcon.vue +++ b/packages/super-editor/src/components/toolbar/ToolbarButtonIcon.vue @@ -64,15 +64,6 @@ const hasColorBar = computed(() => { margin-top: -3px; } -.toolbar-button:hover { - color: black; - background-color: #d8dee5; -} -.toolbar-button:active, -.active { - background-color: #c8d0d8; -} - .color-bar { border-radius: 4px; position: absolute; diff --git a/packages/super-editor/src/components/toolbar/ToolbarDropdown.vue b/packages/super-editor/src/components/toolbar/ToolbarDropdown.vue index afdc2ffb4..d79577b25 100644 --- a/packages/super-editor/src/components/toolbar/ToolbarDropdown.vue +++ b/packages/super-editor/src/components/toolbar/ToolbarDropdown.vue @@ -373,10 +373,10 @@ onBeforeUnmount(() => { .toolbar-dropdown-menu { min-width: 80px; padding: 4px; - border-radius: 8px; - background: #fff; - border: 1px solid #e4e6eb; - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.16); + border-radius: var(--sd-ui-radius, 6px); + background: var(--sd-ui-dropdown-surface, #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; } @@ -389,9 +389,9 @@ onBeforeUnmount(() => { display: flex; align-items: center; padding: 0 10px; - border-radius: 3px; + border-radius: var(--sd-ui-dropdown-option-radius, 3px); cursor: pointer; - font-size: 14px; + font-size: var(--sd-ui-font-size-400, 14px); transition: background-color 0.2s ease-out; box-sizing: border-box; } @@ -413,11 +413,11 @@ onBeforeUnmount(() => { } .toolbar-dropdown-option:hover { - background: #d8dee5; + background: var(--sd-ui-dropdown-surface-hover, #d8dee5); } .toolbar-dropdown-option.selected { - background: #d8dee5; + background: var(--sd-ui-dropdown-surface-active, #d8dee5); } .toolbar-dropdown-menu.high-contrast .toolbar-dropdown-option:not(.render):hover { diff --git a/packages/super-editor/src/components/toolbar/ToolbarSeparator.vue b/packages/super-editor/src/components/toolbar/ToolbarSeparator.vue index 059a16927..3a5efee57 100644 --- a/packages/super-editor/src/components/toolbar/ToolbarSeparator.vue +++ b/packages/super-editor/src/components/toolbar/ToolbarSeparator.vue @@ -14,7 +14,7 @@ const getSeparatorColor = () => { if (isHighContrastMode.value) { return '#000'; } - return '#dbdbdb'; + return 'var(--sd-ui-border, #dbdbdb)'; }; @@ -26,13 +26,13 @@ const getSeparatorColor = () => { diff --git a/packages/superdoc/src/SuperDoc.test.js b/packages/superdoc/src/SuperDoc.test.js index bcb74c01e..5688b9fb3 100644 --- a/packages/superdoc/src/SuperDoc.test.js +++ b/packages/superdoc/src/SuperDoc.test.js @@ -957,9 +957,9 @@ describe('SuperDoc.vue', () => { const styleVars = wrapper.vm.superdocStyleVars; // Active insertBorder should be overridden - expect(styleVars['--sd-track-insert-border']).toBe('#ff0000'); + expect(styleVars['--sd-tracked-changes-insert-border']).toBe('#ff0000'); // deleteBackground should be inherited from base config - expect(styleVars['--sd-track-delete-bg']).toBe('#0000ff'); + expect(styleVars['--sd-tracked-changes-delete-background']).toBe('#0000ff'); }); it('sets track change CSS vars from base config when no active config provided', async () => { @@ -977,9 +977,9 @@ describe('SuperDoc.vue', () => { const styleVars = wrapper.vm.superdocStyleVars; - expect(styleVars['--sd-track-insert-border']).toBe('#11ff11'); - expect(styleVars['--sd-track-delete-border']).toBe('#ff1111'); - expect(styleVars['--sd-track-format-border']).toBe('#1111ff'); + expect(styleVars['--sd-tracked-changes-insert-border']).toBe('#11ff11'); + expect(styleVars['--sd-tracked-changes-delete-border']).toBe('#ff1111'); + expect(styleVars['--sd-tracked-changes-format-border']).toBe('#1111ff'); }); it('sets comment highlight hover color CSS var', async () => { @@ -992,6 +992,6 @@ describe('SuperDoc.vue', () => { await nextTick(); const styleVars = wrapper.vm.superdocStyleVars; - expect(styleVars['--sd-comment-highlight-hover']).toBe('#abcdef88'); + expect(styleVars['--sd-comments-highlight-hover']).toBe('#abcdef88'); }); }); diff --git a/packages/superdoc/src/SuperDoc.vue b/packages/superdoc/src/SuperDoc.vue index d7d74d4f2..c34e4a6ee 100644 --- a/packages/superdoc/src/SuperDoc.vue +++ b/packages/superdoc/src/SuperDoc.vue @@ -127,7 +127,7 @@ const superdocStyleVars = computed(() => { if (!commentsConfig || commentsConfig === false) return vars; if (commentsConfig.highlightHoverColor) { - vars['--sd-comment-highlight-hover'] = commentsConfig.highlightHoverColor; + vars['--sd-comments-highlight-hover'] = commentsConfig.highlightHoverColor; } const trackChangeColors = commentsConfig.trackChangeHighlightColors || {}; @@ -135,11 +135,16 @@ const superdocStyleVars = computed(() => { ...trackChangeColors, ...(commentsConfig.trackChangeActiveHighlightColors || {}), }; - if (activeTrackChangeColors.insertBorder) vars['--sd-track-insert-border'] = activeTrackChangeColors.insertBorder; - if (activeTrackChangeColors.insertBackground) vars['--sd-track-insert-bg'] = activeTrackChangeColors.insertBackground; - if (activeTrackChangeColors.deleteBorder) vars['--sd-track-delete-border'] = activeTrackChangeColors.deleteBorder; - if (activeTrackChangeColors.deleteBackground) vars['--sd-track-delete-bg'] = activeTrackChangeColors.deleteBackground; - if (activeTrackChangeColors.formatBorder) vars['--sd-track-format-border'] = activeTrackChangeColors.formatBorder; + if (activeTrackChangeColors.insertBorder) + vars['--sd-tracked-changes-insert-border'] = activeTrackChangeColors.insertBorder; + if (activeTrackChangeColors.insertBackground) + vars['--sd-tracked-changes-insert-background'] = activeTrackChangeColors.insertBackground; + if (activeTrackChangeColors.deleteBorder) + vars['--sd-tracked-changes-delete-border'] = activeTrackChangeColors.deleteBorder; + if (activeTrackChangeColors.deleteBackground) + vars['--sd-tracked-changes-delete-background'] = activeTrackChangeColors.deleteBackground; + if (activeTrackChangeColors.formatBorder) + vars['--sd-tracked-changes-format-border'] = activeTrackChangeColors.formatBorder; return vars; }); @@ -1226,26 +1231,17 @@ const getPDFViewer = () => { z-index: 3; display: flex; flex-direction: column; - gap: 6px; -} - -.tools .tool-icon { - font-size: 20px; - border-radius: 12px; - border: none; - outline: none; - background-color: #dbdbdb; - cursor: pointer; + gap: var(--sd-ui-tools-gap, 6px); } .tools-item { display: flex; align-items: center; justify-content: center; - width: 50px; - height: 50px; - background-color: rgba(219, 219, 219, 0.6); - border-radius: 12px; + 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)); + border-radius: var(--sd-ui-tools-item-radius, 12px); cursor: pointer; position: relative; } @@ -1255,8 +1251,8 @@ const getPDFViewer = () => { } .superdoc__tools-icon { - width: 20px; - height: 20px; + width: var(--sd-ui-tools-icon-size, 20px); + height: var(--sd-ui-tools-icon-size, 20px); flex-shrink: 0; } @@ -1304,45 +1300,6 @@ const getPDFViewer = () => { z-index: 50; } */ -/* Tools styles */ -.tools { - position: absolute; - z-index: 3; - display: flex; - gap: 6px; -} - -.tools .tool-icon { - font-size: 20px; - border-radius: 12px; - border: none; - outline: none; - background-color: #dbdbdb; - cursor: pointer; -} - -.tools-item { - display: flex; - align-items: center; - justify-content: center; - position: relative; - width: 50px; - height: 50px; - background-color: rgba(219, 219, 219, 0.6); - border-radius: 12px; - cursor: pointer; -} - -.tools-item i { - cursor: pointer; -} - -.superdoc__tools-icon { - width: 20px; - height: 20px; - flex-shrink: 0; -} - .ai-tool > svg { fill: transparent; } diff --git a/packages/superdoc/src/assets/styles/helpers/_all.css b/packages/superdoc/src/assets/styles/helpers/_all.css index 470745bdc..9ebb66bcd 100644 --- a/packages/superdoc/src/assets/styles/helpers/_all.css +++ b/packages/superdoc/src/assets/styles/helpers/_all.css @@ -1 +1,2 @@ @import './variables.css'; +@import './themes.css'; diff --git a/packages/superdoc/src/assets/styles/helpers/themes.css b/packages/superdoc/src/assets/styles/helpers/themes.css new file mode 100644 index 000000000..7baed5d8a --- /dev/null +++ b/packages/superdoc/src/assets/styles/helpers/themes.css @@ -0,0 +1,166 @@ +/* Preset theme: Google Docs-like */ +.sd-theme-google-docs { + /* UI base */ + --sd-ui-font-family: Arial, sans-serif; + --sd-ui-text: #202124; + --sd-ui-surface: #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-button-text: #3c4043; + --sd-ui-toolbar-button-hover-surface: #e8eaed; + --sd-ui-toolbar-button-active-surface: #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-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; + + /* 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-comments-highlight-external-base: #f9ab00; + --sd-comments-highlight-external: #f9ab0033; + --sd-comments-highlight-external-active: #f9ab0044; + --sd-comments-highlight-external-faded: #f9ab001f; + --sd-comments-highlight-external-nested-border: #f9ab0088; + --sd-comments-highlight-internal-base: #188038; + --sd-comments-highlight-internal: #18803833; + --sd-comments-highlight-internal-active: #18803844; + --sd-comments-highlight-internal-faded: #1880381f; + --sd-comments-highlight-internal-nested-border: #18803888; + + /* Layout */ + --sd-layout-page-background: #ffffff; + --sd-layout-page-shadow: 0 1px 2px rgba(60, 64, 67, 0.2), 0 2px 6px rgba(60, 64, 67, 0.12); +} + +/* Preset theme: Microsoft Word-like */ +.sd-theme-word { + /* UI base */ + --sd-ui-font-family: 'Segoe UI', Arial, sans-serif; + --sd-ui-text: #323130; + --sd-ui-surface: #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-button-text: #201f1e; + --sd-ui-toolbar-button-hover-surface: #e8e6e4; + --sd-ui-toolbar-button-active-surface: #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-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; + + /* 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-comments-highlight-external-base: #8a8886; + --sd-comments-highlight-external: #8a888633; + --sd-comments-highlight-external-active: #8a888655; + --sd-comments-highlight-external-faded: #8a88861f; + --sd-comments-highlight-external-nested-border: #8a888688; + --sd-comments-highlight-internal-base: #0f6cbd; + --sd-comments-highlight-internal: #0f6cbd33; + --sd-comments-highlight-internal-active: #0f6cbd55; + --sd-comments-highlight-internal-faded: #0f6cbd1f; + --sd-comments-highlight-internal-nested-border: #0f6cbd88; + + /* Layout */ + --sd-layout-page-background: #ffffff; + --sd-layout-page-shadow: 0 1px 2px rgba(0, 0, 0, 0.16), 0 6px 12px rgba(0, 0, 0, 0.08); +} + +/* Preset theme: Blueprint */ +.sd-theme-blueprint { + /* UI base */ + --sd-ui-font-family: 'IBM Plex Sans', 'Segoe UI', Arial, sans-serif; + --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-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-button-text: #0f172a; + --sd-ui-toolbar-button-hover-surface: #c9dced; + --sd-ui-toolbar-button-active-surface: #b7cee3; + --sd-ui-dropdown-surface: #ffffff; + --sd-ui-dropdown-border: #8ea5b8; + --sd-ui-dropdown-surface-hover: #e7f0f8; + --sd-ui-dropdown-surface-active: #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); + + /* Layout */ + --sd-layout-page-background: #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; + + /* Highlights */ + --sd-comments-highlight-external-base: #b45309; + --sd-comments-highlight-external: #b4530933; + --sd-comments-highlight-external-active: #b4530944; + --sd-comments-highlight-external-faded: #b453091f; + --sd-comments-highlight-external-nested-border: #b4530988; + --sd-comments-highlight-internal-base: #0f766e; + --sd-comments-highlight-internal: #0f766e33; + --sd-comments-highlight-internal-active: #0f766e44; + --sd-comments-highlight-internal-faded: #0f766e1f; + --sd-comments-highlight-internal-nested-border: #0f766e88; +} diff --git a/packages/superdoc/src/assets/styles/helpers/variables.css b/packages/superdoc/src/assets/styles/helpers/variables.css index d9bb9236d..4a2856d7c 100644 --- a/packages/superdoc/src/assets/styles/helpers/variables.css +++ b/packages/superdoc/src/assets/styles/helpers/variables.css @@ -1,3 +1,183 @@ :root { - /* CSS variables */ + /* SuperDoc CSS Variables */ + + /* Primitive: colors */ + --sd-color-blue-50: #ebf0ff; + --sd-color-blue-100: #d6e1ff; + --sd-color-blue-200: #adc3ff; + --sd-color-blue-300: #85a5ff; + --sd-color-blue-400: #5c87ff; + --sd-color-blue-500: #1355ff; + --sd-color-blue-600: #0f44cc; + --sd-color-blue-700: #0b3399; + --sd-color-blue-800: #082266; + --sd-color-blue-900: #041133; + + --sd-color-gray-50: #fafafa; + --sd-color-gray-100: #f5f5f5; + --sd-color-gray-200: #f2f2f2; + --sd-color-gray-300: #e0e0e0; + --sd-color-gray-400: #dbdbdb; + --sd-color-gray-500: #ababab; + --sd-color-gray-600: #888888; + --sd-color-gray-700: #666666; + --sd-color-gray-800: #424242; + --sd-color-gray-900: #212121; + + --sd-color-red-500: #ed4337; + --sd-color-green-500: #00853d; + --sd-color-rose-500: #cb0e47; + + /* Primitive: font-size */ + --sd-font-size-50: 10px; + --sd-font-size-100: 11px; + --sd-font-size-200: 12px; + --sd-font-size-300: 13px; + --sd-font-size-400: 14px; + --sd-font-size-500: 15px; + --sd-font-size-600: 16px; + + /* Primitive: radius */ + --sd-radius-50: 4px; + --sd-radius-100: 6px; + --sd-radius-200: 8px; + --sd-radius-300: 12px; + + /* UI: general */ + --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-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-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-shadow: 0 4px 12px rgba(0, 0, 0, 0.12); + --sd-ui-radius: var(--sd-radius-100); + + --sd-ui-font-size-50: var(--sd-font-size-50); + --sd-ui-font-size-100: var(--sd-font-size-100); + --sd-ui-font-size-200: var(--sd-font-size-200); + --sd-ui-font-size-300: var(--sd-font-size-300); + --sd-ui-font-size-400: var(--sd-font-size-400); + --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; + --sd-ui-dropdown-option-radius: 3px; + --sd-ui-dropdown-surface-hover: #d8dee5; + --sd-ui-dropdown-surface-active: #d8dee5; + --sd-ui-dropdown-shadow: 0 8px 24px rgba(0, 0, 0, 0.16); + + /* UI: tooltip */ + --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 */ + --sd-ui-toolbar-height: 32px; + --sd-ui-toolbar-padding-x: 16px; + --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-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); + + /* UI: context menu */ + --sd-ui-context-menu-surface: #ffffff; + --sd-ui-context-menu-text: #47484a; + --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; + + /* 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-icon-size: 20px; + + /* UI: comments panel */ + --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-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-transition: all 200ms ease; + --sd-ui-comments-panel-author-text: var(--sd-color-gray-900); + --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-background: var(--sd-color-gray-200); + --sd-ui-comments-panel-timestamp-text: var(--sd-color-gray-600); + --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-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-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-hover-background: #f3f3f5; + --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; + + /* Styles: comments */ + --sd-comments-highlight-external-base: #b1124b; + --sd-comments-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-active: #07838366; + --sd-comments-highlight-internal-faded: #07838320; + --sd-comments-highlight-external-nested-border: #b1124b99; + --sd-comments-highlight-internal-nested-border: #07838399; + --sd-comments-highlight-hover: #1354ff55; + --sd-comments-selection-background: #1354ff55; + + /* Styles: tracked changes */ + --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; + --sd-tracked-changes-insert-background-focused: #399c7244; + --sd-tracked-changes-delete-background-focused: #cb0e4744; + --sd-tracked-changes-format-background-focused: #ffd70033; + + /* Styles: content controls (SDT) */ + --sd-content-controls-block-border: #629be7; + --sd-content-controls-block-hover-background: #f2f2f2; + --sd-content-controls-inline-border: #629be7; + --sd-content-controls-inline-hover-background: #f2f2f2; + --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; + --sd-layout-page-shadow: 0 4px 20px rgba(15, 23, 42, 0.08); } diff --git a/packages/superdoc/src/assets/styles/tokens.css b/packages/superdoc/src/assets/styles/tokens.css deleted file mode 100644 index 9105f3652..000000000 --- a/packages/superdoc/src/assets/styles/tokens.css +++ /dev/null @@ -1,112 +0,0 @@ -/* - * SuperDoc Design Tokens as CSS Custom Properties - * - * Generated from brand/tokens/. In production, a build step (Style Dictionary) - * would generate this file automatically. For now, it's maintained manually. - * - * Users of SuperDoc can override any of these variables to customize - * the look and feel of every component. - */ - -:root { - /* ─── Primitive: Colors ─── */ - --sd-color-blue-50: #ebf0ff; - --sd-color-blue-100: #d6e1ff; - --sd-color-blue-200: #adc3ff; - --sd-color-blue-300: #85a5ff; - --sd-color-blue-400: #5c87ff; - --sd-color-blue-500: #1355ff; - --sd-color-blue-600: #0f44cc; - --sd-color-blue-700: #0b3399; - --sd-color-blue-800: #082266; - --sd-color-blue-900: #041133; - - --sd-color-gray-50: #fafafa; - --sd-color-gray-100: #f5f5f5; - --sd-color-gray-200: #f2f2f2; - --sd-color-gray-300: #e0e0e0; - --sd-color-gray-400: #dbdbdb; - --sd-color-gray-500: #ababab; - --sd-color-gray-600: #888888; - --sd-color-gray-700: #666666; - --sd-color-gray-800: #424242; - --sd-color-gray-900: #212121; - - --sd-color-red-500: #ed4337; - --sd-color-green-500: #00853d; - --sd-color-rose-500: #cb0e47; - - /* ─── Primitive: Typography ─── */ - --sd-font-family: 'Inter', system-ui, sans-serif; - --sd-font-mono: 'JetBrains Mono', monospace; - - /* ─── Primitive: Spacing ─── */ - --sd-radius-sm: 4px; - --sd-radius-md: 8px; - --sd-radius-lg: 12px; - - /* ─── Semantic: Surfaces ─── */ - --sd-surface-page: #ffffff; - --sd-surface-canvas: var(--sd-color-gray-50); - --sd-surface-card: #ffffff; - --sd-surface-muted: var(--sd-color-gray-100); - --sd-surface-hover: var(--sd-color-gray-200); - --sd-surface-selected: var(--sd-color-blue-100); - - /* ─── Semantic: Text ─── */ - --sd-text-primary: var(--sd-color-gray-900); - --sd-text-secondary: var(--sd-color-gray-700); - --sd-text-muted: var(--sd-color-gray-600); - --sd-text-placeholder: var(--sd-color-gray-500); - - /* ─── Semantic: Borders ─── */ - --sd-border-default: var(--sd-color-gray-400); - --sd-border-subtle: var(--sd-color-gray-300); - --sd-border-focus: var(--sd-color-blue-500); - - /* ─── Semantic: Actions ─── */ - --sd-action-primary: var(--sd-color-blue-500); - --sd-action-primary-hover: var(--sd-color-blue-600); - - /* ─── Component: Comment Dialog ─── */ - --sd-comment-bg: #f3f6fd; - --sd-comment-bg-hover: #f3f6fd; - --sd-comment-bg-active: var(--sd-surface-card); - --sd-comment-bg-resolved: #f0f0f0; - --sd-comment-border-active: var(--sd-border-subtle); - --sd-comment-radius: var(--sd-radius-lg); - --sd-comment-padding: 16px; - --sd-comment-shadow: 0px 4px 12px 0px rgba(50, 50, 50, 0.15); - --sd-comment-max-width: 300px; - --sd-comment-min-width: 200px; - --sd-comment-separator: var(--sd-border-subtle); - --sd-comment-transition: all 200ms ease; - - /* Comment: author & timestamp */ - --sd-comment-author-color: var(--sd-text-primary); - --sd-comment-author-size: 14px; - --sd-comment-author-weight: 600; - --sd-comment-time-color: var(--sd-text-muted); - --sd-comment-time-size: 12px; - --sd-comment-body-size: 14px; - - /* Comment: tracked change text colors */ - --sd-comment-tc-insert-color: var(--sd-color-green-500); - --sd-comment-tc-delete-color: var(--sd-color-rose-500); - - /* Comment: document highlights */ - --sd-comment-highlight-internal: #078383; - --sd-comment-highlight-external: #b1124b; - --sd-comment-highlight-opacity: 0.2; - --sd-comment-highlight-opacity-active: 0.4; - - /* Comment: internal/external toggle */ - --sd-comment-internal-bg: #cde6e6; - --sd-comment-external-bg: #f5cfda; - - /* Comment: group bubble (collapsed) */ - --sd-comment-group-bg: #e2e9fb; - --sd-comment-group-color: var(--sd-action-primary); - --sd-comment-group-size: 51px; - --sd-comment-group-expanded-size: 300px; -} diff --git a/packages/superdoc/src/components/CommentsLayer/CommentDialog.vue b/packages/superdoc/src/components/CommentsLayer/CommentDialog.vue index 2c8026eec..f0ef8cc3b 100644 --- a/packages/superdoc/src/components/CommentsLayer/CommentDialog.vue +++ b/packages/superdoc/src/components/CommentsLayer/CommentDialog.vue @@ -721,51 +721,51 @@ watch(editingCommentId, (commentId) => { .comments-dialog { display: flex; flex-direction: column; - padding: var(--sd-comment-padding, 16px); - border-radius: var(--sd-comment-radius, 12px); - background-color: var(--sd-comment-bg, #f3f6fd); + 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); border: 1px solid transparent; font-family: var(--sd-ui-font-family, Arial, Helvetica, sans-serif); - font-size: var(--sd-comment-body-size, 14px); + font-size: var(--sd-ui-comments-panel-body-text-size, 14px); line-height: 1.5; - transition: var(--sd-comment-transition, all 200ms ease); + transition: var(--sd-ui-comments-panel-transition, all 200ms ease); box-shadow: none; z-index: 5; - max-width: var(--sd-comment-max-width, 300px); - min-width: var(--sd-comment-min-width, 200px); + max-width: 300px; + min-width: 200px; width: 100%; } .comments-dialog:not(.is-active) { cursor: pointer; } .comments-dialog:not(.is-active):not(.is-resolved):hover { - background-color: var(--sd-comment-bg-hover, #f3f6fd); + background-color: var(--sd-ui-comments-panel-card-hover-background, #f3f6fd); } .comments-dialog:not(.is-resolved):hover :deep(.overflow-menu) { opacity: 1; pointer-events: auto; } .comments-dialog.is-active { - background-color: var(--sd-comment-bg-active, #ffffff); - border-color: var(--sd-comment-border-active, #e0e0e0); - box-shadow: var(--sd-comment-shadow, 0px 4px 12px 0px rgba(50, 50, 50, 0.15)); + 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)); z-index: 10; } .comments-dialog.is-resolved { - background-color: var(--sd-comment-bg-resolved, #f0f0f0); + background-color: var(--sd-ui-comments-panel-card-resolved-background, #f0f0f0); } .comment-separator { - background-color: var(--sd-comment-separator, #e0e0e0); + background-color: var(--sd-ui-comments-panel-separator, #e0e0e0); height: 1px; width: 100%; margin: 10px 0; } .comment { - font-size: var(--sd-comment-body-size, 14px); + font-size: var(--sd-ui-comments-panel-body-text-size, 14px); line-height: 1.5; - color: var(--sd-comment-author-color, #212121); + color: var(--sd-ui-comments-panel-body-text, #212121); margin: 4px 0 0 0; } .comment :deep(p) { @@ -773,22 +773,22 @@ watch(editingCommentId, (commentId) => { } .tracked-change { - font-size: var(--sd-comment-body-size, 14px); + font-size: var(--sd-ui-comments-panel-body-text-size, 14px); line-height: 1.5; - color: var(--sd-comment-author-color, #212121); + color: var(--sd-ui-comments-panel-body-text, #212121); margin: 4px 0 0 0; } .change-type { - color: var(--sd-comment-author-color, #212121); + color: var(--sd-ui-comments-panel-body-text, #212121); } .tracked-change-text { - color: var(--sd-comment-author-color, #212121); + color: var(--sd-ui-comments-panel-body-text, #212121); } .tracked-change-text.is-deleted { - color: var(--sd-comment-tc-delete-color, #cb0e47); + color: var(--sd-ui-comments-panel-tc-delete-text, #cb0e47); } .tracked-change-text.is-inserted { - color: var(--sd-comment-tc-insert-color, #00853d); + color: var(--sd-ui-comments-panel-tc-insert-text, #00853d); font-weight: 500; } @@ -799,7 +799,7 @@ watch(editingCommentId, (commentId) => { gap: 4px; font-size: 11px; font-weight: 500; - color: var(--sd-color-green-500, #00853d); + color: var(--sd-ui-comments-panel-resolved-badge-text, #00853d); margin-bottom: 4px; } .resolved-badge__icon { @@ -823,7 +823,7 @@ watch(editingCommentId, (commentId) => { } .show-more-toggle { font-size: 12px; - color: var(--sd-action-primary, #1355ff); + color: var(--sd-ui-action, #1355ff); cursor: pointer; font-weight: 500; margin-top: 4px; @@ -840,7 +840,7 @@ watch(editingCommentId, (commentId) => { gap: 6px; padding: 8px 0; font-size: 12px; - color: var(--sd-action-primary, #1355ff); + color: var(--sd-ui-action, #1355ff); font-weight: 500; cursor: pointer; user-select: none; @@ -855,7 +855,7 @@ watch(editingCommentId, (commentId) => { --sd-comment-avatar-size: 20px; --sd-comment-avatar-font-size: 8px; margin-left: -4px; - border: 2px solid var(--sd-comment-bg-active, #ffffff); + border: 2px solid var(--sd-ui-comments-panel-card-active-background, #ffffff); } .collapsed-avatars .mini-avatar:first-child { margin-left: 0; @@ -863,10 +863,10 @@ watch(editingCommentId, (commentId) => { /* ── New comment input ── */ .new-comment-input-wrapper { - border: 1.5px solid var(--sd-border-default, #dbdbdb); + border: 1.5px solid var(--sd-ui-comments-panel-input-border, #dbdbdb); border-radius: 12px; padding: 8.5px 10.5px; - background: var(--sd-surface-card, #ffffff); + background: #ffffff; margin-top: 4px; max-height: 150px; overflow-y: auto; @@ -910,10 +910,10 @@ watch(editingCommentId, (commentId) => { margin-top: 10px; } .reply-input-wrapper { - border: 1.5px solid var(--sd-border-default, #dbdbdb); + border: 1.5px solid var(--sd-ui-comments-panel-input-border, #dbdbdb); border-radius: 12px; padding: 8.5px 10.5px; - background: var(--sd-surface-card, #ffffff); + background: #ffffff; max-height: 150px; overflow-y: auto; } @@ -958,7 +958,7 @@ watch(editingCommentId, (commentId) => { color: var(--sd-color-gray-900, #212121); } .reply-btn-primary { - background: var(--sd-action-primary, #1355ff); + background: var(--sd-ui-action, #1355ff); border: none; font-size: 13px; font-weight: 600; @@ -970,7 +970,7 @@ watch(editingCommentId, (commentId) => { transition: background 150ms; } .reply-btn-primary:hover { - background: var(--sd-action-primary-hover, #0f44cc); + background: var(--sd-ui-action-hover, #0f44cc); } .reply-btn-primary.is-disabled { background: var(--sd-color-gray-400, #dbdbdb); diff --git a/packages/superdoc/src/components/CommentsLayer/CommentHeader.vue b/packages/superdoc/src/components/CommentsLayer/CommentHeader.vue index f7f74a692..06253b3de 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-comment-author-size, 14px); - font-weight: var(--sd-comment-author-weight, 600); - color: var(--sd-comment-author-color, #212121); + 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); 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-comment-imported-tag-color, var(--sd-color-gray-600, #888888)); - background: var(--sd-comment-imported-tag-bg, var(--sd-color-gray-200, #f2f2f2)); + color: var(--sd-ui-comments-panel-imported-tag-text, #888888); + background: var(--sd-ui-comments-panel-imported-tag-background, #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-comment-time-size, 12px); - color: var(--sd-comment-time-color, #888888); + font-size: var(--sd-ui-comments-panel-timestamp-size, 12px); + color: var(--sd-ui-comments-panel-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-comment-separator, #dbdbdb); + background-color: #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 d3aca11bb..e38440fd6 100644 --- a/packages/superdoc/src/components/CommentsLayer/CommentsDropdown.vue +++ b/packages/superdoc/src/components/CommentsLayer/CommentsDropdown.vue @@ -176,15 +176,15 @@ onBeforeUnmount(() => { .comments-dropdown__menu { min-width: 120px; border-radius: 8px; - border: 1px solid var(--sd-border-default, #dbdbdb); - background: #fff; - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); + 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)); padding: 4px; box-sizing: border-box; } .comments-dropdown__option { - font-size: 14px; + font-size: var(--sd-ui-comments-panel-dropdown-option-text-size, 14px); display: flex; align-items: center; gap: 8px; @@ -196,7 +196,7 @@ onBeforeUnmount(() => { } .comments-dropdown__option:hover { - background-color: #f3f3f5; + background-color: var(--sd-ui-comments-panel-dropdown-option-hover-background, #f3f3f5); } .comments-dropdown__option.disabled { diff --git a/packages/superdoc/src/components/CommentsLayer/InternalDropdown.vue b/packages/superdoc/src/components/CommentsLayer/InternalDropdown.vue index 0af31611e..ac8a25633 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: '#CDE6E6', + backgroundColor: 'var(--sd-ui-comments-panel-visibility-internal-background, #CDE6E6)', }, { label: 'External', key: 'external', iconString: superdocIcons.external, - backgroundColor: '#F5CFDA', + backgroundColor: 'var(--sd-ui-comments-panel-visibility-external-background, #F5CFDA)', }, ]; diff --git a/packages/superdoc/src/dev/components/SuperdocDev.vue b/packages/superdoc/src/dev/components/SuperdocDev.vue index 2d3f1d7cc..009829b05 100644 --- a/packages/superdoc/src/dev/components/SuperdocDev.vue +++ b/packages/superdoc/src/dev/components/SuperdocDev.vue @@ -48,6 +48,7 @@ const collabUrl = 'ws://localhost:8081/v1/collaboration'; const useWordOverlay = ref(urlParams.get('wordOverlay') !== '0'); const wordOverlayOpacity = ref(Number.isFinite(overlayOpacityFromUrl) ? clampOpacity(overlayOpacityFromUrl) : 0.45); const wordOverlayBlendMode = ref(urlParams.get('wordOverlayBlend') || 'difference'); +const selectedTheme = ref('default'); const generatedWordScreenshots = ref([]); const isGeneratingWordBaseline = ref(false); const wordBaselineStatus = ref(''); @@ -73,6 +74,16 @@ const superdocLogo = SuperdocLogo; const uploadedFileName = ref(''); const uploadDisplayName = computed(() => uploadedFileName.value || 'No file chosen'); +const DEV_THEME_CLASSES = ['sd-theme-google-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 === 'google-docs') html.classList.add('sd-theme-google-docs'); + if (theme === 'word') html.classList.add('sd-theme-word'); + if (theme === 'blueprint') html.classList.add('sd-theme-blueprint'); +}; + // URL loading const documentUrl = ref(''); const isLoadingUrl = ref(false); @@ -1042,6 +1053,10 @@ watch( }, ); +watch(selectedTheme, (theme) => { + applyDevTheme(theme); +}); + const handleTitleChange = (e) => { title.value = e.target.innerText; @@ -1061,6 +1076,8 @@ const toggleCommentsPanel = () => { }; onMounted(async () => { + applyDevTheme(selectedTheme.value); + // Initialize collaboration if enabled via ?collab=1 if (useCollaboration) { clearYjsChanges(); @@ -1100,6 +1117,7 @@ onMounted(async () => { }); onBeforeUnmount(() => { + applyDevTheme('default'); detachWordOverlayListener(); removeWordOverlay(); @@ -1298,6 +1316,15 @@ if (scrollTestMode.value) {
+