Add code groups with syntax highlighting

This commit is contained in:
Eric Fennis
2023-04-30 20:32:37 +02:00
parent ecf8d9ca2c
commit 3ea6fb4706
8 changed files with 368 additions and 62 deletions
+56
View File
@@ -0,0 +1,56 @@
<script setup lang="ts">
import { computed, defineProps, onMounted } from 'vue'
const props = defineProps<{
groups: string[] | undefined,
groupName: string,
}>()
console.log(props.groups);
const getSaveIdname = (name: string) => {
return name.toLowerCase().replace(/\s/g, '-')
}
const tabs = computed(() => props.groups?.map((group) => {
return {
id: getSaveIdname(group),
name: group,
}
}))
const saveTabId = (id: string) => {
console.log(id);
localStorage.setItem(props.groupName, id)
}
onMounted(() => {
const id = localStorage.getItem(props.groupName)
if (id) {
const tab = document.getElementById(`label-tab-${id}`)
if (tab) {
tab.click()
}
}
})
</script>
<template>
<div class="vp-code-group">
<div class="tabs">
<template v-for="(tab, index) in tabs">
<input
type="radio"
:id="`tab-${tab.id}`"
:name="`group-${groupName}`"
:checked="index === 0"
@change="saveTabId(tab.id)"
>
<label :for="`tab-${tab.id}`" :id="`label-tab-${tab.id}`">{{ tab.name }}</label>
</template>
</div>
<slot />
</div>
</template>
@@ -8,7 +8,6 @@ type CodeExampleType = Record<string, {
code: string,
metastring?: string,
}[],
breakpoint: 'base'|'sm'|'md',
}>
const getIconCodes = (name: string): CodeExampleType => {
@@ -24,7 +23,6 @@ const getIconCodes = (name: string): CodeExampleType => {
`,
},
],
breakpoint: 'base',
},
react: {
title: 'React',
@@ -48,7 +46,6 @@ export default App;
metastring: '{1,4}',
},
],
breakpoint: 'base',
},
vue: {
title: 'Vue 3',
@@ -66,7 +63,6 @@ import { ${pascalCase} } from 'lucide-vue-next';
metastring: '{2,6}',
},
],
breakpoint: 'base',
},
svelte: {
title: 'Svelte',
@@ -86,7 +82,6 @@ import { ${pascalCase} } from 'lucide-svelte';
metastring: '{2,5}',
},
],
breakpoint: 'sm',
},
preact:{
title: 'Preact',
@@ -110,7 +105,6 @@ export default App;
metastring: '{1,4}',
},
],
breakpoint: 'md',
},
solid:{
title: 'Solid',
@@ -134,7 +128,6 @@ export default App;
metastring: '{1,4}',
},
],
breakpoint: 'md',
},
angular: {
title: 'Angular',
@@ -152,10 +145,8 @@ import { LucideAngularModule, ${pascalCase} } from 'lucide-angular';\r
// app.component.html
<lucide-icon name="${name}"></lucide-icon>
`,
metastring: '{2,6}',
},
],
breakpoint: 'base',
},
font: {
title: 'Icon Font',
@@ -170,7 +161,6 @@ import { LucideAngularModule, ${pascalCase} } from 'lucide-angular';\r
`,
},
],
breakpoint: 'md',
},
flutter: {
title: 'Flutter',
@@ -179,10 +169,8 @@ import { LucideAngularModule, ${pascalCase} } from 'lucide-angular';\r
language: 'javascript',
code: `Icon(LucideIcons.${name});
`,
metastring: '{1}',
},
],
breakpoint: 'sm',
},
};
}
+236
View File
@@ -0,0 +1,236 @@
import { startCase, camelCase } from "lodash";
import {
BUNDLED_LANGUAGES,
type IThemeRegistration
} from 'shiki'
import {
getHighlighter,
} from 'shiki-processor'
type CodeExampleType = {
title: string,
lang: string,
codes: {
language?: string,
code: string,
metastring?: string,
}[],
}[]
const getIconCodes = (name: string): CodeExampleType => {
const pascalCase = startCase(camelCase(name)).replace(/\s/g, '')
return [
{
lang: 'html',
title: 'HTML',
codes: [
{
language: 'html',
code: `<i icon-name="${name}"></i>
`,
},
],
},
{
lang: 'tsx',
title: 'React',
codes: [
{
language: 'tsx',
code: `import { ${pascalCase} } from 'lucide-react';
const App = () => {
return (
<${pascalCase}
color="red"
size={48}
strokeWidth={1.5}
/>
);
};
export default App;
`,
metastring: '{1,4}',
},
],
},
{
lang: 'vue',
title: 'Vue 3',
codes: [
{
language: 'vue',
code: `<template>
<${pascalCase} color="red" :size="32" />
</template>
<script setup>
import { ${pascalCase} } from 'lucide-vue-next';
</script>
`,
metastring: '{2,6}',
},
],
},
{
lang: 'svelte',
title: 'Svelte',
codes: [
{
language: 'vue',
code: `<script>
import { ${pascalCase} } from 'lucide-svelte';
</script>
<${pascalCase}
color={red}
size={48}
strokeWidth={1.5}
/>
`,
metastring: '{2,5}',
},
],
},
{
lang: 'preact',
title: 'Preact',
codes: [
{
language: 'tsx',
code: `import { ${pascalCase} } from 'lucide-preact';
const App = () => {
return (
<${pascalCase}
color="red"
size={48}
strokeWidth={1.5}
/>
);
};
export default App;
`,
metastring: '{1,4}',
},
],
},
{
lang: 'solid',
title: 'Solid',
codes: [
{
language: 'tsx',
code: `import { ${pascalCase} } from 'lucide-solid';
const App = () => {
return (
<${pascalCase}
color="red"
size={48}
strokeWidth={1.5}
/>
);
};
export default App;
`,
metastring: '{1,4}',
},
],
},
{
lang: 'angular',
title: 'Angular',
codes: [
{
language: 'tsx',
code: `// app.module.ts
import { LucideAngularModule, ${pascalCase} } from 'lucide-angular';\r
@NgModule({
imports: [
LucideAngularModule.pick({ ${pascalCase} })
],
})
// app.component.html
<lucide-icon name="${name}"></lucide-icon>
`,
},
],
},
{
lang: 'html',
title: 'Icon Font',
codes: [
{
language: 'html',
code: `<style>
@import ('~lucide-static/font/Lucide.css')
</style>
<div class="icon-${name}"></div>
`,
},
],
},
{
lang: 'dart',
title: 'Flutter',
codes: [
{
language: 'dart',
code: `Icon(LucideIcons.${name});
`,
},
],
},
]
}
export type ThemeOptions =
| IThemeRegistration
| { light: IThemeRegistration; dark: IThemeRegistration }
const highLightCode = async (code: string, lang: string, active?: boolean) => {
const highlighter = await getHighlighter({
themes: ['material-theme-palenight'],
langs: [...BUNDLED_LANGUAGES],
processors: []
})
const highlightedCode = highlighter.codeToHtml(code, {
lang,
// lineOptions,
theme: 'material-theme-palenight'
}).replace('background-color: #292D3E', '')
return `<div class="language-${lang} ${active ? 'active' : ''}">
<button title="Copy Code" class="copy"></button>
<span class="lang">${lang}</span>
${highlightedCode}
</div>`
}
export default async function createCodeExamples(name: string) {
const codes = getIconCodes(name);
const codeExamplePromises = codes.map(async (codeTemplate, index) => {
const { title, lang, codes } = codeTemplate;
const isFirst = index === 0;
const code = await highLightCode(codes[0].code, codes[0].language || lang, isFirst);
return {
title,
language: codes[0].language || lang,
code,
};
})
return Promise.all(codeExamplePromises);
}
+18 -41
View File
@@ -8,13 +8,22 @@ prev: false
sidebar: true
---
<script setup>
import { computed } from 'vue'
import IconPreview from '../.vitepress/components/IconPreview.vue'
import IconDetailName from '../.vitepress/components/IconDetailName.vue'
import useIconCodeExamples from '../.vitepress/composables/useIconCodeExample.ts'
import { useData } from 'vitepress'
import CodeGroup from '../.vitepress/components/CodeGroup.vue'
const { params } = useData()
const codeExample = useIconCodeExamples(params.value.name)
const { params } = useData()
const tabs = computed(() => params.value.codeExamples?.map(
(codeExample) => codeExample.title) ?? []
)
const codeExample = computed(() => params.value.codeExamples?.map(
(codeExample) => codeExample.code
).join('') ?? []
)
</script>
<IconPreview
@@ -27,44 +36,12 @@ import { useData } from 'vitepress'
{{$params.name}}
</IconDetailName>
::: code-group
```html-vue [HTML]
{{codeExample.html.codes[0].code}}
```
```jsx-vue [React]
{{codeExample.react.codes[0].code}}
```
```vue-vue [Vue]
{{codeExample.vue.codes[0].code}}
```
```svelte-vue [Svelte]
{{codeExample.svelte.codes[0].code}}
```
```jsx-vue [Preact]
{{codeExample.preact.codes[0].code}}
```
```jsx-vue [Solid]
{{codeExample.solid.codes[0].code}}
```
```tsx-vue [Angular]
{{codeExample.angular.codes[0].code}}
```
```html-vue [Font]
{{codeExample.font.codes[0].code}}
```
```html-vue [Flutter]
{{codeExample.flutter.codes[0].code}}
```
:::
<CodeGroup :groups="tabs" groupName="icon-code-example">
<div
class="blocks"
v-html="codeExample"
/>
</CodeGroup>
<style module>
.preview {
+18 -2
View File
@@ -1,6 +1,9 @@
import fs from "fs";
import path from "path";
import { getAllData } from "../.vitepress/lib/icons";
import createCodeExamples from "../.vitepress/lib/createCodeExamples";
const directory = path.join(process.cwd(), 'icons');
@@ -15,10 +18,23 @@ export function getAllNames() {
export default {
paths: async () => {
const icons = await getAllData()
return icons.map((iconData) => {
const result = await createCodeExamples('activity')
// const codeExamplesPromises = icons.map(async (icon) => {
// return createCodeExamples(icon.name);
// })
// const codeExamples = await Promise.all(codeExamplesPromises);
return icons.map((iconData, index) => {
const params = {
...iconData,
codeExamples: result
};
return {
params: iconData,
params,
}
})
}
+17
View File
@@ -0,0 +1,17 @@
import fs from 'fs'
import path from 'path'
import { getAllData } from '../.vitepress/lib/icons'
import createCodeExamples from "../.vitepress/lib/createCodeExamples";
export default {
async load() {
const result = await createCodeExamples('activity')
console.log(result);
return {
test: true,
}
}
}
+3
View File
@@ -33,11 +33,14 @@
"fuse.js": "^6.5.3",
"js-yaml": "^4.1.0",
"jszip": "^3.7.0",
"lodash": "^4.17.20",
"lodash-es": "^4.17.21",
"lucide-react": "workspace:*",
"lucide-vue-next": "workspace:*",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"shiki": "^0.14.2",
"shiki-processor": "^0.1.3",
"svg-pathdata": "^6.0.3",
"vue": "^3.2.47"
}
+20 -7
View File
@@ -64,6 +64,9 @@ importers:
jszip:
specifier: ^3.7.0
version: 3.10.1
lodash:
specifier: ^4.17.20
version: 4.17.21
lodash-es:
specifier: ^4.17.21
version: 4.17.21
@@ -79,6 +82,12 @@ importers:
react-dom:
specifier: ^18.2.0
version: 18.2.0(react@18.2.0)
shiki:
specifier: ^0.14.2
version: 0.14.2
shiki-processor:
specifier: ^0.1.3
version: 0.1.3(shiki@0.14.2)
svg-pathdata:
specifier: ^6.0.3
version: 6.0.3
@@ -10681,7 +10690,6 @@ packages:
/ansi-sequence-parser@1.1.0:
resolution: {integrity: sha512-lEm8mt52to2fT8GhciPCGeCXACSz2UwIN4X2e2LJSnZ5uAbn2/dsYdOmUXq0AtWS5cpAupysIneExOgH0Vd2TQ==}
dev: true
/ansi-styles@2.2.1:
resolution: {integrity: sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==}
@@ -22249,14 +22257,21 @@ packages:
dev: true
optional: true
/shiki@0.14.1:
resolution: {integrity: sha512-+Jz4nBkCBe0mEDqo1eKRcCdjRtrCjozmcbTUjbPTX7OOJfEbTZzlUWlZtGe3Gb5oV1/jnojhG//YZc3rs9zSEw==}
/shiki-processor@0.1.3(shiki@0.14.2):
resolution: {integrity: sha512-oZqVFKweklwt+fj6yUPb+ffrCpYYoJ4RYxNt7w1+aoHetHq5ZaN6oX6+4HrypOi0s/O6A2neBpn+Xf6bM4KfsQ==}
peerDependencies:
shiki: 0.x
dependencies:
shiki: 0.14.2
dev: false
/shiki@0.14.2:
resolution: {integrity: sha512-ltSZlSLOuSY0M0Y75KA+ieRaZ0Trf5Wl3gutE7jzLuIcWxLp5i/uEnLoQWNvgKXQ5OMpGkJnVMRLAuzjc0LJ2A==}
dependencies:
ansi-sequence-parser: 1.1.0
jsonc-parser: 3.2.0
vscode-oniguruma: 1.7.0
vscode-textmate: 8.0.0
dev: true
/side-channel@1.0.4:
resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==}
@@ -24400,7 +24415,7 @@ packages:
body-scroll-lock: 4.0.0-beta.0
mark.js: 8.11.1
minisearch: 6.0.1
shiki: 0.14.1
shiki: 0.14.2
vite: 4.3.1
vue: 3.2.47
transitivePeerDependencies:
@@ -24556,11 +24571,9 @@ packages:
/vscode-oniguruma@1.7.0:
resolution: {integrity: sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==}
dev: true
/vscode-textmate@8.0.0:
resolution: {integrity: sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==}
dev: true
/vue-demi@0.14.0(vue@3.2.47):
resolution: {integrity: sha512-gt58r2ogsNQeVoQ3EhoUAvUsH9xviydl0dWJj7dabBC/2L4uBId7ujtCwDRD0JhkGsV1i0CtfLAeyYKBht9oWg==}