@beyondwork/docx-react-component 1.0.29 → 1.0.31
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.
- package/package.json +65 -96
- package/src/README.md +85 -0
- package/src/api/README.md +26 -0
- package/src/api/public-types.ts +1952 -0
- package/src/api/session-state.ts +62 -0
- package/src/compare/diff-engine.ts +623 -0
- package/src/compare/export-redlines.ts +280 -0
- package/src/compare/index.ts +25 -0
- package/src/compare/snapshot.ts +97 -0
- package/src/component-inventory.md +99 -0
- package/src/core/README.md +10 -0
- package/src/core/commands/README.md +3 -0
- package/{dist/chunk-TJBP2K4T.js → src/core/commands/formatting-commands.ts} +536 -196
- package/src/core/commands/image-commands.ts +373 -0
- package/src/core/commands/index.ts +1879 -0
- package/src/core/commands/list-commands.ts +565 -0
- package/src/core/commands/paragraph-layout-commands.ts +339 -0
- package/src/core/commands/review-commands.ts +108 -0
- package/{dist/core/commands/section-layout-commands.cjs → src/core/commands/section-layout-commands.ts} +340 -137
- package/src/core/commands/structural-helpers.ts +309 -0
- package/{dist/core/commands/style-commands.cjs → src/core/commands/style-commands.ts} +113 -65
- package/src/core/commands/table-structure-commands.ts +854 -0
- package/{dist/chunk-UZXBISGO.js → src/core/commands/text-commands.ts} +142 -86
- package/src/core/schema/README.md +3 -0
- package/src/core/schema/text-schema.ts +516 -0
- package/src/core/search/search-text.ts +357 -0
- package/src/core/selection/README.md +3 -0
- package/src/core/selection/mapping.ts +289 -0
- package/src/core/selection/review-anchors.ts +183 -0
- package/src/core/state/README.md +3 -0
- package/src/core/state/editor-state.ts +892 -0
- package/src/core/state/text-transaction.ts +869 -0
- package/src/formats/xlsx/io/parse-shared-strings.ts +41 -0
- package/src/formats/xlsx/io/parse-sheet.ts +459 -0
- package/src/formats/xlsx/io/parse-styles.ts +59 -0
- package/src/formats/xlsx/io/parse-workbook.ts +75 -0
- package/src/formats/xlsx/io/serialize-shared-strings.ts +72 -0
- package/src/formats/xlsx/io/serialize-sheet.ts +333 -0
- package/src/formats/xlsx/io/serialize-styles.ts +98 -0
- package/src/formats/xlsx/io/serialize-workbook.ts +429 -0
- package/src/formats/xlsx/io/xlsx-session.ts +314 -0
- package/src/formats/xlsx/model/cell.ts +189 -0
- package/src/formats/xlsx/model/sheet.ts +326 -0
- package/src/formats/xlsx/model/styles.ts +118 -0
- package/src/formats/xlsx/model/workbook.ts +453 -0
- package/src/formats/xlsx/runtime/cell-commands.ts +567 -0
- package/src/formats/xlsx/runtime/sheet-commands.ts +206 -0
- package/src/formats/xlsx/runtime/workbook-runtime.ts +177 -0
- package/src/formats/xlsx/runtime/workbook-transaction.ts +822 -0
- package/src/index.ts +142 -0
- package/src/io/README.md +10 -0
- package/src/io/docx-session.ts +3175 -0
- package/src/io/export/README.md +3 -0
- package/src/io/export/export-session.ts +220 -0
- package/src/io/export/minimal-docx.ts +115 -0
- package/src/io/export/reattach-preserved-parts.ts +54 -0
- package/src/io/export/serialize-comments.ts +947 -0
- package/src/io/export/serialize-footnotes.ts +394 -0
- package/src/io/export/serialize-headers-footers.ts +368 -0
- package/src/io/export/serialize-main-document.ts +1342 -0
- package/src/io/export/serialize-numbering.ts +218 -0
- package/src/io/export/serialize-revisions.ts +389 -0
- package/src/io/export/serialize-runtime-revisions.ts +463 -0
- package/src/io/export/serialize-tables.ts +174 -0
- package/src/io/export/split-review-boundaries.ts +356 -0
- package/src/io/export/split-story-blocks-for-runtime-revisions.ts +252 -0
- package/src/io/export/table-properties-xml.ts +318 -0
- package/src/io/normalize/README.md +3 -0
- package/src/io/normalize/normalize-text.ts +670 -0
- package/src/io/ooxml/README.md +3 -0
- package/src/io/ooxml/highlight-colors.ts +39 -0
- package/src/io/ooxml/numbering-sentinels.ts +44 -0
- package/src/io/ooxml/parse-comments.ts +852 -0
- package/src/io/ooxml/parse-complex-content.ts +287 -0
- package/src/io/ooxml/parse-fields.ts +834 -0
- package/src/io/ooxml/parse-footnotes.ts +952 -0
- package/src/io/ooxml/parse-headers-footers.ts +1212 -0
- package/src/io/ooxml/parse-inline-media.ts +461 -0
- package/src/io/ooxml/parse-main-document.ts +2947 -0
- package/src/io/ooxml/parse-numbering.ts +747 -0
- package/src/io/ooxml/parse-revisions.ts +1045 -0
- package/src/io/ooxml/parse-settings.ts +184 -0
- package/src/io/ooxml/parse-shapes.ts +296 -0
- package/src/io/ooxml/parse-styles.ts +639 -0
- package/src/io/ooxml/parse-tables.ts +627 -0
- package/src/io/ooxml/parse-theme.ts +346 -0
- package/src/io/ooxml/part-manifest.ts +136 -0
- package/src/io/ooxml/revision-boundaries.ts +475 -0
- package/src/io/ooxml/workflow-payload.ts +544 -0
- package/src/io/opc/README.md +3 -0
- package/src/io/opc/corrupt-package.ts +166 -0
- package/src/io/opc/docx-package.ts +74 -0
- package/src/io/opc/package-reader.ts +325 -0
- package/src/io/opc/package-writer.ts +273 -0
- package/src/io/source-package-provenance.ts +241 -0
- package/{dist/chunk-RMH72RZI.js → src/legal/bookmarks.ts} +130 -44
- package/src/legal/cross-references.ts +414 -0
- package/src/legal/defined-terms.ts +203 -0
- package/src/legal/index.ts +32 -0
- package/src/legal/signature-blocks.ts +259 -0
- package/src/model/README.md +3 -0
- package/src/model/canonical-document.ts +2722 -0
- package/src/model/cds-1.0.0.ts +212 -0
- package/src/model/snapshot.ts +760 -0
- package/src/preservation/README.md +3 -0
- package/src/preservation/markup-compatibility.ts +48 -0
- package/src/preservation/opaque-fragment-store.ts +89 -0
- package/src/preservation/opaque-region.ts +233 -0
- package/src/preservation/package-preservation.ts +113 -0
- package/src/preservation/preserved-part-manifest.ts +56 -0
- package/src/preservation/relationship-retention.ts +57 -0
- package/src/preservation/store.ts +255 -0
- package/src/review/README.md +16 -0
- package/src/review/store/README.md +3 -0
- package/src/review/store/comment-anchors.ts +70 -0
- package/src/review/store/comment-remapping.ts +154 -0
- package/src/review/store/comment-store.ts +349 -0
- package/src/review/store/comment-thread.ts +109 -0
- package/src/review/store/revision-actions.ts +423 -0
- package/src/review/store/revision-store.ts +323 -0
- package/src/review/store/revision-types.ts +182 -0
- package/src/review/store/runtime-comment-store.ts +43 -0
- package/src/runtime/README.md +3 -0
- package/src/runtime/ai-action-policy.ts +764 -0
- package/src/runtime/context-analytics.ts +824 -0
- package/src/runtime/document-layout.ts +332 -0
- package/src/runtime/document-locations.ts +521 -0
- package/src/runtime/document-navigation.ts +616 -0
- package/src/runtime/document-outline.ts +440 -0
- package/src/runtime/document-runtime.ts +4055 -0
- package/src/runtime/document-search.ts +145 -0
- package/src/runtime/event-refresh-hints.ts +137 -0
- package/src/runtime/numbering-prefix.ts +244 -0
- package/src/runtime/page-layout-estimation.ts +305 -0
- package/src/runtime/read-only-diagnostics-runtime.ts +241 -0
- package/src/runtime/resolved-numbering-geometry.ts +293 -0
- package/src/runtime/review-runtime.ts +44 -0
- package/src/runtime/revision-runtime.ts +107 -0
- package/src/runtime/session-capabilities.ts +192 -0
- package/src/runtime/story-context.ts +164 -0
- package/src/runtime/story-targeting.ts +162 -0
- package/src/runtime/suggestions-snapshot.ts +137 -0
- package/src/runtime/surface-projection.ts +1553 -0
- package/src/runtime/table-commands.ts +173 -0
- package/src/runtime/table-schema.ts +309 -0
- package/src/runtime/table-style-resolver.ts +409 -0
- package/src/runtime/view-state.ts +493 -0
- package/src/runtime/virtualized-rendering.ts +258 -0
- package/src/runtime/workflow-markup.ts +393 -0
- package/src/ui/README.md +30 -0
- package/src/ui/WordReviewEditor.tsx +5268 -0
- package/src/ui/browser-export.ts +52 -0
- package/src/ui/comments/README.md +3 -0
- package/src/ui/compatibility/README.md +3 -0
- package/src/ui/editor-command-bag.ts +127 -0
- package/src/ui/editor-runtime-boundary.ts +1558 -0
- package/src/ui/editor-shell-view.tsx +144 -0
- package/src/ui/editor-surface/README.md +3 -0
- package/src/ui/editor-surface-controller.tsx +66 -0
- package/src/ui/headless/comment-decoration-model.ts +124 -0
- package/src/ui/headless/preserve-editor-selection.ts +5 -0
- package/src/ui/headless/revision-decoration-model.ts +128 -0
- package/src/ui/headless/selection-helpers.ts +54 -0
- package/src/ui/headless/selection-tool-context.ts +19 -0
- package/src/ui/headless/selection-tool-resolver.ts +752 -0
- package/src/ui/headless/selection-tool-types.ts +129 -0
- package/src/ui/headless/selection-toolbar-model.ts +11 -0
- package/src/ui/headless/use-editor-keyboard.ts +103 -0
- package/src/ui/review/README.md +3 -0
- package/src/ui/runtime-shortcut-dispatch.ts +365 -0
- package/src/ui/runtime-snapshot-selectors.ts +197 -0
- package/src/ui/shared/revision-filters.ts +31 -0
- package/src/ui/status/README.md +3 -0
- package/src/ui/theme/README.md +3 -0
- package/src/ui/toolbar/README.md +3 -0
- package/src/ui/workflow-surface-blocked-rails.ts +94 -0
- package/src/ui-tailwind/chrome/chrome-preset-model.ts +107 -0
- package/src/ui-tailwind/chrome/chrome-preset-toolbar.tsx +15 -0
- package/src/ui-tailwind/chrome/responsive-chrome.ts +46 -0
- package/src/ui-tailwind/chrome/review-queue-bar.tsx +97 -0
- package/src/ui-tailwind/chrome/tw-alert-banner.tsx +64 -0
- package/src/ui-tailwind/chrome/tw-context-analytics-summary.tsx +122 -0
- package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +121 -0
- package/src/ui-tailwind/chrome/tw-layout-panel.tsx +114 -0
- package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +30 -0
- package/src/ui-tailwind/chrome/tw-page-ruler.tsx +365 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-blocked.tsx +23 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-comment.tsx +35 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-formatting.tsx +37 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +303 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +116 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-suggestion.tsx +29 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-workflow.tsx +27 -0
- package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +186 -0
- package/src/ui-tailwind/chrome/tw-suggestion-card.tsx +139 -0
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +250 -0
- package/src/ui-tailwind/chrome/tw-unsaved-modal.tsx +58 -0
- package/src/ui-tailwind/chrome/use-before-unload.ts +20 -0
- package/src/ui-tailwind/editor-surface/perf-probe.ts +179 -0
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +189 -0
- package/src/ui-tailwind/editor-surface/pm-contextual-ui.ts +31 -0
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +411 -0
- package/src/ui-tailwind/editor-surface/pm-position-map.ts +123 -0
- package/src/ui-tailwind/editor-surface/pm-schema.ts +927 -0
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +567 -0
- package/src/ui-tailwind/editor-surface/search-plugin.ts +168 -0
- package/src/ui-tailwind/editor-surface/surface-build-keys.ts +63 -0
- package/src/ui-tailwind/editor-surface/tw-caret.tsx +12 -0
- package/src/ui-tailwind/editor-surface/tw-editor-surface.tsx +150 -0
- package/src/ui-tailwind/editor-surface/tw-inline-token.tsx +129 -0
- package/src/ui-tailwind/editor-surface/tw-opaque-block.tsx +58 -0
- package/src/ui-tailwind/editor-surface/tw-paragraph-block.tsx +151 -0
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +1047 -0
- package/src/ui-tailwind/editor-surface/tw-segment-view.tsx +111 -0
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +503 -0
- package/src/ui-tailwind/index.ts +62 -0
- package/src/ui-tailwind/page-chrome-model.ts +27 -0
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +406 -0
- package/src/ui-tailwind/review/tw-health-panel.tsx +149 -0
- package/src/ui-tailwind/review/tw-review-rail.tsx +130 -0
- package/src/ui-tailwind/review/tw-revision-sidebar.tsx +164 -0
- package/src/ui-tailwind/status/tw-status-bar.tsx +65 -0
- package/{dist → src}/ui-tailwind/theme/editor-theme.css +58 -40
- package/src/ui-tailwind/toolbar/toolbar-layout.ts +47 -0
- package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +52 -0
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +1478 -0
- package/src/ui-tailwind/tw-review-workspace.tsx +1587 -0
- package/src/validation/README.md +3 -0
- package/src/validation/compatibility-engine.ts +878 -0
- package/src/validation/compatibility-report.ts +161 -0
- package/src/validation/diagnostics.ts +204 -0
- package/src/validation/docx-comment-proof.ts +720 -0
- package/src/validation/import-diagnostics.ts +128 -0
- package/src/validation/low-priority-word-surfaces.ts +373 -0
- package/dist/canonical-document-BLEbzL2J.d.cts +0 -844
- package/dist/canonical-document-BLEbzL2J.d.ts +0 -844
- package/dist/chunk-2FJS5GZM.js +0 -763
- package/dist/chunk-2FJS5GZM.js.map +0 -1
- package/dist/chunk-2OQBZS3F.js +0 -446
- package/dist/chunk-2OQBZS3F.js.map +0 -1
- package/dist/chunk-2S7W4KFO.js +0 -127
- package/dist/chunk-2S7W4KFO.js.map +0 -1
- package/dist/chunk-2TG72QSW.js +0 -3874
- package/dist/chunk-2TG72QSW.js.map +0 -1
- package/dist/chunk-36QNIZBO.js +0 -532
- package/dist/chunk-36QNIZBO.js.map +0 -1
- package/dist/chunk-4AQOYAW4.js +0 -3069
- package/dist/chunk-4AQOYAW4.js.map +0 -1
- package/dist/chunk-4D5EWJ3P.js +0 -77
- package/dist/chunk-4D5EWJ3P.js.map +0 -1
- package/dist/chunk-5FN54NDH.js +0 -2257
- package/dist/chunk-5FN54NDH.js.map +0 -1
- package/dist/chunk-BOYGQYRQ.js +0 -7306
- package/dist/chunk-BOYGQYRQ.js.map +0 -1
- package/dist/chunk-CN3XMECL.js +0 -212
- package/dist/chunk-CN3XMECL.js.map +0 -1
- package/dist/chunk-EBI3BX6U.js +0 -164
- package/dist/chunk-EBI3BX6U.js.map +0 -1
- package/dist/chunk-EILUG3VB.js +0 -1275
- package/dist/chunk-EILUG3VB.js.map +0 -1
- package/dist/chunk-FUDY333O.js +0 -70
- package/dist/chunk-FUDY333O.js.map +0 -1
- package/dist/chunk-GBVOWFIK.js +0 -1237
- package/dist/chunk-GBVOWFIK.js.map +0 -1
- package/dist/chunk-H4TQ3H3Y.js +0 -262
- package/dist/chunk-H4TQ3H3Y.js.map +0 -1
- package/dist/chunk-JGB3IXZO.js +0 -189
- package/dist/chunk-JGB3IXZO.js.map +0 -1
- package/dist/chunk-KD2QRQPY.js +0 -4342
- package/dist/chunk-KD2QRQPY.js.map +0 -1
- package/dist/chunk-KLMXQVYK.js +0 -369
- package/dist/chunk-KLMXQVYK.js.map +0 -1
- package/dist/chunk-KZUG5KFQ.js +0 -214
- package/dist/chunk-KZUG5KFQ.js.map +0 -1
- package/dist/chunk-QDAQ4CJU.js +0 -345
- package/dist/chunk-QDAQ4CJU.js.map +0 -1
- package/dist/chunk-RMH72RZI.js.map +0 -1
- package/dist/chunk-SWKWQZXM.js +0 -117
- package/dist/chunk-SWKWQZXM.js.map +0 -1
- package/dist/chunk-TJBP2K4T.js.map +0 -1
- package/dist/chunk-TLCEAQDQ.js +0 -542
- package/dist/chunk-TLCEAQDQ.js.map +0 -1
- package/dist/chunk-UZXBISGO.js.map +0 -1
- package/dist/chunk-WGBAKP3Q.js +0 -3220
- package/dist/chunk-WGBAKP3Q.js.map +0 -1
- package/dist/compare/index.cjs +0 -5475
- package/dist/compare/index.cjs.map +0 -1
- package/dist/compare/index.d.cts +0 -114
- package/dist/compare/index.d.ts +0 -114
- package/dist/compare/index.js +0 -731
- package/dist/compare/index.js.map +0 -1
- package/dist/core/commands/formatting-commands.cjs +0 -828
- package/dist/core/commands/formatting-commands.cjs.map +0 -1
- package/dist/core/commands/formatting-commands.d.cts +0 -63
- package/dist/core/commands/formatting-commands.d.ts +0 -63
- package/dist/core/commands/formatting-commands.js +0 -37
- package/dist/core/commands/formatting-commands.js.map +0 -1
- package/dist/core/commands/image-commands.cjs +0 -2023
- package/dist/core/commands/image-commands.cjs.map +0 -1
- package/dist/core/commands/image-commands.d.cts +0 -58
- package/dist/core/commands/image-commands.d.ts +0 -58
- package/dist/core/commands/image-commands.js +0 -18
- package/dist/core/commands/image-commands.js.map +0 -1
- package/dist/core/commands/section-layout-commands.cjs.map +0 -1
- package/dist/core/commands/section-layout-commands.d.cts +0 -62
- package/dist/core/commands/section-layout-commands.d.ts +0 -62
- package/dist/core/commands/section-layout-commands.js +0 -21
- package/dist/core/commands/section-layout-commands.js.map +0 -1
- package/dist/core/commands/style-commands.cjs.map +0 -1
- package/dist/core/commands/style-commands.d.cts +0 -13
- package/dist/core/commands/style-commands.d.ts +0 -13
- package/dist/core/commands/style-commands.js +0 -9
- package/dist/core/commands/style-commands.js.map +0 -1
- package/dist/core/commands/table-structure-commands.cjs +0 -1883
- package/dist/core/commands/table-structure-commands.cjs.map +0 -1
- package/dist/core/commands/table-structure-commands.d.cts +0 -59
- package/dist/core/commands/table-structure-commands.d.ts +0 -59
- package/dist/core/commands/table-structure-commands.js +0 -12
- package/dist/core/commands/table-structure-commands.js.map +0 -1
- package/dist/core/commands/text-commands.cjs +0 -2391
- package/dist/core/commands/text-commands.cjs.map +0 -1
- package/dist/core/commands/text-commands.d.cts +0 -24
- package/dist/core/commands/text-commands.d.ts +0 -24
- package/dist/core/commands/text-commands.js +0 -28
- package/dist/core/commands/text-commands.js.map +0 -1
- package/dist/core/selection/mapping.cjs +0 -200
- package/dist/core/selection/mapping.cjs.map +0 -1
- package/dist/core/selection/mapping.d.cts +0 -2
- package/dist/core/selection/mapping.d.ts +0 -2
- package/dist/core/selection/mapping.js +0 -31
- package/dist/core/selection/mapping.js.map +0 -1
- package/dist/core/state/editor-state.cjs +0 -2278
- package/dist/core/state/editor-state.cjs.map +0 -1
- package/dist/core/state/editor-state.d.cts +0 -2
- package/dist/core/state/editor-state.d.ts +0 -2
- package/dist/core/state/editor-state.js +0 -26
- package/dist/core/state/editor-state.js.map +0 -1
- package/dist/index.cjs +0 -38553
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -15
- package/dist/index.d.ts +0 -15
- package/dist/index.js +0 -7856
- package/dist/index.js.map +0 -1
- package/dist/io/docx-session.cjs +0 -16236
- package/dist/io/docx-session.cjs.map +0 -1
- package/dist/io/docx-session.d.cts +0 -21
- package/dist/io/docx-session.d.ts +0 -21
- package/dist/io/docx-session.js +0 -18
- package/dist/io/docx-session.js.map +0 -1
- package/dist/legal/index.cjs +0 -3900
- package/dist/legal/index.cjs.map +0 -1
- package/dist/legal/index.d.cts +0 -86
- package/dist/legal/index.d.ts +0 -86
- package/dist/legal/index.js +0 -616
- package/dist/legal/index.js.map +0 -1
- package/dist/public-types-7ZL_94cz.d.ts +0 -1573
- package/dist/public-types-CeMaDueh.d.cts +0 -1573
- package/dist/public-types.cjs +0 -19
- package/dist/public-types.cjs.map +0 -1
- package/dist/public-types.d.cts +0 -2
- package/dist/public-types.d.ts +0 -2
- package/dist/public-types.js +0 -1
- package/dist/public-types.js.map +0 -1
- package/dist/runtime/document-runtime.cjs +0 -11140
- package/dist/runtime/document-runtime.cjs.map +0 -1
- package/dist/runtime/document-runtime.d.cts +0 -231
- package/dist/runtime/document-runtime.d.ts +0 -231
- package/dist/runtime/document-runtime.js +0 -21
- package/dist/runtime/document-runtime.js.map +0 -1
- package/dist/structural-helpers-CilgOVhh.d.cts +0 -10
- package/dist/structural-helpers-q0Gd-eBN.d.ts +0 -10
- package/dist/ui-tailwind/editor-surface/search-plugin.cjs +0 -313
- package/dist/ui-tailwind/editor-surface/search-plugin.cjs.map +0 -1
- package/dist/ui-tailwind/editor-surface/search-plugin.d.cts +0 -67
- package/dist/ui-tailwind/editor-surface/search-plugin.d.ts +0 -67
- package/dist/ui-tailwind/editor-surface/search-plugin.js +0 -23
- package/dist/ui-tailwind/editor-surface/search-plugin.js.map +0 -1
- package/dist/ui-tailwind/index.cjs +0 -4833
- package/dist/ui-tailwind/index.cjs.map +0 -1
- package/dist/ui-tailwind/index.d.cts +0 -617
- package/dist/ui-tailwind/index.d.ts +0 -617
- package/dist/ui-tailwind/index.js +0 -575
- package/dist/ui-tailwind/index.js.map +0 -1
|
@@ -0,0 +1,1478 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import * as Popover from "@radix-ui/react-popover";
|
|
4
|
+
import * as Select from "@radix-ui/react-select";
|
|
5
|
+
import * as Toggle from "@radix-ui/react-toggle";
|
|
6
|
+
import * as ToggleGroup from "@radix-ui/react-toggle-group";
|
|
7
|
+
import * as Tooltip from "@radix-ui/react-tooltip";
|
|
8
|
+
import {
|
|
9
|
+
AlertCircle,
|
|
10
|
+
AlignCenter,
|
|
11
|
+
AlignJustify,
|
|
12
|
+
AlignLeft,
|
|
13
|
+
AlignRight,
|
|
14
|
+
Baseline,
|
|
15
|
+
Bold,
|
|
16
|
+
ChevronDown,
|
|
17
|
+
Download,
|
|
18
|
+
Eye,
|
|
19
|
+
EyeOff,
|
|
20
|
+
FileText,
|
|
21
|
+
Highlighter,
|
|
22
|
+
ImagePlus,
|
|
23
|
+
Indent,
|
|
24
|
+
Italic,
|
|
25
|
+
List,
|
|
26
|
+
MessageSquare,
|
|
27
|
+
Minus,
|
|
28
|
+
Monitor,
|
|
29
|
+
MoreHorizontal,
|
|
30
|
+
Outdent,
|
|
31
|
+
PanelRight,
|
|
32
|
+
Plus,
|
|
33
|
+
Redo2,
|
|
34
|
+
RotateCcw,
|
|
35
|
+
Rows3,
|
|
36
|
+
Strikethrough,
|
|
37
|
+
Subscript,
|
|
38
|
+
Superscript,
|
|
39
|
+
Underline,
|
|
40
|
+
Undo2,
|
|
41
|
+
} from "lucide-react";
|
|
42
|
+
|
|
43
|
+
import type {
|
|
44
|
+
ActiveListContext,
|
|
45
|
+
CompatibilityPanelSnapshot,
|
|
46
|
+
EditorStoryTarget,
|
|
47
|
+
EditorWarning,
|
|
48
|
+
FormattingStateSnapshot,
|
|
49
|
+
FormattingAlignment,
|
|
50
|
+
InsertImageOptions,
|
|
51
|
+
SectionBreakType,
|
|
52
|
+
StyleCatalogSnapshot,
|
|
53
|
+
WorkflowBlockedCommandReason,
|
|
54
|
+
WordReviewEditorChromePreset,
|
|
55
|
+
WorkspaceMode,
|
|
56
|
+
ZoomLevel,
|
|
57
|
+
} from "../../api/public-types";
|
|
58
|
+
import type { SessionCapabilities } from "../../runtime/session-capabilities";
|
|
59
|
+
import { preserveEditorSelectionMouseDown } from "../../ui/headless/preserve-editor-selection";
|
|
60
|
+
import { TwHealthPanel } from "../review/tw-health-panel";
|
|
61
|
+
import { resolveToolbarLayoutModel } from "./toolbar-layout";
|
|
62
|
+
import { TwToolbarIconButton } from "./tw-toolbar-icon-button";
|
|
63
|
+
|
|
64
|
+
export interface TwToolbarProps {
|
|
65
|
+
capabilities?: SessionCapabilities;
|
|
66
|
+
compatibility?: CompatibilityPanelSnapshot;
|
|
67
|
+
warnings?: EditorWarning[];
|
|
68
|
+
blockedReasons?: WorkflowBlockedCommandReason[];
|
|
69
|
+
showDiagnosticsChrome?: boolean;
|
|
70
|
+
interactionPolicy?: ToolbarInteractionPolicy;
|
|
71
|
+
preset?: WordReviewEditorChromePreset;
|
|
72
|
+
compactMode?: boolean;
|
|
73
|
+
workspaceMode: WorkspaceMode;
|
|
74
|
+
zoomLevel?: ZoomLevel;
|
|
75
|
+
formattingState?: FormattingStateSnapshot;
|
|
76
|
+
activeListContext?: ActiveListContext | null;
|
|
77
|
+
styleCatalog?: StyleCatalogSnapshot;
|
|
78
|
+
/** Display toggle for tracked change decorations (not a runtime mutation toggle). */
|
|
79
|
+
showTrackedChanges: boolean;
|
|
80
|
+
/** Active story target — shows a breadcrumb when editing a secondary story. */
|
|
81
|
+
activeStory?: EditorStoryTarget;
|
|
82
|
+
/** Called when the user clicks the story breadcrumb to return to main body. */
|
|
83
|
+
onCloseStory?: () => void;
|
|
84
|
+
onUndo: () => void;
|
|
85
|
+
onRedo: () => void;
|
|
86
|
+
onSetParagraphStyle?: (styleId: string) => void;
|
|
87
|
+
onToggleBold?: () => void;
|
|
88
|
+
onToggleItalic?: () => void;
|
|
89
|
+
onToggleUnderline?: () => void;
|
|
90
|
+
onToggleStrikethrough?: () => void;
|
|
91
|
+
onToggleSuperscript?: () => void;
|
|
92
|
+
onToggleSubscript?: () => void;
|
|
93
|
+
onSetFontFamily?: (fontFamily: string) => void;
|
|
94
|
+
onSetFontSize?: (fontSize: number) => void;
|
|
95
|
+
onSetTextColor?: (color: string) => void;
|
|
96
|
+
onSetHighlightColor?: (color: string | null) => void;
|
|
97
|
+
onSetAlignment?: (alignment: FormattingAlignment) => void;
|
|
98
|
+
onToggleBulletedList?: () => void;
|
|
99
|
+
onToggleNumberedList?: () => void;
|
|
100
|
+
onOutdent?: () => void;
|
|
101
|
+
onIndent?: () => void;
|
|
102
|
+
onAddComment: () => void;
|
|
103
|
+
onInsertPageBreak?: () => void;
|
|
104
|
+
onInsertTable?: () => void;
|
|
105
|
+
onInsertSectionBreak?: (type: SectionBreakType) => void;
|
|
106
|
+
onInsertImage?: (options: InsertImageOptions) => void;
|
|
107
|
+
onExport: () => void;
|
|
108
|
+
onWorkspaceModeChange: (value: WorkspaceMode) => void;
|
|
109
|
+
showSidebarToggle?: boolean;
|
|
110
|
+
isSidebarOpen?: boolean;
|
|
111
|
+
onToggleSidebar?: () => void;
|
|
112
|
+
onZoomChange?: (level: ZoomLevel) => void;
|
|
113
|
+
onShowTrackedChangesChange: (show: boolean) => void;
|
|
114
|
+
onRestartNumbering?: () => void;
|
|
115
|
+
onContinueNumbering?: () => void;
|
|
116
|
+
onUpdateFields?: () => void;
|
|
117
|
+
onUpdateTableOfContents?: () => void;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface ToolbarInteractionPolicy {
|
|
121
|
+
mode: "edit" | "suggest" | "comment" | "view" | "blocked";
|
|
122
|
+
canFormatText: boolean;
|
|
123
|
+
canInsertStructural: boolean;
|
|
124
|
+
canAddComment: boolean;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function getSupportedZoomPresets(): ReadonlyArray<number> {
|
|
128
|
+
return [75, 100, 125, 150];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const focusRingClass =
|
|
132
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-canvas";
|
|
133
|
+
|
|
134
|
+
const FONT_FAMILIES = ["Arial", "Times New Roman", "Calibri", "Cambria", "Georgia", "Verdana"];
|
|
135
|
+
const FONT_SIZES = [8, 9, 10, 11, 12, 14, 16, 18, 20, 24, 28, 36];
|
|
136
|
+
const TEXT_COLORS = ["#1f1f1f", "#5c5852", "#1660a8", "#50684d", "#9b4f49", "#7b5f32"];
|
|
137
|
+
const HIGHLIGHT_COLORS = [
|
|
138
|
+
{ value: "#ffff00", label: "Yellow" },
|
|
139
|
+
{ value: "#d9e7f7", label: "Blue" },
|
|
140
|
+
{ value: "#f1dbd6", label: "Rose" },
|
|
141
|
+
{ value: "#e6e0d4", label: "Stone" },
|
|
142
|
+
{ value: null, label: "None" },
|
|
143
|
+
] as const;
|
|
144
|
+
|
|
145
|
+
export function TwToolbar(props: TwToolbarProps) {
|
|
146
|
+
const caps = props.capabilities;
|
|
147
|
+
const preset = props.preset ?? "advanced";
|
|
148
|
+
const isCompact = props.compactMode ?? false;
|
|
149
|
+
const workspaceMode = props.workspaceMode;
|
|
150
|
+
const paragraphStyles = props.styleCatalog?.paragraphs ?? [];
|
|
151
|
+
const zoomLevel = props.zoomLevel ?? 100;
|
|
152
|
+
const canEdit = props.interactionPolicy?.canFormatText ?? (caps ? caps.canEdit : false);
|
|
153
|
+
const canInsertStructural = props.interactionPolicy?.canInsertStructural ?? canEdit;
|
|
154
|
+
const canAddComment = props.interactionPolicy?.canAddComment ?? (caps ? caps.canAddComment : false);
|
|
155
|
+
const showStyleSelectors = preset === "advanced";
|
|
156
|
+
const showAdvancedFormatting = preset === "advanced";
|
|
157
|
+
const showFormattingColors = preset !== "review";
|
|
158
|
+
const showInsertMenu = preset === "simple" || preset === "advanced";
|
|
159
|
+
const showTrackedChangesToggle = preset !== "simple";
|
|
160
|
+
const showDiagnosticsChrome = props.showDiagnosticsChrome ?? true;
|
|
161
|
+
const showHealth = showDiagnosticsChrome && Boolean(props.compatibility && props.warnings);
|
|
162
|
+
const showListActions = preset === "simple" || preset === "advanced";
|
|
163
|
+
const showUpdateActions = preset === "advanced";
|
|
164
|
+
const showSidebarToggle = props.showSidebarToggle ?? false;
|
|
165
|
+
const layoutModel = resolveToolbarLayoutModel({
|
|
166
|
+
compactMode: isCompact,
|
|
167
|
+
preset,
|
|
168
|
+
hasActiveListContext: Boolean(props.activeListContext),
|
|
169
|
+
});
|
|
170
|
+
const showPostFormattingDivider =
|
|
171
|
+
layoutModel.showListActionsInRow ||
|
|
172
|
+
layoutModel.showSpacingActionsInRow ||
|
|
173
|
+
(showInsertMenu && layoutModel.showInsertActionsInRow) ||
|
|
174
|
+
(showUpdateActions && layoutModel.showUpdateActionsInRow) ||
|
|
175
|
+
layoutModel.showCompactOverflow;
|
|
176
|
+
const zoomLabel =
|
|
177
|
+
typeof zoomLevel === "number"
|
|
178
|
+
? `${zoomLevel}%`
|
|
179
|
+
: zoomLevel === "pageWidth"
|
|
180
|
+
? "Fit width"
|
|
181
|
+
: "Fit page";
|
|
182
|
+
|
|
183
|
+
return (
|
|
184
|
+
<header
|
|
185
|
+
className={[
|
|
186
|
+
"shrink-0 rounded-xl border border-border/70 bg-canvas/92 px-2.5 shadow-[0_8px_20px_-18px_var(--color-shadow-strong)] backdrop-blur-sm",
|
|
187
|
+
isCompact
|
|
188
|
+
? "flex min-h-11 flex-wrap items-center gap-1.5 py-2"
|
|
189
|
+
: "flex h-11 items-center gap-1",
|
|
190
|
+
].join(" ")}
|
|
191
|
+
>
|
|
192
|
+
{/* Left cluster: undo/redo + formatting */}
|
|
193
|
+
<div className={`flex min-w-0 flex-1 items-center gap-0.5 ${isCompact ? "flex-wrap" : ""}`}>
|
|
194
|
+
<TwToolbarIconButton
|
|
195
|
+
icon={Undo2}
|
|
196
|
+
label="Undo"
|
|
197
|
+
disabled={caps ? !caps.canUndo : true}
|
|
198
|
+
onClick={props.onUndo}
|
|
199
|
+
/>
|
|
200
|
+
<TwToolbarIconButton
|
|
201
|
+
icon={Redo2}
|
|
202
|
+
label="Redo"
|
|
203
|
+
disabled={caps ? !caps.canRedo : true}
|
|
204
|
+
onClick={props.onRedo}
|
|
205
|
+
/>
|
|
206
|
+
<div className="mx-1 h-4 w-px bg-border" />
|
|
207
|
+
|
|
208
|
+
{showStyleSelectors && layoutModel.showStyleSelectorsInRow ? (
|
|
209
|
+
<>
|
|
210
|
+
<ToolbarParagraphStyleSelect
|
|
211
|
+
disabled={!canEdit || paragraphStyles.length === 0 || !props.onSetParagraphStyle}
|
|
212
|
+
styles={paragraphStyles}
|
|
213
|
+
value={props.formattingState?.paragraphStyleId}
|
|
214
|
+
onValueChange={props.onSetParagraphStyle}
|
|
215
|
+
/>
|
|
216
|
+
|
|
217
|
+
<ToolbarFontFamilySelect
|
|
218
|
+
disabled={!canEdit || !props.onSetFontFamily}
|
|
219
|
+
value={props.formattingState?.fontFamily}
|
|
220
|
+
onValueChange={props.onSetFontFamily}
|
|
221
|
+
/>
|
|
222
|
+
<ToolbarFontSizeSelect
|
|
223
|
+
disabled={!canEdit || !props.onSetFontSize}
|
|
224
|
+
value={props.formattingState?.fontSize}
|
|
225
|
+
onValueChange={props.onSetFontSize}
|
|
226
|
+
/>
|
|
227
|
+
|
|
228
|
+
<div className="mx-1 h-4 w-px bg-border" />
|
|
229
|
+
</>
|
|
230
|
+
) : null}
|
|
231
|
+
|
|
232
|
+
<TwToolbarIconButton
|
|
233
|
+
icon={Bold}
|
|
234
|
+
label="Bold"
|
|
235
|
+
active={props.formattingState?.bold ?? false}
|
|
236
|
+
disabled={!canEdit}
|
|
237
|
+
onClick={props.onToggleBold}
|
|
238
|
+
/>
|
|
239
|
+
<TwToolbarIconButton
|
|
240
|
+
icon={Italic}
|
|
241
|
+
label="Italic"
|
|
242
|
+
active={props.formattingState?.italic ?? false}
|
|
243
|
+
disabled={!canEdit}
|
|
244
|
+
onClick={props.onToggleItalic}
|
|
245
|
+
/>
|
|
246
|
+
<TwToolbarIconButton
|
|
247
|
+
icon={Underline}
|
|
248
|
+
label="Underline"
|
|
249
|
+
active={props.formattingState?.underline ?? false}
|
|
250
|
+
disabled={!canEdit}
|
|
251
|
+
onClick={props.onToggleUnderline}
|
|
252
|
+
/>
|
|
253
|
+
{showAdvancedFormatting ? (
|
|
254
|
+
<ToolbarFormattingOverflow
|
|
255
|
+
disabled={!canEdit}
|
|
256
|
+
formattingState={props.formattingState}
|
|
257
|
+
onToggleStrikethrough={props.onToggleStrikethrough}
|
|
258
|
+
onToggleSuperscript={props.onToggleSuperscript}
|
|
259
|
+
onToggleSubscript={props.onToggleSubscript}
|
|
260
|
+
/>
|
|
261
|
+
) : null}
|
|
262
|
+
{showFormattingColors ? (
|
|
263
|
+
<>
|
|
264
|
+
<ToolbarColorPopover
|
|
265
|
+
ariaLabel="Text color"
|
|
266
|
+
colors={TEXT_COLORS.map((value) => ({ value, label: value }))}
|
|
267
|
+
disabled={!canEdit || !props.onSetTextColor}
|
|
268
|
+
icon={<Baseline className="h-3.5 w-3.5" />}
|
|
269
|
+
onSelect={(value) => {
|
|
270
|
+
if (value) {
|
|
271
|
+
props.onSetTextColor?.(value);
|
|
272
|
+
}
|
|
273
|
+
}}
|
|
274
|
+
title="Text color"
|
|
275
|
+
/>
|
|
276
|
+
<ToolbarColorPopover
|
|
277
|
+
ariaLabel="Highlight color"
|
|
278
|
+
colors={HIGHLIGHT_COLORS.map((entry) => ({ value: entry.value, label: entry.label }))}
|
|
279
|
+
disabled={!canEdit || !props.onSetHighlightColor}
|
|
280
|
+
icon={<Highlighter className="h-3.5 w-3.5" />}
|
|
281
|
+
onSelect={(value) => props.onSetHighlightColor?.(value)}
|
|
282
|
+
title="Highlight color"
|
|
283
|
+
/>
|
|
284
|
+
<ToolbarAlignmentPopover
|
|
285
|
+
activeAlignment={props.formattingState?.alignment}
|
|
286
|
+
disabled={!canEdit || !props.onSetAlignment}
|
|
287
|
+
onSelect={(alignment) => props.onSetAlignment?.(alignment)}
|
|
288
|
+
/>
|
|
289
|
+
</>
|
|
290
|
+
) : null}
|
|
291
|
+
|
|
292
|
+
{showPostFormattingDivider ? <div className="mx-1 h-4 w-px bg-border" /> : null}
|
|
293
|
+
|
|
294
|
+
{showListActions && layoutModel.showListActionsInRow ? (
|
|
295
|
+
<>
|
|
296
|
+
<TwToolbarIconButton
|
|
297
|
+
icon={List}
|
|
298
|
+
label="Bulleted list"
|
|
299
|
+
active={Boolean(props.activeListContext && !props.activeListContext.isOrdered)}
|
|
300
|
+
disabled={!canEdit}
|
|
301
|
+
onClick={props.onToggleBulletedList}
|
|
302
|
+
/>
|
|
303
|
+
<TwToolbarIconButton
|
|
304
|
+
icon={Rows3}
|
|
305
|
+
label="Numbered list"
|
|
306
|
+
active={Boolean(props.activeListContext?.isOrdered)}
|
|
307
|
+
disabled={!canEdit}
|
|
308
|
+
onClick={props.onToggleNumberedList}
|
|
309
|
+
/>
|
|
310
|
+
</>
|
|
311
|
+
) : null}
|
|
312
|
+
{layoutModel.showSpacingActionsInRow ? (
|
|
313
|
+
<>
|
|
314
|
+
<TwToolbarIconButton
|
|
315
|
+
icon={Outdent}
|
|
316
|
+
label="Outdent"
|
|
317
|
+
disabled={!canEdit}
|
|
318
|
+
onClick={props.onOutdent}
|
|
319
|
+
/>
|
|
320
|
+
<TwToolbarIconButton
|
|
321
|
+
icon={Indent}
|
|
322
|
+
label="Indent"
|
|
323
|
+
disabled={!canEdit}
|
|
324
|
+
onClick={props.onIndent}
|
|
325
|
+
/>
|
|
326
|
+
</>
|
|
327
|
+
) : null}
|
|
328
|
+
{layoutModel.showListContinuationInRow ? (
|
|
329
|
+
<>
|
|
330
|
+
<button
|
|
331
|
+
type="button"
|
|
332
|
+
aria-label="Restart numbering"
|
|
333
|
+
disabled={!canEdit || !props.onRestartNumbering}
|
|
334
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
335
|
+
onClick={props.onRestartNumbering}
|
|
336
|
+
className={`inline-flex h-7 items-center rounded-md px-2 text-xs font-medium text-secondary transition-colors hover:bg-surface outline-none disabled:cursor-not-allowed disabled:opacity-40 ${focusRingClass}`}
|
|
337
|
+
>
|
|
338
|
+
Restart
|
|
339
|
+
</button>
|
|
340
|
+
<button
|
|
341
|
+
type="button"
|
|
342
|
+
aria-label="Continue numbering"
|
|
343
|
+
disabled={!canEdit || !props.onContinueNumbering}
|
|
344
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
345
|
+
onClick={props.onContinueNumbering}
|
|
346
|
+
className={`inline-flex h-7 items-center rounded-md px-2 text-xs font-medium text-secondary transition-colors hover:bg-surface outline-none disabled:cursor-not-allowed disabled:opacity-40 ${focusRingClass}`}
|
|
347
|
+
>
|
|
348
|
+
Continue
|
|
349
|
+
</button>
|
|
350
|
+
</>
|
|
351
|
+
) : null}
|
|
352
|
+
{showInsertMenu && layoutModel.showInsertActionsInRow ? (
|
|
353
|
+
<ToolbarInsertMenu
|
|
354
|
+
disabled={!canInsertStructural}
|
|
355
|
+
onInsertImage={props.onInsertImage}
|
|
356
|
+
onInsertPageBreak={props.onInsertPageBreak}
|
|
357
|
+
onInsertSectionBreak={props.onInsertSectionBreak}
|
|
358
|
+
onInsertTable={props.onInsertTable}
|
|
359
|
+
/>
|
|
360
|
+
) : null}
|
|
361
|
+
{showUpdateActions && layoutModel.showUpdateActionsInRow ? (
|
|
362
|
+
<>
|
|
363
|
+
<button
|
|
364
|
+
type="button"
|
|
365
|
+
aria-label="Refresh fields"
|
|
366
|
+
disabled={!canEdit || !props.onUpdateFields}
|
|
367
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
368
|
+
onClick={props.onUpdateFields}
|
|
369
|
+
className={`inline-flex h-7 items-center rounded-md px-2 text-xs font-medium text-secondary transition-colors hover:bg-surface outline-none disabled:cursor-not-allowed disabled:opacity-40 ${focusRingClass}`}
|
|
370
|
+
>
|
|
371
|
+
Fields
|
|
372
|
+
</button>
|
|
373
|
+
<button
|
|
374
|
+
type="button"
|
|
375
|
+
aria-label="Refresh table of contents"
|
|
376
|
+
disabled={!canEdit || !props.onUpdateTableOfContents}
|
|
377
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
378
|
+
onClick={props.onUpdateTableOfContents}
|
|
379
|
+
className={`inline-flex h-7 items-center rounded-md px-2 text-xs font-medium text-secondary transition-colors hover:bg-surface outline-none disabled:cursor-not-allowed disabled:opacity-40 ${focusRingClass}`}
|
|
380
|
+
>
|
|
381
|
+
TOC
|
|
382
|
+
</button>
|
|
383
|
+
</>
|
|
384
|
+
) : null}
|
|
385
|
+
{layoutModel.showCompactOverflow ? (
|
|
386
|
+
<ToolbarCompactOverflow
|
|
387
|
+
activeListContext={props.activeListContext}
|
|
388
|
+
canEdit={canEdit}
|
|
389
|
+
canInsertStructural={canInsertStructural}
|
|
390
|
+
formattingState={props.formattingState}
|
|
391
|
+
paragraphStyles={paragraphStyles}
|
|
392
|
+
showInsertMenu={showInsertMenu}
|
|
393
|
+
showListActions={showListActions}
|
|
394
|
+
showStyleSelectors={showStyleSelectors}
|
|
395
|
+
showUpdateActions={showUpdateActions}
|
|
396
|
+
onSetParagraphStyle={props.onSetParagraphStyle}
|
|
397
|
+
onSetFontFamily={props.onSetFontFamily}
|
|
398
|
+
onSetFontSize={props.onSetFontSize}
|
|
399
|
+
onToggleBulletedList={props.onToggleBulletedList}
|
|
400
|
+
onToggleNumberedList={props.onToggleNumberedList}
|
|
401
|
+
onOutdent={props.onOutdent}
|
|
402
|
+
onIndent={props.onIndent}
|
|
403
|
+
onRestartNumbering={props.onRestartNumbering}
|
|
404
|
+
onContinueNumbering={props.onContinueNumbering}
|
|
405
|
+
onInsertPageBreak={props.onInsertPageBreak}
|
|
406
|
+
onInsertTable={props.onInsertTable}
|
|
407
|
+
onInsertSectionBreak={props.onInsertSectionBreak}
|
|
408
|
+
onInsertImage={props.onInsertImage}
|
|
409
|
+
onUpdateFields={props.onUpdateFields}
|
|
410
|
+
onUpdateTableOfContents={props.onUpdateTableOfContents}
|
|
411
|
+
/>
|
|
412
|
+
) : null}
|
|
413
|
+
|
|
414
|
+
{/* Story focus breadcrumb — visible when editing a secondary story */}
|
|
415
|
+
{props.activeStory && props.activeStory.kind !== "main" ? (
|
|
416
|
+
<>
|
|
417
|
+
<div className="mx-1 h-4 w-px bg-border" />
|
|
418
|
+
<button
|
|
419
|
+
type="button"
|
|
420
|
+
onClick={props.onCloseStory}
|
|
421
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
422
|
+
className={`inline-flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs font-medium text-accent hover:bg-surface transition-colors outline-none ${focusRingClass}`}
|
|
423
|
+
aria-label={`Editing ${storyLabel(props.activeStory)} — click to return to main body`}
|
|
424
|
+
>
|
|
425
|
+
<span className="text-secondary">←</span>
|
|
426
|
+
{storyLabel(props.activeStory)}
|
|
427
|
+
</button>
|
|
428
|
+
</>
|
|
429
|
+
) : null}
|
|
430
|
+
</div>
|
|
431
|
+
|
|
432
|
+
{/* Right cluster: comment, track changes, markup, view, export */}
|
|
433
|
+
<div className={`flex items-center gap-0.5 ${isCompact ? "ml-auto flex-wrap justify-end" : ""}`}>
|
|
434
|
+
{showSidebarToggle ? (
|
|
435
|
+
<>
|
|
436
|
+
<TwToolbarIconButton
|
|
437
|
+
icon={PanelRight}
|
|
438
|
+
label="Toggle sidebar"
|
|
439
|
+
active={props.isSidebarOpen ?? false}
|
|
440
|
+
onClick={props.onToggleSidebar}
|
|
441
|
+
/>
|
|
442
|
+
<div className="mx-1 h-4 w-px bg-border" />
|
|
443
|
+
</>
|
|
444
|
+
) : null}
|
|
445
|
+
|
|
446
|
+
<TwToolbarIconButton
|
|
447
|
+
icon={MessageSquare}
|
|
448
|
+
label="Add comment"
|
|
449
|
+
disabled={!canAddComment}
|
|
450
|
+
emphasis
|
|
451
|
+
onClick={props.onAddComment}
|
|
452
|
+
/>
|
|
453
|
+
|
|
454
|
+
{showTrackedChangesToggle ? (
|
|
455
|
+
<>
|
|
456
|
+
<Tooltip.Root>
|
|
457
|
+
<Tooltip.Trigger asChild>
|
|
458
|
+
<Toggle.Root
|
|
459
|
+
pressed={props.showTrackedChanges}
|
|
460
|
+
onPressedChange={props.onShowTrackedChangesChange}
|
|
461
|
+
disabled={caps ? !caps.trackChangesSupported : false}
|
|
462
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
463
|
+
className={`inline-flex h-7 w-7 items-center justify-center rounded-md text-secondary transition-colors hover:bg-surface data-[state=on]:bg-canvas data-[state=on]:text-accent data-[state=on]:ring-1 data-[state=on]:ring-accent/30 data-[state=on]:shadow-sm outline-none disabled:opacity-40 ${focusRingClass}`}
|
|
464
|
+
>
|
|
465
|
+
{props.showTrackedChanges ? <Eye className="h-4 w-4" /> : <EyeOff className="h-4 w-4" />}
|
|
466
|
+
</Toggle.Root>
|
|
467
|
+
</Tooltip.Trigger>
|
|
468
|
+
<Tooltip.Portal>
|
|
469
|
+
<Tooltip.Content
|
|
470
|
+
className="rounded-md bg-primary px-2 py-1 text-xs text-white shadow-md z-50"
|
|
471
|
+
sideOffset={6}
|
|
472
|
+
>
|
|
473
|
+
{props.showTrackedChanges ? "Hide tracked changes" : "Show tracked changes"}
|
|
474
|
+
</Tooltip.Content>
|
|
475
|
+
</Tooltip.Portal>
|
|
476
|
+
</Tooltip.Root>
|
|
477
|
+
|
|
478
|
+
<div className="mx-1 h-4 w-px bg-border" />
|
|
479
|
+
</>
|
|
480
|
+
) : null}
|
|
481
|
+
|
|
482
|
+
{/* View mode toggle group: Canvas (clean, flowing) / Page (layout-sensitive) */}
|
|
483
|
+
<ToggleGroup.Root
|
|
484
|
+
type="single"
|
|
485
|
+
value={workspaceMode}
|
|
486
|
+
onValueChange={(v: string) => {
|
|
487
|
+
if (v) props.onWorkspaceModeChange(v as WorkspaceMode);
|
|
488
|
+
}}
|
|
489
|
+
className="flex items-center gap-0.5"
|
|
490
|
+
>
|
|
491
|
+
<Tooltip.Root>
|
|
492
|
+
<Tooltip.Trigger asChild>
|
|
493
|
+
<ToggleGroup.Item
|
|
494
|
+
value="canvas"
|
|
495
|
+
aria-label="Canvas workspace"
|
|
496
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
497
|
+
className={`inline-flex h-7 w-7 items-center justify-center rounded-md text-secondary transition-colors hover:bg-surface data-[state=on]:bg-canvas data-[state=on]:text-accent data-[state=on]:ring-1 data-[state=on]:ring-accent/30 data-[state=on]:shadow-sm outline-none ${focusRingClass}`}
|
|
498
|
+
>
|
|
499
|
+
<Monitor className="h-3.5 w-3.5" />
|
|
500
|
+
</ToggleGroup.Item>
|
|
501
|
+
</Tooltip.Trigger>
|
|
502
|
+
<Tooltip.Portal>
|
|
503
|
+
<Tooltip.Content className="rounded-md bg-primary px-2 py-1 text-xs text-white shadow-md z-50" sideOffset={6}>
|
|
504
|
+
Canvas — clean flowing text
|
|
505
|
+
</Tooltip.Content>
|
|
506
|
+
</Tooltip.Portal>
|
|
507
|
+
</Tooltip.Root>
|
|
508
|
+
<Tooltip.Root>
|
|
509
|
+
<Tooltip.Trigger asChild>
|
|
510
|
+
<ToggleGroup.Item
|
|
511
|
+
value="page"
|
|
512
|
+
aria-label="Page workspace"
|
|
513
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
514
|
+
className={`inline-flex h-7 w-7 items-center justify-center rounded-md text-secondary transition-colors hover:bg-surface data-[state=on]:bg-canvas data-[state=on]:text-accent data-[state=on]:ring-1 data-[state=on]:ring-accent/30 data-[state=on]:shadow-sm outline-none ${focusRingClass}`}
|
|
515
|
+
>
|
|
516
|
+
<FileText className="h-3.5 w-3.5" />
|
|
517
|
+
</ToggleGroup.Item>
|
|
518
|
+
</Tooltip.Trigger>
|
|
519
|
+
<Tooltip.Portal>
|
|
520
|
+
<Tooltip.Content className="rounded-md bg-primary px-2 py-1 text-xs text-white shadow-md z-50" sideOffset={6}>
|
|
521
|
+
Page — layout-sensitive view
|
|
522
|
+
</Tooltip.Content>
|
|
523
|
+
</Tooltip.Portal>
|
|
524
|
+
</Tooltip.Root>
|
|
525
|
+
</ToggleGroup.Root>
|
|
526
|
+
|
|
527
|
+
{/* Zoom controls — available in all workspace modes */}
|
|
528
|
+
{props.onZoomChange ? (
|
|
529
|
+
<>
|
|
530
|
+
<div className="mx-1 h-4 w-px bg-border" />
|
|
531
|
+
<div className="flex items-center gap-0.5">
|
|
532
|
+
<Tooltip.Root>
|
|
533
|
+
<Tooltip.Trigger asChild>
|
|
534
|
+
<button
|
|
535
|
+
type="button"
|
|
536
|
+
aria-label="Zoom out"
|
|
537
|
+
className={`inline-flex h-7 w-7 items-center justify-center rounded-md text-secondary transition-colors hover:bg-surface outline-none ${focusRingClass}`}
|
|
538
|
+
disabled={typeof zoomLevel === "number" && zoomLevel <= 50}
|
|
539
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
540
|
+
onClick={() => {
|
|
541
|
+
const current = typeof zoomLevel === "number" ? zoomLevel : 100;
|
|
542
|
+
props.onZoomChange!(Math.max(50, current - 10));
|
|
543
|
+
}}
|
|
544
|
+
>
|
|
545
|
+
<Minus className="h-3 w-3" />
|
|
546
|
+
</button>
|
|
547
|
+
</Tooltip.Trigger>
|
|
548
|
+
<Tooltip.Portal>
|
|
549
|
+
<Tooltip.Content className="rounded-md bg-primary px-2 py-1 text-xs text-white shadow-md z-50" sideOffset={6}>
|
|
550
|
+
Zoom out
|
|
551
|
+
</Tooltip.Content>
|
|
552
|
+
</Tooltip.Portal>
|
|
553
|
+
</Tooltip.Root>
|
|
554
|
+
|
|
555
|
+
<Popover.Root>
|
|
556
|
+
<Tooltip.Root>
|
|
557
|
+
<Tooltip.Trigger asChild>
|
|
558
|
+
<Popover.Trigger asChild>
|
|
559
|
+
<button
|
|
560
|
+
type="button"
|
|
561
|
+
aria-label={`Zoom: ${zoomLabel}`}
|
|
562
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
563
|
+
className={`inline-flex h-7 items-center justify-center rounded-md px-1.5 text-[11px] font-medium text-secondary transition-colors hover:bg-surface outline-none ${focusRingClass}`}
|
|
564
|
+
>
|
|
565
|
+
{zoomLabel}
|
|
566
|
+
</button>
|
|
567
|
+
</Popover.Trigger>
|
|
568
|
+
</Tooltip.Trigger>
|
|
569
|
+
<Tooltip.Portal>
|
|
570
|
+
<Tooltip.Content className="rounded-md bg-primary px-2 py-1 text-xs text-white shadow-md z-50" sideOffset={6}>
|
|
571
|
+
Zoom level
|
|
572
|
+
</Tooltip.Content>
|
|
573
|
+
</Tooltip.Portal>
|
|
574
|
+
</Tooltip.Root>
|
|
575
|
+
<Popover.Portal>
|
|
576
|
+
<Popover.Content
|
|
577
|
+
className="w-[140px] rounded-lg bg-canvas shadow-lg ring-1 ring-border p-1 z-50"
|
|
578
|
+
sideOffset={8}
|
|
579
|
+
align="center"
|
|
580
|
+
>
|
|
581
|
+
{getSupportedZoomPresets().map((preset) => {
|
|
582
|
+
const label = `${preset}%`;
|
|
583
|
+
return (
|
|
584
|
+
<Popover.Close key={preset} asChild>
|
|
585
|
+
<button
|
|
586
|
+
type="button"
|
|
587
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
588
|
+
className={`w-full rounded-md px-3 py-1.5 text-left text-xs transition-colors hover:bg-surface ${
|
|
589
|
+
zoomLevel === preset ? "font-semibold text-accent" : "text-primary"
|
|
590
|
+
}`}
|
|
591
|
+
onClick={() => props.onZoomChange!(preset)}
|
|
592
|
+
>
|
|
593
|
+
{label}
|
|
594
|
+
</button>
|
|
595
|
+
</Popover.Close>
|
|
596
|
+
);
|
|
597
|
+
})}
|
|
598
|
+
</Popover.Content>
|
|
599
|
+
</Popover.Portal>
|
|
600
|
+
</Popover.Root>
|
|
601
|
+
|
|
602
|
+
<Tooltip.Root>
|
|
603
|
+
<Tooltip.Trigger asChild>
|
|
604
|
+
<button
|
|
605
|
+
type="button"
|
|
606
|
+
aria-label="Zoom in"
|
|
607
|
+
className={`inline-flex h-7 w-7 items-center justify-center rounded-md text-secondary transition-colors hover:bg-surface outline-none ${focusRingClass}`}
|
|
608
|
+
disabled={typeof zoomLevel === "number" && zoomLevel >= 200}
|
|
609
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
610
|
+
onClick={() => {
|
|
611
|
+
const current = typeof zoomLevel === "number" ? zoomLevel : 100;
|
|
612
|
+
props.onZoomChange!(Math.min(200, current + 10));
|
|
613
|
+
}}
|
|
614
|
+
>
|
|
615
|
+
<Plus className="h-3 w-3" />
|
|
616
|
+
</button>
|
|
617
|
+
</Tooltip.Trigger>
|
|
618
|
+
<Tooltip.Portal>
|
|
619
|
+
<Tooltip.Content className="rounded-md bg-primary px-2 py-1 text-xs text-white shadow-md z-50" sideOffset={6}>
|
|
620
|
+
Zoom in
|
|
621
|
+
</Tooltip.Content>
|
|
622
|
+
</Tooltip.Portal>
|
|
623
|
+
</Tooltip.Root>
|
|
624
|
+
</div>
|
|
625
|
+
</>
|
|
626
|
+
) : null}
|
|
627
|
+
|
|
628
|
+
{showHealth ? (
|
|
629
|
+
<>
|
|
630
|
+
<Popover.Root>
|
|
631
|
+
<Tooltip.Root>
|
|
632
|
+
<Tooltip.Trigger asChild>
|
|
633
|
+
<Popover.Trigger asChild>
|
|
634
|
+
<button
|
|
635
|
+
type="button"
|
|
636
|
+
aria-label="Document health"
|
|
637
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
638
|
+
className={`relative inline-flex h-7 w-7 items-center justify-center rounded-md text-secondary transition-colors hover:bg-surface hover:text-primary outline-none ${focusRingClass}`}
|
|
639
|
+
>
|
|
640
|
+
<AlertCircle className="h-4 w-4" />
|
|
641
|
+
{(caps?.healthIssueCount ?? 0) > 0 ? (
|
|
642
|
+
<span className="absolute -top-0.5 -right-0.5 flex h-3 min-w-[12px] items-center justify-center rounded-full bg-tertiary text-[8px] font-medium text-white">
|
|
643
|
+
{caps?.healthIssueCount}
|
|
644
|
+
</span>
|
|
645
|
+
) : null}
|
|
646
|
+
</button>
|
|
647
|
+
</Popover.Trigger>
|
|
648
|
+
</Tooltip.Trigger>
|
|
649
|
+
<Tooltip.Portal>
|
|
650
|
+
<Tooltip.Content className="rounded-md bg-primary px-2 py-1 text-xs text-white shadow-md z-50" sideOffset={6}>
|
|
651
|
+
{(caps?.healthIssueCount ?? 0) > 0
|
|
652
|
+
? `Document health — ${caps?.healthIssueCount} issue${(caps?.healthIssueCount ?? 0) !== 1 ? "s" : ""}`
|
|
653
|
+
: "Document health — no issues"}
|
|
654
|
+
</Tooltip.Content>
|
|
655
|
+
</Tooltip.Portal>
|
|
656
|
+
</Tooltip.Root>
|
|
657
|
+
<Popover.Portal>
|
|
658
|
+
<Popover.Content
|
|
659
|
+
className="w-[360px] max-h-[480px] overflow-y-auto rounded-lg bg-canvas shadow-lg ring-1 ring-border p-3 z-50"
|
|
660
|
+
sideOffset={8}
|
|
661
|
+
align="end"
|
|
662
|
+
>
|
|
663
|
+
<TwHealthPanel
|
|
664
|
+
blockedReasons={props.blockedReasons}
|
|
665
|
+
compatibility={props.compatibility!}
|
|
666
|
+
warnings={props.warnings!}
|
|
667
|
+
/>
|
|
668
|
+
</Popover.Content>
|
|
669
|
+
</Popover.Portal>
|
|
670
|
+
</Popover.Root>
|
|
671
|
+
<div className="mx-1 h-4 w-px bg-border" />
|
|
672
|
+
</>
|
|
673
|
+
) : null}
|
|
674
|
+
|
|
675
|
+
<TwToolbarIconButton
|
|
676
|
+
icon={Download}
|
|
677
|
+
label={caps?.exportBlocked ? "Export blocked" : "Download document"}
|
|
678
|
+
disabled={caps ? !caps.canExport : true}
|
|
679
|
+
emphasis
|
|
680
|
+
onClick={props.onExport}
|
|
681
|
+
/>
|
|
682
|
+
</div>
|
|
683
|
+
</header>
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function ToolbarParagraphStyleSelect(props: {
|
|
688
|
+
styles: StyleCatalogSnapshot["paragraphs"];
|
|
689
|
+
value?: string;
|
|
690
|
+
disabled: boolean;
|
|
691
|
+
onValueChange?: (styleId: string) => void;
|
|
692
|
+
}) {
|
|
693
|
+
const resolvedValue =
|
|
694
|
+
props.value && props.styles.some((style) => style.styleId === props.value)
|
|
695
|
+
? props.value
|
|
696
|
+
: "";
|
|
697
|
+
|
|
698
|
+
return (
|
|
699
|
+
<Select.Root
|
|
700
|
+
disabled={props.disabled}
|
|
701
|
+
onValueChange={(value) => props.onValueChange?.(value)}
|
|
702
|
+
value={resolvedValue}
|
|
703
|
+
>
|
|
704
|
+
<Select.Trigger
|
|
705
|
+
aria-label="Paragraph style"
|
|
706
|
+
aria-disabled={props.disabled || undefined}
|
|
707
|
+
data-disabled={props.disabled ? "" : undefined}
|
|
708
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
709
|
+
className={`inline-flex h-7 min-w-[8.5rem] items-center justify-between gap-2 rounded-md border border-border bg-canvas px-2.5 text-xs font-medium text-primary transition-colors hover:bg-surface outline-none disabled:cursor-not-allowed disabled:opacity-40 ${focusRingClass}`}
|
|
710
|
+
>
|
|
711
|
+
<Select.Value placeholder="Style" />
|
|
712
|
+
<Select.Icon>
|
|
713
|
+
<ChevronDown className="h-3.5 w-3.5 text-tertiary" />
|
|
714
|
+
</Select.Icon>
|
|
715
|
+
</Select.Trigger>
|
|
716
|
+
<Select.Portal>
|
|
717
|
+
<Select.Content
|
|
718
|
+
align="start"
|
|
719
|
+
className="z-50 overflow-hidden rounded-lg bg-canvas shadow-lg ring-1 ring-border"
|
|
720
|
+
position="popper"
|
|
721
|
+
sideOffset={8}
|
|
722
|
+
>
|
|
723
|
+
<Select.Viewport className="p-1">
|
|
724
|
+
{props.styles.map((style) => (
|
|
725
|
+
<Select.Item
|
|
726
|
+
className={`flex cursor-pointer items-center rounded-md px-2.5 py-1.5 text-xs text-primary outline-none data-[highlighted]:bg-surface data-[state=checked]:bg-canvas data-[state=checked]:text-accent data-[state=checked]:ring-1 data-[state=checked]:ring-accent/20 ${focusRingClass}`}
|
|
727
|
+
key={style.styleId}
|
|
728
|
+
value={style.styleId}
|
|
729
|
+
>
|
|
730
|
+
<Select.ItemText>{style.displayName}</Select.ItemText>
|
|
731
|
+
</Select.Item>
|
|
732
|
+
))}
|
|
733
|
+
</Select.Viewport>
|
|
734
|
+
</Select.Content>
|
|
735
|
+
</Select.Portal>
|
|
736
|
+
</Select.Root>
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
function ToolbarFontFamilySelect(props: {
|
|
741
|
+
value?: string;
|
|
742
|
+
disabled: boolean;
|
|
743
|
+
onValueChange?: (fontFamily: string) => void;
|
|
744
|
+
}) {
|
|
745
|
+
const resolvedValue = props.value && FONT_FAMILIES.includes(props.value) ? props.value : "";
|
|
746
|
+
|
|
747
|
+
return (
|
|
748
|
+
<Select.Root
|
|
749
|
+
disabled={props.disabled}
|
|
750
|
+
onValueChange={(value) => props.onValueChange?.(value)}
|
|
751
|
+
value={resolvedValue}
|
|
752
|
+
>
|
|
753
|
+
<Select.Trigger
|
|
754
|
+
aria-label="Font family"
|
|
755
|
+
aria-disabled={props.disabled || undefined}
|
|
756
|
+
data-disabled={props.disabled ? "" : undefined}
|
|
757
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
758
|
+
className={`inline-flex h-7 min-w-[7rem] items-center justify-between gap-2 rounded-md border border-border bg-canvas px-2 text-xs font-medium text-primary transition-colors hover:bg-surface outline-none disabled:cursor-not-allowed disabled:opacity-40 ${focusRingClass}`}
|
|
759
|
+
>
|
|
760
|
+
<Select.Value placeholder="Font" />
|
|
761
|
+
<Select.Icon>
|
|
762
|
+
<ChevronDown className="h-3.5 w-3.5 text-tertiary" />
|
|
763
|
+
</Select.Icon>
|
|
764
|
+
</Select.Trigger>
|
|
765
|
+
<Select.Portal>
|
|
766
|
+
<Select.Content
|
|
767
|
+
align="start"
|
|
768
|
+
className="z-50 overflow-hidden rounded-lg bg-canvas shadow-lg ring-1 ring-border"
|
|
769
|
+
position="popper"
|
|
770
|
+
sideOffset={8}
|
|
771
|
+
>
|
|
772
|
+
<Select.Viewport className="p-1">
|
|
773
|
+
{FONT_FAMILIES.map((font) => (
|
|
774
|
+
<Select.Item
|
|
775
|
+
className={`flex cursor-pointer items-center rounded-md px-2.5 py-1.5 text-xs text-primary outline-none data-[highlighted]:bg-surface data-[state=checked]:bg-canvas data-[state=checked]:text-accent data-[state=checked]:ring-1 data-[state=checked]:ring-accent/20 ${focusRingClass}`}
|
|
776
|
+
key={font}
|
|
777
|
+
value={font}
|
|
778
|
+
>
|
|
779
|
+
<Select.ItemText>{font}</Select.ItemText>
|
|
780
|
+
</Select.Item>
|
|
781
|
+
))}
|
|
782
|
+
</Select.Viewport>
|
|
783
|
+
</Select.Content>
|
|
784
|
+
</Select.Portal>
|
|
785
|
+
</Select.Root>
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
function ToolbarFontSizeSelect(props: {
|
|
790
|
+
value?: number;
|
|
791
|
+
disabled: boolean;
|
|
792
|
+
onValueChange?: (fontSize: number) => void;
|
|
793
|
+
}) {
|
|
794
|
+
const resolvedValue =
|
|
795
|
+
typeof props.value === "number" && FONT_SIZES.includes(props.value) ? String(props.value) : "";
|
|
796
|
+
|
|
797
|
+
return (
|
|
798
|
+
<Select.Root
|
|
799
|
+
disabled={props.disabled}
|
|
800
|
+
onValueChange={(value) => props.onValueChange?.(Number(value))}
|
|
801
|
+
value={resolvedValue}
|
|
802
|
+
>
|
|
803
|
+
<Select.Trigger
|
|
804
|
+
aria-label="Font size"
|
|
805
|
+
aria-disabled={props.disabled || undefined}
|
|
806
|
+
data-disabled={props.disabled ? "" : undefined}
|
|
807
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
808
|
+
className={`inline-flex h-7 min-w-[4rem] items-center justify-between gap-2 rounded-md border border-border bg-canvas px-2 text-xs font-medium text-primary transition-colors hover:bg-surface outline-none disabled:cursor-not-allowed disabled:opacity-40 ${focusRingClass}`}
|
|
809
|
+
>
|
|
810
|
+
<Select.Value placeholder="Size" />
|
|
811
|
+
<Select.Icon>
|
|
812
|
+
<ChevronDown className="h-3.5 w-3.5 text-tertiary" />
|
|
813
|
+
</Select.Icon>
|
|
814
|
+
</Select.Trigger>
|
|
815
|
+
<Select.Portal>
|
|
816
|
+
<Select.Content
|
|
817
|
+
align="start"
|
|
818
|
+
className="z-50 overflow-hidden rounded-lg bg-canvas shadow-lg ring-1 ring-border"
|
|
819
|
+
position="popper"
|
|
820
|
+
sideOffset={8}
|
|
821
|
+
>
|
|
822
|
+
<Select.Viewport className="p-1">
|
|
823
|
+
{FONT_SIZES.map((size) => (
|
|
824
|
+
<Select.Item
|
|
825
|
+
className={`flex cursor-pointer items-center rounded-md px-2.5 py-1.5 text-xs text-primary outline-none data-[highlighted]:bg-surface data-[state=checked]:bg-canvas data-[state=checked]:text-accent data-[state=checked]:ring-1 data-[state=checked]:ring-accent/20 ${focusRingClass}`}
|
|
826
|
+
key={size}
|
|
827
|
+
value={String(size)}
|
|
828
|
+
>
|
|
829
|
+
<Select.ItemText>{size}</Select.ItemText>
|
|
830
|
+
</Select.Item>
|
|
831
|
+
))}
|
|
832
|
+
</Select.Viewport>
|
|
833
|
+
</Select.Content>
|
|
834
|
+
</Select.Portal>
|
|
835
|
+
</Select.Root>
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
function ToolbarCompactOverflow(props: {
|
|
840
|
+
activeListContext?: ActiveListContext | null;
|
|
841
|
+
canEdit: boolean;
|
|
842
|
+
canInsertStructural: boolean;
|
|
843
|
+
formattingState?: FormattingStateSnapshot;
|
|
844
|
+
paragraphStyles: StyleCatalogSnapshot["paragraphs"];
|
|
845
|
+
showInsertMenu: boolean;
|
|
846
|
+
showListActions: boolean;
|
|
847
|
+
showStyleSelectors: boolean;
|
|
848
|
+
showUpdateActions: boolean;
|
|
849
|
+
onSetParagraphStyle?: (styleId: string) => void;
|
|
850
|
+
onSetFontFamily?: (fontFamily: string) => void;
|
|
851
|
+
onSetFontSize?: (fontSize: number) => void;
|
|
852
|
+
onToggleBulletedList?: () => void;
|
|
853
|
+
onToggleNumberedList?: () => void;
|
|
854
|
+
onOutdent?: () => void;
|
|
855
|
+
onIndent?: () => void;
|
|
856
|
+
onRestartNumbering?: () => void;
|
|
857
|
+
onContinueNumbering?: () => void;
|
|
858
|
+
onInsertPageBreak?: () => void;
|
|
859
|
+
onInsertTable?: () => void;
|
|
860
|
+
onInsertSectionBreak?: (type: SectionBreakType) => void;
|
|
861
|
+
onInsertImage?: (options: InsertImageOptions) => void;
|
|
862
|
+
onUpdateFields?: () => void;
|
|
863
|
+
onUpdateTableOfContents?: () => void;
|
|
864
|
+
}) {
|
|
865
|
+
const [open, setOpen] = React.useState(false);
|
|
866
|
+
|
|
867
|
+
async function handleImageChange(event: React.ChangeEvent<HTMLInputElement>): Promise<void> {
|
|
868
|
+
const file = event.target.files?.[0];
|
|
869
|
+
if (!file || !props.canInsertStructural || !props.onInsertImage) {
|
|
870
|
+
event.target.value = "";
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
const data = new Uint8Array(await file.arrayBuffer());
|
|
875
|
+
props.onInsertImage({
|
|
876
|
+
data,
|
|
877
|
+
mimeType: file.type || "image/png",
|
|
878
|
+
altText: file.name,
|
|
879
|
+
});
|
|
880
|
+
setOpen(false);
|
|
881
|
+
event.target.value = "";
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
return (
|
|
885
|
+
<div className="relative">
|
|
886
|
+
<Tooltip.Root>
|
|
887
|
+
<Tooltip.Trigger asChild>
|
|
888
|
+
<button
|
|
889
|
+
type="button"
|
|
890
|
+
aria-label="More document tools"
|
|
891
|
+
aria-expanded={open}
|
|
892
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
893
|
+
onClick={() => setOpen((value) => !value)}
|
|
894
|
+
className={`inline-flex h-7 items-center gap-1 rounded-md border border-border bg-canvas px-2 text-xs font-medium text-primary transition-colors hover:bg-surface outline-none ${focusRingClass}`}
|
|
895
|
+
>
|
|
896
|
+
More
|
|
897
|
+
<ChevronDown className="h-3.5 w-3.5 text-tertiary" />
|
|
898
|
+
</button>
|
|
899
|
+
</Tooltip.Trigger>
|
|
900
|
+
<Tooltip.Portal>
|
|
901
|
+
<Tooltip.Content className="rounded-md bg-primary px-2 py-1 text-xs text-white shadow-md z-50" sideOffset={6}>
|
|
902
|
+
More document tools
|
|
903
|
+
</Tooltip.Content>
|
|
904
|
+
</Tooltip.Portal>
|
|
905
|
+
</Tooltip.Root>
|
|
906
|
+
{open ? (
|
|
907
|
+
<div className="absolute left-0 top-9 z-50 w-[min(20rem,calc(100vw-2rem))] rounded-lg bg-canvas p-2 shadow-lg ring-1 ring-border">
|
|
908
|
+
<div className="space-y-3">
|
|
909
|
+
{props.showStyleSelectors ? (
|
|
910
|
+
<div className="space-y-2">
|
|
911
|
+
<div className="px-1 text-[10px] font-semibold uppercase tracking-[0.12em] text-tertiary">
|
|
912
|
+
Text
|
|
913
|
+
</div>
|
|
914
|
+
<div className="grid gap-2">
|
|
915
|
+
<ToolbarParagraphStyleSelect
|
|
916
|
+
disabled={!props.canEdit || props.paragraphStyles.length === 0 || !props.onSetParagraphStyle}
|
|
917
|
+
styles={props.paragraphStyles}
|
|
918
|
+
value={props.formattingState?.paragraphStyleId}
|
|
919
|
+
onValueChange={(styleId) => {
|
|
920
|
+
props.onSetParagraphStyle?.(styleId);
|
|
921
|
+
setOpen(false);
|
|
922
|
+
}}
|
|
923
|
+
/>
|
|
924
|
+
<div className="grid grid-cols-2 gap-2">
|
|
925
|
+
<ToolbarFontFamilySelect
|
|
926
|
+
disabled={!props.canEdit || !props.onSetFontFamily}
|
|
927
|
+
value={props.formattingState?.fontFamily}
|
|
928
|
+
onValueChange={(fontFamily) => {
|
|
929
|
+
props.onSetFontFamily?.(fontFamily);
|
|
930
|
+
setOpen(false);
|
|
931
|
+
}}
|
|
932
|
+
/>
|
|
933
|
+
<ToolbarFontSizeSelect
|
|
934
|
+
disabled={!props.canEdit || !props.onSetFontSize}
|
|
935
|
+
value={props.formattingState?.fontSize}
|
|
936
|
+
onValueChange={(fontSize) => {
|
|
937
|
+
props.onSetFontSize?.(fontSize);
|
|
938
|
+
setOpen(false);
|
|
939
|
+
}}
|
|
940
|
+
/>
|
|
941
|
+
</div>
|
|
942
|
+
</div>
|
|
943
|
+
</div>
|
|
944
|
+
) : null}
|
|
945
|
+
|
|
946
|
+
{props.showListActions ? (
|
|
947
|
+
<div className="space-y-1">
|
|
948
|
+
<div className="px-1 text-[10px] font-semibold uppercase tracking-[0.12em] text-tertiary">
|
|
949
|
+
Structure
|
|
950
|
+
</div>
|
|
951
|
+
<ToolbarMenuButton
|
|
952
|
+
ariaLabel="Bulleted list"
|
|
953
|
+
disabled={!props.canEdit || !props.onToggleBulletedList}
|
|
954
|
+
icon={<List className="h-3.5 w-3.5" />}
|
|
955
|
+
label="Bulleted list"
|
|
956
|
+
onClick={() => {
|
|
957
|
+
props.onToggleBulletedList?.();
|
|
958
|
+
setOpen(false);
|
|
959
|
+
}}
|
|
960
|
+
/>
|
|
961
|
+
<ToolbarMenuButton
|
|
962
|
+
ariaLabel="Numbered list"
|
|
963
|
+
disabled={!props.canEdit || !props.onToggleNumberedList}
|
|
964
|
+
icon={<Rows3 className="h-3.5 w-3.5" />}
|
|
965
|
+
label="Numbered list"
|
|
966
|
+
onClick={() => {
|
|
967
|
+
props.onToggleNumberedList?.();
|
|
968
|
+
setOpen(false);
|
|
969
|
+
}}
|
|
970
|
+
/>
|
|
971
|
+
</div>
|
|
972
|
+
) : null}
|
|
973
|
+
|
|
974
|
+
<div className="space-y-1">
|
|
975
|
+
<div className="px-1 text-[10px] font-semibold uppercase tracking-[0.12em] text-tertiary">
|
|
976
|
+
Paragraph
|
|
977
|
+
</div>
|
|
978
|
+
<ToolbarMenuButton
|
|
979
|
+
ariaLabel="Outdent"
|
|
980
|
+
disabled={!props.canEdit || !props.onOutdent}
|
|
981
|
+
icon={<Outdent className="h-3.5 w-3.5" />}
|
|
982
|
+
label="Outdent"
|
|
983
|
+
onClick={() => {
|
|
984
|
+
props.onOutdent?.();
|
|
985
|
+
setOpen(false);
|
|
986
|
+
}}
|
|
987
|
+
/>
|
|
988
|
+
<ToolbarMenuButton
|
|
989
|
+
ariaLabel="Indent"
|
|
990
|
+
disabled={!props.canEdit || !props.onIndent}
|
|
991
|
+
icon={<Indent className="h-3.5 w-3.5" />}
|
|
992
|
+
label="Indent"
|
|
993
|
+
onClick={() => {
|
|
994
|
+
props.onIndent?.();
|
|
995
|
+
setOpen(false);
|
|
996
|
+
}}
|
|
997
|
+
/>
|
|
998
|
+
{props.activeListContext ? (
|
|
999
|
+
<>
|
|
1000
|
+
<ToolbarMenuButton
|
|
1001
|
+
ariaLabel="Restart numbering"
|
|
1002
|
+
disabled={!props.canEdit || !props.onRestartNumbering}
|
|
1003
|
+
icon={<Rows3 className="h-3.5 w-3.5" />}
|
|
1004
|
+
label="Restart numbering"
|
|
1005
|
+
onClick={() => {
|
|
1006
|
+
props.onRestartNumbering?.();
|
|
1007
|
+
setOpen(false);
|
|
1008
|
+
}}
|
|
1009
|
+
/>
|
|
1010
|
+
<ToolbarMenuButton
|
|
1011
|
+
ariaLabel="Continue numbering"
|
|
1012
|
+
disabled={!props.canEdit || !props.onContinueNumbering}
|
|
1013
|
+
icon={<Rows3 className="h-3.5 w-3.5" />}
|
|
1014
|
+
label="Continue numbering"
|
|
1015
|
+
onClick={() => {
|
|
1016
|
+
props.onContinueNumbering?.();
|
|
1017
|
+
setOpen(false);
|
|
1018
|
+
}}
|
|
1019
|
+
/>
|
|
1020
|
+
</>
|
|
1021
|
+
) : null}
|
|
1022
|
+
</div>
|
|
1023
|
+
|
|
1024
|
+
{props.showInsertMenu ? (
|
|
1025
|
+
<div className="space-y-1">
|
|
1026
|
+
<div className="px-1 text-[10px] font-semibold uppercase tracking-[0.12em] text-tertiary">
|
|
1027
|
+
Insert
|
|
1028
|
+
</div>
|
|
1029
|
+
<ToolbarMenuButton
|
|
1030
|
+
ariaLabel="Insert page break"
|
|
1031
|
+
disabled={!props.canInsertStructural || !props.onInsertPageBreak}
|
|
1032
|
+
icon={<Minus className="h-3.5 w-3.5" />}
|
|
1033
|
+
label="Page break"
|
|
1034
|
+
onClick={() => {
|
|
1035
|
+
props.onInsertPageBreak?.();
|
|
1036
|
+
setOpen(false);
|
|
1037
|
+
}}
|
|
1038
|
+
/>
|
|
1039
|
+
<ToolbarMenuButton
|
|
1040
|
+
ariaLabel="Insert table"
|
|
1041
|
+
disabled={!props.canInsertStructural || !props.onInsertTable}
|
|
1042
|
+
icon={<Rows3 className="h-3.5 w-3.5" />}
|
|
1043
|
+
label="Table"
|
|
1044
|
+
onClick={() => {
|
|
1045
|
+
props.onInsertTable?.();
|
|
1046
|
+
setOpen(false);
|
|
1047
|
+
}}
|
|
1048
|
+
/>
|
|
1049
|
+
<label
|
|
1050
|
+
className={`flex h-8 cursor-pointer items-center gap-2 rounded-md px-2 text-left text-xs font-medium text-primary transition-colors hover:bg-surface ${
|
|
1051
|
+
!props.canInsertStructural || !props.onInsertImage ? "pointer-events-none opacity-40" : ""
|
|
1052
|
+
}`}
|
|
1053
|
+
>
|
|
1054
|
+
<ImagePlus className="h-3.5 w-3.5 text-secondary" />
|
|
1055
|
+
<span>Image</span>
|
|
1056
|
+
<input
|
|
1057
|
+
accept="image/png,image/jpeg,image/gif"
|
|
1058
|
+
aria-label="Insert image"
|
|
1059
|
+
className="sr-only"
|
|
1060
|
+
disabled={!props.canInsertStructural || !props.onInsertImage}
|
|
1061
|
+
type="file"
|
|
1062
|
+
onChange={(event) => {
|
|
1063
|
+
void handleImageChange(event);
|
|
1064
|
+
}}
|
|
1065
|
+
/>
|
|
1066
|
+
</label>
|
|
1067
|
+
<ToolbarMenuButton
|
|
1068
|
+
ariaLabel="Insert next-page section break"
|
|
1069
|
+
disabled={!props.canInsertStructural || !props.onInsertSectionBreak}
|
|
1070
|
+
icon={<FileText className="h-3.5 w-3.5" />}
|
|
1071
|
+
label="Next-page section break"
|
|
1072
|
+
onClick={() => {
|
|
1073
|
+
props.onInsertSectionBreak?.("nextPage");
|
|
1074
|
+
setOpen(false);
|
|
1075
|
+
}}
|
|
1076
|
+
/>
|
|
1077
|
+
</div>
|
|
1078
|
+
) : null}
|
|
1079
|
+
|
|
1080
|
+
{props.showUpdateActions ? (
|
|
1081
|
+
<div className="space-y-1">
|
|
1082
|
+
<div className="px-1 text-[10px] font-semibold uppercase tracking-[0.12em] text-tertiary">
|
|
1083
|
+
Refresh
|
|
1084
|
+
</div>
|
|
1085
|
+
<ToolbarMenuButton
|
|
1086
|
+
ariaLabel="Refresh fields"
|
|
1087
|
+
disabled={!props.canEdit || !props.onUpdateFields}
|
|
1088
|
+
icon={<RotateCcw className="h-3.5 w-3.5" />}
|
|
1089
|
+
label="Fields"
|
|
1090
|
+
onClick={() => {
|
|
1091
|
+
props.onUpdateFields?.();
|
|
1092
|
+
setOpen(false);
|
|
1093
|
+
}}
|
|
1094
|
+
/>
|
|
1095
|
+
<ToolbarMenuButton
|
|
1096
|
+
ariaLabel="Refresh table of contents"
|
|
1097
|
+
disabled={!props.canEdit || !props.onUpdateTableOfContents}
|
|
1098
|
+
icon={<RotateCcw className="h-3.5 w-3.5" />}
|
|
1099
|
+
label="Table of contents"
|
|
1100
|
+
onClick={() => {
|
|
1101
|
+
props.onUpdateTableOfContents?.();
|
|
1102
|
+
setOpen(false);
|
|
1103
|
+
}}
|
|
1104
|
+
/>
|
|
1105
|
+
</div>
|
|
1106
|
+
) : null}
|
|
1107
|
+
</div>
|
|
1108
|
+
</div>
|
|
1109
|
+
) : null}
|
|
1110
|
+
</div>
|
|
1111
|
+
);
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
function ToolbarFormattingOverflow(props: {
|
|
1115
|
+
disabled: boolean;
|
|
1116
|
+
formattingState?: FormattingStateSnapshot;
|
|
1117
|
+
onToggleStrikethrough?: () => void;
|
|
1118
|
+
onToggleSuperscript?: () => void;
|
|
1119
|
+
onToggleSubscript?: () => void;
|
|
1120
|
+
}) {
|
|
1121
|
+
const [open, setOpen] = React.useState(false);
|
|
1122
|
+
|
|
1123
|
+
return (
|
|
1124
|
+
<div className="relative">
|
|
1125
|
+
<Tooltip.Root>
|
|
1126
|
+
<Tooltip.Trigger asChild>
|
|
1127
|
+
<button
|
|
1128
|
+
type="button"
|
|
1129
|
+
aria-label="More text formatting"
|
|
1130
|
+
aria-expanded={open}
|
|
1131
|
+
disabled={props.disabled}
|
|
1132
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
1133
|
+
onClick={() => setOpen((value) => !value)}
|
|
1134
|
+
className={`inline-flex h-7 w-7 items-center justify-center rounded-md text-secondary transition-colors hover:bg-surface outline-none disabled:cursor-not-allowed disabled:opacity-40 ${focusRingClass}`}
|
|
1135
|
+
>
|
|
1136
|
+
<MoreHorizontal className="h-3.5 w-3.5" />
|
|
1137
|
+
</button>
|
|
1138
|
+
</Tooltip.Trigger>
|
|
1139
|
+
<Tooltip.Portal>
|
|
1140
|
+
<Tooltip.Content className="rounded-md bg-primary px-2 py-1 text-xs text-white shadow-md z-50" sideOffset={6}>
|
|
1141
|
+
More text formatting
|
|
1142
|
+
</Tooltip.Content>
|
|
1143
|
+
</Tooltip.Portal>
|
|
1144
|
+
</Tooltip.Root>
|
|
1145
|
+
{open ? (
|
|
1146
|
+
<div className="absolute left-0 top-9 z-50 w-[220px] rounded-lg bg-canvas p-2 shadow-lg ring-1 ring-border">
|
|
1147
|
+
<div className="mb-1 px-1 text-[10px] font-semibold uppercase tracking-[0.12em] text-tertiary">
|
|
1148
|
+
Text styling
|
|
1149
|
+
</div>
|
|
1150
|
+
<div className="grid grid-cols-3 gap-1">
|
|
1151
|
+
<ToolbarPopoverActionButton
|
|
1152
|
+
active={props.formattingState?.strikethrough ?? false}
|
|
1153
|
+
ariaLabel="Strikethrough"
|
|
1154
|
+
disabled={props.disabled}
|
|
1155
|
+
icon={<Strikethrough className="h-3.5 w-3.5" />}
|
|
1156
|
+
onClick={() => {
|
|
1157
|
+
props.onToggleStrikethrough?.();
|
|
1158
|
+
setOpen(false);
|
|
1159
|
+
}}
|
|
1160
|
+
/>
|
|
1161
|
+
<ToolbarPopoverActionButton
|
|
1162
|
+
active={props.formattingState?.superscript ?? false}
|
|
1163
|
+
ariaLabel="Superscript"
|
|
1164
|
+
disabled={props.disabled}
|
|
1165
|
+
icon={<Superscript className="h-3.5 w-3.5" />}
|
|
1166
|
+
onClick={() => {
|
|
1167
|
+
props.onToggleSuperscript?.();
|
|
1168
|
+
setOpen(false);
|
|
1169
|
+
}}
|
|
1170
|
+
/>
|
|
1171
|
+
<ToolbarPopoverActionButton
|
|
1172
|
+
active={props.formattingState?.subscript ?? false}
|
|
1173
|
+
ariaLabel="Subscript"
|
|
1174
|
+
disabled={props.disabled}
|
|
1175
|
+
icon={<Subscript className="h-3.5 w-3.5" />}
|
|
1176
|
+
onClick={() => {
|
|
1177
|
+
props.onToggleSubscript?.();
|
|
1178
|
+
setOpen(false);
|
|
1179
|
+
}}
|
|
1180
|
+
/>
|
|
1181
|
+
</div>
|
|
1182
|
+
</div>
|
|
1183
|
+
) : null}
|
|
1184
|
+
</div>
|
|
1185
|
+
);
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
function ToolbarColorPopover(props: {
|
|
1189
|
+
ariaLabel: string;
|
|
1190
|
+
colors: ReadonlyArray<{ value: string | null; label: string }>;
|
|
1191
|
+
disabled: boolean;
|
|
1192
|
+
icon: React.ReactNode;
|
|
1193
|
+
title: string;
|
|
1194
|
+
onSelect: (value: string | null) => void;
|
|
1195
|
+
}) {
|
|
1196
|
+
const [open, setOpen] = React.useState(false);
|
|
1197
|
+
|
|
1198
|
+
return (
|
|
1199
|
+
<div className="relative">
|
|
1200
|
+
<Tooltip.Root>
|
|
1201
|
+
<Tooltip.Trigger asChild>
|
|
1202
|
+
<button
|
|
1203
|
+
type="button"
|
|
1204
|
+
aria-label={props.ariaLabel}
|
|
1205
|
+
aria-expanded={open}
|
|
1206
|
+
disabled={props.disabled}
|
|
1207
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
1208
|
+
onClick={() => setOpen((value) => !value)}
|
|
1209
|
+
className={`inline-flex h-7 w-7 items-center justify-center rounded-md text-secondary transition-colors hover:bg-surface outline-none disabled:cursor-not-allowed disabled:opacity-40 ${focusRingClass}`}
|
|
1210
|
+
>
|
|
1211
|
+
{props.icon}
|
|
1212
|
+
</button>
|
|
1213
|
+
</Tooltip.Trigger>
|
|
1214
|
+
<Tooltip.Portal>
|
|
1215
|
+
<Tooltip.Content className="rounded-md bg-primary px-2 py-1 text-xs text-white shadow-md z-50" sideOffset={6}>
|
|
1216
|
+
{props.title}
|
|
1217
|
+
</Tooltip.Content>
|
|
1218
|
+
</Tooltip.Portal>
|
|
1219
|
+
</Tooltip.Root>
|
|
1220
|
+
{open ? (
|
|
1221
|
+
<div className="absolute left-0 top-9 z-50 w-[180px] rounded-lg bg-canvas p-2 shadow-lg ring-1 ring-border">
|
|
1222
|
+
<div className="mb-1 px-1 text-[10px] font-semibold uppercase tracking-[0.12em] text-tertiary">
|
|
1223
|
+
{props.title}
|
|
1224
|
+
</div>
|
|
1225
|
+
<div className="grid grid-cols-3 gap-1">
|
|
1226
|
+
{props.colors.map((color) => (
|
|
1227
|
+
<button
|
|
1228
|
+
key={`${props.ariaLabel}-${color.label}`}
|
|
1229
|
+
type="button"
|
|
1230
|
+
aria-label={`${props.title} ${color.label}`}
|
|
1231
|
+
disabled={props.disabled}
|
|
1232
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
1233
|
+
onClick={() => {
|
|
1234
|
+
props.onSelect(color.value);
|
|
1235
|
+
setOpen(false);
|
|
1236
|
+
}}
|
|
1237
|
+
className={`inline-flex h-8 items-center justify-center rounded-md border border-border text-[10px] font-medium text-primary transition-transform hover:scale-[1.04] disabled:cursor-not-allowed disabled:opacity-40 ${
|
|
1238
|
+
color.value ? "" : "bg-surface"
|
|
1239
|
+
} ${focusRingClass}`}
|
|
1240
|
+
style={color.value ? { backgroundColor: color.value } : undefined}
|
|
1241
|
+
>
|
|
1242
|
+
{color.value ? <span className="sr-only">{color.label}</span> : "None"}
|
|
1243
|
+
</button>
|
|
1244
|
+
))}
|
|
1245
|
+
</div>
|
|
1246
|
+
</div>
|
|
1247
|
+
) : null}
|
|
1248
|
+
</div>
|
|
1249
|
+
);
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
function ToolbarAlignmentPopover(props: {
|
|
1253
|
+
activeAlignment?: FormattingAlignment;
|
|
1254
|
+
disabled: boolean;
|
|
1255
|
+
onSelect: (alignment: FormattingAlignment) => void;
|
|
1256
|
+
}) {
|
|
1257
|
+
const [open, setOpen] = React.useState(false);
|
|
1258
|
+
const alignments = [
|
|
1259
|
+
{ value: "left" as const, label: "Align left", icon: <AlignLeft className="h-3.5 w-3.5" /> },
|
|
1260
|
+
{ value: "center" as const, label: "Align center", icon: <AlignCenter className="h-3.5 w-3.5" /> },
|
|
1261
|
+
{ value: "right" as const, label: "Align right", icon: <AlignRight className="h-3.5 w-3.5" /> },
|
|
1262
|
+
{ value: "justify" as const, label: "Align justify", icon: <AlignJustify className="h-3.5 w-3.5" /> },
|
|
1263
|
+
];
|
|
1264
|
+
|
|
1265
|
+
return (
|
|
1266
|
+
<div className="relative">
|
|
1267
|
+
<Tooltip.Root>
|
|
1268
|
+
<Tooltip.Trigger asChild>
|
|
1269
|
+
<button
|
|
1270
|
+
type="button"
|
|
1271
|
+
aria-label="Paragraph alignment"
|
|
1272
|
+
aria-expanded={open}
|
|
1273
|
+
disabled={props.disabled}
|
|
1274
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
1275
|
+
onClick={() => setOpen((value) => !value)}
|
|
1276
|
+
className={`inline-flex h-7 w-7 items-center justify-center rounded-md text-secondary transition-colors hover:bg-surface outline-none disabled:cursor-not-allowed disabled:opacity-40 ${focusRingClass}`}
|
|
1277
|
+
>
|
|
1278
|
+
{(alignments.find((entry) => entry.value === props.activeAlignment) ?? alignments[0])?.icon}
|
|
1279
|
+
</button>
|
|
1280
|
+
</Tooltip.Trigger>
|
|
1281
|
+
<Tooltip.Portal>
|
|
1282
|
+
<Tooltip.Content className="rounded-md bg-primary px-2 py-1 text-xs text-white shadow-md z-50" sideOffset={6}>
|
|
1283
|
+
Paragraph alignment
|
|
1284
|
+
</Tooltip.Content>
|
|
1285
|
+
</Tooltip.Portal>
|
|
1286
|
+
</Tooltip.Root>
|
|
1287
|
+
{open ? (
|
|
1288
|
+
<div className="absolute left-0 top-9 z-50 w-[220px] rounded-lg bg-canvas p-2 shadow-lg ring-1 ring-border">
|
|
1289
|
+
<div className="mb-1 px-1 text-[10px] font-semibold uppercase tracking-[0.12em] text-tertiary">
|
|
1290
|
+
Paragraph alignment
|
|
1291
|
+
</div>
|
|
1292
|
+
<div className="grid grid-cols-2 gap-1">
|
|
1293
|
+
{alignments.map((entry) => (
|
|
1294
|
+
<ToolbarPopoverActionButton
|
|
1295
|
+
key={entry.value}
|
|
1296
|
+
active={props.activeAlignment === entry.value}
|
|
1297
|
+
ariaLabel={entry.label}
|
|
1298
|
+
disabled={props.disabled}
|
|
1299
|
+
icon={entry.icon}
|
|
1300
|
+
onClick={() => {
|
|
1301
|
+
props.onSelect(entry.value);
|
|
1302
|
+
setOpen(false);
|
|
1303
|
+
}}
|
|
1304
|
+
/>
|
|
1305
|
+
))}
|
|
1306
|
+
</div>
|
|
1307
|
+
</div>
|
|
1308
|
+
) : null}
|
|
1309
|
+
</div>
|
|
1310
|
+
);
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
function ToolbarInsertMenu(props: {
|
|
1314
|
+
disabled: boolean;
|
|
1315
|
+
onInsertPageBreak?: () => void;
|
|
1316
|
+
onInsertTable?: () => void;
|
|
1317
|
+
onInsertSectionBreak?: (type: SectionBreakType) => void;
|
|
1318
|
+
onInsertImage?: (options: InsertImageOptions) => void;
|
|
1319
|
+
}) {
|
|
1320
|
+
const [open, setOpen] = React.useState(false);
|
|
1321
|
+
|
|
1322
|
+
async function handleImageChange(event: React.ChangeEvent<HTMLInputElement>): Promise<void> {
|
|
1323
|
+
const file = event.target.files?.[0];
|
|
1324
|
+
if (!file || props.disabled || !props.onInsertImage) {
|
|
1325
|
+
event.target.value = "";
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1328
|
+
const data = new Uint8Array(await file.arrayBuffer());
|
|
1329
|
+
props.onInsertImage({
|
|
1330
|
+
data,
|
|
1331
|
+
mimeType: file.type || "image/png",
|
|
1332
|
+
altText: file.name,
|
|
1333
|
+
});
|
|
1334
|
+
setOpen(false);
|
|
1335
|
+
event.target.value = "";
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
return (
|
|
1339
|
+
<div className="relative">
|
|
1340
|
+
<Tooltip.Root>
|
|
1341
|
+
<Tooltip.Trigger asChild>
|
|
1342
|
+
<button
|
|
1343
|
+
type="button"
|
|
1344
|
+
aria-label="Insert"
|
|
1345
|
+
aria-expanded={open}
|
|
1346
|
+
disabled={props.disabled}
|
|
1347
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
1348
|
+
onClick={() => setOpen((value) => !value)}
|
|
1349
|
+
className={`inline-flex h-7 items-center gap-1 rounded-md border border-border bg-canvas px-2 text-xs font-medium text-primary transition-colors hover:bg-surface outline-none disabled:cursor-not-allowed disabled:opacity-40 ${focusRingClass}`}
|
|
1350
|
+
>
|
|
1351
|
+
Insert
|
|
1352
|
+
<ChevronDown className="h-3.5 w-3.5 text-tertiary" />
|
|
1353
|
+
</button>
|
|
1354
|
+
</Tooltip.Trigger>
|
|
1355
|
+
<Tooltip.Portal>
|
|
1356
|
+
<Tooltip.Content className="rounded-md bg-primary px-2 py-1 text-xs text-white shadow-md z-50" sideOffset={6}>
|
|
1357
|
+
Insert
|
|
1358
|
+
</Tooltip.Content>
|
|
1359
|
+
</Tooltip.Portal>
|
|
1360
|
+
</Tooltip.Root>
|
|
1361
|
+
{open ? (
|
|
1362
|
+
<div className="absolute left-0 top-9 z-50 w-[220px] rounded-lg bg-canvas p-2 shadow-lg ring-1 ring-border">
|
|
1363
|
+
<div className="space-y-1">
|
|
1364
|
+
<ToolbarMenuButton
|
|
1365
|
+
ariaLabel="Insert page break"
|
|
1366
|
+
disabled={props.disabled || !props.onInsertPageBreak}
|
|
1367
|
+
icon={<Minus className="h-3.5 w-3.5" />}
|
|
1368
|
+
label="Page break"
|
|
1369
|
+
onClick={() => {
|
|
1370
|
+
props.onInsertPageBreak?.();
|
|
1371
|
+
setOpen(false);
|
|
1372
|
+
}}
|
|
1373
|
+
/>
|
|
1374
|
+
<ToolbarMenuButton
|
|
1375
|
+
ariaLabel="Insert table"
|
|
1376
|
+
disabled={props.disabled || !props.onInsertTable}
|
|
1377
|
+
icon={<Rows3 className="h-3.5 w-3.5" />}
|
|
1378
|
+
label="Table"
|
|
1379
|
+
onClick={() => {
|
|
1380
|
+
props.onInsertTable?.();
|
|
1381
|
+
setOpen(false);
|
|
1382
|
+
}}
|
|
1383
|
+
/>
|
|
1384
|
+
<label
|
|
1385
|
+
className={`flex h-8 cursor-pointer items-center gap-2 rounded-md px-2 text-xs font-medium text-primary transition-colors hover:bg-surface ${
|
|
1386
|
+
props.disabled || !props.onInsertImage ? "pointer-events-none opacity-40" : ""
|
|
1387
|
+
}`}
|
|
1388
|
+
>
|
|
1389
|
+
<ImagePlus className="h-3.5 w-3.5 text-secondary" />
|
|
1390
|
+
<span>Image</span>
|
|
1391
|
+
<input
|
|
1392
|
+
accept="image/png,image/jpeg,image/gif"
|
|
1393
|
+
aria-label="Insert image"
|
|
1394
|
+
className="sr-only"
|
|
1395
|
+
disabled={props.disabled || !props.onInsertImage}
|
|
1396
|
+
type="file"
|
|
1397
|
+
onChange={(event) => {
|
|
1398
|
+
void handleImageChange(event);
|
|
1399
|
+
}}
|
|
1400
|
+
/>
|
|
1401
|
+
</label>
|
|
1402
|
+
<ToolbarMenuButton
|
|
1403
|
+
ariaLabel="Insert next-page section break"
|
|
1404
|
+
disabled={props.disabled || !props.onInsertSectionBreak}
|
|
1405
|
+
icon={<FileText className="h-3.5 w-3.5" />}
|
|
1406
|
+
label="Next-page section break"
|
|
1407
|
+
onClick={() => {
|
|
1408
|
+
props.onInsertSectionBreak?.("nextPage");
|
|
1409
|
+
setOpen(false);
|
|
1410
|
+
}}
|
|
1411
|
+
/>
|
|
1412
|
+
</div>
|
|
1413
|
+
</div>
|
|
1414
|
+
) : null}
|
|
1415
|
+
</div>
|
|
1416
|
+
);
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
function ToolbarPopoverActionButton(props: {
|
|
1420
|
+
active: boolean;
|
|
1421
|
+
ariaLabel: string;
|
|
1422
|
+
disabled: boolean;
|
|
1423
|
+
icon: React.ReactNode;
|
|
1424
|
+
onClick?: () => void;
|
|
1425
|
+
}) {
|
|
1426
|
+
return (
|
|
1427
|
+
<button
|
|
1428
|
+
type="button"
|
|
1429
|
+
aria-label={props.ariaLabel}
|
|
1430
|
+
aria-pressed={props.active}
|
|
1431
|
+
disabled={props.disabled}
|
|
1432
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
1433
|
+
onClick={props.onClick}
|
|
1434
|
+
className={`inline-flex h-8 items-center justify-center rounded-md border border-border transition-colors disabled:cursor-not-allowed disabled:opacity-40 ${
|
|
1435
|
+
props.active ? "bg-canvas text-accent ring-1 ring-accent/30 shadow-sm" : "bg-canvas text-secondary hover:bg-surface"
|
|
1436
|
+
} ${focusRingClass}`}
|
|
1437
|
+
>
|
|
1438
|
+
{props.icon}
|
|
1439
|
+
</button>
|
|
1440
|
+
);
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
function ToolbarMenuButton(props: {
|
|
1444
|
+
ariaLabel: string;
|
|
1445
|
+
disabled: boolean;
|
|
1446
|
+
icon: React.ReactNode;
|
|
1447
|
+
label: string;
|
|
1448
|
+
onClick?: () => void;
|
|
1449
|
+
}) {
|
|
1450
|
+
return (
|
|
1451
|
+
<button
|
|
1452
|
+
type="button"
|
|
1453
|
+
aria-label={props.ariaLabel}
|
|
1454
|
+
disabled={props.disabled}
|
|
1455
|
+
onMouseDown={preserveEditorSelectionMouseDown}
|
|
1456
|
+
onClick={props.onClick}
|
|
1457
|
+
className={`flex h-8 w-full items-center gap-2 rounded-md px-2 text-left text-xs font-medium text-primary transition-colors hover:bg-surface disabled:cursor-not-allowed disabled:opacity-40 ${focusRingClass}`}
|
|
1458
|
+
>
|
|
1459
|
+
<span className="text-secondary">{props.icon}</span>
|
|
1460
|
+
<span>{props.label}</span>
|
|
1461
|
+
</button>
|
|
1462
|
+
);
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
function storyLabel(target: EditorStoryTarget): string {
|
|
1466
|
+
switch (target.kind) {
|
|
1467
|
+
case "header":
|
|
1468
|
+
return `Header (${target.variant})`;
|
|
1469
|
+
case "footer":
|
|
1470
|
+
return `Footer (${target.variant})`;
|
|
1471
|
+
case "footnote":
|
|
1472
|
+
return "Footnote";
|
|
1473
|
+
case "endnote":
|
|
1474
|
+
return "Endnote";
|
|
1475
|
+
default:
|
|
1476
|
+
return "Document";
|
|
1477
|
+
}
|
|
1478
|
+
}
|