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 tailwind from 'tailwindcss'
|
||||
import autoprefixer from 'autoprefixer'
|
||||
import { createCssVariablesTheme } from 'shiki'
|
||||
import { cssVariables } from './theme/config/shiki'
|
||||
|
||||
// import { transformerMetaWordHighlight, transformerNotationWordHighlight } from '@shikijs/transformers'
|
||||
import { siteConfig } from './theme/config/site'
|
||||
import ComponentPreviewPlugin from './theme/plugins/previewer'
|
||||
|
||||
const cssVariables = createCssVariablesTheme({
|
||||
variablePrefix: '--shiki-',
|
||||
variableDefaults: {},
|
||||
})
|
||||
import CodeWrapperPlugin from './theme/plugins/codewrapper'
|
||||
|
||||
// https://vitepress.dev/reference/site-config
|
||||
export default defineConfig({
|
||||
|
|
@ -65,6 +61,7 @@ export default defineConfig({
|
|||
],
|
||||
config(md) {
|
||||
md.use(ComponentPreviewPlugin)
|
||||
md.use(CodeWrapperPlugin)
|
||||
},
|
||||
},
|
||||
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">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { onMounted, ref, toRefs, watch } from 'vue'
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { makeCodeSandboxParams } from '../utils/codeeditor'
|
||||
import Tooltip from './Tooltip.vue'
|
||||
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<{
|
||||
name: string
|
||||
|
|
@ -12,11 +12,12 @@ const props = defineProps<{
|
|||
style: Style
|
||||
}>()
|
||||
|
||||
const { code } = toRefs(props)
|
||||
const sources = ref<Record<string, string>>({})
|
||||
|
||||
onMounted(() => {
|
||||
sources.value['App.vue'] = props.code
|
||||
})
|
||||
watch(code, () => {
|
||||
sources.value['App.vue'] = code.value
|
||||
}, { immediate: true })
|
||||
</script>
|
||||
|
||||
<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">
|
||||
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 ComponentLoader from './ComponentLoader.vue'
|
||||
import Stackblitz from './Stackblitz.vue'
|
||||
|
|
@ -11,14 +15,35 @@ defineOptions({
|
|||
inheritAttrs: false,
|
||||
})
|
||||
|
||||
withDefaults(defineProps<{
|
||||
const props = withDefaults(defineProps<{
|
||||
name: string
|
||||
align?: 'center' | 'start' | 'end'
|
||||
sfcTsCode?: string
|
||||
sfcTsHtml?: string
|
||||
}>(), { 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>
|
||||
|
||||
<template>
|
||||
|
|
@ -47,8 +72,8 @@ const { style } = useConfigStore()
|
|||
<StyleSwitcher />
|
||||
|
||||
<div class="flex items-center gap-x-1">
|
||||
<Stackblitz :key="style" :style="style" :name="name" :code="decodeURIComponent(sfcTsCode ?? '')" />
|
||||
<CodeSandbox :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="codeString" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
|
@ -62,7 +87,7 @@ const { style } = useConfigStore()
|
|||
</div>
|
||||
</TabsContent>
|
||||
<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 />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { onMounted, ref, toRefs, watch } from 'vue'
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { makeStackblitzParams } from '../utils/codeeditor'
|
||||
import Tooltip from './Tooltip.vue'
|
||||
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<{
|
||||
name: string
|
||||
|
|
@ -12,10 +12,14 @@ const props = defineProps<{
|
|||
style: Style
|
||||
}>()
|
||||
|
||||
const { code } = toRefs(props)
|
||||
const sources = ref<Record<string, string>>({})
|
||||
|
||||
onMounted(() => {
|
||||
sources.value['App.vue'] = props.code
|
||||
})
|
||||
|
||||
watch(code, () => {
|
||||
sources.value['App.vue'] = code.value
|
||||
})
|
||||
|
||||
function handleClick() {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
export { default as CodeWrapper } from './CodeWrapper'
|
||||
export { default as ComponentPreview } from './ComponentPreview.vue'
|
||||
export { default as TabPreview } from './TabPreview.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 Logo from '../components/Logo.vue'
|
||||
import MobileNav from '../components/MobileNav.vue'
|
||||
import CodeConfigCustomizer from '../components/CodeConfigCustomizer.vue'
|
||||
|
||||
import Kbd from '../components/Kbd.vue'
|
||||
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator } from '@/lib/registry/default/ui/command'
|
||||
|
|
@ -126,27 +127,32 @@ watch(() => $route.path, (n) => {
|
|||
</Button>
|
||||
</div>
|
||||
|
||||
<nav class="flex items-center gap-x-1">
|
||||
<nav class="flex items-center">
|
||||
<CodeConfigCustomizer />
|
||||
|
||||
<Button
|
||||
v-for="link in links"
|
||||
:key="link.name"
|
||||
as="a"
|
||||
class="w-9 h-9"
|
||||
: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>
|
||||
|
||||
<ClientOnly>
|
||||
<Button
|
||||
class="flex items-center justify-center"
|
||||
class="w-9 h-9"
|
||||
aria-label="Toggle dark mode"
|
||||
:variant="'ghost'"
|
||||
:size="'icon'" @click="toggleDark()"
|
||||
:size="'icon'"
|
||||
@click="toggleDark()"
|
||||
>
|
||||
<component
|
||||
:is="isDark ? RadixIconsSun : RadixIconsMoon"
|
||||
class="w-[20px] h-5 text-foreground"
|
||||
class="w-5 h-5 text-foreground"
|
||||
/>
|
||||
</Button>
|
||||
</ClientOnly>
|
||||
|
|
@ -292,4 +298,4 @@ watch(() => $route.path, (n) => {
|
|||
<NewYorkSonner :theme="'system'" />
|
||||
<NewYorkToaster />
|
||||
</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 fs from 'node:fs'
|
||||
import type { MarkdownEnv, MarkdownRenderer } from 'vitepress'
|
||||
import { generateDemoComponent, parseProps } from './utils'
|
||||
import type { MarkdownRenderer } from 'vitepress'
|
||||
import { parseProps } from './utils'
|
||||
|
||||
export default function (md: MarkdownRenderer) {
|
||||
function addRenderRule(type: string) {
|
||||
|
|
@ -12,31 +10,9 @@ export default function (md: MarkdownRenderer) {
|
|||
if (!content.match(/^<ComponentPreview\s/) || !content.endsWith('/>'))
|
||||
return defaultRender!(tokens, idx, options, env, self)
|
||||
|
||||
const { path } = env as MarkdownEnv
|
||||
const props = parseProps(content)
|
||||
|
||||
const { name, attrs } = props
|
||||
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,
|
||||
})
|
||||
|
||||
const { attrs } = props
|
||||
const demoScripts = `<ComponentPreview ${attrs ?? ''} v-bind='${JSON.stringify(props)}'></ComponentPreview>`.trim()
|
||||
return demoScripts
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
// Credit to @hairyf https://github.com/hairyf/markdown-it-vitepress-demo
|
||||
|
||||
import type { MarkdownEnv, MarkdownRenderer } from 'vitepress'
|
||||
import { baseParse } from '@vue/compiler-core'
|
||||
import type { AttributeNode, ElementNode } from '@vue/compiler-core'
|
||||
|
||||
|
|
@ -11,36 +10,6 @@ export interface GenerateOptions {
|
|||
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 {
|
||||
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>) {
|
||||
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))
|
||||
console.log({ files, componentName, style, sources })
|
||||
return sdk.openProject({
|
||||
title: `${componentName} - Radix Vue`,
|
||||
files,
|
||||
|
|
@ -91,6 +92,7 @@ function constructFiles(componentName: string, style: Style, sources: Record<str
|
|||
'shadcn-vue': 'latest',
|
||||
'typescript': 'latest',
|
||||
'vaul-vue': 'latest',
|
||||
'vue-sonner': 'latest',
|
||||
'@unovis/vue': 'latest',
|
||||
'@unovis/ts': 'latest',
|
||||
}
|
||||
|
|
@ -104,19 +106,12 @@ function constructFiles(componentName: string, style: Style, sources: Record<str
|
|||
'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 components: Record<string, any> = {}
|
||||
componentFiles.forEach((i) => {
|
||||
components[`src/${i}`] = {
|
||||
isBinary: false,
|
||||
content: transformImportPath(sources[i]),
|
||||
content: sources[i],
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
"embla-carousel-autoplay": "^8.0.0",
|
||||
"embla-carousel-vue": "^8.0.0",
|
||||
"lucide-vue-next": "^0.350.0",
|
||||
"magic-string": "^0.30.8",
|
||||
"radix-vue": "^1.5.2",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"v-calendar": "^3.1.2",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { MagnifyingGlassIcon } from '@radix-icons/vue'
|
||||
import { Search } from 'lucide-vue-next'
|
||||
import { Input } from '@/lib/registry/default/ui/input'
|
||||
</script>
|
||||
|
||||
|
|
@ -7,7 +7,7 @@ import { Input } from '@/lib/registry/default/ui/input'
|
|||
<div class="relative w-full max-w-sm items-center">
|
||||
<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">
|
||||
<MagnifyingGlassIcon class="size-6 text-muted-foreground" />
|
||||
<Search class="size-6 text-muted-foreground" />
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
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 { Button } from '@/lib/registry/new-york/ui/button'
|
||||
import {
|
||||
|
|
@ -56,7 +56,7 @@ const data = [
|
|||
:disabled="goal <= 200"
|
||||
@click="goal -= 10"
|
||||
>
|
||||
<Minus class="h-4 w-4" />
|
||||
<MinusIcon class="h-4 w-4" />
|
||||
<span class="sr-only">Decrease</span>
|
||||
</Button>
|
||||
<div class="flex-1 text-center">
|
||||
|
|
@ -74,7 +74,7 @@ const data = [
|
|||
:disabled="goal >= 400"
|
||||
@click="goal += 10"
|
||||
>
|
||||
<Plus class="h-4 w-4" />
|
||||
<PlusIcon class="h-4 w-4" />
|
||||
<span class="sr-only">Increase</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { computed } from 'vue'
|
||||
import { useSessionStorage } from '@vueuse/core'
|
||||
import { useSessionStorage, useStorage } from '@vueuse/core'
|
||||
import { useData } from 'vitepress'
|
||||
import { type Theme, themes } from './../lib/registry/themes'
|
||||
import { type Style, styles } from '@/lib/registry/styles'
|
||||
|
|
@ -10,6 +10,12 @@ interface Config {
|
|||
style: Style
|
||||
}
|
||||
|
||||
interface CodeConfig {
|
||||
prefix: string
|
||||
componentsPath: string
|
||||
utilsPath: string
|
||||
}
|
||||
|
||||
export const RADII = [0, 0.25, 0.5, 0.75, 1]
|
||||
|
||||
export function useConfigStore() {
|
||||
|
|
@ -18,6 +24,11 @@ export function useConfigStore() {
|
|||
radius: 0.5,
|
||||
style: styles[0].name,
|
||||
})
|
||||
const codeConfig = useStorage<CodeConfig>('code-config', {
|
||||
prefix: '',
|
||||
componentsPath: '@/components',
|
||||
utilsPath: '@/utils',
|
||||
})
|
||||
|
||||
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",
|
||||
"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"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,6 +89,9 @@ importers:
|
|||
lucide-vue-next:
|
||||
specifier: ^0.350.0
|
||||
version: 0.350.0(vue@3.4.21)
|
||||
magic-string:
|
||||
specifier: ^0.30.8
|
||||
version: 0.30.8
|
||||
radix-vue:
|
||||
specifier: ^1.5.2
|
||||
version: 1.5.2(vue@3.4.21)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user