chore: merge 'dev' in 'fix/table-with-many-rows'

This commit is contained in:
romanhrynevych 2024-06-24 21:57:25 +03:00
commit 83a11606f4
846 changed files with 47336 additions and 14713 deletions

View File

@ -1,19 +0,0 @@
// const process = require('node:process')
// process.env.ESLINT_TSCONFIG = 'tsconfig.json'
module.exports = {
extends: '@antfu',
rules: {
'vue/one-component-per-file': 'off',
'vue/no-reserved-component-names': 'off',
'vue/no-useless-v-bind': 'off',
'symbol-description': 'off',
'no-console': 'warn',
'no-tabs': 'off',
'no-invalid-character': 'off',
'import/first': 'off',
'@stylistic/js/no-tabs': 'off',
'n/prefer-global/process': 'off',
},
}

16
.github/actions/setup/action.yml vendored Normal file
View File

@ -0,0 +1,16 @@
name: Setup
description: Installs Node, Enables Corepack and caches pnpm.
runs:
using: composite
steps:
- name: Enable corepack
run: corepack enable
shell: bash
- name: Setup node & pnpm
uses: actions/setup-node@v4
with:
node-version: lts/*
cache: pnpm

View File

@ -18,7 +18,6 @@ on:
# When a labeled '🚀request-deploy' pull request from forked repo, it will be deploy to Cloudflare Pages # When a labeled '🚀request-deploy' pull request from forked repo, it will be deploy to Cloudflare Pages
- labeled - labeled
# Allows you to run this workflow manually from the Actions tab # Allows you to run this workflow manually from the Actions tab
# eslint-disable-next-line yml/no-empty-mapping-value
workflow_dispatch: workflow_dispatch:
permissions: permissions:
@ -49,32 +48,10 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
# Run a build step here - name: Setup (Install Node & pnpm)
- name: Setup Node.js environment uses: ./.github/actions/setup
uses: actions/setup-node@v2
with:
node-version: 18
- uses: pnpm/action-setup@v2
name: Install pnpm
with:
version: 8
run_install: false
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies - name: Install dependencies
run: pnpm i --frozen-lockfile run: pnpm i --frozen-lockfile

View File

@ -14,14 +14,13 @@ jobs:
release: release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: actions/setup-node@v3 - name: Setup (Install Node & pnpm)
with: uses: ./.github/actions/setup
node-version: 18.x
- run: npx changelogithub - run: pnpm dlx changelogithub
env: env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}

View File

@ -19,31 +19,10 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v2 uses: actions/checkout@v4
- name: Setup Node.js environment - name: Setup (Install Node & pnpm)
uses: actions/setup-node@v2 uses: ./.github/actions/setup
with:
node-version: 16
- uses: pnpm/action-setup@v2
name: Install pnpm
with:
version: 8
run_install: false
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies - name: Install dependencies
run: pnpm i --frozen-lockfile run: pnpm i --frozen-lockfile

View File

@ -1,7 +1,6 @@
{ {
"recommendations": [ "recommendations": [
"Vue.volar", "Vue.volar",
"Vue.vscode-typescript-vue-plugin",
"dbaeumer.vscode-eslint" "dbaeumer.vscode-eslint"
] ]
} }

18
.vscode/settings.json vendored
View File

@ -1,11 +1,27 @@
{ {
"vue.server.hybridMode": true,
"vue.server.includeLanguages": [
"vue",
"markdown"
],
"prettier.enable": false, "prettier.enable": false,
"editor.formatOnSave": false, "editor.formatOnSave": false,
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit", "source.fixAll.eslint": "explicit",
"source.organizeImports": "never" "source.organizeImports": "never"
}, },
"eslint.rules.customizations": [
{ "rule": "style/*", "severity": "off" },
{ "rule": "format/*", "severity": "off" },
{ "rule": "*-indent", "severity": "off" },
{ "rule": "*-spacing", "severity": "off" },
{ "rule": "*-spaces", "severity": "off" },
{ "rule": "*-order", "severity": "off" },
{ "rule": "*-dangle", "severity": "off" },
{ "rule": "*-newline", "severity": "off" },
{ "rule": "*quotes", "severity": "off" },
{ "rule": "*semi", "severity": "off" }
],
"eslint.validate": [ "eslint.validate": [
"javascript", "javascript",
"javascriptreact", "javascriptreact",

View File

@ -19,7 +19,7 @@ This repository is structured as follows:
``` ```
apps apps
└── www └── www
├── src ├── src
│ └── content │ └── content
└── registry └── registry
├── default ├── default
@ -32,12 +32,12 @@ packages
└── cli └── cli
``` ```
| Path | Description | | Path | Description |
| --------------------- | ---------------------------------------- | | ----------------------------| -------------------------------------------|
| `apps/www/app` | The Next.js application for the website. | | `apps/www/.vitepress` | The Vitepress application for the website. |
| `apps/www/content` | The content for the website. | | `apps/www/src/content` | The content for the website. |
| `apps/www/registry` | The registry for the components. | | `apps/www/src/lib/registry` | The registry for the components. |
| `packages/cli` | The `shadcn-vue` package. | | `packages/cli` | The `shadcn-vue` package. |
## Development ## Development
@ -79,22 +79,24 @@ The documentation for this project is located in the `www` workspace. You can ru
pnpm dev pnpm dev
``` ```
Documentation is written using [md](https://vitepress.dev/guide/markdown). You can find the documentation files in the `apps/www/content/docs` directory. Documentation is written using [md](https://vitepress.dev/guide/markdown). You can find the documentation files in the `apps/www/src/content` directory.
## Components ## Components
We use a registry system for developing components. You can find the source code for the components under `apps/www/registry`. The components are organized by styles. We use a registry system for developing components. You can find the source code for the components under `apps/www/src/lib/registry`. The components are organized by styles.
```bash ```bash
apps apps
└── www └── www
└── registry └── src
├── default └── lib
│ ├── example └── registry
│ └── ui ├── default
└── new-york │ ├── example
├── example │ └── ui
└── ui └── new-york
├── example
└── ui
``` ```
When adding or modifying components, please ensure that: When adding or modifying components, please ensure that:
@ -130,13 +132,10 @@ the following categories:
e.g. `feat(components): add new prop to the avatar component` e.g. `feat(components): add new prop to the avatar component`
If you are interested in the detailed specification you can visit If you are interested in the detailed specification you can visit
https://www.conventionalcommits.org/ or check out the https://www.conventionalcommits.org/ or check out the
[Angular Commit Message Guidelines](https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#-commit-message-guidelines). [Angular Commit Message Guidelines](https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#-commit-message-guidelines).
## Requests for new components ## Requests for new components
If you have a request for a new component, please open a discussion on GitHub. We'll be happy to help you out. If you have a request for a new component, please open a discussion on GitHub. We'll be happy to help you out.
@ -155,4 +154,4 @@ Tests are written using [Vitest](https://vitest.dev). You can run all the tests
pnpm test pnpm test
``` ```
Please ensure that the tests are passing when submitting a pull request. If you're adding new features, please include tests. Please ensure that the tests are passing when submitting a pull request. If you're adding new features, please include tests.

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 { transformerMetaWordHighlight } from '@shikijs/transformers'
import { cssVariables } from './theme/config/shiki'
// 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({
@ -60,11 +56,11 @@ export default defineConfig({
markdown: { markdown: {
theme: cssVariables, theme: cssVariables,
codeTransformers: [ codeTransformers: [
// transformerMetaWordHighlight(), transformerMetaWordHighlight(),
// transformerNotationWordHighlight(),
], ],
config(md) { config(md) {
md.use(ComponentPreviewPlugin) md.use(ComponentPreviewPlugin)
md.use(CodeWrapperPlugin)
}, },
}, },
rewrites: { rewrites: {
@ -74,13 +70,13 @@ export default defineConfig({
css: { css: {
postcss: { postcss: {
plugins: [ plugins: [
tailwind(), tailwind() as any,
autoprefixer(), autoprefixer(),
], ],
}, },
}, },
plugins: [ plugins: [
Icons({ compiler: 'vue3', autoInstall: true }), Icons({ compiler: 'vue3', autoInstall: true }) as any,
], ],
resolve: { resolve: {
alias: { alias: {

View File

@ -0,0 +1,26 @@
<script setup lang="ts">
import { capitalize } from 'vue'
defineProps<{
type: 'prop' | 'emit' | 'slot' | 'method'
data: { name: string, description: string, type: string, required: boolean }[]
}>()
</script>
<template>
<div>
<h3>{{ capitalize(type) }}</h3>
<div v-for="(item, index) in data" :key="index" class="py-4 border-b text-sm">
<div class="flex items-center gap-2 flex-wrap">
<h5 class="text-sm">
<code>{{ item.name }}</code>
</h5>
<code>{{ item.type }}</code>
<span v-if="item.required" class="font-normal text-red-500 text-xs">Required*</span>
</div>
<div class="[&_p]:!my-2 ml-1" v-html="item.description" />
</div>
</div>
</template>

View File

@ -0,0 +1,19 @@
<script setup lang="ts">
import { announcementConfig } from '../config/site'
import { Separator } from '@/lib/registry/default/ui/separator'
import ArrowRightIcon from '~icons/radix-icons/arrow-right'
</script>
<template>
<a
:href="announcementConfig.link"
class="inline-flex items-center rounded-lg bg-muted px-3 py-1 text-sm font-medium"
>
{{ announcementConfig.icon }} <Separator class="mx-2 h-4" orientation="vertical" />
<span class="sm:hidden">{{ announcementConfig.title }}</span>
<span class="hidden sm:inline">
{{ announcementConfig.title }}
</span>
<ArrowRightIcon class="ml-1 h-4 w-4" />
</a>
</template>

View File

@ -0,0 +1,38 @@
<script setup lang="ts">
import { useClipboard } from '@vueuse/core'
import { toRefs } from 'vue'
import { CheckIcon, ClipboardIcon } from '@radix-icons/vue'
import { Button } from '@/lib/registry/new-york/ui/button'
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/lib/registry/new-york/ui/tooltip'
const props = withDefaults(defineProps<{
code?: string
}>(), {
code: '',
})
const { code } = toRefs(props)
const { copy, copied } = useClipboard({ source: code })
</script>
<template>
<Tooltip :delay-duration="100">
<TooltipTrigger as-child>
<Button
size="icon"
variant="outline"
class="h-7 w-7 [&_svg]:size-3.5"
@click="copy()"
>
<span class="sr-only">Copy</span>
<CheckIcon v-if="copied" />
<ClipboardIcon v-else />
</Button>
</TooltipTrigger>
<TooltipContent>Copy code</TooltipContent>
</Tooltip>
</template>

View File

@ -0,0 +1,12 @@
<script setup lang="ts">
import { useUrlSearchParams } from '@vueuse/core'
import ComponentLoader from './ComponentLoader.vue'
const params = useUrlSearchParams('hash-params')
</script>
<template>
<div v-if="params.name && params.style" :class="params.containerClass">
<ComponentLoader :key="params.style?.toString()" :name="params.name?.toString()" :type-name="'block'" />
</div>
</template>

View File

@ -0,0 +1,245 @@
<script setup lang="ts">
import { CircleHelp, Info, Monitor, Smartphone, Tablet } from 'lucide-vue-next'
import { reactive, ref, watch } from 'vue'
import { codeToHtml } from 'shiki'
import { compileScript, parse, walk } from 'vue/compiler-sfc'
import MagicString from 'magic-string'
import { cssVariables } from '../config/shiki'
import StyleSwitcher from './StyleSwitcher.vue'
import Spinner from './Spinner.vue'
import BlockCopyButton from './BlockCopyButton.vue'
import { useConfigStore } from '@/stores/config'
// import { V0Button } from '@/components/v0-button'
import { Badge } from '@/lib/registry/new-york/ui/badge'
import { Popover, PopoverContent, PopoverTrigger } from '@/lib/registry/new-york/ui/popover'
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@/lib/registry/new-york/ui/resizable'
import { Separator } from '@/lib/registry/new-york/ui/separator'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/lib/registry/new-york/ui/tabs'
import { ToggleGroup, ToggleGroupItem } from '@/lib/registry/new-york/ui/toggle-group'
const props = defineProps<{
name: string
}>()
const { style, codeConfig } = useConfigStore()
const isLoading = ref(true)
const tabValue = ref('preview')
const resizableRef = ref<InstanceType<typeof ResizablePanel>>()
const rawString = ref('')
const codeHtml = ref('')
const metadata = reactive({
description: null as string | null,
iframeHeight: null as string | null,
containerClass: null as string | null,
})
function removeScript(code: string) {
const s = new MagicString(code)
const scriptTagRegex = /<script\s+lang="ts"\s*>[\s\S]+?<\/script>/g
let match
// eslint-disable-next-line no-cond-assign
while ((match = scriptTagRegex.exec(code)) !== null) {
const start = match.index
const end = match.index + match[0].length
s.overwrite(start, end, '') // Replace the script tag with an empty string
}
return s.trimStart().toString()
}
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 {
const baseRawString = await import(`../../../src/lib/registry/${style.value}/block/${props.name}.vue?raw`).then(res => res.default.trim())
rawString.value = transformImportPath(removeScript(baseRawString))
if (!metadata.description) {
const { descriptor } = parse(baseRawString)
const ast = compileScript(descriptor, { id: '' })
walk(ast.scriptAst, {
enter(node: any) {
const declaration = node.declaration
// Check if the declaration is a variable declaration
if (declaration?.type === 'VariableDeclaration') {
// Extract variable names and their values
declaration.declarations.forEach((decl: any) => {
// @ts-expect-error ignore missing type
metadata[decl.id.name] = decl.init ? decl.init.value : null
})
}
},
})
}
codeHtml.value = await codeToHtml(rawString.value, {
lang: 'vue',
theme: cssVariables,
})
}
catch (err) {
console.error(err)
}
}, { immediate: true, deep: true })
</script>
<template>
<Tabs
:id="name"
v-model="tabValue"
class="relative grid w-full scroll-m-20 gap-4"
:style=" {
'--container-height': metadata.iframeHeight ?? '600px',
}"
>
<div class="flex flex-col items-center gap-4 sm:flex-row">
<div class="flex items-center gap-2">
<TabsList class="hidden sm:flex">
<TabsTrigger value="preview">
Preview
</TabsTrigger>
<TabsTrigger value="code">
Code
</TabsTrigger>
</TabsList>
<div class="hidden items-center gap-2 sm:flex">
<Separator
orientation="vertical"
class="mx-2 hidden h-4 md:flex"
/>
<div class="flex items-center gap-2">
<a :href="`#${name}`">
<Badge variant="outline">{{ name }}</Badge>
</a>
<Popover>
<PopoverTrigger class="hidden text-muted-foreground hover:text-foreground sm:flex">
<Info class="h-3.5 w-3.5" />
<span class="sr-only">Block description</span>
</PopoverTrigger>
<PopoverContent
side="right"
:side-offset="10"
class="text-sm"
>
{{ metadata.description }}
</PopoverContent>
</Popover>
</div>
</div>
</div>
<div class="flex items-center gap-2 pr-[14px] sm:ml-auto">
<div class="hidden h-[28px] items-center gap-1.5 rounded-md border p-[2px] shadow-sm md:flex">
<ToggleGroup
type="single"
default-value="100"
@update:model-value="(value) => {
resizableRef?.resize(parseInt(value))
}"
>
<ToggleGroupItem
value="100"
class="h-[22px] w-[22px] rounded-sm p-0"
>
<Monitor class="h-3.5 w-3.5" />
</ToggleGroupItem>
<ToggleGroupItem
value="60"
class="h-[22px] w-[22px] rounded-sm p-0"
>
<Tablet class="h-3.5 w-3.5" />
</ToggleGroupItem>
<ToggleGroupItem
value="30"
class="h-[22px] w-[22px] rounded-sm p-0"
>
<Smartphone class="h-3.5 w-3.5" />
</ToggleGroupItem>
</ToggleGroup>
</div>
<Separator
orientation="vertical"
class="mx-2 hidden h-4 md:flex"
/>
<StyleSwitcher class="h-7" />
<Popover>
<PopoverTrigger class="hidden text-muted-foreground hover:text-foreground sm:flex">
<CircleHelp class="h-3.5 w-3.5" />
<span class="sr-only">Block description</span>
</PopoverTrigger>
<PopoverContent
side="top"
:side-offset="20"
class="space-y-3 rounded-[0.5rem] text-sm"
>
<p class="font-medium">
What is the difference between the New York and Default style?
</p>
<p>
A style comes with its own set of components, animations,
icons and more.
</p>
<p>
The <span class="font-medium">Default</span> style has
larger inputs, uses lucide-react for icons and
tailwindcss-animate for animations.
</p>
<p>
The <span class="font-medium">New York</span> style ships
with smaller buttons and inputs. It also uses shadows on cards
and buttons.
</p>
</PopoverContent>
</Popover>
<Separator orientation="vertical" class="mx-2 h-4" />
<BlockCopyButton :code="rawString" />
<!-- <V0Button
name="{block.name}"
description="{block.description" || "Edit in v0"}
code="{block.code}"
style="{block.style}"
/> -->
</div>
</div>
<TabsContent
v-show="tabValue === 'preview'"
force-mount
value="preview"
class="relative after:absolute after:inset-0 after:right-3 after:z-0 after:rounded-lg after:bg-muted h-[--container-height] px-0"
>
<ResizablePanelGroup id="block-resizable" direction="horizontal" class="relative z-10">
<ResizablePanel
id="block-resizable-panel-1"
ref="resizableRef"
class="relative rounded-lg border bg-background transition-all "
:default-size="100"
:min-size="30"
>
<div v-if="isLoading" class="flex items-center justify-center h-full">
<Spinner />
</div>
<iframe
v-show="!isLoading"
:src="`/blocks/renderer#name=${name}&style=${style}&containerClass=${encodeURIComponent(metadata.containerClass ?? '')}`"
class="relative z-20 w-full bg-background h-[--container-height]"
@load="isLoading = false"
/>
</ResizablePanel>
<ResizableHandle id="block-resizable-handle" class="relative hidden w-3 bg-transparent p-0 after:absolute after:right-0 after:top-1/2 after:h-8 after:w-[6px] after:-translate-y-1/2 after:translate-x-[-1px] after:rounded-full after:bg-border after:transition-all after:hover:h-10 sm:block" />
<ResizablePanel id="block-resizable-panel-2" :default-size="0" :min-size="0" />
</ResizablePanelGroup>
</TabsContent>
<TabsContent value="code" class="h-[--container-height]">
<div
class="language-vue !h-full !max-h-[none] !mt-0"
v-html="codeHtml"
/>
</TabsContent>
</Tabs>
</template>

View File

@ -0,0 +1,53 @@
<script setup lang="ts">
import { ref } from 'vue'
import PageHeader from '../components/PageHeader.vue'
import PageHeaderHeading from '../components/PageHeaderHeading.vue'
import PageHeaderDescription from '../components/PageHeaderDescription.vue'
import PageAction from '../components/PageAction.vue'
import Announcement from '../components/Announcement.vue'
import BlockPreview from './BlockPreview.vue'
import GitHubIcon from '~icons/radix-icons/github-logo'
import { buttonVariants } from '@/lib/registry/new-york/ui/button'
import { cn } from '@/lib/utils'
const blocks = ref<string[]>([])
import('../../../__registry__/index').then((res) => {
blocks.value = Object.values(res.Index.default).filter(i => i.type === 'components:block').map(i => i.name)
})
</script>
<template>
<PageHeader class="page-header pb-8">
<Announcement />
<PageHeaderHeading>Building Blocks for the Web</PageHeaderHeading>
<PageHeaderDescription>
Beautifully designed. Copy and paste into your apps. Open Source.
</PageHeaderDescription>
<PageAction>
<a
href="/blocks.html#blocks"
:class="cn(buttonVariants(), 'rounded-[6px]')"
>
Browse
</a>
<a
href="https://github.com/radix-vue/shadcn-vue"
target="_blank"
:class="cn(
buttonVariants({ variant: 'outline' }),
'rounded-[6px]',
)"
>
<GitHubIcon class="mr-2 h-4 w-4" />
GitHub
</a>
</PageAction>
</PageHeader>
<section id="blocks" class="grid scroll-mt-24 gap-24 lg:gap-48">
<BlockPreview v-for="block in blocks" :key="block" :name="block" />
</section>
</template>

View File

@ -19,7 +19,7 @@ defineProps<CalloutProps>()
<AlertTitle v-if="title"> <AlertTitle v-if="title">
{{ title }} {{ title }}
</AlertTitle> </AlertTitle>
<AlertDescription> <AlertDescription class="[&_a]:underline">
<slot /> <slot />
</AlertDescription> </AlertDescription>
</Alert> </Alert>

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

@ -5,12 +5,13 @@ import { useConfigStore } from '@/stores/config'
const props = defineProps<{ const props = defineProps<{
name: string name: string
typeName?: 'example' | 'block'
}>() }>()
const { style } = useConfigStore() const { style } = useConfigStore()
const Component = defineAsyncComponent({ const Component = defineAsyncComponent({
loadingComponent: Spinner, loadingComponent: Spinner,
loader: () => import(`../../../src/lib/registry/${style.value}/example/${props.name}.vue`), loader: () => import(`../../../src/lib/registry/${style.value}/${props.typeName}/${props.name}.vue`),
timeout: 5000, timeout: 5000,
}) })
</script> </script>

View File

@ -1,4 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { codeToHtml } from 'shiki'
import MagicString from 'magic-string'
import { useClipboard } from '@vueuse/core'
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 +16,38 @@ 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 rawString = ref('')
const codeHtml = ref('')
const transformedRawString = computed(() => transformImportPath(rawString.value))
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 {
rawString.value = await import(`../../../src/lib/registry/${style.value}/example/${props.name}.vue?raw`).then(res => res.default.trim())
codeHtml.value = await codeToHtml(transformedRawString.value, {
lang: 'vue',
theme: cssVariables,
})
}
catch (err) {
console.error(err)
}
}, { immediate: true, deep: true })
const { copy, copied } = useClipboard()
</script> </script>
<template> <template>
@ -47,8 +76,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="rawString" />
<CodeSandbox :key="style" :style="style" :name="name" :code="decodeURIComponent(sfcTsCode ?? '')" /> <CodeSandbox :key="style" :style="style" :name="name" :code="rawString" />
</div> </div>
</div> </div>
<div <div
@ -58,11 +87,15 @@ const { style } = useConfigStore()
'items-end': align === 'end', 'items-end': align === 'end',
})" })"
> >
<ComponentLoader v-bind="$attrs" :key="style" :name="name" /> <ComponentLoader v-bind="$attrs" :key="style" :name="name" :type-name="'example'" />
</div> </div>
</TabsContent> </TabsContent>
<TabsContent value="code"> <TabsContent value="code" class="vp-doc">
<div v-if="sfcTsHtml" class="language-vue" style="flex: 1;" v-html="decodeURIComponent(sfcTsHtml)" /> <div v-if="codeHtml" class="language-vue" style="flex: 1;">
<button title="Copy Code" class="copy" :class="{ copied }" @click="copy(transformedRawString)" />
<div v-html="codeHtml" />
</div>
<slot v-else /> <slot v-else />
</TabsContent> </TabsContent>
</Tabs> </Tabs>

View File

@ -15,7 +15,7 @@ const { copy, copied } = useClipboard()
const codeRef = ref<HTMLElement>() const codeRef = ref<HTMLElement>()
async function copyCode() { async function copyCode() {
await copy(codeRef.value?.innerText.replace(/\u00A0/g, " ") ?? '') await copy(codeRef.value?.textContent?.replace(/\u00A0/g, ' ') ?? '')
} }
</script> </script>

View File

@ -0,0 +1,53 @@
<script setup lang="ts">
import { useRoute } from 'vitepress'
import { computed } from 'vue'
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbSeparator,
} from '@/lib/registry/new-york/ui/breadcrumb'
const route = useRoute()
interface Item {
title: string
href: string
}
function generateBreadcrumb(url: string): Item[] {
const breadcrumbItems: Item[] = []
const segments = url.split('/').filter(segment => segment !== '') // Remove empty segments
// Construct breadcrumb for each segment
let href = ''
for (let i = 0; i < segments.length; i++) {
const segment = segments[i].replace('.html', '')
href += `/${segment}`
breadcrumbItems.push({ title: segment, href })
}
return breadcrumbItems
}
const breadcrumbs = computed(() => generateBreadcrumb(route.path))
</script>
<template>
<Breadcrumb>
<BreadcrumbList>
<template v-for="(breadcrumb, index) in breadcrumbs" :key="breadcrumb.title">
<BreadcrumbItem>
<BreadcrumbLink
class="capitalize"
:href="index === 0 ? undefined : breadcrumb.href"
:class="{ 'text-foreground': index === breadcrumbs.length - 1 }"
>
{{ breadcrumb.title }}
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator v-if="index !== breadcrumbs.length - 1" />
</template>
</BreadcrumbList>
</Breadcrumb>
</template>

View File

@ -8,6 +8,11 @@ import ArrowRightIcon from '~icons/radix-icons/arrow-right'
const { path } = toRefs(useRoute()) const { path } = toRefs(useRoute())
const examples = [ const examples = [
{
name: 'Mail',
href: '/examples/mail',
code: 'https://github.com/radix-vue/shadcn-vue/tree/dev/apps/www/src/examples/mail',
},
{ {
name: 'Dashboard', name: 'Dashboard',
href: '/examples/dashboard', href: '/examples/dashboard',
@ -30,7 +35,7 @@ const examples = [
}, },
{ {
name: 'Forms', name: 'Forms',
href: '/examples/forms/forms', href: '/examples/forms',
code: 'https://github.com/radix-vue/shadcn-vue/tree/dev/apps/www/src/examples/forms', code: 'https://github.com/radix-vue/shadcn-vue/tree/dev/apps/www/src/examples/forms',
}, },
{ {
@ -58,7 +63,7 @@ const currentExample = computed(() => examples.find(ex => path.value.startsWith(
:href="example.href" :href="example.href"
:class="cn( :class="cn(
'flex items-center px-4', 'flex items-center px-4',
path?.startsWith(example.href) || (path === '/' && example.name === 'Dashboard') path?.startsWith(example.href) || (path === '/' && example.name === 'Mail')
? 'font-bold text-primary' ? 'font-bold text-primary'
: 'font-medium text-muted-foreground', : 'font-medium text-muted-foreground',
)" )"

View File

@ -0,0 +1,54 @@
<script lang="ts" setup>
import type { Color } from '../types/colors'
import { useConfigStore } from '@/stores/config'
import { colors } from '@/lib/registry'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/lib/registry/new-york/ui/tooltip'
import RadixIconsCheck from '~icons/radix-icons/check'
defineProps<{
allColors: Color[]
}>()
const { theme, setTheme } = useConfigStore()
</script>
<template>
<div>
<TooltipProvider
v-for="(color, index) in allColors.slice(0, 5)"
:key="index"
>
<Tooltip>
<TooltipTrigger as-child>
<button
:key="index"
class="flex h-9 w-9 items-center justify-center rounded-full border-2 border-border text-xs"
:class="
color === theme
? 'border-primary'
: 'border-transparent'
"
@click="setTheme(color)"
>
<span
class="flex h-6 w-6 items-center justify-center rounded-full"
:style="{ backgroundColor: colors[color][6].rgb }"
>
<RadixIconsCheck
v-if="color === theme"
class="h-4 w-4 text-white"
/>
</span>
</button>
</TooltipTrigger>
<TooltipContent
align="center"
:side-offset="1"
class="capitalize bg-zinc-900 text-zinc-50"
>
{{ allColors[index] }}
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
</template>

View File

@ -4,28 +4,18 @@ import PageHeaderHeading from '../components/PageHeaderHeading.vue'
import PageHeaderDescription from '../components/PageHeaderDescription.vue' import PageHeaderDescription from '../components/PageHeaderDescription.vue'
import PageAction from '../components/PageAction.vue' import PageAction from '../components/PageAction.vue'
import ExamplesNav from '../components/ExamplesNav.vue' import ExamplesNav from '../components/ExamplesNav.vue'
import { announcementConfig } from '../config/site' import Announcement from '../components/Announcement.vue'
import GitHubIcon from '~icons/radix-icons/github-logo' import GitHubIcon from '~icons/radix-icons/github-logo'
import { buttonVariants } from '@/lib/registry/new-york/ui/button' import { buttonVariants } from '@/lib/registry/new-york/ui/button'
import { Separator } from '@/lib/registry/new-york/ui/separator'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import DashboardExample from '@/examples/dashboard/Example.vue' import MailExample from '@/examples/mail/Example.vue'
</script> </script>
<template> <template>
<PageHeader class="page-header pb-8"> <PageHeader class="page-header pb-8">
<a <Announcement />
:href="announcementConfig.link"
class="inline-flex items-center rounded-lg bg-muted px-3 py-1 text-sm font-medium"
>
{{ announcementConfig.icon }} <Separator class="mx-2 h-4" orientation="vertical" />
<span class="sm:hidden">{{ announcementConfig.title }}</span>
<span class="hidden sm:inline">{{ announcementConfig.title }}
</span>
<!-- <ArrowRightIcon class="ml-1 h-4 w-4" /> -->
</a>
<PageHeaderHeading>Build your component library.</PageHeaderHeading> <PageHeaderHeading>Build your component library.</PageHeaderHeading>
<PageHeaderDescription> <PageHeaderDescription>
Beautifully designed components that you can copy and paste into your Beautifully designed components that you can copy and paste into your
@ -55,17 +45,17 @@ import DashboardExample from '@/examples/dashboard/Example.vue'
<ExamplesNav /> <ExamplesNav />
<section class="space-y-8 overflow-hidden rounded-lg border-2 border-primary dark:border-muted md:hidden"> <section class="space-y-8 overflow-hidden rounded-lg border-2 border-primary dark:border-muted md:hidden">
<VPImage <VPImage
alt="Dashboard" alt="Mail"
width="1280" width="1280"
height="866" class="block" :image="{ height="866" class="block" :image="{
dark: '/examples/dashboard-dark.png', dark: '/examples/mail-dark.png',
light: '/examples/dashboard-light.png', light: '/examples/mail-light.png',
}" }"
/> />
</section> </section>
<section class="hidden md:block"> <section class="hidden md:block">
<div class="overflow-hidden rounded-[0.5rem] border bg-background shadow"> <div class="overflow-hidden rounded-[0.5rem] border bg-background shadow">
<DashboardExample /> <MailExample />
</div> </div>
</section> </section>
</template> </template>

View File

@ -2,7 +2,7 @@
</script> </script>
<template> <template>
<a href="/" class="mr-6 flex items-center space-x-2"> <a href="/" class="mr-4 md:mr-2 lg:mr-6 flex items-center lg:space-x1 xl:space-x-2">
<svg class="h-6 w-6" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg class="h-6 w-6" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_102_1338)"> <g clip-path="url(#clip0_102_1338)">
<path d="M208 128L128 208" stroke="#41B883" stroke-width="16" stroke-linecap="round" stroke-linejoin="round" /> <path d="M208 128L128 208" stroke="#41B883" stroke-width="16" stroke-linecap="round" stroke-linejoin="round" />
@ -15,7 +15,7 @@
</defs> </defs>
</svg> </svg>
<span class="font-bold "> <span class="font-bold">
shadcn-vue shadcn-vue
</span> </span>
</a> </a>

View File

@ -5,7 +5,6 @@ import Logo from './Logo.vue'
import { Sheet, SheetContent, SheetTrigger } from '@/lib/registry/default/ui/sheet' import { Sheet, SheetContent, SheetTrigger } from '@/lib/registry/default/ui/sheet'
import { Button } from '@/lib/registry/default/ui/button' import { Button } from '@/lib/registry/default/ui/button'
import { ScrollArea } from '@/lib/registry/default/ui/scroll-area' import { ScrollArea } from '@/lib/registry/default/ui/scroll-area'
import ViewVerticalIcon from '~icons/radix-icons/view-vertical'
const open = ref(false) const open = ref(false)
</script> </script>
@ -17,7 +16,35 @@ const open = ref(false)
variant="ghost" variant="ghost"
class="mr-2 px-2 text-base flex-shrink-0 hover:bg-transparent focus-visible:bg-transparent focus-visible:ring-0 focus-visible:ring-offset-0 md:hidden" class="mr-2 px-2 text-base flex-shrink-0 hover:bg-transparent focus-visible:bg-transparent focus-visible:ring-0 focus-visible:ring-offset-0 md:hidden"
> >
<ViewVerticalIcon class="h-5 w-5" /> <svg
strokeWidth="1.5"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
>
<path
d="M3 5H11"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M3 12H16"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M3 19H21"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
<span class="sr-only">Toggle Menu</span> <span class="sr-only">Toggle Menu</span>
</Button> </Button>
</SheetTrigger> </SheetTrigger>
@ -36,17 +63,26 @@ const open = ref(false)
</div> </div>
<div class="flex flex-col space-y-2"> <div class="flex flex-col space-y-2">
<div v-for="(items, index) in docsConfig.sidebarNav" :key="index" class="flex flex-col space-y-3 pt-6"> <div v-for="(items, index) in docsConfig.sidebarNav" :key="index" class="flex flex-col space-y-3 pt-6">
<h4 class="font-medium"> <div class="flex items-center">
{{ items.title }} <h4 class="font-medium">
</h4> {{ items.title }}
</h4>
<span v-if="items.label" class="ml-2 rounded-md bg-[#adfa1d] px-1.5 py-0.5 text-xs leading-none text-[#000000] no-underline group-hover:no-underline">
{{ items.label }}
</span>
</div>
<a <a
v-for="item in items.items" :key="item.href" v-for="item in items.items" :key="item.href"
:href="item.href" :href="item.href"
class="text-muted-foreground" class="text-muted-foreground inline-flex items-center"
@click="open = false" @click="open = false"
> >
{{ item.title }} {{ item.title }}
<span v-if="item.label" class="ml-2 rounded-md bg-[#adfa1d] px-1.5 py-0.5 text-xs leading-none text-[#000000] no-underline group-hover:no-underline">
{{ item.label }}
</span>
</a> </a>
</div> </div>
</div> </div>

View File

@ -4,7 +4,7 @@ import { cn } from '@/lib/utils'
</script> </script>
<template> <template>
<WrapBalancer :class="cn('max-w-[750px] text-center text-lg text-muted-foreground sm:text-xl', $attrs.class ?? '')" :prefer-native="false"> <WrapBalancer :class="cn('max-w-[750px] text-center text-lg font-light text-foreground', $attrs.class ?? '')" :prefer-native="false">
<slot /> <slot />
</WrapBalancer> </WrapBalancer>
</template> </template>

View File

@ -5,7 +5,7 @@ import { cn } from '@/lib/utils'
<template> <template>
<h1 <h1
:class="cn( :class="cn(
'text-center text-3xl font-bold leading-tight tracking-tighter md:text-6xl lg:leading-[1.1]', 'text-center text-3xl font-bold leading-tight tracking-tighter md:text-5xl lg:leading-[1.1]',
$attrs.class ?? '', $attrs.class ?? '',
)" )"
> >

View File

@ -1,10 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, ref } from 'vue' import { 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,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 })
function handleClick() { function handleClick() {
makeStackblitzParams(props.name, props.style, sources.value) makeStackblitzParams(props.name, props.style, sources.value)

View File

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { type SelectTriggerProps } from 'radix-vue' import type { SelectTriggerProps } from 'radix-vue'
import { useConfigStore } from '@/stores/config' import { useConfigStore } from '@/stores/config'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'

View File

@ -1,6 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { useSlots } from 'vue' import { TabsContent } from '@/lib/registry/default/ui/tabs'
import { TabsContent, TabsTrigger } from '@/lib/registry/default/ui/tabs'
withDefaults(defineProps<{ withDefaults(defineProps<{
title?: string title?: string

View File

@ -5,6 +5,7 @@ import type { TableOfContents, TableOfContentsItem } from '../types/docs'
import TableOfContentTree from './TableOfContentTree.vue' import TableOfContentTree from './TableOfContentTree.vue'
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/lib/registry/default/ui/collapsible' import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/lib/registry/default/ui/collapsible'
import { buttonVariants } from '@/lib/registry/default/ui/button' import { buttonVariants } from '@/lib/registry/default/ui/button'
import { ScrollArea } from '@/lib/registry/default/ui/scroll-area'
const headers = shallowRef<TableOfContents>() const headers = shallowRef<TableOfContents>()
@ -22,8 +23,8 @@ function getHeadingsWithHierarchy(divId: string) {
headings.forEach((heading: HTMLHeadingElement) => { headings.forEach((heading: HTMLHeadingElement) => {
const level = Number.parseInt(heading.tagName.charAt(1)) const level = Number.parseInt(heading.tagName.charAt(1))
if (!heading.id) { if (!heading.id) {
const newId = heading.innerText const newId = heading.textContent
.replaceAll(/[^a-zA-Z0-9 ]/g, '') .replaceAll(/[^a-z0-9 ]/gi, '')
.replaceAll(' ', '-') .replaceAll(' ', '-')
.toLowerCase() .toLowerCase()
heading.id = `${newId}` heading.id = `${newId}`
@ -55,11 +56,15 @@ onContentUpdated(() => {
</script> </script>
<template> <template>
<div class="space-y-2 hidden xl:block"> <div class="hidden xl:block">
<p class="font-medium"> <ScrollArea orientation="vertical" class="h-[calc(100vh-6.5rem)] z-30 md:block overflow-y-auto" type="hover">
On This Page <div class="space-y-2">
</p> <p class="font-medium">
<TableOfContentTree :tree="headers" :level="1" /> On This Page
</p>
<TableOfContentTree :tree="headers" :level="1" />
</div>
</ScrollArea>
</div> </div>
<div class="block xl:hidden mb-6"> <div class="block xl:hidden mb-6">

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, useSlots } from 'vue' import { computed, useSlots } from 'vue'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/lib/registry/default/ui/tabs' import { Tabs, TabsList, TabsTrigger } from '@/lib/registry/default/ui/tabs'
const slots = useSlots() const slots = useSlots()

View File

@ -0,0 +1,106 @@
<script lang="ts" setup>
import { useData } from 'vitepress'
import type { Color } from '../types/colors'
import { RADII, useConfigStore } from '@/stores/config'
import { Button } from '@/lib/registry/new-york/ui/button'
import { Label } from '@/lib/registry/new-york/ui/label'
import { colors } from '@/lib/registry'
import RadixIconsCheck from '~icons/radix-icons/check'
import RadixIconsSun from '~icons/radix-icons/sun'
import RadixIconsMoon from '~icons/radix-icons/moon'
defineProps<{
allColors: Color[]
}>()
const { theme, radius, setRadius, setTheme } = useConfigStore()
const { isDark } = useData()
</script>
<template>
<div class="p-4">
<div class="grid space-y-1">
<h1 class="text-md text-foreground font-semibold">
Customize
</h1>
<p class="text-xs text-muted-foreground">
Pick a style and color for your components.
</p>
</div>
<div class="space-y-1.5 pt-6">
<Label for="color" class="text-xs"> Color </Label>
<div class="grid grid-cols-3 gap-2 py-1.5">
<Button
v-for="(color, index) in allColors"
:key="index"
variant="outline"
class="h-8 justify-start px-3"
:class="
color === theme
? 'border-foreground border-2'
: ''
"
@click="setTheme(color)"
>
<span
class="h-5 w-5 rounded-full flex items-center justify-center"
:style="{ backgroundColor: colors[color][7].rgb }"
>
<RadixIconsCheck
v-if="color === theme"
class="h-3 w-3 text-white"
/>
</span>
<span class="ml-2 text-xs capitalize">
{{ color }}
</span>
</Button>
</div>
</div>
<div class="space-y-1.5 pt-6">
<Label for="radius" class="text-xs"> Radius </Label>
<div class="grid grid-cols-5 gap-2 py-1.5">
<Button
v-for="(r, index) in RADII"
:key="index"
variant="outline"
class="h-8 justify-center px-3"
:class="
r === radius
? 'border-foreground border-2'
: ''
"
@click="setRadius(r)"
>
<span class="text-xs">
{{ r }}
</span>
</Button>
</div>
</div>
<div class="space-y-1.5 pt-6">
<Label for="theme" class="text-xs"> Theme </Label>
<div class="flex space-x-2 py-1.5">
<Button
class="h-8"
variant="outline"
:class="{ 'border-2 border-foreground': !isDark }"
@click="isDark = false"
>
<RadixIconsSun class="w-4 h-4 mr-2" />
<span class="text-xs">Light</span>
</Button>
<Button
class="h-8"
variant="outline"
:class="{ 'border-2 border-foreground': isDark }"
@click="isDark = true"
>
<RadixIconsMoon class="w-4 h-4 mr-2" />
<span class="text-xs">Dark</span>
</Button>
</div>
</div>
</div>
</template>

View File

@ -0,0 +1,47 @@
<script setup lang="ts">
import { Paintbrush } from 'lucide-vue-next'
import { onMounted, watch } from 'vue'
import { allColors } from './theming/utils/data'
import ThemeCustomizer from './ThemeCustomizer.vue'
import { Button } from '@/lib/registry/new-york/ui/button'
import { Popover, PopoverContent, PopoverTrigger } from '@/lib/registry/new-york/ui/popover'
import { useConfigStore } from '@/stores/config'
const { theme, radius } = useConfigStore()
// Whenever the component is mounted, update the document class list
onMounted(() => {
document.documentElement.style.setProperty('--radius', `${radius.value}rem`)
document.documentElement.classList.add(`theme-${theme.value}`)
})
// Whenever the theme value changes, update the document class list
watch(theme, (theme) => {
document.documentElement.classList.remove(
...allColors.map(color => `theme-${color}`),
)
document.documentElement.classList.add(`theme-${theme}`)
})
// Whenever the radius value changes, update the document style
watch(radius, (radius) => {
document.documentElement.style.setProperty('--radius', `${radius}rem`)
})
</script>
<template>
<Popover>
<PopoverTrigger as-child>
<Button
class="w-9 h-9"
:variant="'ghost'"
:size="'icon'"
>
<Paintbrush class="w-4 h-4" />
</Button>
</PopoverTrigger>
<PopoverContent :side-offset="8" align="end" class="w-96">
<ThemeCustomizer :all-colors="allColors" />
</PopoverContent>
</Popover>
</template>

View File

@ -1,4 +1,6 @@
export { default as CodeWrapper } from './CodeWrapper'
export { default as ComponentPreview } from './ComponentPreview.vue' export { default as ComponentPreview } from './ComponentPreview.vue'
export { default as APITable } from './APITable.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'
export { default as TabsMarkdown } from './TabsMarkdown.vue' export { default as TabsMarkdown } from './TabsMarkdown.vue'

View File

@ -1,6 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { type Ref, ref } from 'vue'
import { addDays, startOfToday } from 'date-fns' import type { DateRange } from 'radix-vue'
import { getLocalTimeZone, today } from '@internationalized/date'
import ThemingLayout from './../../layout/ThemingLayout.vue' import ThemingLayout from './../../layout/ThemingLayout.vue'
import CookieSettings from '@/examples/cards/components/CookieSettings.vue' import CookieSettings from '@/examples/cards/components/CookieSettings.vue'
@ -14,19 +16,17 @@ import CardChat from '@/lib/registry/new-york/example/CardChat.vue'
import ActivityGoal from '@/lib/registry/new-york/example/Cards/ActivityGoal.vue' import ActivityGoal from '@/lib/registry/new-york/example/Cards/ActivityGoal.vue'
import Metric from '@/lib/registry/new-york/example/Cards/Metric.vue' import Metric from '@/lib/registry/new-york/example/Cards/Metric.vue'
import DataTable from '@/lib/registry/new-york/example/Cards/DataTable.vue' import DataTable from '@/lib/registry/new-york/example/Cards/DataTable.vue'
import CardStats from '@/lib/registry/default/example/CardStats.vue' import CardStats from '@/lib/registry/new-york/example/CardStats.vue'
import { import { Card } from '@/lib/registry/new-york/ui/card'
Card, import { RangeCalendar } from '@/lib/registry/new-york/ui/range-calendar'
} from '@/lib/registry/new-york/ui/card'
import { Calendar } from '@/lib/registry/new-york/ui/calendar'
const goal = ref(350) const now = today(getLocalTimeZone())
const range = ref({ const range = ref({
start: startOfToday(), start: now,
end: addDays(startOfToday(), 8), end: now.add({ days: 8 }),
}) }) as Ref<DateRange>
</script> </script>
<template> <template>
@ -54,7 +54,7 @@ const range = ref({
<div class="space-y-4 lg:col-span-6 xl:col-span-5 xl:space-y-4"> <div class="space-y-4 lg:col-span-6 xl:col-span-5 xl:space-y-4">
<div class="hidden gap-1 sm:grid-cols-[280px_1fr] md:grid"> <div class="hidden gap-1 sm:grid-cols-[280px_1fr] md:grid">
<Card class="max-w-[280px]"> <Card class="max-w-[280px]">
<Calendar v-model.range="range" /> <RangeCalendar v-model="range" />
</Card> </Card>
<div class="pt-3 sm:pl-2 sm:pt-0 xl:pl-3"> <div class="pt-3 sm:pl-2 sm:pt-0 xl:pl-3">

View File

@ -2,11 +2,41 @@ import { CreditCard } from 'lucide-vue-next'
import RiAppleFill from '~icons/ri/apple-fill' import RiAppleFill from '~icons/ri/apple-fill'
import RiPaypalFill from '~icons/ri/paypal-fill' import RiPaypalFill from '~icons/ri/paypal-fill'
interface Payment { type Color =
status: string | 'zinc'
email: string | 'slate'
amount: number | 'stone'
} | 'gray'
| 'neutral'
| 'red'
| 'rose'
| 'orange'
| 'green'
| 'blue'
| 'yellow'
| 'violet'
// Create an array of color values
export const allColors: Color[] = [
'zinc',
'rose',
'blue',
'green',
'orange',
'red',
'slate',
'stone',
'gray',
'neutral',
'yellow',
'violet',
]
// interface Payment {
// status: string
// email: string
// amount: number
// }
interface TeamMember { interface TeamMember {
name: string name: string

View File

@ -8,11 +8,11 @@ export interface NavItem {
} }
export type SidebarNavItem = NavItem & { export type SidebarNavItem = NavItem & {
items: SidebarNavItem[] items?: SidebarNavItem[]
} }
export type NavItemWithChildren = NavItem & { export type NavItemWithChildren = NavItem & {
items: NavItemWithChildren[] items?: NavItemWithChildren[]
} }
interface DocsConfig { interface DocsConfig {
@ -23,7 +23,7 @@ interface DocsConfig {
export const docsConfig: DocsConfig = { export const docsConfig: DocsConfig = {
mainNav: [ mainNav: [
{ {
title: 'Documentation', title: 'Docs',
href: '/docs/introduction', href: '/docs/introduction',
}, },
{ {
@ -36,7 +36,11 @@ export const docsConfig: DocsConfig = {
}, },
{ {
title: 'Examples', title: 'Examples',
href: '/examples/dashboard', href: '/examples/mail',
},
{
title: 'Blocks',
href: '/blocks',
}, },
{ {
title: 'GitHub', title: 'GitHub',
@ -51,46 +55,47 @@ export const docsConfig: DocsConfig = {
{ {
title: 'Introduction', title: 'Introduction',
href: '/docs/introduction', href: '/docs/introduction',
items: [],
}, },
{ {
title: 'Installation', title: 'Installation',
href: '/docs/installation', href: '/docs/installation',
items: [],
}, },
{ {
title: 'components.json', title: 'components.json',
href: '/docs/components-json', href: '/docs/components-json',
items: [],
}, },
{ {
title: 'Theming', title: 'Theming',
href: '/docs/theming', href: '/docs/theming',
},
{
title: 'Dark Mode',
href: '/docs/dark-mode',
items: [], items: [],
}, },
{ {
title: 'CLI', title: 'CLI',
href: '/docs/cli', href: '/docs/cli',
items: [],
}, },
{ {
title: 'Typography', title: 'Typography',
href: '/docs/typography', href: '/docs/typography',
items: [],
}, },
{ {
title: 'Figma', title: 'Figma',
href: '/docs/figma', href: '/docs/figma',
items: [],
}, },
{ {
title: 'Changelog', title: 'Changelog',
href: '/docs/changelog', href: '/docs/changelog',
items: [],
}, },
{ {
title: 'About', title: 'About',
href: '/docs/about', href: '/docs/about',
},
{
title: 'Contribution',
href: '/docs/contribution',
items: [], items: [],
}, },
], ],
@ -101,21 +106,34 @@ export const docsConfig: DocsConfig = {
{ {
title: 'Vite', title: 'Vite',
href: '/docs/installation/vite', href: '/docs/installation/vite',
items: [],
}, },
{ {
title: 'Nuxt', title: 'Nuxt',
href: '/docs/installation/nuxt', href: '/docs/installation/nuxt',
items: [],
}, },
{ {
title: 'Astro', title: 'Astro',
href: '/docs/installation/astro', href: '/docs/installation/astro',
items: [],
}, },
{ {
title: 'Laravel', title: 'Laravel',
href: '/docs/installation/laravel', href: '/docs/installation/laravel',
},
],
},
{
title: 'Extended',
items: [
{
title: 'Auto Form',
href: '/docs/components/auto-form',
items: [],
label: 'New',
},
{
title: 'Charts',
href: '/docs/charts',
label: 'New Alpha',
items: [], items: [],
}, },
], ],
@ -126,230 +144,219 @@ export const docsConfig: DocsConfig = {
{ {
title: 'Accordion', title: 'Accordion',
href: '/docs/components/accordion', href: '/docs/components/accordion',
items: [],
}, },
{ {
title: 'Alert', title: 'Alert',
href: '/docs/components/alert', href: '/docs/components/alert',
items: [],
}, },
{ {
title: 'Alert Dialog', title: 'Alert Dialog',
href: '/docs/components/alert-dialog', href: '/docs/components/alert-dialog',
items: [],
}, },
{ {
title: 'Aspect Ratio', title: 'Aspect Ratio',
href: '/docs/components/aspect-ratio', href: '/docs/components/aspect-ratio',
items: [],
}, },
{ {
title: 'Avatar', title: 'Avatar',
href: '/docs/components/avatar', href: '/docs/components/avatar',
items: [],
}, },
{ {
title: 'Badge', title: 'Badge',
href: '/docs/components/badge', href: '/docs/components/badge',
},
{
title: 'Breadcrumb',
href: '/docs/components/breadcrumb',
items: [], items: [],
}, },
{ {
title: 'Button', title: 'Button',
href: '/docs/components/button', href: '/docs/components/button',
items: [],
}, },
{ {
title: 'Calendar', title: 'Calendar',
href: '/docs/components/calendar', href: '/docs/components/calendar',
items: [], items: [],
label: 'Updated',
}, },
{ {
title: 'Card', title: 'Card',
href: '/docs/components/card', href: '/docs/components/card',
items: [],
}, },
{ {
title: 'Carousel', title: 'Carousel',
href: '/docs/components/carousel', href: '/docs/components/carousel',
label: 'New',
items: [], items: [],
}, },
{ {
title: 'Checkbox', title: 'Checkbox',
href: '/docs/components/checkbox', href: '/docs/components/checkbox',
items: [],
}, },
{ {
title: 'Collapsible', title: 'Collapsible',
href: '/docs/components/collapsible', href: '/docs/components/collapsible',
items: [],
}, },
{ {
title: 'Combobox', title: 'Combobox',
href: '/docs/components/combobox', href: '/docs/components/combobox',
items: [],
}, },
{ {
title: 'Command', title: 'Command',
href: '/docs/components/command', href: '/docs/components/command',
items: [],
}, },
{ {
title: 'Context Menu', title: 'Context Menu',
href: '/docs/components/context-menu', href: '/docs/components/context-menu',
items: [],
}, },
{ {
title: 'Data Table', title: 'Data Table',
href: '/docs/components/data-table', href: '/docs/components/data-table',
items: [],
}, },
{ {
title: 'Date Picker', title: 'Date Picker',
href: '/docs/components/date-picker', href: '/docs/components/date-picker',
items: [], items: [],
label: 'Updated',
}, },
{ {
title: 'Dialog', title: 'Dialog',
href: '/docs/components/dialog', href: '/docs/components/dialog',
},
{
title: 'Drawer',
href: '/docs/components/drawer',
items: [], items: [],
}, },
{ {
title: 'Dropdown Menu', title: 'Dropdown Menu',
href: '/docs/components/dropdown-menu', href: '/docs/components/dropdown-menu',
items: [],
}, },
{ {
title: 'Form', title: 'Form',
href: '/docs/components/form', href: '/docs/components/form',
items: [],
}, },
{ {
title: 'Hover Card', title: 'Hover Card',
href: '/docs/components/hover-card', href: '/docs/components/hover-card',
items: [],
}, },
{ {
title: 'Input', title: 'Input',
href: '/docs/components/input', href: '/docs/components/input',
items: [],
}, },
{ {
title: 'Label', title: 'Label',
href: '/docs/components/label', href: '/docs/components/label',
items: [],
}, },
{ {
title: 'Menubar', title: 'Menubar',
href: '/docs/components/menubar', href: '/docs/components/menubar',
items: [],
}, },
{ {
title: 'Navigation Menu', title: 'Navigation Menu',
href: '/docs/components/navigation-menu', href: '/docs/components/navigation-menu',
items: [], },
{
title: 'Number Field',
href: '/docs/components/number-field',
label: 'New Alpha',
}, },
{ {
title: 'Pagination', title: 'Pagination',
href: '/docs/components/pagination', href: '/docs/components/pagination',
items: [],
}, },
{ {
title: 'Pin Input', title: 'PIN Input',
href: '/docs/components/pin-input', href: '/docs/components/pin-input',
label: 'New',
items: [], items: [],
}, },
{ {
title: 'Popover', title: 'Popover',
href: '/docs/components/popover', href: '/docs/components/popover',
items: [],
}, },
{ {
title: 'Progress', title: 'Progress',
href: '/docs/components/progress', href: '/docs/components/progress',
items: [],
}, },
{ {
title: 'Radio Group', title: 'Radio Group',
href: '/docs/components/radio-group', href: '/docs/components/radio-group',
},
{
title: 'Range Calendar',
href: '/docs/components/range-calendar',
items: [],
},
{
title: 'Resizable',
href: '/docs/components/resizable',
items: [], items: [],
}, },
{ {
title: 'Scroll Area', title: 'Scroll Area',
href: '/docs/components/scroll-area', href: '/docs/components/scroll-area',
items: [],
}, },
{ {
title: 'Select', title: 'Select',
href: '/docs/components/select', href: '/docs/components/select',
items: [],
}, },
{ {
title: 'Separator', title: 'Separator',
href: '/docs/components/separator', href: '/docs/components/separator',
items: [],
}, },
{ {
title: 'Sheet', title: 'Sheet',
href: '/docs/components/sheet', href: '/docs/components/sheet',
items: [],
}, },
{ {
title: 'Skeleton', title: 'Skeleton',
href: '/docs/components/skeleton', href: '/docs/components/skeleton',
items: [],
}, },
{ {
title: 'Slider', title: 'Slider',
href: '/docs/components/slider', href: '/docs/components/slider',
items: [],
}, },
{ {
title: 'Sonner', title: 'Sonner',
href: '/docs/components/sonner', href: '/docs/components/sonner',
label: 'New',
items: [], items: [],
}, },
{ {
title: 'Switch', title: 'Switch',
href: '/docs/components/switch', href: '/docs/components/switch',
items: [],
}, },
{ {
title: 'Table', title: 'Table',
href: '/docs/components/table', href: '/docs/components/table',
items: [],
}, },
{ {
title: 'Tabs', title: 'Tabs',
href: '/docs/components/tabs', href: '/docs/components/tabs',
},
{
title: 'Tags Input',
href: '/docs/components/tags-input',
items: [], items: [],
}, },
{ {
title: 'Textarea', title: 'Textarea',
href: '/docs/components/textarea', href: '/docs/components/textarea',
items: [],
}, },
{ {
title: 'Toast', title: 'Toast',
href: '/docs/components/toast', href: '/docs/components/toast',
items: [],
}, },
{ {
title: 'Toggle', title: 'Toggle',
href: '/docs/components/toggle', href: '/docs/components/toggle',
items: [],
}, },
{ {
title: 'Toggle Group', title: 'Toggle Group',
href: '/docs/components/toggle-group', href: '/docs/components/toggle-group',
items: [],
}, },
{ {
title: 'Tooltip', title: 'Tooltip',
href: '/docs/components/tooltip', href: '/docs/components/tooltip',
items: [],
}, },
], ],
}, },
@ -373,6 +380,11 @@ interface Example {
code: string code: string
} }
export const examples: Example[] = [ export const examples: Example[] = [
{
name: 'Mail',
href: '/examples/mail',
code: 'https://github.com/radix-vue/shadcn-vue/tree/dev/apps/www/src/examples/mail',
},
{ {
name: 'Dashboard', name: 'Dashboard',
href: '/examples/dashboard', href: '/examples/dashboard',

View File

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

View File

@ -15,6 +15,6 @@ export const siteConfig = {
export const announcementConfig = { export const announcementConfig = {
icon: '✨', icon: '✨',
title: 'New components!', title: 'Extended: Auto Form, Charts',
link: '/docs/components/carousel.html', link: '/docs/components/auto-form.html',
} }

View File

@ -1,4 +1,3 @@
/* eslint-disable vue/component-definition-name-casing */
// https://vitepress.dev/guide/custom-theme // https://vitepress.dev/guide/custom-theme
import Layout from './layout/MainLayout.vue' import Layout from './layout/MainLayout.vue'
import DocsLayout from './layout/DocsLayout.vue' import DocsLayout from './layout/DocsLayout.vue'

