import { define } from 'forge' import { theme } from './theme' // Code block container const CodeBlock = define('CodeBlock', { fontFamily: 'monospace', fontSize: '13px', lineHeight: 1.5, color: theme('syntax-text'), }) // Syntax token spans const TagToken = define('TagToken', { base: 'span', color: theme('syntax-tag'), fontWeight: 600, }) const AttrToken = define('AttrToken', { base: 'span', color: theme('syntax-attr'), }) const StringToken = define('StringToken', { base: 'span', color: theme('syntax-string'), }) const NumberToken = define('NumberToken', { base: 'span', color: theme('syntax-number'), }) const BraceToken = define('BraceToken', { base: 'span', color: theme('syntax-brace'), fontWeight: 600, }) const TextToken = define('TextToken', { base: 'span', color: theme('syntax-text'), }) // Code examples container const CodeExamplesBox = define('CodeExamplesBox', { background: theme('colors-bgElevated'), padding: theme('spacing-4'), borderRadius: theme('radius-lg'), border: `1px solid ${theme('colors-border')}`, display: 'flex', flexDirection: 'column', gap: theme('spacing-2'), }) type CodeProps = { children: string } // Lightweight JSX syntax highlighter export const Code = ({ children }: CodeProps) => { 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 = ({ examples }: CodeExamplesProps) => { 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 }