Skip to content

Commit b96530b

Browse files
committed
wip
1 parent a071f0c commit b96530b

5 files changed

Lines changed: 215 additions & 69 deletions

File tree

content/docs/documentation/getting-started/installation.mdx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ git clone git@github.com:LeadcodeDev/explainer.git
2121

2222
You can start by create new repository from the template.
2323

24-
```bash
24+
```bash [npx]
2525
npx create-astro@latest --template https://github.com/LeadcodeDev/explainer <project_name>
2626
```
2727

@@ -35,17 +35,17 @@ When you create a new repository in Github, you can use this project as a templa
3535

3636
In your local repository, run the following command to install the dependencies.
3737

38-
:::codegroup labels=[pnpm, npm, yarn]
38+
:::codegroup
3939

40-
```bash
40+
```bash [pnpm]
4141
pnpm install
4242
```
4343

44-
```bash
44+
```bash [npm]
4545
npm install
4646
```
4747

48-
```bash
48+
```bash [yarn]
4949
yarn install
5050
```
5151

explainer.config.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import CardGroup from "@/components/content/card-group/card-group.astro";
22
import Card from "@/components/content/card-group/card.astro";
33
import CodeGroup from "@/components/content/code-group/code-group.astro";
4+
import CodeBlock from "@/components/content/codeblock.astro";
45
import { defineExplainerConfig } from "@/utils";
56

67
export default defineExplainerConfig({
@@ -47,6 +48,46 @@ export default defineExplainerConfig({
4748
card: Card,
4849
codegroup: CodeGroup,
4950
"code-group": CodeGroup,
51+
pre: CodeBlock,
52+
},
53+
icons: {
54+
markdown: "devicon:markdown",
55+
mdx: "devicon:markdown",
56+
html: "devicon:html5",
57+
css: "devicon:css3",
58+
javascript: "devicon:javascript",
59+
js: "devicon:javascript",
60+
typescript: "devicon:typescript",
61+
ts: "devicon:typescript",
62+
python: "devicon:python",
63+
py: "devicon:python",
64+
dart: "devicon:dart",
65+
rust: "catppuccin:rust",
66+
rs: "catppuccin:rust",
67+
npm: "devicon:npm",
68+
npx: "devicon:npm",
69+
yarn: "devicon:yarn",
70+
pnpm: "devicon:pnpm",
71+
bun: "devicon:bun",
72+
vite: "devicon:vite",
73+
"tailwind.config.js": "devicon:tailwindcss",
74+
"tailwind.config.ts": "devicon:tailwindcss",
75+
react: "devicon:react",
76+
nextjs: "devicon:nextjs",
77+
svelte: "devicon:svelte",
78+
vue: "devicon:vuejs",
79+
go: "devicon:go",
80+
bash: "devicon:bash",
81+
sh: "devicon:bash",
82+
shell: "devicon:bash",
83+
sql: "devicon:azuresqldatabase",
84+
yaml: "devicon:yaml",
85+
yml: "devicon:yaml",
86+
json: "devicon:json",
87+
dockerfile: "devicon:docker",
88+
git: "devicon:git",
89+
github: "devicon:github",
90+
gitlab: "devicon:gitlab",
5091
},
5192
},
5293
});

src/lib/components/content/code-group/code-group.tsx

Lines changed: 50 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
import { Icon } from "@iconify/react";
22
import clsx from "clsx";
33
import { useEffect, useRef, useState, type PropsWithChildren } from "react";
4+
import config from "../../../../../explainer.config";
45