View File

@ -3,11 +3,10 @@ import { useData, useRoute } from 'vitepress'
import { docsConfig } from '../config/docs' import { docsConfig } from '../config/docs'
import TableOfContentVue from '../components/TableOfContent.vue' import TableOfContentVue from '../components/TableOfContent.vue'
import EditLink from '../components/EditLink.vue' import EditLink from '../components/EditLink.vue'
import DocsBreadcrumb from '../components/DocsBreadcrumb.vue'
import { ScrollArea } from '@/lib/registry/default/ui/scroll-area' import { ScrollArea } from '@/lib/registry/default/ui/scroll-area'
import { Badge } from '@/lib/registry/default/ui/badge'
import RadixIconsCode from '~icons/radix-icons/code' import RadixIconsCode from '~icons/radix-icons/code'
import RadixIconsExternalLink from '~icons/radix-icons/external-link' import RadixIconsExternalLink from '~icons/radix-icons/external-link'
import ChevronRightIcon from '~icons/lucide/chevron-right'
const $route = useRoute() const $route = useRoute()
const { frontmatter } = useData() const { frontmatter } = useData()
@ -28,6 +27,10 @@ const sourceLink = 'https://github.com/radix-vue/shadcn-vue/tree/dev/'
class="mb-1 rounded-md px-2 py-1 text-sm font-semibold" class="mb-1 rounded-md px-2 py-1 text-sm font-semibold"
> >
{{ docsGroup.title }} {{ docsGroup.title }}
<span v-if="docsGroup.label" class="ml-2 font-normal rounded-md bg-[#adfa1d] px-1.5 py-0.5 text-xs leading-none text-[#000000] no-underline group-hover:no-underline">
{{ docsGroup.label }}
</span>
</h4> </h4>
<div <div
@ -62,20 +65,17 @@ const sourceLink = 'https://github.com/radix-vue/shadcn-vue/tree/dev/'
<TableOfContentVue /> <TableOfContentVue />
</div> </div>
<div class="mb-4 flex items-center space-x-1 text-sm text-muted-foreground"> <DocsBreadcrumb class="mb-4" />
<div class="overflow-hidden text-ellipsis whitespace-nowrap">
Docs
</div>
<ChevronRightIcon class="h-4 w-4" />
<div class="font-medium text-foreground">
{{ frontmatter.title }}
</div>
</div>
<div class="space-y-2"> <div class="space-y-2">
<h1 class="scroll-m-20 text-4xl font-bold tracking-tight"> <div class="flex items-center space-x-4">
{{ frontmatter.title }} <h1 class="scroll-m-20 text-4xl font-bold tracking-tight">
</h1> {{ frontmatter.title }}
</h1>
<span v-if="frontmatter.label" class="ml-2 rounded-md bg-[#adfa1d] px-1.5 py-0.5 text-xs leading-none text-[#000000] no-underline group-hover:no-underline">
{{ frontmatter.label }}
</span>
</div>
<p class="text-lg text-muted-foreground"> <p class="text-lg text-muted-foreground">
{{ frontmatter.description }} {{ frontmatter.description }}
</p> </p>

