mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-28 07:32:52 +00:00
components/code.tsx, a server component, imported Line from the @hanzo/ui/marketing barrel, which registers every marketing component (Plans, Faq, Hero, Media, ...) as a client entry of its route; the chunk of app/(marketing)/[...mod], which uses it, loads on every page, so every page carried them. components/models/Links.tsx, also a server component, imported the root barrel, about 150 client components onto every model page (605 KB against a 400 KB budget, and weight.spec red). Both now import through components/ui, which re-exports Line too. With the brand marks, a product page's first-party JavaScript goes from 404 KB to 383, /settings from 403 to 389, /bot from 416 to 397 and /board from 555 to 479. /kai (405) and /gen (403) stay over: their own page code and @hanzo/rooms' ProviderMark table.
115 lines
4.7 KiB
TypeScript
115 lines
4.7 KiB
TypeScript
import { bundledLanguages, getSingletonHighlighter, type BundledLanguage } from 'shiki'
|
||
import { codeTheme } from '@hanzo/ui/core'
|
||
import type { LineProps } from '@hanzo/ui/marketing'
|
||
import { Line } from '@/components/ui'
|
||
|
||
/**
|
||
* A code sample, highlighted where the page is built.
|
||
*
|
||
* A server component: shiki runs once, during the export, and the page ships
|
||
* the coloured spans — no highlighter reaches the browser. A client component
|
||
* that shows code takes this as a prop from the server page that owns the
|
||
* sample, the way ProductLanding's `code.source` does.
|
||
*
|
||
* The theme is @hanzo/ui's `codeTheme` (Dracula dark, GitHub Light light),
|
||
* written as `light-dark(<light>, <dark>)`, so the page's `color-scheme` —
|
||
* which @hanzo/appearance sets with the theme class — picks the palette. The
|
||
* tokens are the theme's. The ground is not Dracula's blue-violet #282a36: the
|
||
* site is monochrome, so the dark ground is the design system's own neutral
|
||
* `--card` (#0f0f0f) and the light one GitHub Light's white; the frame around
|
||
* the block stays the page's.
|
||
*
|
||
* A language shiki does not ship (or none) is set as plain text in the theme's
|
||
* ink rather than failing the build.
|
||
*
|
||
* Every token reads at AA on the light ground: GitHub Light's orange (#E36209)
|
||
* is 3.49:1 on its white, so a light ink under 4.5:1 is darkened, hue kept,
|
||
* until it clears it (`legible`). The dark half is Dracula's as shipped.
|
||
*/
|
||
export type CodeProps = { code: string; lang?: string } & Omit<LineProps, 'children'>
|
||
|
||
const grammar = (lang?: string): BundledLanguage | 'text' =>
|
||
lang && lang in bundledLanguages ? (lang as BundledLanguage) : 'text'
|
||
|
||
/** `#rgb` or `#rrggbb` as its three channels, 0–255; null for anything else. */
|
||
function channels(hex: string): [number, number, number] | null {
|
||
const m = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(hex.trim())
|
||
if (!m) return null
|
||
const h = m[1].length === 3 ? [...m[1]].map((c) => c + c).join('') : m[1]
|
||
return [0, 2, 4].map((i) => parseInt(h.slice(i, i + 2), 16)) as [number, number, number]
|
||
}
|
||
|
||
/** WCAG 2 relative luminance. */
|
||
const luminance = (c: [number, number, number]) => {
|
||
const [r, g, b] = c.map((v) => (v / 255 <= 0.03928 ? v / 255 / 12.92 : ((v / 255 + 0.055) / 1.055) ** 2.4))
|
||
return 0.2126 * r + 0.7152 * g + 0.0722 * b
|
||
}
|
||
|
||
const ratio = (a: [number, number, number], b: [number, number, number]) => {
|
||
const [hi, lo] = [luminance(a), luminance(b)].sort((x, y) => y - x)
|
||
return (hi + 0.05) / (lo + 0.05)
|
||
}
|
||
|
||
/** `ink` darkened in 5% steps, hue kept, until it reads at 4.5:1 on `ground`. */
|
||
function legible(ink: string, ground: string): string {
|
||
const [c, g] = [channels(ink), channels(ground)]
|
||
if (!c || !g) return ink
|
||
for (let k = 0; k <= 20; k++) {
|
||
const shade = c.map((v) => Math.round(v * (1 - k * 0.05))) as [number, number, number]
|
||
if (ratio(shade, g) >= 4.5) return k === 0 ? ink : `#${shade.map((v) => v.toString(16).padStart(2, '0')).join('')}`
|
||
}
|
||
return ink
|
||
}
|
||
|
||
/** A token's `light-dark(<light>, <dark>)` with its light ink made legible on `ground`. */
|
||
const readable = (color: string | undefined, ground: string) => {
|
||
const m = color && /^light-dark\(([^,]+),\s*(.+)\)$/.exec(color)
|
||
return m ? `light-dark(${legible(m[1], ground)}, ${m[2]})` : color
|
||
}
|
||
|
||
export async function Code({ code, lang, ...props }: CodeProps) {
|
||
const language = grammar(lang)
|
||
const shiki = await getSingletonHighlighter({ themes: [codeTheme.dark, codeTheme.light], langs: [] })
|
||
if (language !== 'text') await shiki.loadLanguage(language)
|
||
const dark = shiki.getTheme(codeTheme.dark)
|
||
const light = shiki.getTheme(codeTheme.light)
|
||
const { tokens } = shiki.codeToTokens(code.replace(/\n$/, ''), {
|
||
lang: language,
|
||
themes: codeTheme,
|
||
defaultColor: 'light-dark()',
|
||
})
|
||
return (
|
||
<Line
|
||
render="pre"
|
||
display="block"
|
||
m={0}
|
||
p={16}
|
||
overflowX="auto"
|
||
whiteSpace="pre"
|
||
fontFamily="$mono"
|
||
fontSize="$2"
|
||
lineHeight="var(--leading-relaxed)"
|
||
// inline-style: the light theme's ground and both themes' ink, read from shiki at build; the dark ground is the design system's neutral card.
|
||
style={{
|
||
backgroundColor: `light-dark(${light.bg}, var(--card))`,
|
||
color: `light-dark(${light.fg}, ${dark.fg})`,
|
||
}}
|
||
{...props}
|
||
>
|
||
<code>
|
||
{tokens.map((line, i) => (
|
||
<span key={i}>
|
||
{line.map((t, j) => (
|
||
// inline-style: each token's colour is the code theme's, computed by shiki per token.
|
||
<span key={j} style={{ color: readable(t.htmlStyle?.color, light.bg) }}>
|
||
{t.content}
|
||
</span>
|
||
))}
|
||
{i < tokens.length - 1 ? '\n' : null}
|
||
</span>
|
||
))}
|
||
</code>
|
||
</Line>
|
||
)
|
||
}
|