diff --git a/package.json b/package.json
index 32c5a372..51a0ec86 100644
--- a/package.json
+++ b/package.json
@@ -99,7 +99,6 @@
"react": "^19.2.3",
"react-dom": "^19.2.3",
"react-hook-form": "^7.68.0",
- "remark-gfm": "^4.0.1",
"rimraf": "^6.0.1",
"rollup-preserve-directives": "^1.1.3",
"storybook": "^10.3.6",
diff --git a/packages/components/package.json b/packages/components/package.json
index 71a3caa6..89c68bf8 100644
--- a/packages/components/package.json
+++ b/packages/components/package.json
@@ -30,14 +30,16 @@
},
"sideEffects": false,
"dependencies": {
+ "@internationalized/date": "^3.12.0",
"@koobiq/design-tokens": "^3.17.2",
"@koobiq/logger": "workspace:*",
"@koobiq/react-core": "workspace:*",
"@koobiq/react-icons": "^12.0.0",
"@koobiq/react-primitives": "workspace:*",
"@types/react-transition-group": "^4.4.12",
+ "react-markdown": "^10.1.0",
"react-transition-group": "^4.4.5",
- "@internationalized/date": "^3.12.0"
+ "remark-gfm": "^4.0.1"
},
"peerDependencies": {
"@koobiq/design-tokens": "^3.17.2",
diff --git a/packages/components/src/components/Markdown/Markdown.mdx b/packages/components/src/components/Markdown/Markdown.mdx
new file mode 100644
index 00000000..0c3e02f8
--- /dev/null
+++ b/packages/components/src/components/Markdown/Markdown.mdx
@@ -0,0 +1,92 @@
+import {
+ Meta,
+ Story,
+ Props,
+ Status,
+} from '../../../../../.storybook/components';
+
+import * as Stories from './Markdown.stories';
+
+
+
+# Markdown
+
+
+
+`Markdown` renders a Markdown string as HTML.
+
+## Import
+
+```tsx
+import { Markdown } from '@koobiq/react-components';
+```
+
+## Usage
+
+Pass the Markdown text as `children`.
+
+
+
+## Headers
+
+
+
+### Headers combinations
+
+
+
+## Paragraph
+
+
+
+## Text emphasis
+
+
+
+## Blockquote
+
+
+
+## Lists
+
+
+
+## Inline code
+
+
+
+## Code block
+
+
+
+## Horizontal rule
+
+
+
+## Link
+
+
+
+## Image
+
+
+
+## Table
+
+
+
+## Line break
+
+To create a line break, add two trailing spaces at the end of the line.
+
+
+
+## Large article
+
+A larger combined example, useful as a visual sanity check for spacing and typography.
+
+
+
+## Props
+
+
diff --git a/packages/components/src/components/Markdown/Markdown.module.css b/packages/components/src/components/Markdown/Markdown.module.css
new file mode 100644
index 00000000..3d6f26bf
--- /dev/null
+++ b/packages/components/src/components/Markdown/Markdown.module.css
@@ -0,0 +1,294 @@
+.markdown {
+ display: block;
+ color: var(--kbq-foreground-contrast);
+ background: var(--kbq-markdown-bg, transparent);
+}
+
+.markdownOutput > :first-child {
+ margin-block-start: 0;
+}
+
+.markdownOutput > :last-child {
+ margin-block-end: 0;
+}
+
+.markdownOutput h1,
+.markdownOutput h2,
+.markdownOutput h3,
+.markdownOutput h4,
+.markdownOutput h5,
+.markdownOutput h6 {
+ color: var(--kbq-foreground-contrast);
+}
+
+.markdownOutput :is(h1, h2, h3, h4, h5, h6) + :is(h1, h2, h3, h4, h5, h6) {
+ margin-block-start: 0;
+}
+
+.markdownOutput h1 {
+ margin-block: var(--kbq-markdown-h1-size-margin-top)
+ var(--kbq-markdown-h1-size-margin-bottom);
+ font-size: var(--kbq-md-typography-md-h1-font-size);
+ line-height: var(--kbq-md-typography-md-h1-line-height);
+ font-weight: var(--kbq-md-typography-md-h1-font-weight);
+ letter-spacing: var(--kbq-md-typography-md-h1-letter-spacing);
+}
+
+.markdownOutput h2 {
+ margin-block: var(--kbq-markdown-h2-size-margin-top)
+ var(--kbq-markdown-h2-size-margin-bottom);
+ font-size: var(--kbq-md-typography-md-h2-font-size);
+ line-height: var(--kbq-md-typography-md-h2-line-height);
+ font-weight: var(--kbq-md-typography-md-h2-font-weight);
+ letter-spacing: var(--kbq-md-typography-md-h2-letter-spacing);
+}
+
+.markdownOutput h3 {
+ margin-block: var(--kbq-markdown-h3-size-margin-top)
+ var(--kbq-markdown-h3-size-margin-bottom);
+ font-size: var(--kbq-md-typography-md-h3-font-size);
+ line-height: var(--kbq-md-typography-md-h3-line-height);
+ font-weight: var(--kbq-md-typography-md-h3-font-weight);
+ letter-spacing: var(--kbq-md-typography-md-h3-letter-spacing);
+}
+
+.markdownOutput h4 {
+ margin-block: var(--kbq-markdown-h4-size-margin-top)
+ var(--kbq-markdown-h4-size-margin-bottom);
+ font-size: var(--kbq-md-typography-md-h4-font-size);
+ line-height: var(--kbq-md-typography-md-h4-line-height);
+ font-weight: var(--kbq-md-typography-md-h4-font-weight);
+ letter-spacing: var(--kbq-md-typography-md-h4-letter-spacing);
+}
+
+.markdownOutput h5 {
+ margin-block: var(--kbq-markdown-h5-size-margin-top)
+ var(--kbq-markdown-h5-size-margin-bottom);
+ font-size: var(--kbq-md-typography-md-h5-font-size);
+ line-height: var(--kbq-md-typography-md-h5-line-height);
+ font-weight: var(--kbq-md-typography-md-h5-font-weight);
+ letter-spacing: var(--kbq-md-typography-md-h5-letter-spacing);
+}
+
+.markdownOutput h6 {
+ margin-block: var(--kbq-markdown-h6-size-margin-top)
+ var(--kbq-markdown-h6-size-margin-bottom);
+ font-size: var(--kbq-md-typography-md-h6-font-size);
+ line-height: var(--kbq-md-typography-md-h6-line-height);
+ font-weight: var(--kbq-md-typography-md-h6-font-weight);
+ letter-spacing: var(--kbq-md-typography-md-h6-letter-spacing);
+ text-transform: var(--kbq-md-typography-md-h6-text-transform);
+}
+
+.markdownOutput p {
+ margin-block: var(--kbq-markdown-p-size-margin-top)
+ var(--kbq-markdown-p-size-margin-bottom);
+ color: var(--kbq-foreground-contrast);
+ font-size: var(--kbq-md-typography-md-body-font-size);
+ line-height: var(--kbq-md-typography-md-body-line-height);
+ letter-spacing: var(--kbq-md-typography-md-body-letter-spacing);
+}
+
+.markdownOutput ul,
+.markdownOutput ol {
+ margin-block: var(--kbq-markdown-list-size-margin-top)
+ var(--kbq-markdown-list-size-margin-bottom);
+ color: var(--kbq-foreground-contrast);
+ font-size: var(--kbq-md-typography-md-body-font-size);
+ line-height: var(--kbq-md-typography-md-body-line-height);
+}
+
+.markdownOutput p + ul,
+.markdownOutput p + ol {
+ margin-block-start: var(--kbq-markdown-list-size-margin-top-after-paragraph);
+}
+
+.markdownOutput ul {
+ position: relative;
+ list-style: none;
+ padding-inline: var(--kbq-size-xxl) 0;
+ padding-block: 0;
+}
+
+.markdownOutput ul > li::before {
+ content: '—';
+ display: block;
+ position: absolute;
+ inset-inline-start: 0;
+ inline-size: 24px;
+ text-align: start;
+}
+
+.markdownOutput ol {
+ list-style: none;
+ padding: 0;
+ counter-reset: li;
+}
+
+.markdownOutput ol > li {
+ counter-increment: li;
+}
+
+.markdownOutput ol > li::before {
+ content: counter(li) '.';
+ padding-inline-end: var(--kbq-markdown-list-size-ol-number-padding-right);
+}
+
+.markdownOutput ol > li > * {
+ display: inline-block;
+}
+
+.markdownOutput li {
+ margin-block-end: var(--kbq-markdown-list-size-item-margin-bottom);
+}
+
+.markdownOutput blockquote {
+ margin-block: var(--kbq-markdown-blockquote-size-margin-top)
+ var(--kbq-markdown-blockquote-size-margin-bottom);
+ margin-inline: 0;
+ padding: var(--kbq-markdown-blockquote-size-padding);
+ border-inline-start: var(--kbq-markdown-blockquote-size-line-width) solid
+ var(--kbq-markdown-blockquote-line);
+ color: var(--kbq-markdown-blockquote-text);
+ background: var(--kbq-markdown-blockquote-background);
+}
+
+.markdownOutput blockquote > p {
+ margin-block: 0;
+}
+
+.markdownOutput pre {
+ position: relative;
+ overflow-x: auto;
+ margin-block: var(--kbq-markdown-code-size-multiline-margin-top)
+ var(--kbq-markdown-code-size-multiline-margin-bottom);
+ padding: var(--kbq-markdown-code-size-multiline-padding);
+ border-radius: var(--kbq-markdown-code-size-border-radius);
+ color: var(--kbq-markdown-code-text);
+ background-color: var(--kbq-markdown-code-background);
+}
+
+.markdownOutput code {
+ border-radius: var(--kbq-markdown-code-size-border-radius);
+ font-family: var(--kbq-md-typography-md-inline-code-font-family);
+ font-size: var(--kbq-md-typography-md-inline-code-font-size);
+ line-height: var(--kbq-md-typography-md-inline-code-line-height);
+}
+
+.markdownOutput code:not(pre > code) {
+ white-space: nowrap;
+ padding: var(--kbq-markdown-code-size-inline-padding);
+}
+
+.markdownOutput code:not(pre > code, a code) {
+ color: var(--kbq-markdown-code-text);
+ background-color: var(--kbq-markdown-code-background);
+}
+
+.markdownOutput a code {
+ background-color: var(--kbq-markdown-code-background);
+ font-weight: var(--kbq-md-typography-md-inline-code-as-link-font-weight);
+}
+
+.markdownOutput img {
+ max-inline-size: 100%;
+ margin-block: var(--kbq-markdown-image-size-margin-top)
+ var(--kbq-markdown-image-size-margin-bottom);
+}
+
+.markdownOutput img + em {
+ display: block;
+ margin-block: var(--kbq-markdown-image-size-caption-margin-top)
+ var(--kbq-markdown-image-size-caption-margin-bottom);
+ color: var(--kbq-markdown-image-caption-text);
+ font-size: var(--kbq-md-typography-md-caption-font-size);
+ line-height: var(--kbq-md-typography-md-caption-line-height);
+}
+
+.markdownOutput hr {
+ block-size: 0;
+ margin-block: var(--kbq-markdown-hr-size-margin-vertical);
+ border: none;
+ border-block-end: var(--kbq-markdown-hr-size-width) solid
+ var(--kbq-markdown-hr-color);
+}
+
+.markdownOutput table {
+ max-inline-size: 100%;
+ overflow-x: auto;
+ display: table;
+ border-collapse: collapse;
+ margin-block-end: var(--kbq-markdown-table-size-margin-bottom);
+ color: var(--kbq-markdown-table-body);
+}
+
+.markdownOutput table td,
+.markdownOutput table th {
+ padding: var(--kbq-markdown-table-size-padding);
+ font-weight: 400;
+ vertical-align: top;
+}
+
+.markdownOutput thead {
+ border-block-end: var(--kbq-markdown-table-size-border-width) solid
+ var(--kbq-markdown-table-border);
+ text-align: start;
+ color: var(--kbq-markdown-table-header);
+ font-size: var(--kbq-md-typography-md-table-header-font-size);
+ line-height: var(--kbq-md-typography-md-table-header-line-height);
+}
+
+.markdownOutput thead th:first-child {
+ padding-inline-start: 0;
+}
+
+.markdownOutput thead th:last-child {
+ padding-inline-end: 0;
+}
+
+.markdownOutput tbody {
+ font-size: var(--kbq-md-typography-md-table-cell-font-size);
+ line-height: var(--kbq-md-typography-md-table-cell-line-height);
+}
+
+.markdownOutput tbody td:first-child {
+ padding-inline-start: 0;
+}
+
+.markdownOutput tbody td:last-child {
+ padding-inline-end: 0;
+}
+
+.markdownOutput a {
+ color: var(--kbq-foreground-theme);
+ text-decoration-line: underline;
+ text-decoration-color: var(--kbq-line-theme-less);
+ outline: var(--kbq-markdown-link-size-state-focused-outline-width) solid
+ transparent;
+ outline-offset: var(--kbq-markdown-link-size-state-focused-outline-offset);
+ transition:
+ color var(--kbq-transition-default),
+ outline-color var(--kbq-transition-default),
+ text-decoration-color var(--kbq-transition-default);
+}
+
+.markdownOutput a:hover {
+ color: var(--kbq-states-foreground-theme-hover);
+}
+
+.markdownOutput a:active {
+ color: var(--kbq-states-foreground-theme-active);
+}
+
+.markdownOutput a:focus-visible {
+ outline-color: var(--kbq-states-line-focus-theme);
+}
+
+.markdownOutput a:visited {
+ color: var(--kbq-foreground-visited);
+ text-decoration-color: var(--kbq-line-visited);
+}
+
+.markdownOutput a:has(code) {
+ text-decoration-line: none;
+}
diff --git a/packages/components/src/components/Markdown/Markdown.stories.tsx b/packages/components/src/components/Markdown/Markdown.stories.tsx
new file mode 100644
index 00000000..d1cedc0b
--- /dev/null
+++ b/packages/components/src/components/Markdown/Markdown.stories.tsx
@@ -0,0 +1,242 @@
+import type { Meta, StoryObj } from '@storybook/react';
+
+import { Markdown } from './index.js';
+
+const meta = {
+ title: 'Components/Markdown',
+ component: Markdown,
+ parameters: {
+ layout: 'padded',
+ },
+ tags: ['status:new', 'date:2026-08-05'],
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Overview: Story = {
+ render: () => (
+
+ {`# Heading 1
+## Heading 2
+### Heading 3
+#### Heading 4
+##### Heading 5
+###### Heading 6`}
+
+ ),
+};
+
+export const Headers: Story = {
+ render: () => (
+
+ {`# Heading 1
+## Heading 2
+### Heading 3
+#### Heading 4
+##### Heading 5
+###### Heading 6`}
+
+ ),
+};
+
+export const HeadersCombinations: Story = {
+ render: () => (
+
+ {`## Data Protection System
+### Security Levels
+#### Threat Types
+
+Recommended password length is 12 characters, encryption key length is 256 bits. Weak passwords are not rejected, the system raises security requirements.
+
+### Attack Detection
+
+Monitoring happens in real time. Multiple incidents form a unified threat picture. New events are displayed in the security log. If there are many threats, critical ones are highlighted with priority. Most often attacks are detected automatically, but an administrator can initiate a manual check.`}
+
+ ),
+};
+
+export const Paragraph: Story = {
+ render: () => (
+
+ {`A brute-force attack is a cryptanalytic attack that can, in theory, be used to attempt to decrypt any encrypted data (except for data encrypted in an information-theoretically secure manner).
+
+Such an attack might be used when it is not possible to take advantage of other weaknesses in an encryption system (if any exist) that would make the task easier.`}
+
+ ),
+};
+
+export const TextEmphasis: Story = {
+ render: () => (
+
+ {`**bold**
+__bold__
+
+_italic_
+*italic*
+
+***bold and italic***
+___bold and italic___`}
+
+ ),
+};
+
+export const Blockquote: Story = {
+ render: () => (
+
+ {`> A [brute-force attack](#) is a cryptanalytic attack that can, in theory, be used to attempt to decrypt any encrypted data (except for data encrypted in an information-theoretically secure manner). Such an attack might be used when it is not possible to take advantage of other weaknesses in an encryption system (if any exist) that would make the task easier.`}
+
+ ),
+};
+
+export const Lists: Story = {
+ render: () => (
+
+ {`### Ordered List:
+
+1. First item
+2. Second item
+3. Third item
+4. Fourth item
+
+### Unordered List:
+
+- First item
+- Second item
+- Third item
+- Fourth item`}
+
+ ),
+};
+
+export const InlineCode: Story = {
+ render: () => (
+ {'Use the `bypassSecurityTrustHtml()` function'}
+ ),
+};
+
+export const CodeBlock: Story = {
+ render: () => (
+
+ {`\`\`\`html
+
+
+
+
+
+ Koobiq
+
+
+
+
+
+\`\`\``}
+
+ ),
+};
+
+export const Divider: Story = {
+ render: () => {'---'},
+};
+
+export const Link: Story = {
+ render: () => (
+
+ {`[Link](#)
+
+A [\`brute-force\` attack](#) is a cryptanalytic attack that can, in theory, be used to attempt to decrypt any encrypted data (except for data encrypted in an information-theoretically secure manner).`}
+
+ ),
+};
+
+export const Image: Story = {
+ render: () => (
+
+ {`
+*Caption*`}
+
+ ),
+};
+
+export const Table: Story = {
+ render: () => (
+
+ {`| Default | Left align | Center align | Right align |
+| ---------- | :--------- | :----------: | ----------: |
+| Babable | Zillya | ClamAV | Acronis |
+| Cybereason | McAfee | Cyren | Zoner |
+| ESET NOD32 | Alibaba | eScan | Dr.Web |`}
+
+ ),
+};
+
+export const LineBreak: Story = {
+ render: () => {'First line \nSecond line'},
+};
+
+export const Article: Story = {
+ render: () => (
+
+ {`# Cybersecurity Fundamentals
+
+
+*Caption*
+
+## Network Security
+
+Cybersecurity is the practice of protecting systems, networks, and programs from digital attacks. These cyberattacks are usually aimed at accessing, changing, or destroying sensitive information; extorting money from users; or interrupting normal business processes.
+
+Implementing effective cybersecurity measures is particularly challenging today because there are more devices than people, and attackers are becoming more innovative. A successful cybersecurity approach has multiple layers of protection spread across the computers, networks, programs, or data that one intends to keep safe.
+
+## Data Protection
+
+### Encryption Standards
+
+Network security involves protecting the usability and integrity of network and data. It includes both hardware and software technologies. Effective network security manages access to the network and targets a variety of threats and stops them from entering or spreading on your network.
+
+### Authentication Methods
+
+Multi-factor authentication adds an extra layer of security by requiring users to provide two or more verification factors to gain access to a resource. This significantly reduces the risk of unauthorized access even if passwords are compromised.
+
+## Threat Detection
+
+
+*Image with a caption*
+
+Advanced persistent threats represent sophisticated, long-term cyber attacks where intruders gain access to a network and remain undetected for extended periods. These attacks are typically aimed at stealing data rather than causing damage to the network or organization. Threat detection systems use machine learning and behavioral analysis to identify suspicious activities and potential security breaches before they can cause significant damage.
+
+> Security is not a product, but a process that requires continuous monitoring and improvement.
+
+Incident response planning is crucial for organizations to effectively handle security breaches when they occur. A well-structured incident response plan helps minimize damage and recovery time.
+
+1. Identify and assess the security incident
+2. Contain the threat to prevent further damage
+3. Eradicate the threat from all affected systems
+4. Recover normal operations and monitor for signs of weakness
+
+Risk assessment and vulnerability management are ongoing processes that help organizations identify potential security gaps. Regular security audits and penetration testing help ensure that security measures remain effective against evolving threats.
+
+Cybersecurity awareness training is essential for all employees as human error remains one of the leading causes of security breaches. Regular training helps staff recognize phishing attempts, social engineering tactics, and other common attack vectors.
+
+### Security Monitoring
+
+Security information and event management systems collect and analyze security-related data from various sources across an organization's infrastructure.
+
+\`\`\`bash
+$ nmap -sS -O 192.168.1.1
+\`\`\`
+
+### Compliance Standards
+
+#### Regulatory Requirements
+
+Organizations must comply with various cybersecurity regulations and standards depending on their industry and location. These may include GDPR, HIPAA, SOX, and industry-specific requirements that mandate specific security controls and practices.
+
+- Regular security assessments and audits
+- Implementation of appropriate technical and organizational measures
+- Incident reporting and breach notification procedures
+
+Continuous improvement in cybersecurity requires staying updated with the latest threats, technologies, and best practices in the field.`}
+
+ ),
+};
diff --git a/packages/components/src/components/Markdown/Markdown.test.tsx b/packages/components/src/components/Markdown/Markdown.test.tsx
new file mode 100644
index 00000000..4313fe83
--- /dev/null
+++ b/packages/components/src/components/Markdown/Markdown.test.tsx
@@ -0,0 +1,101 @@
+import { createRef } from 'react';
+
+import { render, screen } from '@testing-library/react';
+import { renderToStaticMarkup } from 'react-dom/server';
+import { describe, it, expect } from 'vitest';
+
+import { Markdown } from './index.js';
+
+describe('Markdown', () => {
+ it('should render on the server', () => {
+ const html = renderToStaticMarkup(# Heading);
+
+ expect(html).toContain('Heading
');
+ });
+
+ it('should accept the ref', () => {
+ const ref = createRef();
+ const { container } = render(# Heading);
+ expect(ref.current).toBe(container.firstElementChild);
+ });
+
+ it('should accept a custom class', () => {
+ const { container } = render(
+ # Heading
+ );
+
+ expect(container.firstElementChild).toHaveClass('foo');
+ });
+
+ it('should render headers', () => {
+ render({'# Heading 1\n\n## Heading 2'});
+
+ expect(
+ screen.getByRole('heading', { level: 1, name: 'Heading 1' })
+ ).toBeInTheDocument();
+
+ expect(
+ screen.getByRole('heading', { level: 2, name: 'Heading 2' })
+ ).toBeInTheDocument();
+ });
+
+ it('should render a paragraph', () => {
+ render(Some paragraph text.);
+ expect(screen.getByText('Some paragraph text.')).toBeInTheDocument();
+ });
+
+ it('should render a list', () => {
+ render({'- First item\n- Second item'});
+
+ expect(screen.getByText('First item')).toBeInTheDocument();
+ expect(screen.getByText('Second item')).toBeInTheDocument();
+ });
+
+ it('should render a table', () => {
+ render({'| A | B |\n| - | - |\n| 1 | 2 |'});
+
+ expect(screen.getByRole('table')).toBeInTheDocument();
+ expect(screen.getByText('A')).toBeInTheDocument();
+ expect(screen.getByText('1')).toBeInTheDocument();
+ });
+
+ it('should render a link with href', () => {
+ render({'[koobiq](https://www.koobiq.io)'});
+
+ const link = screen.getByRole('link', { name: 'koobiq' });
+ expect(link).toHaveAttribute('href', 'https://www.koobiq.io');
+ });
+
+ it('should re-render when children change', () => {
+ const { rerender } = render(# First);
+ expect(screen.getByRole('heading', { name: 'First' })).toBeInTheDocument();
+
+ rerender(# Second);
+ expect(screen.getByRole('heading', { name: 'Second' })).toBeInTheDocument();
+
+ expect(
+ screen.queryByRole('heading', { name: 'First' })
+ ).not.toBeInTheDocument();
+ });
+
+ it('should not render raw HTML', () => {
+ const { container } = render(
+ {''}
+ );
+
+ expect(container.querySelector('script')).not.toBeInTheDocument();
+
+ expect(container).toHaveTextContent(
+ ''
+ );
+ });
+
+ it('should keep the language class on a fenced code block', () => {
+ const { container } = render(
+ {'```js\nconst a = 1;\n```'}
+ );
+
+ const code = container.querySelector('pre > code');
+ expect(code).toHaveClass('language-js');
+ });
+});
diff --git a/packages/components/src/components/Markdown/Markdown.tsx b/packages/components/src/components/Markdown/Markdown.tsx
new file mode 100644
index 00000000..b70a2f88
--- /dev/null
+++ b/packages/components/src/components/Markdown/Markdown.tsx
@@ -0,0 +1,31 @@
+'use client';
+
+import { forwardRef } from 'react';
+
+import { clsx } from '@koobiq/react-core';
+import ReactMarkdown from 'react-markdown';
+import remarkGfm from 'remark-gfm';
+
+import s from './Markdown.module.css';
+import type { MarkdownProps } from './types';
+
+const remarkPlugins = [remarkGfm];
+
+/** Markdown renders a Markdown string as React elements. */
+export const Markdown = forwardRef(
+ (props, ref) => {
+ const { children, className, ...other } = props;
+
+ return (
+
+ );
+ }
+);
+
+Markdown.displayName = 'Markdown';
diff --git a/packages/components/src/components/Markdown/index.ts b/packages/components/src/components/Markdown/index.ts
new file mode 100644
index 00000000..c5c27091
--- /dev/null
+++ b/packages/components/src/components/Markdown/index.ts
@@ -0,0 +1,2 @@
+export * from './Markdown.js';
+export * from './types.js';
diff --git a/packages/components/src/components/Markdown/types.ts b/packages/components/src/components/Markdown/types.ts
new file mode 100644
index 00000000..e001131b
--- /dev/null
+++ b/packages/components/src/components/Markdown/types.ts
@@ -0,0 +1,9 @@
+import type { ComponentPropsWithoutRef } from 'react';
+
+export interface MarkdownProps extends Omit<
+ ComponentPropsWithoutRef<'div'>,
+ 'children' | 'dangerouslySetInnerHTML'
+> {
+ /** Markdown text to convert into HTML. */
+ children: string;
+}
diff --git a/packages/components/src/components/index.ts b/packages/components/src/components/index.ts
index 438226c6..cc3495a4 100644
--- a/packages/components/src/components/index.ts
+++ b/packages/components/src/components/index.ts
@@ -27,6 +27,7 @@ export * from './SidePanel';
export * from './Popover';
export * from './Tooltip';
export * from './List';
+export * from './Markdown';
export * from './AnimatedIcon';
export * from './Select';
export * from './SelectNext';
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2cae3875..fef25af3 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -187,9 +187,6 @@ importers:
react-hook-form:
specifier: ^7.68.0
version: 7.68.0(react@19.2.3)
- remark-gfm:
- specifier: ^4.0.1
- version: 4.0.1
rimraf:
specifier: ^6.0.1
version: 6.0.1
@@ -268,9 +265,15 @@ importers:
react-dom:
specifier: ^19.0.0
version: 19.2.3(react@19.2.3)
+ react-markdown:
+ specifier: ^10.1.0
+ version: 10.1.0(@types/react@19.2.7)(react@19.2.3)
react-transition-group:
specifier: ^4.4.5
version: 4.4.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ remark-gfm:
+ specifier: ^4.0.1
+ version: 4.0.1
packages/core:
dependencies:
@@ -3153,6 +3156,9 @@ packages:
'@types/esrecurse@4.3.1':
resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==}
+ '@types/estree-jsx@1.0.5':
+ resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==}
+
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
@@ -3162,6 +3168,9 @@ packages:
'@types/fs-extra@11.0.4':
resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==}
+ '@types/hast@3.0.5':
+ resolution: {integrity: sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==}
+
'@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
@@ -3211,6 +3220,9 @@ packages:
'@types/triple-beam@1.3.5':
resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==}
+ '@types/unist@2.0.11':
+ resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==}
+
'@types/unist@3.0.3':
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
@@ -3281,6 +3293,9 @@ packages:
resolution: {integrity: sha512-QVLZu3ZPQEE+HICQyAMZ2yLQhxf0meY/wx6Hx14YcTNj13JB3qHlX3lJ02L3fLGHgERRH71kvYDwiXIguT3AjQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ '@ungap/structured-clone@1.3.3':
+ resolution: {integrity: sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==}
+
'@unrs/resolver-binding-android-arm-eabi@1.12.2':
resolution: {integrity: sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==}
cpu: [arm]
@@ -3801,7 +3816,7 @@ packages:
basic-ftp@5.1.0:
resolution: {integrity: sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw==}
engines: {node: '>=10.0.0'}
- deprecated: Security vulnerability fixed in 5.2.1, please upgrade
+ deprecated: Security vulnerability fixed in 5.2.0, please upgrade
bidi-js@1.0.3:
resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==}
@@ -3982,9 +3997,18 @@ packages:
resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==}
engines: {node: '>=10'}
+ character-entities-html4@2.1.0:
+ resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==}
+
+ character-entities-legacy@3.0.0:
+ resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==}
+
character-entities@2.0.2:
resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==}
+ character-reference-invalid@2.0.1:
+ resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==}
+
chardet@2.1.1:
resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==}
@@ -4112,6 +4136,9 @@ packages:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
+ comma-separated-tokens@2.0.3:
+ resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
+
commander@10.0.1:
resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==}
engines: {node: '>=14'}
@@ -4967,6 +4994,9 @@ packages:
resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
engines: {node: '>=4.0'}
+ estree-util-is-identifier-name@3.0.0:
+ resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==}
+
estree-walker@2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
@@ -5490,6 +5520,12 @@ packages:
resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==}
engines: {node: '>= 0.4'}
+ hast-util-to-jsx-runtime@2.3.6:
+ resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==}
+
+ hast-util-whitespace@3.0.0:
+ resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==}
+
heap-js@2.7.1:
resolution: {integrity: sha512-EQfezRg0NCZGNlhlDR3Evrw1FVL2G3LhU7EgPoxufQKruNBSYA8MiRPHeWbU+36o+Fhel0wMwM+sLEiBAlNLJA==}
engines: {node: '>=10.0.0'}
@@ -5532,6 +5568,9 @@ packages:
resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==}
engines: {node: '>=8'}
+ html-url-attributes@3.0.1:
+ resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==}
+
http-cache-semantics@4.2.0:
resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==}
@@ -5624,6 +5663,9 @@ packages:
resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+ inline-style-parser@0.2.7:
+ resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==}
+
install-artifact-from-github@1.4.0:
resolution: {integrity: sha512-+y6WywKZREw5rq7U2jvr2nmZpT7cbWbQQ0N/qfcseYnzHFz2cZz1Et52oY+XttYuYeTkI8Y+R2JNWj68MpQFSg==}
hasBin: true
@@ -5651,6 +5693,12 @@ packages:
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
engines: {node: '>= 0.10'}
+ is-alphabetical@2.0.1:
+ resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==}
+
+ is-alphanumerical@2.0.1:
+ resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==}
+
is-array-buffer@3.0.4:
resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==}
engines: {node: '>= 0.4'}
@@ -5724,6 +5772,9 @@ packages:
resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==}
engines: {node: '>= 0.4'}
+ is-decimal@2.0.1:
+ resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==}
+
is-docker@3.0.0:
resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@@ -5757,6 +5808,9 @@ packages:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
+ is-hexadecimal@2.0.1:
+ resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==}
+
is-inside-container@1.0.0:
resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==}
engines: {node: '>=14.16'}
@@ -6498,9 +6552,21 @@ packages:
mdast-util-gfm@3.0.0:
resolution: {integrity: sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==}
+ mdast-util-mdx-expression@2.0.1:
+ resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==}
+
+ mdast-util-mdx-jsx@3.2.0:
+ resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==}
+
+ mdast-util-mdxjs-esm@2.0.1:
+ resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==}
+
mdast-util-phrasing@4.1.0:
resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==}
+ mdast-util-to-hast@13.2.1:
+ resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==}
+
mdast-util-to-markdown@2.1.2:
resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==}
@@ -7072,6 +7138,9 @@ packages:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
+ parse-entities@4.0.2:
+ resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==}
+
parse-imports-exports@0.2.4:
resolution: {integrity: sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==}
@@ -7355,6 +7424,9 @@ packages:
prop-types@15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+ property-information@7.2.0:
+ resolution: {integrity: sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==}
+
proto-list@1.2.4:
resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==}
@@ -7471,6 +7543,12 @@ packages:
react-is@17.0.2:
resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
+ react-markdown@10.1.0:
+ resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==}
+ peerDependencies:
+ '@types/react': ^19.0.0
+ react: ^19.0.0
+
react-refresh@0.18.0:
resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==}
engines: {node: '>=0.10.0'}
@@ -7562,6 +7640,9 @@ packages:
remark-parse@11.0.0:
resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==}
+ remark-rehype@11.1.2:
+ resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==}
+
remark-stringify@11.0.0:
resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==}
@@ -7850,6 +7931,9 @@ packages:
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
engines: {node: '>=0.10.0'}
+ space-separated-tokens@2.0.2:
+ resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
+
spdx-correct@3.2.0:
resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==}
@@ -7988,6 +8072,9 @@ packages:
string_decoder@1.3.0:
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
+ stringify-entities@4.0.4:
+ resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==}
+
strip-ansi@6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
@@ -8019,6 +8106,12 @@ packages:
stubs@3.0.0:
resolution: {integrity: sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==}
+ style-to-js@1.1.21:
+ resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==}
+
+ style-to-object@1.0.14:
+ resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==}
+
styled-jsx@5.1.6:
resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==}
engines: {node: '>= 12.0.0'}
@@ -8261,6 +8354,9 @@ packages:
resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==}
engines: {node: '>=20'}
+ trim-lines@3.0.1:
+ resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
+
trim-newlines@3.0.1:
resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==}
engines: {node: '>=8'}
@@ -8443,6 +8539,9 @@ packages:
unist-util-is@6.0.0:
resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==}
+ unist-util-position@5.0.0:
+ resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==}
+
unist-util-stringify-position@4.0.0:
resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==}
@@ -11932,6 +12031,10 @@ snapshots:
'@types/esrecurse@4.3.1': {}
+ '@types/estree-jsx@1.0.5':
+ dependencies:
+ '@types/estree': 1.0.9
+
'@types/estree@1.0.8': {}
'@types/estree@1.0.9': {}
@@ -11941,6 +12044,10 @@ snapshots:
'@types/jsonfile': 6.1.4
'@types/node': 25.8.0
+ '@types/hast@3.0.5':
+ dependencies:
+ '@types/unist': 3.0.3
+
'@types/json-schema@7.0.15': {}
'@types/json5@0.0.29': {}
@@ -11985,6 +12092,8 @@ snapshots:
'@types/triple-beam@1.3.5': {}
+ '@types/unist@2.0.11': {}
+
'@types/unist@3.0.3': {}
'@typescript-eslint/eslint-plugin@8.61.0(@typescript-eslint/parser@8.61.0(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3))(eslint@10.3.0(jiti@2.6.1))(typescript@6.0.3)':
@@ -12133,6 +12242,8 @@ snapshots:
'@typescript-eslint/types': 8.61.0
eslint-visitor-keys: 5.0.1
+ '@ungap/structured-clone@1.3.3': {}
+
'@unrs/resolver-binding-android-arm-eabi@1.12.2':
optional: true
@@ -12900,8 +13011,14 @@ snapshots:
char-regex@1.0.2: {}
+ character-entities-html4@2.1.0: {}
+
+ character-entities-legacy@3.0.0: {}
+
character-entities@2.0.2: {}
+ character-reference-invalid@2.0.1: {}
+
chardet@2.1.1: {}
check-error@2.1.1: {}
@@ -13016,6 +13133,8 @@ snapshots:
dependencies:
delayed-stream: 1.0.0
+ comma-separated-tokens@2.0.3: {}
+
commander@10.0.1: {}
commander@2.20.3: {}
@@ -14183,6 +14302,8 @@ snapshots:
estraverse@5.3.0: {}
+ estree-util-is-identifier-name@3.0.0: {}
+
estree-walker@2.0.2: {}
estree-walker@3.0.3:
@@ -14970,6 +15091,30 @@ snapshots:
dependencies:
function-bind: 1.1.2
+ hast-util-to-jsx-runtime@2.3.6:
+ dependencies:
+ '@types/estree': 1.0.9
+ '@types/hast': 3.0.5
+ '@types/unist': 3.0.3
+ comma-separated-tokens: 2.0.3
+ devlop: 1.1.0
+ estree-util-is-identifier-name: 3.0.0
+ hast-util-whitespace: 3.0.0
+ mdast-util-mdx-expression: 2.0.1
+ mdast-util-mdx-jsx: 3.2.0
+ mdast-util-mdxjs-esm: 2.0.1
+ property-information: 7.2.0
+ space-separated-tokens: 2.0.2
+ style-to-js: 1.1.21
+ unist-util-position: 5.0.0
+ vfile-message: 4.0.2
+ transitivePeerDependencies:
+ - supports-color
+
+ hast-util-whitespace@3.0.0:
+ dependencies:
+ '@types/hast': 3.0.5
+
heap-js@2.7.1: {}
hermes-estree@0.25.1: {}
@@ -15004,6 +15149,8 @@ snapshots:
html-tags@3.3.1: {}
+ html-url-attributes@3.0.1: {}
+
http-cache-semantics@4.2.0:
optional: true
@@ -15094,6 +15241,8 @@ snapshots:
ini@4.1.1: {}
+ inline-style-parser@0.2.7: {}
+
install-artifact-from-github@1.4.0:
optional: true
@@ -15118,6 +15267,13 @@ snapshots:
ipaddr.js@1.9.1: {}
+ is-alphabetical@2.0.1: {}
+
+ is-alphanumerical@2.0.1:
+ dependencies:
+ is-alphabetical: 2.0.1
+ is-decimal: 2.0.1
+
is-array-buffer@3.0.4:
dependencies:
call-bind: 1.0.7
@@ -15202,6 +15358,8 @@ snapshots:
call-bound: 1.0.4
has-tostringtag: 1.0.2
+ is-decimal@2.0.1: {}
+
is-docker@3.0.0: {}
is-extglob@2.1.1: {}
@@ -15232,6 +15390,8 @@ snapshots:
dependencies:
is-extglob: 2.1.1
+ is-hexadecimal@2.0.1: {}
+
is-inside-container@1.0.0:
dependencies:
is-docker: 3.0.0
@@ -15996,11 +16156,62 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ mdast-util-mdx-expression@2.0.1:
+ dependencies:
+ '@types/estree-jsx': 1.0.5
+ '@types/hast': 3.0.5
+ '@types/mdast': 4.0.4
+ devlop: 1.1.0
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-mdx-jsx@3.2.0:
+ dependencies:
+ '@types/estree-jsx': 1.0.5
+ '@types/hast': 3.0.5
+ '@types/mdast': 4.0.4
+ '@types/unist': 3.0.3
+ ccount: 2.0.1
+ devlop: 1.1.0
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ parse-entities: 4.0.2
+ stringify-entities: 4.0.4
+ unist-util-stringify-position: 4.0.0
+ vfile-message: 4.0.2
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-mdxjs-esm@2.0.1:
+ dependencies:
+ '@types/estree-jsx': 1.0.5
+ '@types/hast': 3.0.5
+ '@types/mdast': 4.0.4
+ devlop: 1.1.0
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ transitivePeerDependencies:
+ - supports-color
+
mdast-util-phrasing@4.1.0:
dependencies:
'@types/mdast': 4.0.4
unist-util-is: 6.0.0
+ mdast-util-to-hast@13.2.1:
+ dependencies:
+ '@types/hast': 3.0.5
+ '@types/mdast': 4.0.4
+ '@ungap/structured-clone': 1.3.3
+ devlop: 1.1.0
+ micromark-util-sanitize-uri: 2.0.1
+ trim-lines: 3.0.1
+ unist-util-position: 5.0.0
+ unist-util-visit: 5.0.0
+ vfile: 6.0.3
+
mdast-util-to-markdown@2.1.2:
dependencies:
'@types/mdast': 4.0.4
@@ -16701,6 +16912,16 @@ snapshots:
dependencies:
callsites: 3.1.0
+ parse-entities@4.0.2:
+ dependencies:
+ '@types/unist': 2.0.11
+ character-entities-legacy: 3.0.0
+ character-reference-invalid: 2.0.1
+ decode-named-character-reference: 1.0.2
+ is-alphanumerical: 2.0.1
+ is-decimal: 2.0.1
+ is-hexadecimal: 2.0.1
+
parse-imports-exports@0.2.4:
dependencies:
parse-statements: 1.0.11
@@ -16954,6 +17175,8 @@ snapshots:
object-assign: 4.1.1
react-is: 16.13.1
+ property-information@7.2.0: {}
+
proto-list@1.2.4: {}
proto3-json-serializer@3.0.4:
@@ -17174,6 +17397,24 @@ snapshots:
react-is@17.0.2: {}
+ react-markdown@10.1.0(@types/react@19.2.7)(react@19.2.3):
+ dependencies:
+ '@types/hast': 3.0.5
+ '@types/mdast': 4.0.4
+ '@types/react': 19.2.7
+ devlop: 1.1.0
+ hast-util-to-jsx-runtime: 2.3.6
+ html-url-attributes: 3.0.1
+ mdast-util-to-hast: 13.2.1
+ react: 19.2.3
+ remark-parse: 11.0.0
+ remark-rehype: 11.1.2
+ unified: 11.0.5
+ unist-util-visit: 5.0.0
+ vfile: 6.0.3
+ transitivePeerDependencies:
+ - supports-color
+
react-refresh@0.18.0: {}
react-stately@3.45.0(react@19.2.3):
@@ -17346,6 +17587,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ remark-rehype@11.1.2:
+ dependencies:
+ '@types/hast': 3.0.5
+ '@types/mdast': 4.0.4
+ mdast-util-to-hast: 13.2.1
+ unified: 11.0.5
+ vfile: 6.0.3
+
remark-stringify@11.0.0:
dependencies:
'@types/mdast': 4.0.4
@@ -17737,6 +17986,8 @@ snapshots:
source-map@0.6.1: {}
+ space-separated-tokens@2.0.2: {}
+
spdx-correct@3.2.0:
dependencies:
spdx-expression-parse: 3.0.1
@@ -17927,6 +18178,11 @@ snapshots:
dependencies:
safe-buffer: 5.2.1
+ stringify-entities@4.0.4:
+ dependencies:
+ character-entities-html4: 2.1.0
+ character-entities-legacy: 3.0.0
+
strip-ansi@6.0.1:
dependencies:
ansi-regex: 5.0.1
@@ -17949,6 +18205,14 @@ snapshots:
stubs@3.0.0: {}
+ style-to-js@1.1.21:
+ dependencies:
+ style-to-object: 1.0.14
+
+ style-to-object@1.0.14:
+ dependencies:
+ inline-style-parser: 0.2.7
+
styled-jsx@5.1.6(@babel/core@7.29.0)(react@19.2.3):
dependencies:
client-only: 0.0.1
@@ -18256,6 +18520,8 @@ snapshots:
dependencies:
punycode: 2.3.1
+ trim-lines@3.0.1: {}
+
trim-newlines@3.0.1: {}
triple-beam@1.4.1: {}
@@ -18472,6 +18738,10 @@ snapshots:
dependencies:
'@types/unist': 3.0.3
+ unist-util-position@5.0.0:
+ dependencies:
+ '@types/unist': 3.0.3
+
unist-util-stringify-position@4.0.0:
dependencies:
'@types/unist': 3.0.3
diff --git a/tools/api-extractor/config.json b/tools/api-extractor/config.json
index ed92420e..9d220102 100644
--- a/tools/api-extractor/config.json
+++ b/tools/api-extractor/config.json
@@ -32,6 +32,7 @@
"layout",
"Link",
"List",
+ "Markdown",
"Menu",
"Modal",
"Navbar",
diff --git a/tools/public_api_guard/components/Markdown.api.md b/tools/public_api_guard/components/Markdown.api.md
new file mode 100644
index 00000000..5b92c071
--- /dev/null
+++ b/tools/public_api_guard/components/Markdown.api.md
@@ -0,0 +1,21 @@
+## API Report File for "koobiq-react"
+
+> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
+
+```ts
+
+import type { ComponentPropsWithoutRef } from 'react';
+import { ForwardRefExoticComponent } from 'react';
+import { RefAttributes } from 'react';
+
+// @public
+export const Markdown: ForwardRefExoticComponent>;
+
+// @public (undocumented)
+export interface MarkdownProps extends Omit, 'children' | 'dangerouslySetInnerHTML'> {
+ children: string;
+}
+
+// (No @packageDocumentation comment for this package)
+
+```