@beyondwork/docx-react-component 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/README.md +44 -104
  2. package/package.json +76 -46
  3. package/src/README.md +85 -0
  4. package/src/api/README.md +22 -0
  5. package/src/api/public-types.ts +525 -0
  6. package/src/compare/diff-engine.ts +530 -0
  7. package/src/compare/export-redlines.ts +162 -0
  8. package/src/compare/snapshot.ts +37 -0
  9. package/src/component-inventory.md +99 -0
  10. package/src/core/README.md +10 -0
  11. package/src/core/commands/README.md +3 -0
  12. package/src/core/commands/formatting-commands.ts +161 -0
  13. package/src/core/commands/image-commands.ts +144 -0
  14. package/src/core/commands/index.ts +1013 -0
  15. package/src/core/commands/list-commands.ts +370 -0
  16. package/src/core/commands/review-commands.ts +108 -0
  17. package/src/core/commands/text-commands.ts +119 -0
  18. package/src/core/schema/README.md +3 -0
  19. package/src/core/schema/text-schema.ts +512 -0
  20. package/src/core/selection/README.md +3 -0
  21. package/src/core/selection/mapping.ts +238 -0
  22. package/src/core/selection/review-anchors.ts +94 -0
  23. package/src/core/state/README.md +3 -0
  24. package/src/core/state/editor-state.ts +580 -0
  25. package/src/core/state/text-transaction.ts +276 -0
  26. package/src/formats/xlsx/io/parse-shared-strings.ts +41 -0
  27. package/src/formats/xlsx/io/parse-sheet.ts +289 -0
  28. package/src/formats/xlsx/io/parse-styles.ts +57 -0
  29. package/src/formats/xlsx/io/parse-workbook.ts +75 -0
  30. package/src/formats/xlsx/io/xlsx-session.ts +306 -0
  31. package/src/formats/xlsx/model/cell.ts +189 -0
  32. package/src/formats/xlsx/model/sheet.ts +244 -0
  33. package/src/formats/xlsx/model/styles.ts +118 -0
  34. package/src/formats/xlsx/model/workbook.ts +449 -0
  35. package/src/index.ts +45 -0
  36. package/src/io/README.md +10 -0
  37. package/src/io/docx-session.ts +1763 -0
  38. package/src/io/export/README.md +3 -0
  39. package/src/io/export/export-session.ts +165 -0
  40. package/src/io/export/minimal-docx.ts +115 -0
  41. package/src/io/export/reattach-preserved-parts.ts +54 -0
  42. package/src/io/export/serialize-comments.ts +876 -0
  43. package/src/io/export/serialize-footnotes.ts +217 -0
  44. package/src/io/export/serialize-headers-footers.ts +200 -0
  45. package/src/io/export/serialize-main-document.ts +982 -0
  46. package/src/io/export/serialize-numbering.ts +97 -0
  47. package/src/io/export/serialize-revisions.ts +389 -0
  48. package/src/io/export/serialize-runtime-revisions.ts +265 -0
  49. package/src/io/export/serialize-tables.ts +147 -0
  50. package/src/io/export/split-review-boundaries.ts +194 -0
  51. package/src/io/normalize/README.md +3 -0
  52. package/src/io/normalize/normalize-text.ts +437 -0
  53. package/src/io/ooxml/README.md +3 -0
  54. package/src/io/ooxml/parse-comments.ts +779 -0
  55. package/src/io/ooxml/parse-complex-content.ts +287 -0
  56. package/src/io/ooxml/parse-fields.ts +438 -0
  57. package/src/io/ooxml/parse-footnotes.ts +403 -0
  58. package/src/io/ooxml/parse-headers-footers.ts +483 -0
  59. package/src/io/ooxml/parse-inline-media.ts +431 -0
  60. package/src/io/ooxml/parse-main-document.ts +1846 -0
  61. package/src/io/ooxml/parse-numbering.ts +425 -0
  62. package/src/io/ooxml/parse-revisions.ts +658 -0
  63. package/src/io/ooxml/parse-shapes.ts +271 -0
  64. package/src/io/ooxml/parse-tables.ts +568 -0
  65. package/src/io/ooxml/parse-theme.ts +314 -0
  66. package/src/io/ooxml/part-manifest.ts +136 -0
  67. package/src/io/ooxml/revision-boundaries.ts +351 -0
  68. package/src/io/opc/README.md +3 -0
  69. package/src/io/opc/corrupt-package.ts +166 -0
  70. package/src/io/opc/docx-package.ts +74 -0
  71. package/src/io/opc/package-reader.ts +320 -0
  72. package/src/io/opc/package-writer.ts +273 -0
  73. package/src/legal/bookmarks.ts +196 -0
  74. package/src/legal/cross-references.ts +356 -0
  75. package/src/legal/defined-terms.ts +203 -0
  76. package/src/model/README.md +3 -0
  77. package/src/model/canonical-document.ts +1911 -0
  78. package/src/model/cds-1.0.0.ts +196 -0
  79. package/src/model/snapshot.ts +393 -0
  80. package/src/preservation/README.md +3 -0
  81. package/src/preservation/markup-compatibility.ts +48 -0
  82. package/src/preservation/opaque-fragment-store.ts +89 -0
  83. package/src/preservation/opaque-region.ts +233 -0
  84. package/src/preservation/package-preservation.ts +120 -0
  85. package/src/preservation/preserved-part-manifest.ts +56 -0
  86. package/src/preservation/relationship-retention.ts +57 -0
  87. package/src/preservation/store.ts +185 -0
  88. package/src/review/README.md +16 -0
  89. package/src/review/store/README.md +3 -0
  90. package/src/review/store/comment-anchors.ts +70 -0
  91. package/src/review/store/comment-remapping.ts +154 -0
  92. package/src/review/store/comment-store.ts +331 -0
  93. package/src/review/store/comment-thread.ts +109 -0
  94. package/src/review/store/revision-actions.ts +394 -0
  95. package/src/review/store/revision-store.ts +303 -0
  96. package/src/review/store/revision-types.ts +168 -0
  97. package/src/review/store/runtime-comment-store.ts +43 -0
  98. package/src/runtime/README.md +3 -0
  99. package/src/runtime/ai-action-policy.ts +764 -0
  100. package/src/runtime/document-runtime.ts +967 -0
  101. package/src/runtime/read-only-diagnostics-runtime.ts +232 -0
  102. package/src/runtime/review-runtime.ts +44 -0
  103. package/src/runtime/revision-runtime.ts +107 -0
  104. package/src/runtime/session-capabilities.ts +138 -0
  105. package/src/runtime/surface-projection.ts +570 -0
  106. package/src/runtime/table-commands.ts +87 -0
  107. package/src/runtime/table-schema.ts +140 -0
  108. package/src/runtime/virtualized-rendering.ts +258 -0
  109. package/src/ui/README.md +30 -0
  110. package/src/ui/WordReviewEditor.tsx +1504 -0
  111. package/src/ui/comments/README.md +3 -0
  112. package/src/ui/compatibility/README.md +3 -0
  113. package/src/ui/editor-surface/README.md +3 -0
  114. package/src/ui/headless/comment-decoration-model.ts +124 -0
  115. package/src/ui/headless/revision-decoration-model.ts +128 -0
  116. package/src/ui/headless/selection-helpers.ts +34 -0
  117. package/src/ui/headless/use-editor-keyboard.ts +98 -0
  118. package/src/ui/review/README.md +3 -0
  119. package/src/ui/shared/revision-filters.ts +31 -0
  120. package/src/ui/status/README.md +3 -0
  121. package/src/ui/theme/README.md +3 -0
  122. package/src/ui/toolbar/README.md +3 -0
  123. package/src/ui-tailwind/chrome/tw-alert-banner.tsx +48 -0
  124. package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +44 -0
  125. package/src/ui-tailwind/chrome/tw-unsaved-modal.tsx +58 -0
  126. package/src/ui-tailwind/chrome/use-before-unload.ts +20 -0
  127. package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +139 -0
  128. package/src/ui-tailwind/editor-surface/pm-decorations.ts +98 -0
  129. package/src/ui-tailwind/editor-surface/pm-position-map.ts +123 -0
  130. package/src/ui-tailwind/editor-surface/pm-schema.ts +452 -0
  131. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +327 -0
  132. package/src/ui-tailwind/editor-surface/search-plugin.ts +157 -0
  133. package/src/ui-tailwind/editor-surface/tw-caret.tsx +12 -0
  134. package/src/ui-tailwind/editor-surface/tw-editor-surface.tsx +150 -0
  135. package/src/ui-tailwind/editor-surface/tw-inline-token.tsx +118 -0
  136. package/src/ui-tailwind/editor-surface/tw-opaque-block.tsx +52 -0
  137. package/src/ui-tailwind/editor-surface/tw-paragraph-block.tsx +151 -0
  138. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +215 -0
  139. package/src/ui-tailwind/editor-surface/tw-segment-view.tsx +111 -0
  140. package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +122 -0
  141. package/src/ui-tailwind/index.ts +61 -0
  142. package/src/ui-tailwind/review/tw-comment-sidebar.tsx +276 -0
  143. package/src/ui-tailwind/review/tw-health-panel.tsx +120 -0
  144. package/src/ui-tailwind/review/tw-review-rail.tsx +120 -0
  145. package/src/ui-tailwind/review/tw-revision-sidebar.tsx +164 -0
  146. package/src/ui-tailwind/status/tw-status-bar.tsx +58 -0
  147. package/src/ui-tailwind/theme/editor-theme.css +190 -0
  148. package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +48 -0
  149. package/src/ui-tailwind/toolbar/tw-toolbar.tsx +231 -0
  150. package/src/ui-tailwind/tw-review-workspace.tsx +140 -0
  151. package/src/validation/README.md +3 -0
  152. package/src/validation/compatibility-engine.ts +317 -0
  153. package/src/validation/compatibility-report.ts +160 -0
  154. package/src/validation/diagnostics.ts +203 -0
  155. package/src/validation/import-diagnostics.ts +128 -0
  156. package/src/validation/low-priority-word-surfaces.ts +373 -0
  157. package/dist/chunk-32W6IVQE.js +0 -7725
  158. package/dist/chunk-32W6IVQE.js.map +0 -1
  159. package/dist/index.cjs +0 -23722
  160. package/dist/index.cjs.map +0 -1
  161. package/dist/index.d.cts +0 -7
  162. package/dist/index.d.ts +0 -7
  163. package/dist/index.js +0 -16011
  164. package/dist/index.js.map +0 -1
  165. package/dist/public-types-DqCURAz8.d.cts +0 -1152
  166. package/dist/public-types-DqCURAz8.d.ts +0 -1152
  167. package/dist/tailwind.cjs +0 -8295
  168. package/dist/tailwind.cjs.map +0 -1
  169. package/dist/tailwind.d.cts +0 -323
  170. package/dist/tailwind.d.ts +0 -323
  171. package/dist/tailwind.js +0 -553
  172. package/dist/tailwind.js.map +0 -1
