11'use client'
22
33import { Check , Copy } from 'lucide-react'
4+ import { Highlight , themes } from 'prism-react-renderer'
45import { useMemo , useState } from 'react'
56
6- import { MermaidDiagram } from './mermaid-diagram'
7-
87import { Separator } from '@/components/ui/separator'
98
9+ import { MermaidDiagram } from './mermaid-diagram'
10+
1011type CodeDemoChildren = string | JSX . Element | JSX . Element [ ]
1112
1213interface CodeDemoProps {
@@ -33,6 +34,107 @@ const getContent = (c: CodeDemoChildren): string => {
3334 return ''
3435}
3536
37+ const trimLeadingWhitespace = ( content : string ) : string => {
38+ const lines = content . split ( '\n' )
39+ const nonEmptyLines = lines . filter ( ( line ) => line . trim ( ) !== '' )
40+
41+ if ( nonEmptyLines . length === 0 ) return content
42+
43+ // Find the minimum indentation among non-empty lines
44+ const minIndent = Math . min (
45+ ...nonEmptyLines . map ( ( line ) => {
46+ const match = line . match ( / ^ \s * / )
47+ return match ? match [ 0 ] . length : 0
48+ } )
49+ )
50+
51+ // Remove the minimum indentation from all lines
52+ const trimmedLines = lines . map ( ( line ) => {
53+ if ( line . trim ( ) === '' ) return line // Keep empty lines as-is
54+ return line . slice ( minIndent )
55+ } )
56+
57+ return trimmedLines . join ( '\n' )
58+ }
59+
60+ // Map common language aliases to Prism language identifiers
61+ const languageMap : Record < string , string > = {
62+ js : 'javascript' ,
63+ ts : 'typescript' ,
64+ jsx : 'jsx' ,
65+ tsx : 'tsx' ,
66+ py : 'python' ,
67+ sh : 'bash' ,
68+ shell : 'bash' ,
69+ yml : 'yaml' ,
70+ md : 'markdown' ,
71+ json : 'json' ,
72+ html : 'html' ,
73+ css : 'css' ,
74+ sql : 'sql' ,
75+ go : 'go' ,
76+ rust : 'rust' ,
77+ java : 'java' ,
78+ php : 'php' ,
79+ ruby : 'ruby' ,
80+ c : 'c' ,
81+ cpp : 'cpp' ,
82+ 'c++' : 'cpp' ,
83+ csharp : 'csharp' ,
84+ 'c#' : 'csharp' ,
85+ swift : 'swift' ,
86+ kotlin : 'kotlin' ,
87+ scala : 'scala' ,
88+ dart : 'dart' ,
89+ dockerfile : 'docker' ,
90+ yaml : 'yaml' ,
91+ toml : 'toml' ,
92+ ini : 'ini' ,
93+ xml : 'xml' ,
94+ graphql : 'graphql' ,
95+ prisma : 'prisma' ,
96+ }
97+
98+ // Language-specific color constants
99+ const LANGUAGE_COLORS = {
100+ bash : '#8FE457' , // BetweenGreen for bash commands
101+ white : '#ffffff' , // White for text/plain/markdown
102+ default : null , // Use theme default for other languages
103+ } as const
104+
105+ // Define which languages should use which color scheme
106+ const LANGUAGE_COLOR_MAP : Record < string , keyof typeof LANGUAGE_COLORS > = {
107+ bash : 'bash' ,
108+ text : 'white' ,
109+ plain : 'white' ,
110+ markdown : 'white' ,
111+ }
112+
113+ const getLanguageTheme = ( language : string ) => {
114+ const baseTheme = themes . vsDark
115+ const colorScheme = LANGUAGE_COLOR_MAP [ language ]
116+ const overrideColor = colorScheme && LANGUAGE_COLORS [ colorScheme ]
117+
118+ // For white-only languages, use minimal theme with no token styles
119+ if ( colorScheme === 'white' ) {
120+ return {
121+ theme : {
122+ plain : { color : '#ffffff' , backgroundColor : 'transparent' } ,
123+ styles : [ ] ,
124+ } ,
125+ tokenColor : '#ffffff' ,
126+ }
127+ }
128+
129+ return {
130+ theme : {
131+ ...baseTheme ,
132+ plain : { ...baseTheme . plain , backgroundColor : 'transparent' } ,
133+ } ,
134+ tokenColor : overrideColor || null ,
135+ }
136+ }
137+
36138export function CodeDemo ( { children, language, rawContent } : CodeDemoProps ) {
37139 const [ copied , setCopied ] = useState ( false )
38140
@@ -50,7 +152,7 @@ export function CodeDemo({ children, language, rawContent }: CodeDemoProps) {
50152 const childrenContent = useMemo ( ( ) => {
51153 // Use rawContent if available (from remark plugin), otherwise fall back to processing children
52154 const content = rawContent || getContent ( children )
53- return content
155+ return trimLeadingWhitespace ( content )
54156 } , [ children , language , rawContent ] )
55157
56158 // Check if this is a mermaid diagram
@@ -83,8 +185,24 @@ export function CodeDemo({ children, language, rawContent }: CodeDemoProps) {
83185 )
84186 }
85187
188+ // Normalize language and get theme/color in one useMemo
189+ const {
190+ normalizedLanguage,
191+ theme : highlightTheme ,
192+ tokenColor,
193+ } = useMemo ( ( ) => {
194+ const normalized = language . toLowerCase ( ) . trim ( )
195+ const normalizedLang = languageMap [ normalized ] || normalized
196+ const { theme, tokenColor } = getLanguageTheme ( normalizedLang )
197+ return {
198+ normalizedLanguage : normalizedLang ,
199+ theme,
200+ tokenColor,
201+ }
202+ } , [ language ] )
203+
86204 return (
87- < div className = "rounded-lg border bg-muted/30 px-4 w-full max-w-80 md:max-w-full my-3 transition-all group hover:bg-muted/40 overflow-x-auto" >
205+ < div className = "rounded-lg border px-4 w-full max-w-80 md:max-w-full my-3 transition-all group overflow-x-auto" >
88206 < div className = "flex items-center justify-between h-6 mt-0.5 mb-0.5" >
89207 < div className = "text-[10px] text-muted-foreground/40 font-mono tracking-wide" >
90208 { language . toLowerCase ( ) }
@@ -103,9 +221,45 @@ export function CodeDemo({ children, language, rawContent }: CodeDemoProps) {
103221 </ div >
104222 { language && < Separator className = "bg-border/20 mb-0.5" /> }
105223 < div >
106- < pre className = "text-[13px] leading-relaxed py-1 bg-transparent text-foreground/90 rounded-lg scrollbar-thin scrollbar-thumb-muted-foreground/10 scrollbar-track-transparent" >
107- < code className = "font-mono" > { childrenContent } </ code >
108- </ pre >
224+ < Highlight
225+ theme = { highlightTheme }
226+ code = { childrenContent }
227+ language = { normalizedLanguage }
228+ >
229+ { ( { className, style, tokens, getLineProps, getTokenProps } ) => {
230+ return (
231+ < pre
232+ className = { `${ className } text-[13px] leading-relaxed py-2 bg-transparent rounded-lg scrollbar-thin scrollbar-thumb-muted-foreground/10 scrollbar-track-transparent` }
233+ style = { {
234+ ...style ,
235+ backgroundColor : 'transparent' ,
236+ color : tokenColor || style . color ,
237+ } }
238+ >
239+ { tokens . map ( ( line , i ) => (
240+ < div key = { i } { ...getLineProps ( { line } ) } >
241+ { line . map ( ( token , key ) => {
242+ const tokenProps = getTokenProps ( { token, key } )
243+ // Override colors for special languages in render loop
244+ const color = tokenColor || tokenProps . style ?. color
245+
246+ return (
247+ < span
248+ key = { key }
249+ { ...tokenProps }
250+ style = { {
251+ ...tokenProps . style ,
252+ color,
253+ } }
254+ />
255+ )
256+ } ) }
257+ </ div >
258+ ) ) }
259+ </ pre >
260+ )
261+ } }
262+ </ Highlight >
109263 </ div >
110264 </ div >
111265 )
0 commit comments