Skip to content

Commit cf30d3b

Browse files
chore: support for mermaid diagrams
1 parent 34d3d78 commit cf30d3b

File tree

6 files changed

+338
-30
lines changed

6 files changed

+338
-30
lines changed

apps/sim/app/(landing)/studio/[slug]/prose-studio.css

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@
6969
border: 1px solid #2a2a2a;
7070
border-radius: 5px;
7171
padding: 1.5rem;
72-
margin: 2rem 0;
7372
overflow-x: auto;
7473
font-size: 0.875rem;
7574
line-height: 1.7;
@@ -149,6 +148,77 @@
149148
font-size: 0.875rem;
150149
}
151150

151+
/* ── PrismJS syntax highlighting tokens ──
152+
* Custom theme matching Sim design language.
153+
* Colors aligned with the brand palette:
154+
* - Keywords/imports: #FA4EDF (pink)
155+
* - Strings: #FFCC02 (yellow)
156+
* - Functions/methods: #82aaff (periwinkle)
157+
* - Types/classes: #2ABBF8 (cyan)
158+
* - Numbers/booleans: #00F701 (green)
159+
* - Comments: #666
160+
* - Operators/punctuation: #999
161+
*/
162+
163+
.token.comment,
164+
.token.prolog,
165+
.token.doctype,
166+
.token.cdata {
167+
color: #666666;
168+
font-style: italic;
169+
}
170+
171+
.token.punctuation {
172+
color: #999999;
173+
}
174+
175+
.token.property,
176+
.token.tag,
177+
.token.constant,
178+
.token.symbol,
179+
.token.deleted {
180+
color: #2abbf8;
181+
}
182+
183+
.token.boolean,
184+
.token.number {
185+
color: #00f701;
186+
}
187+
188+
.token.selector,
189+
.token.attr-name,
190+
.token.string,
191+
.token.char,
192+
.token.builtin,
193+
.token.inserted {
194+
color: #ffcc02;
195+
}
196+
197+
.token.operator,
198+
.token.entity,
199+
.token.url,
200+
.language-css .token.string,
201+
.style .token.string {
202+
color: #999999;
203+
}
204+
205+
.token.atrule,
206+
.token.attr-value,
207+
.token.keyword {
208+
color: #fa4edf;
209+
}
210+
211+
.token.function,
212+
.token.class-name {
213+
color: #82aaff;
214+
}
215+
216+
.token.regex,
217+
.token.important,
218+
.token.variable {
219+
color: #2abbf8;
220+
}
221+
152222
/* Reduced motion */
153223
@media (prefers-reduced-motion: reduce) {
154224
.prose-studio a {

apps/sim/app/(landing)/studio/hero.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export function StudioHero() {
1010
backgroundSize: '32px 32px',
1111
}}
1212
/>
13-
<div className='relative z-10 mx-auto max-w-5xl px-4 lg:px-4'>
13+
<div className='relative z-10 mx-auto max-w-5xl'>
1414
<div className='flex flex-col items-start'>
1515
<div className='mb-6 inline-flex items-center gap-2 font-mono text-[11px] uppercase tracking-widest text-[#999]'>
1616
<span className='inline-block h-2 w-2 flex-shrink-0 bg-[#2ABBF8]' aria-hidden='true' />

apps/sim/app/(landing)/studio/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export default async function StudioIndex({
5454
dangerouslySetInnerHTML={{ __html: JSON.stringify(studioJsonLd) }}
5555
/>
5656
{pageNum === 1 && !tag && <StudioHero />}
57-
<div className='mx-auto w-full max-w-5xl px-4 py-12 lg:px-4'>
57+
<div className='mx-auto w-full max-w-5xl py-12'>
5858
{activeCategory && (
5959
<div className='mb-8 flex items-center gap-3'>
6060
<span className='font-mono text-[10px] uppercase tracking-widest text-[#666]'>

apps/sim/lib/blog/code.tsx

Lines changed: 107 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,118 @@
11
'use client'
22

3-
import { Code } from '@/components/emcn'
3+
import { useCallback, useState } from 'react'
4+
import { Check, Copy } from 'lucide-react'
5+
import { highlight, languages } from 'prismjs'
6+
import 'prismjs/components/prism-typescript'
7+
import 'prismjs/components/prism-javascript'
8+
import 'prismjs/components/prism-json'
9+
import 'prismjs/components/prism-python'
10+
import 'prismjs/components/prism-bash'
11+
import 'prismjs/components/prism-css'
12+
import 'prismjs/components/prism-yaml'
13+
import 'prismjs/components/prism-markdown'
414

515
interface CodeBlockProps {
616
code: string
7-
language: 'javascript' | 'json' | 'python'
17+
language: string
18+
filename?: string
819
}
920

10-
export function CodeBlock({ code, language }: CodeBlockProps) {
21+
const LANG_MAP: Record<string, string> = {
22+
js: 'javascript',
23+
jsx: 'javascript',
24+
ts: 'typescript',
25+
tsx: 'typescript',
26+
typescript: 'typescript',
27+
javascript: 'javascript',
28+
json: 'json',
29+
python: 'python',
30+
py: 'python',
31+
bash: 'bash',
32+
sh: 'bash',
33+
shell: 'bash',
34+
css: 'css',
35+
yaml: 'yaml',
36+
yml: 'yaml',
37+
md: 'markdown',
38+
markdown: 'markdown',
39+
markup: 'markup',
40+
html: 'markup',
41+
xml: 'markup',
42+
php: 'php',
43+
rust: 'rust',
44+
go: 'go',
45+
java: 'java',
46+
kotlin: 'kotlin',
47+
scala: 'scala',
48+
swift: 'swift',
49+
dart: 'dart',
50+
}
51+
52+
const LANG_LABEL: Record<string, string> = {
53+
javascript: 'JavaScript',
54+
typescript: 'TypeScript',
55+
json: 'JSON',
56+
python: 'Python',
57+
bash: 'Bash',
58+
css: 'CSS',
59+
yaml: 'YAML',
60+
markdown: 'Markdown',
61+
}
62+
63+
export function CodeBlock({ code, language, filename }: CodeBlockProps) {
64+
const [copied, setCopied] = useState(false)
65+
66+
const lang = LANG_MAP[language.toLowerCase()] || 'javascript'
67+
const grammar = languages[lang] || languages.javascript
68+
const highlighted = highlight(code, grammar, lang)
69+
const label = filename || LANG_LABEL[lang] || language
70+
71+
const handleCopy = useCallback(async () => {
72+
try {
73+
await navigator.clipboard.writeText(code)
74+
setCopied(true)
75+
setTimeout(() => setCopied(false), 1500)
76+
} catch {
77+
/* clipboard unavailable */
78+
}
79+
}, [code])
80+
1181
return (
12-
<div className='dark w-full overflow-hidden rounded-md border border-[#2a2a2a] bg-[#1F1F1F] text-sm'>
13-
<Code.Viewer
14-
code={code}
15-
showGutter
16-
language={language}
17-
className='[&_pre]:!pb-0 m-0 rounded-none border-0 bg-transparent'
18-
/>
82+
<div
83+
className='my-8 overflow-hidden border border-[#2A2A2A] bg-[#111111]'
84+
style={{ borderRadius: '5px' }}
85+
>
86+
<div className='flex items-center justify-between border-b border-[#2A2A2A] bg-[#232323] px-4 py-2'>
87+
<div className='flex items-center gap-2'>
88+
<span className='inline-block h-2 w-2 bg-[#00F701]' aria-hidden='true' />
89+
<span className='inline-block h-2 w-2 bg-[#2ABBF8]' aria-hidden='true' />
90+
<span className='inline-block h-2 w-2 bg-[#FA4EDF]' aria-hidden='true' />
91+
</div>
92+
<span className='font-mono text-[10px] uppercase tracking-widest text-[#ECECEC]'>
93+
{label}
94+
</span>
95+
<button
96+
type='button'
97+
onClick={handleCopy}
98+
className='flex items-center text-[#666] transition-colors hover:text-[#ECECEC]'
99+
aria-label={copied ? 'Copied' : 'Copy code'}
100+
>
101+
{copied ? (
102+
<Check className='h-3.5 w-3.5 text-[#00F701]' aria-hidden='true' />
103+
) : (
104+
<Copy className='h-3.5 w-3.5' aria-hidden='true' />
105+
)}
106+
</button>
107+
</div>
108+
<div className='overflow-x-auto p-4'>
109+
<pre className='m-0 border-0 bg-transparent p-0 text-[13px] leading-relaxed'>
110+
<code
111+
className='font-mono text-[#d4d4d8]'
112+
dangerouslySetInnerHTML={{ __html: highlighted }}
113+
/>
114+
</pre>
115+
</div>
19116
</div>
20117
)
21118
}

apps/sim/lib/blog/mdx.tsx

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import clsx from 'clsx'
22
import Image from 'next/image'
33
import type { MDXRemoteProps } from 'next-mdx-remote/rsc'
44
import { CodeBlock } from '@/lib/blog/code'
5+
import { MermaidDiagram } from '@/lib/blog/mermaid'
56

67
export const mdxComponents: MDXRemoteProps['components'] = {
78
img: (props: any) => (
@@ -102,27 +103,18 @@ export const mdxComponents: MDXRemoteProps['components'] = {
102103
const codeContent = child.props.children || ''
103104
const className = child.props.className || ''
104105
const language = className.replace('language-', '') || 'javascript'
105-
106-
const languageMap: Record<string, 'javascript' | 'json' | 'python'> = {
107-
js: 'javascript',
108-
jsx: 'javascript',
109-
ts: 'javascript',
110-
tsx: 'javascript',
111-
typescript: 'javascript',
112-
javascript: 'javascript',
113-
json: 'json',
114-
python: 'python',
115-
py: 'python',
106+
const code = typeof codeContent === 'string' ? codeContent.trim() : String(codeContent)
107+
if (language.toLowerCase() === 'mermaid') {
108+
return (
109+
<div className='not-prose my-6'>
110+
<MermaidDiagram chart={code} />
111+
</div>
112+
)
116113
}
117114

118-
const mappedLanguage = languageMap[language.toLowerCase()] || 'javascript'
119-
120115
return (
121116
<div className='not-prose my-6'>
122-
<CodeBlock
123-
code={typeof codeContent === 'string' ? codeContent.trim() : String(codeContent)}
124-
language={mappedLanguage}
125-
/>
117+
<CodeBlock code={code} language={language} />
126118
</div>
127119
)
128120
}

0 commit comments

Comments
 (0)