diff --git a/packages/x/components/code-highlighter/CodeHighlighter.tsx b/packages/x/components/code-highlighter/CodeHighlighter.tsx index 4438147ac7..448a92b0d2 100644 --- a/packages/x/components/code-highlighter/CodeHighlighter.tsx +++ b/packages/x/components/code-highlighter/CodeHighlighter.tsx @@ -71,6 +71,9 @@ const CodeHighlighter = React.forwardRef(( style = {}, highlightProps, prismLightMode = true, + showLineNumbers = false, + wrapLongLines = false, + showCopyButton = true, ...restProps } = props; @@ -141,7 +144,7 @@ const CodeHighlighter = React.forwardRef(( > {lang} - + {showCopyButton && } ); } @@ -160,6 +163,8 @@ const CodeHighlighter = React.forwardRef(( { const { container } = render( {`console.log("test");`} , @@ -512,6 +512,168 @@ describe('CodeHighlighter', () => { }); }); + describe('showLineNumbers', () => { + it('should render line numbers when showLineNumbers is true', async () => { + const { container } = render( + + {`console.log("test");`} + , + ); + await waitFor(() => { + expect(container.querySelector('pre')).toBeInTheDocument(); + }); + // react-syntax-highlighter renders line numbers with class "react-syntax-highlighter-line-number" + expect( + container.querySelector('.react-syntax-highlighter-line-number'), + ).toBeInTheDocument(); + }); + + it('should not render line numbers by default', async () => { + const { container } = render( + {`console.log("test");`}, + ); + await waitFor(() => { + expect(container.querySelector('pre')).toBeInTheDocument(); + }); + expect( + container.querySelector('.react-syntax-highlighter-line-number'), + ).not.toBeInTheDocument(); + }); + + it('should not render line numbers when showLineNumbers is false', async () => { + const { container } = render( + + {`console.log("test");`} + , + ); + await waitFor(() => { + expect(container.querySelector('pre')).toBeInTheDocument(); + }); + expect( + container.querySelector('.react-syntax-highlighter-line-number'), + ).not.toBeInTheDocument(); + }); + }); + + describe('wrapLongLines', () => { + it('should apply wrapLongLines style when enabled', async () => { + const { container } = render( + + {`console.log("test");`} + , + ); + await waitFor(() => { + expect(container.querySelector('pre')).toBeInTheDocument(); + }); + // react-syntax-highlighter sets whiteSpace: 'pre-wrap' on the code element + const codeElement = container.querySelector('code'); + expect(codeElement).toBeInTheDocument(); + expect(codeElement?.style.whiteSpace).toBe('pre-wrap'); + }); + + it('should not apply wrapLongLines style by default', async () => { + const { container } = render( + {`console.log("test");`}, + ); + await waitFor(() => { + expect(container.querySelector('pre')).toBeInTheDocument(); + }); + const codeElement = container.querySelector('code'); + expect(codeElement).toBeInTheDocument(); + expect(codeElement?.style.whiteSpace).not.toBe('pre-wrap'); + }); + }); + + describe('showCopyButton', () => { + it('should render copy button by default', async () => { + const { container } = render( + {`console.log("test");`}, + ); + await waitFor(() => { + expect(container.querySelector('.ant-codeHighlighter-header')).toBeInTheDocument(); + }); + // The default header should contain a copy button (Actions.Copy renders a button) + const buttons = container.querySelectorAll('.ant-codeHighlighter-header button'); + expect(buttons.length).toBeGreaterThan(0); + }); + + it('should not render copy button when showCopyButton is false', async () => { + const { container } = render( + + {`console.log("test");`} + , + ); + await waitFor(() => { + expect(container.querySelector('.ant-codeHighlighter-header')).toBeInTheDocument(); + }); + // Header should still show the language name + expect(container.querySelector('.ant-codeHighlighter-header')?.textContent).toContain( + 'javascript', + ); + // But no copy button (no buttons in header) + const buttons = container.querySelectorAll('.ant-codeHighlighter-header button'); + expect(buttons.length).toBe(0); + }); + + it('should render copy button when showCopyButton is true', async () => { + const { container } = render( + + {`console.log("test");`} + , + ); + await waitFor(() => { + expect(container.querySelector('.ant-codeHighlighter-header')).toBeInTheDocument(); + }); + const buttons = container.querySelectorAll('.ant-codeHighlighter-header button'); + expect(buttons.length).toBeGreaterThan(0); + }); + + it('should not affect custom header', async () => { + const { container } = render( + custom} + > + {`console.log("test");`} + , + ); + await waitFor(() => { + expect(container.querySelector('.myCustomHeader')).toBeInTheDocument(); + }); + // Custom header should be rendered as-is + expect(container.querySelector('.myCustomHeader')?.textContent).toContain('custom'); + }); + }); + + describe('combined props', () => { + it('should work with showLineNumbers, wrapLongLines, and showCopyButton together', async () => { + const { container } = render( + + {`console.log("test");`} + , + ); + await waitFor(() => { + expect(container.querySelector('pre')).toBeInTheDocument(); + }); + // Line numbers should be present + expect( + container.querySelector('.react-syntax-highlighter-line-number'), + ).toBeInTheDocument(); + // Wrap should be applied + const codeElement = container.querySelector('code'); + expect(codeElement?.style.whiteSpace).toBe('pre-wrap'); + // No copy button + const buttons = container.querySelectorAll('.ant-codeHighlighter-header button'); + expect(buttons.length).toBe(0); + }); + }); + describe('displayName', () => { it('should have displayName in development', () => { const originalEnv = process.env.NODE_ENV; diff --git a/packages/x/components/code-highlighter/demo/configurations.tsx b/packages/x/components/code-highlighter/demo/configurations.tsx new file mode 100644 index 0000000000..60fe91810c --- /dev/null +++ b/packages/x/components/code-highlighter/demo/configurations.tsx @@ -0,0 +1,42 @@ +import { CodeHighlighter } from '@ant-design/x'; +import React from 'react'; + +const App: React.FC = () => { + const code = `function fibonacci(n) { + if (n <= 1) return n; + let a = 0, b = 1; + for (let i = 2; i <= n; i++) { + [a, b] = [b, a + b]; + } + return b; +} + +const result = fibonacci(10); +console.log(result); // 55`; + + return ( +
+

Show Line Number

+ + {code} + + +

Wrap Long Lines

+ + {`const config = { apiKey: "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", endpoint: "https://api.example.com/v1/chat/completions", model: "gpt-4" };`} + + +

Hide Copy Button

+ + {code} + + +

All Combined

+ + {code} + +
+ ); +}; + +export default App; \ No newline at end of file diff --git a/packages/x/components/code-highlighter/index.en-US.md b/packages/x/components/code-highlighter/index.en-US.md index f48ef8f27e..8098c01634 100644 --- a/packages/x/components/code-highlighter/index.en-US.md +++ b/packages/x/components/code-highlighter/index.en-US.md @@ -22,6 +22,7 @@ The CodeHighlighter component is used in scenarios where you need to display cod Basic Custom Header +Configurations With XMarkdown ## API @@ -39,6 +40,9 @@ For common properties, refer to: [Common Properties](/docs/react/common-props). | classNames | Style class names | `string` | - | | highlightProps | Code highlighting configuration | [`highlightProps`](https://github.com/react-syntax-highlighter/react-syntax-highlighter?tab=readme-ov-file#props) | - | | prismLightMode | Whether to use Prism light mode to automatically load language support based on lang prop for smaller bundle size | `boolean` | `true` | +| showLineNumbers | Whether to show line numbers | `boolean` | `false` | +| wrapLongLines | Whether to wrap long lines | `boolean` | `false` | +| showCopyButton | Whether to show copy button (only effective with default header) | `boolean` | `true` | ### CodeHighlighterRef diff --git a/packages/x/components/code-highlighter/index.zh-CN.md b/packages/x/components/code-highlighter/index.zh-CN.md index 5c138cc9d9..470d5c1c08 100644 --- a/packages/x/components/code-highlighter/index.zh-CN.md +++ b/packages/x/components/code-highlighter/index.zh-CN.md @@ -23,6 +23,7 @@ CodeHighlighter 组件用于需要展示带有语法高亮的代码片段的场 基本 自定义 Header +配置项 配合 XMarkdown ## API @@ -38,6 +39,9 @@ CodeHighlighter 组件用于需要展示带有语法高亮的代码片段的场 | header | 头部内容,为 `false` 时不显示头部 | `React.ReactNode \| (() => React.ReactNode \| false) \| false` | - | | highlightProps | 代码高亮配置,透传给 react-syntax-highlighter | [`SyntaxHighlighterProps`](https://github.com/react-syntax-highlighter/react-syntax-highlighter?tab=readme-ov-file#props) | - | | prismLightMode | 是否使用 Prism 轻量模式,根据 `lang` 自动按需加载语言支持以减少打包体积 | `boolean` | `true` | +| showLineNumbers | 是否显示行号 | `boolean` | `false` | +| wrapLongLines | 是否自动换行(长行换行) | `boolean` | `false` | +| showCopyButton | 是否显示复制按钮(仅在默认 header 下生效) | `boolean` | `true` | ### CodeHighlighterRef diff --git a/packages/x/components/code-highlighter/interface.ts b/packages/x/components/code-highlighter/interface.ts index fc0fb7d685..e0eb49bb68 100644 --- a/packages/x/components/code-highlighter/interface.ts +++ b/packages/x/components/code-highlighter/interface.ts @@ -61,4 +61,22 @@ export interface CodeHighlighterProps * ``` */ prismLightMode?: boolean; + /** + * @desc 是否显示行号 + * @descEN Whether to show line numbers + * @default false + */ + showLineNumbers?: boolean; + /** + * @desc 是否自动换行(长行换行) + * @descEN Whether to wrap long lines + * @default false + */ + wrapLongLines?: boolean; + /** + * @desc 是否显示复制按钮(仅在默认 header 下生效) + * @descEN Whether to show copy button (only effective with default header) + * @default true + */ + showCopyButton?: boolean; } diff --git a/packages/x/components/suggestion/__tests__/__snapshots__/demo.test.ts.snap b/packages/x/components/suggestion/__tests__/__snapshots__/demo.test.ts.snap index 35d80e304c..1071dcf990 100644 --- a/packages/x/components/suggestion/__tests__/__snapshots__/demo.test.ts.snap +++ b/packages/x/components/suggestion/__tests__/__snapshots__/demo.test.ts.snap @@ -112,6 +112,62 @@ exports[`renders components/suggestion/demo/block.tsx correctly 1`] = ` `; +exports[`renders components/suggestion/demo/scroll.tsx correctly 1`] = ` +
+
+
+