Skip to content

Commit c041b3a

Browse files
juandavclaude
andcommitted
feat: add syntax highlighting to template preview editor
Uses prism-react-renderer with Dracula theme overlay technique for real-time syntax coloring in the template editor. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent fe64fff commit c041b3a

File tree

2 files changed

+96
-11
lines changed

2 files changed

+96
-11
lines changed

apps/website/src/pages/preview.module.css

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -87,19 +87,52 @@
8787
letter-spacing: 0.5px;
8888
}
8989

90-
.editor {
90+
.editorWrapper {
91+
position: relative;
9192
flex: 1;
93+
min-height: 0;
94+
overflow: hidden;
95+
}
96+
97+
.editorHighlight {
98+
position: absolute;
99+
top: 0;
100+
left: 0;
101+
right: 0;
102+
bottom: 0;
103+
margin: 0;
104+
padding: 1rem;
105+
font-family: var(--ifm-font-family-monospace);
106+
font-size: 0.85rem;
107+
line-height: 1.6;
108+
overflow: auto;
109+
pointer-events: none;
110+
background: #282a36;
111+
white-space: pre;
112+
word-wrap: normal;
113+
}
114+
115+
.editorTextarea {
116+
position: absolute;
117+
top: 0;
118+
left: 0;
92119
width: 100%;
93-
border: none;
94-
resize: none;
120+
height: 100%;
121+
margin: 0;
95122
padding: 1rem;
96123
font-family: var(--ifm-font-family-monospace);
97-
font-size: 0.9rem;
124+
font-size: 0.85rem;
98125
line-height: 1.6;
99-
background: var(--ifm-background-color);
100-
color: var(--ifm-font-color-base);
126+
border: none;
127+
resize: none;
128+
background: transparent;
129+
color: transparent;
130+
caret-color: #f8f8f2;
101131
outline: none;
102-
min-height: 0;
132+
overflow: auto;
133+
white-space: pre;
134+
word-wrap: normal;
135+
-webkit-text-fill-color: transparent;
103136
}
104137

105138
.preview {

apps/website/src/pages/preview.tsx

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useState, useCallback, useEffect, useRef } from 'react';
22
import Layout from '@theme/Layout';
3+
import { Highlight, themes } from 'prism-react-renderer';
34
import * as Handlebars from 'handlebars';
45
import styles from './preview.module.css';
56

@@ -360,6 +361,58 @@ function compileTemplate(
360361
}
361362
}
362363

364+
const adapterToLanguage: Record<Adapter, string> = {
365+
handlebars: 'handlebars',
366+
ejs: 'markup',
367+
pug: 'pug',
368+
};
369+
370+
function HighlightedEditor({
371+
value,
372+
onChange,
373+
language,
374+
}: {
375+
value: string;
376+
onChange: (val: string) => void;
377+
language: string;
378+
}) {
379+
const textareaRef = useRef<HTMLTextAreaElement>(null);
380+
const preRef = useRef<HTMLPreElement>(null);
381+
382+
const handleScroll = () => {
383+
if (textareaRef.current && preRef.current) {
384+
preRef.current.scrollTop = textareaRef.current.scrollTop;
385+
preRef.current.scrollLeft = textareaRef.current.scrollLeft;
386+
}
387+
};
388+
389+
return (
390+
<div className={styles.editorWrapper}>
391+
<Highlight theme={themes.dracula} code={value} language={language}>
392+
{({ tokens, getLineProps, getTokenProps }) => (
393+
<pre ref={preRef} className={styles.editorHighlight}>
394+
{tokens.map((line, i) => (
395+
<div key={i} {...getLineProps({ line })}>
396+
{line.map((token, key) => (
397+
<span key={key} {...getTokenProps({ token })} />
398+
))}
399+
</div>
400+
))}
401+
</pre>
402+
)}
403+
</Highlight>
404+
<textarea
405+
ref={textareaRef}
406+
className={styles.editorTextarea}
407+
value={value}
408+
onChange={(e) => onChange(e.target.value)}
409+
onScroll={handleScroll}
410+
spellCheck={false}
411+
/>
412+
</div>
413+
);
414+
}
415+
363416
export default function Preview(): JSX.Element {
364417
const [adapter, setAdapter] = useState<Adapter>('handlebars');
365418
const [template, setTemplate] = useState(adapters.handlebars.defaultTemplate);
@@ -432,11 +485,10 @@ export default function Preview(): JSX.Element {
432485
<span>Template</span>
433486
<span className={styles.badge}>{adapters[adapter].label}</span>
434487
</div>
435-
<textarea
436-
className={styles.editor}
488+
<HighlightedEditor
437489
value={template}
438-
onChange={(e) => setTemplate(e.target.value)}
439-
spellCheck={false}
490+
onChange={setTemplate}
491+
language={adapterToLanguage[adapter]}
440492
/>
441493
{showContext && (
442494
<textarea

0 commit comments

Comments
 (0)