56
export 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-
};
Lines changed: 118 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,121 @@
11
---
2-
const props = Astro.props;
2+
import { Icon } from "@iconify/react";
3+
4+
interface Props {
5+
class?: string;
6+
"data-language"?: string;
7+
"data-label"?: string;
8+
[key: string]: any;
9+
}
10+
11+
const {
12+
class: className,
13+
"data-language": language,
14+
"data-label": label,
15+
...props
16+
} = Astro.props;
17+
18+
const icons: Record<string, string> = {
19+
markdown: "devicon:markdown",
20+
mdx: "devicon:markdown",
21+
html: "devicon:html5",
22+
css: "devicon:css3",
23+
javascript: "devicon:javascript",
24+
js: "devicon:javascript",
25+
typescript: "devicon:typescript",
26+
ts: "devicon:typescript",
27+
python: "devicon:python",
28+
py: "devicon:python",
29+
dart: "devicon:dart",
30+
rust: "catppuccin:rust",
31+
rs: "catppuccin:rust",
32+
npm: "devicon:npm",
33+
npx: "devicon:npm",
34+
yarn: "devicon:yarn",
35+
pnpm: "devicon:pnpm",
36+
bun: "devicon:bun",
37+
vite: "devicon:vite",
38+
"tailwind.config.js": "devicon:tailwindcss",
39+
"tailwind.config.ts": "devicon:tailwindcss",
40+
react: "devicon:react",
41+
nextjs: "devicon:nextjs",
42+
svelte: "devicon:svelte",
43+
vue: "devicon:vuejs",
44+
go: "devicon:go",
45+
bash: "devicon:bash",
46+
sh: "devicon:bash",
47+
shell: "devicon:bash",
48+
sql: "devicon:azuresqldatabase",
49+
yaml: "devicon:yaml",
50+
yml: "devicon:yaml",
51+
json: "devicon:json",
52+
dockerfile: "devicon:docker",
53+
git: "devicon:git",
54+
github: "devicon:github",
55+
gitlab: "devicon:gitlab",
56+
};
57+
58+
const displayLabel = label || language || "text";
59+
const displayLanguage = language || "text";
60+
61+
let icon =
62+
icons[displayLabel.toLowerCase()] ||
63+
icons[displayLanguage.toLowerCase()] ||
64+
"mdi:code-tags";
65+
66+
if (["bash", "sh", "shell"].includes(displayLanguage.toLowerCase())) {
67+
const html = await Astro.slots.render("default");
68+
const text = html.replace(/<[^>]+>/g, "").trim();
69+
const match = text.match(/^(\w+)/);
70+
if (match) {
71+
const cmd = match[1].toLowerCase();
72+
if (["npm", "npx", "pnpm", "yarn", "bun"].includes(cmd) && icons[cmd]) {
73+
icon = icons[cmd];
74+
}
75+
}
76+
}
377
---
478

5-
<p>
6-
<pre
7-
class="astro-code astro-code-themes github-light catppuccin-frappe has-highlighted"
8-
set:html={props.html}
9-
/>
10-
</p>
79+
<div
80+
class="code-block group relative my-4 rounded-lg border bg-background overflow-hidden text-sm"
81+
>
82+
{
83+
label && (
84+
<div class="code-block-header flex items-center gap-2 border-b bg-background px-4 py-2.5">
85+
<Icon
86+
client:load
87+
icon={icon}
88+
className="size-4 text-muted-foreground"
89+
/>
90+
<span class="font-medium text-muted-foreground">{displayLabel}</span>
91+
</div>
92+
)
93+
}
94+
<div class="relative">
95+
<pre
96+
class={className}
97+
data-language={language}
98+
data-label={label}
99+
{...props}><slot /></pre>
100+
</div>
101+
</div>
102+
103+
<style is:global>
104+
.code-block pre {
105+
margin: 0 !important;
106+
padding: 1rem !important;
107+
border-radius: 0 !important;
108+
border: none !important;
109+
}
110+
111+
.code-group .code-block {
112+
margin: 0;
113+
border: none;
114+
border-radius: 0;
115+
background: transparent;
116+
}
117+
118+
.code-group .code-block .code-block-header {
119+
display: none;
120+
}
121+
</style>

src/lib/utils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ type ExplainerConfig = {
4242
href: string;
4343
}[];
4444
content: {
45+
icons: Record<string, string>;
4546
components: {
4647
[key: string]: (...props: any[]) => any;
4748
};

0 commit comments

Comments
 (0)