@@ -0,0 +1,120 @@
1
+ import React from "react";
2
+
3
+ import * as Tabs from "@radix-ui/react-tabs";
4
+ import * as ScrollArea from "@radix-ui/react-scroll-area";
5
+
6
+ import type {
7
+ CommentSidebarSnapshot,
8
+ CommentSidebarThreadSnapshot,
9
+ CompatibilityPanelSnapshot,
10
+ EditorWarning,
11
+ TrackedChangesSnapshot,
12
+ TrackedChangeEntrySnapshot,
13
+ } from "../../api/public-types";
14
+ import type { MarkupDisplay } from "../../ui/headless/comment-decoration-model";
15
+ import { TwCommentSidebar } from "./tw-comment-sidebar";
16
+ import { TwRevisionSidebar } from "./tw-revision-sidebar";
17
+ import { TwHealthPanel } from "./tw-health-panel";
18
+
19
+ export type ReviewRailTab = "comments" | "changes";
20
+
21
+ export interface TwReviewRailProps {
22
+ activeTab: ReviewRailTab;
23
+ currentUserId?: string;
24
+ comments: CommentSidebarSnapshot;
25
+ trackedChanges: TrackedChangesSnapshot;
26
+ compatibility: CompatibilityPanelSnapshot;
27
+ warnings: EditorWarning[];
28
+ markupDisplay: MarkupDisplay;
29
+ activeCommentId?: string;
30
+ activeRevisionId?: string;
31
+ onActiveTabChange: (tab: ReviewRailTab) => void;
32
+ onOpenComment?: (thread: CommentSidebarThreadSnapshot) => void;
33
+ onResolveComment?: (commentId: string) => void;
34
+ onReopenComment?: (commentId: string) => void;
35
+ onAddReply?: (commentId: string, body: string) => void;
36
+ onEditBody?: (commentId: string, body: string) => void;
37
+ onOpenRevision?: (revision: TrackedChangeEntrySnapshot) => void;
38
+ onAcceptRevision?: (revisionId: string) => void;
39
+ onRejectRevision?: (revisionId: string) => void;
40
+ onAcceptAllChanges?: () => void;
41
+ onRejectAllChanges?: () => void;
42
+ }
43
+
44
+ const focusRingClass =
45
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-canvas";
46
+
47
+ export function TwReviewRail(props: TwReviewRailProps) {
48
+ const warningCount = props.compatibility.featureEntries.filter(
49
+ (e) => e.featureClass !== "supported-roundtrip",
50
+ ).length + props.warnings.length;
51
+
52
+ return (
53
+ <aside
54
+ aria-label="Review rail"
55
+ className="flex w-[340px] shrink-0 flex-col border-l border-border bg-canvas"
56
+ >
57
+ <Tabs.Root
58
+ value={props.activeTab}
59
+ onValueChange={(v: string) => props.onActiveTabChange(v as ReviewRailTab)}
60
+ className="flex flex-1 flex-col min-h-0"
61
+ >
62
+ <Tabs.List className="flex shrink-0 border-b border-border">
63
+ <Tabs.Trigger
64
+ value="comments"
65
+ className={`flex-1 py-2 text-xs text-tertiary transition-colors data-[state=active]:text-primary data-[state=active]:shadow-[inset_0_-2px_0_var(--color-accent)] outline-none ${focusRingClass}`}
66
+ >
67
+ Comments{" "}
68
+ <span className="text-tertiary">{props.comments.totalCount}</span>
69
+ </Tabs.Trigger>
70
+ <Tabs.Trigger
71
+ value="changes"
72
+ className={`flex-1 py-2 text-xs text-tertiary transition-colors data-[state=active]:text-primary data-[state=active]:shadow-[inset_0_-2px_0_var(--color-accent)] outline-none ${focusRingClass}`}
73
+ >
74
+ Changes{" "}
75
+ <span className="text-tertiary">{props.trackedChanges.totalCount}</span>
76
+ </Tabs.Trigger>
77
+ {/* Health moved to toolbar popover */}
78
+ </Tabs.List>
79
+
80
+ <ScrollArea.Root className="flex-1 min-h-0">
81
+ <ScrollArea.Viewport className="h-full w-full">
82
+ <Tabs.Content value="comments" className="p-3 outline-none">
83
+ <TwCommentSidebar
84
+ currentUserId={props.currentUserId}
85
+ comments={props.comments}
86
+ activeCommentId={props.activeCommentId}
87
+ onOpenComment={props.onOpenComment}
88
+ onResolveComment={props.onResolveComment}
89
+ onReopenComment={props.onReopenComment}
90
+ onAddReply={props.onAddReply}
91
+ onEditBody={props.onEditBody}
92
+ />
93
+ </Tabs.Content>
94
+
95
+ <Tabs.Content value="changes" className="p-3 outline-none">
96
+ <TwRevisionSidebar
97
+ trackedChanges={props.trackedChanges}
98
+ markupDisplay={props.markupDisplay}
99
+ activeRevisionId={props.activeRevisionId}
100
+ onOpenRevision={props.onOpenRevision}
101
+ onAcceptRevision={props.onAcceptRevision}
102
+ onRejectRevision={props.onRejectRevision}
103
+ onAcceptAllChanges={props.onAcceptAllChanges}
104
+ onRejectAllChanges={props.onRejectAllChanges}
105
+ />
106
+ </Tabs.Content>
107
+
108
+ {/* Health panel moved to toolbar popover */}
109
+ </ScrollArea.Viewport>
110
+ <ScrollArea.Scrollbar
111
+ orientation="vertical"
112
+ className="flex w-1.5 touch-none select-none p-0.5"
113
+ >
114
+ <ScrollArea.Thumb className="relative flex-1 rounded-full bg-black/[0.12]" />
115
+ </ScrollArea.Scrollbar>
116
+ </ScrollArea.Root>
117
+ </Tabs.Root>
118
+ </aside>
119
+ );
120
+ }
@@ -0,0 +1,164 @@
1
+ import React from "react";
2
+ import { Check, X } from "lucide-react";
3
+
4
+ import type { TrackedChangesSnapshot, TrackedChangeEntrySnapshot } from "../../api/public-types";
5
+ import { selectVisibleRevisions } from "../../ui/shared/revision-filters";
6
+ import type { MarkupDisplay } from "../../ui/headless/comment-decoration-model";
7
+
8
+ export interface TwRevisionSidebarProps {
9
+ trackedChanges: TrackedChangesSnapshot;
10
+ markupDisplay: MarkupDisplay;
11
+ activeRevisionId?: string;
12
+ onOpenRevision?: (revision: TrackedChangeEntrySnapshot) => void;
13
+ onAcceptRevision?: (revisionId: string) => void;
14
+ onRejectRevision?: (revisionId: string) => void;
15
+ onAcceptAllChanges?: () => void;
16
+ onRejectAllChanges?: () => void;
17
+ }
18
+
19
+ const focusRingClass =
20
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-canvas";
21
+
22
+ export function TwRevisionSidebar(props: TwRevisionSidebarProps) {
23
+ const { trackedChanges, markupDisplay, activeRevisionId } = props;
24
+ const visibleRevisions = selectVisibleRevisions(trackedChanges.revisions, markupDisplay);
25
+ const actionablePendingCount = trackedChanges.revisions.filter(
26
+ (r) => r.status === "active" && r.actionability === "actionable",
27
+ ).length;
28
+
29
+ return (
30
+ <div className="outline-none">
31
+ <p className="text-xs text-tertiary mb-3">
32
+ {trackedChanges.pendingChangeIds.length} active · {trackedChanges.acceptedChangeIds.length} accepted · {trackedChanges.preserveOnlyChangeIds.length} preserve-only
33
+ </p>
34
+
35
+ {/* Bulk actions */}
36
+ <div className="flex gap-1.5 mb-3">
37
+ <button
38
+ type="button"
39
+ disabled={actionablePendingCount === 0}
40
+ className="inline-flex items-center gap-1 rounded-md px-2.5 py-1.5 text-xs font-semibold text-accent hover:bg-accent-soft transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
41
+ onClick={props.onAcceptAllChanges}
42
+ >
43
+ Accept all ({actionablePendingCount})
44
+ </button>
45
+ <button
46
+ type="button"
47
+ disabled={actionablePendingCount === 0}
48
+ className="inline-flex items-center gap-1 rounded-md px-2.5 py-1.5 text-xs text-secondary hover:bg-surface transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
49
+ onClick={props.onRejectAllChanges}
50
+ >
51
+ Reject all
52
+ </button>
53
+ </div>
54
+
55
+ {visibleRevisions.length > 0 ? (
56
+ <div className="space-y-1">
57
+ {visibleRevisions.map((rev) => {
58
+ const isActive = activeRevisionId === rev.revisionId;
59
+
60
+ return (
61
+ <div
62
+ key={rev.revisionId}
63
+ role="button"
64
+ tabIndex={0}
65
+ className={`flex rounded-lg transition-colors cursor-pointer ${focusRingClass} ${isActive ? "bg-accent-soft" : "hover:bg-surface"}`}
66
+ onClick={() => props.onOpenRevision?.(rev)}
67
+ onKeyDown={(event) => {
68
+ if (event.key === "Enter" || event.key === " ") {
69
+ event.preventDefault();
70
+ props.onOpenRevision?.(rev);
71
+ }
72
+ }}
73
+ >
74
+ <div className={`w-0.5 shrink-0 rounded-l-lg ${
75
+ rev.kind === "insertion" ? "bg-insert"
76
+ : rev.kind === "deletion" ? "bg-danger"
77
+ : "bg-tertiary"
78
+ }`} />
79
+ <div className="p-2.5 flex-1 min-w-0">
80
+ <div className="flex items-start justify-between gap-2 mb-1">
81
+ <span className="text-sm font-medium text-primary">{rev.anchorLabel}</span>
82
+ <RevisionBadge status={rev.status} actionability={rev.actionability} />
83
+ </div>
84
+ <p className="text-xs text-tertiary mb-1">{rev.authorId} · {rev.createdAt}</p>
85
+ {rev.excerpt ? (
86
+ <p className={`text-sm ${
87
+ rev.kind === "insertion" ? "text-insert"
88
+ : rev.kind === "deletion" ? "text-danger line-through"
89
+ : "text-secondary"
90
+ }`}>
91
+ {rev.excerpt}
92
+ </p>
93
+ ) : (
94
+ <p className="text-sm text-secondary">{rev.label}</p>
95
+ )}
96
+ {rev.detail ? (
97
+ <p className="text-xs text-secondary mt-1">{rev.detail}</p>
98
+ ) : null}
99
+ <div className="flex gap-1.5 mt-2">
100
+ {rev.actionability === "actionable" ? (
101
+ <>
102
+ <button
103
+ type="button"
104
+ disabled={!rev.canAccept || rev.status === "accepted"}
105
+ className="inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs text-insert hover:bg-insert-soft transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
106
+ onClick={(e) => {
107
+ e.stopPropagation();
108
+ props.onAcceptRevision?.(rev.revisionId);
109
+ }}
110
+ >
111
+ <Check className="h-3 w-3" /> Accept
112
+ </button>
113
+ <button
114
+ type="button"
115
+ disabled={!rev.canReject || rev.status === "rejected"}
116
+ className="inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs text-danger hover:bg-delete-soft transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
117
+ onClick={(e) => {
118
+ e.stopPropagation();
119
+ props.onRejectRevision?.(rev.revisionId);
120
+ }}
121
+ >
122
+ <X className="h-3 w-3" /> Reject
123
+ </button>
124
+ </>
125
+ ) : (
126
+ <span className="text-xs text-tertiary px-2 py-1">Preserve-only</span>
127
+ )}
128
+ </div>
129
+ </div>
130
+ </div>
131
+ );
132
+ })}
133
+ </div>
134
+ ) : (
135
+ <p className="text-xs text-tertiary py-4">
136
+ {trackedChanges.totalCount > 0
137
+ ? "Switch to Full markup to see all tracked changes."
138
+ : "Tracked change cards will appear here when present."}
139
+ </p>
140
+ )}
141
+ </div>
142
+ );
143
+ }
144
+
145
+ function RevisionBadge(props: { status: string; actionability: string }) {
146
+ if (props.actionability === "preserve-only") {
147
+ return (
148
+ <span className="inline-flex items-center rounded px-1.5 py-0.5 text-[10px] font-medium text-comment bg-warning-soft">
149
+ preserve-only
150
+ </span>
151
+ );
152
+ }
153
+ const styles: Record<string, string> = {
154
+ active: "text-secondary bg-subtle",
155
+ accepted: "text-insert bg-insert-soft",
156
+ rejected: "text-danger bg-delete-soft",
157
+ detached: "text-comment bg-warning-soft",
158
+ };
159
+ return (
160
+ <span className={`inline-flex items-center rounded px-1.5 py-0.5 text-[10px] font-medium ${styles[props.status] ?? "text-secondary bg-subtle"}`}>
161
+ {props.status}
162
+ </span>
163
+ );
164
+ }
@@ -0,0 +1,58 @@
1
+ import React from "react";
2
+
3
+ export interface TwStatusBarProps {
4
+ isDirty: boolean;
5
+ isExportBlocked: boolean;
6
+ preserveOnlyCount: number;
7
+ commentCount: number;
8
+ changeCount: number;
9
+ sessionId: string;
10
+ }
11
+
12
+ export function TwStatusBar(props: TwStatusBarProps) {
13
+ const saveState = props.isExportBlocked
14
+ ? "Read-only"
15
+ : props.isDirty
16
+ ? "Unsaved"
17
+ : "Ready";
18
+ const exportState = props.isExportBlocked
19
+ ? "Blocked"
20
+ : props.preserveOnlyCount > 0
21
+ ? "Warnings"
22
+ : "Ready";
23
+
24
+ return (
25
+ <footer className="flex h-7 shrink-0 items-center gap-4 border-t border-border px-3 text-xs text-tertiary">
26
+ <span className="flex items-center gap-1.5">
27
+ <span
28
+ className={`inline-block h-1.5 w-1.5 rounded-full ${
29
+ props.isExportBlocked
30
+ ? "bg-danger"
31
+ : props.isDirty
32
+ ? "bg-comment"
33
+ : "bg-insert"
34
+ }`}
35
+ />
36
+ {saveState}
37
+ </span>
38
+ <span className="flex items-center gap-1.5">
39
+ <span
40
+ className={`inline-block h-1.5 w-1.5 rounded-full ${
41
+ props.isExportBlocked
42
+ ? "bg-danger"
43
+ : props.preserveOnlyCount > 0
44
+ ? "bg-comment"
45
+ : "bg-insert"
46
+ }`}
47
+ />
48
+ Export {exportState.toLowerCase()}
49
+ </span>
50
+ <span>
51
+ {props.commentCount} comment{props.commentCount !== 1 ? "s" : ""} ·{" "}
52
+ {props.changeCount} change{props.changeCount !== 1 ? "s" : ""}
53
+ </span>
54
+ <span className="flex-1" />
55
+ <span>{props.sessionId}</span>
56
+ </footer>
57
+ );
58
+ }
@@ -0,0 +1,190 @@
1
+ /*
2
+ * Word Review Editor — Portable Theme
3
+ *
4
+ * This file defines all CSS custom properties consumed by the Tailwind-based
5
+ * editor components. Import it in your app to style the editor, or override
6
+ * individual variables in your own stylesheet for custom themes.
7
+ *
8
+ * Usage:
9
+ * @import "@docx-react-component/ui-tailwind/theme/editor-theme.css";
10
+ *
11
+ * Custom theme:
12
+ * .my-brand { --color-accent: #8b5cf6; }
13
+ * <div class="my-brand"><WordReviewEditor ... /></div>
14
+ */
15
+
16
+ @import "tailwindcss";
17
+
18
+ @theme {
19
+ /* Backgrounds */
20
+ --color-surface: #f7f7f5;
21
+ --color-surface-hover: #ebebea;
22
+ --color-surface-active: #e3e3e1;
23
+ --color-subtle: #f0f0ee;
24
+ --color-canvas: #ffffff;
25
+ --color-shell-bg: #f3efe7;
26
+ --color-card: #faf8f2;
27
+ --color-editor-frame: #f6f3ee;
28
+
29
+ /* Text */
30
+ --color-primary: #1f1f1f;
31
+ --color-secondary: #6b6b6b;
32
+ --color-tertiary: #616161;
33
+
34
+ /* Accent */
35
+ --color-accent: #1660a8;
36
+ --color-accent-soft: rgba(22, 96, 168, 0.08);
37
+
38
+ /* Review — comments */
39
+ --color-comment: #7a4f00;
40
+ --color-comment-soft: rgba(255, 212, 0, 0.14);
41
+ --color-comment-strong: rgba(255, 212, 0, 0.25);
42
+
43
+ /* Review — insertions */
44
+ --color-insert: #1a7f37;
45
+ --color-insert-soft: rgba(26, 127, 55, 0.08);
46
+
47
+ /* Review — deletions */
48
+ --color-delete: #cf222e;
49
+ --color-delete-soft: rgba(207, 34, 46, 0.06);
50
+
51
+ /* Semantic */
52
+ --color-warning: #7a4f00;
53
+ --color-warning-soft: rgba(154, 103, 0, 0.08);
54
+ --color-danger: #cf222e;
55
+ --color-danger-soft: rgba(207, 34, 46, 0.08);
56
+ --color-success: #1a7f37;
57
+ --color-success-soft: rgba(26, 127, 55, 0.08);
58
+
59
+ /* Borders */
60
+ --color-border: rgba(0, 0, 0, 0.06);
61
+ --color-border-strong: rgba(0, 0, 0, 0.12);
62
+
63
+ /* Shadows */
64
+ --color-shadow: rgba(16, 24, 40, 0.08);
65
+ --color-shadow-strong: rgba(16, 24, 40, 0.14);
66
+
67
+ /* Typography roles */
68
+ --font-legal-serif: var(--font-legal-serif, "Source Serif 4", "Georgia", "Times New Roman", serif);
69
+ --font-legal-sans: var(--font-legal-sans, "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif);
70
+ --font-legal-mono: "JetBrains Mono", "SF Mono", "Fira Code", "Consolas", monospace;
71
+ }
72
+
73
+ /* ─── Dark mode overrides ─── */
74
+ .dark {
75
+ --color-surface: #1e1e1e;
76
+ --color-surface-hover: #2a2a2a;
77
+ --color-surface-active: #333333;
78
+ --color-subtle: #252525;
79
+ --color-canvas: #191919;
80
+ --color-shell-bg: #171514;
81
+ --color-card: #23211f;
82
+ --color-editor-frame: #1d1b19;
83
+
84
+ --color-primary: #e4e4e4;
85
+ --color-secondary: #a0a0a0;
86
+ --color-tertiary: #6e6e6e;
87
+
88
+ --color-accent: #4ca6f0;
89
+ --color-accent-soft: rgba(76, 166, 240, 0.12);
90
+
91
+ --color-comment: #d4a020;
92
+ --color-comment-soft: rgba(212, 160, 32, 0.16);
93
+ --color-comment-strong: rgba(212, 160, 32, 0.28);
94
+ --color-insert: #3fb950;
95
+ --color-insert-soft: rgba(63, 185, 80, 0.12);
96
+ --color-delete: #f85149;
97
+ --color-delete-soft: rgba(248, 81, 73, 0.12);
98
+
99
+ --color-warning: #d4a020;
100
+ --color-warning-soft: rgba(212, 160, 32, 0.12);
101
+ --color-danger: #f85149;
102
+ --color-danger-soft: rgba(248, 81, 73, 0.12);
103
+ --color-success: #3fb950;
104
+ --color-success-soft: rgba(63, 185, 80, 0.12);
105
+
106
+ --color-border: rgba(255, 255, 255, 0.08);
107
+ --color-border-strong: rgba(255, 255, 255, 0.14);
108
+
109
+ --color-shadow: rgba(0, 0, 0, 0.32);
110
+ --color-shadow-strong: rgba(0, 0, 0, 0.48);
111
+ }
112
+
113
+ /* ─── Base resets ─── */
114
+ * {
115
+ box-sizing: border-box;
116
+ }
117
+
118
+ /* ─── Reduced motion ─── */
119
+ [data-reduced-motion="true"] {
120
+ scroll-behavior: auto;
121
+ }
122
+
123
+ [data-reduced-motion="true"] *,
124
+ [data-reduced-motion="true"] *::before,
125
+ [data-reduced-motion="true"] *::after {
126
+ animation: none !important;
127
+ scroll-behavior: auto !important;
128
+ transition: none !important;
129
+ }
130
+
131
+ /* ─── Caret blink animation ─── */
132
+ @keyframes wre-blink {
133
+ 0%, 100% { opacity: 1; }
134
+ 50% { opacity: 0; }
135
+ }
136
+
137
+ .animate-wre-blink {
138
+ animation: wre-blink 1s step-end infinite;
139
+ }
140
+
141
+ /* ─── Focus ring utility ─── */
142
+ .wre-focus-ring:focus-visible {
143
+ outline: none;
144
+ box-shadow:
145
+ 0 0 0 2px var(--color-canvas),
146
+ 0 0 0 4px var(--color-accent);
147
+ }
148
+
149
+ /* ─── ProseMirror surface ─── */
150
+ .prosemirror-surface .ProseMirror {
151
+ outline: none;
152
+ min-height: 200px;
153
+ cursor: text;
154
+ border: none;
155
+ box-shadow: none;
156
+ }
157
+
158
+ .prosemirror-surface .ProseMirror:focus {
159
+ outline: none;
160
+ }
161
+
162
+ .prosemirror-surface .ProseMirror [contenteditable] {
163
+ outline: none;
164
+ }
165
+
166
+ .prosemirror-surface:focus-visible {
167
+ outline: none;
168
+ }
169
+
170
+ .prosemirror-surface .ProseMirror p {
171
+ margin: 0 0 0.5em 0;
172
+ }
173
+
174
+ .prosemirror-surface .ProseMirror [data-node-type="opaque_block"] {
175
+ user-select: none;
176
+ cursor: default;
177
+ }
178
+
179
+ .prosemirror-surface .ProseMirror [data-node-type="opaque_inline"] {
180
+ user-select: none;
181
+ cursor: default;
182
+ }
183
+
184
+ .prosemirror-surface .ProseMirror [data-node-type="tab"] {
185
+ user-select: none;
186
+ }
187
+
188
+ .prosemirror-surface .ProseMirror ::selection {
189
+ background: var(--color-accent-soft);
190
+ }
@@ -0,0 +1,48 @@
1
+ import React from "react";
2
+ import * as Tooltip from "@radix-ui/react-tooltip";
3
+
4
+ export interface TwToolbarIconButtonProps {
5
+ icon: React.ComponentType<{ className?: string }>;
6
+ label: string;
7
+ disabled?: boolean;
8
+ active?: boolean;
9
+ emphasis?: boolean;
10
+ onClick?: () => void;
11
+ }
12
+
13
+ const focusRingClass =
14
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-canvas";
15
+
16
+ export function TwToolbarIconButton(props: TwToolbarIconButtonProps) {
17
+ return (
18
+ <Tooltip.Root>
19
+ <Tooltip.Trigger asChild>
20
+ <button
21
+ type="button"
22
+ disabled={props.disabled}
23
+ onClick={props.onClick}
24
+ className={[
25
+ "inline-flex h-7 w-7 items-center justify-center rounded-md transition-colors outline-none",
26
+ "disabled:opacity-30 disabled:cursor-not-allowed",
27
+ props.emphasis
28
+ ? "text-accent hover:bg-accent-soft"
29
+ : props.active
30
+ ? "bg-accent-soft text-accent"
31
+ : "text-secondary hover:bg-surface hover:text-primary",
32
+ focusRingClass,
33
+ ].join(" ")}
34
+ >
35
+ <props.icon className="h-4 w-4" />
36
+ </button>
37
+ </Tooltip.Trigger>
38
+ <Tooltip.Portal>
39
+ <Tooltip.Content
40
+ className="rounded-md bg-primary px-2 py-1 text-xs text-white shadow-md z-50"
41
+ sideOffset={6}
42
+ >
43
+ {props.label}
44
+ </Tooltip.Content>
45
+ </Tooltip.Portal>
46
+ </Tooltip.Root>
47
+ );
48
+ }