View File

@ -4,28 +4,16 @@ import PageHeaderHeading from '../components/PageHeaderHeading.vue'
import PageHeaderDescription from '../components/PageHeaderDescription.vue' import PageHeaderDescription from '../components/PageHeaderDescription.vue'
import PageAction from '../components/PageAction.vue' import PageAction from '../components/PageAction.vue'
import ExamplesNav from '../components/ExamplesNav.vue' import ExamplesNav from '../components/ExamplesNav.vue'
import { announcementConfig } from '../config/site' import Announcement from '../components/Announcement.vue'
import ArrowRightIcon from '~icons/radix-icons/arrow-right'
import { buttonVariants } from '@/lib/registry/new-york/ui/button' import { buttonVariants } from '@/lib/registry/new-york/ui/button'
import { Separator } from '@/lib/registry/new-york/ui/separator'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
</script> </script>
<template> <template>
<div class="container relative"> <div class="container relative">
<PageHeader class="page-header pb-8"> <PageHeader class="page-header pb-8">
<a <Announcement />
:href="announcementConfig.link"
class="inline-flex items-center rounded-lg bg-muted px-3 py-1 text-sm font-medium"
>
{{ announcementConfig.icon }} <Separator class="mx-2 h-4" orientation="vertical" />
<span class="sm:hidden">{{ announcementConfig.title }}</span>
<span class="hidden sm:inline">
{{ announcementConfig.title }}
</span>
<ArrowRightIcon class="ml-1 h-4 w-4" />
</a>
<PageHeaderHeading class="hidden md:block"> <PageHeaderHeading class="hidden md:block">
Check out some examples. Check out some examples.
</PageHeaderHeading> </PageHeaderHeading>

View File

@ -2,12 +2,13 @@
import { useMagicKeys, useToggle } from '@vueuse/core' import { useMagicKeys, useToggle } from '@vueuse/core'
import { onMounted, ref, watch } from 'vue' import { onMounted, ref, watch } from 'vue'
import { Content, useData, useRoute, useRouter } from 'vitepress' import { Content, useData, useRoute, useRouter } from 'vitepress'
import { SearchIcon } from 'lucide-vue-next'
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 ThemePopover from '../components/ThemePopover.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'
import { Button } from '@/lib/registry/default/ui/button' import { Button } from '@/lib/registry/default/ui/button'
@ -19,6 +20,7 @@ import { Dialog, DialogContent } from '@/lib/registry/default/ui/dialog'
import { Toaster as DefaultToaster } from '@/lib/registry/default/ui/toast' import { Toaster as DefaultToaster } from '@/lib/registry/default/ui/toast'
import { Toaster as NewYorkSonner } from '@/lib/registry/new-york/ui/sonner' import { Toaster as NewYorkSonner } from '@/lib/registry/new-york/ui/sonner'
import { Toaster as NewYorkToaster } from '@/lib/registry/new-york/ui/toast' import { Toaster as NewYorkToaster } from '@/lib/registry/new-york/ui/toast'
import { TooltipProvider } from '@/lib/registry/new-york/ui/tooltip'
import File from '~icons/radix-icons/file' import File from '~icons/radix-icons/file'
import Circle from '~icons/radix-icons/circle' import Circle from '~icons/radix-icons/circle'
@ -84,215 +86,225 @@ watch(() => $route.path, (n) => {
</script> </script>
<template> <template>
<div class="flex min-h-screen flex-col bg-background"> <TooltipProvider>
<header class="sticky z-40 top-0 bg-background/80 backdrop-blur-lg border-b border-border"> <div v-if="$route.data.frontmatter.layout === false">
<div <Content :key="$route.path" />
class="container flex justify-between h-14 max-w-screen-2xl items-center" </div>
> <div v-else vaul-drawer-wrapper class="flex min-h-screen flex-col bg-background">
<MobileNav /> <header class="sticky z-40 top-0 bg-background/80 backdrop-blur-lg border-b border-border">
<div
class="container flex h-14 max-w-screen-2xl items-center"
>
<div class="mr-4 md:mr-1 hidden md:flex">
<Logo />
<div class="mr-4 hidden md:flex"> <nav
<Logo /> class="flex items-center max-lg:space-x-4 space-x-6 text-sm font-medium"
<nav
class="flex items-center space-x-6 text-sm font-medium"
>
<a
v-for="route in docsConfig.mainNav"
:key="route.title"
:href="route.href"
:target="route.external ? '_target' : undefined"
class="transition-colors hover:text-foreground/80 text-foreground/60"
:class="{
'font-semibold !text-foreground': $route.path === `${route.href}.html`,
}"
> >
{{ route.title }} <a
</a> v-for="route in docsConfig.mainNav"
</nav> :key="route.title"
</div> :href="route.href"
:target="route.external ? '_target' : undefined"
<div class=" flex items-center justify-end space-x-4 "> class="transition-colors hover:text-foreground/80 text-foreground/60"
<Button :class="{
variant="outline" 'font-semibold !text-foreground': $route.path === `${route.href}.html`,
class="w-72 h-8 px-3 hidden lg:flex lg:justify-between lg:items-center" 'hidden lg:block': route?.href?.includes('github'),
@click="isOpen = true" }"
>
<div class="flex items-center">
<SearchIcon class="w-4 h-4 mr-2 text-muted-foreground" />
<span class="text-muted-foreground"> Search for anything... </span>
</div>
<div class="flex items-center gap-x-1">
<Kbd> <span></span>K </Kbd>
</div>
</Button>
<div class="flex items-center gap-x-1">
<Button
v-for="link in links"
:key="link.name"
as="a"
:href="link.href" target="_blank"
:variant="'ghost'" :size="'icon'"
>
<component :is="link.icon" class="w-[20px] h-5" />
</Button>
<ClientOnly>
<Button
class="flex items-center justify-center"
aria-label="Toggle dark mode"
:variant="'ghost'"
:size="'icon'" @click="toggleDark()"
> >
<component {{ route.title }}
:is="isDark ? RadixIconsSun : RadixIconsMoon" </a>
class="w-[20px] h-5 text-foreground" </nav>
/> </div>
<MobileNav />
<div class="flex flex-1 items-center justify-between space-x-2 md:justify-end">
<div class="w-full flex-1 md:w-auto md:flex-none">
<Button
variant="outline"
class="relative h-8 w-full justify-start rounded-[0.5rem] bg-background text-sm font-normal text-muted-foreground shadow-none sm:pr-12 md:w-40 lg:w-64"
@click="isOpen = true"
>
<span class="hidden lg:inline-flex">Search documentation...</span>
<span class="inline-flex lg:hidden">Search...</span>
<Kbd class="pointer-events-none absolute right-[0.3rem] top-[0.3rem] hidden h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium opacity-100 sm:flex">
<span class="text-xs"></span>K
</Kbd>
</Button> </Button>
</ClientOnly> </div>
<nav class="flex items-center">
<ThemePopover />
<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'"
>
<component :is="link.icon" class="w-5 h-5" />
</Button>
<ClientOnly>
<Button
class="w-9 h-9"
aria-label="Toggle dark mode"
:variant="'ghost'"
:size="'icon'"
@click="toggleDark()"
>
<component
:is="isDark ? RadixIconsSun : RadixIconsMoon"
class="w-5 h-5 text-foreground"
/>
</Button>
</ClientOnly>
</nav>
</div> </div>
</div> </div>
</header>
<div class="flex-1 bg-background">
<Transition name="fade" mode="out-in">
<component :is="'docs'" v-if="$route.path.includes('docs')">
<Transition name="fade" mode="out-in">
<Content :key="$route.path" />
</Transition>
</component>
<component :is="'examples'" v-else-if="$route.path.includes('examples')">
<Transition name="fade" mode="out-in">
<Content :key="$route.path" />
</Transition>
</component>
<component :is="frontmatter.layout" v-else-if="frontmatter.layout">
<slot />
</component>
<main v-else class="container">
<Transition name="fade" mode="out-in">
<Content :key="$route.path" />
</Transition>
</main>
</Transition>
</div> </div>
</header>
<div class="flex-1 bg-background"> <footer class="py-6 md:px-8 md:py-0">
<Transition name="fade" mode="out-in"> <div class="container flex flex-col items-center justify-between gap-4 md:h-24 md:flex-row">
<component :is="'docs'" v-if="$route.path.includes('docs')"> <div class="text-center text-sm leading-loose text-muted-foreground md:text-left">
<Transition name="fade" mode="out-in"> <span class="inline-block">
<Content :key="$route.path" /> Built and designed by
</Transition> <a
</component> href="https://twitter.com/shadcn"
<component :is="'examples'" v-else-if="$route.path.includes('examples')"> target="_blank"
<Transition name="fade" mode="out-in"> class="underline underline-offset-4 font-bold decoration-foreground"
<Content :key="$route.path" /> >
</Transition> shadcn
</component> </a>
<component :is="frontmatter.layout" v-else-if="frontmatter.layout"> </span>
<slot /> <span class="ml-0.5"> . </span>
</component> <span class="inline-block ml-2">
<main v-else class="container"> Ported to Vue by
<Transition name="fade" mode="out-in"> <a
<Content :key="$route.path" /> href="https://github.com/radix-vue"
</Transition> target="_blank"
</main> class="underline underline-offset-4 font-bold decoration-foreground"
</Transition> >
</div> Radix Vue
</a>
<footer class="py-6 md:px-8 md:py-0"> </span>
<div class="container flex flex-col items-center justify-between gap-4 md:h-24 md:flex-row"> <span class="ml-0.5"> . </span>
<div class="text-center text-sm leading-loose text-muted-foreground md:text-left"> <span class="inline-block ml-2">
<span class="inline-block"> The code source is available on
Built and designed by <a
<a href="https://github.com/radix-vue/shadcn-vue"
href="https://twitter.com/shadcn" target="_blank"
target="_blank" class="underline underline-offset-4 font-bold decoration-foreground"
class="underline underline-offset-4 font-bold decoration-foreground" >
> GitHub
shadcn </a>
</a> </span>
</span> </div>
<span class="ml-0.5"> . </span>
<span class="inline-block ml-2">
Ported to Vue by
<a
href="https://github.com/radix-vue"
target="_blank"
class="underline underline-offset-4 font-bold decoration-foreground"
>
Radix Vue
</a>
</span>
<span class="ml-0.5"> . </span>
<span class="inline-block ml-2">
The code source is available on
<a
href="https://github.com/radix-vue/shadcn-vue"
target="_blank"
class="underline underline-offset-4 font-bold decoration-foreground"
>
GitHub
</a>
</span>
</div> </div>
</div> </footer>
</footer>
<Dialog v-model:open="isOpen"> <Dialog v-model:open="isOpen">
<DialogContent class="p-0"> <DialogContent class="p-0">
<Command> <Command>
<CommandInput placeholder="Type a command or search..." /> <CommandInput placeholder="Type a command or search..." />
<CommandEmpty> <CommandEmpty>
No results found. No results found.
</CommandEmpty> </CommandEmpty>
<CommandList <CommandList
@escape-key-down=" isOpen = false" @escape-key-down=" isOpen = false"
> >
<CommandGroup heading="Links"> <CommandGroup heading="Links">
<CommandItem <CommandItem
v-for="item in docsConfig.mainNav" v-for="item in docsConfig.mainNav"
:key="item.title" :key="item.title"
:heading="item.title" :heading="item.title"
:value="item.title" :value="item.title"
class="py-3" class="py-3"
@select="handleSelectLink(item)" @select="handleSelectLink(item)"
> >
<File class="mr-2 h-5 w-5" /> <File class="mr-2 h-5 w-5" />
<span>{{ item.title }}</span> <span>{{ item.title }}</span>
</CommandItem> </CommandItem>
</CommandGroup> </CommandGroup>
<CommandSeparator /> <CommandSeparator />
<CommandGroup v-for="item in docsConfig.sidebarNav" :key="item.title" :heading="item.title"> <CommandGroup v-for="item in docsConfig.sidebarNav" :key="item.title" :heading="item.title">
<CommandItem <CommandItem
v-for="subItem in item.items" v-for="subItem in item.items"
:key="subItem.title" :key="subItem.title"
:heading="subItem.title" :heading="subItem.title"
:value="subItem.title" :value="subItem.title"
class="py-3" class="py-3"
@select=" @select="
handleSelectLink(subItem)" handleSelectLink(subItem)"
> >
<Circle class="mr-2 h-4 w-4" /> <Circle class="mr-2 h-4 w-4" />
<span>{{ subItem.title }}</span> <span>{{ subItem.title }}</span>
</CommandItem> </CommandItem>
</CommandGroup> </CommandGroup>
<CommandSeparator /> <CommandSeparator />
<CommandGroup heading="Theme"> <CommandGroup heading="Theme">
<CommandItem <CommandItem
value="light-theme" value="light-theme"
class="py-3" class="py-3"
@select=" @select="
() => { () => {
isDark = false; isDark = false;
isOpen = false; isOpen = false;
} }
" "
> >
<RadixIconsSun class="mr-2 h-5 w-5" /> <RadixIconsSun class="mr-2 h-5 w-5" />
<span>Light Theme</span> <span>Light Theme</span>
</CommandItem> </CommandItem>
<CommandItem <CommandItem
value="dark-theme" value="dark-theme"
class="py-3" class="py-3"
@select=" @select="
() => { () => {
isDark = true; isDark = true;
isOpen = false; isOpen = false;
} }
" "
> >
<RadixIconsMoon class="mr-2 h-5 w-5" /> <RadixIconsMoon class="mr-2 h-5 w-5" />
<span>Dark Theme</span> <span>Dark Theme</span>
</CommandItem> </CommandItem>
</CommandGroup> </CommandGroup>
</CommandList> </CommandList>
</Command> </Command>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
<DefaultToaster /> <DefaultToaster />
<ClientOnly> <NewYorkSonner class="pointer-events-auto" :theme="'system'" />
<NewYorkSonner :theme="isDark ? 'dark' : 'light'" /> <NewYorkToaster />
</ClientOnly> </div>
<NewYorkToaster /> </TooltipProvider>
</div>
</template> </template>

View File

