Code snippet
Shows code or a command, such as an install step or an example, on one or more lines. Long lines scroll horizontally. For code within running text, use Inline code.
pnpm add @earendil-works/elanor-reactCode
import {CodeSnippet} from '@earendil-works/elanor-react';
export default function CodeSnippetDemo() {
return <CodeSnippet>pnpm add @earendil-works/elanor-react</CodeSnippet>;
} Usage
import {CodeSnippet} from '@earendil-works/elanor-react';
<CodeSnippet>pnpm install</CodeSnippet>;
Code uses semantic.typography.code.primary in semantic.color.text.code on the semantic.color.background.fill.secondary fill, aligned to the start.
Copy action
Pass a Copy button to actions. With a mouse, the action shows on hover, on keyboard focus, and while it reports a copy; on touch screens it always shows and grows to a 48px target.
pnpm add @earendil-works/elanor-reactCode
import {CodeSnippet, CopyButton} from '@earendil-works/elanor-react';
const command = 'pnpm add @earendil-works/elanor-react';
export default function CodeSnippetCopy() {
return (
<CodeSnippet actions={<CopyButton value={command} aria-label="Copy command" />}>
{command}
</CodeSnippet>
);
} Multiple lines
Code keeps its line breaks. Actions stay on the first line.
import {Button} from '@earendil-works/elanor-react';
export function Save() {
return <Button>Save changes</Button>;
}Code
import {CodeSnippet, CopyButton} from '@earendil-works/elanor-react';
const code = `import {Button} from '@earendil-works/elanor-react';
export function Save() {
return <Button>Save changes</Button>;
}`;
export default function CodeSnippetMultiLine() {
return (
<CodeSnippet actions={<CopyButton value={code} aria-label="Copy code" />}>{code}</CodeSnippet>
);
} Syntax highlighting
Pass highlighted lines to HighlightedCode as the snippet’s content. Elanor includes no highlighter: use Shiki or Highlights with a theme from @earendil-works/elanor-code-themes. The themes use the code syntax tokens, so colors follow the brand and the color scheme. Leave commands, keys and codes plain.
On a server-rendered page, highlight on the server. The page then shows the colors without JavaScript:
import elanorTheme from '@earendil-works/elanor-code-themes/shiki';
import {CodeSnippet, HighlightedCode} from '@earendil-works/elanor-react';
import {codeToTokensBase} from 'shiki';
const lines = await codeToTokensBase(code, {lang: 'tsx', theme: elanorTheme});
<CodeSnippet>
<HighlightedCode lines={lines} />
</CodeSnippet>;
In the browser, useHighlightedCode(code, language, highlighter) highlights after the component mounts. The snippet shows plain code until then, and for languages the highlighter doesn’t support. This demo’s highlighter uses Shiki and loads each language on first use:
import {Button} from '@earendil-works/elanor-react';
export function Save({count}: {count: number}) {
// Disabled until something changes.
return <Button disabled={count === 0}>Save {count} changes</Button>;
}Code
import {
CodeSnippet,
CopyButton,
HighlightedCode,
useHighlightedCode,
} from '@earendil-works/elanor-react';
import {highlighter} from './highlighter.ts';
const code = `import {Button} from '@earendil-works/elanor-react';
export function Save({count}: {count: number}) {
// Disabled until something changes.
return <Button disabled={count === 0}>Save {count} changes</Button>;
}`;
export default function CodeSnippetSyntaxHighlighting() {
const lines = useHighlightedCode(code, 'tsx', highlighter);
return (
<CodeSnippet actions={<CopyButton value={code} aria-label="Copy code" />}>
{lines ? <HighlightedCode lines={lines} /> : code}
</CodeSnippet>
);
} import elanorTheme from '@earendil-works/elanor-code-themes/shiki';
import type {CodeHighlighter} from '@earendil-works/elanor-react';
import type {HighlighterCore, LanguageInput} from 'shiki/core';
// The languages the app highlights, each loaded when it first appears.
const languages = new Map<string, LanguageInput>([
['tsx', () => import('shiki/langs/tsx.mjs')],
['css', () => import('shiki/langs/css.mjs')],
['json', () => import('shiki/langs/json.mjs')],
]);
let shiki: Promise<HighlighterCore> | undefined;
async function createShiki() {
const [{createHighlighterCore}, {createJavaScriptRegexEngine}] = await Promise.all([
import('shiki/core'),
import('shiki/engine/javascript'),
]);
return createHighlighterCore({
themes: [elanorTheme],
langs: [],
engine: createJavaScriptRegexEngine(),
});
}
export const highlighter: CodeHighlighter = async (code, language) => {
const grammar = languages.get(language);
if (!grammar) {
return null;
}
shiki ??= createShiki();
const loaded = await shiki;
await loaded.loadLanguage(grammar);
// A long minified line would stall the page while Shiki tokenizes it; keep such lines plain.
return loaded.codeToTokensBase(code, {
lang: language,
theme: elanorTheme.name,
tokenizeMaxLineLength: 1000,
});
}; A highlighter using Highlights:
import elanorTheme from '@earendil-works/elanor-code-themes/highlights';
import type {CodeHighlighter} from '@earendil-works/elanor-react';
export const highlighter: CodeHighlighter = async (code, language) => {
// Loads Highlights when code first needs it.
const {codeToTokens, isSupportedLanguage} = await import('@pierre/highlights');
return isSupportedLanguage(language)
? codeToTokens(code, {lang: language, theme: elanorTheme, tokenizeMaxLineLength: 1000}).tokens
: null;
};
Highlights needs WebAssembly with SIMD. Under a Content Security Policy, allow 'wasm-unsafe-eval'.
API
CodeSnippet
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | - | The code, rendered inside <code>. |
actions | ReactNode | - | Controls at the end of the snippet, such as a copy button. |
Renders <pre>. Also accepts its HTML attributes and ref.
HighlightedCode
Renders highlighted lines inside a CodeSnippet, on the server or in the browser.
| Prop | Type | Default | Description |
|---|---|---|---|
linesrequired | HighlightedLines | - |
Also accepts HTML attributes and ref.