Code block
Code in a box of its own, read left to right on any page. The block renders on the server; the copy button is its only script.
split - size not measured yet
Code block
split - size not measured yetCode in a box of its own, read left to right on any page. The block renders on the server; the copy button is its only script.
app/invoices/page.tsx
export default async function Page() {
const invoices = await getInvoices({ status: 'overdue', sortBy: 'dueDate', order: 'descending' })
return <InvoiceTable items={invoices} />
}Try it
showCopy
wrap
The code
code-block.tsx
import type { CodeBlockDesignProps } from 'topclassui/code-block'
import { CodeBlock } from 'topclassui/code-block'
/** The docs stage: the Try it controls hand their values in as props. One line is long, so wrap shows. */
export default function Demo(props: CodeBlockDesignProps) {
return (
<CodeBlock lang="tsx" title="app/invoices/page.tsx" {...props}>
{`export default async function Page() {
const invoices = await getInvoices({ status: 'overdue', sortBy: 'dueDate', order: 'descending' })
return <InvoiceTable items={invoices} />
}`}
</CodeBlock>
)
}
Pass the code as a template-literal child. No highlighter is built in: pass coloured nodes as highlighted.
Every state
3 statesapp/page.tsx
export default async function Page() {
const invoices = await getInvoices()
return <InvoiceTable items={invoices} />
}columns.ts
export const columns = ['number', 'customer', 'issued', 'due', 'amount', 'status', 'actions'] as constapp/reports/page.tsx
export default async function Page() {
const invoices = await getInvoices()
return <InvoiceTable items={invoices} />
}Import from the package
topclassui/code-block
import { CodeBlock } from 'topclassui/code-block'Install once:npm i topclassui@beta zod
Examples
- The code is
children, a string: every space and line break is kept, and that string is what the button copies. titlenames the block: the code becomes a region named by it, and the button reads "Copy page.tsx".langis written asdata-langand nothing else. To colour the code, make the nodes yourself, at build time or in your own Server Component, and pass them ashighlighted; the button still copieschildren.- A long line scrolls inside the block;
wrapbreaks it instead. showCopy={false}drops the button, and the block then ships no JavaScript at all.- In a Server Component pass
{...getLabels(locale)}: the button's words come fromlabels.common.
basic
page.tsx
export default async function Page() {
const invoices = await getInvoices()
return <InvoiceTable items={invoices} />
}basic.tsx
import { CodeBlock } from 'topclassui/code-block'
/** The drawn block: a file name, the copy button, and the code passed as a template literal. */
export default function Demo() {
return (
<CodeBlock lang="tsx" title="page.tsx">
{`export default async function Page() {
const invoices = await getInvoices()
return <InvoiceTable items={invoices} />
}`}
</CodeBlock>
)
}
highlighted
sample.tsx
import { CodeBlock } from 'topclassui/code-block'
// Coloured by the caller, copied as plain text
export const sample = <CodeBlock title="page.tsx">{code}</CodeBlock>highlighted.tsx
import { CodeBlock } from 'topclassui/code-block'
const CODE = `import { CodeBlock } from 'topclassui/code-block'
// Coloured by the caller, copied as plain text
export const sample = <CodeBlock title="page.tsx">{code}</CodeBlock>`
const KEYWORD = 'font-semibold'
const STRING = 'italic'
const COMMENT = 'text-neutral-on-solid/70'
/** highlighted: nodes the caller made, here by hand, rendered in place of the text. The button
* still copies children. A highlighter at build time would hand the same kind of nodes. */
export default function Demo() {
return (
<CodeBlock
lang="tsx"
title="sample.tsx"
highlighted={(
<>
<span className={KEYWORD}>import</span>{' { CodeBlock } '}<span className={KEYWORD}>from</span>{' '}
<span className={STRING}>{`'topclassui/code-block'`}</span>
{'\n\n'}
<span className={COMMENT}>{'// Coloured by the caller, copied as plain text'}</span>
{'\n'}
<span className={KEYWORD}>export const</span>{' sample = <CodeBlock title='}<span className={STRING}>{'"page.tsx"'}</span>
{'>{code}</CodeBlock>'}
</>
)}
>
{CODE}
</CodeBlock>
)
}
long-line
token.ts
const token = 'a1b2c3d4e5a1b2c3d4e5a1b2c3d4e5a1b2c3d4e5a1b2c3d4e5a1b2c3d4e5a1b2c3d4e5a1b2c3d4e5a1b2c3d4e5a1b2c3d4e5a1b2c3d4e5a1b2c3d4e5a1b2c3d4e5a1b2c3d4e5a1b2c3d4e5a1b2c3d4e5a1b2c3d4e5a1b2c3d4e5a1b2c3d4e5a1b2c3d4e5a1b2c3d4e5a1b2c3d4e5a1b2c3d4e5a1b2c3d4e5a1b2c3d4e5a1b2c3d4e5a1b2c3d4e5a1b2c3d4e5abcd'long-line.tsx
import { CodeBlock } from 'topclassui/code-block'
const LINE = ["const token = '", 'a1b2c3d4e5'.repeat(28), "abcd'"].join('')
/** One line of 300 characters: it scrolls inside the block, and the page never scrolls sideways. */
export default function Demo() {
return (
<CodeBlock lang="ts" title="token.ts">
{LINE}
</CodeBlock>
)
}
wrap
invoice-row.tsx
export const columns = ['number', 'customer', 'issued', 'due', 'amount', 'status', 'actions'] as const satisfies readonly InvoiceColumn[]wrap.tsx
import { CodeBlock } from 'topclassui/code-block'
/** wrap: a long line breaks inside the block instead of scrolling it, and the code is no tab stop. */
export default function Demo() {
return (
<CodeBlock lang="tsx" title="invoice-row.tsx" wrap>
{`export const columns = ['number', 'customer', 'issued', 'due', 'amount', 'status', 'actions'] as const satisfies readonly InvoiceColumn[]`}
</CodeBlock>
)
}
no-copy
globals.css
:root {
--tc-radius: 0.75rem;
}no-copy.tsx
import { CodeBlock } from 'topclassui/code-block'
/** A title and no button: the block ships no script at all. */
export default function Demo() {
return (
<CodeBlock lang="css" title="globals.css" showCopy={false}>
{`:root {
--tc-radius: 0.75rem;
}`}
</CodeBlock>
)
}
no-header
pnpm add topclassui zodno-header.tsx
import { CodeBlock } from 'topclassui/code-block'
/** No title and no copy button: no header either, so the code takes the top padding back. */
export default function Demo() {
return (
<CodeBlock lang="sh" showCopy={false}>
{'pnpm add topclassui zod'}
</CodeBlock>
)
}
In Arabic, right to left
الفواتير.tsx
export default async function Page() {
const invoices = await getInvoices()
return <InvoiceTable items={invoices} />
}rtl.tsx
import { getLabels } from 'topclassui/labels/get-labels'
import { TopClassProvider } from 'topclassui/provider'
import { CodeBlock } from 'topclassui/code-block'
/** Arabic: the header keeps the page's direction, so the button sits at the inline end, on the
* left, and the button reads its Arabic name. The code still reads left to right. */
export default function Demo() {
return (
<TopClassProvider direction="rtl" locale="ar">
<div dir="rtl" lang="ar">
<CodeBlock {...getLabels('ar')} lang="tsx" title="الفواتير.tsx">
{`export default async function Page() {
const invoices = await getInvoices()
return <InvoiceTable items={invoices} />
}`}
</CodeBlock>
</div>
</TopClassProvider>
)
}
Props
Keyboard and accessibility
- The button's name never changes: the tick is the cue to the eye, and a polite status says "Copied" or "Could not copy" once per press.
- Copying needs a secure context (https or localhost); elsewhere the button says "Could not copy" and keeps its icon.
- Colours you pass in
highlightedare yours to check against the block's surface.
Right to left
The code is always dir="ltr" and translate="no". The header keeps the page's direction, so in Arabic the copy button sits on the left, at the inline end.
Server and client
Renders on the server; the interactive part is a client component that ships only when it is used.
Files: index.tsx, client.tsx.
On the chip: split - size not measured yet.