@ -1,35 +1,19 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, watch } from 'vue' import { onMounted, watch } from 'vue'
import { Paintbrush } from 'lucide-vue-next' import { Paintbrush } from 'lucide-vue-next'
import { useData } from 'vitepress'
import PageHeader from '../components/PageHeader.vue' import PageHeader from '../components/PageHeader.vue'
import PageHeaderHeading from '../components/PageHeaderHeading.vue' import PageHeaderHeading from '../components/PageHeaderHeading.vue'
import PageHeaderDescription from '../components/PageHeaderDescription.vue' import PageHeaderDescription from '../components/PageHeaderDescription.vue'
import CustomizerCode from '../components/CustomizerCode.vue' import CustomizerCode from '../components/CustomizerCode.vue'
import { RADII, useConfigStore } from '@/stores/config' import type { Color } from '../types/colors'
import { colors } from '@/lib/registry' import ThemeCustomizer from '../components/ThemeCustomizer.vue'
import InlineThemePicker from '../components/InlineThemePicker.vue'
import PageAction from '../components/PageAction.vue'
import { useConfigStore } from '@/stores/config'
import { Button } from '@/lib/registry/new-york/ui/button' import { Button } from '@/lib/registry/new-york/ui/button'
import { Label } from '@/lib/registry/new-york/ui/label'
import { Popover, PopoverContent, PopoverTrigger } from '@/lib/registry/new-york/ui/popover' import { Popover, PopoverContent, PopoverTrigger } from '@/lib/registry/new-york/ui/popover'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/lib/registry/new-york/ui/tooltip'
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '@/lib/registry/new-york/ui/dialog' import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '@/lib/registry/new-york/ui/dialog'
import RadixIconsCheck from '~icons/radix-icons/check' import { Drawer, DrawerContent, DrawerTrigger } from '@/lib/registry/new-york/ui/drawer'
import RadixIconsSun from '~icons/radix-icons/sun'
import RadixIconsMoon from '~icons/radix-icons/moon'
type Color =
| 'zinc'
| 'slate'
| 'stone'
| 'gray'
| 'neutral'
| 'red'
| 'rose'
| 'orange'
| 'green'
| 'blue'
| 'yellow'
| 'violet'
// Create an array of color values // Create an array of color values
const allColors: Color[] = [ const allColors: Color[] = [
@ -47,8 +31,7 @@ const allColors: Color[] = [
'violet', 'violet',
] ]
const { theme, radius, setRadius, setTheme } = useConfigStore() const { theme, radius } = useConfigStore()
const { isDark } = useData()
// Whenever the component is mounted, update the document class list // Whenever the component is mounted, update the document class list
onMounted(() => { onMounted(() => {
@ -72,173 +55,63 @@ watch(radius, (radius) => {
<template> <template>
<div class="container relative"> <div class="container relative">
<div class="flex justify-between items-center"> <PageHeader>
<div> <PageHeaderHeading class="hidden md:block">
<PageHeader class="page-header pb-8"> Add colors. Make it yours.
<PageHeaderHeading class="hidden md:block"> </PageHeaderHeading>
Make it yours. <PageHeaderHeading class="md:hidden">
</PageHeaderHeading> Make it yours
<PageHeaderDescription> </PageHeaderHeading>
Hand-picked themes that you can copy and paste into your apps. <PageHeaderDescription>
</PageHeaderDescription> Hand-picked themes that you can copy and paste into your apps.
</PageHeader> </PageHeaderDescription>
</div>
<div class="px-4 pb-8 md:ml-auto md:pb-0"> <PageAction>
<div class="flex items-center space-x-2"> <InlineThemePicker class="gap-x-1 me-4 hidden lg:flex" :all-colors="allColors" />
<div class="hidden md:flex">
<div class="mr-4 hidden items-center space-x-1 lg:flex"> <Drawer>
<TooltipProvider <DrawerTrigger as-child>
v-for="(color, index) in allColors.slice(0, 5)" <Button variant="outline" class="md:hidden h-9 rounded-[0.5rem]">
:key="index" <Paintbrush class="w-4 h-4 mr-2" />
> Customize
<Tooltip> </Button>
<TooltipTrigger as-child> </DrawerTrigger>
<button <DrawerContent class="p-6 pt-0">
:key="index" <ThemeCustomizer :all-colors="allColors" />
class="flex h-9 w-9 items-center justify-center rounded-full border-2 border-border text-xs" </DrawerContent>
:class=" </Drawer>
color === theme
? 'border-primary' <Popover>
: 'border-transparent' <PopoverTrigger as-child>
" <Button variant="outline" class="hidden md:flex h-9 rounded-[0.5rem]">
@click="setTheme(color)" <Paintbrush class="w-4 h-4 mr-2" />
> Customize
<span </Button>
class="flex h-6 w-6 items-center justify-center rounded-full" </PopoverTrigger>
:style="{ backgroundColor: colors[color][6].rgb }" <PopoverContent :side-offset="8" align="end" class="w-96">
> <ThemeCustomizer :all-colors="allColors" />
<RadixIconsCheck </PopoverContent>
v-if="color === theme" </Popover>
class="h-4 w-4 text-white"
/> <Dialog>
</span> <DialogTrigger as-child>
</button> <Button class="h-9 ml-2 rounded-[0.5rem]">
</TooltipTrigger> Copy code
<TooltipContent </Button>
align="center" </DialogTrigger>
:side-offset="1" <DialogContent class="sm:max-w-[625px]">
class="capitalize bg-zinc-900 text-zinc-50" <DialogHeader>
> <DialogTitle>Theme</DialogTitle>
{{ allColors[index] }} <DialogDescription>
</TooltipContent> Copy and paste the following code into your CSS file.
</Tooltip> </DialogDescription>
</TooltipProvider> </DialogHeader>
</div> <CustomizerCode />
<Popover> </DialogContent>
<PopoverTrigger as-child> </Dialog>
<Button variant="outline" class="h-9 rounded-[0.5rem]"> </PageAction>
<Paintbrush class="w-4 h-4 mr-2" /> </PageHeader>
Customize
</Button>
</PopoverTrigger>
<PopoverContent :side-offset="8" align="end" class="w-96">
<div class="p-4">
<div class="grid space-y-1">
<h1 class="text-md text-foreground font-semibold">
Customize
</h1>
<p class="text-xs text-muted-foreground">
Pick a style and color for your components.
</p>
</div>
<div class="space-y-1.5 pt-6">
<Label for="color" class="text-xs"> Color </Label>
<div class="grid grid-cols-3 gap-2 py-1.5">
<Button
v-for="(color, index) in allColors"
:key="index"
variant="outline"
class="h-8 justify-start px-3"
:class="
color === theme
? 'border-foreground border-2'
: ''
"
@click="setTheme(color)"
>
<span
class="h-5 w-5 rounded-full flex items-center justify-center"
:style="{ backgroundColor: colors[color][7].rgb }"
>
<RadixIconsCheck
v-if="color === theme"
class="h-3 w-3 text-white"
/>
</span>
<span class="ml-2 text-xs capitalize">
{{ color }}
</span>
</Button>
</div>
</div>
<div class="space-y-1.5 pt-6">
<Label for="radius" class="text-xs"> Radius </Label>
<div class="grid grid-cols-5 gap-2 py-1.5">
<Button
v-for="(r, index) in RADII"
:key="index"
variant="outline"
class="h-8 justify-center px-3"
:class="
r === radius
? 'border-foreground border-2'
: ''
"
@click="setRadius(r)"
>
<span class="text-xs">
{{ r }}
</span>
</Button>
</div>
</div>
<div class="space-y-1.5 pt-6">
<Label for="theme" class="text-xs"> Theme </Label>
<div class="flex space-x-2 py-1.5">
<Button
class="h-8"
variant="outline"
:class="{ 'border-2 border-foreground': !isDark }"
@click="isDark = false"
>
<RadixIconsSun class="w-4 h-4 mr-2" />
<span class="text-xs">Light</span>
</Button>
<Button
class="h-8"
variant="outline"
:class="{ 'border-2 border-foreground': isDark }"
@click="isDark = true"
>
<RadixIconsMoon class="w-4 h-4 mr-2" />
<span class="text-xs">Dark</span>
</Button>
</div>
</div>
</div>
</PopoverContent>
</Popover>
<Dialog>
<DialogTrigger as-child>
<Button class="h-9 ml-2 rounded-[0.5rem]">
Copy code
</Button>
</DialogTrigger>
<DialogContent class="sm:max-w-[625px]">
<DialogHeader>
<DialogTitle>Theme</DialogTitle>
<DialogDescription>
Copy and paste the following code into your CSS file.
</DialogDescription>
</DialogHeader>
<CustomizerCode />
</DialogContent>
</Dialog>
</div>
</div>
</div>
</div>
<section> <section>
<slot /> <slot />
</section> </section>

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

@ -7,26 +7,41 @@
--font-geist-sans: "geist-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, --font-geist-sans: "geist-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
--background: 0 0% 100%; --background: 0 0% 100%;
--foreground: 240 10% 3.9%; --foreground: 240 10% 3.9%;
--card: 0 0% 100%; --card: 0 0% 100%;
--card-foreground: 240 10% 3.9%; --card-foreground: 240 10% 3.9%;
--popover: 0 0% 100%; --popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%; --popover-foreground: 240 10% 3.9%;
--primary: 240 5.9% 10%; --primary: 240 5.9% 10%;
--primary-foreground: 0 0% 98%; --primary-foreground: 0 0% 98%;
--secondary: 240 4.8% 95.9%; --secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%; --secondary-foreground: 240 5.9% 10%;
--muted: 240 4.8% 95.9%; --muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%; --muted-foreground: 240 3.8% 46.1%;
--accent: 240 4.8% 95.9%; --accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%; --accent-foreground: 240 5.9% 10%;
--destructive: 0 72.22% 50.59%; --destructive: 0 72.22% 50.59%;
--destructive-foreground: 0 0% 98%; --destructive-foreground: 0 0% 98%;
--border: 240 5.9% 90%; --border: 240 5.9% 90%;
--input: 240 5.9% 90%; --input: 240 5.9% 90%;
--ring: 240 5% 64.9%; --ring: 240 5% 64.9%;
--radius: 0.5rem; --radius: 0.5rem;
--vis-primary-color: var(--primary);
--vis-secondary-color: 160 81% 40%;
--vis-text-color: var(--muted-foreground);
--vis-font-family: inherit !important;
--vis-area-stroke-width: 2px !important;
--vis-donut-central-label-text-color: hsl(var(--muted-foreground)) !important;
--vis-tooltip-background-color: none !important;
--vis-tooltip-border-color: none !important;
--vis-tooltip-text-color: none !important;
--vis-tooltip-shadow-color: none !important;
--vis-tooltip-backdrop-filter: none !important;
--vis-tooltip-padding: none !important;
} }
.dark { .dark {
@ -61,7 +76,9 @@
} }
body { body {
@apply bg-background text-foreground min-h-screen antialiased font-sans; @apply bg-background text-foreground min-h-screen antialiased font-sans;
font-feature-settings: "rlig" 1, "calt" 1; /* font-feature-settings: "rlig" 1, "calt" 1; */
font-synthesis-weight: none;
text-rendering: optimizeLegibility;
} }
/* Mobile tap highlight */ /* Mobile tap highlight */
@ -140,22 +157,30 @@
} }
div[class^="language-"] { div[class^="language-"] {
@apply mb-4 mt-6 max-h-[650px] overflow-x-auto md:rounded-lg border !bg-zinc-950 dark:!bg-zinc-900 @apply mb-4 mt-6 max-h-[650px] overflow-x-auto md:rounded-lg border !bg-secondary-foreground dark:!bg-secondary
} }
pre { pre {
@apply py-4; @apply py-4;
} }
pre code { pre code {
@apply relative font-mono text-sm ; @apply relative font-mono text-sm ;
}
.line-numbers-wrapper, code {
--vp-code-line-height: 1.7;
}
.line-numbers-wrapper {
@apply font-mono;
} }
pre code .line { pre code .line {
@apply px-4 min-h-6 !py-0.5 w-full inline-block; @apply px-4 min-h-4 !py-0.5 w-full inline-block leading-[--vp-code-line-height];
} }
.line-number { .line-number {
@apply min-h-[1.375rem] !text-sm !inline-block text-muted-foreground; @apply !text-[.75rem] !inline-block text-muted-foreground leading-[--vp-code-line-height];
} }
::view-transition-old(root), ::view-transition-old(root),

View File

@ -345,13 +345,13 @@
} }
.vp-doc [class*='language-'] code .highlighted { .vp-doc [class*='language-'] code .highlighted {
background-color: hsl(240 3.7% 15.9%);
transition: background-color 0.5s; transition: background-color 0.5s;
/* margin: 0 -24px; /* margin: 0 -24px;
padding: 0 24px; */ padding: 0 24px; */
width: calc(100% + 2 * 24px); width: calc(100% + 2 * 24px);
display: inline-block; display: inline-block;
} @apply bg-[hsl(var(--foreground))] dark:bg-[hsl(var(--background)_/_50%)]
}
.vp-doc [class*='language-'] code .highlighted.error { .vp-doc [class*='language-'] code .highlighted.error {
background-color: var(--vp-code-line-error-color); background-color: var(--vp-code-line-error-color);

View File

@ -0,0 +1,13 @@
export type Color =
| 'zinc'
| 'slate'
| 'stone'
| 'gray'
| 'neutral'
| 'red'
| 'rose'
| 'orange'
| 'green'
| 'blue'
| 'yellow'
| 'violet'

View File

@ -2,9 +2,11 @@ import { getParameters } from 'codesandbox/lib/api/define'
import sdk from '@stackblitz/sdk' import sdk from '@stackblitz/sdk'
import { dependencies as deps } from '../../../package.json' import { dependencies as deps } from '../../../package.json'
import { Index as demoIndex } from '../../../../www/__registry__' import { Index as demoIndex } from '../../../../www/__registry__'
// @ts-expect-error ?raw
import tailwindConfigRaw from '../../../tailwind.config?raw' import tailwindConfigRaw from '../../../tailwind.config?raw'
// @ts-expect-error ?raw
import cssRaw from '../../../../../packages/cli/test/fixtures/nuxt/assets/css/tailwind.css?raw' import cssRaw from '../../../../../packages/cli/test/fixtures/nuxt/assets/css/tailwind.css?raw'
import { type Style } from '@/lib/registry/styles' import type { Style } from '@/lib/registry/styles'
export function makeCodeSandboxParams(componentName: string, style: Style, sources: Record<string, string>) { export function makeCodeSandboxParams(componentName: string, style: Style, sources: Record<string, string>) {
let files: Record<string, any> = {} let files: Record<string, any> = {}
@ -18,6 +20,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))
return sdk.openProject({ return sdk.openProject({
title: `${componentName} - Radix Vue`, title: `${componentName} - Radix Vue`,
files, files,
@ -34,7 +37,15 @@ const viteConfig = {
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import tailwind from 'tailwindcss';
import autoprefixer from 'autoprefixer';
export default defineConfig({ export default defineConfig({
css: {
postcss: {
plugins: [tailwind(), autoprefixer()],
},
},
plugins: [vue()], plugins: [vue()],
resolve: { resolve: {
alias: { alias: {
@ -54,7 +65,7 @@ export default defineConfig({
<title>Vite + Vue + TS</title> <title>Vite + Vue + TS</title>
</head> </head>
<body> <body>
<div id="app"></div> <div vaul-drawer-wrapper id="app"></div>
<script type="module" src="/src/main.ts"></script> <script type="module" src="/src/main.ts"></script>
</body> </body>
</html> </html>
@ -90,6 +101,10 @@ function constructFiles(componentName: string, style: Style, sources: Record<str
[iconPackage]: 'latest', [iconPackage]: 'latest',
'shadcn-vue': 'latest', 'shadcn-vue': 'latest',
'typescript': 'latest', 'typescript': 'latest',
'vaul-vue': 'latest',
'vue-sonner': 'latest',
'@unovis/vue': 'latest',
'@unovis/ts': 'latest',
} }
const devDependencies = { const devDependencies = {
@ -97,10 +112,10 @@ function constructFiles(componentName: string, style: Style, sources: Record<str
'@vitejs/plugin-vue': 'latest', '@vitejs/plugin-vue': 'latest',
'vue-tsc': 'latest', 'vue-tsc': 'latest',
'tailwindcss': 'latest', 'tailwindcss': 'latest',
'postcss': 'latest',
'autoprefixer': 'latest', 'autoprefixer': 'latest',
} }
// We have static replace here as this is only showing for code reproduction, doesn't need dynamic codeConfig
const transformImportPath = (code: string) => { const transformImportPath = (code: string) => {
let parsed = code let parsed = code
parsed = parsed.replaceAll(`@/lib/registry/${style}`, '@/components') parsed = parsed.replaceAll(`@/lib/registry/${style}`, '@/components')
@ -117,7 +132,7 @@ function constructFiles(componentName: string, style: Style, sources: Record<str
} }
}) })
// @ts-expect-error componentName migth not exist in Index // @ts-expect-error componentName might not exist in Index
const registryDependencies = demoIndex[style][componentName as any]?.registryDependencies?.filter(i => i !== 'utils') const registryDependencies = demoIndex[style][componentName as any]?.registryDependencies?.filter(i => i !== 'utils')
const files = { const files = {
@ -139,15 +154,6 @@ function constructFiles(componentName: string, style: Style, sources: Record<str
content: tailwindConfigRaw, content: tailwindConfigRaw,
isBinary: false, isBinary: false,
}, },
'postcss.config.js': {
content: `module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
}
}`,
isBinary: false,
},
'tsconfig.json': { 'tsconfig.json': {
content: `{ content: `{
"$schema": "https://json.schemastore.org/tsconfig", "$schema": "https://json.schemastore.org/tsconfig",
@ -164,7 +170,6 @@ function constructFiles(componentName: string, style: Style, sources: Record<str
isBinary: false, isBinary: false,
content: `import { type ClassValue, clsx } from 'clsx' content: `import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge' import { twMerge } from 'tailwind-merge'
import { camelize, getCurrentInstance, toHandlerKey } from 'vue'
export function cn(...inputs: ClassValue[]) { export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)) return twMerge(clsx(inputs))

View File

@ -1 +1 @@
> Files inside this directory is autogenerated by `./scripts/build-registry.ts`. **Do not edit them manually.** > Files inside this directory is autogenerated by `./scripts/build-registry.ts`. **Do not edit them manually.**

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
{ {
"name": "www", "name": "www",
"type": "module", "type": "module",
"version": "0.9.0", "version": "0.10.5",
"files": [ "files": [
"dist" "dist"
], ],
@ -12,61 +12,70 @@
"typecheck": "vue-tsc", "typecheck": "vue-tsc",
"typecheck:registry": "vue-tsc -p tsconfig.registry.json", "typecheck:registry": "vue-tsc -p tsconfig.registry.json",
"build:registry": "tsx ./scripts/build-registry.ts", "build:registry": "tsx ./scripts/build-registry.ts",
"build:registry-strict": "pnpm typecheck:registry && tsx ./scripts/build-registry.ts" "build:registry-strict": "pnpm typecheck:registry && tsx ./scripts/build-registry.ts",
"docs:gen": "tsx ./scripts/autogen.ts"
}, },
"dependencies": { "dependencies": {
"@formkit/auto-animate": "^0.8.1", "@formkit/auto-animate": "^0.8.2",
"@morev/vue-transitions": "^2.3.6", "@internationalized/date": "^3.5.4",
"@radix-icons/vue": "^1.0.0", "@radix-icons/vue": "^1.0.0",
"@stackblitz/sdk": "^1.9.0", "@stackblitz/sdk": "^1.10.0",
"@tanstack/vue-table": "^8.11.8", "@tanstack/vue-table": "^8.17.3",
"@unovis/ts": "^1.3.3", "@unovis/ts": "^1.4.1",
"@unovis/vue": "^1.3.3", "@unovis/vue": "^1.4.1",
"@vee-validate/zod": "^4.12.5", "@vee-validate/zod": "^4.13.1",
"@vueuse/core": "^10.7.2", "@vueuse/core": "^10.11.0",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.1.0", "clsx": "^2.1.1",
"codesandbox": "^2.2.3", "codesandbox": "^2.2.3",
"date-fns": "^2.30.0", "date-fns": "^3.6.0",
"embla-carousel": "^8.0.0-rc22", "embla-carousel-autoplay": "^8.1.5",
"embla-carousel-autoplay": "^8.0.0-rc22", "embla-carousel-vue": "^8.1.5",
"embla-carousel-vue": "^8.0.0-rc22", "lucide-vue-next": "^0.383.0",
"lucide-vue-next": "^0.276.0", "magic-string": "^0.30.10",
"radix-vue": "^1.4.1", "radix-vue": "^1.8.4",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"v-calendar": "^3.1.2", "v-calendar": "^3.1.2",
"vee-validate": "4.12.5", "vaul-vue": "^0.2.0",
"vue": "^3.4.15", "vee-validate": "4.13.1",
"vue-sonner": "^1.0.3", "vue": "^3.4.29",
"vue-sonner": "^1.1.2",
"vue-wrap-balancer": "^1.1.3", "vue-wrap-balancer": "^1.1.3",
"zod": "^3.22.4" "zod": "^3.23.8"
}, },
"devDependencies": { "devDependencies": {
"@iconify-json/radix-icons": "^1.1.11", "@babel/traverse": "^7.24.7",
"@iconify-json/tabler": "^1.1.89", "@iconify-json/gravity-ui": "^1.1.3",
"@iconify/json": "^2.2.108", "@iconify-json/lucide": "^1.1.190",
"@iconify/vue": "^4.1.1", "@iconify-json/ph": "^1.1.13",
"@shikijs/transformers": "^1.0.0-beta.3", "@iconify-json/radix-icons": "^1.1.14",
"@types/lodash.template": "^4.5.2", "@iconify-json/ri": "^1.1.20",
"@types/node": "^20.8.10", "@iconify-json/simple-icons": "^1.1.104",
"@vitejs/plugin-vue": "^5.0.3", "@iconify-json/tabler": "^1.1.113",
"@vitejs/plugin-vue-jsx": "^3.1.0", "@iconify/vue": "^4.1.2",
"@vue/compiler-core": "^3.4.15", "@oxc-parser/wasm": "^0.14.0",
"@vue/compiler-dom": "^3.4.15", "@shikijs/transformers": "^1.7.0",
"@types/lodash-es": "^4.17.12",
"@types/node": "^20.14.4",
"@vitejs/plugin-vue": "^5.0.5",
"@vitejs/plugin-vue-jsx": "^4.0.0",
"@vue/compiler-core": "^3.4.29",
"@vue/compiler-dom": "^3.4.29",
"@vue/tsconfig": "^0.5.1", "@vue/tsconfig": "^0.5.1",
"autoprefixer": "^10.4.17", "autoprefixer": "^10.4.19",
"lodash.template": "^4.5.0", "fast-glob": "^3.3.2",
"oxc-parser": "^0.2.0", "lodash-es": "^4.17.21",
"markdown-it": "^14.1.0",
"pathe": "^1.1.2", "pathe": "^1.1.2",
"rimraf": "^5.0.5", "rimraf": "^5.0.7",
"shiki": "^1.0.0-beta.3", "shiki": "^1.7.0",
"tailwind-merge": "^2.2.1", "tailwind-merge": "^2.3.0",
"tailwindcss": "^3.4.1", "tailwindcss": "^3.4.4",
"tsx": "^4.7.0", "tsx": "^4.15.6",
"typescript": "^5.3.3", "typescript": "^5.4.5",
"unplugin-icons": "^0.18.3", "unplugin-icons": "^0.19.0",
"vite": "^5.0.12", "vitepress": "^1.2.3",
"vitepress": "^1.0.0-rc.41", "vue-component-meta": "^2.0.21",
"vue-tsc": "^1.8.27" "vue-tsc": "^2.0.21"
} }
} }

150
apps/www/scripts/autogen.ts Normal file
View File

@ -0,0 +1,150 @@
import { join, parse, resolve } from 'node:path'
import { existsSync, mkdirSync, writeFileSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import fg from 'fast-glob'
import MarkdownIt from 'markdown-it'
import type { ComponentMeta, MetaCheckerOptions, PropertyMeta, PropertyMetaSchema } from 'vue-component-meta'
import { createComponentMetaChecker } from 'vue-component-meta'
const __dirname = fileURLToPath(new URL('.', import.meta.url))
const md = new MarkdownIt()
const ROOTPATH = '../'
const OUTPUTPATH = '../src/content/meta'
const checkerOptions: MetaCheckerOptions = {
forceUseTs: true,
printer: { newLine: 1 },
}
const tsconfigChecker = createComponentMetaChecker(
resolve(__dirname, ROOTPATH, 'tsconfig.registry.json'),
checkerOptions,
)
const components = fg.sync(['chart/**/*.vue', 'chart*/**/*.vue'], {
cwd: resolve(__dirname, ROOTPATH, 'src/lib/registry/default/ui/'),
absolute: true,
})
components.forEach((componentPath) => {
try {
const componentName = parse(componentPath).name
const meta = parseMeta(tsconfigChecker.getComponentMeta(componentPath))
const metaDirPath = resolve(__dirname, OUTPUTPATH)
// if meta dir doesn't exist create
if (!existsSync(metaDirPath))
mkdirSync(metaDirPath)
const metaMdFilePath = join(metaDirPath, `${componentName}.md`)
let parsedString = '<!-- This file was automatic generated. Do not edit it manually -->\n\n'
if (meta.props.length)
parsedString += `<APITable :type="'prop'" :data="${JSON.stringify(meta.props, null, 2).replace(/"/g, '\'')}" />\n`
if (meta.events.length)
parsedString += `\n<APITable :type="'emit'" :data="${JSON.stringify(meta.events, null, 2).replace(/"/g, '\'')}" />\n`
if (meta.slots.length)
parsedString += `\n<APITable :type="'slot'" :data="${JSON.stringify(meta.slots, null, 2).replace(/"/g, '\'')}" />\n`
if (meta.methods.length)
parsedString += `\n<APITable :type="'method'" :data="${JSON.stringify(meta.methods, null, 2).replace(/"/g, '\'')}" />\n`
writeFileSync(metaMdFilePath, parsedString)
}
catch (err) {
console.log(err)
}
})
function parseTypeFromSchema(schema: PropertyMetaSchema): string {
if (typeof schema === 'object' && (schema.kind === 'enum' || schema.kind === 'array')) {
const isFlatEnum = schema.schema?.every(val => typeof val === 'string')
const enumValue = schema?.schema?.filter(i => i !== 'undefined') ?? []
if (isFlatEnum && /^[A-Z]/.test(schema.type))
return enumValue.join(' | ')
else if (typeof schema.schema?.[0] === 'object' && schema.schema?.[0].kind === 'enum')
return schema.schema.map((s: PropertyMetaSchema) => parseTypeFromSchema(s)).join(' | ')
else
return schema.type
}
else if (typeof schema === 'object' && schema.kind === 'object') {
return schema.type
}
else if (typeof schema === 'string') {
return schema
}
else {
return ''
}
}
// Utilities
function parseMeta(meta: ComponentMeta) {
const props = meta.props
// Exclude global props
.filter(prop => !prop.global)
.map((prop) => {
let defaultValue = prop.default
const { name, description, required } = prop
if (name === 'as')
defaultValue = defaultValue ?? '"div"'
if (defaultValue === 'undefined')
defaultValue = undefined
return ({
name,
description: md.render(description),
type: prop.type.replace(/\s*\|\s*undefined/g, ''),
required,
default: defaultValue ?? undefined,
})
})
const events = meta.events
.map((event) => {
const { name, type } = event
return ({
name,
type: type.replace(/\s*\|\s*undefined/g, ''),
})
})
const defaultSlot = meta.slots?.[0]
const slots: { name: string, description: string, type: string }[] = []
if (defaultSlot && defaultSlot.type !== '{}') {
const schema = defaultSlot.schema
if (typeof schema === 'object' && schema.schema) {
Object.values(schema.schema).forEach((childMeta: PropertyMeta) => {
slots.push({
name: childMeta.name,
description: md.render(childMeta.description),
type: parseTypeFromSchema(childMeta.schema),
})
})
}
}
// exposed method
const methods = meta.exposed
.filter(expose => typeof expose.schema === 'object' && expose.schema.kind === 'event')
.map(expose => ({
name: expose.name,
description: md.render(expose.description),
type: expose.type,
}))
return {
props,
events,
slots,
methods,
}
}

View File

@ -1,6 +1,6 @@
import fs from 'node:fs' import fs from 'node:fs'
import path, { basename } from 'node:path' import path, { basename } from 'node:path'
import template from 'lodash.template' import { template } from 'lodash-es'
import { rimraf } from 'rimraf' import { rimraf } from 'rimraf'
import { colorMapping, colors } from '../src/lib/registry/colors' import { colorMapping, colors } from '../src/lib/registry/colors'
@ -40,7 +40,7 @@ for (const style of styles) {
file => `../src/lib/registry/${style.name}/${file}`, file => `../src/lib/registry/${style.name}/${file}`,
) )
const type = item.type.split(':')[1] // const type = item.type.split(':')[1]
index += ` index += `
"${item.name}": { "${item.name}": {
name: "${item.name}", name: "${item.name}",
@ -282,7 +282,7 @@ for (const baseColor of ['slate', 'gray', 'zinc', 'neutral', 'stone', 'lime']) {
for (const [key, value] of Object.entries(values)) { for (const [key, value] of Object.entries(values)) {
if (typeof value === 'string') { if (typeof value === 'string') {
const resolvedColor = value.replace( const resolvedColor = value.replace(
/{{base}}-/g, /\{\{base\}\}-/g,
`${baseColor}-`, `${baseColor}-`,
) )
base.inlineColors[mode][key] = resolvedColor base.inlineColors[mode][key] = resolvedColor
@ -396,4 +396,4 @@ fs.writeFileSync(
'utf8', 'utf8',
) )
console.log('✅ Done!') console.log('✅ Done!!')

View File

@ -0,0 +1,176 @@
<mxfile host="app.diagrams.net" modified="2024-03-15T08:14:00.888Z" agent="Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0" etag="eUNOuIh_rCPXdI6LG0BE" version="24.0.6" type="device">
<diagram name="Page-1" id="10a91c8b-09ff-31b1-d368-03940ed4cc9e">
<mxGraphModel dx="1636" dy="971" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1100" pageHeight="850" background="none" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="PaMXV6_IjdSjTMUUNi7L-41" value="" style="rounded=0;whiteSpace=wrap;html=1;fontColor=none;noLabel=1;strokeColor=none;fillColor=none;" vertex="1" parent="1">
<mxGeometry x="30" y="70" width="1010" height="680" as="geometry" />
</mxCell>
<mxCell id="62893188c0fa7362-1" value="Shadcn/Vue" style="whiteSpace=wrap;html=1;rounded=1;shadow=0;labelBackgroundColor=none;strokeWidth=1;fontFamily=Garamond;fontSize=17;align=center;sketch=1;curveFitting=1;jiggle=2;" parent="1" vertex="1">
<mxGeometry x="380" y="130" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="62893188c0fa7362-2" value="Packages" style="whiteSpace=wrap;html=1;rounded=1;shadow=0;labelBackgroundColor=none;strokeWidth=1;fontFamily=Garamond;fontSize=17;align=center;sketch=1;curveFitting=1;jiggle=2;" parent="1" vertex="1">
<mxGeometry x="160" y="250" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="62893188c0fa7362-3" value="Apps/www" style="whiteSpace=wrap;html=1;rounded=1;shadow=0;labelBackgroundColor=none;strokeWidth=1;fontFamily=Garamond;fontSize=17;align=center;sketch=1;curveFitting=1;jiggle=2;" parent="1" vertex="1">
<mxGeometry x="630" y="250" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="62893188c0fa7362-4" value="" style="rounded=0;html=1;labelBackgroundColor=none;startArrow=none;startFill=0;startSize=5;endArrow=none;endFill=0;endSize=5;jettySize=auto;orthogonalLoop=1;strokeWidth=1;fontFamily=Garamond;fontSize=17;sketch=1;curveFitting=1;jiggle=2;shadow=0;" parent="1" source="PaMXV6_IjdSjTMUUNi7L-27" target="62893188c0fa7362-3" edge="1">
<mxGeometry x="-0.3002" y="13" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="62893188c0fa7362-5" value="" style="rounded=0;html=1;labelBackgroundColor=none;startArrow=none;startFill=0;startSize=5;endArrow=none;endFill=0;endSize=5;jettySize=auto;orthogonalLoop=1;strokeWidth=1;fontFamily=Garamond;fontSize=17;sketch=1;curveFitting=1;jiggle=2;shadow=0;" parent="1" source="62893188c0fa7362-1" target="62893188c0fa7362-2" edge="1">
<mxGeometry x="-0.359" y="-11" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="62893188c0fa7362-8" value="Module" style="whiteSpace=wrap;html=1;rounded=1;shadow=0;labelBackgroundColor=none;strokeWidth=1;fontFamily=Garamond;fontSize=17;align=center;sketch=1;curveFitting=1;jiggle=2;" parent="1" vertex="1">
<mxGeometry x="80" y="360" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="62893188c0fa7362-9" value="CLI" style="whiteSpace=wrap;html=1;rounded=1;shadow=0;labelBackgroundColor=none;strokeWidth=1;fontFamily=Garamond;fontSize=17;align=center;sketch=1;curveFitting=1;jiggle=2;" parent="1" vertex="1">
<mxGeometry x="220" y="360" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="62893188c0fa7362-14" value="" style="rounded=0;html=1;labelBackgroundColor=none;startArrow=none;startFill=0;startSize=5;endArrow=none;endFill=0;endSize=5;jettySize=auto;orthogonalLoop=1;strokeWidth=1;fontFamily=Garamond;fontSize=17;sketch=1;curveFitting=1;jiggle=2;shadow=0;" parent="1" source="62893188c0fa7362-2" target="62893188c0fa7362-8" edge="1">
<mxGeometry x="-0.2" y="-14" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="62893188c0fa7362-15" value="" style="rounded=0;html=1;labelBackgroundColor=none;startArrow=none;startFill=0;startSize=5;endArrow=none;endFill=0;endSize=5;jettySize=auto;orthogonalLoop=1;strokeWidth=1;fontFamily=Garamond;fontSize=17;sketch=1;curveFitting=1;jiggle=2;shadow=0;" parent="1" source="62893188c0fa7362-2" target="62893188c0fa7362-9" edge="1">
<mxGeometry x="-0.2" y="14" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="62893188c0fa7362-16" value="" style="rounded=0;html=1;labelBackgroundColor=none;startArrow=none;startFill=0;startSize=5;endArrow=none;endFill=0;endSize=5;jettySize=auto;orthogonalLoop=1;strokeWidth=1;fontFamily=Garamond;fontSize=17;entryX=0.75;entryY=0;entryDx=0;entryDy=0;sketch=1;curveFitting=1;jiggle=2;shadow=0;" parent="1" source="62893188c0fa7362-3" target="PaMXV6_IjdSjTMUUNi7L-2" edge="1">
<mxGeometry x="-0.2614" y="-13" relative="1" as="geometry">
<mxPoint as="offset" />
<mxPoint x="644.5454545454545" y="360" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="62893188c0fa7362-17" value="" style="rounded=0;html=1;labelBackgroundColor=none;startArrow=none;startFill=0;startSize=5;endArrow=none;endFill=0;endSize=5;jettySize=auto;orthogonalLoop=1;strokeWidth=1;fontFamily=Garamond;fontSize=17;entryX=0.25;entryY=0;entryDx=0;entryDy=0;sketch=1;curveFitting=1;jiggle=2;shadow=0;" parent="1" source="62893188c0fa7362-3" target="PaMXV6_IjdSjTMUUNi7L-1" edge="1">
<mxGeometry x="-0.1294" y="17" relative="1" as="geometry">
<mxPoint as="offset" />
<mxPoint x="782.7272727272725" y="360" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="PaMXV6_IjdSjTMUUNi7L-1" value="Registry" style="whiteSpace=wrap;html=1;rounded=1;shadow=0;labelBackgroundColor=none;strokeWidth=1;fontFamily=Garamond;fontSize=17;align=center;sketch=1;curveFitting=1;jiggle=2;" vertex="1" parent="1">
<mxGeometry x="720" y="370" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="PaMXV6_IjdSjTMUUNi7L-2" value=".vitepress" style="whiteSpace=wrap;html=1;rounded=1;shadow=0;labelBackgroundColor=none;strokeWidth=1;fontFamily=Garamond;fontSize=17;align=center;sketch=1;curveFitting=1;jiggle=2;" vertex="1" parent="1">
<mxGeometry x="420" y="370" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="PaMXV6_IjdSjTMUUNi7L-3" value="Scripts" style="whiteSpace=wrap;html=1;rounded=1;shadow=0;labelBackgroundColor=none;strokeWidth=1;fontFamily=Garamond;fontSize=17;align=center;sketch=1;curveFitting=1;jiggle=2;" vertex="1" parent="1">
<mxGeometry x="870" y="370" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="PaMXV6_IjdSjTMUUNi7L-4" value="Src" style="whiteSpace=wrap;html=1;rounded=1;shadow=0;labelBackgroundColor=none;strokeWidth=1;fontFamily=Garamond;fontSize=17;align=center;sketch=1;curveFitting=1;jiggle=2;" vertex="1" parent="1">
<mxGeometry x="570" y="370" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="PaMXV6_IjdSjTMUUNi7L-7" value="" style="rounded=0;html=1;labelBackgroundColor=none;startArrow=none;startFill=0;startSize=5;endArrow=none;endFill=0;endSize=5;jettySize=auto;orthogonalLoop=1;strokeWidth=1;fontFamily=Garamond;fontSize=17;entryX=0.333;entryY=0.017;entryDx=0;entryDy=0;exitX=1;exitY=1;exitDx=0;exitDy=0;entryPerimeter=0;sketch=1;curveFitting=1;jiggle=2;shadow=0;" edge="1" parent="1" source="62893188c0fa7362-3" target="PaMXV6_IjdSjTMUUNi7L-3">
<mxGeometry x="-0.1294" y="17" relative="1" as="geometry">
<mxPoint as="offset" />
<mxPoint x="841" y="270" as="sourcePoint" />
<mxPoint x="910" y="320" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="PaMXV6_IjdSjTMUUNi7L-9" value="" style="rounded=0;html=1;labelBackgroundColor=none;startArrow=none;startFill=0;startSize=5;endArrow=none;endFill=0;endSize=5;jettySize=auto;orthogonalLoop=1;strokeWidth=1;fontFamily=Garamond;fontSize=17;entryX=0.5;entryY=0;entryDx=0;entryDy=0;exitX=0.358;exitY=1.017;exitDx=0;exitDy=0;exitPerimeter=0;sketch=1;curveFitting=1;jiggle=2;shadow=0;" edge="1" parent="1" source="62893188c0fa7362-3" target="PaMXV6_IjdSjTMUUNi7L-4">
<mxGeometry x="-0.2614" y="-13" relative="1" as="geometry">
<mxPoint as="offset" />
<mxPoint x="720" y="540" as="sourcePoint" />
<mxPoint x="613" y="600" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="PaMXV6_IjdSjTMUUNi7L-11" value="Content" style="whiteSpace=wrap;html=1;rounded=1;shadow=0;labelBackgroundColor=none;strokeWidth=1;fontFamily=Garamond;fontSize=17;align=center;sketch=1;curveFitting=1;jiggle=2;" vertex="1" parent="1">
<mxGeometry x="420" y="500" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="PaMXV6_IjdSjTMUUNi7L-12" value="Examples" style="whiteSpace=wrap;html=1;rounded=1;shadow=0;labelBackgroundColor=none;strokeWidth=1;fontFamily=Garamond;fontSize=17;align=center;sketch=1;curveFitting=1;jiggle=2;" vertex="1" parent="1">
<mxGeometry x="570" y="500" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="PaMXV6_IjdSjTMUUNi7L-13" value="Lib/Registry" style="whiteSpace=wrap;html=1;rounded=1;shadow=0;labelBackgroundColor=none;strokeWidth=1;fontFamily=Garamond;fontSize=17;align=center;sketch=1;curveFitting=1;jiggle=2;" vertex="1" parent="1">
<mxGeometry x="720" y="500" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="PaMXV6_IjdSjTMUUNi7L-14" value="" style="rounded=0;html=1;labelBackgroundColor=none;startArrow=none;startFill=0;startSize=5;endArrow=none;endFill=0;endSize=5;jettySize=auto;orthogonalLoop=1;strokeWidth=1;fontFamily=Garamond;fontSize=17;entryX=0.5;entryY=0;entryDx=0;entryDy=0;exitX=0.308;exitY=1.033;exitDx=0;exitDy=0;exitPerimeter=0;sketch=1;curveFitting=1;jiggle=2;shadow=0;" edge="1" parent="1" source="PaMXV6_IjdSjTMUUNi7L-4" target="PaMXV6_IjdSjTMUUNi7L-11">
<mxGeometry x="-0.2614" y="-13" relative="1" as="geometry">
<mxPoint as="offset" />
<mxPoint x="560" y="470" as="sourcePoint" />
<mxPoint x="507" y="529" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="PaMXV6_IjdSjTMUUNi7L-15" value="" style="rounded=0;html=1;labelBackgroundColor=none;startArrow=none;startFill=0;startSize=5;endArrow=none;endFill=0;endSize=5;jettySize=auto;orthogonalLoop=1;strokeWidth=1;fontFamily=Garamond;fontSize=17;entryX=0.5;entryY=0;entryDx=0;entryDy=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;sketch=1;curveFitting=1;jiggle=2;shadow=0;" edge="1" parent="1" source="PaMXV6_IjdSjTMUUNi7L-4" target="PaMXV6_IjdSjTMUUNi7L-12">
<mxGeometry x="-0.2614" y="-13" relative="1" as="geometry">
<mxPoint as="offset" />
<mxPoint x="657" y="442" as="sourcePoint" />
<mxPoint x="540" y="560" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="PaMXV6_IjdSjTMUUNi7L-16" value="" style="rounded=0;html=1;labelBackgroundColor=none;startArrow=none;startFill=0;startSize=5;endArrow=none;endFill=0;endSize=5;jettySize=auto;orthogonalLoop=1;strokeWidth=1;fontFamily=Garamond;fontSize=17;entryX=0.5;entryY=0;entryDx=0;entryDy=0;exitX=0.75;exitY=1;exitDx=0;exitDy=0;sketch=1;curveFitting=1;jiggle=2;shadow=0;" edge="1" parent="1" source="PaMXV6_IjdSjTMUUNi7L-4" target="PaMXV6_IjdSjTMUUNi7L-13">
<mxGeometry x="-0.2614" y="-13" relative="1" as="geometry">
<mxPoint as="offset" />
<mxPoint x="660" y="435" as="sourcePoint" />
<mxPoint x="660" y="555" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="PaMXV6_IjdSjTMUUNi7L-20" value="Default" style="rounded=1;whiteSpace=wrap;html=1;fontFamily=Garamond;fontSize=17;sketch=1;curveFitting=1;jiggle=2;shadow=0;" vertex="1" parent="1">
<mxGeometry x="650" y="630" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="PaMXV6_IjdSjTMUUNi7L-21" value="New York" style="rounded=1;whiteSpace=wrap;html=1;fontFamily=Garamond;fontSize=17;sketch=1;curveFitting=1;jiggle=2;shadow=0;" vertex="1" parent="1">
<mxGeometry x="800" y="630" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="PaMXV6_IjdSjTMUUNi7L-22" value="" style="rounded=0;html=1;labelBackgroundColor=none;startArrow=none;startFill=0;startSize=5;endArrow=none;endFill=0;endSize=5;jettySize=auto;orthogonalLoop=1;strokeWidth=1;fontFamily=Garamond;fontSize=17;entryX=0.5;entryY=0;entryDx=0;entryDy=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;sketch=1;curveFitting=1;jiggle=2;shadow=0;" edge="1" parent="1" source="PaMXV6_IjdSjTMUUNi7L-13" target="PaMXV6_IjdSjTMUUNi7L-20">
<mxGeometry x="-0.2614" y="-13" relative="1" as="geometry">
<mxPoint as="offset" />
<mxPoint x="730" y="670" as="sourcePoint" />
<mxPoint x="530" y="818" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="PaMXV6_IjdSjTMUUNi7L-23" value="" style="rounded=0;html=1;labelBackgroundColor=none;startArrow=none;startFill=0;startSize=5;endArrow=none;endFill=0;endSize=5;jettySize=auto;orthogonalLoop=1;strokeWidth=1;fontFamily=Garamond;fontSize=17;entryX=0.5;entryY=0;entryDx=0;entryDy=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;sketch=1;curveFitting=1;jiggle=2;shadow=0;" edge="1" parent="1" source="PaMXV6_IjdSjTMUUNi7L-13" target="PaMXV6_IjdSjTMUUNi7L-21">
<mxGeometry x="-0.2614" y="-13" relative="1" as="geometry">
<mxPoint as="offset" />
<mxPoint x="790" y="670" as="sourcePoint" />
<mxPoint x="750" y="820" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="PaMXV6_IjdSjTMUUNi7L-26" value="1" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;sketch=1;curveFitting=1;jiggle=2;fontFamily=Garamond;fontSize=21;" vertex="1" parent="1">
<mxGeometry x="140" y="230" width="40" height="40" as="geometry" />
</mxCell>
<mxCell id="PaMXV6_IjdSjTMUUNi7L-28" value="3" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;sketch=1;curveFitting=1;jiggle=2;fontFamily=Garamond;fontSize=21;" vertex="1" parent="1">
<mxGeometry x="400" y="350" width="40" height="40" as="geometry" />
</mxCell>
<mxCell id="PaMXV6_IjdSjTMUUNi7L-29" value="4" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;sketch=1;curveFitting=1;jiggle=2;fontFamily=Garamond;fontSize=21;" vertex="1" parent="1">
<mxGeometry x="550" y="350" width="40" height="40" as="geometry" />
</mxCell>
<mxCell id="PaMXV6_IjdSjTMUUNi7L-30" value="5" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;sketch=1;curveFitting=1;jiggle=2;fontFamily=Garamond;fontSize=21;" vertex="1" parent="1">
<mxGeometry x="700" y="350" width="40" height="40" as="geometry" />
</mxCell>
<mxCell id="PaMXV6_IjdSjTMUUNi7L-31" value="6" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;sketch=1;curveFitting=1;jiggle=2;fontFamily=Garamond;fontSize=21;" vertex="1" parent="1">
<mxGeometry x="850" y="350" width="40" height="40" as="geometry" />
</mxCell>
<mxCell id="PaMXV6_IjdSjTMUUNi7L-32" value="7" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;sketch=1;curveFitting=1;jiggle=2;fontFamily=Garamond;fontSize=21;" vertex="1" parent="1">
<mxGeometry x="400" y="480" width="40" height="40" as="geometry" />
</mxCell>
<mxCell id="PaMXV6_IjdSjTMUUNi7L-33" value="8" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;sketch=1;curveFitting=1;jiggle=2;fontFamily=Garamond;fontSize=21;" vertex="1" parent="1">
<mxGeometry x="550" y="480" width="40" height="40" as="geometry" />
</mxCell>
<mxCell id="PaMXV6_IjdSjTMUUNi7L-34" value="9" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;sketch=1;curveFitting=1;jiggle=2;fontFamily=Garamond;fontSize=21;" vertex="1" parent="1">
<mxGeometry x="700" y="480" width="40" height="40" as="geometry" />
</mxCell>
<mxCell id="PaMXV6_IjdSjTMUUNi7L-36" value="10" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;sketch=1;curveFitting=1;jiggle=2;fontFamily=Garamond;fontSize=21;" vertex="1" parent="1">
<mxGeometry x="630" y="610" width="40" height="40" as="geometry" />
</mxCell>
<mxCell id="PaMXV6_IjdSjTMUUNi7L-38" value="11" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;sketch=1;curveFitting=1;jiggle=2;fontFamily=Garamond;fontSize=21;" vertex="1" parent="1">
<mxGeometry x="780" y="610" width="40" height="40" as="geometry" />
</mxCell>
<mxCell id="PaMXV6_IjdSjTMUUNi7L-39" value="" style="rounded=0;html=1;labelBackgroundColor=none;startArrow=none;startFill=0;startSize=5;endArrow=none;endFill=0;endSize=5;jettySize=auto;orthogonalLoop=1;strokeWidth=1;fontFamily=Garamond;fontSize=17;sketch=1;curveFitting=1;jiggle=2;shadow=0;" edge="1" parent="1" source="62893188c0fa7362-1" target="PaMXV6_IjdSjTMUUNi7L-27">
<mxGeometry x="-0.3002" y="13" relative="1" as="geometry">
<mxPoint as="offset" />
<mxPoint x="500" y="189" as="sourcePoint" />
<mxPoint x="630" y="251" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="PaMXV6_IjdSjTMUUNi7L-27" value="2" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;sketch=1;curveFitting=1;jiggle=2;fontFamily=Garamond;fontSize=21;" vertex="1" parent="1">
<mxGeometry x="610" y="230" width="40" height="40" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

10
apps/www/src/components.d.ts vendored Normal file
View File

@ -0,0 +1,10 @@
/* eslint-disable */
// @ts-nocheck
export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
ComponentPreview: typeof import('../.vitepress/theme/components/ComponentPreview.vue')['default']
}
}

View File

@ -47,3 +47,5 @@ const { site, theme, page, frontmatter } = useData()
## More ## More
Check out the documentation for the [full list of runtime APIs](https://vitepress.dev/reference/runtime-api#usedata). Check out the documentation for the [full list of runtime APIs](https://vitepress.dev/reference/runtime-api#usedata).
kick

View File

@ -0,0 +1,9 @@
---
title: Building Blocks
---
<script setup>
import Blocks from "../../.vitepress/theme/components/Blocks.vue"
</script>
<Blocks />

View File

@ -0,0 +1,10 @@
---
title: Blocks - shadcn-vue
layout: false
---
<script setup>
import BlockPage from "../../../.vitepress/theme/components/BlockPage.vue"
</script>
<BlockPage />

View File

@ -1,3 +1,139 @@
--- ---
title: Changelog title: Changelog
description: Latest updates and announcements.
--- ---
## June 2024
### New Component - Number Field
A new component has been added to the project [`NumberField`](/docs/components/number-field.html).
A number field allows a user to enter a number and increment or decrement the value using stepper buttons.
<ComponentPreview name="NumberFieldDemo" class="max-w-[180px]" />
## May 2024
### New Component - Charts
Several kinds of chart components has been added to the project.
Charts are versatile visualization tools, allowing users to represent data using various options for effective analysis.
1. [`Area Chart`](/docs/charts/area) - An area chart visually represents data over time, displaying trends and patterns through filled-in areas under a line graph.
<ComponentPreview name="AreaChartDemo" />
2. [`Bar Chart`](/docs/charts/bar) - A line chart visually represents data using rectangular bars of varying lengths to compare quantities across different categories or groups.
<ComponentPreview name="BarChartDemo" />
3. [`Donut Chart`](/docs/charts/donut) - A line chart visually represents data in a circular form, similar to a pie chart but with a central void, emphasizing proportions within categories.
<ComponentPreview name="DonutChartDemo" />
4. [`Line Chart`](/docs/charts/line) - A line chart visually displays data points connected by straight lines, illustrating trends or relationships over a continuous axis.
<ComponentPreview name="LineChartDemo" />
### New Component - Auto Form
[`Auto Form`](/docs/components/auto-form.html) is a drop-in form builder for your internal and low-priority forms with existing zod schemas.
For example, if you already have zod schemas for your API and want to create a simple admin panel to edit user profiles, simply pass the schema to AutoForm and you're done.
The following form has been created by passing a `zod` schema object to our `AutoForm` component.
<ComponentPreview name="AutoFormBasic" />
## April 2024
### Component Updated - Calendar
The [`Calendar`](/docs/components/calendar.html) component has been updated and is now built on top of the [RadixVue Calendar](https://www.radix-vue.com/components/calendar.html) component, which uses the [@internationalized/date](https://react-spectrum.adobe.com/internationalized/date/index.html) package to handle dates.
If you're looking for a range calendar, check out the [`Range Calendar`](/docs/components/range-calendar.html) component.
And if you're looking for a date picker input, check out the [`Date Picker`](/docs/components/date-picker.html) component.
<ComponentPreview name="CalendarDemo" />
<ComponentPreview name="RangeCalendarDemo" />
<ComponentPreview name="DatePickerDemo" />
### Building Blocks for the Web
[`Blocks`](/blocks) are composed of different components that can be used to build your apps, with each block being a standalone section of your application. These blocks are fully responsive, accessible, and composable, and are built using the same principles as the other components in `shadcn-vue`.
<div>
<image
src="/examples/block-dark.png"
:width="1280"
:height="727"
alt="Building Blocks"
class="hidden dark:block"
/>
<image
src="/examples/block-light.png"
:width="1280"
:height="727"
alt="Building Blocks"
class="block dark:hidden"
/>
</div>
## March 2024
### New Component - Breadcrumb
[`Breadcrumb`](/docs/components/breadcrumb.html) displays the path to the current resource using a hierarchy of links.
<ComponentPreview name="BreadcrumbDemo" />
### New Component - Pin Input (OTP Input)
[`Pin Input`](/docs/components/pin-input.html) allows users to input a sequence of one-character alphanumeric inputs.
<ComponentPreview name="PinInputDemo" />
### New Component - Resizable
[`Resizable`](/docs/components/resizable.html) - Accessible resizable panel groups and layouts with keyboard support.
<ComponentPreview name="ResizableDemo" />
### New Component - Drawer
[`Drawer`](/docs/components/drawer.html) - A drawer component for vue that is built on top of [Vaul Vue](https://github.com/radix-vue/vaul-vue).
<ComponentPreview name="DrawerDemo" />
## February 2024
### New Component - Tag Inputs
[`Tag inputs`](/docs/components/tags-input.html) render tags inside an input, followed by an actual text input.
<ComponentPreview name="TagsInputDemo" />
## January 2024
### New Component - Sonner
[`Sonner`](/docs/components/sonner.html) is an opinionated toast component for Vue.
The Sonner component is provided by [vue-sonner](https://vue-sonner.vercel.app/), which is a Vue port of Sonner, originally created by [Emil Kowalski](https://twitter.com/emilkowalski_) for React.
<ComponentPreview name="SonnerDemo" />
### New Component - Toggle Group
[`Toggle Group`](/docs/components/toggle-group.html) - A set of two-state buttons that can be toggled on or off.
<ComponentPreview name="ToggleGroupDemo" />
### New Component - Carousel
[`Carousel`](/docs/components/toggle-group.html) - A carousel with motion and swipe built using [Embla](https://www.embla-carousel.com/) library.
<ComponentPreview name="CarouselDemo" />

View File

@ -0,0 +1,107 @@
---
title: Charts
description: Versatile visualization tool, allowing users to represent data using various types of charts for effective analysis.
label: Alpha
---
<script setup>
import Area from '~icons/gravity-ui/chart-area-stacked'
import Bar from '~icons/gravity-ui/chart-column'
import Line from '~icons/gravity-ui/chart-line'
import Pie from '~icons/gravity-ui/chart-pie'
</script>
<Callout>
Only works with Vue >3.3
</Callout>
`Charts` components were built on top of [Unovis](https://unovis.dev/) (a modular data visualization framework), and inspired by [tremor](https://www.tremor.so).
## Chart type
<div class="grid gap-4 mt-8 sm:grid-cols-2 sm:gap-6 not-docs">
<LinkedCard href="/docs/charts/area">
<Area class="text-foreground/80 w-11 h-11" />
<p class="mt-2 font-medium">Area</p>
</LinkedCard>
<LinkedCard href="/docs/charts/line">
<Line class="text-foreground/80 w-11 h-11" />
<p class="mt-2 font-medium">Line</p>
</LinkedCard>
<LinkedCard href="/docs/charts/bar">
<Bar class="text-foreground/80 w-11 h-11" />
<p class="mt-2 font-medium">Bar</p>
</LinkedCard>
<LinkedCard href="/docs/charts/donut">
<Pie class="text-foreground/80 w-11 h-11" />
<p class="mt-2 font-medium">Donut</p>
</LinkedCard>
</div>
## Installation
<Steps>
### Update `css`
Add the following tooltip styling to your `tailwind.css` file:
```css
@layer base {
:root {
/* ... */
--vis-tooltip-background-color: none !important;
--vis-tooltip-border-color: none !important;
--vis-tooltip-text-color: none !important;
--vis-tooltip-shadow-color: none !important;
--vis-tooltip-backdrop-filter: none !important;
--vis-tooltip-padding: none !important;
--vis-primary-color: var(--primary);
/* change to any hsl value you want */
--vis-secondary-color: 160 81% 40%;
--vis-text-color: var(--muted-foreground);
}
```
If you are not using `css-variables` for your component, you need to update the `--vis-primary-color` and `--vis-text-color` to your desired hsl value.
You may use [this tool](https://redpixelthemes.com/blog/tailwindcss-colors-different-formats/) to help you find the hsl value for your primary color and text color. Be sure to provide `dark` mode styling as well.
</Steps>
## Colors
By default, we construct the primary theme color, and secondary (`--vis-secondary-color`) color with different opacity for the graph.
However, you can always pass in the desired `color` into each chart.
```vue
<template>
<AreaChart
:data="data"
:colors="['blue', 'pink', 'orange', 'red']"
/>
</template>
```
## Custom tooltip
If you want to customize the `Tooltip` for the chart, you can pass `customTooltip` prop with a custom Vue component.
The custom component would receive `title` and `data` props, check out [ChartTooltip.vue component](https://github.com/radix-vue/shadcn-vue/tree/dev/apps/www/src/lib/registry/default/ui/chart/ChartTooltip.vue) for example.
The expected prop definition would be:
```ts
defineProps<{
title?: string
data: {
name: string
color: string
value: any
}[]
}>()
```

View File

@ -0,0 +1,46 @@
---
title: Area
description: An area chart visually represents data over time, displaying trends and patterns through filled-in areas under a line graph.
source: apps/www/src/lib/registry/default/ui/chart-area
label: Alpha
---
<ComponentPreview name="AreaChartDemo" />
## Installation
<Callout>
Only works with Vue >3.3
</Callout>
<Steps>
### Run the following command
```bash
npx shadcn-vue@latest add chart-area
```
### Setup
Follow the [guide](/docs/charts.html#installation) to complete the setup.
</Steps>
## API
<!-- @include: @/content/meta/AreaChart.md -->
## Example
### Sparkline
We can turn the chart into sparkline chart by hiding axis, gridline and legends.
<ComponentPreview name="AreaChartSparkline" />
### Custom Tooltip
If you want to render custom tooltip, you can easily pass in a custom component. Refer to prop definition [here](/docs/charts.html#custom-tooltip).
<ComponentPreview name="AreaChartCustomTooltip" />

View File

@ -0,0 +1,50 @@
---
title: Bar
description: A line chart visually represents data using rectangular bars of varying lengths to compare quantities across different categories or groups.
source: apps/www/src/lib/registry/default/ui/chart-bar
label: Alpha
---
<ComponentPreview name="BarChartDemo" />
## Installation
<Callout>
Only works with Vue >3.3
</Callout>
<Steps>
### Run the following command
```bash
npx shadcn-vue@latest add chart-bar
```
### Setup
Follow the [guide](/docs/charts.html#installation) to complete the setup.
</Steps>
## API
<!-- @include: @/content/meta/BarChart.md -->
## Example
### Stacked
You can stack the bar chart by settings prop `type` to `stacked`.
<ComponentPreview name="BarChartStacked" />
### Rounded
<ComponentPreview name="BarChartRounded" />
### Custom Tooltip
If you want to render custom tooltip, you can easily pass in a custom component. Refer to prop definition [here](/docs/charts.html#custom-tooltip).
<ComponentPreview name="BarChartCustomTooltip" />

View File

@ -0,0 +1,52 @@
---
title: Donut
description: A line chart visually represents data in a circular form, similar to a pie chart but with a central void, emphasizing proportions within categories.
source: apps/www/src/lib/registry/default/ui/chart-donut
label: Alpha
---
<ComponentPreview name="DonutChartDemo" />
## Installation
<Callout>
Only works with Vue >3.3
</Callout>
<Steps>
### Run the following command
```bash
npx shadcn-vue@latest add chart-donut
```
### Setup
Follow the [guide](/docs/charts.html#installation) to complete the setup.
</Steps>
## API
<!-- @include: @/content/meta/DonutChart.md -->
## Example
### Pie Chart
If you want to render pie chart instead, pass `type` as `pie`.
<ComponentPreview name="DonutChartPie" />
### Color
We generate colors automatically based on the primary and secondary color and assigned them accordingly. Feel free to pass in your own array of colors.
<ComponentPreview name="DonutChartColor" />
### Custom Tooltip
If you want to render custom tooltip, you can easily pass in a custom component. Refer to prop definition [here](/docs/charts.html#custom-tooltip).
<ComponentPreview name="DonutChartCustomTooltip" />

View File

@ -0,0 +1,46 @@
---
title: Line
description: A line chart visually displays data points connected by straight lines, illustrating trends or relationships over a continuous axis.
source: apps/www/src/lib/registry/default/ui/chart-line
label: Alpha
---
<ComponentPreview name="LineChartDemo" />
## Installation
<Callout>
Only works with Vue >3.3
</Callout>
<Steps>
### Run the following command
```bash
npx shadcn-vue@latest add chart-line
```
### Setup
Follow the [guide](/docs/charts.html#installation) to complete the setup.
</Steps>
## API
<!-- @include: @/content/meta/LineChart.md -->
## Example
### Sparkline
We can turn the chart into sparkline chart by hiding axis, gridline and legends.
<ComponentPreview name="LineChartSparkline" />
### Custom Tooltip
If you want to render custom tooltip, you can easily pass in a custom component. Refer to prop definition [here](/docs/charts.html#custom-tooltip).
<ComponentPreview name="LineChartCustomTooltip" />

View File

@ -24,7 +24,7 @@ Where is your global CSS file? src/index.css
Do you want to use CSS variables for colors? no / yes Do you want to use CSS variables for colors? no / yes
Where is your tailwind.config.js located? tailwind.config.js Where is your tailwind.config.js located? tailwind.config.js
Configure the import alias for components: @/components Configure the import alias for components: @/components
Configure the import alias for utils: @/lib/utils Configure the import alias for utils: @/lib/utils
``` ```
### Options ### Options
@ -101,4 +101,4 @@ Arguments:
Options: Options:
-c, --cwd <cwd> the working directory. (default: the current directory) -c, --cwd <cwd> the working directory. (default: the current directory)
-h, --help display help for command -h, --help display help for command
``` ```

View File

@ -89,7 +89,6 @@ This is used to generate the default color palette for your components. **This c
} }
``` ```
### tailwind.cssVariables ### tailwind.cssVariables
You can choose between using CSS variables or Tailwind CSS utility classes for theming. You can choose between using CSS variables or Tailwind CSS utility classes for theming.
@ -109,7 +108,6 @@ For more information, see the [theming docs](/docs/theming).
**This cannot be changed after initialization.** To switch between CSS variables and utility classes, you'll have to delete and re-install your components. **This cannot be changed after initialization.** To switch between CSS variables and utility classes, you'll have to delete and re-install your components.
## aliases ## aliases
The CLI uses these values and the `paths` config from your `tsconfig.json` or `jsconfig.json` file to place generated components in the correct location. The CLI uses these values and the `paths` config from your `tsconfig.json` or `jsconfig.json` file to place generated components in the correct location.
@ -117,7 +115,6 @@ The CLI uses these values and the `paths` config from your `tsconfig.json` or `j
Path aliases have to be set up in your `tsconfig.json` or `jsconfig.json` file. Path aliases have to be set up in your `tsconfig.json` or `jsconfig.json` file.
> A fallback to `tsconfig.app.json` if no `paths` were found in `tsconfig.json` > A fallback to `tsconfig.app.json` if no `paths` were found in `tsconfig.json`
<Callout class="mt-6"> <Callout class="mt-6">
@ -126,7 +123,6 @@ Path aliases have to be set up in your `tsconfig.json` or `jsconfig.json` file.
</Callout> </Callout>
### aliases.utils ### aliases.utils
Import alias for your utility functions. Import alias for your utility functions.
@ -150,3 +146,17 @@ Import alias for your components.
} }
} }
``` ```
### aliases.ui
Import alias for `ui` components.
The CLI will use the `aliases.ui` value to determine where to place your `ui` components. Use this config if you want to customize the installation directory for your `ui` components.
```json title="components.json"
{
"aliases": {
"ui": "@/app/ui"
}
}
```

View File

@ -0,0 +1,6 @@
<script setup>
import { useRouter } from 'vitepress'
const router = useRouter()
router.go('/docs/components/accordion')
</script>

View File

@ -1,15 +1,13 @@
--- ---
title: Accordion title: Accordion
description: A vertically stacked set of interactive headings that each reveal a section of content. description: A vertically stacked set of interactive headings that each reveal a section of content.
source: apps/www/src/lib/registry/default/ui/accordion source: apps/www/src/lib/registry/default/ui/accordion
primitive: https://www.radix-vue.com/components/accordion.html primitive: https://www.radix-vue.com/components/accordion.html
--- ---
<ComponentPreview name="AccordionDemo" class="sm:max-w-[70%]" /> <ComponentPreview name="AccordionDemo" class="sm:max-w-[70%]" />
## Installation ## Installation
<Steps> <Steps>
@ -46,9 +44,8 @@ module.exports = {
}, },
} }
``` ```
</Steps> </Steps>
## Usage ## Usage
@ -68,4 +65,3 @@ import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/
</Accordion> </Accordion>
</template> </template>
``` ```

View File

@ -1,21 +1,18 @@
--- ---
title: Alert Dialog title: Alert Dialog
description: A modal dialog that interrupts the user with important content and expects a response. description: A modal dialog that interrupts the user with important content and expects a response.
source: apps/www/src/lib/registry/default/ui/alert-dialog source: apps/www/src/lib/registry/default/ui/alert-dialog
primitive: https://www.radix-vue.com/components/alert-dialog.html primitive: https://www.radix-vue.com/components/alert-dialog.html
--- ---
<ComponentPreview name="AlertDialogDemo" />
## Installation
<ComponentPreview name="AlertDialogDemo" />
## Installation
```bash ```bash
npx shadcn-vue@latest add alert-dialog npx shadcn-vue@latest add alert-dialog
``` ```
## Usage ## Usage
```vue ```vue
@ -51,4 +48,4 @@ import {
</AlertDialogContent> </AlertDialogContent>
</AlertDialog> </AlertDialog>
</template> </template>
``` ```

View File

@ -3,16 +3,14 @@ title: Alert
description: Displays a callout for user attention. description: Displays a callout for user attention.
--- ---
<ComponentPreview name="AlertDemo" />
<ComponentPreview name="AlertDemo" />
## Installation ## Installation
```bash ```bash
npx shadcn-vue@latest add alert npx shadcn-vue@latest add alert
``` ```
## Usage ## Usage
```vue ```vue
@ -34,11 +32,8 @@ import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
### Default ### Default
<ComponentPreview name="AlertDemo" /> <ComponentPreview name="AlertDemo" />
### Destructive ### Destructive
<ComponentPreview name="AlertDestructiveDemo" /> <ComponentPreview name="AlertDestructiveDemo" />

View File

@ -1,11 +1,10 @@
--- ---
title: Aspect Ratio title: Aspect Ratio
description: Displays content within a desired ratio. description: Displays content within a desired ratio.
source: apps/www/src/lib/registry/default/ui/aspect-ratio source: apps/www/src/lib/registry/default/ui/aspect-ratio
primitive: https://www.radix-vue.com/components/aspect-ratio.html primitive: https://www.radix-vue.com/components/aspect-ratio.html
--- ---
<ComponentPreview name="AspectRatioDemo" /> <ComponentPreview name="AspectRatioDemo" />
## Installation ## Installation
@ -51,4 +50,4 @@ import { AspectRatio } from '@/components/ui/aspect-ratio'
</AspectRatio> </AspectRatio>
</div> </div>
</template> </template>
``` ```

View File

@ -0,0 +1,548 @@
---
title: AutoForm
description: Automatically generate a form from Zod schema.
primitive: https://vee-validate.logaretm.com/v4/guide/overview/
---
<Callout class="mt-6">
Credit: Heavily inspired by [AutoForm](https://github.com/vantezzen/auto-form) by Vantezzen
</Callout>
## What is AutoForm
AutoForm is a drop-in form builder for your internal and low-priority forms with existing zod schemas. For example, if you already have zod schemas for your API and want to create a simple admin panel to edit user profiles, simply pass the schema to AutoForm and you're done.
## Installation
<Steps>
### Run the following command
```bash
npx shadcn-vue@latest update form
npx shadcn-vue@latest add auto-form
```
</Steps>
## Field types
Currently, these field types are supported out of the box:
- boolean (checkbox, switch)
- date (date picker)
- enum (select, radio group)
- number (input)
- string (input, textfield)
- file (file)
You can add support for other field types by adding them to the `INPUT_COMPONENTS` object in `auto-form/constants.ts`.
## Zod configuration
### Validations
Your form schema can use any of zod's validation methods including refine.
<Callout>
⚠️ However, there's a known issue with Zods `refine` and `superRefine` not executing whenever some object keys are missing.
[Read more](https://github.com/logaretm/vee-validate/issues/4338)
</Callout>
### Descriptions
You can use the `describe` method to set a label for each field. If no label is set, the field name will be used and un-camel-cased.
```ts
const formSchema = z.object({
username: z.string().describe('Your username'),
someValue: z.string(), // Will be "Some Value"
})
```
You can also configure the label with [`fieldConfig`](#label) too.
### Optional fields
By default, all fields are required. You can make a field optional by using the `optional` method.
```ts
const formSchema = z.object({
username: z.string().optional(),
})
```
### Default values
You can set a default value for a field using the `default` method.
```ts
const formSchema = z.object({
favouriteNumber: z.number().default(5),
})
```
If you want to set default value of date, convert it to Date first using `new Date(val)`.
### Sub-objects
You can nest objects to create accordion sections.
```ts
const formSchema = z.object({
address: z.object({
street: z.string(),
city: z.string(),
zip: z.string(),
// You can nest objects as deep as you want
nested: z.object({
foo: z.string(),
bar: z.string(),
nested: z.object({
foo: z.string(),
bar: z.string(),
}),
}),
}),
})
```
Like with normal objects, you can use the `describe` method to set a label and description for the section:
```ts
const formSchema = z.object({
address: z
.object({
street: z.string(),
city: z.string(),
zip: z.string(),
})
.describe('Your address'),
})
```
### Select/Enums
AutoForm supports `enum` and `nativeEnum` to create select fields.
```ts
const formSchema = z.object({
color: z.enum(['red', 'green', 'blue']),
})
enum BreadTypes {
// For native enums, you can alternatively define a backed enum to set a custom label
White = 'White bread',
Brown = 'Brown bread',
Wholegrain = 'Wholegrain bread',
Other,
}
// Keep in mind that zod will validate and return the enum labels, not the enum values!
const formSchema = z.object({
bread: z.nativeEnum(BreadTypes),
})
```
### Arrays
AutoForm supports arrays _of objects_. Because inferring things like field labels from arrays of strings/numbers/etc. is difficult, only objects are supported.
```ts
const formSchema = z.object({
guestListName: z.string(),
invitedGuests: z
.array(
// Define the fields for each item
z.object({
name: z.string(),
age: z.number(),
})
)
// Optionally set a custom label - otherwise this will be inferred from the field name
.describe('Guests invited to the party'),
})
```
Arrays are not supported as the root element of the form schema.
You also can set default value of an array using .default(), but please make sure the array element has same structure with the schema.
```ts
const formSchema = z.object({
guestListName: z.string(),
invitedGuests: z
.array(
// Define the fields for each item
z.object({
name: z.string(),
age: z.number(),
})
)
.describe('Guests invited to the party')
.default([
{ name: 'John', age: 24, },
{ name: 'Jane', age: 20, },
]),
})
```
## Field configuration
As zod doesn't allow adding other properties to the schema, you can use the `fieldConfig` prop to add additional configuration for the UI of each field.
```vue
<template>
<AutoForm
:field-config="{
username: {
// fieldConfig
},
}"
/>
</template>
```
### Label
You can use the `label` property to customize label if you want to overwrite the pre-defined label via [Zod's description](#descriptions).
```vue
<template>
<AutoForm
:field-config="{
username: {
label: 'Custom username',
},
}"
/>
</template>
```
### Description
You can use the `description` property to add a description below the field.
```vue
<template>
<AutoForm
:field-config="{
username: {
description: 'Enter a unique username. This will be shown to other users.',
},
}"
/>
</template>
```
### Input props
You can use the `inputProps` property to pass props to the input component. You can use any props that the HTML component accepts.
```vue
<template>
<AutoForm
:field-config="{
username: {
inputProps: {
type: 'text',
placeholder: 'Username',
},
},
}"
/>
</template>
// This will be rendered as:
<input type="text" placeholder="Username" />
```
Disabling the label of an input can be done by using the `showLabel` property in `inputProps`.
```vue
<template>
<AutoForm
:field-config="{
username: {
inputProps: {
type: 'text',
placeholder: 'Username',
showLabel: false,
},
},
}"
/>
</template>
```
### Component
By default, AutoForm will use the Zod type to determine which input component to use. You can override this by using the `component` property.
```vue
<template>
<AutoForm
:field-config="{
acceptTerms: {
// Booleans use a checkbox by default, use a switch instead
component: 'switch',
},
}"
/>
</template>
```
The complete list of supported field types is typed. Current supported types are:
- `checkbox` (default for booleans)
- `switch`
- `date` (default for dates)
- `select` (default for enums)
- `radio`
- `textarea`
Alternatively, you can pass a Vue component to the `component` property to use a custom component.
In `CustomField.vue`
```vue
<script setup lang="ts">
import { computed } from 'vue'
import AutoFormLabel from './AutoFormLabel.vue'
import type { FieldProps } from './interface'
import { FormControl, FormDescription, FormField, FormItem, FormMessage } from '@/ui/form'
import { Input } from '@/ui/input'
import { AutoFormLabel } from '@/ui/auto-form'
const props = defineProps<FieldProps>()
</script>
<template>
<FormField v-slot="slotProps" :name="fieldName">
<FormItem v-bind="$attrs">
<AutoFormLabel v-if="!config?.hideLabel" :required="required">
{{ config?.label }}
</AutoFormLabel>
<FormControl>
<CustomInput v-bind="slotProps" />
</FormControl>
<FormDescription v-if="config?.description">
{{ config.description }}
</FormDescription>
<FormMessage />
</FormItem>
</FormField>
</template>
```
Pass the above component in `fieldConfig`.
```vue
<template>
<AutoForm
:field-config="{
username: {
component: CustomField,
},
}"
/>
</template>
```
### Named slot
You can use Vue named slot to customize the rendered `AutoFormField`.
```vue
<template>
<AutoForm
:field-config="{
customParent: {
label: 'Wrapper',
},
}"
>
<template #customParent="slotProps">
<div class="flex items-end space-x-2">
<AutoFormField v-bind="slotProps" class="w-full" />
<Button type="button">
Check
</Button>
</div>
</template>
</AutoForm>
</template>
```
### Accessing the form data
There are two ways to access the form data:
### @submit
The preferred way is to use the `submit` emit. This will be called when the form is submitted and the data is valid.
```vue
<template>
<AutoForm
@submit="(data) => {
// Do something with the data
}"
/>
</template>
```
### Controlled form
By passing the `form` as props, you can control and use the method provided by `Form`.
```vue
<script setup lang="ts">
import * as z from 'zod'
import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/zod'
const schema = z.object({
username: z.string(),
})
const form = useForm({
validationSchema: toTypedSchema(schema),
})
form.setFieldValue('username', 'bar')
</script>
<template>
<AutoForm :form="form" :schema="schema" />
</template>
```
### Submitting the form
You can use any `button` component to create a submit button. Most importantly is to add attributes `type="submit"`.
```vue
<template>
<AutoForm>
<CustomButton type="submit">
Send now
</CustomButton>
</AutoForm>
// or
<AutoForm>
<button type="submit">
Send now
</button>
</AutoForm>
</template>
```
### Adding other elements
All children passed to the `AutoForm` component will be rendered below the form.
```vue
<template>
<AutoForm>
<Button>Send now</Button>
<p class="text-gray-500 text-sm">
By submitting this form, you agree to our
<a href="#" class="text-primary underline">
terms and conditions
</a>.
</p>
</AutoForm>
</template>
```
### Dependencies
AutoForm allows you to add dependencies between fields to control fields based on the value of other fields. For this, a `dependencies` array can be passed to the `AutoForm` component.
```vue
<template>
<AutoForm
:dependencies="[
{
// 'age' hides 'parentsAllowed' when the age is 18 or older
sourceField: 'age',
type: DependencyType.HIDES,
targetField: 'parentsAllowed',
when: age => age >= 18,
},
{
// 'vegetarian' checkbox hides the 'Beef Wellington' option from 'mealOptions'
// if its not already selected
sourceField: 'vegetarian',
type: DependencyType.SETS_OPTIONS,
targetField: 'mealOptions',
when: (vegetarian, mealOption) =>
vegetarian && mealOption !== 'Beef Wellington',
options: ['Pasta', 'Salad'],
},
]"
/>
</template>
```
The following dependency types are supported:
- `DependencyType.HIDES`: Hides the target field when the `when` function returns true
- `DependencyType.DISABLES`: Disables the target field when the `when` function returns true
- `DependencyType.REQUIRES`: Sets the target field to required when the `when` function returns true
- `DependencyType.SETS_OPTIONS`: Sets the options of the target field to the `options` array when the `when` function returns true
The `when` function is called with the value of the source field and the value of the target field and should return a boolean to indicate if the dependency should be applied.
Please note that dependencies will not cause the inverse action when returning `false` - for example, if you mark a field as required in your zod schema (i.e. by not explicitly setting `optional`), returning `false` in your `REQURIES` dependency will not mark it as optional. You should instead use zod's `optional` method to mark as optional by default and use the `REQURIES` dependency to mark it as required when the dependency is met.
Please note that dependencies do not have any effect on the validation of the form. You should use zod's `refine` method to validate the form based on the value of other fields.
You can create multiple dependencies for the same field and dependency type - for example to hide a field based on multiple other fields. This will then hide the field when any of the dependencies are met.
## Example
### Basic
<ComponentPreview name="AutoFormBasic" />
### Input Without Label
This example shows how to use AutoForm input without label.
<ComponentPreview name="AutoFormInputWithoutLabel" />
### Sub Object
Automatically generate a form from a Zod schema.
<ComponentPreview name="AutoFormSubObject" />
### Controlled
This example shows how to use AutoForm in a controlled way.
<ComponentPreview name="AutoFormControlled" />
### Confirm Password
Refined schema to validate that two fields match.
<ComponentPreview name="AutoFormConfirmPassword" />
### API Example
The form select options are fetched from an API.
<ComponentPreview name="AutoFormApi" />
### Array support
You can use arrays in your schemas to create dynamic forms.
<ComponentPreview name="AutoFormArray" />
### Dependencies
Create dependencies between fields.
<ComponentPreview name="AutoFormDependencies" />

View File

@ -1,20 +1,17 @@
--- ---
title: Avatar title: Avatar
description: An image element with a fallback for representing the user. description: An image element with a fallback for representing the user.
source: apps/www/src/lib/registry/default/ui/avatar source: apps/www/src/lib/registry/default/ui/avatar
primitive: https://www.radix-vue.com/components/avatar.html primitive: https://www.radix-vue.com/components/avatar.html
--- ---
<ComponentPreview name="AvatarDemo" />
<ComponentPreview name="AvatarDemo" />
## Installation ## Installation
```bash ```bash
npx shadcn-vue@latest add avatar npx shadcn-vue@latest add avatar
``` ```
## Usage ## Usage
@ -29,4 +26,4 @@ import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
<AvatarFallback>CN</AvatarFallback> <AvatarFallback>CN</AvatarFallback>
</Avatar> </Avatar>
</template> </template>
``` ```

View File

@ -3,8 +3,7 @@ title: Badge
description: Displays a badge or a component that looks like a badge. description: Displays a badge or a component that looks like a badge.
--- ---
<ComponentPreview name="BadgeDemo" />
<ComponentPreview name="BadgeDemo" />
## Installation ## Installation
@ -80,13 +79,11 @@ import { Badge } from '@/components/ui/badge'
</template> </template>
``` ```
## Examples ## Examples
### Default ### Default
<ComponentPreview name="BadgeDemo" /> <ComponentPreview name="BadgeDemo" />
### Secondary ### Secondary
@ -98,4 +95,4 @@ import { Badge } from '@/components/ui/badge'
### Destructive ### Destructive
<ComponentPreview name="BadgeDestructiveDemo" /> <ComponentPreview name="BadgeDestructiveDemo" />

View File

@ -0,0 +1,205 @@
---
title: Breadcrumb
description: Displays the path to the current resource using a hierarchy of links.
---
<ComponentPreview name="BreadcrumbDemo" class="[&_.preview]:p-2" />
## Installation
```bash
npx shadcn-vue@latest add breadcrumb
```
## Usage
```vue
<script setup lang="ts">
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from '@/components/ui/breadcrumb'
</script>
<template>
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink href="/">
Home
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink href="/components">
Components
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>Breadcrumb</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
</template>
```
## Examples
### Custom separator
Use a custom component as `slot` for `<BreadcrumbSeparator />` to create a custom separator.
<ComponentPreview name="BreadcrumbSeparatorDemo" />
```vue showLineNumbers {2,20-22}
<script setup lang="ts">
import { Slash } from 'lucide-react'
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbSeparator,
} from '@/components/ui/breadcrumb'
</script>
<template>
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink href="/">
Home
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator>
<Slash />
</BreadcrumbSeparator>
<BreadcrumbItem>
<BreadcrumbLink href="/components">
Components
</BreadcrumbLink>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
</template>
```
---
### Dropdown
You can compose `<BreadcrumbItem />` with a `<DropdownMenu />` to create a dropdown in the breadcrumb.
<ComponentPreview name="BreadcrumbDropdown" class="[&_.preview]:p-2" />
```vue showLineNumbers {2-7,16-26}
<script setup lang="ts">
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/lib/components/ui/dropdown-menu'
import { BreadcrumbItem } from '@/components/ui/breadcrumb'
import ChevronDownIcon from '~icons/radix-icons/chevron-down'
</script>
<template>
<BreadcrumbItem>
<DropdownMenu>
<DropdownMenuTrigger class="flex items-center gap-1">
Components
<ChevronDownIcon />
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem>Documentation</DropdownMenuItem>
<DropdownMenuItem>Themes</DropdownMenuItem>
<DropdownMenuItem>GitHub</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</BreadcrumbItem>
</template>
```
---
### Collapsed
We provide a `<BreadcrumbEllipsis />` component to show a collapsed state when the breadcrumb is too long.
<ComponentPreview name="BreadcrumbEllipsisDemo" class="[&_.preview]:p-2" />
```vue showLineNumbers {3,15}
<script setup lang="ts">
import {
Breadcrumb,
BreadcrumbEllipsis,
BreadcrumbItem,
BreadcrumbList,
} from '@/components/ui/breadcrumb'
</script>
<template>
<Breadcrumb>
<BreadcrumbList>
<!-- ... -->
<BreadcrumbItem>
<BreadcrumbEllipsis />
</BreadcrumbItem>
<!-- ... -->
</BreadcrumbList>
</Breadcrumb>
</template>
```
---
### Link component
To use a custom link component from your routing library, you can use the `asChild` prop on `<BreadcrumbLink />`.
<ComponentPreview name="BreadcrumbLinkDemo" />
```vue showLineNumbers {15-19}
<script setup lang="ts">
import { RouterLink } from 'vue-router'
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
} from '@/components/ui/breadcrumb'
</script>
<template>
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink as-child>
<RouterLink to="/">
Home
</RouterLink>
</BreadcrumbLink>
</BreadcrumbItem>
<!-- -->
</BreadcrumbList>
</Breadcrumb>
</template>
```
---
### Responsive
Here's an example of a responsive breadcrumb that composes `<BreadcrumbItem />` with `<BreadcrumbEllipsis />`, `<DropdownMenu />`, and `<Drawer />`.
It displays a dropdown on desktop and a drawer on mobile.
<ComponentPreview name="BreadcrumbResponsive" class="[&_.preview]:p-2" />

View File

@ -3,8 +3,7 @@ title: Button
description: Displays a button or a component that looks like a button. description: Displays a button or a component that looks like a button.
--- ---
<ComponentPreview name="ButtonDemo" />
<ComponentPreview name="ButtonDemo" />
## Installation ## Installation
@ -94,24 +93,20 @@ import { Button } from '@/components/ui/button'
</template> </template>
``` ```
## Examples ## Examples
### Primary ### Primary
<ComponentPreview name="ButtonDemo" /> <ComponentPreview name="ButtonDemo" />
### Secondary ### Secondary
<ComponentPreview name="ButtonSecondaryDemo" /> <ComponentPreview name="ButtonSecondaryDemo" />
### Destructive ### Destructive
<ComponentPreview name="ButtonDestructiveDemo" /> <ComponentPreview name="ButtonDestructiveDemo" />
### Outline ### Outline
<ComponentPreview name="ButtonOutlineDemo" /> <ComponentPreview name="ButtonOutlineDemo" />
@ -138,4 +133,4 @@ import { Button } from '@/components/ui/button'
### As Child ### As Child
<ComponentPreview name="ButtonAsChildDemo" /> <ComponentPreview name="ButtonAsChildDemo" />

View File

@ -1,95 +1,42 @@
--- ---
title: Calendar title: Calendar
description: A date field component that allows users to enter and edit date. description: A date field component that allows users to enter and edit date.
source: apps/www/src/lib/registry/default/ui/calendar source: apps/www/src/lib/registry/default/ui/calendar
primitive: https://vcalendar.io/ primitive: https://www.radix-vue.com/components/calendar.html
--- ---
<ComponentPreview name="CalendarDemo" />
<ComponentPreview name="CalendarDemo" /> <Callout class="text-base mt-12">
If you're looking for **previous** Calendar implementation, checkout to <span class="font-bold underline">[VCalendar](/docs/components/v-calendar)</span> component
</Callout>
## About ## About
The `Calendar` component is built on top of [VCalendar](https://vcalendar.io/getting-started/installation.html). The `<Calendar />` component is built on top of the [RadixVue Calendar](https://www.radix-vue.com/components/calendar.html) component, which uses the [@internationalized/date](https://react-spectrum.adobe.com/internationalized/date/index.html) package to handle dates.
If you're looking for a range calendar, check out the [Range Calendar](/docs/components/range-calendar) component.
## Installation ## Installation
<TabPreview name="CLI">
<template #CLI>
```bash ```bash
npx shadcn-vue@latest add calendar npx shadcn-vue@latest add calendar
``` ```
</template>
<template #Manual> ## Datepicker
<Steps> You can use the `<Calendar />` component to build a date picker. See the [Date Picker](/docs/components/date-picker) page for more information.
### Install the following dependency ## Examples
```bash ### Form
npm install v-calendar
```
### Copy and paste the following code into your project <ComponentPreview name="CalendarForm" />
## Advanced Customization
<<< @/lib/registry/default/ui/calendar/Calendar.vue ### Month & Year Selects
<ComponentPreview name="CalendarWithSelect" />
</Steps>
</template>
</TabPreview>
## Usage
```vue
<script setup lang="ts">
import { Calendar } from '@/components/ui/calendar'
</script>
<template>
<Calendar />
</template>
```
The API is essentially the same, i.e. props and slots. See the [VCalendar](https://vcalendar.io/getting-started/installation.html) documentation for more information.
### Slots
The slots available are [those currently supported](https://github.com/nathanreyes/v-calendar/blob/v3.1.2/src/components/Calendar/CalendarSlot.vue#L16-L28) by VCalendar, namely :
- `day-content`
- `day-popover`
- `dp-footer`
- `footer`
- `header-title-wrapper`
- `header-title`
- `header-prev-button`
- `header-next-button`
- `nav`
- `nav-prev-button`
- `nav-next-button`
- `page`
- `time-header`
Example using the `day-content` slot:
```vue
<script setup lang="ts">
import { Calendar } from '@/components/ui/calendar'
</script>
<template>
<Calendar>
<template #day-content="{ day, dayProps, dayEvents }">
<div v-bind="dayProps" v-on="dayEvents">
{{ day.label }}
</div>
</template>
</Calendar>
</template>
```

View File

@ -3,16 +3,13 @@ title: Card
description: Displays a card with header, content, and footer. description: Displays a card with header, content, and footer.
--- ---
<ComponentPreview name="CardFormDemo" />
<ComponentPreview name="CardFormDemo" />
## Installation ## Installation
```bash ```bash
npx shadcn-vue@latest add card npx shadcn-vue@latest add card
``` ```
## Usage ## Usage
@ -46,4 +43,4 @@ import {
## Examples ## Examples
<ComponentPreview name="CardDemo" /> <ComponentPreview name="CardDemo" />

View File

@ -1,24 +1,21 @@
--- ---
title: Carousel title: Carousel
description: A carousel with motion and swipe built using Embla. description: A carousel with motion and swipe built using Embla.
source: apps/www/src/lib/registry/default/ui/carousel source: apps/www/src/lib/registry/default/ui/carousel
primitive: https://www.embla-carousel.com/api primitive: https://www.embla-carousel.com/api
--- ---
<ComponentPreview name="CarouselDemo" />
<ComponentPreview name="CarouselDemo" />
## About ## About
The carousel component is built using the [Embla Carousel](https://www.embla-carousel.com/) library. The carousel component is built using the [Embla Carousel](https://www.embla-carousel.com/) library.
## Installation ## Installation
```bash ```bash
npx shadcn-vue@latest add carousel npx shadcn-vue@latest add carousel
``` ```
## Usage ## Usage
@ -54,7 +51,6 @@ To set the size of the items, you can use the `basis` utility class on the `<Car
<ComponentPreview name="CarouselSize" /> <ComponentPreview name="CarouselSize" />
Example Example
```vue:line-numbers title="Example" {4-6} ```vue:line-numbers title="Example" {4-6}
@ -68,7 +64,6 @@ Example
</Carousel> </Carousel>
``` ```
Responsive Responsive
```vue:line-numbers title="Responsive" {4-6} ```vue:line-numbers title="Responsive" {4-6}
@ -151,6 +146,10 @@ Use the `orientation` prop to set the orientation of the carousel.
</Carousel> </Carousel>
``` ```
### Thumbnails
<ComponentPreview name="CarouselThumbnails" />
## Options ## Options
You can pass options to the carousel using the `opts` prop. See the [Embla Carousel docs](https://www.embla-carousel.com/api/options/) for more information. You can pass options to the carousel using the `opts` prop. See the [Embla Carousel docs](https://www.embla-carousel.com/api/options/) for more information.
@ -259,7 +258,6 @@ You can use the `plugins` prop to add plugins to the carousel.
npm i embla-carousel-autoplay npm i embla-carousel-autoplay
``` ```
```vue:line-numbers {2,8-10} ```vue:line-numbers {2,8-10}
<script setup lang="ts"> <script setup lang="ts">
import Autoplay from 'embla-carousel-autoplay' import Autoplay from 'embla-carousel-autoplay'

View File

@ -1,20 +1,17 @@
--- ---
title: Checkbox title: Checkbox
description: A control that allows the user to toggle between checked and not checked. description: A control that allows the user to toggle between checked and not checked.
source: apps/www/src/lib/registry/default/ui/checkbox source: apps/www/src/lib/registry/default/ui/checkbox
primitive: https://www.radix-vue.com/components/checkbox.html primitive: https://www.radix-vue.com/components/checkbox.html
--- ---
<ComponentPreview name="CheckboxDemo" />
<ComponentPreview name="CheckboxDemo" />
## Installation ## Installation
```bash ```bash
npx shadcn-vue@latest add checkbox npx shadcn-vue@latest add checkbox
``` ```
## Usage ## Usage

View File

@ -1,23 +1,21 @@
--- ---
title: Collapsible title: Collapsible
description: An interactive component which expands/collapses a panel. description: An interactive component which expands/collapses a panel.
source: apps/www/src/lib/registry/default/ui/collapsible source: apps/www/src/lib/registry/default/ui/collapsible
primitive: https://www.radix-vue.com/components/collapsible.html primitive: https://www.radix-vue.com/components/collapsible.html
--- ---
<ComponentPreview name="CollapsibleDemo" />
<ComponentPreview name="CollapsibleDemo" />
## Installation ## Installation
<Steps> <Steps>
### Run the following command ### Run the following command
```bash ```bash
npx shadcn-vue@latest add collapsible npx shadcn-vue@latest add collapsible
``` ```
### Update `tailwind.config.js` ### Update `tailwind.config.js`
@ -46,9 +44,8 @@ module.exports = {
}, },
} }
``` ```
</Steps>
</Steps>
## Usage ## Usage
@ -73,4 +70,4 @@ const isOpen = ref(false)
</CollapsibleContent> </CollapsibleContent>
</Collapsible> </Collapsible>
</template> </template>
``` ```

View File

@ -1,9 +1,9 @@
--- ---
title: Combobox title: Combobox
description: Autocomplete input and command palette with a list of suggestions. description: Autocomplete input and command palette with a list of suggestions.
--- ---
<ComponentPreview name="ComboboxDemo" /> <ComponentPreview name="ComboboxDemo" />
<br> <br>
<Callout title="Note" class="bg-destructive"> <Callout title="Note" class="bg-destructive">
@ -11,14 +11,13 @@ description: Autocomplete input and command palette with a list of suggestions.
[Radix Vue](https://github.com/radix-vue/radix-vue/releases/tag/v1.2.0) introduced a breaking change. You will need to wrap `ComboboxGroup` and `ComboboxItem` inside of `ComboboxList` now. [Radix Vue](https://github.com/radix-vue/radix-vue/releases/tag/v1.2.0) introduced a breaking change. You will need to wrap `ComboboxGroup` and `ComboboxItem` inside of `ComboboxList` now.
</Callout> </Callout>
## Installation ## Installation
The Combobox is built using a composition of the `<Popover />` and the `<Command />` components. The Combobox is built using a composition of the `<Popover />` and the `<Command />` components.
See installation instructions for the [Popover](/docs/components/popover#installation) and the [Command](/docs/components/command#installation) components. See installation instructions for the [Popover](/docs/components/popover#installation) and the [Command](/docs/components/command#installation) components.
## Usage ## Usage
```vue ```vue
@ -45,13 +44,13 @@ import {
const frameworks = [ const frameworks = [
{ value: 'next.js', label: 'Next.js' }, { value: 'next.js', label: 'Next.js' },
{ value: 'sveltekit', label: 'SvelteKit' }, { value: 'sveltekit', label: 'SvelteKit' },
{ value: 'nuxt.js', label: 'Nuxt.js' }, { value: 'nuxt', label: 'Nuxt' },
{ value: 'remix', label: 'Remix' }, { value: 'remix', label: 'Remix' },
{ value: 'astro', label: 'Astro' }, { value: 'astro', label: 'Astro' },
] ]
const open = ref(false) const open = ref(false)
const value = ref({}) const value = ref('')
</script> </script>
<template> <template>
@ -110,6 +109,12 @@ const value = ref({})
<ComponentPreview name="ComboboxDropdownMenu" /> <ComponentPreview name="ComboboxDropdownMenu" />
### Responsive
You can create a responsive combobox by using the `<Popover />` on desktop and the `<Drawer />` components on mobile.
<ComponentPreview name="ComboboxResponsive" />
### Form ### Form
<ComponentPreview name="ComboboxForm" /> <ComponentPreview name="ComboboxForm" />

View File

@ -1,21 +1,17 @@
--- ---
title: Command title: Command
description: Fast, composable, unstyled command menu. description: Fast, composable, unstyled command menu.
source: apps/www/src/lib/registry/default/ui/command source: apps/www/src/lib/registry/default/ui/command
primitive: https://www.radix-vue.com/components/combobox.html primitive: https://www.radix-vue.com/components/combobox.html
--- ---
<ComponentPreview name="CommandDemo" />
<ComponentPreview name="CommandDemo" />
## Installation ## Installation
```bash ```bash
npx shadcn-vue@latest add command npx shadcn-vue@latest add command
``` ```
## Usage ## Usage
```vue ```vue
@ -66,9 +62,9 @@ import {
</template> </template>
``` ```
## Examples ## Examples
### Dialog ### Dialog
<ComponentPreview name="CommandDialogDemo" /> <ComponentPreview name="CommandDialogDemo" />
@ -105,7 +101,7 @@ watch(CmdJ, (v) => {
<span class="text-xs"></span>J <span class="text-xs"></span>J
</kbd> </kbd>
</p> </p>
<CommandDialog :open="open" :on-open-change="handleOpenChange"> <CommandDialog :open="open" @update:open="handleOpenChange">
<CommandInput placeholder="Type a command or search..." /> <CommandInput placeholder="Type a command or search..." />
<CommandList> <CommandList>
<CommandEmpty>No results found.</CommandEmpty> <CommandEmpty>No results found.</CommandEmpty>
@ -140,4 +136,4 @@ watch(CmdJ, (v) => {
### Combobox ### Combobox
You can use the `<Command />` component as a combobox. See the [Combobox](/docs/components/combobox) page for more information. You can use the `<Command />` component as a combobox. See the [Combobox](/docs/components/combobox) page for more information.

View File

@ -1,18 +1,17 @@
--- ---
title: Context Menu title: Context Menu
description: Displays a menu to the user — such as a set of actions or functions — triggered by a button. description: Displays a menu to the user — such as a set of actions or functions — triggered by a button.
source: apps/www/src/lib/registry/default/ui/context-menu source: apps/www/src/lib/registry/default/ui/context-menu
primitive: https://www.radix-vue.com/components/context-menu.html primitive: https://www.radix-vue.com/components/context-menu.html
--- ---
<ComponentPreview name="ContextMenuDemo" />
<ComponentPreview name="ContextMenuDemo" />
## Installation ## Installation
```bash ```bash
npx shadcn-vue@latest add context-menu npx shadcn-vue@latest add context-menu
``` ```
## Usage ## Usage
@ -46,4 +45,4 @@ import {
</ContextMenuContent> </ContextMenuContent>
</ContextMenu> </ContextMenu>
</template> </template>
``` ```

View File

@ -4,7 +4,6 @@ description: Powerful table and datagrids built using TanStack Table.
primitive: https://tanstack.com/table/v8/docs/guide/introduction primitive: https://tanstack.com/table/v8/docs/guide/introduction
--- ---
<ComponentPreview name="DataTableDemo" /> <ComponentPreview name="DataTableDemo" />
## Introduction ## Introduction
@ -56,7 +55,6 @@ npm install @tanstack/vue-table
<ComponentPreview name="DataTableColumnPinningDemo" /> <ComponentPreview name="DataTableColumnPinningDemo" />
## Prerequisites ## Prerequisites
We are going to build a table to show recent payments. Here's what our data looks like: We are going to build a table to show recent payments. Here's what our data looks like:
@ -99,7 +97,7 @@ Start by creating the following file structure:
└── app.vue └── app.vue
``` ```
I'm using a Nuxt.js example here but this works for any other Vue framework. I'm using a Nuxt example here but this works for any other Vue framework.
- `columns.ts` It will contain our column definitions. - `columns.ts` It will contain our column definitions.
- `data-table.vue` It will contain our `<DataTable />` component. - `data-table.vue` It will contain our `<DataTable />` component.
@ -116,7 +114,7 @@ Let's start by building a basic table.
First, we'll define our columns in the `columns.ts` file. First, we'll define our columns in the `columns.ts` file.
```ts:line-numbers {1,12-27} ```ts:line-numbers
import { h } from 'vue' import { h } from 'vue'
export const columns: ColumnDef<Payment>[] = [ export const columns: ColumnDef<Payment>[] = [
@ -148,66 +146,70 @@ formatted, sorted and filtered.
Next, we'll create a `<DataTable />` component to render our table. Next, we'll create a `<DataTable />` component to render our table.
```vue:line-numbers ```vue
<script setup lang="ts" generic="TData, TValue"> <script setup lang="ts" generic="TData, TValue">
import type { ColumnDef } from '@tanstack/vue-table' import type { ColumnDef } from '@tanstack/vue-table'
import { import {
FlexRender, FlexRender,
getCoreRowModel, getCoreRowModel,
useVueTable, useVueTable,
} from "@tanstack/vue-table" } from '@tanstack/vue-table'
import { import {
Table, Table,
TableBody, TableBody,
TableCell, TableCell,
TableHead, TableHead,
TableHeader, TableHeader,
TableRow, TableRow,
} from "@/components/ui/table" } from '@/components/ui/table'
const props = defineProps<{ const props = defineProps<{
columns: ColumnDef<TData, TValue>[] columns: ColumnDef<TData, TValue>[]
data: TData[] data: TData[]
}>() }>()
const table = useVueTable({ const table = useVueTable({
get data() { return props.data }, get data() { return props.data },
get columns() { return props.columns }, get columns() { return props.columns },
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
}) })
</script> </script>
<template> <template>
<div class="border rounded-md"> <div class="border rounded-md">
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow v-for="headerGroup in table.getHeaderGroups()" :key="headerGroup.id"> <TableRow v-for="headerGroup in table.getHeaderGroups()" :key="headerGroup.id">
<TableHead v-for="header in headerGroup.headers" :key="header.id"> <TableHead v-for="header in headerGroup.headers" :key="header.id">
<FlexRender v-if="!header.isPlaceholder" :render="header.column.columnDef.header" <FlexRender
:props="header.getContext()" /> v-if="!header.isPlaceholder" :render="header.column.columnDef.header"
</TableHead> :props="header.getContext()"
</TableRow> />
</TableHeader> </TableHead>
<TableBody> </TableRow>
<template v-if="table.getRowModel().rows?.length"> </TableHeader>
<TableRow v-for="row in table.getRowModel().rows" :key="row.id" <TableBody>
:data-state="row.getIsSelected() ? 'selected' : undefined"> <template v-if="table.getRowModel().rows?.length">
<TableCell v-for="cell in row.getVisibleCells()" :key="cell.id"> <TableRow
<FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" /> v-for="row in table.getRowModel().rows" :key="row.id"
</TableCell> :data-state="row.getIsSelected() ? 'selected' : undefined"
</TableRow> >
</template> <TableCell v-for="cell in row.getVisibleCells()" :key="cell.id">
<template v-else> <FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
<TableRow> </TableCell>
<TableCell :colSpan="columns.length" class="h-24 text-center"> </TableRow>
No results. </template>
</TableCell> <template v-else>
</TableRow> <TableRow>
</template> <TableCell :colspan="columns.length" class="h-24 text-center">
</TableBody> No results.
</Table> </TableCell>
</div> </TableRow>
</template>
</TableBody>
</Table>
</div>
</template> </template>
``` ```
@ -219,17 +221,16 @@ const table = useVueTable({
</Callout> </Callout>
### Render the table ### Render the table
Finally, we'll render our table in our index component. Finally, we'll render our table in our index component.
```vue:line-numbers {28} ```vue
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue' import { onMounted, ref } from 'vue'
import { columns } from "./components/columns" import { columns } from './components/columns'
import type { Payment } from './components/columns'; import type { Payment } from './components/columns'
import DataTable from "./components/DataTable.vue" import DataTable from './components/DataTable.vue'
const data = ref<Payment[]>([]) const data = ref<Payment[]>([])
@ -237,18 +238,18 @@ async function getData(): Promise<Payment[]> {
// Fetch data from your API here. // Fetch data from your API here.
return [ return [
{ {
id: "728ed52f", id: '728ed52f',
amount: 100, amount: 100,
status: "pending", status: 'pending',
email: "m@example.com", email: 'm@example.com',
}, },
// ... // ...
] ]
} }
onMounted(async () => { onMounted(async () => {
data.value = await getData(); data.value = await getData()
}); })
</script> </script>
<template> <template>
@ -270,24 +271,23 @@ Let's format the amount cell to display the dollar amount. We'll also align the
Update the `header` and `cell` definitions for amount as follows: Update the `header` and `cell` definitions for amount as follows:
```ts
```ts:line-numbers title="components/payments/columns.ts" {5-17}
import { h } from 'vue' import { h } from 'vue'
export const columns: ColumnDef<Payment>[] = [ export const columns: ColumnDef<Payment>[] = [
{ {
accessorKey: "amount", accessorKey: 'amount',
header: () => h('div', { class: 'text-right' }, 'Amount'), header: () => h('div', { class: 'text-right' }, 'Amount'),
cell: ({ row }) => { cell: ({ row }) => {
const amount = parseFloat(row.getValue("amount")) const amount = Number.parseFloat(row.getValue('amount'))
const formatted = new Intl.NumberFormat("en-US", { const formatted = new Intl.NumberFormat('en-US', {
style: "currency", style: 'currency',
currency: "USD", currency: 'USD',
}).format(amount) }).format(amount)
return h('div', { class: 'text-right font-medium' }, formatted) return h('div', { class: 'text-right font-medium' }, formatted)
}, },
} }
] ]
``` ```
You can use the same approach to format other cells and headers. You can use the same approach to format other cells and headers.
@ -299,10 +299,9 @@ Let's add row actions to our table. We'll use a `<Dropdown />` component for thi
<Steps> <Steps>
### Add the following into your `DataTableDropDown.vue` component: ### Add the following into your `DataTableDropDown.vue` component
```vue:line-numbers ```vue
// DataTableDropDown.vue
<script setup lang="ts"> <script setup lang="ts">
import { MoreHorizontal } from 'lucide-vue-next' import { MoreHorizontal } from 'lucide-vue-next'
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu' import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'
@ -338,33 +337,30 @@ function copy(id: string) {
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
</template> </template>
``` ```
### Update columns definition ### Update columns definition
Update our columns definition to add a new `actions` column. The `actions` cell returns a `<Dropdown />` component. Update our columns definition to add a new `actions` column. The `actions` cell returns a `<Dropdown />` component.
```ts
```ts:line-numbers showLineNumber{2,6-16} import { ColumnDef } from '@tanstack/vue-table'
import { ColumnDef } from "@tanstack/vue-table"
import DropdownAction from '@/components/DataTableDropDown.vue' import DropdownAction from '@/components/DataTableDropDown.vue'
export const columns: ColumnDef<Payment>[] = [ export const columns: ColumnDef<Payment>[] = [
// ... // ...
{ {
id: 'actions', id: 'actions',
enableHiding: false, enableHiding: false,
cell: ({ row }) => { cell: ({ row }) => {
const payment = row.original const payment = row.original
return h('div', { class: 'relative' }, h(DropdownAction, { return h('div', { class: 'relative' }, h(DropdownAction, {
payment, payment,
})) }))
},
}, },
},
] ]
``` ```
You can access the row data using `row.original` in the `cell` function. Use this to handle actions for your row eg. use the `id` to make a DELETE call to your API. You can access the row data using `row.original` in the `cell` function. Use this to handle actions for your row eg. use the `id` to make a DELETE call to your API.
@ -401,47 +397,45 @@ This will automatically paginate your rows into pages of 10. See the [pagination
We can add pagination controls to our table using the `<Button />` component and the `table.previousPage()`, `table.nextPage()` API methods. We can add pagination controls to our table using the `<Button />` component and the `table.previousPage()`, `table.nextPage()` API methods.
```vue:line-numbers {3,15,21-39} ```vue
<script lang="ts" generic="TData, TValue"> <script lang="ts" generic="TData, TValue">
import { Button } from "@/components/ui/button" import { Button } from '@/components/ui/button'
const table = useVueTable({ const table = useVueTable({
get data() { return props.data }, get data() { return props.data },
get columns() { return props.columns }, get columns() { return props.columns },
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(), getPaginationRowModel: getPaginationRowModel(),
}) })
</script> </script>
<template> <template>
<div> <div>
<div class="border rounded-md"> <div class="border rounded-md">
<Table> <Table>
{ // .... } { // .... }
</Table> </Table>
</div>
<div class="flex items-center justify-end py-4 space-x-2">
<Button
variant="outline"
size="sm"
:disabled="!table.getCanPreviousPage()"
@click="table.previousPage()"
>
Previous
</Button>
<Button
variant="outline"
size="sm"
:disabled="!table.getCanNextPage()"
@click="table.nextPage()"
>
Next
</Button>
</div>
</div> </div>
<div class="flex items-center justify-end py-4 space-x-2">
<Button
variant="outline"
size="sm"
:disabled="!table.getCanPreviousPage()"
@click="table.previousPage()"
>
Previous
</Button>
<Button
variant="outline"
size="sm"
:disabled="!table.getCanNextPage()"
@click="table.nextPage()"
>
Next
</Button>
</div>
</div>
</template> </template>
``` ```
See [Reusable Components](#reusable-components) section for a more advanced pagination component. See [Reusable Components](#reusable-components) section for a more advanced pagination component.
@ -454,25 +448,23 @@ Let's make the email column sortable.
<Steps> <Steps>
### Add the following into your `utils` file: ### Add the following into your `utils` file
```ts:line-numbers {5,6,12-17} ```ts
import { type ClassValue, clsx } from 'clsx' import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge' import { twMerge } from 'tailwind-merge'
import { camelize, getCurrentInstance, toHandlerKey } from 'vue'
import type { Updater } from '@tanstack/vue-table' import type { Updater } from '@tanstack/vue-table'
import { type Ref } from 'vue' import type { Ref } from 'vue'
export function cn(...inputs: ClassValue[]) { export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)) return twMerge(clsx(inputs))
} }
export function valueUpdater<T extends Updater<any>>(updaterOrValue: T, ref: Ref) { export function valueUpdater<T extends Updater<any>>(updaterOrValue: T, ref: Ref) {
ref.value ref.value = typeof updaterOrValue === 'function'
= typeof updaterOrValue === 'function' ? updaterOrValue(ref.value)
? updaterOrValue(ref.value) : updaterOrValue
: updaterOrValue
} }
``` ```
@ -480,62 +472,60 @@ The `valueUpdater` function updates a Vue `ref` object's value. It handles both
### Update `<DataTable>` ### Update `<DataTable>`
```vue:line-numbers {4,7,16,34,41-44} ```vue:line-numbers {4,14,17,33,40-44}
<script setup lang="ts" generic="TData, TValue"> <script setup lang="ts" generic="TData, TValue">
import type { import type {
ColumnDef, ColumnDef,
SortingState, SortingState,
} from '@tanstack/vue-table' } from '@tanstack/vue-table'
import { valueUpdater } from '@/lib/utils'
import { ArrowUpDown, ChevronDown } from 'lucide-vue-next' import { ArrowUpDown, ChevronDown } from 'lucide-vue-next'
import { h, ref } from 'vue' import { h, ref } from 'vue'
import { import {
FlexRender, FlexRender,
getCoreRowModel, getCoreRowModel,
getPaginationRowModel, getPaginationRowModel,
getSortedRowModel, getSortedRowModel,
useVueTable, useVueTable,
} from "@tanstack/vue-table" } from '@tanstack/vue-table'
import { valueUpdater } from '@/lib/utils'
import { import {
Table, Table,
TableBody, TableBody,
TableCell, TableCell,
TableHead, TableHead,
TableHeader, TableHeader,
TableRow, TableRow,
} from "@/components/ui/table" } from '@/components/ui/table'
const props = defineProps<{ const props = defineProps<{
columns: ColumnDef<TData, TValue>[] columns: ColumnDef<TData, TValue>[]
data: TData[] data: TData[]
}>() }>()
const sorting = ref<SortingState>([]) const sorting = ref<SortingState>([])
const table = useVueTable({ const table = useVueTable({
get data() { return props.data }, get data() { return props.data },
get columns() { return props.columns }, get columns() { return props.columns },
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(), getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(), getSortedRowModel: getSortedRowModel(),
onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting), onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting),
state: { state: {
get sorting() { return sorting.value }, get sorting() { return sorting.value },
}, },
}) })
</script> </script>
<template> <template>
<div> <div>
<div class="border rounded-md"> <div class="border rounded-md">
<Table>{ ... }</Table> <Table>{ ... }</Table>
</div>
</div> </div>
</div>
</template> </template>
``` ```
@ -971,11 +961,9 @@ export const columns = [
{ {
accessorKey: "email", accessorKey: "email",
header: ({ column }) => ( header: ({ column }) => (
return h(DataTableColumnHeader, { h(DataTableColumnHeader, {
props: { column: column,
column: column, title: 'Email'
title: 'Email'
}
}) })
), ),
}, },

View File

@ -1,81 +1,42 @@
--- ---
title: Date Picker title: Date Picker
description: A date picker component with range and presets. description: A date picker component with range and presets.
source: apps/www/src/lib/registry/default/example/DatePickerDemo.vue
primitive: https://www.radix-vue.com/components/calendar.html
--- ---
<ComponentPreview name="DatePickerDemo" />
<ComponentPreview name="DatePickerDemo" /> <Callout class="text-base mt-12">
If you're looking for **previous** Date Picker implementation, checkout to <span class="font-bold underline">[VCalendar Datepicker](/docs/components/v-date-picker)</span> component
</Callout>
## Installation ## Installation
The Date Picker is built using a composition of the `<Popover />` and the `<Calendar />` components. The Date Picker is built using a composition of the `<Popover />` and either the `<Calendar />` or `<RangeCalendar />` components.
See installation instructions for the [Popover](/docs/components/popover#installation) and the [Calendar](/docs/components/calendar#installation) components.
## Usage
```vue
<script setup lang="ts">
import { format } from 'date-fns'
import { Calendar as CalendarIcon } from 'lucide-vue-next'
import { ref } from 'vue'
import { cn } from '@/lib/utils'
import { Button } from '@/components/ui/button'
import { Calendar } from '@/components/ui/calendar'
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/popover'
const date = ref<Date>()
</script>
<template>
<Popover>
<PopoverTrigger as-child>
<Button
:variant="'outline'"
:class="cn(
'w-[280px] justify-start text-left font-normal',
!date && 'text-muted-foreground',
)"
>
<CalendarIcon class="mr-2 h-4 w-4" />
<span>{{ date ? format(date, "PPP") : "Pick a date" }}</span>
</Button>
</PopoverTrigger>
<PopoverContent class="w-auto p-0">
<Calendar v-model="date" />
</PopoverContent>
</Popover>
</template>
```
See installations instructions for the [Popover](/docs/components/popover), [Calendar](/docs/components/calendar), and [Range Calendar](/docs/components/range-calendar) components.
## Examples ## Examples
### Date Picker ### Date Picker
<ComponentPreview name="DatePickerDemo" /> <ComponentPreview name="DatePickerDemo" />
### Date Range Picker ### Date Range Picker
<ComponentPreview name="DatePickerWithRange" /> <ComponentPreview name="DatePickerWithRange" />
### Date Time Picker ### Date Range Picker with Independent Months
<ComponentPreview name="DateTimePickerDemo" /> <ComponentPreview name="DatePickerWithIndependentMonths" />
### With Presets ### With Presets
<ComponentPreview name="DatePickerWithPresets" /> <ComponentPreview name="DatePickerWithPresets" />
### With Slot
<ComponentPreview name="RangePickerWithSlot" />
### Form ### Form
<ComponentPreview name="DatePickerForm" /> <ComponentPreview name="DatePickerForm" />

View File

@ -1,13 +1,12 @@
--- ---
title: Dialog title: Dialog
description: A window overlaid on either the primary window or another dialog window, rendering the content underneath inert. description: A window overlaid on either the primary window or another dialog window, rendering the content underneath inert.
source: apps/www/src/lib/registry/default/ui/dialog source: apps/www/src/lib/registry/default/ui/dialog
primitive: https://www.radix-vue.com/components/dialog.html primitive: https://www.radix-vue.com/components/dialog.html
--- ---
<ComponentPreview name="DialogDemo" />
<ComponentPreview name="DialogDemo" />
## Installation ## Installation
```bash ```bash
npx shadcn-vue@latest add dialog npx shadcn-vue@latest add dialog
@ -49,17 +48,24 @@ import {
</template> </template>
``` ```
## Examples ## Examples
### Custom close button ### Custom close button
<ComponentPreview name="DialogCustomCloseButton" /> <ComponentPreview name="DialogCustomCloseButton" />
### Scroll body
<ComponentPreview name="DialogScrollBodyDemo" />
### Scroll overlay
<ComponentPreview name="DialogScrollOverlayDemo" />
## Notes ## Notes
To activate the `Dialog` component from within a `Context Menu` or `Dropdown Menu`, you must encase the `Context Menu` or `Dropdown Menu` component in the `Dialog` component. For more information, refer to the linked issue [here](https://github.com/radix-ui/primitives/issues/1836). To activate the `Dialog` component from within a `Context Menu` or `Dropdown Menu`, you must encase the `Context Menu` or `Dropdown Menu` component in the `Dialog` component. For more information, refer to the linked issue [here](https://github.com/radix-ui/primitives/issues/1836).
```js:line-numbers showLineNumber{14-25} ```js:line-numbers showLineNumber{14-25}
<Dialog> <Dialog>
<ContextMenu> <ContextMenu>

View File

@ -0,0 +1,70 @@
---
title: Drawer
description: A drawer component for vue.
source: apps/www/src/lib/registry/default/ui/drawer
primitive: https://github.com/radix-vue/vaul-vue
---
<ComponentPreview name="DrawerDemo" />
## About
Drawer is built on top of [Vaul Vue](https://github.com/radix-vue/vaul-vue).
## Installation
```bash
npx shadcn-vue@latest add drawer
```
## Usage
```vue showLineNumbers
<script setup lang="ts">
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from '@/components/ui/drawer'
</script>
<template>
<Drawer>
<DrawerTrigger>Open</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Are you absolutely sure?</DrawerTitle>
<DrawerDescription>This action cannot be undone.</DrawerDescription>
</DrawerHeader>
<DrawerFooter>
<Button>Submit</Button>
<DrawerClose>
<Button variant="outline">
Cancel
</Button>
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
</template>
```
### Scale Background
If you want the background to have a zoom effect, you need to add the `vaul-drawer-wrapper` attribute to the root component.
```html
<div vaul-drawer-wrapper id="app"></div>
```
## Examples
### Responsive Dialog
You can combine the `Dialog` and `Drawer` components to create a responsive dialog. This renders a `Dialog` component on desktop and a `Drawer` on mobile.
<ComponentPreview name="DrawerDialog" />

View File

@ -1,18 +1,17 @@
--- ---
title: Dropdown Menu title: Dropdown Menu
description: Displays a menu to the user — such as a set of actions or functions — triggered by a button. description: Displays a menu to the user — such as a set of actions or functions — triggered by a button.
source: apps/www/src/lib/registry/default/ui/dropdown-menu source: apps/www/src/lib/registry/default/ui/dropdown-menu
primitive: https://www.radix-vue.com/components/dropdown-menu.html primitive: https://www.radix-vue.com/components/dropdown-menu.html
--- ---
<ComponentPreview name="DropdownMenuDemo" />
<ComponentPreview name="DropdownMenuDemo" />
## Installation ## Installation
```bash ```bash
npx shadcn-vue@latest add dropdown-menu npx shadcn-vue@latest add dropdown-menu
``` ```
## Usage ## Usage
```vue ```vue
@ -40,4 +39,14 @@ import {
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
</template> </template>
``` ```
## Examples
### Checkboxes
<ComponentPreview name="DropdownMenuCheckboxes" />
### Radio Group
<ComponentPreview name="DropdownMenuRadioGroup" />

View File

@ -16,16 +16,14 @@ Well-designed HTML forms are:
In this guide, we will take a look at building forms with [`vee-validate`](https://vee-validate.logaretm.com/v4/) and [`zod`](https://zod.dev). We're going to use a `<FormField>` component to compose accessible forms using Radix Vue components. In this guide, we will take a look at building forms with [`vee-validate`](https://vee-validate.logaretm.com/v4/) and [`zod`](https://zod.dev). We're going to use a `<FormField>` component to compose accessible forms using Radix Vue components.
## Features ## Features
The `<Form />` component is a wrapper around the `vee-validate` library. It provides a few things: The `<Form />` component is a wrapper around the `vee-validate` library. It provides a few things:
- Composable components for building forms. - Composable components for building forms.
- A `<FormField />` component for building controlled form fields. - A `<FormField />` component for building controlled form fields.
- Form validation using `zod`. - Form validation using `zod`.
- Applies the correct `aria` attributes to form fields based on states, handle unqiue IDs - Applies the correct `aria` attributes to form fields based on states, handle unique IDs
- Built to work with all Radix Vue components. - Built to work with all Radix Vue components.
- Bring your own schema library. We use `zod` but you can use any other supported schema validation you want, like [`yup`](https://github.com/jquense/yup) or [`valibot`](https://valibot.dev/). - Bring your own schema library. We use `zod` but you can use any other supported schema validation you want, like [`yup`](https://github.com/jquense/yup) or [`valibot`](https://valibot.dev/).
- **You have full control over the markup and styling.** - **You have full control over the markup and styling.**
@ -53,7 +51,6 @@ The `<Form />` component is a wrapper around the `vee-validate` library. It prov
## Example ## Example
<TabPreview name="Component" :names="['Component', 'Native']"> <TabPreview name="Component" :names="['Component', 'Native']">
<template #Component> <template #Component>
@ -170,12 +167,10 @@ const formSchema = toTypedSchema(z.object({
</script> </script>
``` ```
### Define a form ### Define a form
Use the `useForm` composable from `vee-validate` or use `<Form />` component to create a form. Use the `useForm` composable from `vee-validate` or use `<Form />` component to create a form.
<TabPreview name="Composition" :names="['Composition', 'Component']"> <TabPreview name="Composition" :names="['Composition', 'Component']">
<template #Composition> <template #Composition>
@ -254,7 +249,7 @@ function onSubmit(values) {
### Build your form ### Build your form
Based on last step we can either use `<Form />` component or `useForm` composable Based on last step we can either use `<Form />` component or `useForm` composable
`useForm` is recommended cause values are typed automatically `useForm` is recommended because values are typed automatically
```vue:line-numbers {2} ```vue:line-numbers {2}
<script setup lang="ts"> <script setup lang="ts">
@ -327,11 +322,11 @@ See the following links for more examples on how to use the `vee-validate` featu
- [Input](/docs/components/input#form) - [Input](/docs/components/input#form)
- [Radio Group](/docs/components/radio-group#form) - [Radio Group](/docs/components/radio-group#form)
- [Select](/docs/components/select#form) - [Select](/docs/components/select#form)
- [Slider](/docs/components/slider#form)
- [Switch](/docs/components/switch#form) - [Switch](/docs/components/switch#form)
- [Textarea](/docs/components/textarea#form) - [Textarea](/docs/components/textarea#form)
- [Combobox](/docs/components/combobox#form) - [Combobox](/docs/components/combobox#form)
## Extras ## Extras
This example shows how to add motion to your forms with [Formkit AutoAnimate](https://auto-animate.formkit.com/) This example shows how to add motion to your forms with [Formkit AutoAnimate](https://auto-animate.formkit.com/)

View File

@ -1,18 +1,17 @@
--- ---
title: Hover Card title: Hover Card
description: For sighted users to preview content available behind a link. description: For sighted users to preview content available behind a link.
source: apps/www/src/lib/registry/default/ui/hover-card source: apps/www/src/lib/registry/default/ui/hover-card
primitive: https://www.radix-vue.com/components/hover-card.html primitive: https://www.radix-vue.com/components/hover-card.html
--- ---
<ComponentPreview name="HoverCardDemo" />
<ComponentPreview name="HoverCardDemo" />
## Installation ## Installation
```bash ```bash
npx shadcn-vue@latest add hover-card npx shadcn-vue@latest add hover-card
``` ```
## Usage ## Usage
```vue ```vue
@ -32,4 +31,4 @@ import {
</HoverCardContent> </HoverCardContent>
</HoverCard> </HoverCard>
</template> </template>
``` ```

View File

@ -3,8 +3,7 @@ title: Input
description: Displays a form input field or a component that looks like an input field. description: Displays a form input field or a component that looks like an input field.
--- ---
<ComponentPreview name="InputDemo" class="max-w-xs" />
<ComponentPreview name="InputDemo" class="max-w-xs" />
## Installation ## Installation
@ -26,8 +25,6 @@ npx shadcn-vue@latest add input
</Steps> </Steps>
</template> </template>
</TabPreview> </TabPreview>
@ -43,6 +40,8 @@ import { Input } from '@/components/ui/input'
</template> </template>
``` ```
## Examples
### Default ### Default
<ComponentPreview name="InputDemo" class="max-w-xs" /> <ComponentPreview name="InputDemo" class="max-w-xs" />
@ -63,6 +62,10 @@ import { Input } from '@/components/ui/input'
<ComponentPreview name="InputWithButton" class="max-w-xs" /> <ComponentPreview name="InputWithButton" class="max-w-xs" />
### With Icon
<ComponentPreview name="InputWithIcon" class="max-w-xs" />
### Form ### Form
<ComponentPreview name="InputForm" /> <ComponentPreview name="InputForm" />

Some files were not shown because too many files have changed in this diff Show More