CRDCDH-3760 Implement Rich Text Editor and add to SRF review comments#40
CRDCDH-3760 Implement Rich Text Editor and add to SRF review comments#40Alejandro-Vega wants to merge 61 commits into
Conversation
#144 Bundle Size — 12.86MiB (+3.42%).3a9ea21(current) vs a8ce1bb 3.7.0#141(baseline) Important Bundle introduced 4 and removed 1 duplicate packages – View changed duplicate packages Warning Bundle introduced 22 new packages: direction, compute-scroll-into-view, scroll-into-view-if-needed and 19 more – View changed packages Bundle metrics
Bundle size by type
Bundle analysis report Branch CRDCDH-3760 Project dashboard Generated by RelativeCI Documentation Report issue |
There was a problem hiding this comment.
Pull request overview
This PR introduces a new Slate-based Rich Text Editor (storing a constrained markdown subset) and updates SRF review comment entry/display to use rich text instead of a plain textarea.
Changes:
- Added
RichTextEditorcomponent (toolbar, Slate controller/hook, markdown serialize/deserialize, keyboard handlers) plus unit/accessibility tests and Storybook stories. - Added
RichTextViewerto render stored markdown in the review comments dialog. - Updated review dialogs/tests and added frontend dependencies (
slate*,rehype-raw) to support the editor/viewer.
Reviewed changes
Copilot reviewed 46 out of 47 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| apps/frontend/src/content/questionnaire/FormView.test.tsx | Mocks the new RichTextEditor for FormView tests. |
| apps/frontend/src/config/EditorConfig.ts | Defines mark/block formats (bold/italic + lists) used by the editor. |
| apps/frontend/src/components/RichTextViewer/index.tsx | New markdown viewer for rendering stored rich text in the UI. |
| apps/frontend/src/components/RichTextViewer/index.test.tsx | Viewer accessibility/basic rendering tests. |
| apps/frontend/src/components/RichTextViewer/index.stories.tsx | Storybook stories for viewer formatting/edge cases. |
| apps/frontend/src/components/RichTextEditor/utils/markdown/markdownUtils.ts | Markdown line-ending normalization + list-line parsing helpers. |
| apps/frontend/src/components/RichTextEditor/utils/markdown/markdownUtils.test.ts | Unit tests for markdown utils. |
| apps/frontend/src/components/RichTextEditor/utils/markdown/markdownSerializer.ts | Slate → markdown serialization and plain-text-length computation. |
| apps/frontend/src/components/RichTextEditor/utils/markdown/markdownSerializer.test.ts | Unit tests for serializer/plain-text-length. |
| apps/frontend/src/components/RichTextEditor/utils/markdown/markdownInlineParser.ts | Inline markdown → Slate text node parsing for supported marks. |
| apps/frontend/src/components/RichTextEditor/utils/markdown/markdownInlineParser.test.ts | Unit tests for inline parser. |
| apps/frontend/src/components/RichTextEditor/utils/markdown/markdownDeserializer.ts | Markdown → Slate document deserialization (paragraphs/lists). |
| apps/frontend/src/components/RichTextEditor/utils/markdown/markdownDeserializer.test.ts | Unit tests for deserializer. |
| apps/frontend/src/components/RichTextEditor/utils/keyboard/keyboardSelectionUtils.ts | Keyboard selection helpers for indentation/list behaviors. |
| apps/frontend/src/components/RichTextEditor/utils/keyboard/keyboardSelectionUtils.test.ts | Unit tests for keyboard selection helpers. |
| apps/frontend/src/components/RichTextEditor/utils/keyboard/keyboardListHandlers.ts | Tab/Enter/Backspace/list-shortcut handlers for editor UX. |
| apps/frontend/src/components/RichTextEditor/utils/keyboard/keyboardListHandlers.test.ts | Unit tests for list/keyboard handlers. |
| apps/frontend/src/components/RichTextEditor/utils/keyboard/keyboardHotkeyHandlers.ts | Ctrl/Cmd hotkeys for undo/redo and enabled marks. |
| apps/frontend/src/components/RichTextEditor/utils/keyboard/keyboardHotkeyHandlers.test.ts | Unit tests for hotkey handlers. |
| apps/frontend/src/components/RichTextEditor/utils/index.ts | Barrel exports for editor utilities. |
| apps/frontend/src/components/RichTextEditor/utils/editorTransforms.ts | Slate transforms for mark/block toggling and list wrapping. |
| apps/frontend/src/components/RichTextEditor/utils/editorTransforms.test.ts | Unit tests for editor transforms. |
| apps/frontend/src/components/RichTextEditor/utils/editorGuards.ts | Type guards for list formats/elements. |
| apps/frontend/src/components/RichTextEditor/utils/editorGuards.test.ts | Unit tests for editor guards. |
| apps/frontend/src/components/RichTextEditor/utils/documentUtils.ts | Helpers for creating/normalizing Slate documents and emptiness checks. |
| apps/frontend/src/components/RichTextEditor/utils/documentUtils.test.ts | Unit tests for document utils. |
| apps/frontend/src/components/RichTextEditor/types.ts | Shared editor types (marks, blocks, Slate custom types). |
| apps/frontend/src/components/RichTextEditor/ToolbarButton.tsx | Toolbar icon-button component. |
| apps/frontend/src/components/RichTextEditor/ToolbarButton.test.tsx | ToolbarButton accessibility/basic tests. |
| apps/frontend/src/components/RichTextEditor/Toolbar.tsx | Editor toolbar (marks, lists, undo/redo). |
| apps/frontend/src/components/RichTextEditor/Toolbar.test.tsx | Toolbar rendering + interaction tests. |
| apps/frontend/src/components/RichTextEditor/index.tsx | Lazy-loaded editor entrypoint + exports. |
| apps/frontend/src/components/RichTextEditor/index.test.tsx | Editor wrapper accessibility/basic tests. |
| apps/frontend/src/components/RichTextEditor/index.stories.tsx | Storybook stories for editor usage and states. |
| apps/frontend/src/components/RichTextEditor/hooks/useRichTextEditor.tsx | Hook owning editor instance, serialization, handlers, reset. |
| apps/frontend/src/components/RichTextEditor/hooks/useRichTextEditor.test.tsx | Hook unit tests (serialization calls, reset behavior). |
| apps/frontend/src/components/RichTextEditor/EditorLeaf.tsx | Renders marked inline leaves using configured HTML tags. |
| apps/frontend/src/components/RichTextEditor/EditorLeaf.test.tsx | EditorLeaf accessibility/basic tests. |
| apps/frontend/src/components/RichTextEditor/EditorElement.tsx | Renders block elements (p/ul/ol/li). |
| apps/frontend/src/components/RichTextEditor/EditorElement.test.tsx | EditorElement accessibility/basic tests. |
| apps/frontend/src/components/RichTextEditor/Controller.tsx | Slate controller wiring toolbar + editable area + imperative reset. |
| apps/frontend/src/components/RichTextEditor/Controller.test.tsx | Controller behavior tests (toolbar toggle, aria/readOnly). |
| apps/frontend/src/components/ReviewCommentsDialog/index.tsx | Uses RichTextViewer to display stored review comments. |
| apps/frontend/src/components/Questionnaire/ReviewFormDialog.tsx | Replaces textarea with RichTextEditor + markdown-based validation. |
| apps/frontend/src/components/Questionnaire/ReviewFormDialog.test.tsx | Updates dialog tests to mock the new editor and validate behavior. |
| apps/frontend/package.json | Adds dependencies for viewer/editor (rehype-raw, slate*). |
| apps/frontend/package-lock.json | Lockfile updates for new dependencies/transitives. |
Files not reviewed (1)
- apps/frontend/package-lock.json: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
amattu2
left a comment
There was a problem hiding this comment.
Overall there's really not that many issues. I left some comments to consider.
Some comments were written before you made some additional commits, so if they're outdated, just ignore them.
| </Suspense> | ||
| )); | ||
|
|
||
| RichTextEditor.displayName = "RichTextEditor"; |
There was a problem hiding this comment.
Both this component and the controller have the same displayName. Is that intentional?
| format: "underline", | ||
| tooltip: "Underline (Ctrl+U)", | ||
| icon: FormatUnderlinedIcon, | ||
| enabled: false, | ||
| hotkey: "u", | ||
| htmlTag: "u", | ||
| markdownSyntax: ["<u>", "</u>"], | ||
| }, |
| value={field.value} | ||
| onChange={field.onChange} | ||
| onTextLengthChange={setPlainTextLength} | ||
| placeholder={`${reviewCommentLimitLabel} characters allowed`} |
| import { useRichTextEditor } from "./hooks/useRichTextEditor"; | ||
| import Toolbar from "./Toolbar"; | ||
|
|
||
| const StyledEditorWrapper = styled(Box)({ |
There was a problem hiding this comment.
Or a slightly off-white is fine too. Whatever is consistent with the rest of the app.
| "&:hover": { | ||
| borderColor: "#6B7294", | ||
| }, | ||
| "&:focus-within": { |
There was a problem hiding this comment.
This might not be an issue, but when focusing on the textarea, the box shadow is applied to the entire text editor, rather than just the textarea itself. I'm not sure that there's a better alternative, as it would look weird if the box shadow was between the textarea and the toolbar. It might be worth just confirming that what we've done here is consistent with other WYSIWYG editors.
| * Owns the Slate editor instance, initial value, serialization, render callbacks, | ||
| * and keyboard handler for the rich text editor component. | ||
| */ | ||
| export const useRichTextEditor = ({ |
| className?: string; | ||
| }; | ||
|
|
||
| const RichTextViewer = ({ content, className }: Props): ReactElement | null => { |
| } | ||
|
|
||
| return ( | ||
| <StyledMarkdown |
There was a problem hiding this comment.
I'm not sure whether this can be fixed, but HTML is just stripped out completely. We obviously don't want to render it, but can we still visually preserve the content without treating it as markdown+HTML?
I also noticed here that the x2 newlines between the headings gets removed. This is probably not a deal breaker, just pointing it out.






Overview
Created a Rich Text Editor component and replaced existing review comment input for both approval and inquire dialogs.
Change Details (Specifics)
Related Ticket(s)
CRDCDH-3659 (US)
CRDCDH-3760 (Task)