Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions setupTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ window.ResizeObserver = ResizeObserver;

Element.prototype.scrollIntoView = jest.fn();

jest.mock('@ably/ui/core/utils/syntax-highlighter', () => ({
jest.mock('src/utilities/syntax-highlighter', () => ({
highlightSnippet: jest.fn,
LINE_HIGHLIGHT_CLASSES: {
addition: 'code-line-addition',
Expand All @@ -23,12 +23,12 @@ jest.mock('@ably/ui/core/utils/syntax-highlighter', () => ({
splitHtmlLines: (html) => html.split('\n'),
}));

jest.mock('@ably/ui/core/Code', () => ({
jest.mock('src/components/ui/Code', () => ({
__esModule: true,
default: () => null,
}));

jest.mock('@ably/ui/core/utils/syntax-highlighter-registry', () => ({
jest.mock('src/utilities/syntax-highlighter-registry', () => ({
__esModule: true,
default: [],
}));
Expand Down
4 changes: 2 additions & 2 deletions src/components/Layout/MDXWrapper.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { WindowLocation } from '@reach/router';
import { render, screen, waitFor } from '@testing-library/react';
import { Helmet } from 'react-helmet';
import If from './mdx/If';
import CodeSnippet from '@ably/ui/core/CodeSnippet';
import CodeSnippet from 'src/components/ui/CodeSnippet';
import UserContext from 'src/contexts/user-context';
import MDXWrapper from './MDXWrapper';

Expand Down Expand Up @@ -54,7 +54,7 @@ jest.mock('src/components/Icon', () => {
});

// Mock Code component used by CodeSnippet
jest.mock('@ably/ui/core/Code', () => {
jest.mock('src/components/ui/Code', () => {
return {
__esModule: true,
default: ({ language, snippet }: any) => (
Expand Down
4 changes: 2 additions & 2 deletions src/components/Layout/MDXWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import React, {
ReactElement,
} from 'react';
import { navigate, PageProps } from 'gatsby';
import CodeSnippet from '@ably/ui/core/CodeSnippet';
import type { CodeSnippetProps, SDKType } from '@ably/ui/core/CodeSnippet';
import CodeSnippet from 'src/components/ui/CodeSnippet';
import type { CodeSnippetProps, SDKType } from 'src/components/ui/CodeSnippet';
import cn from 'src/utilities/cn';

import { getRandomChannelName } from '../../utilities/get-random-channel-name';
Expand Down
119 changes: 119 additions & 0 deletions src/components/ui/Code.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import React from 'react';
import {
highlightSnippet,
LINE_HIGHLIGHT_CLASSES,
registerDefaultLanguages,
splitHtmlLines,
} from 'src/utilities/syntax-highlighter';
import languagesRegistry from 'src/utilities/syntax-highlighter-registry';
import cn from 'src/utilities/cn';

registerDefaultLanguages(languagesRegistry);

export type LineHighlightType = 'addition' | 'removal' | 'highlight';

type CodeProps = {
language: string;
snippet: string;
textSize?: string;
padding?: string;
additionalCSS?: string;
showLines?: boolean;
lineCSS?: string;
wrap?: boolean;
lineHighlights?: Record<number, LineHighlightType>;
};

const Code = ({
language,
snippet,
textSize = 'ui-text-code',
padding = 'p-8',
additionalCSS = '',
showLines,
lineCSS,
wrap = false,
lineHighlights,
}: CodeProps) => {
const trimmedSnippet = snippet.trimEnd();
const HTMLraw = highlightSnippet(language, trimmedSnippet) ?? '';
const className = `language-${language} ${textSize}`;

const lines = trimmedSnippet.split(/\r\n|\r|\n/);
const lineCount = lines.length;

const hasHighlights = lineHighlights && Object.keys(lineHighlights).length > 0;

// Per-line rendering when highlights are present
if (hasHighlights) {
const htmlLines = splitHtmlLines(HTMLraw);

return (
<div className={cn('hljs overflow-y-auto', padding, additionalCSS)} data-id="code">
<pre
lang={language}
className={cn(
'h-full flex-1 text-p4 leading-normal',
wrap ? 'whitespace-pre-wrap break-words' : 'overflow-x-auto',
)}
>
<code className={className}>
{htmlLines.map((lineHtml, i) => {
const lineNum = i + 1;
const highlightType = lineHighlights[lineNum];
const highlightClass = highlightType ? LINE_HIGHLIGHT_CLASSES[highlightType] : undefined;

return (
<span key={i} className={cn('flex min-w-full pl-2', highlightClass)}>
{showLines && (
<span
className={cn(
'mr-4 font-mono text-right text-neutral-800 select-none shrink-0 inline-block leading-normal',
lineCSS,
)}
style={{ minWidth: `${String(lineCount).length}ch` }}
>
{lineNum}
</span>
)}
<span
className="flex-1 !leading-normal"
dangerouslySetInnerHTML={{
__html: lineHtml || '&nbsp;',
}}
/>
</span>
);
})}
</code>
</pre>
</div>
);
}

// Default: single-block rendering (no highlights)
return (
<div className={cn('hljs overflow-y-auto flex', padding, additionalCSS)} data-id="code">
{showLines ? (
<div className="text-p4 leading-normal pt-px">
{[...Array(lineCount)].map((_, i) => (
<p className={cn('mr-4 font-mono text-right text-neutral-800', lineCSS)} key={i}>
{i + 1}
</p>
))}
</div>
) : null}
<pre
lang={language}
className={cn(
'h-full flex-1 text-p4 leading-normal',
wrap ? 'whitespace-pre-wrap break-words' : 'overflow-x-auto',
)}
>
<code className={className} dangerouslySetInnerHTML={{ __html: HTMLraw }} />
</pre>
</div>
);
};

export default Code;
Loading