refactor: code preview (#411)
* feat: generate code dynamically * chore: cleanup and transform path on component * feat: create config sheet * feat: code wrapper * fix: not acting immediately * chore: add key to vnode * chore: add vue-sonner to demos dependencies, add placeholder for codeConfigs * chore: fix wrong icons * chore: improve crawling logic --------- Co-authored-by: Sadegh Barati <sadeghbaratiwork@gmail.com>
This commit is contained in:
parent
4d08adc81e
commit
72f9bd5ef5
|
|
@ -3,16 +3,12 @@ import { defineConfig } from 'vitepress'
|
||||||
import Icons from 'unplugin-icons/vite'
|
import Icons from 'unplugin-icons/vite'
|
||||||
import tailwind from 'tailwindcss'
|
import tailwind from 'tailwindcss'
|
||||||
import autoprefixer from 'autoprefixer'
|
import autoprefixer from 'autoprefixer'
|
||||||
import { createCssVariablesTheme } from 'shiki'
|
import { cssVariables } from './theme/config/shiki'
|
||||||
|
|
||||||
// import { transformerMetaWordHighlight, transformerNotationWordHighlight } from '@shikijs/transformers'
|
// import { transformerMetaWordHighlight, transformerNotationWordHighlight } from '@shikijs/transformers'
|
||||||
import { siteConfig } from './theme/config/site'
|
import { siteConfig } from './theme/config/site'
|
||||||
import ComponentPreviewPlugin from './theme/plugins/previewer'
|
import ComponentPreviewPlugin from './theme/plugins/previewer'
|
||||||
|
import CodeWrapperPlugin from './theme/plugins/codewrapper'
|
||||||
const cssVariables = createCssVariablesTheme({
|
|
||||||
variablePrefix: '--shiki-',
|
|
||||||
variableDefaults: {},
|
|
||||||
})
|
|
||||||
|
|
||||||
// https://vitepress.dev/reference/site-config
|
// https://vitepress.dev/reference/site-config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
|
@ -65,6 +61,7 @@ export default defineConfig({
|
||||||
],
|
],
|
||||||
config(md) {
|
config(md) {
|
||||||
md.use(ComponentPreviewPlugin)
|
md.use(ComponentPreviewPlugin)
|
||||||
|
md.use(CodeWrapperPlugin)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
rewrites: {
|
rewrites: {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { useForm } from 'vee-validate'
|
||||||
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
|
import * as z from 'zod'
|
||||||
|
import { useConfigStore } from '@/stores/config'
|
||||||
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
|
import { Input } from '@/lib/registry/new-york/ui/input'
|
||||||
|
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/lib/registry/new-york/ui/form'
|
||||||
|
import { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger } from '@/lib/registry/new-york/ui/sheet'
|
||||||
|
import RadixIconsGear from '~icons/radix-icons/gear'
|
||||||
|
|
||||||
|
const { codeConfig, setCodeConfig } = useConfigStore()
|
||||||
|
|
||||||
|
const formSchema = toTypedSchema(z.object({
|
||||||
|
prefix: z.string().default(''),
|
||||||
|
componentsPath: z.string().default('@/components'),
|
||||||
|
utilsPath: z.string().default('@/utils'),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const { handleSubmit, setValues } = useForm({
|
||||||
|
validationSchema: formSchema,
|
||||||
|
initialValues: codeConfig.value,
|
||||||
|
})
|
||||||
|
|
||||||
|
const onSubmit = handleSubmit((values) => {
|
||||||
|
setCodeConfig(values)
|
||||||
|
setValues(values)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Sheet
|
||||||
|
@update:open="(open) => {
|
||||||
|
if (open) setValues(codeConfig)
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<SheetTrigger as-child>
|
||||||
|
<Button
|
||||||
|
class="w-9 h-9"
|
||||||
|
:variant="'ghost'"
|
||||||
|
:size="'icon'"
|
||||||
|
>
|
||||||
|
<RadixIconsGear class="w-5 h-5" />
|
||||||
|
</Button>
|
||||||
|
</SheetTrigger>
|
||||||
|
<SheetContent>
|
||||||
|
<form @submit="onSubmit">
|
||||||
|
<SheetHeader>
|
||||||
|
<SheetTitle>Edit code config</SheetTitle>
|
||||||
|
<SheetDescription>
|
||||||
|
Configure how the CodeBlock should render on the site.
|
||||||
|
</SheetDescription>
|
||||||
|
</SheetHeader>
|
||||||
|
|
||||||
|
<div class="my-4">
|
||||||
|
<!-- <FormField v-slot="{ componentField }" name="prefix">
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Prefix</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="" v-bind="componentField" />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription />
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField> -->
|
||||||
|
<FormField v-slot="{ componentField }" name="componentsPath">
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Components Path</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="@/components" v-bind="componentField" />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription />
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
<FormField v-slot="{ componentField }" name="utilsPath">
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Utils Path</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="@/utils" v-bind="componentField" />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription />
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<SheetFooter>
|
||||||
|
<SheetClose as-child>
|
||||||
|
<Button type="submit">
|
||||||
|
Save changes
|
||||||
|
</Button>
|
||||||
|
</SheetClose>
|
||||||
|
</SheetFooter>
|
||||||
|
</form>
|
||||||
|
</SheetContent>
|
||||||
|
</Sheet>
|
||||||
|
</template>
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref, toRefs, watch } from 'vue'
|
||||||
import { Icon } from '@iconify/vue'
|
import { Icon } from '@iconify/vue'
|
||||||
import { makeCodeSandboxParams } from '../utils/codeeditor'
|
import { makeCodeSandboxParams } from '../utils/codeeditor'
|
||||||
import Tooltip from './Tooltip.vue'
|
import Tooltip from './Tooltip.vue'
|
||||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
import { type Style } from '@/lib/registry/styles'
|
import type { Style } from '@/lib/registry/styles'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
name: string
|
name: string
|
||||||
|
|
@ -12,11 +12,12 @@ const props = defineProps<{
|
||||||
style: Style
|
style: Style
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const { code } = toRefs(props)
|
||||||
const sources = ref<Record<string, string>>({})
|
const sources = ref<Record<string, string>>({})
|
||||||
|
|
||||||
onMounted(() => {
|
watch(code, () => {
|
||||||
sources.value['App.vue'] = props.code
|
sources.value['App.vue'] = code.value
|
||||||
})
|
}, { immediate: true })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
46
apps/www/.vitepress/theme/components/CodeWrapper.ts
Normal file
46
apps/www/.vitepress/theme/components/CodeWrapper.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { type VNode, type VNodeArrayChildren, cloneVNode, defineComponent } from 'vue'
|
||||||
|
import { useConfigStore } from '@/stores/config'
|
||||||
|
|
||||||
|
function crawlSpan(children: VNodeArrayChildren, cb: (vnode: VNode) => void) {
|
||||||
|
children.forEach((childNode) => {
|
||||||
|
if (!Array.isArray(childNode) && typeof childNode === 'object') {
|
||||||
|
if (typeof childNode?.children === 'string')
|
||||||
|
cb(childNode)
|
||||||
|
else
|
||||||
|
crawlSpan(childNode?.children as VNodeArrayChildren ?? [], cb)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent(
|
||||||
|
(props, { slots }) => {
|
||||||
|
const { codeConfig } = useConfigStore()
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
const clonedVNode = slots.default?.()?.[0]
|
||||||
|
? cloneVNode(slots.default?.()?.[0], {
|
||||||
|
key: JSON.stringify(codeConfig.value),
|
||||||
|
})
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
// @ts-expect-error cloneVNode
|
||||||
|
const preVNode = [...clonedVNode?.children].find((node: VNode) => node.type === 'pre') as VNode
|
||||||
|
// @ts-expect-error cloneVNode
|
||||||
|
const codeVNode = preVNode.children?.at(0) as VNode
|
||||||
|
|
||||||
|
if (codeVNode) {
|
||||||
|
crawlSpan(codeVNode.children as VNodeArrayChildren, (vnode) => {
|
||||||
|
if (typeof vnode.children === 'string') {
|
||||||
|
vnode.children = vnode.children.replaceAll('@/components', codeConfig.value.componentsPath)
|
||||||
|
vnode.children = vnode.children.replaceAll('@/libs', codeConfig.value.utilsPath)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return clonedVNode
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return slots.default?.()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
@ -1,4 +1,8 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
import { codeToHtml } from 'shiki'
|
||||||
|
import MagicString from 'magic-string'
|
||||||
|
import { cssVariables } from '../config/shiki'
|
||||||
import StyleSwitcher from './StyleSwitcher.vue'
|
import StyleSwitcher from './StyleSwitcher.vue'
|
||||||
import ComponentLoader from './ComponentLoader.vue'
|
import ComponentLoader from './ComponentLoader.vue'
|
||||||
import Stackblitz from './Stackblitz.vue'
|
import Stackblitz from './Stackblitz.vue'
|
||||||
|
|
@ -11,14 +15,35 @@ defineOptions({
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
name: string
|
name: string
|
||||||
align?: 'center' | 'start' | 'end'
|
align?: 'center' | 'start' | 'end'
|
||||||
sfcTsCode?: string
|
|
||||||
sfcTsHtml?: string
|
|
||||||
}>(), { align: 'center' })
|
}>(), { align: 'center' })
|
||||||
|
|
||||||
const { style } = useConfigStore()
|
const { style, codeConfig } = useConfigStore()
|
||||||
|
|
||||||
|
const codeString = ref('')
|
||||||
|
const codeHtml = ref('')
|
||||||
|
|
||||||
|
function transformImportPath(code: string) {
|
||||||
|
const s = new MagicString(code)
|
||||||
|
s.replaceAll(`@/lib/registry/${style.value}`, codeConfig.value.componentsPath)
|
||||||
|
s.replaceAll(`@/lib/utils`, codeConfig.value.utilsPath)
|
||||||
|
return s.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
watch([style, codeConfig], async () => {
|
||||||
|
try {
|
||||||
|
codeString.value = await import(`../../../src/lib/registry/${style.value}/example/${props.name}.vue?raw`).then(res => transformImportPath(res.default.trim()))
|
||||||
|
codeHtml.value = await codeToHtml(codeString.value, {
|
||||||
|
lang: 'vue',
|
||||||
|
theme: cssVariables,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
}, { immediate: true, deep: true })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -47,8 +72,8 @@ const { style } = useConfigStore()
|
||||||
<StyleSwitcher />
|
<StyleSwitcher />
|
||||||
|
|
||||||
<div class="flex items-center gap-x-1">
|
<div class="flex items-center gap-x-1">
|
||||||
<Stackblitz :key="style" :style="style" :name="name" :code="decodeURIComponent(sfcTsCode ?? '')" />
|
<Stackblitz :key="style" :style="style" :name="name" :code="codeString" />
|
||||||
<CodeSandbox :key="style" :style="style" :name="name" :code="decodeURIComponent(sfcTsCode ?? '')" />
|
<CodeSandbox :key="style" :style="style" :name="name" :code="codeString" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|
@ -62,7 +87,7 @@ const { style } = useConfigStore()
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value="code">
|
<TabsContent value="code">
|
||||||
<div v-if="sfcTsHtml" class="language-vue" style="flex: 1;" v-html="decodeURIComponent(sfcTsHtml)" />
|
<div v-if="codeHtml" class="language-vue" style="flex: 1;" v-html="codeHtml" />
|
||||||
<slot v-else />
|
<slot v-else />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref, toRefs, watch } from 'vue'
|
||||||
import { Icon } from '@iconify/vue'
|
import { Icon } from '@iconify/vue'
|
||||||
import { makeStackblitzParams } from '../utils/codeeditor'
|
import { makeStackblitzParams } from '../utils/codeeditor'
|
||||||
import Tooltip from './Tooltip.vue'
|
import Tooltip from './Tooltip.vue'
|
||||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
import { type Style } from '@/lib/registry/styles'
|
import type { Style } from '@/lib/registry/styles'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
name: string
|
name: string
|
||||||
|
|
@ -12,10 +12,14 @@ const props = defineProps<{
|
||||||
style: Style
|
style: Style
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const { code } = toRefs(props)
|
||||||
const sources = ref<Record<string, string>>({})
|
const sources = ref<Record<string, string>>({})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
sources.value['App.vue'] = props.code
|
})
|
||||||
|
|
||||||
|
watch(code, () => {
|
||||||
|
sources.value['App.vue'] = code.value
|
||||||
})
|
})
|
||||||
|
|
||||||
function handleClick() {
|
function handleClick() {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
export { default as CodeWrapper } from './CodeWrapper'
|
||||||
export { default as ComponentPreview } from './ComponentPreview.vue'
|
export { default as ComponentPreview } from './ComponentPreview.vue'
|
||||||
export { default as TabPreview } from './TabPreview.vue'
|
export { default as TabPreview } from './TabPreview.vue'
|
||||||
export { default as TabMarkdown } from './TabMarkdown.vue'
|
export { default as TabMarkdown } from './TabMarkdown.vue'
|
||||||
|
|
|
||||||
6
apps/www/.vitepress/theme/config/shiki.ts
Normal file
6
apps/www/.vitepress/theme/config/shiki.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { createCssVariablesTheme } from 'shiki'
|
||||||
|
|
||||||
|
export const cssVariables = createCssVariablesTheme({
|
||||||
|
variablePrefix: '--shiki-',
|
||||||
|
variableDefaults: {},
|
||||||
|
})
|
||||||
|
|
@ -5,6 +5,7 @@ import { Content, useData, useRoute, useRouter } from 'vitepress'
|
||||||
import { type NavItem, docsConfig } from '../config/docs'
|
import { type NavItem, docsConfig } from '../config/docs'
|
||||||
import Logo from '../components/Logo.vue'
|
import Logo from '../components/Logo.vue'
|
||||||
import MobileNav from '../components/MobileNav.vue'
|
import MobileNav from '../components/MobileNav.vue'
|
||||||
|
import CodeConfigCustomizer from '../components/CodeConfigCustomizer.vue'
|
||||||
|
|
||||||
import Kbd from '../components/Kbd.vue'
|
import Kbd from '../components/Kbd.vue'
|
||||||
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator } from '@/lib/registry/default/ui/command'
|
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator } from '@/lib/registry/default/ui/command'
|
||||||
|
|
@ -126,27 +127,32 @@ watch(() => $route.path, (n) => {
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav class="flex items-center gap-x-1">
|
<nav class="flex items-center">
|
||||||
|
<CodeConfigCustomizer />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
v-for="link in links"
|
v-for="link in links"
|
||||||
:key="link.name"
|
:key="link.name"
|
||||||
as="a"
|
as="a"
|
||||||
|
class="w-9 h-9"
|
||||||
:href="link.href" target="_blank"
|
:href="link.href" target="_blank"
|
||||||
:variant="'ghost'" :size="'icon'"
|
:variant="'ghost'"
|
||||||
|
:size="'icon'"
|
||||||
>
|
>
|
||||||
<component :is="link.icon" class="w-[20px] h-5" />
|
<component :is="link.icon" class="w-5 h-5" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<ClientOnly>
|
<ClientOnly>
|
||||||
<Button
|
<Button
|
||||||
class="flex items-center justify-center"
|
class="w-9 h-9"
|
||||||
aria-label="Toggle dark mode"
|
aria-label="Toggle dark mode"
|
||||||
:variant="'ghost'"
|
:variant="'ghost'"
|
||||||
:size="'icon'" @click="toggleDark()"
|
:size="'icon'"
|
||||||
|
@click="toggleDark()"
|
||||||
>
|
>
|
||||||
<component
|
<component
|
||||||
:is="isDark ? RadixIconsSun : RadixIconsMoon"
|
:is="isDark ? RadixIconsSun : RadixIconsMoon"
|
||||||
class="w-[20px] h-5 text-foreground"
|
class="w-5 h-5 text-foreground"
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</ClientOnly>
|
</ClientOnly>
|
||||||
|
|
@ -292,4 +298,4 @@ watch(() => $route.path, (n) => {
|
||||||
<NewYorkSonner :theme="'system'" />
|
<NewYorkSonner :theme="'system'" />
|
||||||
<NewYorkToaster />
|
<NewYorkToaster />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>../components/CodeConfigCustomizer.vue
|
||||||
|
|
|
||||||
20
apps/www/.vitepress/theme/plugins/codewrapper.ts
Normal file
20
apps/www/.vitepress/theme/plugins/codewrapper.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import type { MarkdownRenderer } from 'vitepress'
|
||||||
|
|
||||||
|
export default function (md: MarkdownRenderer) {
|
||||||
|
const defaultFenceRenderer = md.renderer.rules.fence
|
||||||
|
if (!defaultFenceRenderer)
|
||||||
|
return
|
||||||
|
|
||||||
|
md.renderer.rules.fence = function (tokens, idx, options, env, self) {
|
||||||
|
// Check if this is a code block
|
||||||
|
const token = tokens[idx]
|
||||||
|
const isAllowedExtension = (token.info.includes('vue') || token.info.includes('astro') || token.info.includes('ts'))
|
||||||
|
if (token && token.tag === 'code' && isAllowedExtension) {
|
||||||
|
// Wrap the code block in CodeWrapper
|
||||||
|
return `<CodeWrapper>${defaultFenceRenderer(tokens, idx, options, env, self)}</CodeWrapper>`
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not a code block, return the default rendering
|
||||||
|
return defaultFenceRenderer(tokens, idx, options, env, self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
import { dirname, resolve } from 'node:path'
|
import type { MarkdownRenderer } from 'vitepress'
|
||||||
import fs from 'node:fs'
|
import { parseProps } from './utils'
|
||||||
import type { MarkdownEnv, MarkdownRenderer } from 'vitepress'
|
|
||||||
import { generateDemoComponent, parseProps } from './utils'
|
|
||||||
|
|
||||||
export default function (md: MarkdownRenderer) {
|
export default function (md: MarkdownRenderer) {
|
||||||
function addRenderRule(type: string) {
|
function addRenderRule(type: string) {
|
||||||
|
|
@ -12,31 +10,9 @@ export default function (md: MarkdownRenderer) {
|
||||||
if (!content.match(/^<ComponentPreview\s/) || !content.endsWith('/>'))
|
if (!content.match(/^<ComponentPreview\s/) || !content.endsWith('/>'))
|
||||||
return defaultRender!(tokens, idx, options, env, self)
|
return defaultRender!(tokens, idx, options, env, self)
|
||||||
|
|
||||||
const { path } = env as MarkdownEnv
|
|
||||||
const props = parseProps(content)
|
const props = parseProps(content)
|
||||||
|
const { attrs } = props
|
||||||
const { name, attrs } = props
|
const demoScripts = `<ComponentPreview ${attrs ?? ''} v-bind='${JSON.stringify(props)}'></ComponentPreview>`.trim()
|
||||||
const pluginPath = dirname(__dirname)
|
|
||||||
const srcPath = resolve(pluginPath, '../../src/lib/registry/default/example/', `${name}.vue`).replace(/\\/g, '/')
|
|
||||||
|
|
||||||
if (!fs.existsSync(srcPath)) {
|
|
||||||
console.error(`rendering ${path}: ${srcPath} does not exist`)
|
|
||||||
return defaultRender!(tokens, idx, options, env, self)
|
|
||||||
}
|
|
||||||
|
|
||||||
let code = fs.readFileSync(srcPath, 'utf-8')
|
|
||||||
code = code.replaceAll(
|
|
||||||
'@/lib/registry/default/',
|
|
||||||
'@/components/',
|
|
||||||
)
|
|
||||||
|
|
||||||
const demoScripts = generateDemoComponent(md, env, {
|
|
||||||
attrs,
|
|
||||||
props,
|
|
||||||
code,
|
|
||||||
path: srcPath,
|
|
||||||
})
|
|
||||||
|
|
||||||
return demoScripts
|
return demoScripts
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
// Credit to @hairyf https://github.com/hairyf/markdown-it-vitepress-demo
|
// Credit to @hairyf https://github.com/hairyf/markdown-it-vitepress-demo
|
||||||
|
|
||||||
import type { MarkdownEnv, MarkdownRenderer } from 'vitepress'
|
|
||||||
import { baseParse } from '@vue/compiler-core'
|
import { baseParse } from '@vue/compiler-core'
|
||||||
import type { AttributeNode, ElementNode } from '@vue/compiler-core'
|
import type { AttributeNode, ElementNode } from '@vue/compiler-core'
|
||||||
|
|
||||||
|
|
@ -11,36 +10,6 @@ export interface GenerateOptions {
|
||||||
code: string
|
code: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parse(
|
|
||||||
md: MarkdownRenderer,
|
|
||||||
env: MarkdownEnv,
|
|
||||||
{ code, attrs: _attrs, props }: GenerateOptions,
|
|
||||||
) {
|
|
||||||
const highlightedHtml = md.options.highlight!(code, 'vue', _attrs || '')
|
|
||||||
const sfcTsHtml = highlightedHtml
|
|
||||||
|
|
||||||
const attrs
|
|
||||||
= `sfcTsCode="${encodeURIComponent(code)}"\n`
|
|
||||||
+ `sfcTsHtml="${encodeURIComponent(sfcTsHtml)}"\n`
|
|
||||||
+ `v-bind='${JSON.stringify(props)}'\n`
|
|
||||||
|
|
||||||
return {
|
|
||||||
attrs,
|
|
||||||
highlightedHtml,
|
|
||||||
sfcTsCode: code,
|
|
||||||
sfcTsHtml,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function generateDemoComponent(
|
|
||||||
md: MarkdownRenderer,
|
|
||||||
env: MarkdownEnv,
|
|
||||||
options: GenerateOptions,
|
|
||||||
) {
|
|
||||||
const { attrs } = parse(md, env, options)
|
|
||||||
return `<ComponentPreview \n${attrs}></ComponentPreview>`.trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isUndefined(v: any): v is undefined {
|
export function isUndefined(v: any): v is undefined {
|
||||||
return v === undefined || v === null
|
return v === undefined || v === null
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ export function makeCodeSandboxParams(componentName: string, style: Style, sourc
|
||||||
export function makeStackblitzParams(componentName: string, style: Style, sources: Record<string, string>) {
|
export function makeStackblitzParams(componentName: string, style: Style, sources: Record<string, string>) {
|
||||||
const files: Record<string, string> = {}
|
const files: Record<string, string> = {}
|
||||||
Object.entries(constructFiles(componentName, style, sources)).forEach(([k, v]) => (files[`${k}`] = typeof v.content === 'object' ? JSON.stringify(v.content, null, 2) : v.content))
|
Object.entries(constructFiles(componentName, style, sources)).forEach(([k, v]) => (files[`${k}`] = typeof v.content === 'object' ? JSON.stringify(v.content, null, 2) : v.content))
|
||||||
|
console.log({ files, componentName, style, sources })
|
||||||
return sdk.openProject({
|
return sdk.openProject({
|
||||||
title: `${componentName} - Radix Vue`,
|
title: `${componentName} - Radix Vue`,
|
||||||
files,
|
files,
|
||||||
|
|
@ -91,6 +92,7 @@ function constructFiles(componentName: string, style: Style, sources: Record<str
|
||||||
'shadcn-vue': 'latest',
|
'shadcn-vue': 'latest',
|
||||||
'typescript': 'latest',
|
'typescript': 'latest',
|
||||||
'vaul-vue': 'latest',
|
'vaul-vue': 'latest',
|
||||||
|
'vue-sonner': 'latest',
|
||||||
'@unovis/vue': 'latest',
|
'@unovis/vue': 'latest',
|
||||||
'@unovis/ts': 'latest',
|
'@unovis/ts': 'latest',
|
||||||
}
|
}
|
||||||
|
|
@ -104,19 +106,12 @@ function constructFiles(componentName: string, style: Style, sources: Record<str
|
||||||
'autoprefixer': 'latest',
|
'autoprefixer': 'latest',
|
||||||
}
|
}
|
||||||
|
|
||||||
const transformImportPath = (code: string) => {
|
|
||||||
let parsed = code
|
|
||||||
parsed = parsed.replaceAll(`@/lib/registry/${style}`, '@/components')
|
|
||||||
parsed = parsed.replaceAll('@/lib/utils', '@/utils')
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
|
|
||||||
const componentFiles = Object.keys(sources).filter(key => key.endsWith('.vue') && key !== 'index.vue')
|
const componentFiles = Object.keys(sources).filter(key => key.endsWith('.vue') && key !== 'index.vue')
|
||||||
const components: Record<string, any> = {}
|
const components: Record<string, any> = {}
|
||||||
componentFiles.forEach((i) => {
|
componentFiles.forEach((i) => {
|
||||||
components[`src/${i}`] = {
|
components[`src/${i}`] = {
|
||||||
isBinary: false,
|
isBinary: false,
|
||||||
content: transformImportPath(sources[i]),
|
content: sources[i],
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@
|
||||||
"embla-carousel-autoplay": "^8.0.0",
|
"embla-carousel-autoplay": "^8.0.0",
|
||||||
"embla-carousel-vue": "^8.0.0",
|
"embla-carousel-vue": "^8.0.0",
|
||||||
"lucide-vue-next": "^0.350.0",
|
"lucide-vue-next": "^0.350.0",
|
||||||
|
"magic-string": "^0.30.8",
|
||||||
"radix-vue": "^1.5.2",
|
"radix-vue": "^1.5.2",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"v-calendar": "^3.1.2",
|
"v-calendar": "^3.1.2",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { MagnifyingGlassIcon } from '@radix-icons/vue'
|
import { Search } from 'lucide-vue-next'
|
||||||
import { Input } from '@/lib/registry/default/ui/input'
|
import { Input } from '@/lib/registry/default/ui/input'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -7,7 +7,7 @@ import { Input } from '@/lib/registry/default/ui/input'
|
||||||
<div class="relative w-full max-w-sm items-center">
|
<div class="relative w-full max-w-sm items-center">
|
||||||
<Input id="search" type="text" placeholder="Search..." class="pl-10" />
|
<Input id="search" type="text" placeholder="Search..." class="pl-10" />
|
||||||
<span class="absolute start-0 inset-y-0 flex items-center justify-center px-2">
|
<span class="absolute start-0 inset-y-0 flex items-center justify-center px-2">
|
||||||
<MagnifyingGlassIcon class="size-6 text-muted-foreground" />
|
<Search class="size-6 text-muted-foreground" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { Minus, Plus } from 'lucide-vue-next'
|
import { MinusIcon, PlusIcon } from '@radix-icons/vue'
|
||||||
import { VisStackedBar, VisXYContainer } from '@unovis/vue'
|
import { VisStackedBar, VisXYContainer } from '@unovis/vue'
|
||||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
import {
|
import {
|
||||||
|
|
@ -56,7 +56,7 @@ const data = [
|
||||||
:disabled="goal <= 200"
|
:disabled="goal <= 200"
|
||||||
@click="goal -= 10"
|
@click="goal -= 10"
|
||||||
>
|
>
|
||||||
<Minus class="h-4 w-4" />
|
<MinusIcon class="h-4 w-4" />
|
||||||
<span class="sr-only">Decrease</span>
|
<span class="sr-only">Decrease</span>
|
||||||
</Button>
|
</Button>
|
||||||
<div class="flex-1 text-center">
|
<div class="flex-1 text-center">
|
||||||
|
|
@ -74,7 +74,7 @@ const data = [
|
||||||
:disabled="goal >= 400"
|
:disabled="goal >= 400"
|
||||||
@click="goal += 10"
|
@click="goal += 10"
|
||||||
>
|
>
|
||||||
<Plus class="h-4 w-4" />
|
<PlusIcon class="h-4 w-4" />
|
||||||
<span class="sr-only">Increase</span>
|
<span class="sr-only">Increase</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useSessionStorage } from '@vueuse/core'
|
import { useSessionStorage, useStorage } from '@vueuse/core'
|
||||||
import { useData } from 'vitepress'
|
import { useData } from 'vitepress'
|
||||||
import { type Theme, themes } from './../lib/registry/themes'
|
import { type Theme, themes } from './../lib/registry/themes'
|
||||||
import { type Style, styles } from '@/lib/registry/styles'
|
import { type Style, styles } from '@/lib/registry/styles'
|
||||||
|
|
@ -10,6 +10,12 @@ interface Config {
|
||||||
style: Style
|
style: Style
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CodeConfig {
|
||||||
|
prefix: string
|
||||||
|
componentsPath: string
|
||||||
|
utilsPath: string
|
||||||
|
}
|
||||||
|
|
||||||
export const RADII = [0, 0.25, 0.5, 0.75, 1]
|
export const RADII = [0, 0.25, 0.5, 0.75, 1]
|
||||||
|
|
||||||
export function useConfigStore() {
|
export function useConfigStore() {
|
||||||
|
|
@ -18,6 +24,11 @@ export function useConfigStore() {
|
||||||
radius: 0.5,
|
radius: 0.5,
|
||||||
style: styles[0].name,
|
style: styles[0].name,
|
||||||
})
|
})
|
||||||
|
const codeConfig = useStorage<CodeConfig>('code-config', {
|
||||||
|
prefix: '',
|
||||||
|
componentsPath: '@/components',
|
||||||
|
utilsPath: '@/utils',
|
||||||
|
})
|
||||||
|
|
||||||
const themeClass = computed(() => `theme-${config.value.theme}`)
|
const themeClass = computed(() => `theme-${config.value.theme}`)
|
||||||
|
|
||||||
|
|
@ -40,5 +51,21 @@ export function useConfigStore() {
|
||||||
})`
|
})`
|
||||||
})
|
})
|
||||||
|
|
||||||
return { config, theme, setTheme, radius, setRadius, themeClass, style, themePrimary }
|
const setCodeConfig = (payload: CodeConfig) => {
|
||||||
|
codeConfig.value = payload
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
config,
|
||||||
|
theme,
|
||||||
|
setTheme,
|
||||||
|
radius,
|
||||||
|
setRadius,
|
||||||
|
themeClass,
|
||||||
|
style,
|
||||||
|
themePrimary,
|
||||||
|
|
||||||
|
codeConfig,
|
||||||
|
setCodeConfig,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,6 @@
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"sourceMap": true
|
"sourceMap": true
|
||||||
},
|
},
|
||||||
"include": ["src", ".vitepress/**/*.vue", "scripts/build-registry.ts", ".vitepress/**/*.mts", ".vitepress/**/*.vue", "src/lib/**/*"],
|
"include": ["src", ".vitepress/**/*.vue", "scripts/build-registry.ts", ".vitepress/**/*.mts", ".vitepress/**/*.ts", "src/lib/**/*"],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,9 @@ importers:
|
||||||
lucide-vue-next:
|
lucide-vue-next:
|
||||||
specifier: ^0.350.0
|
specifier: ^0.350.0
|
||||||
version: 0.350.0(vue@3.4.21)
|
version: 0.350.0(vue@3.4.21)
|
||||||
|
magic-string:
|
||||||
|
specifier: ^0.30.8
|
||||||
|
version: 0.30.8
|
||||||
radix-vue:
|
radix-vue:
|
||||||
specifier: ^1.5.2
|
specifier: ^1.5.2
|
||||||
version: 1.5.2(vue@3.4.21)
|
version: 1.5.2(vue@3.4.21)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user