11import { Icon } from "@iconify/react" ;
22import clsx from "clsx" ;
33import { useEffect , useRef , useState , type PropsWithChildren } from "react" ;
4+ import config from "../../../../../explainer.config" ;
45
56export type CodeGroupProps = {
67 labels ?: string | string [ ] ;
78 languages ?: string | string [ ] ;
89 codes ?: string | string [ ] ;
910} ;
1011
11- export default function CodeGroupComponent ( {
12- children,
13- labels : propLabels ,
14- languages : propLanguages ,
15- codes : propCodes ,
16- } : PropsWithChildren < CodeGroupProps > ) {
12+ export default function CodeGroupComponent (
13+ props : PropsWithChildren < CodeGroupProps > ,
14+ ) {
15+ const icons = config . content . icons ;
1716 const [ activeTab , setActiveTab ] = useState ( 0 ) ;
1817 const [ tabs , setTabs ] = useState <
1918 { label : string ; language : string ; icon : string } [ ]
@@ -32,9 +31,9 @@ export default function CodeGroupComponent({
3231 return [ ] ;
3332 } ;
3433
35- const parsedCodes = parseProp ( propCodes ) ;
36- const parsedLabels = parseProp ( propLabels ) ;
37- const parsedLanguages = parseProp ( propLanguages ) ;
34+ const parsedCodes = parseProp ( props . codes ) ;
35+ const parsedLabels = parseProp ( props . labels ) ;
36+ const parsedLanguages = parseProp ( props . languages ) ;
3837
3938 const isPropsMode = parsedCodes . length > 0 ;
4039
@@ -53,17 +52,31 @@ export default function CodeGroupComponent({
5352
5453 const language = el . getAttribute ( "data-language" ) || "text" ;
5554
55+ let icon = getCurrentIcon ( label , language ) ;
56+
57+ if ( [ "bash" , "sh" , "shell" ] . includes ( language . toLowerCase ( ) ) ) {
58+ const text = el . textContent ?. trim ( ) || "" ;
59+ const match = text . replace ( / ^ \$ \s * / , "" ) . match ( / ^ ( \w + ) / ) ;
60+ if ( match ) {
61+ const cmd = match [ 1 ] . toLowerCase ( ) ;
62+ if ( [ "npm" , "npx" , "pnpm" , "yarn" , "bun" ] . includes ( cmd ) ) {
63+ const cmdIcon = icons [ cmd as keyof typeof icons ] ;
64+ if ( cmdIcon ) icon = cmdIcon ;
65+ }
66+ }
67+ }
68+
5669 newTabs . push ( {
5770 label,
5871 language,
59- icon : getCurrentIcon ( label , language ) ,
72+ icon,
6073 } ) ;
6174 } ) ;
6275
6376 if ( JSON . stringify ( newTabs ) !== JSON . stringify ( tabs ) ) {
6477 setTabs ( newTabs ) ;
6578 }
66- } , [ children , isPropsMode ] ) ;
79+ } , [ props . children , isPropsMode ] ) ;
6780
6881 useEffect ( ( ) => {
6982 if ( isPropsMode || ! containerRef . current ) return ;
@@ -99,11 +112,30 @@ export default function CodeGroupComponent({
99112
100113 // Render for Props Mode (rehype plugin generated)
101114 if ( isPropsMode ) {
102- const tabsData = parsedCodes . map ( ( code : string , i : number ) => ( {
103- label : parsedLabels [ i ] || parsedLanguages [ i ] || "Code" ,
104- language : parsedLanguages [ i ] ,
105- content : code ,
106- } ) ) ;
115+ const tabsData = parsedCodes . map ( ( code : string , i : number ) => {
116+ const language = parsedLanguages [ i ] || "text" ;
117+ const label = parsedLabels [ i ] || language || "Code" ;
118+ let icon = getCurrentIcon ( label , language ) ;
119+
120+ if ( [ "bash" , "sh" , "shell" ] . includes ( language . toLowerCase ( ) ) ) {
121+ const text = code . replace ( / < [ ^ > ] + > / g, "" ) . trim ( ) ;
122+ const match = text . replace ( / ^ \$ \s * / , "" ) . match ( / ^ ( \w + ) / ) ;
123+ if ( match ) {
124+ const cmd = match [ 1 ] . toLowerCase ( ) ;
125+ if ( [ "npm" , "pnpm" , "yarn" , "bun" ] . includes ( cmd ) ) {
126+ const cmdIcon = icons [ cmd as keyof typeof icons ] ;
127+ if ( cmdIcon ) icon = cmdIcon ;
128+ }
129+ }
130+ }
131+
132+ return {
133+ label,
134+ language,
135+ icon,
136+ content : code ,
137+ } ;
138+ } ) ;
107139
108140 return (
109141 < div className = "code-group border rounded-md overflow-hidden mb-5 bg-background" >
@@ -120,7 +152,7 @@ export default function CodeGroupComponent({
120152 ) }
121153 type = "button"
122154 >
123- < Icon icon = { getCurrentIcon ( tab . label , tab . language ) } width = { 16 } />
155+ < Icon icon = { tab . icon } width = { 16 } />
124156 < span > { tab . label } </ span >
125157 </ button >
126158 ) ) }
@@ -156,47 +188,8 @@ export default function CodeGroupComponent({
156188 </ div >
157189 ) }
158190 < div ref = { containerRef } className = "code-group-content" >
159- { children }
191+ { props . children }
160192 </ div >
161193 </ div >
162194 ) ;
163195}
164-
165- const icons = {
166- markdown : "devicon:markdown" ,
167- mdx : "devicon:markdown" ,
168- html : "devicon:html5" ,
169- css : "devicon:css3" ,
170- javascript : "devicon:javascript" ,
171- js : "devicon:javascript" ,
172- typescript : "devicon:typescript" ,
173- ts : "devicon:typescript" ,
174- python : "devicon:python" ,
175- py : "devicon:python" ,
176- dart : "devicon:dart" ,
177- rust : "catppuccin:rust" ,
178- rs : "catppuccin:rust" ,
179- npm : "devicon:npm" ,
180- yarn : "devicon:yarn" ,
181- pnpm : "devicon:pnpm" ,
182- bun : "devicon:bun" ,
183- vite : "devicon:vite" ,
184- "tailwind.config.js" : "devicon:tailwindcss" ,
185- "tailwind.config.ts" : "devicon:tailwindcss" ,
186- react : "devicon:react" ,
187- nextjs : "devicon:nextjs" ,
188- svelte : "devicon:svelte" ,
189- vue : "devicon:vuejs" ,
190- go : "devicon:go" ,
191- bash : "devicon:bash" ,
192- sh : "devicon:bash" ,
193- shell : "devicon:bash" ,
194- sql : "devicon:azuresqldatabase" ,
195- yaml : "devicon:yaml" ,
196- yml : "devicon:yaml" ,
197- json : "devicon:json" ,
198- dockerfile : "devicon:docker" ,
199- git : "devicon:git" ,
200- github : "devicon:github" ,
201- gitlab : "devicon:gitlab" ,
202- } ;
0 commit comments