feat(components): added Markdown component (#DS-5225) - #447
Conversation
|
Warning Review limit reached
Next review available in: 34 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughAdds a client-side ChangesMarkdown component
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Markdown
participant marked
participant DOMPurify
participant React
Markdown->>marked: Parse Markdown with markedOptions
marked-->>Markdown: Return generated HTML
Markdown->>DOMPurify: Sanitize HTML in the browser
DOMPurify-->>Markdown: Return sanitized HTML
Markdown->>React: Render with dangerouslySetInnerHTML
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Visit the preview URL for this PR (updated for commit 5b2ecb9): https://react-koobiq-next--prs-447-2v2xguuh.web.app (expires Tue, 11 Aug 2026 10:07:30 GMT) 🔥 via Firebase Hosting GitHub Action 🌎 Sign: fc29847d4a9e5cb1adf458c76a9b681c76e2eeff |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/components/package.json`:
- Around line 45-56: Update the dependency declarations in package.json so
dompurify and marked are required rather than optional peer dependencies, since
Markdown directly imports them. Remove their optional peer metadata while
preserving the existing Markdown export behavior.
In `@packages/components/src/components/Markdown/types.ts`:
- Around line 5-7: Update MarkdownProps in
packages/components/src/components/Markdown/types.ts to omit
dangerouslySetInnerHTML alongside children. In
packages/components/src/components/Markdown/Markdown.tsx, destructure and
discard dangerouslySetInnerHTML before spreading the remaining props onto the
div. Regenerate tools/public_api_guard/components/Markdown.api.md so the public
API reflects the updated type.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 0e62241d-b177-4229-9755-ebb327fb4469
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (12)
package.jsonpackages/components/package.jsonpackages/components/src/components/Markdown/Markdown.mdxpackages/components/src/components/Markdown/Markdown.module.csspackages/components/src/components/Markdown/Markdown.stories.tsxpackages/components/src/components/Markdown/Markdown.test.tsxpackages/components/src/components/Markdown/Markdown.tsxpackages/components/src/components/Markdown/index.tspackages/components/src/components/Markdown/types.tspackages/components/src/components/index.tstools/api-extractor/config.jsontools/public_api_guard/components/Markdown.api.md
| export interface MarkdownProps extends Omit< | ||
| ComponentPropsWithoutRef<'div'>, | ||
| 'children' |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C 3 'dangerouslySetInnerHTML|ComponentPropsWithoutRef' \
packages/components/src/components/Markdown/types.ts \
packages/components/src/components/Markdown/Markdown.tsx \
tools/public_api_guard/components/Markdown.api.mdRepository: koobiq/react-components
Length of output: 2772
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf 'Markdown source outline:\n'
ast-grep outline packages/components/src/components/Markdown/Markdown.tsx --view expanded || true
printf '\nMarkdown source relevant section:\n'
sed -n '1,130p' packages/components/src/components/Markdown/Markdown.tsx
printf '\nReact prop merge behavior probe:\n'
node - <<'JS'
function shallowCloneProps(props) {
const result = {};
for (const key in props) {
if (Object.prototype.hasOwnProperty.call(props, key)) {
result[key] = props[key];
}
}
return result;
}
const input = {
ref: { current: null },
className: 'markdown',
children: 'Markdown text',
dangerouslySetInnerHTML: { __html: '<p>bad html</p>' }
};
const { children, markedOptions, className, ...other } = input;
const outerDivProps = shallowCloneProps(other);
outerDivProps.children = children;
outerDivProps.className = className;
console.log(JSON.stringify(outerDivProps));
JSRepository: koobiq/react-components
Length of output: 3325
🌐 Web query:
React DOMProps omit dangerouslySetInnerHTML component type ComponentPropsWithoutRef
💡 Result:
In React, ComponentPropsWithoutRef is a utility type that extracts all props accepted by a native DOM element (or a component), excluding the ref attribute [1][2][3]. Because dangerouslySetInnerHTML is a standard React prop supported by all intrinsic HTML elements, it is included in the type definition returned by ComponentPropsWithoutRef<'element'> [4][5]. For example, when using ComponentPropsWithoutRef<'div'>, the resulting type includes dangerouslySetInnerHTML as an optional property [5]. If you are creating a custom component that accepts standard DOM props but you wish to explicitly exclude or restrict dangerouslySetInnerHTML, you must use Omit to remove it [6]. Common patterns involving these concepts: 1. Extracting props: You can use ComponentPropsWithoutRef to create props interfaces that mirror standard HTML elements [1][7]. type MyComponentProps = React.ComponentPropsWithoutRef<'div'>; 2. Omitting dangerouslySetInnerHTML: If you want to prevent users of your component from passing dangerouslySetInnerHTML, you can explicitly omit it [6]. type SafeComponentProps = Omit<React.ComponentPropsWithoutRef<'div'>, 'dangerouslySetInnerHTML'>; 3. Usage of dangerouslySetInnerHTML: This prop expects an object with an html key, containing the raw HTML string [4][8]. It should be used with extreme caution due to the risk of Cross-Site Scripting (XSS) vulnerabilities, especially when handling user-provided data [4][9]. React also enforces that you cannot pass both children and dangerouslySetInnerHTML simultaneously [4].
Citations:
- 1: https://stevekinney.com/courses/react-typescript/mirror-dom-props
- 2: https://stackoverflow.com/questions/55484676/how-to-reference-a-components-props-in-react
- 3: https://www.totaltypescript.com/concepts/react-componentprops-type-helper
- 4: https://react.dev/reference/react-dom/components/common
- 5: https://www.jsdocs.io/package/@types/react
- 6: https://github.com/primer/brand/blob/main/packages/react/src/Prose/Prose.tsx
- 7: https://www.totaltypescript.com/tutorials/react-with-typescript/components/using-html-props-in-react-components/solution
- 8: https://learning-zone.github.io/react-basics/
- 9: https://atomizedobjects.com/blog/react/how-to-use-dangerouslysetinnerhtml-in-react/
🏁 Script executed:
#!/bin/bash
set -euo pipefail
node - <<'JS'
const domProps = { ref: { current: null }, className: 'markdown' };
const outerProps = { ...domProps, children: 'Markdown text' };
const input = { ...outerProps, dangerouslySetInnerHTML: { __html: '<p>bad html</p>' } };
const checked = { children: input.children, markedOptions: input.markedOptions, className: input.className };
const { dangerouslySetInnerHTML: _, ...other } = input;
const filtered = { ...checked, ...other };
const invalid = { dangerouslysetinnerhtml: input.dangerouslySetInnerHTML, children: input.children };
const valid = { children: input.children };
console.log('Invalid outer div props include children and dangerouslySetInnerHTML:', filtered.children != null && filtered.dangerouslySetInnerHTML != null);
console.log('Explicit invalid JSX equivalent throws when constructed in React:', typeof React === 'object' && typeof React.createElement === 'function'
? (() => { try { React.createElement('div', invalid); console.log('React.createElement did not throw'); } catch (error) { console.log('React.createElement threw:', error.message); } })()
: 'React not loaded');
console.log('Filtered outer div props include children and dangerouslySetInnerHTML:', filtered.dangerouslySetInnerHTML != null);
console.log('Filtered outer div props have only valid content:', JSON.stringify(valid));
JSRepository: koobiq/react-components
Length of output: 476
Exclude and filter dangerouslySetInnerHTML.
ComponentPropsWithoutRef<'div'> includes dangerouslySetInnerHTML, and Markdown passes spread props to a div that already renders children. Pass both props and React throws, so omit it from MarkdownProps and extract it before forwarding the remaining DOM props. Regenerate the API report to match the public type.
📍 Affects 3 files
packages/components/src/components/Markdown/types.ts#L5-L7(this comment)packages/components/src/components/Markdown/Markdown.tsx#L81-L94tools/public_api_guard/components/Markdown.api.md#L16-L19
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/components/src/components/Markdown/types.ts` around lines 5 - 7,
Update MarkdownProps in packages/components/src/components/Markdown/types.ts to
omit dangerouslySetInnerHTML alongside children. In
packages/components/src/components/Markdown/Markdown.tsx, destructure and
discard dangerouslySetInnerHTML before spreading the remaining props onto the
div. Regenerate tools/public_api_guard/components/Markdown.api.md so the public
API reflects the updated type.
✅ Addressed in commit 5b2ecb9
There was a problem hiding this comment.
@KamilEmeleev я это исправил, как считаешь это справедливое замечание?
There was a problem hiding this comment.
да, справедливое замечание. проблема некритичная - поэтому minor, но дополнительная защита здесь не помешает.
There was a problem hiding this comment.
Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!
There was a problem hiding this comment.
Pull request overview
Adds a new Markdown component to @koobiq/react-components, exposing it via the package public API and Storybook docs/stories, using marked for parsing and DOMPurify for sanitization.
Changes:
- Introduces
Markdowncomponent implementation, styles, types, tests, stories, and MDX documentation. - Exposes the component through the components barrel exports and updates API Extractor config + API report.
- Adds
markedanddompurifyto the workspace lockfile and declares them as peer dependencies of@koobiq/react-components.
Reviewed changes
Copilot reviewed 12 out of 13 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/public_api_guard/components/Markdown.api.md | Adds the generated API Extractor report for the new component. |
| tools/api-extractor/config.json | Registers Markdown for API Extractor/public API guarding. |
| pnpm-lock.yaml | Locks new dependencies (marked, dompurify) and related graph updates. |
| packages/components/src/components/Markdown/types.ts | Defines the public MarkdownProps type surface. |
| packages/components/src/components/Markdown/Markdown.tsx | Implements Markdown-to-HTML rendering + sanitation and class injection. |
| packages/components/src/components/Markdown/Markdown.test.tsx | Adds unit tests for rendering, options, and sanitization behavior. |
| packages/components/src/components/Markdown/Markdown.stories.tsx | Adds Storybook stories covering common Markdown structures. |
| packages/components/src/components/Markdown/Markdown.module.css | Adds CSS module styles for rendered Markdown elements. |
| packages/components/src/components/Markdown/Markdown.mdx | Adds Storybook documentation page for the new component. |
| packages/components/src/components/Markdown/index.ts | Exports the component and its types from the component folder. |
| packages/components/src/components/index.ts | Re-exports Markdown from the components barrel. |
| packages/components/package.json | Declares marked/dompurify as peer deps (currently marked optional). |
| package.json | Adds marked/dompurify to the workspace root dependencies for development/tooling. |
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| "@koobiq/design-tokens": "^3.17.2", | ||
| "@koobiq/react-icons": "^12.0.0", | ||
| "dompurify": "^3.4.13", | ||
| "marked": "^18.0.9", |
There was a problem hiding this comment.
Предлагаю использовать react-markdown: он преобразует Markdown в React-элементы, поэтому результат становится частью обычного React-дерева и без дополнительной обработки участвует в SSR в Next.js. Стандартные Markdown-элементы также можно переопределять компонентами дизайн-системы через components.
marked возвращает HTML-строку, которую нужно вставлять через dangerouslySetInnerHTML, а для недоверенного контента — дополнительно санитизировать. Это рабочий подход, но он создаёт отдельный пайплайн обработки HTML и хуже вписывается в компонентную архитектуру React.
Choosing the Right Markdown Renderer for React
Пример использования react-markdown в компонентной библиотеке
Summary by CodeRabbit
New Features
Bug Fixes