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:
zernonia 2024-03-14 18:28:13 +08:00 committed by GitHub
parent 4d08adc81e
commit 72f9bd5ef5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 278 additions and 103 deletions

View File

@ -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: {

View File

@ -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>

View File

@ -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>

View 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?.()
}
}
},
)

View File

@ -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>

View File

@ -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() {

View File

@ -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'

View File

@ -0,0 +1,6 @@
import { createCssVariablesTheme } from 'shiki'
export const cssVariables = createCssVariablesTheme({
variablePrefix: '--shiki-',
variableDefaults: {},
})

View File

@ -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

View 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)
}
}

View File

@ -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
} }
} }

View File

@ -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
} }

View File

@ -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],
} }
}) })

View File

@ -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",

View File

@ -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>

View File

@ -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>

View File

@ -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,
}
} }

View File

@ -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"]
} }

View File

@ -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)