chore: merge 'dev' in 'fix/table-with-many-rows'
This commit is contained in:
commit
83a11606f4
|
|
@ -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
16
.github/actions/setup/action.yml
vendored
Normal 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
|
||||
29
.github/workflows/publish.yaml
vendored
29
.github/workflows/publish.yaml
vendored
|
|
@ -18,7 +18,6 @@ on:
|
|||
# When a labeled '🚀request-deploy' pull request from forked repo, it will be deploy to Cloudflare Pages
|
||||
- labeled
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
# eslint-disable-next-line yml/no-empty-mapping-value
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
|
|
@ -49,32 +48,10 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Run a build step here
|
||||
- name: Setup Node.js environment
|
||||
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: Setup (Install Node & pnpm)
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm i --frozen-lockfile
|
||||
|
|
|
|||
9
.github/workflows/release.yaml
vendored
9
.github/workflows/release.yaml
vendored
|
|
@ -14,14 +14,13 @@ jobs:
|
|||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.x
|
||||
- name: Setup (Install Node & pnpm)
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- run: npx changelogithub
|
||||
- run: pnpm dlx changelogithub
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
|
|
|
|||
27
.github/workflows/test.yaml
vendored
27
.github/workflows/test.yaml
vendored
|
|
@ -19,31 +19,10 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js environment
|
||||
uses: actions/setup-node@v2
|
||||
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: Setup (Install Node & pnpm)
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm i --frozen-lockfile
|
||||
|
|
|
|||
1
.vscode/extensions.json
vendored
1
.vscode/extensions.json
vendored
|
|
@ -1,7 +1,6 @@
|
|||
{
|
||||
"recommendations": [
|
||||
"Vue.volar",
|
||||
"Vue.vscode-typescript-vue-plugin",
|
||||
"dbaeumer.vscode-eslint"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
18
.vscode/settings.json
vendored
18
.vscode/settings.json
vendored
|
|
@ -1,11 +1,27 @@
|
|||
{
|
||||
"vue.server.hybridMode": true,
|
||||
"vue.server.includeLanguages": [
|
||||
"vue",
|
||||
"markdown"
|
||||
],
|
||||
"prettier.enable": false,
|
||||
"editor.formatOnSave": false,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit",
|
||||
"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": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ This repository is structured as follows:
|
|||
```
|
||||
apps
|
||||
└── www
|
||||
├── src
|
||||
├── src
|
||||
│ └── content
|
||||
└── registry
|
||||
├── default
|
||||
|
|
@ -32,12 +32,12 @@ packages
|
|||
└── cli
|
||||
```
|
||||
|
||||
| Path | Description |
|
||||
| --------------------- | ---------------------------------------- |
|
||||
| `apps/www/app` | The Next.js application for the website. |
|
||||
| `apps/www/content` | The content for the website. |
|
||||
| `apps/www/registry` | The registry for the components. |
|
||||
| `packages/cli` | The `shadcn-vue` package. |
|
||||
| Path | Description |
|
||||
| ----------------------------| -------------------------------------------|
|
||||
| `apps/www/.vitepress` | The Vitepress application for the website. |
|
||||
| `apps/www/src/content` | The content for the website. |
|
||||
| `apps/www/src/lib/registry` | The registry for the components. |
|
||||
| `packages/cli` | The `shadcn-vue` package. |
|
||||
|
||||
## Development
|
||||
|
||||
|
|
@ -79,22 +79,24 @@ The documentation for this project is located in the `www` workspace. You can ru
|
|||
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
|
||||
|
||||
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
|
||||
apps
|
||||
└── www
|
||||
└── registry
|
||||
├── default
|
||||
│ ├── example
|
||||
│ └── ui
|
||||
└── new-york
|
||||
├── example
|
||||
└── ui
|
||||
└── src
|
||||
└── lib
|
||||
└── registry
|
||||
├── default
|
||||
│ ├── example
|
||||
│ └── ui
|
||||
└── new-york
|
||||
├── example
|
||||
└── ui
|
||||
```
|
||||
|
||||
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`
|
||||
|
||||
|
||||
If you are interested in the detailed specification you can visit
|
||||
https://www.conventionalcommits.org/ or check out the
|
||||
[Angular Commit Message Guidelines](https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#-commit-message-guidelines).
|
||||
|
||||
|
||||
|
||||
## 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.
|
||||
|
|
@ -155,4 +154,4 @@ Tests are written using [Vitest](https://vitest.dev). You can run all the tests
|
|||
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.
|
||||
|
|
|
|||
|
|
@ -3,16 +3,12 @@ import { defineConfig } from 'vitepress'
|
|||
import Icons from 'unplugin-icons/vite'
|
||||
import tailwind from 'tailwindcss'
|
||||
import autoprefixer from 'autoprefixer'
|
||||
import { createCssVariablesTheme } from 'shiki'
|
||||
import { transformerMetaWordHighlight } from '@shikijs/transformers'
|
||||
import { cssVariables } from './theme/config/shiki'
|
||||
|
||||
// import { transformerMetaWordHighlight, transformerNotationWordHighlight } from '@shikijs/transformers'
|
||||
import { siteConfig } from './theme/config/site'
|
||||
import ComponentPreviewPlugin from './theme/plugins/previewer'
|
||||
|
||||
const cssVariables = createCssVariablesTheme({
|
||||
variablePrefix: '--shiki-',
|
||||
variableDefaults: {},
|
||||
})
|
||||
import CodeWrapperPlugin from './theme/plugins/codewrapper'
|
||||
|
||||
// https://vitepress.dev/reference/site-config
|
||||
export default defineConfig({
|
||||
|
|
@ -60,11 +56,11 @@ export default defineConfig({
|
|||
markdown: {
|
||||
theme: cssVariables,
|
||||
codeTransformers: [
|
||||
// transformerMetaWordHighlight(),
|
||||
// transformerNotationWordHighlight(),
|
||||
transformerMetaWordHighlight(),
|
||||
],
|
||||
config(md) {
|
||||
md.use(ComponentPreviewPlugin)
|
||||
md.use(CodeWrapperPlugin)
|
||||
},
|
||||
},
|
||||
rewrites: {
|
||||
|
|
@ -74,13 +70,13 @@ export default defineConfig({
|
|||
css: {
|
||||
postcss: {
|
||||
plugins: [
|
||||
tailwind(),
|
||||
tailwind() as any,
|
||||
autoprefixer(),
|
||||
],
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
Icons({ compiler: 'vue3', autoInstall: true }),
|
||||
Icons({ compiler: 'vue3', autoInstall: true }) as any,
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
|
|
|
|||
26
apps/www/.vitepress/theme/components/APITable.vue
Normal file
26
apps/www/.vitepress/theme/components/APITable.vue
Normal 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>
|
||||
19
apps/www/.vitepress/theme/components/Announcement.vue
Normal file
19
apps/www/.vitepress/theme/components/Announcement.vue
Normal 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>
|
||||
38
apps/www/.vitepress/theme/components/BlockCopyButton.vue
Normal file
38
apps/www/.vitepress/theme/components/BlockCopyButton.vue
Normal 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>
|
||||
12
apps/www/.vitepress/theme/components/BlockPage.vue
Normal file
12
apps/www/.vitepress/theme/components/BlockPage.vue
Normal 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>
|
||||
245
apps/www/.vitepress/theme/components/BlockPreview.vue
Normal file
245
apps/www/.vitepress/theme/components/BlockPreview.vue
Normal 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>
|
||||
53
apps/www/.vitepress/theme/components/Blocks.vue
Normal file
53
apps/www/.vitepress/theme/components/Blocks.vue
Normal 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>
|
||||
|
|
@ -19,7 +19,7 @@ defineProps<CalloutProps>()
|
|||
<AlertTitle v-if="title">
|
||||
{{ title }}
|
||||
</AlertTitle>
|
||||
<AlertDescription>
|
||||
<AlertDescription class="[&_a]:underline">
|
||||
<slot />
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,98 @@
|
|||
<script lang="ts" setup>
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import * as z from 'zod'
|
||||
import { useConfigStore } from '@/stores/config'
|
||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||
import { Input } from '@/lib/registry/new-york/ui/input'
|
||||
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/lib/registry/new-york/ui/form'
|
||||
import { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger } from '@/lib/registry/new-york/ui/sheet'
|
||||
import RadixIconsGear from '~icons/radix-icons/gear'
|
||||
|
||||
const { codeConfig, setCodeConfig } = useConfigStore()
|
||||
|
||||
const formSchema = toTypedSchema(z.object({
|
||||
prefix: z.string().default(''),
|
||||
componentsPath: z.string().default('@/components'),
|
||||
utilsPath: z.string().default('@/utils'),
|
||||
}))
|
||||
|
||||
const { handleSubmit, setValues } = useForm({
|
||||
validationSchema: formSchema,
|
||||
initialValues: codeConfig.value,
|
||||
})
|
||||
|
||||
const onSubmit = handleSubmit((values) => {
|
||||
setCodeConfig(values)
|
||||
setValues(values)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Sheet
|
||||
@update:open="(open) => {
|
||||
if (open) setValues(codeConfig)
|
||||
}"
|
||||
>
|
||||
<SheetTrigger as-child>
|
||||
<Button
|
||||
class="w-9 h-9"
|
||||
:variant="'ghost'"
|
||||
:size="'icon'"
|
||||
>
|
||||
<RadixIconsGear class="w-5 h-5" />
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent>
|
||||
<form @submit="onSubmit">
|
||||
<SheetHeader>
|
||||
<SheetTitle>Edit code config</SheetTitle>
|
||||
<SheetDescription>
|
||||
Configure how the CodeBlock should render on the site.
|
||||
</SheetDescription>
|
||||
</SheetHeader>
|
||||
|
||||
<div class="my-4">
|
||||
<!-- <FormField v-slot="{ componentField }" name="prefix">
|
||||
<FormItem>
|
||||
<FormLabel>Prefix</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="" v-bind="componentField" />
|
||||
</FormControl>
|
||||
<FormDescription />
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField> -->
|
||||
<FormField v-slot="{ componentField }" name="componentsPath">
|
||||
<FormItem>
|
||||
<FormLabel>Components Path</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="@/components" v-bind="componentField" />
|
||||
</FormControl>
|
||||
<FormDescription />
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
<FormField v-slot="{ componentField }" name="utilsPath">
|
||||
<FormItem>
|
||||
<FormLabel>Utils Path</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="@/utils" v-bind="componentField" />
|
||||
</FormControl>
|
||||
<FormDescription />
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</div>
|
||||
|
||||
<SheetFooter>
|
||||
<SheetClose as-child>
|
||||
<Button type="submit">
|
||||
Save changes
|
||||
</Button>
|
||||
</SheetClose>
|
||||
</SheetFooter>
|
||||
</form>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
</template>
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { ref, toRefs, watch } from 'vue'
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { makeCodeSandboxParams } from '../utils/codeeditor'
|
||||
import Tooltip from './Tooltip.vue'
|
||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||
import { type Style } from '@/lib/registry/styles'
|
||||
import type { Style } from '@/lib/registry/styles'
|
||||
|
||||
const props = defineProps<{
|
||||
name: string
|
||||
|
|
@ -12,11 +12,12 @@ const props = defineProps<{
|
|||
style: Style
|
||||
}>()
|
||||
|
||||
const { code } = toRefs(props)
|
||||
const sources = ref<Record<string, string>>({})
|
||||
|
||||
onMounted(() => {
|
||||
sources.value['App.vue'] = props.code
|
||||
})
|
||||
watch(code, () => {
|
||||
sources.value['App.vue'] = code.value
|
||||
}, { immediate: true })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
|||
46
apps/www/.vitepress/theme/components/CodeWrapper.ts
Normal file
46
apps/www/.vitepress/theme/components/CodeWrapper.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import { type VNode, type VNodeArrayChildren, cloneVNode, defineComponent } from 'vue'
|
||||
import { useConfigStore } from '@/stores/config'
|
||||
|
||||
function crawlSpan(children: VNodeArrayChildren, cb: (vnode: VNode) => void) {
|
||||
children.forEach((childNode) => {
|
||||
if (!Array.isArray(childNode) && typeof childNode === 'object') {
|
||||
if (typeof childNode?.children === 'string')
|
||||
cb(childNode)
|
||||
else
|
||||
crawlSpan(childNode?.children as VNodeArrayChildren ?? [], cb)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default defineComponent(
|
||||
(props, { slots }) => {
|
||||
const { codeConfig } = useConfigStore()
|
||||
|
||||
return () => {
|
||||
const clonedVNode = slots.default?.()?.[0]
|
||||
? cloneVNode(slots.default?.()?.[0], {
|
||||
key: JSON.stringify(codeConfig.value),
|
||||
})
|
||||
: undefined
|
||||
|
||||
// @ts-expect-error cloneVNode
|
||||
const preVNode = [...clonedVNode?.children].find((node: VNode) => node.type === 'pre') as VNode
|
||||
// @ts-expect-error cloneVNode
|
||||
const codeVNode = preVNode.children?.at(0) as VNode
|
||||
|
||||
if (codeVNode) {
|
||||
crawlSpan(codeVNode.children as VNodeArrayChildren, (vnode) => {
|
||||
if (typeof vnode.children === 'string') {
|
||||
vnode.children = vnode.children.replaceAll('@/components', codeConfig.value.componentsPath)
|
||||
vnode.children = vnode.children.replaceAll('@/libs', codeConfig.value.utilsPath)
|
||||
}
|
||||
})
|
||||
|
||||
return clonedVNode
|
||||
}
|
||||
else {
|
||||
return slots.default?.()
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
@ -5,12 +5,13 @@ import { useConfigStore } from '@/stores/config'
|
|||
|
||||
const props = defineProps<{
|
||||
name: string
|
||||
typeName?: 'example' | 'block'
|
||||
}>()
|
||||
const { style } = useConfigStore()
|
||||
|
||||
const Component = defineAsyncComponent({
|
||||
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,
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,9 @@
|
|||
<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 ComponentLoader from './ComponentLoader.vue'
|
||||
import Stackblitz from './Stackblitz.vue'
|
||||
|
|
@ -11,14 +16,38 @@ defineOptions({
|
|||
inheritAttrs: false,
|
||||
})
|
||||
|
||||
withDefaults(defineProps<{
|
||||
const props = withDefaults(defineProps<{
|
||||
name: string
|
||||
align?: 'center' | 'start' | 'end'
|
||||
sfcTsCode?: string
|
||||
sfcTsHtml?: string
|
||||
}>(), { align: 'center' })
|
||||
|
||||
const { style } = useConfigStore()
|
||||
const { style, codeConfig } = useConfigStore()
|
||||
|
||||
const 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>
|
||||
|
||||
<template>
|
||||
|
|
@ -47,8 +76,8 @@ const { style } = useConfigStore()
|
|||
<StyleSwitcher />
|
||||
|
||||
<div class="flex items-center gap-x-1">
|
||||
<Stackblitz :key="style" :style="style" :name="name" :code="decodeURIComponent(sfcTsCode ?? '')" />
|
||||
<CodeSandbox :key="style" :style="style" :name="name" :code="decodeURIComponent(sfcTsCode ?? '')" />
|
||||
<Stackblitz :key="style" :style="style" :name="name" :code="rawString" />
|
||||
<CodeSandbox :key="style" :style="style" :name="name" :code="rawString" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
|
@ -58,11 +87,15 @@ const { style } = useConfigStore()
|
|||
'items-end': align === 'end',
|
||||
})"
|
||||
>
|
||||
<ComponentLoader v-bind="$attrs" :key="style" :name="name" />
|
||||
<ComponentLoader v-bind="$attrs" :key="style" :name="name" :type-name="'example'" />
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="code">
|
||||
<div v-if="sfcTsHtml" class="language-vue" style="flex: 1;" v-html="decodeURIComponent(sfcTsHtml)" />
|
||||
<TabsContent value="code" class="vp-doc">
|
||||
<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 />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ const { copy, copied } = useClipboard()
|
|||
|
||||
const codeRef = ref<HTMLElement>()
|
||||
async function copyCode() {
|
||||
await copy(codeRef.value?.innerText.replace(/\u00A0/g, " ") ?? '')
|
||||
await copy(codeRef.value?.textContent?.replace(/\u00A0/g, ' ') ?? '')
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
|||
53
apps/www/.vitepress/theme/components/DocsBreadcrumb.vue
Normal file
53
apps/www/.vitepress/theme/components/DocsBreadcrumb.vue
Normal 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>
|
||||
|
|
@ -8,6 +8,11 @@ import ArrowRightIcon from '~icons/radix-icons/arrow-right'
|
|||
const { path } = toRefs(useRoute())
|
||||
|
||||
const examples = [
|
||||
{
|
||||
name: 'Mail',
|
||||
href: '/examples/mail',
|
||||
code: 'https://github.com/radix-vue/shadcn-vue/tree/dev/apps/www/src/examples/mail',
|
||||
},
|
||||
{
|
||||
name: 'Dashboard',
|
||||
href: '/examples/dashboard',
|
||||
|
|
@ -30,7 +35,7 @@ const examples = [
|
|||
},
|
||||
{
|
||||
name: 'Forms',
|
||||
href: '/examples/forms/forms',
|
||||
href: '/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"
|
||||
:class="cn(
|
||||
'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-medium text-muted-foreground',
|
||||
)"
|
||||
|
|
|
|||
54
apps/www/.vitepress/theme/components/InlineThemePicker.vue
Normal file
54
apps/www/.vitepress/theme/components/InlineThemePicker.vue
Normal 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>
|
||||
|
|
@ -4,28 +4,18 @@ import PageHeaderHeading from '../components/PageHeaderHeading.vue'
|
|||
import PageHeaderDescription from '../components/PageHeaderDescription.vue'
|
||||
import PageAction from '../components/PageAction.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 { buttonVariants } from '@/lib/registry/new-york/ui/button'
|
||||
import { Separator } from '@/lib/registry/new-york/ui/separator'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
import DashboardExample from '@/examples/dashboard/Example.vue'
|
||||
import MailExample from '@/examples/mail/Example.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageHeader class="page-header pb-8">
|
||||
<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>
|
||||
<Announcement />
|
||||
<PageHeaderHeading>Build your component library.</PageHeaderHeading>
|
||||
<PageHeaderDescription>
|
||||
Beautifully designed components that you can copy and paste into your
|
||||
|
|
@ -55,17 +45,17 @@ import DashboardExample from '@/examples/dashboard/Example.vue'
|
|||
<ExamplesNav />
|
||||
<section class="space-y-8 overflow-hidden rounded-lg border-2 border-primary dark:border-muted md:hidden">
|
||||
<VPImage
|
||||
alt="Dashboard"
|
||||
alt="Mail"
|
||||
width="1280"
|
||||
height="866" class="block" :image="{
|
||||
dark: '/examples/dashboard-dark.png',
|
||||
light: '/examples/dashboard-light.png',
|
||||
dark: '/examples/mail-dark.png',
|
||||
light: '/examples/mail-light.png',
|
||||
}"
|
||||
/>
|
||||
</section>
|
||||
<section class="hidden md:block">
|
||||
<div class="overflow-hidden rounded-[0.5rem] border bg-background shadow">
|
||||
<DashboardExample />
|
||||
<MailExample />
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
</script>
|
||||
|
||||
<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">
|
||||
<g clip-path="url(#clip0_102_1338)">
|
||||
<path d="M208 128L128 208" stroke="#41B883" stroke-width="16" stroke-linecap="round" stroke-linejoin="round" />
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
</defs>
|
||||
</svg>
|
||||
|
||||
<span class="font-bold ">
|
||||
<span class="font-bold">
|
||||
shadcn-vue
|
||||
</span>
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import Logo from './Logo.vue'
|
|||
import { Sheet, SheetContent, SheetTrigger } from '@/lib/registry/default/ui/sheet'
|
||||
import { Button } from '@/lib/registry/default/ui/button'
|
||||
import { ScrollArea } from '@/lib/registry/default/ui/scroll-area'
|
||||
import ViewVerticalIcon from '~icons/radix-icons/view-vertical'
|
||||
|
||||
const open = ref(false)
|
||||
</script>
|
||||
|
|
@ -17,7 +16,35 @@ const open = ref(false)
|
|||
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"
|
||||
>
|
||||
<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>
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
|
|
@ -36,17 +63,26 @@ const open = ref(false)
|
|||
</div>
|
||||
<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">
|
||||
<h4 class="font-medium">
|
||||
{{ items.title }}
|
||||
</h4>
|
||||
<div class="flex items-center">
|
||||
<h4 class="font-medium">
|
||||
{{ 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
|
||||
v-for="item in items.items" :key="item.href"
|
||||
:href="item.href"
|
||||
class="text-muted-foreground"
|
||||
class="text-muted-foreground inline-flex items-center"
|
||||
@click="open = false"
|
||||
>
|
||||
{{ 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>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { cn } from '@/lib/utils'
|
|||
</script>
|
||||
|
||||
<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 />
|
||||
</WrapBalancer>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { cn } from '@/lib/utils'
|
|||
<template>
|
||||
<h1
|
||||
: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 ?? '',
|
||||
)"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { ref, toRefs, watch } from 'vue'
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { makeStackblitzParams } from '../utils/codeeditor'
|
||||
import Tooltip from './Tooltip.vue'
|
||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||
import { type Style } from '@/lib/registry/styles'
|
||||
import type { Style } from '@/lib/registry/styles'
|
||||
|
||||
const props = defineProps<{
|
||||
name: string
|
||||
|
|
@ -12,11 +12,12 @@ const props = defineProps<{
|
|||
style: Style
|
||||
}>()
|
||||
|
||||
const { code } = toRefs(props)
|
||||
const sources = ref<Record<string, string>>({})
|
||||
|
||||
onMounted(() => {
|
||||
sources.value['App.vue'] = props.code
|
||||
})
|
||||
watch(code, () => {
|
||||
sources.value['App.vue'] = code.value
|
||||
}, { immediate: true })
|
||||
|
||||
function handleClick() {
|
||||
makeStackblitzParams(props.name, props.style, sources.value)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { type SelectTriggerProps } from 'radix-vue'
|
||||
import type { SelectTriggerProps } from 'radix-vue'
|
||||
import { useConfigStore } from '@/stores/config'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { useSlots } from 'vue'
|
||||
import { TabsContent, TabsTrigger } from '@/lib/registry/default/ui/tabs'
|
||||
import { TabsContent } from '@/lib/registry/default/ui/tabs'
|
||||
|
||||
withDefaults(defineProps<{
|
||||
title?: string
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import type { TableOfContents, TableOfContentsItem } from '../types/docs'
|
|||
import TableOfContentTree from './TableOfContentTree.vue'
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/lib/registry/default/ui/collapsible'
|
||||
import { buttonVariants } from '@/lib/registry/default/ui/button'
|
||||
import { ScrollArea } from '@/lib/registry/default/ui/scroll-area'
|
||||
|
||||
const headers = shallowRef<TableOfContents>()
|
||||
|
||||
|
|
@ -22,8 +23,8 @@ function getHeadingsWithHierarchy(divId: string) {
|
|||
headings.forEach((heading: HTMLHeadingElement) => {
|
||||
const level = Number.parseInt(heading.tagName.charAt(1))
|
||||
if (!heading.id) {
|
||||
const newId = heading.innerText
|
||||
.replaceAll(/[^a-zA-Z0-9 ]/g, '')
|
||||
const newId = heading.textContent
|
||||
.replaceAll(/[^a-z0-9 ]/gi, '')
|
||||
.replaceAll(' ', '-')
|
||||
.toLowerCase()
|
||||
heading.id = `${newId}`
|
||||
|
|
@ -55,11 +56,15 @@ onContentUpdated(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-2 hidden xl:block">
|
||||
<p class="font-medium">
|
||||
On This Page
|
||||
</p>
|
||||
<TableOfContentTree :tree="headers" :level="1" />
|
||||
<div class="hidden xl:block">
|
||||
<ScrollArea orientation="vertical" class="h-[calc(100vh-6.5rem)] z-30 md:block overflow-y-auto" type="hover">
|
||||
<div class="space-y-2">
|
||||
<p class="font-medium">
|
||||
On This Page
|
||||
</p>
|
||||
<TableOfContentTree :tree="headers" :level="1" />
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
|
||||
<div class="block xl:hidden mb-6">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
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()
|
||||
|
||||
|
|
|
|||
106
apps/www/.vitepress/theme/components/ThemeCustomizer.vue
Normal file
106
apps/www/.vitepress/theme/components/ThemeCustomizer.vue
Normal 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>
|
||||
47
apps/www/.vitepress/theme/components/ThemePopover.vue
Normal file
47
apps/www/.vitepress/theme/components/ThemePopover.vue
Normal 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>
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
export { default as CodeWrapper } from './CodeWrapper'
|
||||
export { default as ComponentPreview } from './ComponentPreview.vue'
|
||||
export { default as APITable } from './APITable.vue'
|
||||
export { default as TabPreview } from './TabPreview.vue'
|
||||
export { default as TabMarkdown } from './TabMarkdown.vue'
|
||||
export { default as TabsMarkdown } from './TabsMarkdown.vue'
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { addDays, startOfToday } from 'date-fns'
|
||||
import { type Ref, ref } from 'vue'
|
||||
import type { DateRange } from 'radix-vue'
|
||||
import { getLocalTimeZone, today } from '@internationalized/date'
|
||||
|
||||
import ThemingLayout from './../../layout/ThemingLayout.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 Metric from '@/lib/registry/new-york/example/Cards/Metric.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 {
|
||||
Card,
|
||||
} from '@/lib/registry/new-york/ui/card'
|
||||
import { Calendar } from '@/lib/registry/new-york/ui/calendar'
|
||||
import { Card } from '@/lib/registry/new-york/ui/card'
|
||||
import { RangeCalendar } from '@/lib/registry/new-york/ui/range-calendar'
|
||||
|
||||
const goal = ref(350)
|
||||
const now = today(getLocalTimeZone())
|
||||
|
||||
const range = ref({
|
||||
start: startOfToday(),
|
||||
end: addDays(startOfToday(), 8),
|
||||
})
|
||||
start: now,
|
||||
end: now.add({ days: 8 }),
|
||||
}) as Ref<DateRange>
|
||||
</script>
|
||||
|
||||
<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="hidden gap-1 sm:grid-cols-[280px_1fr] md:grid">
|
||||
<Card class="max-w-[280px]">
|
||||
<Calendar v-model.range="range" />
|
||||
<RangeCalendar v-model="range" />
|
||||
</Card>
|
||||
|
||||
<div class="pt-3 sm:pl-2 sm:pt-0 xl:pl-3">
|
||||
|
|
|
|||
|
|
@ -2,11 +2,41 @@ import { CreditCard } from 'lucide-vue-next'
|
|||
import RiAppleFill from '~icons/ri/apple-fill'
|
||||
import RiPaypalFill from '~icons/ri/paypal-fill'
|
||||
|
||||
interface Payment {
|
||||
status: string
|
||||
email: string
|
||||
amount: number
|
||||
}
|
||||
type Color =
|
||||
| 'zinc'
|
||||
| 'slate'
|
||||
| '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 {
|
||||
name: string
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ export interface NavItem {
|
|||
}
|
||||
|
||||
export type SidebarNavItem = NavItem & {
|
||||
items: SidebarNavItem[]
|
||||
items?: SidebarNavItem[]
|
||||
}
|
||||
|
||||
export type NavItemWithChildren = NavItem & {
|
||||
items: NavItemWithChildren[]
|
||||
items?: NavItemWithChildren[]
|
||||
}
|
||||
|
||||
interface DocsConfig {
|
||||
|
|
@ -23,7 +23,7 @@ interface DocsConfig {
|
|||
export const docsConfig: DocsConfig = {
|
||||
mainNav: [
|
||||
{
|
||||
title: 'Documentation',
|
||||
title: 'Docs',
|
||||
href: '/docs/introduction',
|
||||
},
|
||||
{
|
||||
|
|
@ -36,7 +36,11 @@ export const docsConfig: DocsConfig = {
|
|||
},
|
||||
{
|
||||
title: 'Examples',
|
||||
href: '/examples/dashboard',
|
||||
href: '/examples/mail',
|
||||
},
|
||||
{
|
||||
title: 'Blocks',
|
||||
href: '/blocks',
|
||||
},
|
||||
{
|
||||
title: 'GitHub',
|
||||
|
|
@ -51,46 +55,47 @@ export const docsConfig: DocsConfig = {
|
|||
{
|
||||
title: 'Introduction',
|
||||
href: '/docs/introduction',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Installation',
|
||||
href: '/docs/installation',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'components.json',
|
||||
href: '/docs/components-json',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Theming',
|
||||
href: '/docs/theming',
|
||||
},
|
||||
{
|
||||
title: 'Dark Mode',
|
||||
href: '/docs/dark-mode',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'CLI',
|
||||
href: '/docs/cli',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Typography',
|
||||
href: '/docs/typography',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Figma',
|
||||
href: '/docs/figma',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Changelog',
|
||||
href: '/docs/changelog',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'About',
|
||||
href: '/docs/about',
|
||||
},
|
||||
{
|
||||
title: 'Contribution',
|
||||
href: '/docs/contribution',
|
||||
items: [],
|
||||
},
|
||||
],
|
||||
|
|
@ -101,21 +106,34 @@ export const docsConfig: DocsConfig = {
|
|||
{
|
||||
title: 'Vite',
|
||||
href: '/docs/installation/vite',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Nuxt',
|
||||
href: '/docs/installation/nuxt',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Astro',
|
||||
href: '/docs/installation/astro',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: '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: [],
|
||||
},
|
||||
],
|
||||
|
|
@ -126,230 +144,219 @@ export const docsConfig: DocsConfig = {
|
|||
{
|
||||
title: 'Accordion',
|
||||
href: '/docs/components/accordion',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Alert',
|
||||
href: '/docs/components/alert',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Alert Dialog',
|
||||
href: '/docs/components/alert-dialog',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Aspect Ratio',
|
||||
href: '/docs/components/aspect-ratio',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Avatar',
|
||||
href: '/docs/components/avatar',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Badge',
|
||||
href: '/docs/components/badge',
|
||||
},
|
||||
{
|
||||
title: 'Breadcrumb',
|
||||
href: '/docs/components/breadcrumb',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Button',
|
||||
href: '/docs/components/button',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Calendar',
|
||||
href: '/docs/components/calendar',
|
||||
items: [],
|
||||
label: 'Updated',
|
||||
},
|
||||
{
|
||||
title: 'Card',
|
||||
href: '/docs/components/card',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Carousel',
|
||||
href: '/docs/components/carousel',
|
||||
label: 'New',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Checkbox',
|
||||
href: '/docs/components/checkbox',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Collapsible',
|
||||
href: '/docs/components/collapsible',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Combobox',
|
||||
href: '/docs/components/combobox',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Command',
|
||||
href: '/docs/components/command',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Context Menu',
|
||||
href: '/docs/components/context-menu',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Data Table',
|
||||
href: '/docs/components/data-table',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Date Picker',
|
||||
href: '/docs/components/date-picker',
|
||||
items: [],
|
||||
label: 'Updated',
|
||||
},
|
||||
{
|
||||
title: 'Dialog',
|
||||
href: '/docs/components/dialog',
|
||||
},
|
||||
{
|
||||
title: 'Drawer',
|
||||
href: '/docs/components/drawer',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Dropdown Menu',
|
||||
href: '/docs/components/dropdown-menu',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Form',
|
||||
href: '/docs/components/form',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Hover Card',
|
||||
href: '/docs/components/hover-card',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Input',
|
||||
href: '/docs/components/input',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Label',
|
||||
href: '/docs/components/label',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Menubar',
|
||||
href: '/docs/components/menubar',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Navigation Menu',
|
||||
href: '/docs/components/navigation-menu',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Number Field',
|
||||
href: '/docs/components/number-field',
|
||||
label: 'New Alpha',
|
||||
},
|
||||
{
|
||||
title: 'Pagination',
|
||||
href: '/docs/components/pagination',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Pin Input',
|
||||
title: 'PIN Input',
|
||||
href: '/docs/components/pin-input',
|
||||
label: 'New',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Popover',
|
||||
href: '/docs/components/popover',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Progress',
|
||||
href: '/docs/components/progress',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Radio Group',
|
||||
href: '/docs/components/radio-group',
|
||||
},
|
||||
{
|
||||
title: 'Range Calendar',
|
||||
href: '/docs/components/range-calendar',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Resizable',
|
||||
href: '/docs/components/resizable',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Scroll Area',
|
||||
href: '/docs/components/scroll-area',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Select',
|
||||
href: '/docs/components/select',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Separator',
|
||||
href: '/docs/components/separator',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Sheet',
|
||||
href: '/docs/components/sheet',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Skeleton',
|
||||
href: '/docs/components/skeleton',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Slider',
|
||||
href: '/docs/components/slider',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Sonner',
|
||||
href: '/docs/components/sonner',
|
||||
label: 'New',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Switch',
|
||||
href: '/docs/components/switch',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Table',
|
||||
href: '/docs/components/table',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Tabs',
|
||||
href: '/docs/components/tabs',
|
||||
},
|
||||
{
|
||||
title: 'Tags Input',
|
||||
href: '/docs/components/tags-input',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Textarea',
|
||||
href: '/docs/components/textarea',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Toast',
|
||||
href: '/docs/components/toast',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Toggle',
|
||||
href: '/docs/components/toggle',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Toggle Group',
|
||||
href: '/docs/components/toggle-group',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Tooltip',
|
||||
href: '/docs/components/tooltip',
|
||||
items: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
@ -373,6 +380,11 @@ interface Example {
|
|||
code: string
|
||||
}
|
||||
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',
|
||||
href: '/examples/dashboard',
|
||||
|
|
|
|||
6
apps/www/.vitepress/theme/config/shiki.ts
Normal file
6
apps/www/.vitepress/theme/config/shiki.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { createCssVariablesTheme } from 'shiki'
|
||||
|
||||
export const cssVariables = createCssVariablesTheme({
|
||||
variablePrefix: '--shiki-',
|
||||
variableDefaults: {},
|
||||
})
|
||||
|
|
@ -15,6 +15,6 @@ export const siteConfig = {
|
|||
|
||||
export const announcementConfig = {
|
||||
icon: '✨',
|
||||
title: 'New components!',
|
||||
link: '/docs/components/carousel.html',
|
||||
title: 'Extended: Auto Form, Charts',
|
||||
link: '/docs/components/auto-form.html',
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
/* eslint-disable vue/component-definition-name-casing */
|
||||
// https://vitepress.dev/guide/custom-theme
|
||||
import Layout from './layout/MainLayout.vue'
|
||||
import DocsLayout from './layout/DocsLayout.vue'
|
||||
|
|
|
|||
|
|
@ -3,11 +3,10 @@ import { useData, useRoute } from 'vitepress'
|
|||
import { docsConfig } from '../config/docs'
|
||||
import TableOfContentVue from '../components/TableOfContent.vue'
|
||||
import EditLink from '../components/EditLink.vue'
|
||||
import DocsBreadcrumb from '../components/DocsBreadcrumb.vue'
|
||||
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 RadixIconsExternalLink from '~icons/radix-icons/external-link'
|
||||
import ChevronRightIcon from '~icons/lucide/chevron-right'
|
||||
|
||||
const $route = useRoute()
|
||||
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"
|
||||
>
|
||||
{{ 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>
|
||||
|
||||
<div
|
||||
|
|
@ -62,20 +65,17 @@ const sourceLink = 'https://github.com/radix-vue/shadcn-vue/tree/dev/'
|
|||
<TableOfContentVue />
|
||||
</div>
|
||||
|
||||
<div class="mb-4 flex items-center space-x-1 text-sm text-muted-foreground">
|
||||
<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>
|
||||
<DocsBreadcrumb class="mb-4" />
|
||||
|
||||
<div class="space-y-2">
|
||||
<h1 class="scroll-m-20 text-4xl font-bold tracking-tight">
|
||||
{{ frontmatter.title }}
|
||||
</h1>
|
||||
<div class="flex items-center space-x-4">
|
||||
<h1 class="scroll-m-20 text-4xl font-bold tracking-tight">
|
||||
{{ 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">
|
||||
{{ frontmatter.description }}
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -4,28 +4,16 @@ import PageHeaderHeading from '../components/PageHeaderHeading.vue'
|
|||
import PageHeaderDescription from '../components/PageHeaderDescription.vue'
|
||||
import PageAction from '../components/PageAction.vue'
|
||||
import ExamplesNav from '../components/ExamplesNav.vue'
|
||||
import { announcementConfig } from '../config/site'
|
||||
import ArrowRightIcon from '~icons/radix-icons/arrow-right'
|
||||
import Announcement from '../components/Announcement.vue'
|
||||
|
||||
import { buttonVariants } from '@/lib/registry/new-york/ui/button'
|
||||
import { Separator } from '@/lib/registry/new-york/ui/separator'
|
||||
import { cn } from '@/lib/utils'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container relative">
|
||||
<PageHeader class="page-header pb-8">
|
||||
<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>
|
||||
<Announcement />
|
||||
<PageHeaderHeading class="hidden md:block">
|
||||
Check out some examples.
|
||||
</PageHeaderHeading>
|
||||
|
|
|
|||
|
|
@ -2,12 +2,13 @@
|
|||
import { useMagicKeys, useToggle } from '@vueuse/core'
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import { Content, useData, useRoute, useRouter } from 'vitepress'
|
||||
import { SearchIcon } from 'lucide-vue-next'
|
||||
import { type NavItem, docsConfig } from '../config/docs'
|
||||
import Logo from '../components/Logo.vue'
|
||||
import MobileNav from '../components/MobileNav.vue'
|
||||
import CodeConfigCustomizer from '../components/CodeConfigCustomizer.vue'
|
||||
|
||||
import Kbd from '../components/Kbd.vue'
|
||||
import ThemePopover from '../components/ThemePopover.vue'
|
||||
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator } from '@/lib/registry/default/ui/command'
|
||||
|
||||
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 NewYorkSonner } from '@/lib/registry/new-york/ui/sonner'
|
||||
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 Circle from '~icons/radix-icons/circle'
|
||||
|
|
@ -84,215 +86,225 @@ watch(() => $route.path, (n) => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex min-h-screen flex-col bg-background">
|
||||
<header class="sticky z-40 top-0 bg-background/80 backdrop-blur-lg border-b border-border">
|
||||
<div
|
||||
class="container flex justify-between h-14 max-w-screen-2xl items-center"
|
||||
>
|
||||
<MobileNav />
|
||||
<TooltipProvider>
|
||||
<div v-if="$route.data.frontmatter.layout === false">
|
||||
<Content :key="$route.path" />
|
||||
</div>
|
||||
<div v-else vaul-drawer-wrapper class="flex min-h-screen flex-col bg-background">
|
||||
<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">
|
||||
<Logo />
|
||||
|
||||
<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`,
|
||||
}"
|
||||
<nav
|
||||
class="flex items-center max-lg:space-x-4 space-x-6 text-sm font-medium"
|
||||
>
|
||||
{{ route.title }}
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class=" flex items-center justify-end space-x-4 ">
|
||||
<Button
|
||||
variant="outline"
|
||||
class="w-72 h-8 px-3 hidden lg:flex lg:justify-between lg:items-center"
|
||||
@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()"
|
||||
<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`,
|
||||
'hidden lg:block': route?.href?.includes('github'),
|
||||
}"
|
||||
>
|
||||
<component
|
||||
:is="isDark ? RadixIconsSun : RadixIconsMoon"
|
||||
class="w-[20px] h-5 text-foreground"
|
||||
/>
|
||||
{{ route.title }}
|
||||
</a>
|
||||
</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>
|
||||
</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>
|
||||
</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>
|
||||
</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>
|
||||
|
||||
<footer class="py-6 md:px-8 md:py-0">
|
||||
<div class="container flex flex-col items-center justify-between gap-4 md:h-24 md:flex-row">
|
||||
<div class="text-center text-sm leading-loose text-muted-foreground md:text-left">
|
||||
<span class="inline-block">
|
||||
Built and designed by
|
||||
<a
|
||||
href="https://twitter.com/shadcn"
|
||||
target="_blank"
|
||||
class="underline underline-offset-4 font-bold decoration-foreground"
|
||||
>
|
||||
shadcn
|
||||
</a>
|
||||
</span>
|
||||
<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>
|
||||
<footer class="py-6 md:px-8 md:py-0">
|
||||
<div class="container flex flex-col items-center justify-between gap-4 md:h-24 md:flex-row">
|
||||
<div class="text-center text-sm leading-loose text-muted-foreground md:text-left">
|
||||
<span class="inline-block">
|
||||
Built and designed by
|
||||
<a
|
||||
href="https://twitter.com/shadcn"
|
||||
target="_blank"
|
||||
class="underline underline-offset-4 font-bold decoration-foreground"
|
||||
>
|
||||
shadcn
|
||||
</a>
|
||||
</span>
|
||||
<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">
|
||||
<DialogContent class="p-0">
|
||||
<Command>
|
||||
<CommandInput placeholder="Type a command or search..." />
|
||||
<CommandEmpty>
|
||||
No results found.
|
||||
</CommandEmpty>
|
||||
<CommandList
|
||||
@escape-key-down=" isOpen = false"
|
||||
>
|
||||
<CommandGroup heading="Links">
|
||||
<CommandItem
|
||||
v-for="item in docsConfig.mainNav"
|
||||
:key="item.title"
|
||||
:heading="item.title"
|
||||
:value="item.title"
|
||||
class="py-3"
|
||||
@select="handleSelectLink(item)"
|
||||
>
|
||||
<File class="mr-2 h-5 w-5" />
|
||||
<span>{{ item.title }}</span>
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
<CommandSeparator />
|
||||
<CommandGroup v-for="item in docsConfig.sidebarNav" :key="item.title" :heading="item.title">
|
||||
<CommandItem
|
||||
v-for="subItem in item.items"
|
||||
:key="subItem.title"
|
||||
:heading="subItem.title"
|
||||
:value="subItem.title"
|
||||
class="py-3"
|
||||
@select="
|
||||
handleSelectLink(subItem)"
|
||||
>
|
||||
<Circle class="mr-2 h-4 w-4" />
|
||||
<span>{{ subItem.title }}</span>
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
<CommandSeparator />
|
||||
<CommandGroup heading="Theme">
|
||||
<CommandItem
|
||||
value="light-theme"
|
||||
class="py-3"
|
||||
@select="
|
||||
() => {
|
||||
isDark = false;
|
||||
isOpen = false;
|
||||
}
|
||||
"
|
||||
>
|
||||
<RadixIconsSun class="mr-2 h-5 w-5" />
|
||||
<span>Light Theme</span>
|
||||
</CommandItem>
|
||||
<CommandItem
|
||||
value="dark-theme"
|
||||
class="py-3"
|
||||
@select="
|
||||
() => {
|
||||
isDark = true;
|
||||
isOpen = false;
|
||||
}
|
||||
"
|
||||
>
|
||||
<RadixIconsMoon class="mr-2 h-5 w-5" />
|
||||
<span>Dark Theme</span>
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<DefaultToaster />
|
||||
<ClientOnly>
|
||||
<NewYorkSonner :theme="isDark ? 'dark' : 'light'" />
|
||||
</ClientOnly>
|
||||
<NewYorkToaster />
|
||||
</div>
|
||||
<Dialog v-model:open="isOpen">
|
||||
<DialogContent class="p-0">
|
||||
<Command>
|
||||
<CommandInput placeholder="Type a command or search..." />
|
||||
<CommandEmpty>
|
||||
No results found.
|
||||
</CommandEmpty>
|
||||
<CommandList
|
||||
@escape-key-down=" isOpen = false"
|
||||
>
|
||||
<CommandGroup heading="Links">
|
||||
<CommandItem
|
||||
v-for="item in docsConfig.mainNav"
|
||||
:key="item.title"
|
||||
:heading="item.title"
|
||||
:value="item.title"
|
||||
class="py-3"
|
||||
@select="handleSelectLink(item)"
|
||||
>
|
||||
<File class="mr-2 h-5 w-5" />
|
||||
<span>{{ item.title }}</span>
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
<CommandSeparator />
|
||||
<CommandGroup v-for="item in docsConfig.sidebarNav" :key="item.title" :heading="item.title">
|
||||
<CommandItem
|
||||
v-for="subItem in item.items"
|
||||
:key="subItem.title"
|
||||
:heading="subItem.title"
|
||||
:value="subItem.title"
|
||||
class="py-3"
|
||||
@select="
|
||||
handleSelectLink(subItem)"
|
||||
>
|
||||
<Circle class="mr-2 h-4 w-4" />
|
||||
<span>{{ subItem.title }}</span>
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
<CommandSeparator />
|
||||
<CommandGroup heading="Theme">
|
||||
<CommandItem
|
||||
value="light-theme"
|
||||
class="py-3"
|
||||
@select="
|
||||
() => {
|
||||
isDark = false;
|
||||
isOpen = false;
|
||||
}
|
||||
"
|
||||
>
|
||||
<RadixIconsSun class="mr-2 h-5 w-5" />
|
||||
<span>Light Theme</span>
|
||||
</CommandItem>
|
||||
<CommandItem
|
||||
value="dark-theme"
|
||||
class="py-3"
|
||||
@select="
|
||||
() => {
|
||||
isDark = true;
|
||||
isOpen = false;
|
||||
}
|
||||
"
|
||||
>
|
||||
<RadixIconsMoon class="mr-2 h-5 w-5" />
|
||||
<span>Dark Theme</span>
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<DefaultToaster />
|
||||
<NewYorkSonner class="pointer-events-auto" :theme="'system'" />
|
||||
<NewYorkToaster />
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,35 +1,19 @@
|
|||
<script setup lang="ts">
|
||||
import { onMounted, watch } from 'vue'
|
||||
import { Paintbrush } from 'lucide-vue-next'
|
||||
import { useData } from 'vitepress'
|
||||
import PageHeader from '../components/PageHeader.vue'
|
||||
import PageHeaderHeading from '../components/PageHeaderHeading.vue'
|
||||
import PageHeaderDescription from '../components/PageHeaderDescription.vue'
|
||||
import CustomizerCode from '../components/CustomizerCode.vue'
|
||||
import { RADII, useConfigStore } from '@/stores/config'
|
||||
import { colors } from '@/lib/registry'
|
||||
import type { Color } from '../types/colors'
|
||||
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 { Label } from '@/lib/registry/new-york/ui/label'
|
||||
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 RadixIconsCheck from '~icons/radix-icons/check'
|
||||
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'
|
||||
import { Drawer, DrawerContent, DrawerTrigger } from '@/lib/registry/new-york/ui/drawer'
|
||||
|
||||
// Create an array of color values
|
||||
const allColors: Color[] = [
|
||||
|
|
@ -47,8 +31,7 @@ const allColors: Color[] = [
|
|||
'violet',
|
||||
]
|
||||
|
||||
const { theme, radius, setRadius, setTheme } = useConfigStore()
|
||||
const { isDark } = useData()
|
||||
const { theme, radius } = useConfigStore()
|
||||
|
||||
// Whenever the component is mounted, update the document class list
|
||||
onMounted(() => {
|
||||
|
|
@ -72,173 +55,63 @@ watch(radius, (radius) => {
|
|||
|
||||
<template>
|
||||
<div class="container relative">
|
||||
<div class="flex justify-between items-center">
|
||||
<div>
|
||||
<PageHeader class="page-header pb-8">
|
||||
<PageHeaderHeading class="hidden md:block">
|
||||
Make it yours.
|
||||
</PageHeaderHeading>
|
||||
<PageHeaderDescription>
|
||||
Hand-picked themes that you can copy and paste into your apps.
|
||||
</PageHeaderDescription>
|
||||
</PageHeader>
|
||||
</div>
|
||||
<div class="px-4 pb-8 md:ml-auto md:pb-0">
|
||||
<div class="flex items-center space-x-2">
|
||||
<div class="hidden md:flex">
|
||||
<div class="mr-4 hidden items-center space-x-1 lg:flex">
|
||||
<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>
|
||||
<Popover>
|
||||
<PopoverTrigger as-child>
|
||||
<Button variant="outline" class="h-9 rounded-[0.5rem]">
|
||||
<Paintbrush class="w-4 h-4 mr-2" />
|
||||
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>
|
||||
<PageHeader>
|
||||
<PageHeaderHeading class="hidden md:block">
|
||||
Add colors. Make it yours.
|
||||
</PageHeaderHeading>
|
||||
<PageHeaderHeading class="md:hidden">
|
||||
Make it yours
|
||||
</PageHeaderHeading>
|
||||
<PageHeaderDescription>
|
||||
Hand-picked themes that you can copy and paste into your apps.
|
||||
</PageHeaderDescription>
|
||||
|
||||
<PageAction>
|
||||
<InlineThemePicker class="gap-x-1 me-4 hidden lg:flex" :all-colors="allColors" />
|
||||
|
||||
<Drawer>
|
||||
<DrawerTrigger as-child>
|
||||
<Button variant="outline" class="md:hidden h-9 rounded-[0.5rem]">
|
||||
<Paintbrush class="w-4 h-4 mr-2" />
|
||||
Customize
|
||||
</Button>
|
||||
</DrawerTrigger>
|
||||
<DrawerContent class="p-6 pt-0">
|
||||
<ThemeCustomizer :all-colors="allColors" />
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
<Popover>
|
||||
<PopoverTrigger as-child>
|
||||
<Button variant="outline" class="hidden md:flex h-9 rounded-[0.5rem]">
|
||||
<Paintbrush class="w-4 h-4 mr-2" />
|
||||
Customize
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent :side-offset="8" align="end" class="w-96">
|
||||
<ThemeCustomizer :all-colors="allColors" />
|
||||
</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>
|
||||
</PageAction>
|
||||
</PageHeader>
|
||||
|
||||
<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>
|
||||
<slot />
|
||||
</section>
|
||||
|
|
|
|||
20
apps/www/.vitepress/theme/plugins/codewrapper.ts
Normal file
20
apps/www/.vitepress/theme/plugins/codewrapper.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import type { MarkdownRenderer } from 'vitepress'
|
||||
|
||||
export default function (md: MarkdownRenderer) {
|
||||
const defaultFenceRenderer = md.renderer.rules.fence
|
||||
if (!defaultFenceRenderer)
|
||||
return
|
||||
|
||||
md.renderer.rules.fence = function (tokens, idx, options, env, self) {
|
||||
// Check if this is a code block
|
||||
const token = tokens[idx]
|
||||
const isAllowedExtension = (token.info.includes('vue') || token.info.includes('astro') || token.info.includes('ts'))
|
||||
if (token && token.tag === 'code' && isAllowedExtension) {
|
||||
// Wrap the code block in CodeWrapper
|
||||
return `<CodeWrapper>${defaultFenceRenderer(tokens, idx, options, env, self)}</CodeWrapper>`
|
||||
}
|
||||
|
||||
// If not a code block, return the default rendering
|
||||
return defaultFenceRenderer(tokens, idx, options, env, self)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,5 @@
|
|||
import { dirname, resolve } from 'node:path'
|
||||
import fs from 'node:fs'
|
||||
import type { MarkdownEnv, MarkdownRenderer } from 'vitepress'
|
||||
import { generateDemoComponent, parseProps } from './utils'
|
||||
import type { MarkdownRenderer } from 'vitepress'
|
||||
import { parseProps } from './utils'
|
||||
|
||||
export default function (md: MarkdownRenderer) {
|
||||
function addRenderRule(type: string) {
|
||||
|
|
@ -12,31 +10,9 @@ export default function (md: MarkdownRenderer) {
|
|||
if (!content.match(/^<ComponentPreview\s/) || !content.endsWith('/>'))
|
||||
return defaultRender!(tokens, idx, options, env, self)
|
||||
|
||||
const { path } = env as MarkdownEnv
|
||||
const props = parseProps(content)
|
||||
|
||||
const { name, attrs } = props
|
||||
const pluginPath = dirname(__dirname)
|
||||
const srcPath = resolve(pluginPath, '../../src/lib/registry/default/example/', `${name}.vue`).replace(/\\/g, '/')
|
||||
|
||||
if (!fs.existsSync(srcPath)) {
|
||||
console.error(`rendering ${path}: ${srcPath} does not exist`)
|
||||
return defaultRender!(tokens, idx, options, env, self)
|
||||
}
|
||||
|
||||
let code = fs.readFileSync(srcPath, 'utf-8')
|
||||
code = code.replaceAll(
|
||||
'@/lib/registry/default/',
|
||||
'@/components/',
|
||||
)
|
||||
|
||||
const demoScripts = generateDemoComponent(md, env, {
|
||||
attrs,
|
||||
props,
|
||||
code,
|
||||
path: srcPath,
|
||||
})
|
||||
|
||||
const { attrs } = props
|
||||
const demoScripts = `<ComponentPreview ${attrs ?? ''} v-bind='${JSON.stringify(props)}'></ComponentPreview>`.trim()
|
||||
return demoScripts
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
// Credit to @hairyf https://github.com/hairyf/markdown-it-vitepress-demo
|
||||
|
||||
import type { MarkdownEnv, MarkdownRenderer } from 'vitepress'
|
||||
import { baseParse } from '@vue/compiler-core'
|
||||
import type { AttributeNode, ElementNode } from '@vue/compiler-core'
|
||||
|
||||
|
|
@ -11,36 +10,6 @@ export interface GenerateOptions {
|
|||
code: string
|
||||
}
|
||||
|
||||
export function parse(
|
||||
md: MarkdownRenderer,
|
||||
env: MarkdownEnv,
|
||||
{ code, attrs: _attrs, props }: GenerateOptions,
|
||||
) {
|
||||
const highlightedHtml = md.options.highlight!(code, 'vue', _attrs || '')
|
||||
const sfcTsHtml = highlightedHtml
|
||||
|
||||
const attrs
|
||||
= `sfcTsCode="${encodeURIComponent(code)}"\n`
|
||||
+ `sfcTsHtml="${encodeURIComponent(sfcTsHtml)}"\n`
|
||||
+ `v-bind='${JSON.stringify(props)}'\n`
|
||||
|
||||
return {
|
||||
attrs,
|
||||
highlightedHtml,
|
||||
sfcTsCode: code,
|
||||
sfcTsHtml,
|
||||
}
|
||||
}
|
||||
|
||||
export function generateDemoComponent(
|
||||
md: MarkdownRenderer,
|
||||
env: MarkdownEnv,
|
||||
options: GenerateOptions,
|
||||
) {
|
||||
const { attrs } = parse(md, env, options)
|
||||
return `<ComponentPreview \n${attrs}></ComponentPreview>`.trim()
|
||||
}
|
||||
|
||||
export function isUndefined(v: any): v is undefined {
|
||||
return v === undefined || v === null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,26 +7,41 @@
|
|||
--font-geist-sans: "geist-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||
Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 240 10% 3.9%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 240 10% 3.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 240 10% 3.9%;
|
||||
--primary: 240 5.9% 10%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
--secondary: 240 4.8% 95.9%;
|
||||
--secondary-foreground: 240 5.9% 10%;
|
||||
--muted: 240 4.8% 95.9%;
|
||||
--muted-foreground: 240 3.8% 46.1%;
|
||||
--accent: 240 4.8% 95.9%;
|
||||
--accent-foreground: 240 5.9% 10%;
|
||||
--destructive: 0 72.22% 50.59%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 240 5.9% 90%;
|
||||
--input: 240 5.9% 90%;
|
||||
--ring: 240 5% 64.9%;
|
||||
--radius: 0.5rem;
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 240 10% 3.9%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 240 10% 3.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 240 10% 3.9%;
|
||||
--primary: 240 5.9% 10%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
--secondary: 240 4.8% 95.9%;
|
||||
--secondary-foreground: 240 5.9% 10%;
|
||||
--muted: 240 4.8% 95.9%;
|
||||
--muted-foreground: 240 3.8% 46.1%;
|
||||
--accent: 240 4.8% 95.9%;
|
||||
--accent-foreground: 240 5.9% 10%;
|
||||
--destructive: 0 72.22% 50.59%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 240 5.9% 90%;
|
||||
--input: 240 5.9% 90%;
|
||||
--ring: 240 5% 64.9%;
|
||||
--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 {
|
||||
|
|
@ -61,7 +76,9 @@
|
|||
}
|
||||
body {
|
||||
@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 */
|
||||
|
|
@ -140,22 +157,30 @@
|
|||
}
|
||||
|
||||
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 {
|
||||
@apply py-4;
|
||||
}
|
||||
|
||||
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 {
|
||||
@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 {
|
||||
@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),
|
||||
|
|
|
|||
|
|
@ -345,13 +345,13 @@
|
|||
}
|
||||
|
||||
.vp-doc [class*='language-'] code .highlighted {
|
||||
background-color: hsl(240 3.7% 15.9%);
|
||||
transition: background-color 0.5s;
|
||||
/* margin: 0 -24px;
|
||||
padding: 0 24px; */
|
||||
width: calc(100% + 2 * 24px);
|
||||
display: inline-block;
|
||||
}
|
||||
@apply bg-[hsl(var(--foreground))] dark:bg-[hsl(var(--background)_/_50%)]
|
||||
}
|
||||
|
||||
.vp-doc [class*='language-'] code .highlighted.error {
|
||||
background-color: var(--vp-code-line-error-color);
|
||||
|
|
|
|||
13
apps/www/.vitepress/theme/types/colors.ts
Normal file
13
apps/www/.vitepress/theme/types/colors.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
export type Color =
|
||||
| 'zinc'
|
||||
| 'slate'
|
||||
| 'stone'
|
||||
| 'gray'
|
||||
| 'neutral'
|
||||
| 'red'
|
||||
| 'rose'
|
||||
| 'orange'
|
||||
| 'green'
|
||||
| 'blue'
|
||||
| 'yellow'
|
||||
| 'violet'
|
||||
|
|
@ -2,9 +2,11 @@ import { getParameters } from 'codesandbox/lib/api/define'
|
|||
import sdk from '@stackblitz/sdk'
|
||||
import { dependencies as deps } from '../../../package.json'
|
||||
import { Index as demoIndex } from '../../../../www/__registry__'
|
||||
// @ts-expect-error ?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 { type Style } from '@/lib/registry/styles'
|
||||
import type { Style } from '@/lib/registry/styles'
|
||||
|
||||
export function makeCodeSandboxParams(componentName: string, style: Style, sources: Record<string, string>) {
|
||||
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>) {
|
||||
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))
|
||||
|
||||
return sdk.openProject({
|
||||
title: `${componentName} - Radix Vue`,
|
||||
files,
|
||||
|
|
@ -34,7 +37,15 @@ const viteConfig = {
|
|||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
import tailwind from 'tailwindcss';
|
||||
import autoprefixer from 'autoprefixer';
|
||||
|
||||
export default defineConfig({
|
||||
css: {
|
||||
postcss: {
|
||||
plugins: [tailwind(), autoprefixer()],
|
||||
},
|
||||
},
|
||||
plugins: [vue()],
|
||||
resolve: {
|
||||
alias: {
|
||||
|
|
@ -54,7 +65,7 @@ export default defineConfig({
|
|||
<title>Vite + Vue + TS</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<div vaul-drawer-wrapper id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -90,6 +101,10 @@ function constructFiles(componentName: string, style: Style, sources: Record<str
|
|||
[iconPackage]: 'latest',
|
||||
'shadcn-vue': 'latest',
|
||||
'typescript': 'latest',
|
||||
'vaul-vue': 'latest',
|
||||
'vue-sonner': 'latest',
|
||||
'@unovis/vue': 'latest',
|
||||
'@unovis/ts': 'latest',
|
||||
}
|
||||
|
||||
const devDependencies = {
|
||||
|
|
@ -97,10 +112,10 @@ function constructFiles(componentName: string, style: Style, sources: Record<str
|
|||
'@vitejs/plugin-vue': 'latest',
|
||||
'vue-tsc': 'latest',
|
||||
'tailwindcss': 'latest',
|
||||
'postcss': '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) => {
|
||||
let parsed = code
|
||||
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 files = {
|
||||
|
|
@ -139,15 +154,6 @@ function constructFiles(componentName: string, style: Style, sources: Record<str
|
|||
content: tailwindConfigRaw,
|
||||
isBinary: false,
|
||||
},
|
||||
'postcss.config.js': {
|
||||
content: `module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
}
|
||||
}`,
|
||||
isBinary: false,
|
||||
},
|
||||
'tsconfig.json': {
|
||||
content: `{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
|
|
@ -164,7 +170,6 @@ function constructFiles(componentName: string, style: Style, sources: Record<str
|
|||
isBinary: false,
|
||||
content: `import { type ClassValue, clsx } from 'clsx'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { camelize, getCurrentInstance, toHandlerKey } from 'vue'
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "www",
|
||||
"type": "module",
|
||||
"version": "0.9.0",
|
||||
"version": "0.10.5",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
|
|
@ -12,61 +12,70 @@
|
|||
"typecheck": "vue-tsc",
|
||||
"typecheck:registry": "vue-tsc -p tsconfig.registry.json",
|
||||
"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": {
|
||||
"@formkit/auto-animate": "^0.8.1",
|
||||
"@morev/vue-transitions": "^2.3.6",
|
||||
"@formkit/auto-animate": "^0.8.2",
|
||||
"@internationalized/date": "^3.5.4",
|
||||
"@radix-icons/vue": "^1.0.0",
|
||||
"@stackblitz/sdk": "^1.9.0",
|
||||
"@tanstack/vue-table": "^8.11.8",
|
||||
"@unovis/ts": "^1.3.3",
|
||||
"@unovis/vue": "^1.3.3",
|
||||
"@vee-validate/zod": "^4.12.5",
|
||||
"@vueuse/core": "^10.7.2",
|
||||
"@stackblitz/sdk": "^1.10.0",
|
||||
"@tanstack/vue-table": "^8.17.3",
|
||||
"@unovis/ts": "^1.4.1",
|
||||
"@unovis/vue": "^1.4.1",
|
||||
"@vee-validate/zod": "^4.13.1",
|
||||
"@vueuse/core": "^10.11.0",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.0",
|
||||
"clsx": "^2.1.1",
|
||||
"codesandbox": "^2.2.3",
|
||||
"date-fns": "^2.30.0",
|
||||
"embla-carousel": "^8.0.0-rc22",
|
||||
"embla-carousel-autoplay": "^8.0.0-rc22",
|
||||
"embla-carousel-vue": "^8.0.0-rc22",
|
||||
"lucide-vue-next": "^0.276.0",
|
||||
"radix-vue": "^1.4.1",
|
||||
"date-fns": "^3.6.0",
|
||||
"embla-carousel-autoplay": "^8.1.5",
|
||||
"embla-carousel-vue": "^8.1.5",
|
||||
"lucide-vue-next": "^0.383.0",
|
||||
"magic-string": "^0.30.10",
|
||||
"radix-vue": "^1.8.4",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"v-calendar": "^3.1.2",
|
||||
"vee-validate": "4.12.5",
|
||||
"vue": "^3.4.15",
|
||||
"vue-sonner": "^1.0.3",
|
||||
"vaul-vue": "^0.2.0",
|
||||
"vee-validate": "4.13.1",
|
||||
"vue": "^3.4.29",
|
||||
"vue-sonner": "^1.1.2",
|
||||
"vue-wrap-balancer": "^1.1.3",
|
||||
"zod": "^3.22.4"
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/radix-icons": "^1.1.11",
|
||||
"@iconify-json/tabler": "^1.1.89",
|
||||
"@iconify/json": "^2.2.108",
|
||||
"@iconify/vue": "^4.1.1",
|
||||
"@shikijs/transformers": "^1.0.0-beta.3",
|
||||
"@types/lodash.template": "^4.5.2",
|
||||
"@types/node": "^20.8.10",
|
||||
"@vitejs/plugin-vue": "^5.0.3",
|
||||
"@vitejs/plugin-vue-jsx": "^3.1.0",
|
||||
"@vue/compiler-core": "^3.4.15",
|
||||
"@vue/compiler-dom": "^3.4.15",
|
||||
"@babel/traverse": "^7.24.7",
|
||||
"@iconify-json/gravity-ui": "^1.1.3",
|
||||
"@iconify-json/lucide": "^1.1.190",
|
||||
"@iconify-json/ph": "^1.1.13",
|
||||
"@iconify-json/radix-icons": "^1.1.14",
|
||||
"@iconify-json/ri": "^1.1.20",
|
||||
"@iconify-json/simple-icons": "^1.1.104",
|
||||
"@iconify-json/tabler": "^1.1.113",
|
||||
"@iconify/vue": "^4.1.2",
|
||||
"@oxc-parser/wasm": "^0.14.0",
|
||||
"@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",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"lodash.template": "^4.5.0",
|
||||
"oxc-parser": "^0.2.0",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"fast-glob": "^3.3.2",
|
||||
"lodash-es": "^4.17.21",
|
||||
"markdown-it": "^14.1.0",
|
||||
"pathe": "^1.1.2",
|
||||
"rimraf": "^5.0.5",
|
||||
"shiki": "^1.0.0-beta.3",
|
||||
"tailwind-merge": "^2.2.1",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"tsx": "^4.7.0",
|
||||
"typescript": "^5.3.3",
|
||||
"unplugin-icons": "^0.18.3",
|
||||
"vite": "^5.0.12",
|
||||
"vitepress": "^1.0.0-rc.41",
|
||||
"vue-tsc": "^1.8.27"
|
||||
"rimraf": "^5.0.7",
|
||||
"shiki": "^1.7.0",
|
||||
"tailwind-merge": "^2.3.0",
|
||||
"tailwindcss": "^3.4.4",
|
||||
"tsx": "^4.15.6",
|
||||
"typescript": "^5.4.5",
|
||||
"unplugin-icons": "^0.19.0",
|
||||
"vitepress": "^1.2.3",
|
||||
"vue-component-meta": "^2.0.21",
|
||||
"vue-tsc": "^2.0.21"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
150
apps/www/scripts/autogen.ts
Normal file
150
apps/www/scripts/autogen.ts
Normal 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,
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import fs from 'node:fs'
|
||||
import path, { basename } from 'node:path'
|
||||
import template from 'lodash.template'
|
||||
import { template } from 'lodash-es'
|
||||
import { rimraf } from 'rimraf'
|
||||
|
||||
import { colorMapping, colors } from '../src/lib/registry/colors'
|
||||
|
|
@ -40,7 +40,7 @@ for (const style of styles) {
|
|||
file => `../src/lib/registry/${style.name}/${file}`,
|
||||
)
|
||||
|
||||
const type = item.type.split(':')[1]
|
||||
// const type = item.type.split(':')[1]
|
||||
index += `
|
||||
"${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)) {
|
||||
if (typeof value === 'string') {
|
||||
const resolvedColor = value.replace(
|
||||
/{{base}}-/g,
|
||||
/\{\{base\}\}-/g,
|
||||
`${baseColor}-`,
|
||||
)
|
||||
base.inlineColors[mode][key] = resolvedColor
|
||||
|
|
@ -396,4 +396,4 @@ fs.writeFileSync(
|
|||
'utf8',
|
||||
)
|
||||
|
||||
console.log('✅ Done!')
|
||||
console.log('✅ Done!!')
|
||||
|
|
|
|||
176
apps/www/src/assets/diagrams.drawio
Normal file
176
apps/www/src/assets/diagrams.drawio
Normal 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
10
apps/www/src/components.d.ts
vendored
Normal 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']
|
||||
}
|
||||
}
|
||||
|
|
@ -47,3 +47,5 @@ const { site, theme, page, frontmatter } = useData()
|
|||
## More
|
||||
|
||||
Check out the documentation for the [full list of runtime APIs](https://vitepress.dev/reference/runtime-api#usedata).
|
||||
|
||||
kick
|
||||
|
|
|
|||
9
apps/www/src/content/blocks.md
Normal file
9
apps/www/src/content/blocks.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
title: Building Blocks
|
||||
---
|
||||
|
||||
<script setup>
|
||||
import Blocks from "../../.vitepress/theme/components/Blocks.vue"
|
||||
</script>
|
||||
|
||||
<Blocks />
|
||||
10
apps/www/src/content/blocks/renderer.md
Normal file
10
apps/www/src/content/blocks/renderer.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
title: Blocks - shadcn-vue
|
||||
layout: false
|
||||
---
|
||||
|
||||
<script setup>
|
||||
import BlockPage from "../../../.vitepress/theme/components/BlockPage.vue"
|
||||
</script>
|
||||
|
||||
<BlockPage />
|
||||
|
|
@ -1,3 +1,139 @@
|
|||
---
|
||||
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" />
|
||||
|
|
|
|||
107
apps/www/src/content/docs/charts.md
Normal file
107
apps/www/src/content/docs/charts.md
Normal 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
|
||||
}[]
|
||||
}>()
|
||||
```
|
||||
46
apps/www/src/content/docs/charts/area.md
Normal file
46
apps/www/src/content/docs/charts/area.md
Normal 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" />
|
||||
50
apps/www/src/content/docs/charts/bar.md
Normal file
50
apps/www/src/content/docs/charts/bar.md
Normal 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" />
|
||||
52
apps/www/src/content/docs/charts/donut.md
Normal file
52
apps/www/src/content/docs/charts/donut.md
Normal 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" />
|
||||
46
apps/www/src/content/docs/charts/line.md
Normal file
46
apps/www/src/content/docs/charts/line.md
Normal 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" />
|
||||
|
|
@ -24,7 +24,7 @@ Where is your global CSS file? › › src/index.css
|
|||
Do you want to use CSS variables for colors? › no / yes
|
||||
Where is your tailwind.config.js located? › tailwind.config.js
|
||||
Configure the import alias for components: › @/components
|
||||
Configure the import alias for utils: › @/lib/utils
|
||||
Configure the import alias for utils: › @/lib/utils
|
||||
```
|
||||
|
||||
### Options
|
||||
|
|
@ -101,4 +101,4 @@ Arguments:
|
|||
Options:
|
||||
-c, --cwd <cwd> the working directory. (default: the current directory)
|
||||
-h, --help display help for command
|
||||
```
|
||||
```
|
||||
|
|
|
|||
|
|
@ -89,7 +89,6 @@ This is used to generate the default color palette for your components. **This c
|
|||
}
|
||||
```
|
||||
|
||||
|
||||
### tailwind.cssVariables
|
||||
|
||||
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.
|
||||
|
||||
|
||||
## 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.
|
||||
|
|
@ -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.
|
||||
|
||||
> A fallback to `tsconfig.app.json` if no `paths` were found in `tsconfig.json`
|
||||
|
||||
|
||||
<Callout class="mt-6">
|
||||
|
||||
|
|
@ -126,7 +123,6 @@ Path aliases have to be set up in your `tsconfig.json` or `jsconfig.json` file.
|
|||
|
||||
</Callout>
|
||||
|
||||
|
||||
### aliases.utils
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
6
apps/www/src/content/docs/components.md
Normal file
6
apps/www/src/content/docs/components.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<script setup>
|
||||
import { useRouter } from 'vitepress'
|
||||
|
||||
const router = useRouter()
|
||||
router.go('/docs/components/accordion')
|
||||
</script>
|
||||
|
|
@ -1,15 +1,13 @@
|
|||
---
|
||||
title: Accordion
|
||||
description: A vertically stacked set of interactive headings that each reveal a section of content.
|
||||
source: apps/www/src/lib/registry/default/ui/accordion
|
||||
description: A vertically stacked set of interactive headings that each reveal a section of content.
|
||||
source: apps/www/src/lib/registry/default/ui/accordion
|
||||
primitive: https://www.radix-vue.com/components/accordion.html
|
||||
---
|
||||
|
||||
|
||||
<ComponentPreview name="AccordionDemo" class="sm:max-w-[70%]" />
|
||||
|
||||
## Installation
|
||||
|
||||
|
||||
<Steps>
|
||||
|
||||
|
|
@ -46,9 +44,8 @@ module.exports = {
|
|||
},
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
</Steps>
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
|
|
@ -68,4 +65,3 @@ import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/
|
|||
</Accordion>
|
||||
</template>
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +1,18 @@
|
|||
---
|
||||
title: Alert Dialog
|
||||
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
|
||||
---
|
||||
|
||||
|
||||
<ComponentPreview name="AlertDialogDemo" />
|
||||
|
||||
## Installation
|
||||
|
||||
|
||||
<ComponentPreview name="AlertDialogDemo" />
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npx shadcn-vue@latest add alert-dialog
|
||||
```
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
```vue
|
||||
|
|
@ -51,4 +48,4 @@ import {
|
|||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</template>
|
||||
```
|
||||
```
|
||||
|
|
|
|||
|
|
@ -3,16 +3,14 @@ title: Alert
|
|||
description: Displays a callout for user attention.
|
||||
---
|
||||
|
||||
|
||||
<ComponentPreview name="AlertDemo" />
|
||||
<ComponentPreview name="AlertDemo" />
|
||||
|
||||
## Installation
|
||||
|
||||
|
||||
```bash
|
||||
npx shadcn-vue@latest add alert
|
||||
```
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
```vue
|
||||
|
|
@ -34,11 +32,8 @@ import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
|
|||
|
||||
### Default
|
||||
|
||||
<ComponentPreview name="AlertDemo" />
|
||||
|
||||
<ComponentPreview name="AlertDemo" />
|
||||
|
||||
### Destructive
|
||||
|
||||
<ComponentPreview name="AlertDestructiveDemo" />
|
||||
|
||||
|
||||
<ComponentPreview name="AlertDestructiveDemo" />
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
---
|
||||
title: Aspect 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
|
||||
---
|
||||
|
||||
|
||||
<ComponentPreview name="AspectRatioDemo" />
|
||||
|
||||
## Installation
|
||||
|
|
@ -51,4 +50,4 @@ import { AspectRatio } from '@/components/ui/aspect-ratio'
|
|||
</AspectRatio>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
```
|
||||
|
|
|
|||
548
apps/www/src/content/docs/components/auto-form.md
Normal file
548
apps/www/src/content/docs/components/auto-form.md
Normal 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 Zod’s `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" />
|
||||
|
|
@ -1,20 +1,17 @@
|
|||
---
|
||||
title: Avatar
|
||||
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
|
||||
---
|
||||
|
||||
|
||||
<ComponentPreview name="AvatarDemo" />
|
||||
|
||||
<ComponentPreview name="AvatarDemo" />
|
||||
|
||||
## Installation
|
||||
|
||||
|
||||
```bash
|
||||
npx shadcn-vue@latest add avatar
|
||||
```
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
|
|
@ -29,4 +26,4 @@ import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
|||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
</template>
|
||||
```
|
||||
```
|
||||
|
|
|
|||
|
|
@ -3,8 +3,7 @@ title: Badge
|
|||
description: Displays a badge or a component that looks like a badge.
|
||||
---
|
||||
|
||||
|
||||
<ComponentPreview name="BadgeDemo" />
|
||||
<ComponentPreview name="BadgeDemo" />
|
||||
|
||||
## Installation
|
||||
|
||||
|
|
@ -80,13 +79,11 @@ import { Badge } from '@/components/ui/badge'
|
|||
</template>
|
||||
```
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
### Default
|
||||
|
||||
<ComponentPreview name="BadgeDemo" />
|
||||
|
||||
<ComponentPreview name="BadgeDemo" />
|
||||
|
||||
### Secondary
|
||||
|
||||
|
|
@ -98,4 +95,4 @@ import { Badge } from '@/components/ui/badge'
|
|||
|
||||
### Destructive
|
||||
|
||||
<ComponentPreview name="BadgeDestructiveDemo" />
|
||||
<ComponentPreview name="BadgeDestructiveDemo" />
|
||||
|
|
|
|||
205
apps/www/src/content/docs/components/breadcrumb.md
Normal file
205
apps/www/src/content/docs/components/breadcrumb.md
Normal 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" />
|
||||
|
|
@ -3,8 +3,7 @@ title: Button
|
|||
description: Displays a button or a component that looks like a button.
|
||||
---
|
||||
|
||||
|
||||
<ComponentPreview name="ButtonDemo" />
|
||||
<ComponentPreview name="ButtonDemo" />
|
||||
|
||||
## Installation
|
||||
|
||||
|
|
@ -94,24 +93,20 @@ import { Button } from '@/components/ui/button'
|
|||
</template>
|
||||
```
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
### Primary
|
||||
|
||||
<ComponentPreview name="ButtonDemo" />
|
||||
|
||||
<ComponentPreview name="ButtonDemo" />
|
||||
|
||||
### Secondary
|
||||
|
||||
<ComponentPreview name="ButtonSecondaryDemo" />
|
||||
|
||||
|
||||
### Destructive
|
||||
|
||||
<ComponentPreview name="ButtonDestructiveDemo" />
|
||||
|
||||
|
||||
### Outline
|
||||
|
||||
<ComponentPreview name="ButtonOutlineDemo" />
|
||||
|
|
@ -138,4 +133,4 @@ import { Button } from '@/components/ui/button'
|
|||
|
||||
### As Child
|
||||
|
||||
<ComponentPreview name="ButtonAsChildDemo" />
|
||||
<ComponentPreview name="ButtonAsChildDemo" />
|
||||
|
|
|
|||
|
|
@ -1,95 +1,42 @@
|
|||
---
|
||||
title: Calendar
|
||||
description: A date field component that allows users to enter and edit date.
|
||||
source: apps/www/src/lib/registry/default/ui/calendar
|
||||
primitive: https://vcalendar.io/
|
||||
source: apps/www/src/lib/registry/default/ui/calendar
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
<TabPreview name="CLI">
|
||||
<template #CLI>
|
||||
|
||||
```bash
|
||||
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
|
||||
npm install v-calendar
|
||||
```
|
||||
### Form
|
||||
|
||||
### Copy and paste the following code into your project
|
||||
<ComponentPreview name="CalendarForm" />
|
||||
|
||||
## Advanced Customization
|
||||
|
||||
<<< @/lib/registry/default/ui/calendar/Calendar.vue
|
||||
### Month & Year Selects
|
||||
|
||||
|
||||
</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>
|
||||
```
|
||||
<ComponentPreview name="CalendarWithSelect" />
|
||||
|
|
|
|||
|
|
@ -3,16 +3,13 @@ title: Card
|
|||
description: Displays a card with header, content, and footer.
|
||||
---
|
||||
|
||||
|
||||
<ComponentPreview name="CardFormDemo" />
|
||||
<ComponentPreview name="CardFormDemo" />
|
||||
|
||||
## Installation
|
||||
|
||||
|
||||
|
||||
```bash
|
||||
npx shadcn-vue@latest add card
|
||||
```
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
|
|
@ -46,4 +43,4 @@ import {
|
|||
|
||||
## Examples
|
||||
|
||||
<ComponentPreview name="CardDemo" />
|
||||
<ComponentPreview name="CardDemo" />
|
||||
|
|
|
|||
|
|
@ -1,24 +1,21 @@
|
|||
---
|
||||
title: Carousel
|
||||
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
|
||||
---
|
||||
|
||||
|
||||
<ComponentPreview name="CarouselDemo" />
|
||||
|
||||
<ComponentPreview name="CarouselDemo" />
|
||||
|
||||
## About
|
||||
|
||||
The carousel component is built using the [Embla Carousel](https://www.embla-carousel.com/) library.
|
||||
|
||||
## Installation
|
||||
|
||||
|
||||
```bash
|
||||
npx shadcn-vue@latest add carousel
|
||||
```
|
||||
```
|
||||
|
||||
## 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" />
|
||||
|
||||
|
||||
Example
|
||||
|
||||
```vue:line-numbers title="Example" {4-6}
|
||||
|
|
@ -68,7 +64,6 @@ Example
|
|||
</Carousel>
|
||||
```
|
||||
|
||||
|
||||
Responsive
|
||||
|
||||
```vue:line-numbers title="Responsive" {4-6}
|
||||
|
|
@ -151,6 +146,10 @@ Use the `orientation` prop to set the orientation of the carousel.
|
|||
</Carousel>
|
||||
```
|
||||
|
||||
### Thumbnails
|
||||
|
||||
<ComponentPreview name="CarouselThumbnails" />
|
||||
|
||||
## 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.
|
||||
|
|
@ -259,7 +258,6 @@ You can use the `plugins` prop to add plugins to the carousel.
|
|||
npm i embla-carousel-autoplay
|
||||
```
|
||||
|
||||
|
||||
```vue:line-numbers {2,8-10}
|
||||
<script setup lang="ts">
|
||||
import Autoplay from 'embla-carousel-autoplay'
|
||||
|
|
|
|||
|
|
@ -1,20 +1,17 @@
|
|||
---
|
||||
title: Checkbox
|
||||
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
|
||||
---
|
||||
|
||||
|
||||
<ComponentPreview name="CheckboxDemo" />
|
||||
|
||||
<ComponentPreview name="CheckboxDemo" />
|
||||
|
||||
## Installation
|
||||
|
||||
|
||||
```bash
|
||||
npx shadcn-vue@latest add checkbox
|
||||
```
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
|
|
|
|||
|
|
@ -1,23 +1,21 @@
|
|||
---
|
||||
---
|
||||
title: Collapsible
|
||||
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
|
||||
---
|
||||
|
||||
|
||||
<ComponentPreview name="CollapsibleDemo" />
|
||||
<ComponentPreview name="CollapsibleDemo" />
|
||||
|
||||
## Installation
|
||||
|
||||
|
||||
<Steps>
|
||||
|
||||
### Run the following command
|
||||
|
||||
```bash
|
||||
npx shadcn-vue@latest add collapsible
|
||||
```
|
||||
```
|
||||
|
||||
### Update `tailwind.config.js`
|
||||
|
||||
|
|
@ -46,9 +44,8 @@ module.exports = {
|
|||
},
|
||||
}
|
||||
```
|
||||
|
||||
</Steps>
|
||||
|
||||
</Steps>
|
||||
|
||||
## Usage
|
||||
|
||||
|
|
@ -73,4 +70,4 @@ const isOpen = ref(false)
|
|||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
</template>
|
||||
```
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
---
|
||||
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>
|
||||
<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.
|
||||
|
||||
</Callout>
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
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.
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
```vue
|
||||
|
|
@ -45,13 +44,13 @@ import {
|
|||
const frameworks = [
|
||||
{ value: 'next.js', label: 'Next.js' },
|
||||
{ value: 'sveltekit', label: 'SvelteKit' },
|
||||
{ value: 'nuxt.js', label: 'Nuxt.js' },
|
||||
{ value: 'nuxt', label: 'Nuxt' },
|
||||
{ value: 'remix', label: 'Remix' },
|
||||
{ value: 'astro', label: 'Astro' },
|
||||
]
|
||||
|
||||
const open = ref(false)
|
||||
const value = ref({})
|
||||
const value = ref('')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -110,6 +109,12 @@ const value = ref({})
|
|||
|
||||
<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
|
||||
|
||||
<ComponentPreview name="ComboboxForm" />
|
||||
|
|
|
|||
|
|
@ -1,21 +1,17 @@
|
|||
---
|
||||
title: Command
|
||||
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
|
||||
---
|
||||
|
||||
|
||||
<ComponentPreview name="CommandDemo" />
|
||||
|
||||
|
||||
<ComponentPreview name="CommandDemo" />
|
||||
|
||||
## Installation
|
||||
|
||||
|
||||
```bash
|
||||
npx shadcn-vue@latest add command
|
||||
```
|
||||
```
|
||||
## Usage
|
||||
|
||||
```vue
|
||||
|
|
@ -66,9 +62,9 @@ import {
|
|||
</template>
|
||||
```
|
||||
|
||||
## Examples
|
||||
## Examples
|
||||
|
||||
### Dialog
|
||||
### Dialog
|
||||
|
||||
<ComponentPreview name="CommandDialogDemo" />
|
||||
|
||||
|
|
@ -105,7 +101,7 @@ watch(CmdJ, (v) => {
|
|||
<span class="text-xs">⌘</span>J
|
||||
</kbd>
|
||||
</p>
|
||||
<CommandDialog :open="open" :on-open-change="handleOpenChange">
|
||||
<CommandDialog :open="open" @update:open="handleOpenChange">
|
||||
<CommandInput placeholder="Type a command or search..." />
|
||||
<CommandList>
|
||||
<CommandEmpty>No results found.</CommandEmpty>
|
||||
|
|
@ -140,4 +136,4 @@ watch(CmdJ, (v) => {
|
|||
|
||||
### 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.
|
||||
|
|
|
|||
|
|
@ -1,18 +1,17 @@
|
|||
---
|
||||
---
|
||||
title: Context Menu
|
||||
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
|
||||
---
|
||||
|
||||
|
||||
<ComponentPreview name="ContextMenuDemo" />
|
||||
<ComponentPreview name="ContextMenuDemo" />
|
||||
|
||||
## Installation
|
||||
|
||||
|
||||
```bash
|
||||
npx shadcn-vue@latest add context-menu
|
||||
```
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
|
|
@ -46,4 +45,4 @@ import {
|
|||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
</template>
|
||||
```
|
||||
```
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ description: Powerful table and datagrids built using TanStack Table.
|
|||
primitive: https://tanstack.com/table/v8/docs/guide/introduction
|
||||
---
|
||||
|
||||
|
||||
<ComponentPreview name="DataTableDemo" />
|
||||
|
||||
## Introduction
|
||||
|
|
@ -56,7 +55,6 @@ npm install @tanstack/vue-table
|
|||
|
||||
<ComponentPreview name="DataTableColumnPinningDemo" />
|
||||
|
||||
|
||||
## Prerequisites
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
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.
|
||||
- `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.
|
||||
|
||||
```ts:line-numbers {1,12-27}
|
||||
```ts:line-numbers
|
||||
import { h } from 'vue'
|
||||
|
||||
export const columns: ColumnDef<Payment>[] = [
|
||||
|
|
@ -148,66 +146,70 @@ formatted, sorted and filtered.
|
|||
|
||||
Next, we'll create a `<DataTable />` component to render our table.
|
||||
|
||||
```vue:line-numbers
|
||||
```vue
|
||||
<script setup lang="ts" generic="TData, TValue">
|
||||
import type { ColumnDef } from '@tanstack/vue-table'
|
||||
import {
|
||||
FlexRender,
|
||||
getCoreRowModel,
|
||||
useVueTable,
|
||||
} from "@tanstack/vue-table"
|
||||
FlexRender,
|
||||
getCoreRowModel,
|
||||
useVueTable,
|
||||
} from '@tanstack/vue-table'
|
||||
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table"
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table'
|
||||
|
||||
const props = defineProps<{
|
||||
columns: ColumnDef<TData, TValue>[]
|
||||
data: TData[]
|
||||
columns: ColumnDef<TData, TValue>[]
|
||||
data: TData[]
|
||||
}>()
|
||||
|
||||
const table = useVueTable({
|
||||
get data() { return props.data },
|
||||
get columns() { return props.columns },
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
get data() { return props.data },
|
||||
get columns() { return props.columns },
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="border rounded-md">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow v-for="headerGroup in table.getHeaderGroups()" :key="headerGroup.id">
|
||||
<TableHead v-for="header in headerGroup.headers" :key="header.id">
|
||||
<FlexRender v-if="!header.isPlaceholder" :render="header.column.columnDef.header"
|
||||
:props="header.getContext()" />
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<template v-if="table.getRowModel().rows?.length">
|
||||
<TableRow v-for="row in table.getRowModel().rows" :key="row.id"
|
||||
:data-state="row.getIsSelected() ? 'selected' : undefined">
|
||||
<TableCell v-for="cell in row.getVisibleCells()" :key="cell.id">
|
||||
<FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</template>
|
||||
<template v-else>
|
||||
<TableRow>
|
||||
<TableCell :colSpan="columns.length" class="h-24 text-center">
|
||||
No results.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</template>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
<div class="border rounded-md">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow v-for="headerGroup in table.getHeaderGroups()" :key="headerGroup.id">
|
||||
<TableHead v-for="header in headerGroup.headers" :key="header.id">
|
||||
<FlexRender
|
||||
v-if="!header.isPlaceholder" :render="header.column.columnDef.header"
|
||||
:props="header.getContext()"
|
||||
/>
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<template v-if="table.getRowModel().rows?.length">
|
||||
<TableRow
|
||||
v-for="row in table.getRowModel().rows" :key="row.id"
|
||||
:data-state="row.getIsSelected() ? 'selected' : undefined"
|
||||
>
|
||||
<TableCell v-for="cell in row.getVisibleCells()" :key="cell.id">
|
||||
<FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</template>
|
||||
<template v-else>
|
||||
<TableRow>
|
||||
<TableCell :colspan="columns.length" class="h-24 text-center">
|
||||
No results.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</template>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
|
|
@ -219,17 +221,16 @@ const table = useVueTable({
|
|||
|
||||
</Callout>
|
||||
|
||||
|
||||
### Render the table
|
||||
|
||||
Finally, we'll render our table in our index component.
|
||||
|
||||
```vue:line-numbers {28}
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { columns } from "./components/columns"
|
||||
import type { Payment } from './components/columns';
|
||||
import DataTable from "./components/DataTable.vue"
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { columns } from './components/columns'
|
||||
import type { Payment } from './components/columns'
|
||||
import DataTable from './components/DataTable.vue'
|
||||
|
||||
const data = ref<Payment[]>([])
|
||||
|
||||
|
|
@ -237,18 +238,18 @@ async function getData(): Promise<Payment[]> {
|
|||
// Fetch data from your API here.
|
||||
return [
|
||||
{
|
||||
id: "728ed52f",
|
||||
id: '728ed52f',
|
||||
amount: 100,
|
||||
status: "pending",
|
||||
email: "m@example.com",
|
||||
status: 'pending',
|
||||
email: 'm@example.com',
|
||||
},
|
||||
// ...
|
||||
]
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
data.value = await getData();
|
||||
});
|
||||
data.value = await getData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<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:
|
||||
|
||||
|
||||
```ts:line-numbers title="components/payments/columns.ts" {5-17}
|
||||
```ts
|
||||
import { h } from 'vue'
|
||||
|
||||
export const columns: ColumnDef<Payment>[] = [
|
||||
{
|
||||
accessorKey: "amount",
|
||||
header: () => h('div', { class: 'text-right' }, 'Amount'),
|
||||
cell: ({ row }) => {
|
||||
const amount = parseFloat(row.getValue("amount"))
|
||||
const formatted = new Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
}).format(amount)
|
||||
{
|
||||
accessorKey: 'amount',
|
||||
header: () => h('div', { class: 'text-right' }, 'Amount'),
|
||||
cell: ({ row }) => {
|
||||
const amount = Number.parseFloat(row.getValue('amount'))
|
||||
const formatted = new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
}).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.
|
||||
|
|
@ -299,10 +299,9 @@ Let's add row actions to our table. We'll use a `<Dropdown />` component for thi
|
|||
|
||||
<Steps>
|
||||
|
||||
### Add the following into your `DataTableDropDown.vue` component:
|
||||
### Add the following into your `DataTableDropDown.vue` component
|
||||
|
||||
```vue:line-numbers
|
||||
// DataTableDropDown.vue
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { MoreHorizontal } from 'lucide-vue-next'
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'
|
||||
|
|
@ -338,33 +337,30 @@ function copy(id: string) {
|
|||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</template>
|
||||
|
||||
```
|
||||
|
||||
### Update columns definition
|
||||
|
||||
Update our columns definition to add a new `actions` column. The `actions` cell returns a `<Dropdown />` component.
|
||||
|
||||
|
||||
```ts:line-numbers showLineNumber{2,6-16}
|
||||
import { ColumnDef } from "@tanstack/vue-table"
|
||||
```ts
|
||||
import { ColumnDef } from '@tanstack/vue-table'
|
||||
import DropdownAction from '@/components/DataTableDropDown.vue'
|
||||
|
||||
export const columns: ColumnDef<Payment>[] = [
|
||||
// ...
|
||||
{
|
||||
id: 'actions',
|
||||
enableHiding: false,
|
||||
cell: ({ row }) => {
|
||||
const payment = row.original
|
||||
{
|
||||
id: 'actions',
|
||||
enableHiding: false,
|
||||
cell: ({ row }) => {
|
||||
const payment = row.original
|
||||
|
||||
return h('div', { class: 'relative' }, h(DropdownAction, {
|
||||
payment,
|
||||
}))
|
||||
},
|
||||
return h('div', { class: 'relative' }, h(DropdownAction, {
|
||||
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.
|
||||
|
|
@ -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.
|
||||
|
||||
```vue:line-numbers {3,15,21-39}
|
||||
```vue
|
||||
<script lang="ts" generic="TData, TValue">
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
||||
const table = useVueTable({
|
||||
get data() { return props.data },
|
||||
get columns() { return props.columns },
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
get data() { return props.data },
|
||||
get columns() { return props.columns },
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="border rounded-md">
|
||||
<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 class="border rounded-md">
|
||||
<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>
|
||||
</template>
|
||||
|
||||
```
|
||||
|
||||
See [Reusable Components](#reusable-components) section for a more advanced pagination component.
|
||||
|
|
@ -454,25 +448,23 @@ Let's make the email column sortable.
|
|||
|
||||
<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 { twMerge } from 'tailwind-merge'
|
||||
import { camelize, getCurrentInstance, toHandlerKey } from 'vue'
|
||||
|
||||
import type { Updater } from '@tanstack/vue-table'
|
||||
import { type Ref } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
||||
export function valueUpdater<T extends Updater<any>>(updaterOrValue: T, ref: Ref) {
|
||||
ref.value
|
||||
= typeof updaterOrValue === 'function'
|
||||
? updaterOrValue(ref.value)
|
||||
: updaterOrValue
|
||||
ref.value = typeof updaterOrValue === 'function'
|
||||
? updaterOrValue(ref.value)
|
||||
: updaterOrValue
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -480,62 +472,60 @@ The `valueUpdater` function updates a Vue `ref` object's value. It handles both
|
|||
|
||||
### 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">
|
||||
import type {
|
||||
ColumnDef,
|
||||
SortingState,
|
||||
} from '@tanstack/vue-table'
|
||||
|
||||
import { valueUpdater } from '@/lib/utils'
|
||||
|
||||
import { ArrowUpDown, ChevronDown } from 'lucide-vue-next'
|
||||
import { h, ref } from 'vue'
|
||||
|
||||
import {
|
||||
FlexRender,
|
||||
getCoreRowModel,
|
||||
getPaginationRowModel,
|
||||
getSortedRowModel,
|
||||
useVueTable,
|
||||
} from "@tanstack/vue-table"
|
||||
FlexRender,
|
||||
getCoreRowModel,
|
||||
getPaginationRowModel,
|
||||
getSortedRowModel,
|
||||
useVueTable,
|
||||
} from '@tanstack/vue-table'
|
||||
import { valueUpdater } from '@/lib/utils'
|
||||
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table"
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table'
|
||||
|
||||
const props = defineProps<{
|
||||
columns: ColumnDef<TData, TValue>[]
|
||||
data: TData[]
|
||||
columns: ColumnDef<TData, TValue>[]
|
||||
data: TData[]
|
||||
}>()
|
||||
|
||||
const sorting = ref<SortingState>([])
|
||||
|
||||
const table = useVueTable({
|
||||
get data() { return props.data },
|
||||
get columns() { return props.columns },
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting),
|
||||
state: {
|
||||
get sorting() { return sorting.value },
|
||||
},
|
||||
get data() { return props.data },
|
||||
get columns() { return props.columns },
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting),
|
||||
state: {
|
||||
get sorting() { return sorting.value },
|
||||
},
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="border rounded-md">
|
||||
<Table>{ ... }</Table>
|
||||
</div>
|
||||
<div>
|
||||
<div class="border rounded-md">
|
||||
<Table>{ ... }</Table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
|
|
@ -971,11 +961,9 @@ export const columns = [
|
|||
{
|
||||
accessorKey: "email",
|
||||
header: ({ column }) => (
|
||||
return h(DataTableColumnHeader, {
|
||||
props: {
|
||||
column: column,
|
||||
title: 'Email'
|
||||
}
|
||||
h(DataTableColumnHeader, {
|
||||
column: column,
|
||||
title: 'Email'
|
||||
})
|
||||
),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,81 +1,42 @@
|
|||
---
|
||||
title: Date Picker
|
||||
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
|
||||
|
||||
The Date Picker is built using a composition of the `<Popover />` and the `<Calendar />` 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>
|
||||
```
|
||||
The Date Picker is built using a composition of the `<Popover />` and either the `<Calendar />` or `<RangeCalendar />` components.
|
||||
|
||||
See installations instructions for the [Popover](/docs/components/popover), [Calendar](/docs/components/calendar), and [Range Calendar](/docs/components/range-calendar) components.
|
||||
|
||||
## Examples
|
||||
|
||||
### Date Picker
|
||||
|
||||
<ComponentPreview name="DatePickerDemo" />
|
||||
<ComponentPreview name="DatePickerDemo" />
|
||||
|
||||
### 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
|
||||
|
||||
<ComponentPreview name="DatePickerWithPresets" />
|
||||
|
||||
### With Slot
|
||||
|
||||
<ComponentPreview name="RangePickerWithSlot" />
|
||||
<ComponentPreview name="DatePickerWithPresets" />
|
||||
|
||||
### Form
|
||||
|
||||
<ComponentPreview name="DatePickerForm" />
|
||||
<ComponentPreview name="DatePickerForm" />
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
---
|
||||
title: Dialog
|
||||
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
|
||||
---
|
||||
|
||||
<ComponentPreview name="DialogDemo" />
|
||||
|
||||
<ComponentPreview name="DialogDemo" />
|
||||
|
||||
## Installation
|
||||
```bash
|
||||
npx shadcn-vue@latest add dialog
|
||||
|
|
@ -49,17 +48,24 @@ import {
|
|||
</template>
|
||||
```
|
||||
|
||||
## Examples
|
||||
## Examples
|
||||
|
||||
### Custom close button
|
||||
|
||||
<ComponentPreview name="DialogCustomCloseButton" />
|
||||
<ComponentPreview name="DialogCustomCloseButton" />
|
||||
|
||||
### Scroll body
|
||||
|
||||
<ComponentPreview name="DialogScrollBodyDemo" />
|
||||
|
||||
### Scroll overlay
|
||||
|
||||
<ComponentPreview name="DialogScrollOverlayDemo" />
|
||||
|
||||
## 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).
|
||||
|
||||
|
||||
```js:line-numbers showLineNumber{14-25}
|
||||
<Dialog>
|
||||
<ContextMenu>
|
||||
|
|
|
|||
70
apps/www/src/content/docs/components/drawer.md
Normal file
70
apps/www/src/content/docs/components/drawer.md
Normal 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" />
|
||||
|
|
@ -1,18 +1,17 @@
|
|||
---
|
||||
title: Dropdown Menu
|
||||
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
|
||||
---
|
||||
|
||||
|
||||
<ComponentPreview name="DropdownMenuDemo" />
|
||||
<ComponentPreview name="DropdownMenuDemo" />
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npx shadcn-vue@latest add dropdown-menu
|
||||
```
|
||||
```
|
||||
## Usage
|
||||
|
||||
```vue
|
||||
|
|
@ -40,4 +39,14 @@ import {
|
|||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</template>
|
||||
```
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Checkboxes
|
||||
|
||||
<ComponentPreview name="DropdownMenuCheckboxes" />
|
||||
|
||||
### Radio Group
|
||||
|
||||
<ComponentPreview name="DropdownMenuRadioGroup" />
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
The `<Form />` component is a wrapper around the `vee-validate` library. It provides a few things:
|
||||
|
||||
|
||||
- Composable components for building forms.
|
||||
- A `<FormField />` component for building controlled form fields.
|
||||
- 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.
|
||||
- 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.**
|
||||
|
|
@ -53,7 +51,6 @@ The `<Form />` component is a wrapper around the `vee-validate` library. It prov
|
|||
|
||||
## Example
|
||||
|
||||
|
||||
<TabPreview name="Component" :names="['Component', 'Native']">
|
||||
<template #Component>
|
||||
|
||||
|
|
@ -170,12 +167,10 @@ const formSchema = toTypedSchema(z.object({
|
|||
</script>
|
||||
```
|
||||
|
||||
|
||||
### Define a form
|
||||
|
||||
Use the `useForm` composable from `vee-validate` or use `<Form />` component to create a form.
|
||||
|
||||
|
||||
<TabPreview name="Composition" :names="['Composition', 'Component']">
|
||||
<template #Composition>
|
||||
|
||||
|
|
@ -254,7 +249,7 @@ function onSubmit(values) {
|
|||
### Build your form
|
||||
|
||||
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}
|
||||
<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)
|
||||
- [Radio Group](/docs/components/radio-group#form)
|
||||
- [Select](/docs/components/select#form)
|
||||
- [Slider](/docs/components/slider#form)
|
||||
- [Switch](/docs/components/switch#form)
|
||||
- [Textarea](/docs/components/textarea#form)
|
||||
- [Combobox](/docs/components/combobox#form)
|
||||
|
||||
|
||||
## Extras
|
||||
|
||||
This example shows how to add motion to your forms with [Formkit AutoAnimate](https://auto-animate.formkit.com/)
|
||||
|
|
|
|||
|
|
@ -1,18 +1,17 @@
|
|||
---
|
||||
title: Hover Card
|
||||
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
|
||||
---
|
||||
|
||||
|
||||
<ComponentPreview name="HoverCardDemo" />
|
||||
<ComponentPreview name="HoverCardDemo" />
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npx shadcn-vue@latest add hover-card
|
||||
```
|
||||
```
|
||||
## Usage
|
||||
|
||||
```vue
|
||||
|
|
@ -32,4 +31,4 @@ import {
|
|||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
</template>
|
||||
```
|
||||
```
|
||||
|
|
|
|||
|
|
@ -3,8 +3,7 @@ title: Input
|
|||
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
|
||||
|
||||
|
|
@ -26,8 +25,6 @@ npx shadcn-vue@latest add input
|
|||
|
||||
</Steps>
|
||||
|
||||
|
||||
|
||||
</template>
|
||||
</TabPreview>
|
||||
|
||||
|
|
@ -43,6 +40,8 @@ import { Input } from '@/components/ui/input'
|
|||
</template>
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Default
|
||||
|
||||
<ComponentPreview name="InputDemo" class="max-w-xs" />
|
||||
|
|
@ -63,6 +62,10 @@ import { Input } from '@/components/ui/input'
|
|||
|
||||
<ComponentPreview name="InputWithButton" class="max-w-xs" />
|
||||
|
||||
### With Icon
|
||||
|
||||
<ComponentPreview name="InputWithIcon" class="max-w-xs" />
|
||||
|
||||
### Form
|
||||
|
||||
<ComponentPreview name="InputForm" />
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user