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
if (code[i] === '<') {
i++
// Check for closing tag
if (code[i] === '/') {
tokens.push({ type: 'tag', value: '' })
i++
} else {
tokens.push({ type: 'tag', value: '<' })
}
// Get tag name
let tagName = ''
while (i < code.length && /[A-Za-z0-9.]/.test(code[i]!)) {
tagName += code[i]
i++
}
if (tagName) {
tokens.push({ type: 'tag', value: tagName })
}
// Parse attributes inside the tag
while (i < code.length && code[i] !== '>') {
// 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
}