feat: Add codehighlight for the Code'

This commit is contained in:
deytenit committed 2026-03-06 23:06:30 +03:00
1 parent 6784a28701
commit 56d0d65971
11 files changed
+422 -14

No files matched your search

+1
View File
@@ -47,6 +47,7 @@
"react-aria": "^3.45.0",
"react-icons": "^5.5.0",
"react-stately": "^3.44.0",
"shiki": "^4.0.1",
"tw-animate-css": "^1.2.8"
},
"devDependencies": {
@@ -90,6 +90,24 @@ export const LongWithScroll: StoryObj<typeof CodeBlock> = {
},
};
export const ShikiHighlight: StoryObj<typeof CodeBlock> = {
render: (args) => (
<CodeBlock {...args}>
<Code>
{`import React from 'react';
export const MyComponent = () => {
return <div>Hello World</div>;
};`}
</Code>
</CodeBlock>
),
args: {
label: 'ShikiHighlight.tsx',
language: 'tsx',
},
};
export const PlainText: StoryObj<typeof CodeBlock> = {
render: (args) => (
<CodeBlock {...args}>
+23 -3
View File
@@ -1,9 +1,17 @@
import { render, screen, fireEvent, act } from '@testing-library/react';
import React from 'react';
import { vi, describe, it, expect, beforeEach } from 'vitest';
import { Code } from './Code';
import { CodeBlock } from './CodeBlock/CodeBlock';
// Mock shiki
vi.mock('shiki', () => ({
createHighlighter: vi.fn().mockResolvedValue({
codeToHtml: vi.fn().mockReturnValue('<pre><code><span class="line">highlighted code</span></code></pre>'),
}),
}));
// Mock clipboard API
const mockWriteText = vi.fn().mockImplementation(() => Promise.resolve());
Object.assign(navigator, {
@@ -15,6 +23,7 @@ Object.assign(navigator, {
describe('Code Component', () => {
beforeEach(() => {
mockWriteText.mockClear();
vi.clearAllMocks();
});
it('renders inline code correctly', () => {
@@ -46,7 +55,6 @@ describe('Code Component', () => {
await act(async () => {
fireEvent.click(codeElement);
await Promise.resolve();
});
expect(mockWriteText).toHaveBeenCalledWith('copied!');
@@ -64,7 +72,6 @@ describe('Code Component', () => {
const copyButton = screen.getByLabelText('Copy code');
await act(async () => {
fireEvent.click(copyButton);
await Promise.resolve();
});
expect(mockWriteText).toHaveBeenCalledWith('block copy');
@@ -76,9 +83,22 @@ describe('Code Component', () => {
const codeElement = screen.getByText('extracted');
await act(async () => {
fireEvent.click(codeElement);
await Promise.resolve();
});
expect(mockWriteText).toHaveBeenCalledWith('extracted');
});
it('uses shiki for highlighting when language is provided in block mode', async () => {
await act(async () => {
render(
<CodeBlock language="typescript">
<Code>const x: number = 1;</Code>
</CodeBlock>
);
});
// Wait for shiki async highlighting
const highlighted = await screen.findByText('highlighted code');
expect(highlighted).toBeInTheDocument();
});
});
+63 -1
View File
@@ -3,8 +3,12 @@ import React, { memo, useCallback, useEffect, useRef, useState } from 'react';
import { usePress } from 'react-aria';
import { useCodeContext } from './contexts/CodeContext/CodeContext';
import { codeHighlighter } from './lib/highlighter/highlighter';
import type { ClassNameProps, TestIdProps } from '@ermnvldmr/stl';
import type { CodeLanguage } from './lib/highlighter/highlighter';
export type { CodeLanguage };
/**
* Props for the Code component.
@@ -18,6 +22,12 @@ export interface CodeProps extends ClassNameProps, TestIdProps {
* If omitted in inline mode, the component will attempt to extract text from `children`.
*/
copyValue?: string;
/**
* The language of the code. Only used when rendered inside a CodeBlock.
* If provided, shiki will be used for syntax highlighting.
*/
language?: CodeLanguage;
}
/**
@@ -30,12 +40,16 @@ export interface CodeProps extends ClassNameProps, TestIdProps {
export const Code = memo(function Code({
children,
copyValue,
language: propsLanguage,
className,
'data-testid': testId,
}: CodeProps) {
const context = useCodeContext();
const [isCopied, setIsCopied] = useState(false);
const codeRef = useRef<HTMLElement>(null);
const [highlightedHtml, setHighlightedHtml] = useState<string | null>(null);
const language = propsLanguage ?? context?.language;
const handleCopy = useCallback(async () => {
try {
@@ -67,6 +81,22 @@ export const Code = memo(function Code({
}
}, [isCopied]);
useEffect(() => {
if (context?.isInCodeBlock && language && typeof children === 'string') {
const highlight = async () => {
try {
const html = await codeHighlighter.highlight(children, language);
setHighlightedHtml(html);
} catch (err) {
console.error('Failed to highlight code:', err);
}
};
void highlight();
} else {
setHighlightedHtml(null);
}
}, [children, language, context?.isInCodeBlock]);
const { pressProps } = usePress({
onPress: onCopyPress,
});
@@ -74,7 +104,39 @@ export const Code = memo(function Code({
if (context?.isInCodeBlock) {
const { showLineNumbers } = context;
let content = children;
let content: React.ReactNode = children;
if (highlightedHtml) {
return (
<div
ref={codeRef as any}
className={cn(
'block min-w-full font-mono text-sm',
'[&>pre]:!bg-transparent [&>pre]:!p-0 [&>pre]:!m-0',
showLineNumbers && [
'[&>pre>code]:[counter-reset:line]',
'[&>pre>code>.line]:relative [&>pre>code>.line]:pl-10',
'[&>pre>code>.line]:[counter-increment:line]',
'[&>pre>code>.line]:before:content-[counter(line)]',
'[&>pre>code>.line]:before:absolute [&>pre>code>.line]:before:left-0 [&>pre>code>.line]:before:top-0',
'[&>pre>code>.line]:before:inline-block',
'[&>pre>code>.line]:before:w-8',
'[&>pre>code>.line]:before:text-right',
'[&>pre>code>.line]:before:text-[var(--rb-muted-text)]/50',
'[&>pre>code>.line]:before:select-none',
'[&>pre>code>.line]:before:border-r',
'[&>pre>code>.line]:before:border-[var(--rb-outline)]/10',
'[&>pre>code>.line]:before:pr-3',
'[&>pre>code>.line]:before:font-mono [&>pre>code>.line]:before:text-[10px]',
],
className
)}
dangerouslySetInnerHTML={{ __html: highlightedHtml }}
data-testid={testId}
/>
);
}
if (typeof children === 'string') {
content = children
.trimEnd()
@@ -7,6 +7,7 @@ import { Container } from '../../Container/Container';
import { CodeContext, type CodeContextValue } from '../contexts/CodeContext/CodeContext';
import type { ClassNameProps, TestIdProps } from '@ermnvldmr/stl';
import type { CodeLanguage } from '../lib/highlighter/highlighter';
/**
* Props for the CodeBlock component.
@@ -21,6 +22,11 @@ export interface CodeBlockProps extends ClassNameProps, TestIdProps {
*/
label?: string;
/**
* The language of the code block.
*/
language?: CodeLanguage;
/**
* Whether to render line number gutters.
* Requires children (Code) to be rendered as block-level elements.
@@ -49,6 +55,7 @@ export interface CodeBlockProps extends ClassNameProps, TestIdProps {
export const CodeBlock = memo(function CodeBlock({
children,
label,
language,
showLineNumbers = true,
copyValue,
maxHeight,
@@ -92,8 +99,9 @@ export const CodeBlock = memo(function CodeBlock({
() => ({
isInCodeBlock: true,
showLineNumbers,
language,
}),
[showLineNumbers]
[showLineNumbers, language]
);
return (
@@ -1,5 +1,7 @@
import { createContext, useContext } from 'react';
import type { CodeLanguage } from '../../lib/highlighter/highlighter';
/**
* Value for the Code context.
*/
@@ -8,6 +10,8 @@ export interface CodeContextValue {
isInCodeBlock: boolean;
/** Whether to show line numbers in the block. */
showLineNumbers?: boolean;
/** The language of the code in the block. */
language?: CodeLanguage;
}
/**
@@ -0,0 +1,113 @@
import { createHighlighter } from 'shiki';
import type { Highlighter } from 'shiki';
/**
* Supported languages for syntax highlighting.
*/
export type CodeLanguage =
| 'typescript'
| 'javascript'
| 'tsx'
| 'jsx'
| 'bash'
| 'shell'
| 'json'
| 'css'
| 'html'
| 'markdown'
| 'yaml'
| 'dockerfile'
| 'toml'
| 'rust'
| 'go'
| 'python';
export const SUPPORTED_LANGUAGES: CodeLanguage[] = [
'typescript',
'javascript',
'tsx',
'jsx',
'bash',
'shell',
'json',
'css',
'html',
'markdown',
'yaml',
'dockerfile',
'toml',
'rust',
'go',
'python',
];
/**
* A singleton highlighter service for code blocks.
*/
class CodeHighlighter {
private static instance: CodeHighlighter;
private highlighter: Highlighter | null = null;
private initializingPromise: Promise<Highlighter> | null = null;
private constructor() {}
/**
* Gets the singleton instance of the highlighter.
*/
public static getInstance(): CodeHighlighter {
if (!CodeHighlighter.instance) {
CodeHighlighter.instance = new CodeHighlighter();
}
return CodeHighlighter.instance;
}
/**
* Initializes the highlighter if it hasn't been already.
*/
private async getHighlighter(): Promise<Highlighter> {
if (this.highlighter) {
return this.highlighter;
}
if (this.initializingPromise) {
return this.initializingPromise;
}
this.initializingPromise = createHighlighter({
themes: ['github-dark', 'github-light'],
langs: SUPPORTED_LANGUAGES,
}).then((highlighter) => {
this.highlighter = highlighter;
this.initializingPromise = null;
return highlighter;
});
return this.initializingPromise;
}
/**
* Highlights the given code.
*
* @param code - The raw source code to highlight
* @param lang - The language of the code
* @returns A promise resolving to the highlighted HTML
*/
public async highlight(code: string, lang: CodeLanguage): Promise<string> {
try {
const highlighter = await this.getHighlighter();
return highlighter.codeToHtml(code, {
lang,
themes: {
light: 'github-light',
dark: 'github-dark',
},
});
} catch (err) {
console.error('Failed to highlight code:', err);
throw err;
}
}
}
export const codeHighlighter = CodeHighlighter.getInstance();
@@ -1,12 +1,25 @@
import React, { memo } from 'react';
import React, { memo, useMemo } from 'react';
import { Code } from '../../Code/Code';
import type { CodeProps } from '../../Code/Code';
import type { CodeLanguage } from '../../Code/lib/highlighter/highlighter';
/**
* A Markdown code component for inline snippets.
*/
export const MarkdownCode = memo(function MarkdownCode(props: CodeProps) {
return <Code {...props} />;
const { className, ...rest } = props;
const language = useMemo(() => {
if (className) {
const match = /language-(\w+)/.exec(className);
if (match) {
return match[1] as CodeLanguage;
}
}
return undefined;
}, [className]);
return <Code language={language} {...rest} />;
});
@@ -1,8 +1,9 @@
import React, { memo } from 'react';
import React, { memo, useMemo } from 'react';
import { CodeBlock } from '../../Code/CodeBlock/CodeBlock';
import type { CodeBlockProps } from '../../Code/CodeBlock/CodeBlock';
import type { CodeLanguage } from '../../Code/lib/highlighter/highlighter';
/**
* A Markdown pre component for code blocks.
@@ -10,5 +11,26 @@ import type { CodeBlockProps } from '../../Code/CodeBlock/CodeBlock';
* Wraps content in the design system CodeBlock.
*/
export const MarkdownPre = memo(function MarkdownPre(props: CodeBlockProps) {
return <CodeBlock {...props} />;
const { children, ...rest } = props;
// MDX often passes a <code> element as the only child of <pre>.
// That <code> element usually has the language class (e.g., "language-typescript").
const language = useMemo(() => {
if (React.isValidElement<{ className?: string }>(children) && children.props.className) {
const className = children.props.className;
const match = /language-(\w+)/.exec(className);
if (match) {
// We cast here because we know it comes from markdown,
// shiki will handle it or fallback to plaintext if unknown at runtime.
return match[1] as CodeLanguage;
}
}
return undefined;
}, [children]);
return (
<CodeBlock language={language} {...rest}>
{children}
</CodeBlock>
);
});
+4
View File
@@ -285,4 +285,8 @@
);
background-size: 200% 100%;
}
.dark .shiki span {
color: var(--shiki-dark) !important;
}
}
+148 -5
View File
@@ -198,7 +198,7 @@ importers:
version: 7.0.1
storybook-react-rsbuild:
specifier: ^1.0.3
version: 1.0.3(@rsbuild/core@1.7.3)(@storybook/test@8.6.15(storybook@8.6.15(prettier@3.5.3)))(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.56.0)(storybook@8.6.15(prettier@3.5.3))(typescript@5.8.3)(webpack@5.105.1)
version: 1.0.3(@rsbuild/core@1.7.3)(@storybook/test@8.6.15(storybook@8.6.15(prettier@3.5.3)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.56.0)(storybook@8.6.15(prettier@3.5.3))(typescript@5.8.3)(webpack@5.105.1)
typescript:
specifier: ^5.8.3
version: 5.8.3
@@ -244,6 +244,9 @@ importers:
react-stately:
specifier: ^3.44.0
version: 3.44.0(react@19.2.3)
shiki:
specifier: ^4.0.1
version: 4.0.1
tailwindcss:
specifier: ^4.1.4
version: 4.1.18
@@ -295,7 +298,7 @@ importers:
version: 4.1.18
'@tailwindcss/typography':
specifier: ^0.5.19
version: 0.5.19(tailwindcss@4.1.18)
version: 0.5.19
'@testing-library/jest-dom':
specifier: ^6.9.1
version: 6.9.1
@@ -3057,6 +3060,37 @@ packages:
'@rushstack/ts-command-line@5.1.5':
resolution: {integrity: sha512-YmrFTFUdHXblYSa+Xc9OO9FsL/XFcckZy0ycQ6q7VSBsVs5P0uD9vcges5Q9vctGlVdu27w+Ct6IuJ458V0cTQ==}
'@shikijs/core@4.0.1':
resolution: {integrity: sha512-vWvqi9JNgz1dRL9Nvog5wtx7RuNkf7MEPl2mU/cyUUxJeH1CAr3t+81h8zO8zs7DK6cKLMoU9TvukWIDjP4Lzg==}
engines: {node: '>=20'}
'@shikijs/engine-javascript@4.0.1':
resolution: {integrity: sha512-DJK9NiwtGYqMuKCRO4Ip0FKNDQpmaiS+K5bFjJ7DWFn4zHueDWgaUG8kAofkrnXF6zPPYYQY7J5FYVW9MbZyBg==}
engines: {node: '>=20'}
'@shikijs/engine-oniguruma@4.0.1':
resolution: {integrity: sha512-oCWdCTDch3J8Kc0OZJ98KuUPC02O1VqIE3W/e2uvrHqTxYRR21RGEJMtchrgrxhsoJJCzmIciKsqG+q/yD+Cxg==}
engines: {node: '>=20'}
'@shikijs/langs@4.0.1':
resolution: {integrity: sha512-v/mluaybWdnGJR4GqAR6zh8qAZohW9k+cGYT28Y7M8+jLbC0l4yG085O1A+WkseHTn+awd+P3UBymb2+MXFc8w==}
engines: {node: '>=20'}
'@shikijs/primitive@4.0.1':
resolution: {integrity: sha512-ns0hHZc5eWZuvuIEJz2pTx3Qecz0aRVYumVQJ8JgWY2tq/dH8WxdcVM49Fc2NsHEILNIT6vfdW9MF26RANWiTA==}
engines: {node: '>=20'}
'@shikijs/themes@4.0.1':
resolution: {integrity: sha512-FW41C/D6j/yKQkzVdjrRPiJCtgeDaYRJFEyCKFCINuRJRj9WcmubhP4KQHPZ4+9eT87jruSrYPyoblNRyDFzvA==}
engines: {node: '>=20'}
'@shikijs/types@4.0.1':
resolution: {integrity: sha512-EaygPEn57+jJ76mw+nTLvIpJMAcMPokFbrF8lufsZP7Ukk+ToJYEcswN1G0e49nUZAq7aCQtoeW219A8HK1ZOw==}
engines: {node: '>=20'}
'@shikijs/vscode-textmate@10.0.2':
resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==}
'@sideway/address@4.1.5':
resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==}
@@ -5121,6 +5155,9 @@ packages:
hast-util-to-estree@3.1.3:
resolution: {integrity: sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==}
hast-util-to-html@9.0.5:
resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==}
hast-util-to-jsx-runtime@2.3.6:
resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==}
@@ -5154,6 +5191,9 @@ packages:
engines: {node: ^14.13.1 || >=16.0.0}
hasBin: true
html-void-elements@3.0.0:
resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
htmlparser2@3.10.1:
resolution: {integrity: sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==}
@@ -6166,6 +6206,12 @@ packages:
resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
engines: {node: '>=6'}
oniguruma-parser@0.12.1:
resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==}
oniguruma-to-es@4.3.4:
resolution: {integrity: sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==}
open@8.4.2:
resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==}
engines: {node: '>=12'}
@@ -6569,6 +6615,15 @@ packages:
resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
engines: {node: '>= 0.4'}
regex-recursion@6.0.2:
resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==}
regex-utilities@2.3.0:
resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==}
regex@6.1.0:
resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==}
regexp.prototype.flags@1.5.4:
resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
engines: {node: '>= 0.4'}
@@ -6779,6 +6834,10 @@ packages:
resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==}
engines: {node: '>= 0.4'}
shiki@4.0.1:
resolution: {integrity: sha512-EkAEhDTN5WhpoQFXFw79OHIrSAfHhlImeCdSyg4u4XvrpxKEmdo/9x/HWSowujAnUrFsGOwWiE58a6GVentMnQ==}
engines: {node: '>=20'}
side-channel-list@1.0.0:
resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==}
engines: {node: '>= 0.4'}
@@ -10716,6 +10775,46 @@ snapshots:
- '@types/node'
optional: true
'@shikijs/core@4.0.1':
dependencies:
'@shikijs/primitive': 4.0.1
'@shikijs/types': 4.0.1
'@shikijs/vscode-textmate': 10.0.2
'@types/hast': 3.0.4
hast-util-to-html: 9.0.5
'@shikijs/engine-javascript@4.0.1':
dependencies:
'@shikijs/types': 4.0.1
'@shikijs/vscode-textmate': 10.0.2
oniguruma-to-es: 4.3.4
'@shikijs/engine-oniguruma@4.0.1':
dependencies:
'@shikijs/types': 4.0.1
'@shikijs/vscode-textmate': 10.0.2
'@shikijs/langs@4.0.1':
dependencies:
'@shikijs/types': 4.0.1
'@shikijs/primitive@4.0.1':
dependencies:
'@shikijs/types': 4.0.1
'@shikijs/vscode-textmate': 10.0.2
'@types/hast': 3.0.4
'@shikijs/themes@4.0.1':
dependencies:
'@shikijs/types': 4.0.1
'@shikijs/types@4.0.1':
dependencies:
'@shikijs/vscode-textmate': 10.0.2
'@types/hast': 3.0.4
'@shikijs/vscode-textmate@10.0.2': {}
'@sideway/address@4.1.5':
dependencies:
'@hapi/hoek': 9.3.0
@@ -11206,10 +11305,9 @@ snapshots:
postcss: 8.5.6
tailwindcss: 4.1.18
'@tailwindcss/typography@0.5.19(tailwindcss@4.1.18)':
'@tailwindcss/typography@0.5.19':
dependencies:
postcss-selector-parser: 6.0.10
tailwindcss: 4.1.18
'@testing-library/dom@10.4.0':
dependencies:
@@ -13226,6 +13324,20 @@ snapshots:
transitivePeerDependencies:
- supports-color
hast-util-to-html@9.0.5:
dependencies:
'@types/hast': 3.0.4
'@types/unist': 3.0.3
ccount: 2.0.1
comma-separated-tokens: 2.0.3
hast-util-whitespace: 3.0.0
html-void-elements: 3.0.0
mdast-util-to-hast: 13.2.1
property-information: 7.1.0
space-separated-tokens: 2.0.2
stringify-entities: 4.0.4
zwitch: 2.0.4
hast-util-to-jsx-runtime@2.3.6:
dependencies:
'@types/estree': 1.0.8
@@ -13280,6 +13392,8 @@ snapshots:
relateurl: 0.2.7
terser: 5.46.0
html-void-elements@3.0.0: {}
htmlparser2@3.10.1:
dependencies:
domelementtype: 1.3.1
@@ -14753,6 +14867,14 @@ snapshots:
dependencies:
mimic-fn: 2.1.0
oniguruma-parser@0.12.1: {}
oniguruma-to-es@4.3.4:
dependencies:
oniguruma-parser: 0.12.1
regex: 6.1.0
regex-recursion: 6.0.2
open@8.4.2:
dependencies:
define-lazy-prop: 2.0.0
@@ -15197,6 +15319,16 @@ snapshots:
get-proto: 1.0.1
which-builtin-type: 1.2.1
regex-recursion@6.0.2:
dependencies:
regex-utilities: 2.3.0
regex-utilities@2.3.0: {}
regex@6.1.0:
dependencies:
regex-utilities: 2.3.0
regexp.prototype.flags@1.5.4:
dependencies:
call-bind: 1.0.8
@@ -15458,6 +15590,17 @@ snapshots:
shell-quote@1.8.3: {}
shiki@4.0.1:
dependencies:
'@shikijs/core': 4.0.1
'@shikijs/engine-javascript': 4.0.1
'@shikijs/engine-oniguruma': 4.0.1
'@shikijs/langs': 4.0.1
'@shikijs/themes': 4.0.1
'@shikijs/types': 4.0.1
'@shikijs/vscode-textmate': 10.0.2
'@types/hast': 3.0.4
side-channel-list@1.0.0:
dependencies:
es-errors: 1.3.0
@@ -15653,7 +15796,7 @@ snapshots:
- tslib
- webpack
storybook-react-rsbuild@1.0.3(@rsbuild/core@1.7.3)(@storybook/test@8.6.15(storybook@8.6.15(prettier@3.5.3)))(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.56.0)(storybook@8.6.15(prettier@3.5.3))(typescript@5.8.3)(webpack@5.105.1):
storybook-react-rsbuild@1.0.3(@rsbuild/core@1.7.3)(@storybook/test@8.6.15(storybook@8.6.15(prettier@3.5.3)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.56.0)(storybook@8.6.15(prettier@3.5.3))(typescript@5.8.3)(webpack@5.105.1):
dependencies:
'@rollup/pluginutils': 5.3.0(rollup@4.56.0)
'@rsbuild/core': 1.7.3