From 7db4fe00fdf1284028f85dac45897a2ba0d5a141 Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Sat, 29 Nov 2025 22:58:09 -0800 Subject: [PATCH] code examples --- src/avatar.tsx | 15 +-- src/box.tsx | 15 +-- src/button.tsx | 15 +-- src/code.tsx | 226 ++++++++++++++++++++++++++++++++++++++++++++ src/divider.tsx | 13 ++- src/grid.tsx | 15 +-- src/icon.tsx | 15 +-- src/image.tsx | 15 +-- src/input.tsx | 15 +-- src/placeholder.tsx | 15 +-- src/section.tsx | 15 ++- src/select.tsx | 15 +-- src/stack.tsx | 15 +-- src/text.tsx | 15 +-- test/server.tsx | 3 +- test/utils.ts | 4 + 16 files changed, 349 insertions(+), 77 deletions(-) create mode 100644 src/code.tsx create mode 100644 test/utils.ts diff --git a/src/avatar.tsx b/src/avatar.tsx index 642c76c..1c59026 100644 --- a/src/avatar.tsx +++ b/src/avatar.tsx @@ -3,6 +3,7 @@ import { H2, Text } from "./text" import "hono/jsx" import type { FC, JSX } from "hono/jsx" import { VStack, HStack } from "./stack" +import { CodeExamples } from "./code" export type AvatarProps = { src: string @@ -36,12 +37,14 @@ export const Test = () => { return (
{/* API Usage Examples */} - -
<Avatar src="/user.jpg" />
-
<Avatar src="/user.jpg" size={64} />
-
<Avatar src="/user.jpg" size={48} rounded />
-
<Avatar src="/user.jpg" style={{ border: "2px solid blue" }} />
-
+ ', + '', + '', + '', + ]} + /> {/* Size variations */} diff --git a/src/box.tsx b/src/box.tsx index 8616760..19d3766 100644 --- a/src/box.tsx +++ b/src/box.tsx @@ -1,5 +1,6 @@ import "hono/jsx" import type { FC, PropsWithChildren, JSX } from "hono/jsx" +import { CodeExamples } from "./code" type BoxProps = PropsWithChildren & { bg?: string @@ -48,12 +49,14 @@ export const Test = () => { return (
{/* API Usage Examples */} -
-
<Box>Content</Box>
-
<Box bg="#3b82f6" p={16}>Content</Box>
-
<RedBox>Content</RedBox>
-
<GrayBox>Content</GrayBox>
-
+ Content', + 'Content', + 'Content', + 'Content', + ]} + />

Box Component

diff --git a/src/button.tsx b/src/button.tsx index 0a3cff3..b26161d 100644 --- a/src/button.tsx +++ b/src/button.tsx @@ -3,6 +3,7 @@ import type { JSX, FC } from "hono/jsx" import { VStack, HStack } from "./stack" import { Section } from "./section" import { H2 } from "./text" +import { CodeExamples } from "./code" export type ButtonProps = JSX.IntrinsicElements["button"] & { variant?: "primary" | "secondary" | "outline" | "ghost" | "destructive" @@ -80,12 +81,14 @@ export const Test = () => { return (
{/* API Usage Examples */} - -
<Button>Click Me</Button>
-
<Button variant="secondary">Secondary</Button>
-
<Button variant="outline" size="lg">Large</Button>
-
<Button onClick={handleClick}>Action</Button>
-
+ Click Me', + '', + '', + '', + ]} + /> {/* Variants */} diff --git a/src/code.tsx b/src/code.tsx new file mode 100644 index 0000000..7fc5de1 --- /dev/null +++ b/src/code.tsx @@ -0,0 +1,226 @@ +import "hono/jsx" +import type { FC } from "hono/jsx" +import { VStack } from "./stack" + +type CodeProps = { + children: string +} + +// Color scheme +const colors = { + tag: "#0ea5e9", // cyan-500 + attr: "#8b5cf6", // violet-500 + string: "#10b981", // emerald-500 + number: "#f59e0b", // amber-500 + brace: "#ef4444", // red-500 + text: "#374151", // gray-700 +} + +// Lightweight JSX syntax highlighter +export const Code: FC = ({ children }) => { + const tokens = tokenizeJSX(children) + + return ( +
+ {tokens.map((token, i) => { + if (token.type === "tag") { + return ( + + {token.value} + + ) + } + if (token.type === "attr") { + return ( + + {token.value} + + ) + } + if (token.type === "string") { + return ( + + {token.value} + + ) + } + if (token.type === "number") { + return ( + + {token.value} + + ) + } + if (token.type === "brace") { + return ( + + {token.value} + + ) + } + return {token.value} + })} +
+ ) +} + +type CodeExamplesProps = { + examples: string[] +} + +// Container for multiple code examples +export const CodeExamples: FC = ({ examples }) => { + return ( + + {examples.map((example, i) => ( + {example} + ))} + + ) +} + +type Token = { + type: "tag" | "attr" | "string" | "number" | "brace" | "text" + value: string +} + +function tokenizeJSX(code: string): Token[] { + const tokens: Token[] = [] + let i = 0 + + while (i < code.length) { + // Match opening/closing tags: < or ") { + // Skip whitespace + if (/\s/.test(code[i]!)) { + tokens.push({ type: "text", value: code[i]! }) + i++ + continue + } + + // Check for self-closing / + if (code[i] === "/" && code[i + 1] === ">") { + tokens.push({ type: "tag", value: " />" }) + i += 2 + break + } + + // Parse attribute name + let attrName = "" + while (i < code.length && /[a-zA-Z0-9-]/.test(code[i]!)) { + attrName += code[i] + i++ + } + if (attrName) { + tokens.push({ type: "attr", value: attrName }) + } + + // Check for = + if (code[i] === "=") { + tokens.push({ type: "text", value: "=" }) + i++ + + // Parse attribute value + if (code[i] === '"') { + // String value + let str = '"' + i++ + while (i < code.length && code[i] !== '"') { + str += code[i] + i++ + } + if (code[i] === '"') { + str += '"' + i++ + } + tokens.push({ type: "string", value: str }) + } else if (code[i] === "{") { + // Brace value + tokens.push({ type: "brace", value: "{" }) + i++ + + // Get content inside braces + let content = "" + let depth = 1 + while (i < code.length && depth > 0) { + if (code[i] === "{") depth++ + if (code[i] === "}") { + depth-- + if (depth === 0) break + } + content += code[i] + i++ + } + + // Check if content is a number + if (/^\d+$/.test(content)) { + tokens.push({ type: "number", value: content }) + } else { + tokens.push({ type: "text", value: content }) + } + + if (code[i] === "}") { + tokens.push({ type: "brace", value: "}" }) + i++ + } + } + } + } + + // Closing > + if (code[i] === ">") { + tokens.push({ type: "tag", value: ">" }) + i++ + } + } else { + // Regular text + let text = "" + while (i < code.length && code[i] !== "<") { + text += code[i] + i++ + } + if (text) { + tokens.push({ type: "text", value: text }) + } + } + } + + return tokens +} diff --git a/src/divider.tsx b/src/divider.tsx index b81d029..f0d4f4b 100644 --- a/src/divider.tsx +++ b/src/divider.tsx @@ -3,6 +3,7 @@ import { H2 } from "./text" import "hono/jsx" import type { FC, PropsWithChildren, JSX } from "hono/jsx" import { VStack } from "./stack" +import { CodeExamples } from "./code" type DividerProps = PropsWithChildren & { style?: JSX.CSSProperties @@ -45,11 +46,13 @@ export const Test = () => { return (
{/* API Usage Examples */} - -
<Divider />
-
<Divider>OR</Divider>
-
<Divider style={{ margin: "24px 0" }} />
-
+ ', + 'OR', + '', + ]} + />

Divider Examples

diff --git a/src/grid.tsx b/src/grid.tsx index 2b913a6..15047b6 100644 --- a/src/grid.tsx +++ b/src/grid.tsx @@ -5,6 +5,7 @@ import { VStack } from "./stack" import { Button } from "./button" import { Section } from "./section" import { H2, H3 } from "./text" +import { CodeExamples } from "./code" type GridProps = PropsWithChildren & { cols?: GridCols @@ -73,12 +74,14 @@ export const Test = () => { return (
{/* API Usage Examples */} - -
<Grid cols={3}>...</Grid>
-
<Grid cols={4} gap={6}>...</Grid>
-
<Grid cols={{ sm: 1, md: 2, lg: 3 }}>...</Grid>
-
<Grid cols={2} v="center" h="center">...</Grid>
-
+ ...', + '...', + '...', + '...', + ]} + />

Grid Examples

diff --git a/src/icon.tsx b/src/icon.tsx index 3f01805..8e411bc 100644 --- a/src/icon.tsx +++ b/src/icon.tsx @@ -5,6 +5,7 @@ import { Grid } from "./grid" import { VStack } from "./stack" import { Section } from "./section" import { H2, Text } from "./text" +import { CodeExamples } from "./code" export type IconName = keyof typeof icons @@ -73,12 +74,14 @@ export const Test = () => { return (
{/* API Usage Examples */} - -
<Icon name="Heart" />
-
<Icon name="Star" size={8} />
-
<Icon name="Home" style={{ color: "#3b82f6" }} />
-
<IconLink name="ExternalLink" href="/link" />
-
+ ', + '', + '', + '', + ]} + /> {/* === ICON TESTS === */} diff --git a/src/image.tsx b/src/image.tsx index e456816..bb487d0 100644 --- a/src/image.tsx +++ b/src/image.tsx @@ -4,6 +4,7 @@ import "hono/jsx" import type { FC, JSX } from "hono/jsx" import { VStack, HStack } from "./stack" import { Grid } from "./grid" +import { CodeExamples } from "./code" export type ImageProps = { src: string @@ -36,12 +37,14 @@ export const Test = () => { return (
{/* API Usage Examples */} - -
<Image src="/photo.jpg" />
-
<Image src="/photo.jpg" width={200} height={200} />
-
<Image src="/photo.jpg" objectFit="cover" />
-
<Image src="/photo.jpg" style={{ borderRadius: "8px" }} />
-
+ ', + '', + '', + '', + ]} + />

Image Examples

diff --git a/src/input.tsx b/src/input.tsx index 4bce523..089daf3 100644 --- a/src/input.tsx +++ b/src/input.tsx @@ -3,6 +3,7 @@ import { H2, H3, H4, H5, Text, SmallText } from "./text" import "hono/jsx" import type { JSX, FC } from "hono/jsx" import { VStack, HStack } from "./stack" +import { CodeExamples } from "./code" export type InputProps = JSX.IntrinsicElements["input"] & { labelPosition?: "above" | "left" | "right" @@ -73,12 +74,14 @@ export const Test = () => { return (
{/* API Usage Examples */} - -
<Input placeholder="Enter name" />
-
<Input type="email" placeholder="Email" />
-
<Input>Label</Input>
-
<Input labelPosition="left">Name</Input>
-
+ ', + '', + 'Label', + 'Name', + ]} + /> {/* Basic inputs */} diff --git a/src/placeholder.tsx b/src/placeholder.tsx index 4c09745..b17ef48 100644 --- a/src/placeholder.tsx +++ b/src/placeholder.tsx @@ -7,6 +7,7 @@ import { Image } from "./image" import type { ImageProps } from "./image" import { VStack, HStack } from "./stack" import { Grid } from "./grid" +import { CodeExamples } from "./code" export const Placeholder = { Avatar(props: PlaceholderAvatarProps) { @@ -38,12 +39,14 @@ export const Test = () => { return (
{/* API Usage Examples */} - -
<Placeholder.Avatar />
-
<Placeholder.Avatar type="avataaars" size={64} />
-
<Placeholder.Image width={200} height={200} />
-
<Placeholder.Image seed={42} />
-
+ ', + '', + '', + '', + ]} + /> {/* === AVATAR TESTS === */} diff --git a/src/section.tsx b/src/section.tsx index 1fc089b..35c1dbf 100644 --- a/src/section.tsx +++ b/src/section.tsx @@ -2,6 +2,7 @@ import "hono/jsx" import type { FC, PropsWithChildren, JSX } from "hono/jsx" import { VStack } from "./stack" import type { TailwindSize } from "./types" +import { CodeExamples } from "./code" type SectionProps = PropsWithChildren & { gap?: TailwindSize @@ -21,11 +22,15 @@ export const Test = () => { return (
{/* API Usage Examples */} -
-
<Section>...</Section>
-
<Section gap={4}>...</Section>
-
<Section maxWidth="600px">...</Section>
-
<Section style={{ backgroundColor: "#f3f4f6" }}>...</Section>
+
+ ...
', + '
...
', + '
...
', + '
...
', + ]} + />
diff --git a/src/select.tsx b/src/select.tsx index e0a1c87..3f6440e 100644 --- a/src/select.tsx +++ b/src/select.tsx @@ -3,6 +3,7 @@ import { H2, H3, H4, H5, Text, SmallText } from "./text" import "hono/jsx" import type { JSX, FC } from "hono/jsx" import { VStack, HStack } from "./stack" +import { CodeExamples } from "./code" export type SelectOption = { value: string @@ -164,12 +165,14 @@ export const Test = () => { return (
{/* API Usage Examples */} - -
<Select options={options} />
-
<Select options={options} placeholder="Choose" />
-
<Select options={options}>Label</Select>
-
<Select options={options} labelPosition="left">Label</Select>
-
+ ', + 'Label', + '', + ]} + /> {/* Basic selects */} diff --git a/src/stack.tsx b/src/stack.tsx index b713b76..000841d 100644 --- a/src/stack.tsx +++ b/src/stack.tsx @@ -5,6 +5,7 @@ import { Grid } from "./grid" import { Section } from "./section" import { H2 } from "./text" import { RedBox, GreenBox, BlueBox } from "./box" +import { CodeExamples } from "./code" export const VStack: FC = (props) => { return ( @@ -69,12 +70,14 @@ export const Test = () => { return (
{/* API Usage Examples */} - -
<VStack>...</VStack>
-
<VStack gap={4} v="center">...</VStack>
-
<HStack>...</HStack>
-
<HStack gap={6} h="between" v="center">...</HStack>
-
+ ...', + '...', + '...', + '...', + ]} + /> {/* HStack layout matrix */} diff --git a/src/text.tsx b/src/text.tsx index d1cf013..cc20415 100644 --- a/src/text.tsx +++ b/src/text.tsx @@ -1,5 +1,6 @@ import "hono/jsx" import type { FC, PropsWithChildren, JSX } from "hono/jsx" +import { CodeExamples } from "./code" type TextProps = PropsWithChildren & { style?: JSX.CSSProperties @@ -37,12 +38,14 @@ export const Test = () => { return (
{/* API Usage Examples */} -
-
<H1>Heading 1</H1>
-
<H2>Heading 2</H2>
-
<Text>Regular text</Text>
-
<SmallText>Small text</SmallText>
-
+ Heading 1', + '

Heading 2

', + 'Regular text', + 'Small text', + ]} + />

Heading 1 (24px, bold)

diff --git a/test/server.tsx b/test/server.tsx index 190516c..9be7c58 100644 --- a/test/server.tsx +++ b/test/server.tsx @@ -1,6 +1,7 @@ import { Hono } from 'hono' import { readdirSync } from 'fs' import { join } from 'path' +import { capitalize } from './utils' const port = process.env.PORT ?? '3100' const app = new Hono() @@ -14,7 +15,7 @@ app.get('/:file', async c => { return c.text('404 Not Found', 404) const page = await import(path + `?t=${Date.now()}`) - return c.html(<>

{file}

) + return c.html(<>

<{capitalize(file)} />

) }) app.get('/', c => { diff --git a/test/utils.ts b/test/utils.ts new file mode 100644 index 0000000..6dc2f75 --- /dev/null +++ b/test/utils.ts @@ -0,0 +1,4 @@ +// capitalize a word. that's it. +export function capitalize(str: string): string { + return str.charAt(0).toUpperCase() + str.slice(1) +}