Compare commits
151 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47f53cb985 | ||
| 1e1f335853 | |||
| 32d6b8b397 | |||
|
|
3eaef4a748 | ||
|
|
5b1f952e0a | ||
|
|
693b0d2a08 | ||
|
|
8a24d11a65 | ||
|
|
e4fea78b33 | ||
|
|
5869165a84 | ||
|
|
857f10de51 | ||
|
|
5ada562803 | ||
|
|
6c0ab55e92 | ||
|
|
3ddd70dd6b | ||
|
|
2d5ad5b962 | ||
|
|
16f1d19460 | ||
|
|
ac69980ac9 | ||
|
|
58fc125974 | ||
|
|
383c846c02 | ||
|
|
b7ef4653f7 | ||
|
|
384c87a91c | ||
|
|
75a5bce92f | ||
|
|
3d0db2de7b | ||
|
|
a5b25a9c43 | ||
|
|
be888c80b6 | ||
|
|
93be758257 | ||
|
|
26df827d33 | ||
|
|
f267b0ba4a | ||
|
|
3c506ef188 | ||
|
|
df60e15a97 | ||
|
|
3646203d4f | ||
|
|
68c40f6908 | ||
|
|
ff1b5f0a1b | ||
|
|
15f3eb305b | ||
|
|
baceb4ce91 | ||
|
|
2cb767122a | ||
|
|
eff3e75466 | ||
|
|
83419c4dc3 | ||
|
|
52b9b20b3c | ||
|
|
e0d4980e31 | ||
|
|
6760ebb5ae | ||
|
|
d143272fb8 | ||
|
|
4f3bb61283 | ||
|
|
f9615d3657 | ||
|
|
d48ffcfffb | ||
|
|
e63d5553e3 | ||
|
|
a01a1bd94d | ||
|
|
f5b02256bc | ||
|
|
c164421e58 | ||
|
|
5c71911104 | ||
|
|
982f918e53 | ||
|
|
f8f3fc754f | ||
|
|
1c7c60330a | ||
|
|
ff6d9d0da6 | ||
|
|
603497e822 | ||
|
|
235ec8a691 | ||
|
|
ec350df9d4 | ||
|
|
64b6d6ac38 | ||
|
|
775d59b51d | ||
|
|
7c9aac46a1 | ||
|
|
ccbdc2f90f | ||
|
|
573443f352 | ||
|
|
481bebf413 | ||
|
|
cc84ac172c | ||
|
|
501137a672 | ||
|
|
f73e1ddaaf | ||
|
|
85b10641c2 | ||
|
|
68d9804109 | ||
|
|
1a1dd4a611 | ||
|
|
0fc50183e4 | ||
|
|
2a50f65ec5 | ||
|
|
3dc6e10897 | ||
|
|
12657dab5c | ||
|
|
bdcd555c84 | ||
|
|
514949706f | ||
|
|
0d5d2d0d10 | ||
|
|
6ac90c10ab | ||
|
|
5708af535a | ||
|
|
b2952b6416 | ||
|
|
0ee23fb53d | ||
|
|
dbb29de523 | ||
|
|
183da0890d | ||
|
|
bec12b1653 | ||
|
|
45557fd8e7 | ||
|
|
6757908fe1 | ||
|
|
6550f34842 | ||
|
|
4d4804231b | ||
|
|
edad815fd9 | ||
|
|
9ddbd5c0d2 | ||
|
|
7a03a4caab | ||
|
|
b371e5f3ad | ||
|
|
d50ea38f4a | ||
|
|
f03778304d | ||
|
|
0f1befad1f | ||
|
|
cdfbd51190 | ||
|
|
f597d258b0 | ||
|
|
eeca60d09b | ||
|
|
6aebbc1a90 | ||
|
|
2e93d15af4 | ||
|
|
f1a2dab738 | ||
|
|
63732f1c47 | ||
|
|
9d303e91ca | ||
|
|
edc4ee9437 | ||
|
|
36ad0f846d | ||
|
|
88a93ce22e | ||
|
|
8cbd55660f | ||
|
|
ae43e5e08a | ||
|
|
a1faecc7d2 | ||
|
|
9960925881 | ||
|
|
ce6eb79a3d | ||
|
|
f48d1d4f4c | ||
|
|
47ff130ffb | ||
|
|
e54e06bb98 | ||
|
|
15f630552f | ||
|
|
80f06066c6 | ||
|
|
6a6968b8ee | ||
|
|
ebe3e6d8d2 | ||
|
|
bd0a8206ef | ||
|
|
63bb21808f | ||
|
|
e63eec02e4 | ||
|
|
dce0bd0c30 | ||
|
|
af5d0b323d | ||
|
|
1aa9b6172e | ||
|
|
18c64563a1 | ||
|
|
175762a959 | ||
|
|
61dcd63ef2 | ||
|
|
61ed5f1758 | ||
|
|
5eb41b0ba9 | ||
|
|
7f73e4d74b | ||
|
|
ead6ad8437 | ||
|
|
3e5dd97fc5 | ||
|
|
cc3503689f | ||
|
|
08e10236fc | ||
|
|
7fdf916443 | ||
|
|
b20a45552a | ||
|
|
0721d1c864 | ||
|
|
60e12b962f | ||
|
|
bcbfab5c9b | ||
|
|
631ffb81d5 | ||
|
|
3dd2fbda95 | ||
|
|
9ebbd30175 | ||
|
|
d17bb2bb2a | ||
|
|
10319df960 | ||
|
|
e67c98b485 | ||
|
|
9dfb0b86c0 | ||
|
|
b468704853 | ||
|
|
db3fff29c8 | ||
|
|
77c6a16040 | ||
|
|
32d7b9ca4a | ||
|
|
18e40cf002 | ||
|
|
f6bc669106 | ||
|
|
d21fa783e9 |
12
.editorconfig
Normal file
12
.editorconfig
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# EditorConfig is awesome: https://EditorConfig.org
|
||||||
|
|
||||||
|
# top-most EditorConfig file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
17
.gitea/actions/setup/action.yml
Normal file
17
.gitea/actions/setup/action.yml
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
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
|
||||||
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
|
@ -48,32 +48,10 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
# Run a build step here
|
- name: Setup (Install Node & pnpm)
|
||||||
- name: Setup Node.js environment
|
uses: ./.github/actions/setup
|
||||||
uses: actions/setup-node@v2
|
|
||||||
with:
|
|
||||||
node-version: 18
|
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v2
|
|
||||||
name: Install pnpm
|
|
||||||
with:
|
|
||||||
version: 9.0.5
|
|
||||||
run_install: false
|
|
||||||
|
|
||||||
- name: Get pnpm store directory
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
|
||||||
name: Setup pnpm cache
|
|
||||||
with:
|
|
||||||
path: ${{ env.STORE_PATH }}
|
|
||||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-pnpm-store-
|
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm i --frozen-lockfile
|
run: pnpm i --frozen-lockfile
|
||||||
39
.gitea/workflows/release.yaml
Normal file
39
.gitea/workflows/release.yaml
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
# .github/workflows/release.yml
|
||||||
|
|
||||||
|
name: Release
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup (Install Node & pnpm)
|
||||||
|
uses: ./.github/actions/setup
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm i --frozen-lockfile
|
||||||
|
|
||||||
|
- run: pnpm dlx changelogithub
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||||
|
|
||||||
|
- name: Build CLI & Publish to npm
|
||||||
|
run: pnpm --filter shadcn-vue pub:release
|
||||||
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build Module & Publish to npm
|
||||||
|
run: pnpm --filter shadcn-nuxt pub:release
|
||||||
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
31
.gitea/workflows/test.yaml
Normal file
31
.gitea/workflows/test.yaml
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
name: Test
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- dev
|
||||||
|
paths:
|
||||||
|
- 'packages/**'
|
||||||
|
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- dev
|
||||||
|
paths:
|
||||||
|
- 'packages/**'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup (Install Node & pnpm)
|
||||||
|
uses: ./.github/actions/setup
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm i --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: pnpm test
|
||||||
27
.github/workflows/release.yaml
vendored
27
.github/workflows/release.yaml
vendored
|
|
@ -1,27 +0,0 @@
|
||||||
# .github/workflows/release.yml
|
|
||||||
|
|
||||||
name: Release
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- 'v*'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
release:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: 18.x
|
|
||||||
|
|
||||||
- run: npx changelogithub
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
|
||||||
52
.github/workflows/test.yaml
vendored
52
.github/workflows/test.yaml
vendored
|
|
@ -1,52 +0,0 @@
|
||||||
name: Test
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- dev
|
|
||||||
paths:
|
|
||||||
- 'packages/**'
|
|
||||||
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- dev
|
|
||||||
paths:
|
|
||||||
- 'packages/**'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Setup Node.js environment
|
|
||||||
uses: actions/setup-node@v2
|
|
||||||
with:
|
|
||||||
node-version: 16
|
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v2
|
|
||||||
name: Install pnpm
|
|
||||||
with:
|
|
||||||
version: 9.0.5
|
|
||||||
run_install: false
|
|
||||||
|
|
||||||
- name: Get pnpm store directory
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
|
||||||
name: Setup pnpm cache
|
|
||||||
with:
|
|
||||||
path: ${{ env.STORE_PATH }}
|
|
||||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-pnpm-store-
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm i --frozen-lockfile
|
|
||||||
|
|
||||||
- name: Test
|
|
||||||
run: pnpm test
|
|
||||||
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"recommendations": [
|
"recommendations": [
|
||||||
"Vue.volar",
|
"Vue.volar",
|
||||||
"dbaeumer.vscode-eslint"
|
"dbaeumer.vscode-eslint",
|
||||||
|
"bradlc.vscode-tailwindcss"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
9
.vscode/settings.json
vendored
9
.vscode/settings.json
vendored
|
|
@ -1,11 +1,16 @@
|
||||||
{
|
{
|
||||||
"vue.server.hybridMode": true,
|
"vue.server.hybridMode": true,
|
||||||
|
"vue.server.includeLanguages": [
|
||||||
|
"vue",
|
||||||
|
"markdown"
|
||||||
|
],
|
||||||
"prettier.enable": false,
|
"prettier.enable": false,
|
||||||
"editor.formatOnSave": false,
|
"editor.formatOnSave": false,
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.eslint": "explicit",
|
"source.fixAll.eslint": "explicit",
|
||||||
"source.organizeImports": "never"
|
"source.organizeImports": "never"
|
||||||
},
|
},
|
||||||
|
"eslint.useFlatConfig": true,
|
||||||
"eslint.rules.customizations": [
|
"eslint.rules.customizations": [
|
||||||
{ "rule": "style/*", "severity": "off" },
|
{ "rule": "style/*", "severity": "off" },
|
||||||
{ "rule": "format/*", "severity": "off" },
|
{ "rule": "format/*", "severity": "off" },
|
||||||
|
|
@ -29,5 +34,9 @@
|
||||||
"json",
|
"json",
|
||||||
"jsonc",
|
"jsonc",
|
||||||
"yaml"
|
"yaml"
|
||||||
|
],
|
||||||
|
"tailwindCSS.experimental.classRegex": [
|
||||||
|
["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
|
||||||
|
["cn\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img align="center" src="https://raw.githubusercontent.com/radix-vue/shadcn-vue/dev/apps/www/src/public/android-chrome-192x192.png" height="96" />
|
<img align="center" src="https://raw.githubusercontent.com/radix-vue/shadcn-vue/dev/apps/www/src/public/android-chrome-192x192.png" height="96" />
|
||||||
<h1 align="center">
|
<h1 align="center">
|
||||||
shadcn-vue
|
shadcn-vue by Niklas Hermanns
|
||||||
</h1>
|
</h1>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
@ -31,3 +31,7 @@ All credits go to these open-source works and resources
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Licensed under the [MIT license](https://github.com/shadcn/ui/blob/main/LICENSE.md).
|
Licensed under the [MIT license](https://github.com/shadcn/ui/blob/main/LICENSE.md).
|
||||||
|
|
||||||
|
## Actions
|
||||||
|
|
||||||
|
- Test
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import { defineConfig } from 'vitepress'
|
import { transformerMetaWordHighlight } from '@shikijs/transformers'
|
||||||
import Icons from 'unplugin-icons/vite'
|
|
||||||
import tailwind from 'tailwindcss'
|
|
||||||
import autoprefixer from 'autoprefixer'
|
import autoprefixer from 'autoprefixer'
|
||||||
import { cssVariables } from './theme/config/shiki'
|
import tailwind from 'tailwindcss'
|
||||||
|
import Icons from 'unplugin-icons/vite'
|
||||||
|
import { defineConfig } from 'vitepress'
|
||||||
|
|
||||||
// import { transformerMetaWordHighlight, transformerNotationWordHighlight } from '@shikijs/transformers'
|
|
||||||
import { siteConfig } from './theme/config/site'
|
import { siteConfig } from './theme/config/site'
|
||||||
import ComponentPreviewPlugin from './theme/plugins/previewer'
|
|
||||||
import CodeWrapperPlugin from './theme/plugins/codewrapper'
|
import CodeWrapperPlugin from './theme/plugins/codewrapper'
|
||||||
|
import ComponentPreviewPlugin from './theme/plugins/previewer'
|
||||||
|
|
||||||
// https://vitepress.dev/reference/site-config
|
// https://vitepress.dev/reference/site-config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
|
@ -31,7 +30,6 @@ export default defineConfig({
|
||||||
['meta', { name: 'og:site_name', content: siteConfig.name }],
|
['meta', { name: 'og:site_name', content: siteConfig.name }],
|
||||||
['meta', { name: 'og:image', content: siteConfig.ogImage }],
|
['meta', { name: 'og:image', content: siteConfig.ogImage }],
|
||||||
['meta', { name: 'twitter:image', content: siteConfig.ogImage }],
|
['meta', { name: 'twitter:image', content: siteConfig.ogImage }],
|
||||||
|
|
||||||
],
|
],
|
||||||
|
|
||||||
sitemap: {
|
sitemap: {
|
||||||
|
|
@ -50,14 +48,16 @@ export default defineConfig({
|
||||||
pattern: 'https://github.com/radix-vue/shadcn-vue/tree/dev/apps/www/src/:path',
|
pattern: 'https://github.com/radix-vue/shadcn-vue/tree/dev/apps/www/src/:path',
|
||||||
text: 'Edit this page on GitHub',
|
text: 'Edit this page on GitHub',
|
||||||
},
|
},
|
||||||
|
carbonAds: {
|
||||||
|
code: 'CW7DK27U',
|
||||||
|
placement: 'wwwshadcn-vuecom',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
srcDir: path.resolve(__dirname, '../src'),
|
srcDir: path.resolve(__dirname, '../src'),
|
||||||
markdown: {
|
markdown: {
|
||||||
theme: cssVariables,
|
|
||||||
codeTransformers: [
|
codeTransformers: [
|
||||||
// transformerMetaWordHighlight(),
|
transformerMetaWordHighlight(),
|
||||||
// transformerNotationWordHighlight(),
|
|
||||||
],
|
],
|
||||||
config(md) {
|
config(md) {
|
||||||
md.use(ComponentPreviewPlugin)
|
md.use(ComponentPreviewPlugin)
|
||||||
|
|
@ -77,7 +77,7 @@ export default defineConfig({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
Icons({ compiler: 'vue3', autoInstall: true }),
|
Icons({ compiler: 'vue3', autoInstall: true }) as any,
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
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>
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { announcementConfig } from '../config/site'
|
|
||||||
import { Separator } from '@/lib/registry/default/ui/separator'
|
import { Separator } from '@/lib/registry/default/ui/separator'
|
||||||
import ArrowRightIcon from '~icons/radix-icons/arrow-right'
|
import ArrowRightIcon from '~icons/radix-icons/arrow-right'
|
||||||
|
import { announcementConfig } from '../config/site'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
233
apps/www/.vitepress/theme/components/BlockContainer.vue
Normal file
233
apps/www/.vitepress/theme/components/BlockContainer.vue
Normal file
|
|
@ -0,0 +1,233 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useConfigStore } from '@/stores/config'
|
||||||
|
import { CircleHelp, Info, Monitor, Smartphone, Tablet } from 'lucide-vue-next'
|
||||||
|
import MagicString from 'magic-string'
|
||||||
|
import { reactive, ref, watch } from 'vue'
|
||||||
|
import { compileScript, parse, walk } from 'vue/compiler-sfc'
|
||||||
|
import { highlight } from '../config/shiki'
|
||||||
|
import BlockCopyButton from './BlockCopyButton.vue'
|
||||||
|
import StyleSwitcher from './StyleSwitcher.vue'
|
||||||
|
|
||||||
|
// 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'
|
||||||
|
import BlockPreview from './BlockPreview.vue'
|
||||||
|
|
||||||
|
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 = highlight(rawString.value, 'vue')
|
||||||
|
}
|
||||||
|
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 as string))
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<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-vue-next 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"
|
||||||
|
:default-size="100"
|
||||||
|
:min-size="30"
|
||||||
|
:as-child="true"
|
||||||
|
>
|
||||||
|
<BlockPreview :name="name" styles="default" :container-class="metadata.containerClass ?? ''" container />
|
||||||
|
</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>
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
<script setup lang="ts">
|
<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 { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from '@/lib/registry/new-york/ui/tooltip'
|
} from '@/lib/registry/new-york/ui/tooltip'
|
||||||
|
import { CheckIcon, ClipboardIcon } from '@radix-icons/vue'
|
||||||
|
import { useClipboard } from '@vueuse/core'
|
||||||
|
import { toRefs } from 'vue'
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
code?: string
|
code?: string
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,11 @@
|
||||||
import { useUrlSearchParams } from '@vueuse/core'
|
import { useUrlSearchParams } from '@vueuse/core'
|
||||||
import ComponentLoader from './ComponentLoader.vue'
|
import ComponentLoader from './ComponentLoader.vue'
|
||||||
|
|
||||||
const params = useUrlSearchParams('hash-params')
|
const params = useUrlSearchParams('history')
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="params.name && params.style" :class="params.containerClass">
|
<div v-if="params.name" :class="params.containerClass">
|
||||||
<ComponentLoader :key="params.style?.toString()" :name="params.name?.toString()" :type-name="'block'" />
|
<ComponentLoader :key="params.style?.toString()" :name="params.name?.toString()" :type-name="'block'" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,245 +1,44 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { CircleHelp, Info, Monitor, Smartphone, Tablet } from 'lucide-vue-next'
|
import { computed, ref } from 'vue'
|
||||||
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 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<{
|
const props = defineProps<{
|
||||||
name: string
|
name: string
|
||||||
|
styles?: string
|
||||||
|
containerClass?: string
|
||||||
|
container?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const { style, codeConfig } = useConfigStore()
|
|
||||||
|
|
||||||
const isLoading = ref(true)
|
const isLoading = ref(true)
|
||||||
const tabValue = ref('preview')
|
|
||||||
const resizableRef = ref<InstanceType<typeof ResizablePanel>>()
|
|
||||||
|
|
||||||
const rawString = ref('')
|
const iframeURL = computed(() => {
|
||||||
const codeHtml = ref('')
|
// @ts-expect-error env available in import.meta
|
||||||
const metadata = reactive({
|
if (import.meta.env.SSR)
|
||||||
description: null as string | null,
|
return ''
|
||||||
iframeHeight: null as string | null,
|
|
||||||
containerClass: null as string | null,
|
const url = new URL(`${window.location.origin}/blocks/renderer`)
|
||||||
|
Object.entries(props).forEach(([key, value]) => {
|
||||||
|
if (value)
|
||||||
|
url.searchParams.append(key, value as string)
|
||||||
|
})
|
||||||
|
return url.href
|
||||||
})
|
})
|
||||||
|
|
||||||
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Tabs
|
<div class="relative rounded-lg border overflow-hidden bg-background" :class="[container ? '' : 'aspect-[4/2.5]']">
|
||||||
:id="name"
|
<div v-if="isLoading" class="flex items-center justify-center h-full">
|
||||||
v-model="tabValue"
|
<Spinner />
|
||||||
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>
|
</div>
|
||||||
<TabsContent
|
<div
|
||||||
v-show="tabValue === 'preview'"
|
:class="[container ? 'w-full' : 'absolute inset-0 hidden w-[1600px] bg-background md:block']"
|
||||||
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">
|
<iframe
|
||||||
<ResizablePanel
|
v-show="!isLoading"
|
||||||
id="block-resizable-panel-1"
|
:src="iframeURL"
|
||||||
ref="resizableRef"
|
class="relative z-20 w-full bg-background" :class="[container ? 'h-[--container-height]' : 'size-full']"
|
||||||
class="relative rounded-lg border bg-background transition-all "
|
@load="isLoading = false"
|
||||||
: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>
|
</div>
|
||||||
</Tabs>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
<script setup lang="ts">
|
<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 { buttonVariants } from '@/lib/registry/new-york/ui/button'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import GitHubIcon from '~icons/radix-icons/github-logo'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import Announcement from '../components/Announcement.vue'
|
||||||
|
import PageAction from '../components/PageAction.vue'
|
||||||
|
import PageHeader from '../components/PageHeader.vue'
|
||||||
|
import PageHeaderDescription from '../components/PageHeaderDescription.vue'
|
||||||
|
|
||||||
|
import PageHeaderHeading from '../components/PageHeaderHeading.vue'
|
||||||
|
import BlockContainer from './BlockContainer.vue'
|
||||||
|
|
||||||
const blocks = ref<string[]>([])
|
const blocks = ref<string[]>([])
|
||||||
|
|
||||||
|
|
@ -48,6 +48,6 @@ import('../../../__registry__/index').then((res) => {
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|
||||||
<section id="blocks" class="grid scroll-mt-24 gap-24 lg:gap-48">
|
<section id="blocks" class="grid scroll-mt-24 gap-24 lg:gap-48">
|
||||||
<BlockPreview v-for="block in blocks" :key="block" :name="block" />
|
<BlockContainer v-for="block in blocks" :key="block" :name="block" />
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ defineProps<CalloutProps>()
|
||||||
<AlertTitle v-if="title">
|
<AlertTitle v-if="title">
|
||||||
{{ title }}
|
{{ title }}
|
||||||
</AlertTitle>
|
</AlertTitle>
|
||||||
<AlertDescription>
|
<AlertDescription class="[&_a]:underline">
|
||||||
<slot />
|
<slot />
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|
|
||||||
71
apps/www/.vitepress/theme/components/CarbonAds.vue
Normal file
71
apps/www/.vitepress/theme/components/CarbonAds.vue
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useData } from 'vitepress'
|
||||||
|
import { onMounted, ref, watch } from 'vue'
|
||||||
|
|
||||||
|
const { page, theme } = useData()
|
||||||
|
const carbonOptions = theme.value.carbonAds
|
||||||
|
const container = ref()
|
||||||
|
|
||||||
|
let isInitialized = false
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
if (!isInitialized) {
|
||||||
|
isInitialized = true
|
||||||
|
const s = document.createElement('script')
|
||||||
|
s.type = 'text/javascript'
|
||||||
|
s.id = '_carbonads_js'
|
||||||
|
s.src = `//cdn.carbonads.com/carbon.js?serve=${carbonOptions.code}&placement=${carbonOptions.placement}&format=cover`
|
||||||
|
s.async = true
|
||||||
|
container.value.appendChild(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => page.value.relativePath, () => {
|
||||||
|
if (isInitialized) {
|
||||||
|
;(window as any)._carbonads?.refresh()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// no need to account for option changes during dev, we can just
|
||||||
|
// refresh the page
|
||||||
|
if (carbonOptions) {
|
||||||
|
onMounted(() => {
|
||||||
|
// @ts-expect-error ignoring env
|
||||||
|
if (import.meta.env.DEV)
|
||||||
|
return
|
||||||
|
|
||||||
|
// if the page is loaded when aside is active, load carbon directly.
|
||||||
|
// otherwise, only load it if the page resizes to wide enough. this avoids
|
||||||
|
// loading carbon at all on mobile where it's never shown
|
||||||
|
init()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
ref="container"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#carbon-responsive {
|
||||||
|
@apply w-[238px] !mt-6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carbon-responsive-wrap {
|
||||||
|
@apply bg-muted/50 border border-muted p-4 rounded-md flex flex-col items-center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carbon-responsive-wrap .carbon-img {
|
||||||
|
@apply flex-none rounded overflow-hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carbon-responsive-wrap .carbon-text {
|
||||||
|
@apply text-muted-foreground text-sm flex-none text-center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#carbonads .carbon-poweredby {
|
||||||
|
@apply bg-background text-muted-foreground block text-right text-[10px] uppercase no-underline !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
<script lang="ts" setup>
|
<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 { 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 { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/lib/registry/new-york/ui/form'
|
||||||
|
import { Input } from '@/lib/registry/new-york/ui/input'
|
||||||
import { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger } from '@/lib/registry/new-york/ui/sheet'
|
import { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger } from '@/lib/registry/new-york/ui/sheet'
|
||||||
|
import { useConfigStore } from '@/stores/config'
|
||||||
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
import RadixIconsGear from '~icons/radix-icons/gear'
|
import RadixIconsGear from '~icons/radix-icons/gear'
|
||||||
|
import { useForm } from 'vee-validate'
|
||||||
|
import * as z from 'zod'
|
||||||
|
|
||||||
const { codeConfig, setCodeConfig } = useConfigStore()
|
const { codeConfig, setCodeConfig } = useConfigStore()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, toRefs, watch } from 'vue'
|
import type { Style } from '@/lib/registry/styles'
|
||||||
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
import { Icon } from '@iconify/vue'
|
import { Icon } from '@iconify/vue'
|
||||||
|
import { ref, toRefs, watch } from 'vue'
|
||||||
import { makeCodeSandboxParams } from '../utils/codeeditor'
|
import { makeCodeSandboxParams } from '../utils/codeeditor'
|
||||||
import Tooltip from './Tooltip.vue'
|
import Tooltip from './Tooltip.vue'
|
||||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
|
||||||
import type { Style } from '@/lib/registry/styles'
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
name: string
|
name: string
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { type VNode, type VNodeArrayChildren, cloneVNode, defineComponent } from 'vue'
|
|
||||||
import { useConfigStore } from '@/stores/config'
|
import { useConfigStore } from '@/stores/config'
|
||||||
|
import { cloneVNode, defineComponent, type VNode, type VNodeArrayChildren } from 'vue'
|
||||||
|
|
||||||
function crawlSpan(children: VNodeArrayChildren, cb: (vnode: VNode) => void) {
|
function crawlSpan(children: VNodeArrayChildren, cb: (vnode: VNode) => void) {
|
||||||
children.forEach((childNode) => {
|
children.forEach((childNode) => {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useConfigStore } from '@/stores/config'
|
||||||
import { defineAsyncComponent } from 'vue'
|
import { defineAsyncComponent } from 'vue'
|
||||||
import Spinner from './Spinner.vue'
|
import Spinner from './Spinner.vue'
|
||||||
import { useConfigStore } from '@/stores/config'
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
name: string
|
name: string
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from 'vue'
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/lib/registry/default/ui/tabs'
|
||||||
import { codeToHtml } from 'shiki'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { useConfigStore } from '@/stores/config'
|
||||||
|
import { useClipboard } from '@vueuse/core'
|
||||||
import MagicString from 'magic-string'
|
import MagicString from 'magic-string'
|
||||||
import { cssVariables } from '../config/shiki'
|
import { computed, ref, watch } from 'vue'
|
||||||
import StyleSwitcher from './StyleSwitcher.vue'
|
import { highlight } from '../config/shiki'
|
||||||
|
import CodeSandbox from './CodeSandbox.vue'
|
||||||
import ComponentLoader from './ComponentLoader.vue'
|
import ComponentLoader from './ComponentLoader.vue'
|
||||||
import Stackblitz from './Stackblitz.vue'
|
import Stackblitz from './Stackblitz.vue'
|
||||||
import CodeSandbox from './CodeSandbox.vue'
|
import StyleSwitcher from './StyleSwitcher.vue'
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/lib/registry/default/ui/tabs'
|
|
||||||
import { useConfigStore } from '@/stores/config'
|
|
||||||
import { cn } from '@/lib/utils'
|
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
|
|
@ -24,6 +24,7 @@ const { style, codeConfig } = useConfigStore()
|
||||||
|
|
||||||
const rawString = ref('')
|
const rawString = ref('')
|
||||||
const codeHtml = ref('')
|
const codeHtml = ref('')
|
||||||
|
const transformedRawString = computed(() => transformImportPath(rawString.value))
|
||||||
|
|
||||||
function transformImportPath(code: string) {
|
function transformImportPath(code: string) {
|
||||||
const s = new MagicString(code)
|
const s = new MagicString(code)
|
||||||
|
|
@ -35,15 +36,14 @@ function transformImportPath(code: string) {
|
||||||
watch([style, codeConfig], async () => {
|
watch([style, codeConfig], async () => {
|
||||||
try {
|
try {
|
||||||
rawString.value = await import(`../../../src/lib/registry/${style.value}/example/${props.name}.vue?raw`).then(res => res.default.trim())
|
rawString.value = await import(`../../../src/lib/registry/${style.value}/example/${props.name}.vue?raw`).then(res => res.default.trim())
|
||||||
codeHtml.value = await codeToHtml(transformImportPath(rawString.value), {
|
codeHtml.value = highlight(transformedRawString.value, 'vue')
|
||||||
lang: 'vue',
|
|
||||||
theme: cssVariables,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
}
|
}
|
||||||
}, { immediate: true, deep: true })
|
}, { immediate: true, deep: true })
|
||||||
|
|
||||||
|
const { copy, copied } = useClipboard()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -86,8 +86,12 @@ watch([style, codeConfig], async () => {
|
||||||
<ComponentLoader v-bind="$attrs" :key="style" :name="name" :type-name="'example'" />
|
<ComponentLoader v-bind="$attrs" :key="style" :name="name" :type-name="'example'" />
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value="code">
|
<TabsContent value="code" class="vp-doc">
|
||||||
<div v-if="codeHtml" class="language-vue" style="flex: 1;" v-html="codeHtml" />
|
<div v-if="codeHtml" class="language-vue" style="flex: 1;">
|
||||||
|
<button title="Copy Code" class="copy" :class="{ copied }" @click="copy(transformedRawString)" />
|
||||||
|
|
||||||
|
<div v-html="codeHtml" />
|
||||||
|
</div>
|
||||||
<slot v-else />
|
<slot v-else />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from 'vue'
|
|
||||||
import { useClipboard } from '@vueuse/core'
|
|
||||||
import { useConfigStore } from '@/stores/config'
|
|
||||||
import { themes } from '@/lib/registry'
|
import { themes } from '@/lib/registry'
|
||||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
|
import { useConfigStore } from '@/stores/config'
|
||||||
|
import { useClipboard } from '@vueuse/core'
|
||||||
import CheckIcon from '~icons/radix-icons/check'
|
import CheckIcon from '~icons/radix-icons/check'
|
||||||
import CopyIcon from '~icons/radix-icons/copy'
|
import CopyIcon from '~icons/radix-icons/copy'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
const { theme, config } = useConfigStore()
|
const { theme, config } = useConfigStore()
|
||||||
|
|
||||||
|
|
|
||||||
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 {
|
||||||
|
Breadcrumb,
|
||||||
|
BreadcrumbItem,
|
||||||
|
BreadcrumbLink,
|
||||||
|
BreadcrumbList,
|
||||||
|
BreadcrumbSeparator,
|
||||||
|
} from '@/lib/registry/new-york/ui/breadcrumb'
|
||||||
|
import { useRoute } from 'vitepress'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { Button } from '@/lib/registry/default/ui/button'
|
||||||
|
import Pencil2Icon from '~icons/radix-icons/pencil-2'
|
||||||
import { useData } from 'vitepress'
|
import { useData } from 'vitepress'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import Pencil2Icon from '~icons/radix-icons/pencil-2'
|
|
||||||
import { Button } from '@/lib/registry/default/ui/button'
|
|
||||||
|
|
||||||
const { theme, page } = useData()
|
const { theme, page } = useData()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { ScrollArea, ScrollBar } from '@/lib/registry/default/ui/scroll-area'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import ArrowRightIcon from '~icons/radix-icons/arrow-right'
|
||||||
import { useRoute } from 'vitepress'
|
import { useRoute } from 'vitepress'
|
||||||
import { computed, toRefs } from 'vue'
|
import { computed, toRefs } from 'vue'
|
||||||
import { cn } from '@/lib/utils'
|
|
||||||
import { ScrollArea, ScrollBar } from '@/lib/registry/default/ui/scroll-area'
|
|
||||||
import ArrowRightIcon from '~icons/radix-icons/arrow-right'
|
|
||||||
|
|
||||||
const { path } = toRefs(useRoute())
|
const { path } = toRefs(useRoute())
|
||||||
|
|
||||||
|
|
@ -35,7 +35,7 @@ const examples = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Forms',
|
name: 'Forms',
|
||||||
href: '/examples/forms/forms',
|
href: '/examples/forms',
|
||||||
code: 'https://github.com/radix-vue/shadcn-vue/tree/dev/apps/www/src/examples/forms',
|
code: 'https://github.com/radix-vue/shadcn-vue/tree/dev/apps/www/src/examples/forms',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { Color } from '../types/colors'
|
import type { Color } from '../types/colors'
|
||||||
import { useConfigStore } from '@/stores/config'
|
|
||||||
import { colors } from '@/lib/registry'
|
import { colors } from '@/lib/registry'
|
||||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/lib/registry/new-york/ui/tooltip'
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/lib/registry/new-york/ui/tooltip'
|
||||||
|
import { useConfigStore } from '@/stores/config'
|
||||||
import RadixIconsCheck from '~icons/radix-icons/check'
|
import RadixIconsCheck from '~icons/radix-icons/check'
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,16 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
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 ExamplesNav from '../components/ExamplesNav.vue'
|
|
||||||
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 MailExample from '@/examples/mail/Example.vue'
|
import MailExample from '@/examples/mail/Example.vue'
|
||||||
|
import { buttonVariants } from '@/lib/registry/new-york/ui/button'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import GitHubIcon from '~icons/radix-icons/github-logo'
|
||||||
|
import Announcement from '../components/Announcement.vue'
|
||||||
|
import ExamplesNav from '../components/ExamplesNav.vue'
|
||||||
|
import PageAction from '../components/PageAction.vue'
|
||||||
|
|
||||||
|
import PageHeader from '../components/PageHeader.vue'
|
||||||
|
import PageHeaderDescription from '../components/PageHeaderDescription.vue'
|
||||||
|
|
||||||
|
import PageHeaderHeading from '../components/PageHeaderHeading.vue'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { Button } from '@/lib/registry/default/ui/button'
|
||||||
|
import { ScrollArea } from '@/lib/registry/default/ui/scroll-area'
|
||||||
|
import { Sheet, SheetContent, SheetTrigger } from '@/lib/registry/default/ui/sheet'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { docsConfig } from '../config/docs'
|
import { docsConfig } from '../config/docs'
|
||||||
import Logo from './Logo.vue'
|
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'
|
|
||||||
|
|
||||||
const open = ref(false)
|
const open = ref(false)
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -63,17 +63,26 @@ const open = ref(false)
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col space-y-2">
|
<div class="flex flex-col space-y-2">
|
||||||
<div v-for="(items, index) in docsConfig.sidebarNav" :key="index" class="flex flex-col space-y-3 pt-6">
|
<div v-for="(items, index) in docsConfig.sidebarNav" :key="index" class="flex flex-col space-y-3 pt-6">
|
||||||
<h4 class="font-medium">
|
<div class="flex items-center">
|
||||||
{{ items.title }}
|
<h4 class="font-medium">
|
||||||
</h4>
|
{{ items.title }}
|
||||||
|
</h4>
|
||||||
|
<span v-if="items.label" class="ml-2 rounded-md bg-[#adfa1d] px-1.5 py-0.5 text-xs leading-none text-[#000000] no-underline group-hover:no-underline">
|
||||||
|
{{ items.label }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<a
|
<a
|
||||||
v-for="item in items.items" :key="item.href"
|
v-for="item in items.items" :key="item.href"
|
||||||
:href="item.href"
|
:href="item.href"
|
||||||
class="text-muted-foreground"
|
class="text-muted-foreground inline-flex items-center"
|
||||||
@click="open = false"
|
@click="open = false"
|
||||||
>
|
>
|
||||||
{{ item.title }}
|
{{ item.title }}
|
||||||
|
|
||||||
|
<span v-if="item.label" class="ml-2 rounded-md bg-[#adfa1d] px-1.5 py-0.5 text-xs leading-none text-[#000000] no-underline group-hover:no-underline">
|
||||||
|
{{ item.label }}
|
||||||
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import WrapBalancer from 'vue-wrap-balancer'
|
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import WrapBalancer from 'vue-wrap-balancer'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<WrapBalancer :class="cn('max-w-[750px] text-center text-lg text-muted-foreground sm:text-xl', $attrs.class ?? '')" :prefer-native="false">
|
<WrapBalancer :class="cn('max-w-[750px] text-center text-lg font-light text-foreground', $attrs.class ?? '')" :prefer-native="false">
|
||||||
<slot />
|
<slot />
|
||||||
</WrapBalancer>
|
</WrapBalancer>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { cn } from '@/lib/utils'
|
||||||
<template>
|
<template>
|
||||||
<h1
|
<h1
|
||||||
:class="cn(
|
:class="cn(
|
||||||
'text-center text-3xl font-bold leading-tight tracking-tighter md:text-6xl lg:leading-[1.1]',
|
'text-center text-3xl font-bold leading-tight tracking-tighter md:text-5xl lg:leading-[1.1]',
|
||||||
$attrs.class ?? '',
|
$attrs.class ?? '',
|
||||||
)"
|
)"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, toRefs, watch } from 'vue'
|
import type { Style } from '@/lib/registry/styles'
|
||||||
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
import { Icon } from '@iconify/vue'
|
import { Icon } from '@iconify/vue'
|
||||||
|
import { ref, toRefs, watch } from 'vue'
|
||||||
import { makeStackblitzParams } from '../utils/codeeditor'
|
import { makeStackblitzParams } from '../utils/codeeditor'
|
||||||
import Tooltip from './Tooltip.vue'
|
import Tooltip from './Tooltip.vue'
|
||||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
|
||||||
import type { Style } from '@/lib/registry/styles'
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
name: string
|
name: string
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { SelectTriggerProps } from 'radix-vue'
|
import type { SelectTriggerProps } from 'radix-vue'
|
||||||
import { useConfigStore } from '@/stores/config'
|
|
||||||
|
|
||||||
import { cn } from '@/lib/utils'
|
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
|
|
@ -10,7 +7,10 @@ import {
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@/lib/registry/new-york/ui/select'
|
} from '@/lib/registry/new-york/ui/select'
|
||||||
|
|
||||||
import { styles } from '@/lib/registry/styles'
|
import { styles } from '@/lib/registry/styles'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { useConfigStore } from '@/stores/config'
|
||||||
|
|
||||||
const props = defineProps<SelectTriggerProps & { class?: string }>()
|
const props = defineProps<SelectTriggerProps & { class?: string }>()
|
||||||
const { config } = useConfigStore()
|
const { config } = useConfigStore()
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,16 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { TableOfContents, TableOfContentsItem } from '../types/docs'
|
||||||
|
import { buttonVariants } from '@/lib/registry/default/ui/button'
|
||||||
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/lib/registry/default/ui/collapsible'
|
||||||
|
import { ScrollArea } from '@/lib/registry/default/ui/scroll-area'
|
||||||
import { onContentUpdated } from 'vitepress'
|
import { onContentUpdated } from 'vitepress'
|
||||||
import { shallowRef } from 'vue'
|
import { shallowRef } from 'vue'
|
||||||
import type { TableOfContents, TableOfContentsItem } from '../types/docs'
|
import CarbonAds from '../components/CarbonAds.vue'
|
||||||
import TableOfContentTree from './TableOfContentTree.vue'
|
import TableOfContentTree from './TableOfContentTree.vue'
|
||||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/lib/registry/default/ui/collapsible'
|
|
||||||
import { buttonVariants } from '@/lib/registry/default/ui/button'
|
defineProps<{
|
||||||
import { ScrollArea } from '@/lib/registry/default/ui/scroll-area'
|
showCarbonAds?: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
const headers = shallowRef<TableOfContents>()
|
const headers = shallowRef<TableOfContents>()
|
||||||
|
|
||||||
|
|
@ -24,7 +29,7 @@ function getHeadingsWithHierarchy(divId: string) {
|
||||||
const level = Number.parseInt(heading.tagName.charAt(1))
|
const level = Number.parseInt(heading.tagName.charAt(1))
|
||||||
if (!heading.id) {
|
if (!heading.id) {
|
||||||
const newId = heading.textContent
|
const newId = heading.textContent
|
||||||
.replaceAll(/[^a-zA-Z0-9 ]/g, '')
|
?.replaceAll(/[^a-z0-9 ]/gi, '')
|
||||||
.replaceAll(' ', '-')
|
.replaceAll(' ', '-')
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
heading.id = `${newId}`
|
heading.id = `${newId}`
|
||||||
|
|
@ -63,6 +68,7 @@ onContentUpdated(() => {
|
||||||
On This Page
|
On This Page
|
||||||
</p>
|
</p>
|
||||||
<TableOfContentTree :tree="headers" :level="1" />
|
<TableOfContentTree :tree="headers" :level="1" />
|
||||||
|
<CarbonAds v-if="showCarbonAds" />
|
||||||
</div>
|
</div>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, onUnmounted, ref, watch } from 'vue'
|
|
||||||
import { useRoute } from 'vitepress'
|
|
||||||
import type { TableOfContentsItem } from '../types/docs'
|
import type { TableOfContentsItem } from '../types/docs'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { useRoute } from 'vitepress'
|
||||||
|
import { onMounted, onUnmounted, ref, watch } from 'vue'
|
||||||
|
|
||||||
withDefaults(defineProps<{
|
withDefaults(defineProps<{
|
||||||
level: number
|
level: number
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, useSlots } from 'vue'
|
|
||||||
import { Tabs, TabsList, TabsTrigger } from '@/lib/registry/default/ui/tabs'
|
import { Tabs, TabsList, TabsTrigger } from '@/lib/registry/default/ui/tabs'
|
||||||
|
import { computed, useSlots } from 'vue'
|
||||||
|
|
||||||
const slots = useSlots()
|
const slots = useSlots()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useData } from 'vitepress'
|
|
||||||
import type { Color } from '../types/colors'
|
import type { Color } from '../types/colors'
|
||||||
import { RADII, useConfigStore } from '@/stores/config'
|
import { colors } from '@/lib/registry'
|
||||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
import { Label } from '@/lib/registry/new-york/ui/label'
|
import { Label } from '@/lib/registry/new-york/ui/label'
|
||||||
import { colors } from '@/lib/registry'
|
import { RADII, useConfigStore } from '@/stores/config'
|
||||||
import RadixIconsCheck from '~icons/radix-icons/check'
|
import RadixIconsCheck from '~icons/radix-icons/check'
|
||||||
import RadixIconsSun from '~icons/radix-icons/sun'
|
|
||||||
import RadixIconsMoon from '~icons/radix-icons/moon'
|
import RadixIconsMoon from '~icons/radix-icons/moon'
|
||||||
|
import RadixIconsSun from '~icons/radix-icons/sun'
|
||||||
|
import { useData } from 'vitepress'
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
allColors: Color[]
|
allColors: Color[]
|
||||||
|
|
@ -43,7 +43,7 @@ const { isDark } = useData()
|
||||||
@click="setTheme(color)"
|
@click="setTheme(color)"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="h-5 w-5 rounded-full flex items-center justify-center"
|
class="h-5 w-5 rounded-full flex items-center justify-center shrink-0"
|
||||||
:style="{ backgroundColor: colors[color][7].rgb }"
|
:style="{ backgroundColor: colors[color][7].rgb }"
|
||||||
>
|
>
|
||||||
<RadixIconsCheck
|
<RadixIconsCheck
|
||||||
|
|
|
||||||
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 { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from '@/lib/registry/new-york/ui/popover'
|
||||||
|
import { useConfigStore } from '@/stores/config'
|
||||||
|
import { Paintbrush } from 'lucide-vue-next'
|
||||||
|
import { onMounted, watch } from 'vue'
|
||||||
|
import ThemeCustomizer from './ThemeCustomizer.vue'
|
||||||
|
import { allColors } from './theming/utils/data'
|
||||||
|
|
||||||
|
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,10 +1,12 @@
|
||||||
|
export { default as APITable } from './APITable.vue'
|
||||||
|
export { default as BlockPreview } from './BlockPreview.vue'
|
||||||
|
export { default as Callout } from './Callout.vue'
|
||||||
export { default as CodeWrapper } from './CodeWrapper'
|
export { default as CodeWrapper } from './CodeWrapper'
|
||||||
export { default as ComponentPreview } from './ComponentPreview.vue'
|
export { default as ComponentPreview } from './ComponentPreview.vue'
|
||||||
export { default as TabPreview } from './TabPreview.vue'
|
|
||||||
export { default as TabMarkdown } from './TabMarkdown.vue'
|
|
||||||
export { default as TabsMarkdown } from './TabsMarkdown.vue'
|
|
||||||
export { default as Callout } from './Callout.vue'
|
|
||||||
export { default as LinkedCard } from './LinkedCard.vue'
|
export { default as LinkedCard } from './LinkedCard.vue'
|
||||||
export { default as ManualInstall } from './ManualInstall.vue'
|
export { default as ManualInstall } from './ManualInstall.vue'
|
||||||
export { default as Steps } from './Steps.vue'
|
export { default as Steps } from './Steps.vue'
|
||||||
|
export { default as TabMarkdown } from './TabMarkdown.vue'
|
||||||
|
export { default as TabPreview } from './TabPreview.vue'
|
||||||
|
export { default as TabsMarkdown } from './TabsMarkdown.vue'
|
||||||
export { default as VPImage } from './VPImage.vue'
|
export { default as VPImage } from './VPImage.vue'
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,25 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type Ref, ref } from 'vue'
|
|
||||||
import type { DateRange } from 'radix-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'
|
import CookieSettings from '@/examples/cards/components/CookieSettings.vue'
|
||||||
import CreateAccount from '@/examples/cards/components/CreateAccount.vue'
|
import CreateAccount from '@/examples/cards/components/CreateAccount.vue'
|
||||||
|
|
||||||
import PaymentMethod from '@/examples/cards/components/PaymentMethod.vue'
|
import PaymentMethod from '@/examples/cards/components/PaymentMethod.vue'
|
||||||
|
|
||||||
import ReportAnIssue from '@/examples/cards/components/ReportAnIssue.vue'
|
import ReportAnIssue from '@/examples/cards/components/ReportAnIssue.vue'
|
||||||
import ShareDocument from '@/examples/cards/components/ShareDocument.vue'
|
import ShareDocument from '@/examples/cards/components/ShareDocument.vue'
|
||||||
import TeamMembers from '@/examples/cards/components/TeamMembers.vue'
|
import TeamMembers from '@/examples/cards/components/TeamMembers.vue'
|
||||||
|
|
||||||
import CardChat from '@/lib/registry/new-york/example/CardChat.vue'
|
import CardChat from '@/lib/registry/new-york/example/CardChat.vue'
|
||||||
import ActivityGoal from '@/lib/registry/new-york/example/Cards/ActivityGoal.vue'
|
import ActivityGoal from '@/lib/registry/new-york/example/Cards/ActivityGoal.vue'
|
||||||
import Metric from '@/lib/registry/new-york/example/Cards/Metric.vue'
|
|
||||||
import DataTable from '@/lib/registry/new-york/example/Cards/DataTable.vue'
|
import DataTable from '@/lib/registry/new-york/example/Cards/DataTable.vue'
|
||||||
import CardStats from '@/lib/registry/new-york/example/CardStats.vue'
|
|
||||||
|
|
||||||
import {
|
import Metric from '@/lib/registry/new-york/example/Cards/Metric.vue'
|
||||||
Card,
|
import CardStats from '@/lib/registry/new-york/example/CardStats.vue'
|
||||||
} from '@/lib/registry/new-york/ui/card'
|
import { Card } from '@/lib/registry/new-york/ui/card'
|
||||||
import { RangeCalendar } from '@/lib/registry/new-york/ui/range-calendar'
|
import { RangeCalendar } from '@/lib/registry/new-york/ui/range-calendar'
|
||||||
|
import { getLocalTimeZone, today } from '@internationalized/date'
|
||||||
|
|
||||||
|
import { type Ref, ref } from 'vue'
|
||||||
|
import ThemingLayout from './../../layout/ThemingLayout.vue'
|
||||||
|
|
||||||
const now = today(getLocalTimeZone())
|
const now = today(getLocalTimeZone())
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,42 @@
|
||||||
import { CreditCard } from 'lucide-vue-next'
|
|
||||||
import RiAppleFill from '~icons/ri/apple-fill'
|
import RiAppleFill from '~icons/ri/apple-fill'
|
||||||
import RiPaypalFill from '~icons/ri/paypal-fill'
|
import RiPaypalFill from '~icons/ri/paypal-fill'
|
||||||
|
import { CreditCard } from 'lucide-vue-next'
|
||||||
|
|
||||||
interface Payment {
|
type Color =
|
||||||
status: string
|
| 'zinc'
|
||||||
email: string
|
| 'slate'
|
||||||
amount: number
|
| 'stone'
|
||||||
}
|
| 'gray'
|
||||||
|
| 'neutral'
|
||||||
|
| 'red'
|
||||||
|
| 'rose'
|
||||||
|
| 'orange'
|
||||||
|
| 'green'
|
||||||
|
| 'blue'
|
||||||
|
| 'yellow'
|
||||||
|
| 'violet'
|
||||||
|
|
||||||
|
// Create an array of color values
|
||||||
|
export const allColors: Color[] = [
|
||||||
|
'zinc',
|
||||||
|
'rose',
|
||||||
|
'blue',
|
||||||
|
'green',
|
||||||
|
'orange',
|
||||||
|
'red',
|
||||||
|
'slate',
|
||||||
|
'stone',
|
||||||
|
'gray',
|
||||||
|
'neutral',
|
||||||
|
'yellow',
|
||||||
|
'violet',
|
||||||
|
]
|
||||||
|
|
||||||
|
// interface Payment {
|
||||||
|
// status: string
|
||||||
|
// email: string
|
||||||
|
// amount: number
|
||||||
|
// }
|
||||||
|
|
||||||
interface TeamMember {
|
interface TeamMember {
|
||||||
name: string
|
name: string
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,11 @@ export interface NavItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SidebarNavItem = NavItem & {
|
export type SidebarNavItem = NavItem & {
|
||||||
items: SidebarNavItem[]
|
items?: SidebarNavItem[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NavItemWithChildren = NavItem & {
|
export type NavItemWithChildren = NavItem & {
|
||||||
items: NavItemWithChildren[]
|
items?: NavItemWithChildren[]
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DocsConfig {
|
interface DocsConfig {
|
||||||
|
|
@ -55,22 +55,18 @@ export const docsConfig: DocsConfig = {
|
||||||
{
|
{
|
||||||
title: 'Introduction',
|
title: 'Introduction',
|
||||||
href: '/docs/introduction',
|
href: '/docs/introduction',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Installation',
|
title: 'Installation',
|
||||||
href: '/docs/installation',
|
href: '/docs/installation',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'components.json',
|
title: 'components.json',
|
||||||
href: '/docs/components-json',
|
href: '/docs/components-json',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Theming',
|
title: 'Theming',
|
||||||
href: '/docs/theming',
|
href: '/docs/theming',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Dark Mode',
|
title: 'Dark Mode',
|
||||||
|
|
@ -80,27 +76,22 @@ export const docsConfig: DocsConfig = {
|
||||||
{
|
{
|
||||||
title: 'CLI',
|
title: 'CLI',
|
||||||
href: '/docs/cli',
|
href: '/docs/cli',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Typography',
|
title: 'Typography',
|
||||||
href: '/docs/typography',
|
href: '/docs/typography',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Figma',
|
title: 'Figma',
|
||||||
href: '/docs/figma',
|
href: '/docs/figma',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Changelog',
|
title: 'Changelog',
|
||||||
href: '/docs/changelog',
|
href: '/docs/changelog',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'About',
|
title: 'About',
|
||||||
href: '/docs/about',
|
href: '/docs/about',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Contribution',
|
title: 'Contribution',
|
||||||
|
|
@ -115,21 +106,32 @@ export const docsConfig: DocsConfig = {
|
||||||
{
|
{
|
||||||
title: 'Vite',
|
title: 'Vite',
|
||||||
href: '/docs/installation/vite',
|
href: '/docs/installation/vite',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Nuxt',
|
title: 'Nuxt',
|
||||||
href: '/docs/installation/nuxt',
|
href: '/docs/installation/nuxt',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Astro',
|
title: 'Astro',
|
||||||
href: '/docs/installation/astro',
|
href: '/docs/installation/astro',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Laravel',
|
title: 'Laravel',
|
||||||
href: '/docs/installation/laravel',
|
href: '/docs/installation/laravel',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Extended',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: 'Auto Form',
|
||||||
|
href: '/docs/components/auto-form',
|
||||||
|
items: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Charts',
|
||||||
|
href: '/docs/charts',
|
||||||
items: [],
|
items: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
@ -137,35 +139,34 @@ export const docsConfig: DocsConfig = {
|
||||||
{
|
{
|
||||||
title: 'Components',
|
title: 'Components',
|
||||||
items: [
|
items: [
|
||||||
|
{
|
||||||
|
title: 'Sidebar',
|
||||||
|
href: '/docs/components/sidebar',
|
||||||
|
label: 'New',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'Accordion',
|
title: 'Accordion',
|
||||||
href: '/docs/components/accordion',
|
href: '/docs/components/accordion',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Alert',
|
title: 'Alert',
|
||||||
href: '/docs/components/alert',
|
href: '/docs/components/alert',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Alert Dialog',
|
title: 'Alert Dialog',
|
||||||
href: '/docs/components/alert-dialog',
|
href: '/docs/components/alert-dialog',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Aspect Ratio',
|
title: 'Aspect Ratio',
|
||||||
href: '/docs/components/aspect-ratio',
|
href: '/docs/components/aspect-ratio',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Avatar',
|
title: 'Avatar',
|
||||||
href: '/docs/components/avatar',
|
href: '/docs/components/avatar',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Badge',
|
title: 'Badge',
|
||||||
href: '/docs/components/badge',
|
href: '/docs/components/badge',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Breadcrumb',
|
title: 'Breadcrumb',
|
||||||
|
|
@ -175,18 +176,15 @@ export const docsConfig: DocsConfig = {
|
||||||
{
|
{
|
||||||
title: 'Button',
|
title: 'Button',
|
||||||
href: '/docs/components/button',
|
href: '/docs/components/button',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Calendar',
|
title: 'Calendar',
|
||||||
href: '/docs/components/calendar',
|
href: '/docs/components/calendar',
|
||||||
items: [],
|
items: [],
|
||||||
label: 'Updated',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Card',
|
title: 'Card',
|
||||||
href: '/docs/components/card',
|
href: '/docs/components/card',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Carousel',
|
title: 'Carousel',
|
||||||
|
|
@ -196,43 +194,35 @@ export const docsConfig: DocsConfig = {
|
||||||
{
|
{
|
||||||
title: 'Checkbox',
|
title: 'Checkbox',
|
||||||
href: '/docs/components/checkbox',
|
href: '/docs/components/checkbox',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Collapsible',
|
title: 'Collapsible',
|
||||||
href: '/docs/components/collapsible',
|
href: '/docs/components/collapsible',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Combobox',
|
title: 'Combobox',
|
||||||
href: '/docs/components/combobox',
|
href: '/docs/components/combobox',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Command',
|
title: 'Command',
|
||||||
href: '/docs/components/command',
|
href: '/docs/components/command',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Context Menu',
|
title: 'Context Menu',
|
||||||
href: '/docs/components/context-menu',
|
href: '/docs/components/context-menu',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Data Table',
|
title: 'Data Table',
|
||||||
href: '/docs/components/data-table',
|
href: '/docs/components/data-table',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Date Picker',
|
title: 'Date Picker',
|
||||||
href: '/docs/components/date-picker',
|
href: '/docs/components/date-picker',
|
||||||
items: [],
|
items: [],
|
||||||
label: 'Updated',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Dialog',
|
title: 'Dialog',
|
||||||
href: '/docs/components/dialog',
|
href: '/docs/components/dialog',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Drawer',
|
title: 'Drawer',
|
||||||
|
|
@ -242,68 +232,60 @@ export const docsConfig: DocsConfig = {
|
||||||
{
|
{
|
||||||
title: 'Dropdown Menu',
|
title: 'Dropdown Menu',
|
||||||
href: '/docs/components/dropdown-menu',
|
href: '/docs/components/dropdown-menu',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Form',
|
title: 'Form',
|
||||||
href: '/docs/components/form',
|
href: '/docs/components/form',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Hover Card',
|
title: 'Hover Card',
|
||||||
href: '/docs/components/hover-card',
|
href: '/docs/components/hover-card',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Input',
|
title: 'Input',
|
||||||
href: '/docs/components/input',
|
href: '/docs/components/input',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Label',
|
title: 'Label',
|
||||||
href: '/docs/components/label',
|
href: '/docs/components/label',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Menubar',
|
title: 'Menubar',
|
||||||
href: '/docs/components/menubar',
|
href: '/docs/components/menubar',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Navigation Menu',
|
title: 'Navigation Menu',
|
||||||
href: '/docs/components/navigation-menu',
|
href: '/docs/components/navigation-menu',
|
||||||
items: [],
|
},
|
||||||
|
{
|
||||||
|
title: 'Number Field',
|
||||||
|
href: '/docs/components/number-field',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Pagination',
|
title: 'Pagination',
|
||||||
href: '/docs/components/pagination',
|
href: '/docs/components/pagination',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Pin Input',
|
title: 'PIN Input',
|
||||||
href: '/docs/components/pin-input',
|
href: '/docs/components/pin-input',
|
||||||
items: [],
|
items: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Popover',
|
title: 'Popover',
|
||||||
href: '/docs/components/popover',
|
href: '/docs/components/popover',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Progress',
|
title: 'Progress',
|
||||||
href: '/docs/components/progress',
|
href: '/docs/components/progress',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Radio Group',
|
title: 'Radio Group',
|
||||||
href: '/docs/components/radio-group',
|
href: '/docs/components/radio-group',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Range Calendar',
|
title: 'Range Calendar',
|
||||||
href: '/docs/components/range-calendar',
|
href: '/docs/components/range-calendar',
|
||||||
items: [],
|
items: [],
|
||||||
label: 'New',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Resizable',
|
title: 'Resizable',
|
||||||
|
|
@ -313,52 +295,47 @@ export const docsConfig: DocsConfig = {
|
||||||
{
|
{
|
||||||
title: 'Scroll Area',
|
title: 'Scroll Area',
|
||||||
href: '/docs/components/scroll-area',
|
href: '/docs/components/scroll-area',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Select',
|
title: 'Select',
|
||||||
href: '/docs/components/select',
|
href: '/docs/components/select',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Separator',
|
title: 'Separator',
|
||||||
href: '/docs/components/separator',
|
href: '/docs/components/separator',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Sheet',
|
title: 'Sheet',
|
||||||
href: '/docs/components/sheet',
|
href: '/docs/components/sheet',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Skeleton',
|
title: 'Skeleton',
|
||||||
href: '/docs/components/skeleton',
|
href: '/docs/components/skeleton',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Slider',
|
title: 'Slider',
|
||||||
href: '/docs/components/slider',
|
href: '/docs/components/slider',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Sonner',
|
title: 'Sonner',
|
||||||
href: '/docs/components/sonner',
|
href: '/docs/components/sonner',
|
||||||
items: [],
|
items: [],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Stepper',
|
||||||
|
href: '/docs/components/stepper',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'Switch',
|
title: 'Switch',
|
||||||
href: '/docs/components/switch',
|
href: '/docs/components/switch',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Table',
|
title: 'Table',
|
||||||
href: '/docs/components/table',
|
href: '/docs/components/table',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Tabs',
|
title: 'Tabs',
|
||||||
href: '/docs/components/tabs',
|
href: '/docs/components/tabs',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Tags Input',
|
title: 'Tags Input',
|
||||||
|
|
@ -368,27 +345,22 @@ export const docsConfig: DocsConfig = {
|
||||||
{
|
{
|
||||||
title: 'Textarea',
|
title: 'Textarea',
|
||||||
href: '/docs/components/textarea',
|
href: '/docs/components/textarea',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Toast',
|
title: 'Toast',
|
||||||
href: '/docs/components/toast',
|
href: '/docs/components/toast',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Toggle',
|
title: 'Toggle',
|
||||||
href: '/docs/components/toggle',
|
href: '/docs/components/toggle',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Toggle Group',
|
title: 'Toggle Group',
|
||||||
href: '/docs/components/toggle-group',
|
href: '/docs/components/toggle-group',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Tooltip',
|
title: 'Tooltip',
|
||||||
href: '/docs/components/tooltip',
|
href: '/docs/components/tooltip',
|
||||||
items: [],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,41 @@
|
||||||
import { createCssVariablesTheme } from 'shiki'
|
import type { HighlighterCore } from 'shiki/core'
|
||||||
|
import type { ThemeOptions } from 'vitepress'
|
||||||
|
import { computedAsync } from '@vueuse/core'
|
||||||
|
import { createHighlighterCore } from 'shiki/core'
|
||||||
|
import { createJavaScriptRegexEngine } from 'shiki/engine/javascript'
|
||||||
|
|
||||||
export const cssVariables = createCssVariablesTheme({
|
export const shikiThemes: ThemeOptions = {
|
||||||
variablePrefix: '--shiki-',
|
light: 'github-light-default',
|
||||||
variableDefaults: {},
|
dark: 'github-dark-default',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const highlighter = computedAsync<HighlighterCore>(async (onCancel) => {
|
||||||
|
const shiki = await createHighlighterCore({
|
||||||
|
engine: createJavaScriptRegexEngine(),
|
||||||
|
themes: [
|
||||||
|
() => import('shiki/themes/github-dark-default.mjs'),
|
||||||
|
() => import('shiki/themes/github-light-default.mjs'),
|
||||||
|
],
|
||||||
|
langs: [
|
||||||
|
() => import('shiki/langs/javascript.mjs'),
|
||||||
|
() => import('shiki/langs/vue.mjs'),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
onCancel(() => shiki?.dispose())
|
||||||
|
return shiki
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export function highlight(code: string, lang: string) {
|
||||||
|
if (!highlighter.value)
|
||||||
|
return code
|
||||||
|
|
||||||
|
return highlighter.value.codeToHtml(code, {
|
||||||
|
lang,
|
||||||
|
defaultColor: false,
|
||||||
|
themes: {
|
||||||
|
dark: 'github-dark-default',
|
||||||
|
light: 'github-light-default',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,6 @@ export const siteConfig = {
|
||||||
|
|
||||||
export const announcementConfig = {
|
export const announcementConfig = {
|
||||||
icon: '✨',
|
icon: '✨',
|
||||||
title: 'Introducing Blocks!',
|
title: 'Extended: Auto Form, Charts',
|
||||||
link: '/blocks',
|
link: '/docs/components/auto-form.html',
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
// https://vitepress.dev/guide/custom-theme
|
import * as components from './components'
|
||||||
import Layout from './layout/MainLayout.vue'
|
|
||||||
import DocsLayout from './layout/DocsLayout.vue'
|
import DocsLayout from './layout/DocsLayout.vue'
|
||||||
import ExamplesLayout from './layout/ExamplesLayout.vue'
|
import ExamplesLayout from './layout/ExamplesLayout.vue'
|
||||||
import * as components from './components'
|
// https://vitepress.dev/guide/custom-theme
|
||||||
|
import Layout from './layout/MainLayout.vue'
|
||||||
import './style.css'
|
import './style.css'
|
||||||
import './styles/vp-doc.css'
|
import './styles/vp-doc.css'
|
||||||
import './styles/shiki.css'
|
import './styles/shiki.css'
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useData, useRoute } from 'vitepress'
|
|
||||||
import { docsConfig } from '../config/docs'
|
|
||||||
import TableOfContentVue from '../components/TableOfContent.vue'
|
|
||||||
import EditLink from '../components/EditLink.vue'
|
|
||||||
import { ScrollArea } from '@/lib/registry/default/ui/scroll-area'
|
import { ScrollArea } from '@/lib/registry/default/ui/scroll-area'
|
||||||
import RadixIconsCode from '~icons/radix-icons/code'
|
import RadixIconsCode from '~icons/radix-icons/code'
|
||||||
import RadixIconsExternalLink from '~icons/radix-icons/external-link'
|
import RadixIconsExternalLink from '~icons/radix-icons/external-link'
|
||||||
import ChevronRightIcon from '~icons/lucide/chevron-right'
|
import { useData, useRoute } from 'vitepress'
|
||||||
|
import DocsBreadcrumb from '../components/DocsBreadcrumb.vue'
|
||||||
|
import EditLink from '../components/EditLink.vue'
|
||||||
|
import TableOfContent from '../components/TableOfContent.vue'
|
||||||
|
import { docsConfig } from '../config/docs'
|
||||||
|
|
||||||
const $route = useRoute()
|
const $route = useRoute()
|
||||||
const { frontmatter } = useData()
|
const { frontmatter } = useData()
|
||||||
|
|
@ -27,6 +27,10 @@ const sourceLink = 'https://github.com/radix-vue/shadcn-vue/tree/dev/'
|
||||||
class="mb-1 rounded-md px-2 py-1 text-sm font-semibold"
|
class="mb-1 rounded-md px-2 py-1 text-sm font-semibold"
|
||||||
>
|
>
|
||||||
{{ docsGroup.title }}
|
{{ docsGroup.title }}
|
||||||
|
|
||||||
|
<span v-if="docsGroup.label" class="ml-2 font-normal rounded-md bg-[#adfa1d] px-1.5 py-0.5 text-xs leading-none text-[#000000] no-underline group-hover:no-underline">
|
||||||
|
{{ docsGroup.label }}
|
||||||
|
</span>
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|
@ -58,23 +62,20 @@ const sourceLink = 'https://github.com/radix-vue/shadcn-vue/tree/dev/'
|
||||||
<main class="relative py-6 lg:gap-10 lg:py-8 xl:grid xl:grid-cols-[1fr_300px]">
|
<main class="relative py-6 lg:gap-10 lg:py-8 xl:grid xl:grid-cols-[1fr_300px]">
|
||||||
<div class="mx-auto w-full min-w-0">
|
<div class="mx-auto w-full min-w-0">
|
||||||
<div class="block xl:hidden">
|
<div class="block xl:hidden">
|
||||||
<TableOfContentVue />
|
<TableOfContent />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-4 flex items-center space-x-1 text-sm text-muted-foreground">
|
<DocsBreadcrumb class="mb-4" />
|
||||||
<div class="overflow-hidden text-ellipsis whitespace-nowrap">
|
|
||||||
Docs
|
|
||||||
</div>
|
|
||||||
<ChevronRightIcon class="h-4 w-4" />
|
|
||||||
<div class="font-medium text-foreground">
|
|
||||||
{{ frontmatter.title }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<h1 class="scroll-m-20 text-4xl font-bold tracking-tight">
|
<div class="flex items-center space-x-4">
|
||||||
{{ frontmatter.title }}
|
<h1 class="scroll-m-20 text-4xl font-bold tracking-tight">
|
||||||
</h1>
|
{{ frontmatter.title }}
|
||||||
|
</h1>
|
||||||
|
<span v-if="frontmatter.label" class="ml-2 rounded-md bg-[#adfa1d] px-1.5 py-0.5 text-xs leading-none text-[#000000] no-underline group-hover:no-underline">
|
||||||
|
{{ frontmatter.label }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<p class="text-lg text-muted-foreground">
|
<p class="text-lg text-muted-foreground">
|
||||||
{{ frontmatter.description }}
|
{{ frontmatter.description }}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -103,7 +104,7 @@ const sourceLink = 'https://github.com/radix-vue/shadcn-vue/tree/dev/'
|
||||||
|
|
||||||
<div class="hidden text-sm xl:block">
|
<div class="hidden text-sm xl:block">
|
||||||
<div class="sticky top-16 -mt-10 h-[calc(100vh-3.5rem)] overflow-hidden pt-6">
|
<div class="sticky top-16 -mt-10 h-[calc(100vh-3.5rem)] overflow-hidden pt-6">
|
||||||
<TableOfContentVue />
|
<TableOfContent show-carbon-ads />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
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 ExamplesNav from '../components/ExamplesNav.vue'
|
|
||||||
import Announcement from '../components/Announcement.vue'
|
|
||||||
|
|
||||||
import { buttonVariants } from '@/lib/registry/new-york/ui/button'
|
import { buttonVariants } from '@/lib/registry/new-york/ui/button'
|
||||||
import { Separator } from '@/lib/registry/new-york/ui/separator'
|
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import Announcement from '../components/Announcement.vue'
|
||||||
|
import ExamplesNav from '../components/ExamplesNav.vue'
|
||||||
|
import PageAction from '../components/PageAction.vue'
|
||||||
|
import PageHeader from '../components/PageHeader.vue'
|
||||||
|
|
||||||
|
import PageHeaderDescription from '../components/PageHeaderDescription.vue'
|
||||||
|
import PageHeaderHeading from '../components/PageHeaderHeading.vue'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,30 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useMagicKeys, useToggle } from '@vueuse/core'
|
|
||||||
import { onMounted, ref, watch } from 'vue'
|
|
||||||
import { Content, useData, useRoute, useRouter } from 'vitepress'
|
|
||||||
import { type NavItem, docsConfig } from '../config/docs'
|
|
||||||
import Logo from '../components/Logo.vue'
|
|
||||||
import MobileNav from '../components/MobileNav.vue'
|
|
||||||
import CodeConfigCustomizer from '../components/CodeConfigCustomizer.vue'
|
|
||||||
|
|
||||||
import Kbd from '../components/Kbd.vue'
|
|
||||||
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator } from '@/lib/registry/default/ui/command'
|
|
||||||
|
|
||||||
import { Button } from '@/lib/registry/default/ui/button'
|
import { Button } from '@/lib/registry/default/ui/button'
|
||||||
import RadixIconsGithubLogo from '~icons/radix-icons/github-logo'
|
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator } from '@/lib/registry/default/ui/command'
|
||||||
import RadixIconsMoon from '~icons/radix-icons/moon'
|
|
||||||
import RadixIconsSun from '~icons/radix-icons/sun'
|
|
||||||
import { useConfigStore } from '@/stores/config'
|
|
||||||
import { Dialog, DialogContent } from '@/lib/registry/default/ui/dialog'
|
import { Dialog, DialogContent } from '@/lib/registry/default/ui/dialog'
|
||||||
import { Toaster as DefaultToaster } from '@/lib/registry/default/ui/toast'
|
import { Toaster as DefaultToaster } from '@/lib/registry/default/ui/toast'
|
||||||
import { Toaster as NewYorkSonner } from '@/lib/registry/new-york/ui/sonner'
|
import { Toaster as NewYorkSonner } from '@/lib/registry/new-york/ui/sonner'
|
||||||
import { Toaster as NewYorkToaster } from '@/lib/registry/new-york/ui/toast'
|
import { Toaster as NewYorkToaster } from '@/lib/registry/new-york/ui/toast'
|
||||||
import { TooltipProvider } from '@/lib/registry/new-york/ui/tooltip'
|
import { TooltipProvider } from '@/lib/registry/new-york/ui/tooltip'
|
||||||
|
|
||||||
import File from '~icons/radix-icons/file'
|
import { useConfigStore } from '@/stores/config'
|
||||||
|
import { useMagicKeys, useToggle } from '@vueuse/core'
|
||||||
import Circle from '~icons/radix-icons/circle'
|
import Circle from '~icons/radix-icons/circle'
|
||||||
|
|
||||||
|
import File from '~icons/radix-icons/file'
|
||||||
|
import RadixIconsGithubLogo from '~icons/radix-icons/github-logo'
|
||||||
|
import RadixIconsMoon from '~icons/radix-icons/moon'
|
||||||
|
import RadixIconsSun from '~icons/radix-icons/sun'
|
||||||
|
import { Content, useData, useRoute, useRouter } from 'vitepress'
|
||||||
|
import { onMounted, ref, watch } from 'vue'
|
||||||
|
import CodeConfigCustomizer from '../components/CodeConfigCustomizer.vue'
|
||||||
|
import Kbd from '../components/Kbd.vue'
|
||||||
|
import Logo from '../components/Logo.vue'
|
||||||
|
import MobileNav from '../components/MobileNav.vue'
|
||||||
|
|
||||||
|
import ThemePopover from '../components/ThemePopover.vue'
|
||||||
|
import { docsConfig, type NavItem } from '../config/docs'
|
||||||
|
|
||||||
const { radius, theme } = useConfigStore()
|
const { radius, theme } = useConfigStore()
|
||||||
// Whenever the component is mounted, update the document class list
|
// Whenever the component is mounted, update the document class list
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|
@ -40,7 +41,7 @@ const toggleDark = useToggle(isDark)
|
||||||
const links = [
|
const links = [
|
||||||
{
|
{
|
||||||
name: 'GitHub',
|
name: 'GitHub',
|
||||||
href: 'https://github.com/radix-vue/shadcn-vue',
|
href: 'https://github.com/unovue/shadcn-vue',
|
||||||
icon: RadixIconsGithubLogo,
|
icon: RadixIconsGithubLogo,
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
|
|
@ -133,6 +134,8 @@ watch(() => $route.path, (n) => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav class="flex items-center">
|
<nav class="flex items-center">
|
||||||
|
<ThemePopover />
|
||||||
|
|
||||||
<CodeConfigCustomizer />
|
<CodeConfigCustomizer />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -206,7 +209,7 @@ watch(() => $route.path, (n) => {
|
||||||
<span class="inline-block ml-2">
|
<span class="inline-block ml-2">
|
||||||
Ported to Vue by
|
Ported to Vue by
|
||||||
<a
|
<a
|
||||||
href="https://github.com/radix-vue"
|
href="https://github.com/unovue"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="underline underline-offset-4 font-bold decoration-foreground"
|
class="underline underline-offset-4 font-bold decoration-foreground"
|
||||||
>
|
>
|
||||||
|
|
@ -217,7 +220,7 @@ watch(() => $route.path, (n) => {
|
||||||
<span class="inline-block ml-2">
|
<span class="inline-block ml-2">
|
||||||
The code source is available on
|
The code source is available on
|
||||||
<a
|
<a
|
||||||
href="https://github.com/radix-vue/shadcn-vue"
|
href="https://github.com/unovue/shadcn-vue"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="underline underline-offset-4 font-bold decoration-foreground"
|
class="underline underline-offset-4 font-bold decoration-foreground"
|
||||||
>
|
>
|
||||||
|
|
@ -300,7 +303,7 @@ watch(() => $route.path, (n) => {
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
<DefaultToaster />
|
<DefaultToaster />
|
||||||
<NewYorkSonner :theme="'system'" />
|
<NewYorkSonner class="pointer-events-auto" :theme="'system'" />
|
||||||
<NewYorkToaster />
|
<NewYorkToaster />
|
||||||
</div>
|
</div>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, watch } from 'vue'
|
|
||||||
import { Paintbrush } from 'lucide-vue-next'
|
|
||||||
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 type { Color } from '../types/colors'
|
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 { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from '@/lib/registry/new-york/ui/popover'
|
|
||||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '@/lib/registry/new-york/ui/dialog'
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '@/lib/registry/new-york/ui/dialog'
|
||||||
import { Drawer, DrawerContent, DrawerTrigger } from '@/lib/registry/new-york/ui/drawer'
|
import { Drawer, DrawerContent, DrawerTrigger } from '@/lib/registry/new-york/ui/drawer'
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from '@/lib/registry/new-york/ui/popover'
|
||||||
|
import { useConfigStore } from '@/stores/config'
|
||||||
|
import { Paintbrush } from 'lucide-vue-next'
|
||||||
|
import { onMounted, watch } from 'vue'
|
||||||
|
import CustomizerCode from '../components/CustomizerCode.vue'
|
||||||
|
import InlineThemePicker from '../components/InlineThemePicker.vue'
|
||||||
|
import PageAction from '../components/PageAction.vue'
|
||||||
|
import PageHeader from '../components/PageHeader.vue'
|
||||||
|
import PageHeaderDescription from '../components/PageHeaderDescription.vue'
|
||||||
|
import PageHeaderHeading from '../components/PageHeaderHeading.vue'
|
||||||
|
import ThemeCustomizer from '../components/ThemeCustomizer.vue'
|
||||||
|
|
||||||
// Create an array of color values
|
// Create an array of color values
|
||||||
const allColors: Color[] = [
|
const allColors: Color[] = [
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// Credit to @hairyf https://github.com/hairyf/markdown-it-vitepress-demo
|
// Credit to @hairyf https://github.com/hairyf/markdown-it-vitepress-demo
|
||||||
|
|
||||||
import { baseParse } from '@vue/compiler-core'
|
|
||||||
import type { AttributeNode, ElementNode } from '@vue/compiler-core'
|
import type { AttributeNode, ElementNode } from '@vue/compiler-core'
|
||||||
|
import { baseParse } from '@vue/compiler-core'
|
||||||
|
|
||||||
export interface GenerateOptions {
|
export interface GenerateOptions {
|
||||||
attrs?: string
|
attrs?: string
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,31 @@
|
||||||
--input: 240 5.9% 90%;
|
--input: 240 5.9% 90%;
|
||||||
--ring: 240 5% 64.9%;
|
--ring: 240 5% 64.9%;
|
||||||
--radius: 0.5rem;
|
--radius: 0.5rem;
|
||||||
|
|
||||||
|
--sidebar-background: 0 0% 98%;
|
||||||
|
--sidebar-foreground: 240 5.3% 26.1%;
|
||||||
|
--sidebar-primary: 240 5.9% 10%;
|
||||||
|
--sidebar-primary-foreground: 0 0% 98%;
|
||||||
|
--sidebar-accent: 240 4.8% 95.9%;
|
||||||
|
--sidebar-accent-foreground: 240 5.9% 10%;
|
||||||
|
--sidebar-border: 220 13% 91%;
|
||||||
|
--sidebar-ring: 217.2 91.2% 59.8%;
|
||||||
|
|
||||||
|
|
||||||
|
--vis-primary-color: var(--primary);
|
||||||
|
--vis-secondary-color: 160 81% 40%;
|
||||||
|
--vis-text-color: var(--muted-foreground);
|
||||||
|
|
||||||
|
|
||||||
|
--vis-font-family: inherit !important;
|
||||||
|
--vis-area-stroke-width: 2px !important;
|
||||||
|
--vis-donut-central-label-text-color: hsl(var(--muted-foreground)) !important;
|
||||||
|
--vis-tooltip-background-color: none !important;
|
||||||
|
--vis-tooltip-border-color: none !important;
|
||||||
|
--vis-tooltip-text-color: none !important;
|
||||||
|
--vis-tooltip-shadow-color: none !important;
|
||||||
|
--vis-tooltip-backdrop-filter: none !important;
|
||||||
|
--vis-tooltip-padding: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
|
|
@ -49,11 +74,22 @@
|
||||||
--border: 240 3.7% 15.9%;
|
--border: 240 3.7% 15.9%;
|
||||||
--input: 240 3.7% 15.9%;
|
--input: 240 3.7% 15.9%;
|
||||||
--ring: 240 4.9% 83.9%;
|
--ring: 240 4.9% 83.9%;
|
||||||
|
|
||||||
|
--sidebar-background: 240 5.9% 10%;
|
||||||
|
--sidebar-foreground: 240 4.8% 95.9%;
|
||||||
|
--sidebar-primary: 224.3 76.3% 48%;
|
||||||
|
--sidebar-primary-foreground: 0 0% 100%;
|
||||||
|
--sidebar-accent: 240 3.7% 15.9%;
|
||||||
|
--sidebar-accent-foreground: 240 4.8% 95.9%;
|
||||||
|
--sidebar-border: 240 3.7% 15.9%;
|
||||||
|
--sidebar-ring: 217.2 91.2% 59.8%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
* {
|
* {
|
||||||
@apply border-border;
|
@apply border-border;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: hsl(var(--border)) transparent;
|
||||||
}
|
}
|
||||||
html {
|
html {
|
||||||
-webkit-text-size-adjust: 100%;
|
-webkit-text-size-adjust: 100%;
|
||||||
|
|
@ -82,19 +118,6 @@
|
||||||
src: url("/fonts/Geist/GeistVariableVF.woff2") format("woff2");
|
src: url("/fonts/Geist/GeistVariableVF.woff2") format("woff2");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* === Scrollbars === */
|
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
|
||||||
@apply w-2;
|
|
||||||
@apply h-2;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-track {
|
|
||||||
@apply !bg-muted;
|
|
||||||
}
|
|
||||||
::-webkit-scrollbar-thumb {
|
|
||||||
@apply rounded-sm !bg-muted-foreground/30;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Firefox */
|
/* Firefox */
|
||||||
/* https://developer.mozilla.org/en-US/docs/Web/CSS/scrollbar-color#browser_compatibility */
|
/* https://developer.mozilla.org/en-US/docs/Web/CSS/scrollbar-color#browser_compatibility */
|
||||||
|
|
@ -106,14 +129,14 @@
|
||||||
scrollbar-color: hsl(215.4 16.3% 56.9% / 0.3);
|
scrollbar-color: hsl(215.4 16.3% 56.9% / 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hide-scrollbar::-webkit-scrollbar {
|
html.dark .shiki,
|
||||||
display: none;
|
html.dark .shiki span {
|
||||||
}
|
color: var(--shiki-dark);
|
||||||
|
}
|
||||||
.hide-scrollbar {
|
html:not(.dark) .shiki,
|
||||||
-ms-overflow-style: none;
|
html:not(.dark) .shiki span {
|
||||||
scrollbar-width: none;
|
color: var(--shiki-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
.antialised {
|
.antialised {
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
|
|
@ -142,22 +165,30 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
div[class^="language-"] {
|
div[class^="language-"] {
|
||||||
@apply mb-4 mt-6 max-h-[650px] overflow-x-auto md:rounded-lg border !bg-zinc-950 dark:!bg-zinc-900
|
@apply mb-4 mt-6 max-h-[650px] overflow-x-auto md:rounded-lg border
|
||||||
}
|
}
|
||||||
pre {
|
pre {
|
||||||
@apply py-4;
|
@apply py-4;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre code {
|
pre code {
|
||||||
@apply relative font-mono text-sm ;
|
@apply relative font-mono text-sm ;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-numbers-wrapper, code {
|
||||||
|
--vp-code-line-height: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-numbers-wrapper {
|
||||||
|
@apply font-mono;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre code .line {
|
pre code .line {
|
||||||
@apply px-4 min-h-6 !py-0.5 w-full inline-block;
|
@apply px-4 min-h-4 !py-0.5 w-full inline-block leading-[--vp-code-line-height];
|
||||||
}
|
}
|
||||||
|
|
||||||
.line-number {
|
.line-number {
|
||||||
@apply min-h-[1.375rem] !text-sm !inline-block text-muted-foreground;
|
@apply !text-[.75rem] !inline-block text-muted-foreground leading-[--vp-code-line-height];
|
||||||
}
|
}
|
||||||
|
|
||||||
::view-transition-old(root),
|
::view-transition-old(root),
|
||||||
|
|
|
||||||
|
|
@ -345,12 +345,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.vp-doc [class*='language-'] code .highlighted {
|
.vp-doc [class*='language-'] code .highlighted {
|
||||||
background-color: hsl(240 3.7% 15.9%);
|
|
||||||
transition: background-color 0.5s;
|
transition: background-color 0.5s;
|
||||||
/* margin: 0 -24px;
|
/* margin: 0 -24px;
|
||||||
padding: 0 24px; */
|
padding: 0 24px; */
|
||||||
width: calc(100% + 2 * 24px);
|
width: calc(100% + 2 * 24px);
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@apply bg-[hsl(var(--muted))] dark:bg-[hsl(var(--muted))]
|
||||||
}
|
}
|
||||||
|
|
||||||
.vp-doc [class*='language-'] code .highlighted.error {
|
.vp-doc [class*='language-'] code .highlighted.error {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,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__'
|
|
||||||
import tailwindConfigRaw from '../../../tailwind.config?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'
|
||||||
|
import sdk from '@stackblitz/sdk'
|
||||||
|
import { getParameters } from 'codesandbox/lib/api/define'
|
||||||
|
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'
|
||||||
|
|
||||||
export function makeCodeSandboxParams(componentName: string, style: Style, sources: Record<string, string>) {
|
export function makeCodeSandboxParams(componentName: string, style: Style, sources: Record<string, string>) {
|
||||||
let files: Record<string, any> = {}
|
let files: Record<string, any> = {}
|
||||||
|
|
@ -35,7 +36,15 @@ const viteConfig = {
|
||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
|
||||||
|
import tailwind from 'tailwindcss';
|
||||||
|
import autoprefixer from 'autoprefixer';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
css: {
|
||||||
|
postcss: {
|
||||||
|
plugins: [tailwind(), autoprefixer()],
|
||||||
|
},
|
||||||
|
},
|
||||||
plugins: [vue()],
|
plugins: [vue()],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
|
@ -82,7 +91,7 @@ function constructFiles(componentName: string, style: Style, sources: Record<str
|
||||||
const iconPackage = style === 'default' ? 'lucide-vue-next' : '@radix-icons/vue'
|
const iconPackage = style === 'default' ? 'lucide-vue-next' : '@radix-icons/vue'
|
||||||
const dependencies = {
|
const dependencies = {
|
||||||
'vue': 'latest',
|
'vue': 'latest',
|
||||||
'radix-vue': deps['radix-vue'],
|
'radix-vue': 'latest',
|
||||||
'@radix-ui/colors': 'latest',
|
'@radix-ui/colors': 'latest',
|
||||||
'clsx': 'latest',
|
'clsx': 'latest',
|
||||||
'class-variance-authority': 'latest',
|
'class-variance-authority': 'latest',
|
||||||
|
|
@ -102,7 +111,6 @@ function constructFiles(componentName: string, style: Style, sources: Record<str
|
||||||
'@vitejs/plugin-vue': 'latest',
|
'@vitejs/plugin-vue': 'latest',
|
||||||
'vue-tsc': 'latest',
|
'vue-tsc': 'latest',
|
||||||
'tailwindcss': 'latest',
|
'tailwindcss': 'latest',
|
||||||
'postcss': 'latest',
|
|
||||||
'autoprefixer': 'latest',
|
'autoprefixer': 'latest',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -123,7 +131,7 @@ function constructFiles(componentName: string, style: Style, sources: Record<str
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// @ts-expect-error componentName migth not exist in Index
|
// @ts-expect-error componentName might not exist in Index
|
||||||
const registryDependencies = demoIndex[style][componentName as any]?.registryDependencies?.filter(i => i !== 'utils')
|
const registryDependencies = demoIndex[style][componentName as any]?.registryDependencies?.filter(i => i !== 'utils')
|
||||||
|
|
||||||
const files = {
|
const files = {
|
||||||
|
|
@ -145,15 +153,6 @@ function constructFiles(componentName: string, style: Style, sources: Record<str
|
||||||
content: tailwindConfigRaw,
|
content: tailwindConfigRaw,
|
||||||
isBinary: false,
|
isBinary: false,
|
||||||
},
|
},
|
||||||
'postcss.config.js': {
|
|
||||||
content: `module.exports = {
|
|
||||||
plugins: {
|
|
||||||
tailwindcss: {},
|
|
||||||
autoprefixer: {},
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
isBinary: false,
|
|
||||||
},
|
|
||||||
'tsconfig.json': {
|
'tsconfig.json': {
|
||||||
content: `{
|
content: `{
|
||||||
"$schema": "https://json.schemastore.org/tsconfig",
|
"$schema": "https://json.schemastore.org/tsconfig",
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "www",
|
"name": "www",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.10.4",
|
"version": "0.11.3",
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
|
|
@ -12,64 +12,70 @@
|
||||||
"typecheck": "vue-tsc",
|
"typecheck": "vue-tsc",
|
||||||
"typecheck:registry": "vue-tsc -p tsconfig.registry.json",
|
"typecheck:registry": "vue-tsc -p tsconfig.registry.json",
|
||||||
"build:registry": "tsx ./scripts/build-registry.ts",
|
"build:registry": "tsx ./scripts/build-registry.ts",
|
||||||
"build:registry-strict": "pnpm typecheck:registry && tsx ./scripts/build-registry.ts"
|
"build:registry-strict": "pnpm typecheck:registry && tsx ./scripts/build-registry.ts",
|
||||||
|
"docs:gen": "tsx ./scripts/autogen.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@formkit/auto-animate": "^0.8.2",
|
"@formkit/auto-animate": "^0.8.2",
|
||||||
"@internationalized/date": "^3.5.2",
|
"@internationalized/date": "^3.5.5",
|
||||||
"@radix-icons/vue": "^1.0.0",
|
"@radix-icons/vue": "^1.0.0",
|
||||||
"@stackblitz/sdk": "^1.9.0",
|
"@stackblitz/sdk": "^1.11.0",
|
||||||
"@tanstack/vue-table": "^8.16.0",
|
"@tanstack/vue-table": "^8.20.5",
|
||||||
"@unovis/ts": "^1.4.0",
|
"@unovis/ts": "^1.4.4",
|
||||||
"@unovis/vue": "^1.4.0",
|
"@unovis/vue": "^1.4.4",
|
||||||
"@vee-validate/zod": "^4.12.6",
|
"@vee-validate/zod": "^4.13.2",
|
||||||
"@vueuse/core": "^10.9.0",
|
"@vueuse/core": "^11.1.0",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"codesandbox": "^2.2.3",
|
"codesandbox": "^2.2.3",
|
||||||
"date-fns": "^3.6.0",
|
"date-fns": "^4.1.0",
|
||||||
"embla-carousel": "^8.0.2",
|
"embla-carousel-autoplay": "^8.3.0",
|
||||||
"embla-carousel-autoplay": "^8.0.2",
|
"embla-carousel-vue": "^8.3.0",
|
||||||
"embla-carousel-vue": "^8.0.2",
|
"lucide-vue-next": "^0.441.0",
|
||||||
"lucide-vue-next": "^0.359.0",
|
"magic-string": "^0.30.11",
|
||||||
"magic-string": "^0.30.10",
|
"radix-vue": "catalog:",
|
||||||
"radix-vue": "^1.7.2",
|
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"v-calendar": "^3.1.2",
|
"v-calendar": "^3.1.2",
|
||||||
"vaul-vue": "^0.1.0",
|
"vaul-vue": "^0.2.0",
|
||||||
"vee-validate": "4.12.5",
|
"vee-validate": "4.13.2",
|
||||||
"vue": "^3.4.24",
|
"vue": "^3.5.6",
|
||||||
"vue-sonner": "^1.1.2",
|
"vue-sonner": "^1.1.5",
|
||||||
"vue-wrap-balancer": "^1.1.3",
|
"vue-wrap-balancer": "^1.2.1",
|
||||||
"zod": "^3.23.3"
|
"zod": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@iconify-json/lucide": "^1.1.180",
|
"@babel/traverse": "^7.25.6",
|
||||||
"@iconify-json/ph": "^1.1.12",
|
"@iconify-json/gravity-ui": "^1.1.3",
|
||||||
|
"@iconify-json/lucide": "^1.1.198",
|
||||||
|
"@iconify-json/ph": "^1.1.13",
|
||||||
"@iconify-json/radix-icons": "^1.1.14",
|
"@iconify-json/radix-icons": "^1.1.14",
|
||||||
"@iconify-json/simple-icons": "^1.1.94",
|
"@iconify-json/ri": "^1.1.21",
|
||||||
"@iconify-json/tabler": "^1.1.106",
|
"@iconify-json/simple-icons": "^1.1.108",
|
||||||
|
"@iconify-json/tabler": "^1.1.116",
|
||||||
"@iconify/vue": "^4.1.2",
|
"@iconify/vue": "^4.1.2",
|
||||||
"@oxc-parser/wasm": "^0.1.0",
|
"@oxc-parser/wasm": "catalog:",
|
||||||
"@shikijs/transformers": "^1.3.0",
|
"@shikijs/transformers": "^1.17.7",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/node": "^20.12.7",
|
"@types/node": "^22.5.5",
|
||||||
"@vitejs/plugin-vue": "^5.0.4",
|
"@vitejs/plugin-vue": "^5.1.4",
|
||||||
"@vitejs/plugin-vue-jsx": "^3.1.0",
|
"@vitejs/plugin-vue-jsx": "^4.0.1",
|
||||||
"@vue/compiler-core": "^3.4.24",
|
"@vue/compiler-core": "^3.5.6",
|
||||||
"@vue/compiler-dom": "^3.4.24",
|
"@vue/compiler-dom": "^3.5.6",
|
||||||
"@vue/tsconfig": "^0.5.1",
|
"@vue/tsconfig": "^0.5.1",
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.20",
|
||||||
|
"fast-glob": "^3.3.2",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
|
"markdown-it": "^14.1.0",
|
||||||
"pathe": "^1.1.2",
|
"pathe": "^1.1.2",
|
||||||
"rimraf": "^5.0.5",
|
"rimraf": "^6.0.1",
|
||||||
"shiki": "^1.3.0",
|
"shiki": "^1.22.1",
|
||||||
"tailwind-merge": "^2.3.0",
|
"tailwind-merge": "^2.5.2",
|
||||||
"tailwindcss": "^3.4.3",
|
"tailwindcss": "^3.4.12",
|
||||||
"tsx": "^4.7.2",
|
"tsx": "^4.19.1",
|
||||||
"typescript": "^5.4.5",
|
"typescript": "catalog:",
|
||||||
"unplugin-icons": "^0.18.5",
|
"unplugin-icons": "^0.19.3",
|
||||||
"vitepress": "^1.1.3",
|
"vitepress": "^1.3.4",
|
||||||
"vue-tsc": "^2.0.14"
|
"vue-component-meta": "^2.1.6",
|
||||||
|
"vue-tsc": "^2.1.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
150
apps/www/scripts/autogen.ts
Normal file
150
apps/www/scripts/autogen.ts
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
import type { ComponentMeta, MetaCheckerOptions, PropertyMeta, PropertyMetaSchema } from 'vue-component-meta'
|
||||||
|
import { existsSync, mkdirSync, writeFileSync } from 'node:fs'
|
||||||
|
import { join, parse, resolve } from 'node:path'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
import fg from 'fast-glob'
|
||||||
|
import MarkdownIt from 'markdown-it'
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,10 +4,10 @@ import { template } from 'lodash-es'
|
||||||
import { rimraf } from 'rimraf'
|
import { rimraf } from 'rimraf'
|
||||||
|
|
||||||
import { colorMapping, colors } from '../src/lib/registry/colors'
|
import { colorMapping, colors } from '../src/lib/registry/colors'
|
||||||
|
import { buildRegistry } from '../src/lib/registry/registry'
|
||||||
import { registrySchema } from '../src/lib/registry/schema'
|
import { registrySchema } from '../src/lib/registry/schema'
|
||||||
import { styles } from '../src/lib/registry/styles'
|
import { styles } from '../src/lib/registry/styles'
|
||||||
import { themes } from '../src/lib/registry/themes'
|
import { themes } from '../src/lib/registry/themes'
|
||||||
import { buildRegistry } from '../src/lib/registry/registry'
|
|
||||||
|
|
||||||
const REGISTRY_PATH = path.join(process.cwd(), 'src/public/registry')
|
const REGISTRY_PATH = path.join(process.cwd(), 'src/public/registry')
|
||||||
|
|
||||||
|
|
@ -80,10 +80,17 @@ for (const style of styles) {
|
||||||
continue
|
continue
|
||||||
|
|
||||||
const files = item.files?.map((file) => {
|
const files = item.files?.map((file) => {
|
||||||
let content = fs.readFileSync(
|
let content: string = ''
|
||||||
path.join(process.cwd(), 'src/lib/registry', style.name, file),
|
|
||||||
'utf8',
|
try {
|
||||||
)
|
content = fs.readFileSync(
|
||||||
|
path.join(process.cwd(), 'src/lib/registry', style.name, file),
|
||||||
|
'utf8',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log(`'${file}' is missing`)
|
||||||
|
}
|
||||||
|
|
||||||
// Replace Windows-style newlines with Unix-style newlines
|
// Replace Windows-style newlines with Unix-style newlines
|
||||||
content = content.replace(/\r\n/g, newLine)
|
content = content.replace(/\r\n/g, newLine)
|
||||||
|
|
@ -99,7 +106,7 @@ for (const style of styles) {
|
||||||
files,
|
files,
|
||||||
}
|
}
|
||||||
|
|
||||||
const payloadStr = JSON.stringify(payload, null, 2).replace(/\r\n/g, newLine)
|
const payloadStr = `${JSON.stringify(payload, null, 2).replace(/\r\n/g, newLine)}\n`
|
||||||
|
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
path.join(targetPath, `${item.name}.json`),
|
path.join(targetPath, `${item.name}.json`),
|
||||||
|
|
@ -282,8 +289,8 @@ for (const baseColor of ['slate', 'gray', 'zinc', 'neutral', 'stone', 'lime']) {
|
||||||
for (const [key, value] of Object.entries(values)) {
|
for (const [key, value] of Object.entries(values)) {
|
||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
const resolvedColor = value.replace(
|
const resolvedColor = value.replace(
|
||||||
/{{base}}-/g,
|
/\{\{base\}\}-/g,
|
||||||
`${baseColor}-`,
|
`${baseColor}-`,
|
||||||
)
|
)
|
||||||
base.inlineColors[mode][key] = resolvedColor
|
base.inlineColors[mode][key] = resolvedColor
|
||||||
|
|
||||||
|
|
@ -396,4 +403,4 @@ fs.writeFileSync(
|
||||||
'utf8',
|
'utf8',
|
||||||
)
|
)
|
||||||
|
|
||||||
console.log('✅ Done!')
|
console.log('✅ Done!!')
|
||||||
|
|
|
||||||
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']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,139 @@
|
||||||
---
|
---
|
||||||
title: Changelog
|
title: Changelog
|
||||||
|
description: Latest updates and announcements.
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## June 2024
|
||||||
|
|
||||||
|
### New Component - Number Field
|
||||||
|
A new component has been added to the project [`NumberField`](/docs/components/number-field.html).
|
||||||
|
|
||||||
|
A number field allows a user to enter a number and increment or decrement the value using stepper buttons.
|
||||||
|
|
||||||
|
<ComponentPreview name="NumberFieldDemo" class="max-w-[180px]" />
|
||||||
|
|
||||||
|
## May 2024
|
||||||
|
|
||||||
|
### New Component - Charts
|
||||||
|
Several kinds of chart components has been added to the project.
|
||||||
|
|
||||||
|
Charts are versatile visualization tools, allowing users to represent data using various options for effective analysis.
|
||||||
|
|
||||||
|
1. [`Area Chart`](/docs/charts/area) - An area chart visually represents data over time, displaying trends and patterns through filled-in areas under a line graph.
|
||||||
|
|
||||||
|
<ComponentPreview name="AreaChartDemo" />
|
||||||
|
|
||||||
|
2. [`Bar Chart`](/docs/charts/bar) - A line chart visually represents data using rectangular bars of varying lengths to compare quantities across different categories or groups.
|
||||||
|
|
||||||
|
<ComponentPreview name="BarChartDemo" />
|
||||||
|
|
||||||
|
3. [`Donut Chart`](/docs/charts/donut) - A line chart visually represents data in a circular form, similar to a pie chart but with a central void, emphasizing proportions within categories.
|
||||||
|
|
||||||
|
<ComponentPreview name="DonutChartDemo" />
|
||||||
|
|
||||||
|
4. [`Line Chart`](/docs/charts/line) - A line chart visually displays data points connected by straight lines, illustrating trends or relationships over a continuous axis.
|
||||||
|
|
||||||
|
<ComponentPreview name="LineChartDemo" />
|
||||||
|
|
||||||
|
### New Component - Auto Form
|
||||||
|
|
||||||
|
[`Auto Form`](/docs/components/auto-form.html) is a drop-in form builder for your internal and low-priority forms with existing zod schemas.
|
||||||
|
|
||||||
|
For example, if you already have zod schemas for your API and want to create a simple admin panel to edit user profiles, simply pass the schema to AutoForm and you're done.
|
||||||
|
|
||||||
|
The following form has been created by passing a `zod` schema object to our `AutoForm` component.
|
||||||
|
|
||||||
|
<ComponentPreview name="AutoFormBasic" />
|
||||||
|
|
||||||
|
## April 2024
|
||||||
|
|
||||||
|
### Component Updated - Calendar
|
||||||
|
|
||||||
|
The [`Calendar`](/docs/components/calendar.html) component has been updated and is now built on top of the [RadixVue Calendar](https://www.radix-vue.com/components/calendar.html) component, which uses the [@internationalized/date](https://react-spectrum.adobe.com/internationalized/date/index.html) package to handle dates.
|
||||||
|
|
||||||
|
If you're looking for a range calendar, check out the [`Range Calendar`](/docs/components/range-calendar.html) component.
|
||||||
|
|
||||||
|
And if you're looking for a date picker input, check out the [`Date Picker`](/docs/components/date-picker.html) component.
|
||||||
|
|
||||||
|
<ComponentPreview name="CalendarDemo" />
|
||||||
|
|
||||||
|
<ComponentPreview name="RangeCalendarDemo" />
|
||||||
|
|
||||||
|
<ComponentPreview name="DatePickerDemo" />
|
||||||
|
|
||||||
|
### Building Blocks for the Web
|
||||||
|
|
||||||
|
[`Blocks`](/blocks) are composed of different components that can be used to build your apps, with each block being a standalone section of your application. These blocks are fully responsive, accessible, and composable, and are built using the same principles as the other components in `shadcn-vue`.
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<image
|
||||||
|
src="/examples/block-dark.png"
|
||||||
|
:width="1280"
|
||||||
|
:height="727"
|
||||||
|
alt="Building Blocks"
|
||||||
|
class="hidden dark:block"
|
||||||
|
/>
|
||||||
|
<image
|
||||||
|
src="/examples/block-light.png"
|
||||||
|
:width="1280"
|
||||||
|
:height="727"
|
||||||
|
alt="Building Blocks"
|
||||||
|
class="block dark:hidden"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## March 2024
|
||||||
|
|
||||||
|
### New Component - Breadcrumb
|
||||||
|
|
||||||
|
[`Breadcrumb`](/docs/components/breadcrumb.html) displays the path to the current resource using a hierarchy of links.
|
||||||
|
|
||||||
|
<ComponentPreview name="BreadcrumbDemo" />
|
||||||
|
|
||||||
|
### New Component - Pin Input (OTP Input)
|
||||||
|
|
||||||
|
[`Pin Input`](/docs/components/pin-input.html) allows users to input a sequence of one-character alphanumeric inputs.
|
||||||
|
|
||||||
|
<ComponentPreview name="PinInputDemo" />
|
||||||
|
|
||||||
|
### New Component - Resizable
|
||||||
|
|
||||||
|
[`Resizable`](/docs/components/resizable.html) - Accessible resizable panel groups and layouts with keyboard support.
|
||||||
|
|
||||||
|
<ComponentPreview name="ResizableDemo" />
|
||||||
|
|
||||||
|
### New Component - Drawer
|
||||||
|
|
||||||
|
[`Drawer`](/docs/components/drawer.html) - A drawer component for vue that is built on top of [Vaul Vue](https://github.com/radix-vue/vaul-vue).
|
||||||
|
|
||||||
|
<ComponentPreview name="DrawerDemo" />
|
||||||
|
|
||||||
|
## February 2024
|
||||||
|
|
||||||
|
### New Component - Tag Inputs
|
||||||
|
|
||||||
|
[`Tag inputs`](/docs/components/tags-input.html) render tags inside an input, followed by an actual text input.
|
||||||
|
|
||||||
|
<ComponentPreview name="TagsInputDemo" />
|
||||||
|
|
||||||
|
## January 2024
|
||||||
|
|
||||||
|
### New Component - Sonner
|
||||||
|
|
||||||
|
[`Sonner`](/docs/components/sonner.html) is an opinionated toast component for Vue.
|
||||||
|
|
||||||
|
The Sonner component is provided by [vue-sonner](https://vue-sonner.vercel.app/), which is a Vue port of Sonner, originally created by [Emil Kowalski](https://twitter.com/emilkowalski_) for React.
|
||||||
|
|
||||||
|
<ComponentPreview name="SonnerDemo" />
|
||||||
|
|
||||||
|
### New Component - Toggle Group
|
||||||
|
|
||||||
|
[`Toggle Group`](/docs/components/toggle-group.html) - A set of two-state buttons that can be toggled on or off.
|
||||||
|
|
||||||
|
<ComponentPreview name="ToggleGroupDemo" />
|
||||||
|
|
||||||
|
### New Component - Carousel
|
||||||
|
|
||||||
|
[`Carousel`](/docs/components/carousel.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" />
|
||||||
|
|
@ -15,7 +15,7 @@ npx shadcn-vue@latest init
|
||||||
|
|
||||||
You will be asked a few questions to configure `components.json`:
|
You will be asked a few questions to configure `components.json`:
|
||||||
|
|
||||||
```txt:line-numbers
|
```ansi:line-numbers
|
||||||
Would you like to use TypeScript (recommended)? no / yes
|
Would you like to use TypeScript (recommended)? no / yes
|
||||||
Which framework are you using? Vite / Nuxt / Laravel
|
Which framework are you using? Vite / Nuxt / Laravel
|
||||||
Which style would you like to use? › Default
|
Which style would you like to use? › Default
|
||||||
|
|
@ -29,7 +29,7 @@ Configure the import alias for utils: › @/lib/utils
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
|
||||||
```txt
|
```ansi
|
||||||
Usage: shadcn-vue init [options]
|
Usage: shadcn-vue init [options]
|
||||||
|
|
||||||
initialize your project and install dependencies
|
initialize your project and install dependencies
|
||||||
|
|
@ -50,7 +50,7 @@ npx shadcn-vue@latest add [component]
|
||||||
|
|
||||||
You will be presented with a list of components to choose from:
|
You will be presented with a list of components to choose from:
|
||||||
|
|
||||||
```txt
|
```ansi
|
||||||
Which components would you like to add? › Space to select. Return to submit.
|
Which components would you like to add? › Space to select. Return to submit.
|
||||||
|
|
||||||
◯ accordion
|
◯ accordion
|
||||||
|
|
@ -67,7 +67,7 @@ Which components would you like to add? › Space to select. Return to submit.
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
|
||||||
```txt
|
```ansi
|
||||||
Usage: shadcn-vue add [options] [components...]
|
Usage: shadcn-vue add [options] [components...]
|
||||||
|
|
||||||
add components to your project
|
add components to your project
|
||||||
|
|
@ -90,7 +90,7 @@ Use the `update` command to update components in your project. This will overwri
|
||||||
|
|
||||||
We plan on improving this command in the future to improve the update experience.
|
We plan on improving this command in the future to improve the update experience.
|
||||||
|
|
||||||
```txt
|
```ansi
|
||||||
Usage: shadcn-vue update [options] [components...]
|
Usage: shadcn-vue update [options] [components...]
|
||||||
|
|
||||||
update components in your project
|
update components in your project
|
||||||
|
|
|
||||||
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>
|
||||||
|
|
@ -46,7 +46,7 @@ import { AspectRatio } from '@/components/ui/aspect-ratio'
|
||||||
<template>
|
<template>
|
||||||
<div class="w-[450px]">
|
<div class="w-[450px]">
|
||||||
<AspectRatio :ratio="16 / 9">
|
<AspectRatio :ratio="16 / 9">
|
||||||
<img src="..." alt="Image" class="rounded-md object-cover">
|
<img src="..." alt="Image" class="rounded-md object-cover w-full h-full">
|
||||||
</AspectRatio>
|
</AspectRatio>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
549
apps/www/src/content/docs/components/auto-form.md
Normal file
549
apps/www/src/content/docs/components/auto-form.md
Normal file
|
|
@ -0,0 +1,549 @@
|
||||||
|
---
|
||||||
|
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 type { FieldProps } from './interface'
|
||||||
|
import { AutoFormLabel } from '@/ui/auto-form'
|
||||||
|
import { FormControl, FormDescription, FormField, FormItem, FormMessage } from '@/ui/form'
|
||||||
|
import { Input } from '@/ui/input'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import AutoFormLabel from './AutoFormLabel.vue'
|
||||||
|
|
||||||
|
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 { AutoForm } from '@/components/ui/auto-form'
|
||||||
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
|
import { useForm } from 'vee-validate'
|
||||||
|
import * as z from '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" />
|
||||||
|
|
@ -23,8 +23,8 @@ npx shadcn-vue@latest add badge
|
||||||
|
|
||||||
```vue
|
```vue
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type VariantProps, cva } from 'class-variance-authority'
|
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { cva, type VariantProps } from 'class-variance-authority'
|
||||||
|
|
||||||
defineProps<Props>()
|
defineProps<Props>()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ import {
|
||||||
BreadcrumbList,
|
BreadcrumbList,
|
||||||
BreadcrumbPage,
|
BreadcrumbPage,
|
||||||
BreadcrumbSeparator,
|
BreadcrumbSeparator,
|
||||||
} from '@/lib/components/ui/breadcrumb'
|
} from '@/components/ui/breadcrumb'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -58,14 +58,14 @@ Use a custom component as `slot` for `<BreadcrumbSeparator />` to create a custo
|
||||||
|
|
||||||
```vue showLineNumbers {2,20-22}
|
```vue showLineNumbers {2,20-22}
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Slash } from 'lucide-react'
|
|
||||||
import {
|
import {
|
||||||
Breadcrumb,
|
Breadcrumb,
|
||||||
BreadcrumbItem,
|
BreadcrumbItem,
|
||||||
BreadcrumbLink,
|
BreadcrumbLink,
|
||||||
BreadcrumbList,
|
BreadcrumbList,
|
||||||
BreadcrumbSeparator,
|
BreadcrumbSeparator,
|
||||||
} from '@/lib/components/ui/breadcrumb'
|
} from '@/components/ui/breadcrumb'
|
||||||
|
import { Slash } from 'lucide-vue-next'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -99,6 +99,8 @@ You can compose `<BreadcrumbItem />` with a `<DropdownMenu />` to create a dropd
|
||||||
|
|
||||||
```vue showLineNumbers {2-7,16-26}
|
```vue showLineNumbers {2-7,16-26}
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { BreadcrumbItem } from '@/components/ui/breadcrumb'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
|
|
@ -106,8 +108,6 @@ import {
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from '@/lib/components/ui/dropdown-menu'
|
} from '@/lib/components/ui/dropdown-menu'
|
||||||
|
|
||||||
import { BreadcrumbItem } from '@/lib/components/ui/breadcrumb'
|
|
||||||
|
|
||||||
import ChevronDownIcon from '~icons/radix-icons/chevron-down'
|
import ChevronDownIcon from '~icons/radix-icons/chevron-down'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -143,7 +143,7 @@ import {
|
||||||
BreadcrumbEllipsis,
|
BreadcrumbEllipsis,
|
||||||
BreadcrumbItem,
|
BreadcrumbItem,
|
||||||
BreadcrumbList,
|
BreadcrumbList,
|
||||||
} from '@/lib/components/ui/breadcrumb'
|
} from '@/components/ui/breadcrumb'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -169,13 +169,13 @@ To use a custom link component from your routing library, you can use the `asChi
|
||||||
|
|
||||||
```vue showLineNumbers {15-19}
|
```vue showLineNumbers {15-19}
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { RouterLink } from 'vue-router'
|
|
||||||
import {
|
import {
|
||||||
Breadcrumb,
|
Breadcrumb,
|
||||||
BreadcrumbItem,
|
BreadcrumbItem,
|
||||||
BreadcrumbLink,
|
BreadcrumbLink,
|
||||||
BreadcrumbList,
|
BreadcrumbList,
|
||||||
} from '@/lib/components/ui/breadcrumb'
|
} from '@/components/ui/breadcrumb'
|
||||||
|
import { RouterLink } from 'vue-router'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ npx shadcn-vue@latest add button
|
||||||
|
|
||||||
```vue
|
```vue
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { cva } from 'class-variance-authority'
|
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { cva } from 'class-variance-authority'
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
|
'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,10 @@ If you're looking for a range calendar, check out the [Range Calendar](/docs/com
|
||||||
```bash
|
```bash
|
||||||
npx shadcn-vue@latest add calendar
|
npx shadcn-vue@latest add calendar
|
||||||
```
|
```
|
||||||
|
::: tip
|
||||||
|
The component depends on the [@internationalized/date](https://react-spectrum.adobe.com/internationalized/date/index.html) package, which solves a lot of the problems that come with working with dates and times in JavaScript.
|
||||||
|
Check [Dates & Times in Radix Vue](https://www.radix-vue.com/guides/dates.html) for more information and installation instructions.
|
||||||
|
:::
|
||||||
|
|
||||||
## Datepicker
|
## Datepicker
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -183,7 +183,7 @@ Use the `@init-api` emit method on `<Carousel />` component to set the instance
|
||||||
|
|
||||||
You can access it through setting a template ref on the `<Carousel />` component.
|
You can access it through setting a template ref on the `<Carousel />` component.
|
||||||
|
|
||||||
```vue:line-numbers {2,5,9}
|
```vue:line-numbers {2,5,10}
|
||||||
<script setup>
|
<script setup>
|
||||||
const carouselContainerRef = ref<InstanceType<typeof Carousel> | null>(null)
|
const carouselContainerRef = ref<InstanceType<typeof Carousel> | null>(null)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,12 +51,12 @@ module.exports = {
|
||||||
|
|
||||||
```vue
|
```vue
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
|
||||||
import {
|
import {
|
||||||
Collapsible,
|
Collapsible,
|
||||||
CollapsibleContent,
|
CollapsibleContent,
|
||||||
CollapsibleTrigger,
|
CollapsibleTrigger,
|
||||||
} from '@/components/ui/collapsible'
|
} from '@/components/ui/collapsible'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
const isOpen = ref(false)
|
const isOpen = ref(false)
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,8 @@ See installation instructions for the [Popover](/docs/components/popover#install
|
||||||
|
|
||||||
```vue
|
```vue
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Check, ChevronsUpDown } from 'lucide-vue-next'
|
|
||||||
|
|
||||||
import { ref } from 'vue'
|
|
||||||
import { cn } from '@/lib/utils'
|
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Command,
|
Command,
|
||||||
CommandEmpty,
|
CommandEmpty,
|
||||||
|
|
@ -40,11 +37,14 @@ import {
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from '@/components/ui/popover'
|
} from '@/components/ui/popover'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { Check, ChevronsUpDown } from 'lucide-vue-next'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
const frameworks = [
|
const frameworks = [
|
||||||
{ value: 'next.js', label: 'Next.js' },
|
{ value: 'next.js', label: 'Next.js' },
|
||||||
{ value: 'sveltekit', label: 'SvelteKit' },
|
{ value: 'sveltekit', label: 'SvelteKit' },
|
||||||
{ value: 'nuxt.js', label: 'Nuxt.js' },
|
{ value: 'nuxt', label: 'Nuxt' },
|
||||||
{ value: 'remix', label: 'Remix' },
|
{ value: 'remix', label: 'Remix' },
|
||||||
{ value: 'astro', label: 'Astro' },
|
{ value: 'astro', label: 'Astro' },
|
||||||
]
|
]
|
||||||
|
|
@ -76,7 +76,7 @@ const value = ref('')
|
||||||
<CommandItem
|
<CommandItem
|
||||||
v-for="framework in frameworks"
|
v-for="framework in frameworks"
|
||||||
:key="framework.value"
|
:key="framework.value"
|
||||||
:value="framework"
|
:value="framework.value"
|
||||||
@select="open = false"
|
@select="open = false"
|
||||||
>
|
>
|
||||||
<Check
|
<Check
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ watch(CmdJ, (v) => {
|
||||||
<span class="text-xs">⌘</span>J
|
<span class="text-xs">⌘</span>J
|
||||||
</kbd>
|
</kbd>
|
||||||
</p>
|
</p>
|
||||||
<CommandDialog :open="open" :on-open-change="handleOpenChange">
|
<CommandDialog :open="open" @update:open="handleOpenChange">
|
||||||
<CommandInput placeholder="Type a command or search..." />
|
<CommandInput placeholder="Type a command or search..." />
|
||||||
<CommandList>
|
<CommandList>
|
||||||
<CommandEmpty>No results found.</CommandEmpty>
|
<CommandEmpty>No results found.</CommandEmpty>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
title: Data Table
|
title: Data Table
|
||||||
description: Powerful table and datagrids built using TanStack Table.
|
description: Powerful table and datagrids built using TanStack Table.
|
||||||
primitive: https://tanstack.com/table/v8/docs/guide/introduction
|
primitive: https://tanstack.com/table/v8/docs/introduction
|
||||||
---
|
---
|
||||||
|
|
||||||
<ComponentPreview name="DataTableDemo" />
|
<ComponentPreview name="DataTableDemo" />
|
||||||
|
|
@ -10,7 +10,7 @@ primitive: https://tanstack.com/table/v8/docs/guide/introduction
|
||||||
|
|
||||||
Every data table or datagrid I've created has been unique. They all behave differently, have specific sorting and filtering requirements, and work with different data sources.
|
Every data table or datagrid I've created has been unique. They all behave differently, have specific sorting and filtering requirements, and work with different data sources.
|
||||||
|
|
||||||
It doesn't make sense to combine all of these variations into a single component. If we do that, we'll lose the flexibility that [headless UI](https://tanstack.com/table/v8/docs/guide/introduction#what-is-headless-ui) provides.
|
It doesn't make sense to combine all of these variations into a single component. If we do that, we'll lose the flexibility that [headless UI](https://tanstack.com/table/latest/docs/introduction#what-is-headless-ui) provides.
|
||||||
|
|
||||||
So instead of a data-table component, I thought it would be more helpful to provide a guide on how to build your own.
|
So instead of a data-table component, I thought it would be more helpful to provide a guide on how to build your own.
|
||||||
|
|
||||||
|
|
@ -55,6 +55,20 @@ npm install @tanstack/vue-table
|
||||||
|
|
||||||
<ComponentPreview name="DataTableColumnPinningDemo" />
|
<ComponentPreview name="DataTableColumnPinningDemo" />
|
||||||
|
|
||||||
|
### Reactive Table
|
||||||
|
|
||||||
|
A reactive table was added in `v8.20.0` of the TanStack Table. You can see the [docs](https://tanstack.com/table/latest/docs/framework/vue/guide/table-state#using-reactive-data) for more information. We added an example where we are randomizing `status` column. One main point is that you need to mutate **full** data, as it is a `shallowRef` object.
|
||||||
|
|
||||||
|
> __*⚠️ `shallowRef` is used under the hood for performance reasons, meaning that the data is not deeply reactive, only the `.value` is. To update the data you have to mutate the data directly.*__
|
||||||
|
|
||||||
|
Relative PR: [Tanstack/table #5687](https://github.com/TanStack/table/pull/5687#issuecomment-2281067245)
|
||||||
|
|
||||||
|
If you want to mutate `props.data`, you should use [`defineModel`](https://vuejs.org/api/sfc-script-setup.html#definemodel).
|
||||||
|
|
||||||
|
There is no difference between using `ref` or `shallowRef` for your data object; it will be automatically mutated by the TanStack Table to `shallowRef`.
|
||||||
|
|
||||||
|
<ComponentPreview name="DataTableReactiveDemo" />
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
We are going to build a table to show recent payments. Here's what our data looks like:
|
We are going to build a table to show recent payments. Here's what our data looks like:
|
||||||
|
|
@ -88,7 +102,7 @@ export const payments: Payment[] = [
|
||||||
|
|
||||||
Start by creating the following file structure:
|
Start by creating the following file structure:
|
||||||
|
|
||||||
```txt
|
```ansi
|
||||||
components
|
components
|
||||||
└── payments
|
└── payments
|
||||||
├── columns.ts
|
├── columns.ts
|
||||||
|
|
@ -97,7 +111,7 @@ Start by creating the following file structure:
|
||||||
└── app.vue
|
└── app.vue
|
||||||
```
|
```
|
||||||
|
|
||||||
I'm using a Nuxt.js example here but this works for any other Vue framework.
|
I'm using a Nuxt example here but this works for any other Vue framework.
|
||||||
|
|
||||||
- `columns.ts` It will contain our column definitions.
|
- `columns.ts` It will contain our column definitions.
|
||||||
- `data-table.vue` It will contain our `<DataTable />` component.
|
- `data-table.vue` It will contain our `<DataTable />` component.
|
||||||
|
|
@ -114,7 +128,7 @@ Let's start by building a basic table.
|
||||||
|
|
||||||
First, we'll define our columns in the `columns.ts` file.
|
First, we'll define our columns in the `columns.ts` file.
|
||||||
|
|
||||||
```ts:line-numbers {1,12-27}
|
```ts:line-numbers
|
||||||
import { h } from 'vue'
|
import { h } from 'vue'
|
||||||
|
|
||||||
export const columns: ColumnDef<Payment>[] = [
|
export const columns: ColumnDef<Payment>[] = [
|
||||||
|
|
@ -146,66 +160,70 @@ formatted, sorted and filtered.
|
||||||
|
|
||||||
Next, we'll create a `<DataTable />` component to render our table.
|
Next, we'll create a `<DataTable />` component to render our table.
|
||||||
|
|
||||||
```vue:line-numbers
|
```vue
|
||||||
<script setup lang="ts" generic="TData, TValue">
|
<script setup lang="ts" generic="TData, TValue">
|
||||||
import type { ColumnDef } from '@tanstack/vue-table'
|
import type { ColumnDef } from '@tanstack/vue-table'
|
||||||
import {
|
import {
|
||||||
FlexRender,
|
Table,
|
||||||
getCoreRowModel,
|
TableBody,
|
||||||
useVueTable,
|
TableCell,
|
||||||
} from "@tanstack/vue-table"
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from '@/components/ui/table'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Table,
|
FlexRender,
|
||||||
TableBody,
|
getCoreRowModel,
|
||||||
TableCell,
|
useVueTable,
|
||||||
TableHead,
|
} from '@tanstack/vue-table'
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
|
||||||
} from "@/components/ui/table"
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
columns: ColumnDef<TData, TValue>[]
|
columns: ColumnDef<TData, TValue>[]
|
||||||
data: TData[]
|
data: TData[]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const table = useVueTable({
|
const table = useVueTable({
|
||||||
get data() { return props.data },
|
get data() { return props.data },
|
||||||
get columns() { return props.columns },
|
get columns() { return props.columns },
|
||||||
getCoreRowModel: getCoreRowModel(),
|
getCoreRowModel: getCoreRowModel(),
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="border rounded-md">
|
<div class="border rounded-md">
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow v-for="headerGroup in table.getHeaderGroups()" :key="headerGroup.id">
|
<TableRow v-for="headerGroup in table.getHeaderGroups()" :key="headerGroup.id">
|
||||||
<TableHead v-for="header in headerGroup.headers" :key="header.id">
|
<TableHead v-for="header in headerGroup.headers" :key="header.id">
|
||||||
<FlexRender v-if="!header.isPlaceholder" :render="header.column.columnDef.header"
|
<FlexRender
|
||||||
:props="header.getContext()" />
|
v-if="!header.isPlaceholder" :render="header.column.columnDef.header"
|
||||||
</TableHead>
|
:props="header.getContext()"
|
||||||
</TableRow>
|
/>
|
||||||
</TableHeader>
|
</TableHead>
|
||||||
<TableBody>
|
</TableRow>
|
||||||
<template v-if="table.getRowModel().rows?.length">
|
</TableHeader>
|
||||||
<TableRow v-for="row in table.getRowModel().rows" :key="row.id"
|
<TableBody>
|
||||||
:data-state="row.getIsSelected() ? 'selected' : undefined">
|
<template v-if="table.getRowModel().rows?.length">
|
||||||
<TableCell v-for="cell in row.getVisibleCells()" :key="cell.id">
|
<TableRow
|
||||||
<FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
|
v-for="row in table.getRowModel().rows" :key="row.id"
|
||||||
</TableCell>
|
:data-state="row.getIsSelected() ? 'selected' : undefined"
|
||||||
</TableRow>
|
>
|
||||||
</template>
|
<TableCell v-for="cell in row.getVisibleCells()" :key="cell.id">
|
||||||
<template v-else>
|
<FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
|
||||||
<TableRow>
|
</TableCell>
|
||||||
<TableCell :colSpan="columns.length" class="h-24 text-center">
|
</TableRow>
|
||||||
No results.
|
</template>
|
||||||
</TableCell>
|
<template v-else>
|
||||||
</TableRow>
|
<TableRow>
|
||||||
</template>
|
<TableCell :colspan="columns.length" class="h-24 text-center">
|
||||||
</TableBody>
|
No results.
|
||||||
</Table>
|
</TableCell>
|
||||||
</div>
|
</TableRow>
|
||||||
|
</template>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -221,12 +239,12 @@ const table = useVueTable({
|
||||||
|
|
||||||
Finally, we'll render our table in our index component.
|
Finally, we'll render our table in our index component.
|
||||||
|
|
||||||
```vue:line-numbers {28}
|
```vue
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue'
|
import type { Payment } from './components/columns'
|
||||||
import { columns } from "./components/columns"
|
import { onMounted, ref } from 'vue'
|
||||||
import type { Payment } from './components/columns';
|
import { columns } from './components/columns'
|
||||||
import DataTable from "./components/DataTable.vue"
|
import DataTable from './components/DataTable.vue'
|
||||||
|
|
||||||
const data = ref<Payment[]>([])
|
const data = ref<Payment[]>([])
|
||||||
|
|
||||||
|
|
@ -234,18 +252,18 @@ async function getData(): Promise<Payment[]> {
|
||||||
// Fetch data from your API here.
|
// Fetch data from your API here.
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
id: "728ed52f",
|
id: '728ed52f',
|
||||||
amount: 100,
|
amount: 100,
|
||||||
status: "pending",
|
status: 'pending',
|
||||||
email: "m@example.com",
|
email: 'm@example.com',
|
||||||
},
|
},
|
||||||
// ...
|
// ...
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
data.value = await getData();
|
data.value = await getData()
|
||||||
});
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -267,23 +285,23 @@ Let's format the amount cell to display the dollar amount. We'll also align the
|
||||||
|
|
||||||
Update the `header` and `cell` definitions for amount as follows:
|
Update the `header` and `cell` definitions for amount as follows:
|
||||||
|
|
||||||
```ts:line-numbers title="components/payments/columns.ts" {5-17}
|
```ts
|
||||||
import { h } from 'vue'
|
import { h } from 'vue'
|
||||||
|
|
||||||
export const columns: ColumnDef<Payment>[] = [
|
export const columns: ColumnDef<Payment>[] = [
|
||||||
{
|
{
|
||||||
accessorKey: "amount",
|
accessorKey: 'amount',
|
||||||
header: () => h('div', { class: 'text-right' }, 'Amount'),
|
header: () => h('div', { class: 'text-right' }, 'Amount'),
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const amount = parseFloat(row.getValue("amount"))
|
const amount = Number.parseFloat(row.getValue('amount'))
|
||||||
const formatted = new Intl.NumberFormat("en-US", {
|
const formatted = new Intl.NumberFormat('en-US', {
|
||||||
style: "currency",
|
style: 'currency',
|
||||||
currency: "USD",
|
currency: 'USD',
|
||||||
}).format(amount)
|
}).format(amount)
|
||||||
|
|
||||||
return h('div', { class: 'text-right font-medium' }, formatted)
|
return h('div', { class: 'text-right font-medium' }, formatted)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
You can use the same approach to format other cells and headers.
|
You can use the same approach to format other cells and headers.
|
||||||
|
|
@ -295,14 +313,13 @@ Let's add row actions to our table. We'll use a `<Dropdown />` component for thi
|
||||||
|
|
||||||
<Steps>
|
<Steps>
|
||||||
|
|
||||||
### Add the following into your `DataTableDropDown.vue` component:
|
### Add the following into your `DataTableDropDown.vue` component
|
||||||
|
|
||||||
```vue:line-numbers
|
```vue
|
||||||
// DataTableDropDown.vue
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { MoreHorizontal } from 'lucide-vue-next'
|
|
||||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'
|
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'
|
||||||
|
import { MoreHorizontal } from 'lucide-vue-next'
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
payment: {
|
payment: {
|
||||||
|
|
@ -334,32 +351,30 @@ function copy(id: string) {
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Update columns definition
|
### Update columns definition
|
||||||
|
|
||||||
Update our columns definition to add a new `actions` column. The `actions` cell returns a `<Dropdown />` component.
|
Update our columns definition to add a new `actions` column. The `actions` cell returns a `<Dropdown />` component.
|
||||||
|
|
||||||
```ts:line-numbers showLineNumber{2,6-16}
|
```ts
|
||||||
import { ColumnDef } from "@tanstack/vue-table"
|
|
||||||
import DropdownAction from '@/components/DataTableDropDown.vue'
|
import DropdownAction from '@/components/DataTableDropDown.vue'
|
||||||
|
import { ColumnDef } from '@tanstack/vue-table'
|
||||||
|
|
||||||
export const columns: ColumnDef<Payment>[] = [
|
export const columns: ColumnDef<Payment>[] = [
|
||||||
// ...
|
// ...
|
||||||
{
|
{
|
||||||
id: 'actions',
|
id: 'actions',
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const payment = row.original
|
const payment = row.original
|
||||||
|
|
||||||
return h('div', { class: 'relative' }, h(DropdownAction, {
|
return h('div', { class: 'relative' }, h(DropdownAction, {
|
||||||
payment,
|
payment,
|
||||||
}))
|
}))
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
You can access the row data using `row.original` in the `cell` function. Use this to handle actions for your row eg. use the `id` to make a DELETE call to your API.
|
You can access the row data using `row.original` in the `cell` function. Use this to handle actions for your row eg. use the `id` to make a DELETE call to your API.
|
||||||
|
|
@ -396,47 +411,45 @@ This will automatically paginate your rows into pages of 10. See the [pagination
|
||||||
|
|
||||||
We can add pagination controls to our table using the `<Button />` component and the `table.previousPage()`, `table.nextPage()` API methods.
|
We can add pagination controls to our table using the `<Button />` component and the `table.previousPage()`, `table.nextPage()` API methods.
|
||||||
|
|
||||||
```vue:line-numbers {3,15,21-39}
|
```vue
|
||||||
<script lang="ts" generic="TData, TValue">
|
<script lang="ts" generic="TData, TValue">
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from '@/components/ui/button'
|
||||||
|
|
||||||
const table = useVueTable({
|
const table = useVueTable({
|
||||||
get data() { return props.data },
|
get data() { return props.data },
|
||||||
get columns() { return props.columns },
|
get columns() { return props.columns },
|
||||||
getCoreRowModel: getCoreRowModel(),
|
getCoreRowModel: getCoreRowModel(),
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
})
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="border rounded-md">
|
<div class="border rounded-md">
|
||||||
<Table>
|
<Table>
|
||||||
{ // .... }
|
{ // .... }
|
||||||
</Table>
|
</Table>
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-end py-4 space-x-2">
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
:disabled="!table.getCanPreviousPage()"
|
|
||||||
@click="table.previousPage()"
|
|
||||||
>
|
|
||||||
Previous
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
:disabled="!table.getCanNextPage()"
|
|
||||||
@click="table.nextPage()"
|
|
||||||
>
|
|
||||||
Next
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex items-center justify-end py-4 space-x-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
:disabled="!table.getCanPreviousPage()"
|
||||||
|
@click="table.previousPage()"
|
||||||
|
>
|
||||||
|
Previous
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
:disabled="!table.getCanNextPage()"
|
||||||
|
@click="table.nextPage()"
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
See [Reusable Components](#reusable-components) section for a more advanced pagination component.
|
See [Reusable Components](#reusable-components) section for a more advanced pagination component.
|
||||||
|
|
@ -449,24 +462,23 @@ Let's make the email column sortable.
|
||||||
|
|
||||||
<Steps>
|
<Steps>
|
||||||
|
|
||||||
### Add the following into your `utils` file:
|
### Add the following into your `utils` file
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import type { Updater } from '@tanstack/vue-table'
|
||||||
|
import type { Ref } from 'vue'
|
||||||
|
|
||||||
```ts:line-numbers {5,6,12-17}
|
|
||||||
import { type ClassValue, clsx } from 'clsx'
|
import { type ClassValue, clsx } from 'clsx'
|
||||||
import { twMerge } from 'tailwind-merge'
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
|
||||||
import type { Updater } from '@tanstack/vue-table'
|
|
||||||
import { type Ref } from 'vue'
|
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs))
|
return twMerge(clsx(inputs))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function valueUpdater<T extends Updater<any>>(updaterOrValue: T, ref: Ref) {
|
export function valueUpdater<T extends Updater<any>>(updaterOrValue: T, ref: Ref) {
|
||||||
ref.value
|
ref.value = typeof updaterOrValue === 'function'
|
||||||
= typeof updaterOrValue === 'function'
|
? updaterOrValue(ref.value)
|
||||||
? updaterOrValue(ref.value)
|
: updaterOrValue
|
||||||
: updaterOrValue
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -474,62 +486,60 @@ The `valueUpdater` function updates a Vue `ref` object's value. It handles both
|
||||||
|
|
||||||
### Update `<DataTable>`
|
### Update `<DataTable>`
|
||||||
|
|
||||||
```vue:line-numbers {4,7,16,34,41-44}
|
```vue:line-numbers {4,14,17,33,40-44}
|
||||||
<script setup lang="ts" generic="TData, TValue">
|
<script setup lang="ts" generic="TData, TValue">
|
||||||
import type {
|
import type {
|
||||||
ColumnDef,
|
ColumnDef,
|
||||||
SortingState,
|
SortingState,
|
||||||
} from '@tanstack/vue-table'
|
} from '@tanstack/vue-table'
|
||||||
|
|
||||||
import { valueUpdater } from '@/lib/utils'
|
|
||||||
|
|
||||||
import { ArrowUpDown, ChevronDown } from 'lucide-vue-next'
|
import { ArrowUpDown, ChevronDown } from 'lucide-vue-next'
|
||||||
import { h, ref } from 'vue'
|
import { h, ref } from 'vue'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
FlexRender,
|
FlexRender,
|
||||||
getCoreRowModel,
|
getCoreRowModel,
|
||||||
getPaginationRowModel,
|
getPaginationRowModel,
|
||||||
getSortedRowModel,
|
getSortedRowModel,
|
||||||
useVueTable,
|
useVueTable,
|
||||||
} from "@tanstack/vue-table"
|
} from '@tanstack/vue-table'
|
||||||
|
import { valueUpdater } from '@/lib/utils'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
TableCell,
|
TableCell,
|
||||||
TableHead,
|
TableHead,
|
||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table"
|
} from '@/components/ui/table'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
columns: ColumnDef<TData, TValue>[]
|
columns: ColumnDef<TData, TValue>[]
|
||||||
data: TData[]
|
data: TData[]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const sorting = ref<SortingState>([])
|
const sorting = ref<SortingState>([])
|
||||||
|
|
||||||
const table = useVueTable({
|
const table = useVueTable({
|
||||||
get data() { return props.data },
|
get data() { return props.data },
|
||||||
get columns() { return props.columns },
|
get columns() { return props.columns },
|
||||||
getCoreRowModel: getCoreRowModel(),
|
getCoreRowModel: getCoreRowModel(),
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
getSortedRowModel: getSortedRowModel(),
|
getSortedRowModel: getSortedRowModel(),
|
||||||
onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting),
|
onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting),
|
||||||
state: {
|
state: {
|
||||||
get sorting() { return sorting.value },
|
get sorting() { return sorting.value },
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="border rounded-md">
|
<div class="border rounded-md">
|
||||||
<Table>{ ... }</Table>
|
<Table>{ ... }</Table>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -870,11 +880,237 @@ This adds a checkbox to each row and a checkbox in the header to select all rows
|
||||||
|
|
||||||
You can show the number of selected rows using the `table.getFilteredSelectedRowModel()` API.
|
You can show the number of selected rows using the `table.getFilteredSelectedRowModel()` API.
|
||||||
|
|
||||||
```vue
|
```vue:line-numbers {8-11}
|
||||||
<div class="flex-1 text-sm text-muted-foreground">
|
<template>
|
||||||
{{ table.getFilteredSelectedRowModel().rows.length }} of
|
<div>
|
||||||
{{ table.getFilteredRowModel().rows.length }} row(s) selected.
|
<div class="border rounded-md">
|
||||||
</div>
|
<Table />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-end space-x-2 py-4">
|
||||||
|
<div class="flex-1 text-sm text-muted-foreground">
|
||||||
|
{{ table.getFilteredSelectedRowModel().rows.length }} of
|
||||||
|
{{ table.getFilteredRowModel().rows.length }} row(s) selected.
|
||||||
|
</div>
|
||||||
|
<div class="space-x-2">
|
||||||
|
<PaginationButtons />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
|
||||||
|
## Expanding
|
||||||
|
|
||||||
|
Let's make rows expandable.
|
||||||
|
|
||||||
|
### Update `<DataTable>`
|
||||||
|
|
||||||
|
```vue:line-numbers {7,30,43,52,57,63,103-116}
|
||||||
|
<script setup lang="ts" generic="TData, TValue">
|
||||||
|
import type {
|
||||||
|
ColumnDef,
|
||||||
|
ColumnFiltersState,
|
||||||
|
SortingState,
|
||||||
|
VisibilityState,
|
||||||
|
ExpandedState,
|
||||||
|
} from '@tanstack/vue-table'
|
||||||
|
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuCheckboxItem,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from '@/components/ui/dropdown-menu'
|
||||||
|
|
||||||
|
import { valueUpdater } from '@/lib/utils'
|
||||||
|
|
||||||
|
import { ArrowUpDown, ChevronDown } from 'lucide-vue-next'
|
||||||
|
import { Input } from '@/components/ui/input'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { h, ref } from 'vue'
|
||||||
|
|
||||||
|
import {
|
||||||
|
FlexRender,
|
||||||
|
getCoreRowModel,
|
||||||
|
getPaginationRowModel,
|
||||||
|
getFilteredRowModel,
|
||||||
|
getSortedRowModel,
|
||||||
|
getExpandedRowModel,
|
||||||
|
useVueTable,
|
||||||
|
} from "@tanstack/vue-table"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
columns: ColumnDef<TData, TValue>[]
|
||||||
|
data: TData[]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const sorting = ref<SortingState>([])
|
||||||
|
const columnFilters = ref<ColumnFiltersState>([])
|
||||||
|
const columnVisibility = ref<VisibilityState>({})
|
||||||
|
const rowSelection = ref({})
|
||||||
|
const expanded = ref<ExpandedState>({})
|
||||||
|
|
||||||
|
const table = useVueTable({
|
||||||
|
get data() { return props.data },
|
||||||
|
get columns() { return props.columns },
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
|
getSortedRowModel: getSortedRowModel(),
|
||||||
|
getFilteredRowModel: getFilteredRowModel(),
|
||||||
|
getExpandedRowModel: getExpandedRowModel(),
|
||||||
|
onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting),
|
||||||
|
onColumnFiltersChange: updaterOrValue => valueUpdater(updaterOrValue, columnFilters),
|
||||||
|
onColumnVisibilityChange: updaterOrValue => valueUpdater(updaterOrValue, columnVisibility),
|
||||||
|
onRowSelectionChange: updaterOrValue => valueUpdater(updaterOrValue, rowSelection),
|
||||||
|
onExpandedChange: updaterOrValue => valueUpdater(updaterOrValue, expanded),
|
||||||
|
state: {
|
||||||
|
get sorting() { return sorting.value },
|
||||||
|
get columnFilters() { return columnFilters.value },
|
||||||
|
get columnVisibility() { return columnVisibility.value },
|
||||||
|
get rowSelection() { return rowSelection.value },
|
||||||
|
get expanded() { return expanded.value },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center py-4">
|
||||||
|
<Input class="max-w-sm" placeholder="Filter emails..."
|
||||||
|
:model-value="table.getColumn('email')?.getFilterValue() as string"
|
||||||
|
@update:model-value=" table.getColumn('email')?.setFilterValue($event)" />
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger as-child>
|
||||||
|
<Button variant="outline" class="ml-auto">
|
||||||
|
Columns
|
||||||
|
<ChevronDown class="w-4 h-4 ml-2" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<DropdownMenuCheckboxItem
|
||||||
|
v-for="column in table.getAllColumns().filter((column) => column.getCanHide())" :key="column.id"
|
||||||
|
class="capitalize" :checked="column.getIsVisible()" @update:checked="(value) => {
|
||||||
|
column.toggleVisibility(!!value)
|
||||||
|
}">
|
||||||
|
{{ column.id }}
|
||||||
|
</DropdownMenuCheckboxItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</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">
|
||||||
|
<template v-for="row in table.getRowModel().rows" :key="row.id">
|
||||||
|
<TableRow :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>
|
||||||
|
<TableRow v-if="row.getIsExpanded()">
|
||||||
|
<TableCell :colspan="row.getAllCells().length">
|
||||||
|
{{ JSON.stringify(row.original) }}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell :colSpan="columns.length" class="h-24 text-center">
|
||||||
|
No results.
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</template>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add the expand action to the `DataTableDropDown.vue` component
|
||||||
|
|
||||||
|
```vue:line-numbers {12-14,34-36}
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { MoreHorizontal } from 'lucide-vue-next'
|
||||||
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
payment: {
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
}>()
|
||||||
|
|
||||||
|
defineEmits<{
|
||||||
|
(e: 'expand'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
function copy(id: string) {
|
||||||
|
navigator.clipboard.writeText(id)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger as-child>
|
||||||
|
<Button variant="ghost" class="w-8 h-8 p-0">
|
||||||
|
<span class="sr-only">Open menu</span>
|
||||||
|
<MoreHorizontal class="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||||
|
<DropdownMenuItem @click="copy(payment.id)">
|
||||||
|
Copy payment ID
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem @click="$emit('expand')">
|
||||||
|
Expand
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem>View customer</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>View payment details</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Make rows expandable
|
||||||
|
|
||||||
|
Now we can update the action cell to add the expand control.
|
||||||
|
|
||||||
|
```vue:line-numbers {11}
|
||||||
|
<script setup lang="ts">
|
||||||
|
export const columns: ColumnDef<Payment>[] = [
|
||||||
|
{
|
||||||
|
id: 'actions',
|
||||||
|
enableHiding: false,
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const payment = row.original
|
||||||
|
|
||||||
|
return h('div', { class: 'relative' }, h(DropdownAction, {
|
||||||
|
payment,
|
||||||
|
onExpand: row.toggleExpanded,
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
</Steps>
|
</Steps>
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,10 @@ import {
|
||||||
|
|
||||||
<ComponentPreview name="DialogScrollOverlayDemo" />
|
<ComponentPreview name="DialogScrollOverlayDemo" />
|
||||||
|
|
||||||
|
### Form
|
||||||
|
|
||||||
|
<ComponentPreview name="DialogForm" />
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
To activate the `Dialog` component from within a `Context Menu` or `Dropdown Menu`, you must encase the `Context Menu` or `Dropdown Menu` component in the `Dialog` component. For more information, refer to the linked issue [here](https://github.com/radix-ui/primitives/issues/1836).
|
To activate the `Dialog` component from within a `Context Menu` or `Dropdown Menu`, you must encase the `Context Menu` or `Dropdown Menu` component in the `Dialog` component. For more information, refer to the linked issue [here](https://github.com/radix-ui/primitives/issues/1836).
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ The `<Form />` component is a wrapper around the `vee-validate` library. It prov
|
||||||
- Composable components for building forms.
|
- Composable components for building forms.
|
||||||
- A `<FormField />` component for building controlled form fields.
|
- A `<FormField />` component for building controlled form fields.
|
||||||
- Form validation using `zod`.
|
- Form validation using `zod`.
|
||||||
- Applies the correct `aria` attributes to form fields based on states, handle unqiue IDs
|
- Applies the correct `aria` attributes to form fields based on states, handle unique IDs
|
||||||
- Built to work with all Radix Vue components.
|
- Built to work with all Radix Vue components.
|
||||||
- Bring your own schema library. We use `zod` but you can use any other supported schema validation you want, like [`yup`](https://github.com/jquense/yup) or [`valibot`](https://valibot.dev/).
|
- Bring your own schema library. We use `zod` but you can use any other supported schema validation you want, like [`yup`](https://github.com/jquense/yup) or [`valibot`](https://valibot.dev/).
|
||||||
- **You have full control over the markup and styling.**
|
- **You have full control over the markup and styling.**
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ import {
|
||||||
|
|
||||||
### Link Component
|
### Link Component
|
||||||
|
|
||||||
When using the Nuxt.js `<NuxtLink />` component, you can use `navigationMenuTriggerStyle()` to apply the correct styles to the trigger.
|
When using the Nuxt `<NuxtLink />` component, you can use `navigationMenuTriggerStyle()` to apply the correct styles to the trigger.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { navigationMenuTriggerStyle } from '@/components/ui/navigation-menu'
|
import { navigationMenuTriggerStyle } from '@/components/ui/navigation-menu'
|
||||||
|
|
|
||||||
71
apps/www/src/content/docs/components/number-field.md
Normal file
71
apps/www/src/content/docs/components/number-field.md
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
---
|
||||||
|
title: Number Field
|
||||||
|
description: A number field allows a user to enter a number and increment or decrement the value using stepper buttons.
|
||||||
|
source: apps/www/src/lib/registry/default/ui/number-field
|
||||||
|
primitive: https://www.radix-vue.com/components/number-field.html
|
||||||
|
---
|
||||||
|
|
||||||
|
<ComponentPreview name="NumberFieldDemo" class="max-w-[180px]" />
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
<TabPreview name="CLI">
|
||||||
|
<template #CLI>
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn-vue@latest add number-field
|
||||||
|
```
|
||||||
|
</template>
|
||||||
|
</TabPreview>
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Label } from '@/components/ui/label'
|
||||||
|
import {
|
||||||
|
NumberField,
|
||||||
|
NumberFieldContent,
|
||||||
|
NumberFieldDecrement,
|
||||||
|
NumberFieldIncrement,
|
||||||
|
NumberFieldInput,
|
||||||
|
} from '@/components/ui/number-field'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NumberField>
|
||||||
|
<Label>Age</Label>
|
||||||
|
<NumberFieldContent>
|
||||||
|
<NumberFieldDecrement />
|
||||||
|
<NumberFieldInput />
|
||||||
|
<NumberFieldIncrement />
|
||||||
|
</NumberFieldContent>
|
||||||
|
</NumberField>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Default
|
||||||
|
|
||||||
|
<ComponentPreview name="NumberFieldDemo" class="max-w-[180px]" />
|
||||||
|
|
||||||
|
### Disabled
|
||||||
|
|
||||||
|
<ComponentPreview name="NumberFieldDisabled" class="max-w-[180px]" />
|
||||||
|
|
||||||
|
### Decimal
|
||||||
|
|
||||||
|
<ComponentPreview name="NumberFieldDecimal" class="max-w-[180px]" />
|
||||||
|
|
||||||
|
### Percentage
|
||||||
|
|
||||||
|
<ComponentPreview name="NumberFieldPercentage" class="max-w-[180px]" />
|
||||||
|
|
||||||
|
### Currency
|
||||||
|
|
||||||
|
<ComponentPreview name="NumberFieldCurrency" class="max-w-[220px]" />
|
||||||
|
|
||||||
|
### Form
|
||||||
|
|
||||||
|
<ComponentPreview name="NumberFieldForm" class="max-w-xs" />
|
||||||
|
|
@ -17,6 +17,10 @@ npx shadcn-vue@latest add pagination
|
||||||
|
|
||||||
```vue
|
```vue
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
} from '@/components/ui/button'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Pagination,
|
Pagination,
|
||||||
PaginationEllipsis,
|
PaginationEllipsis,
|
||||||
|
|
@ -27,10 +31,6 @@ import {
|
||||||
PaginationNext,
|
PaginationNext,
|
||||||
PaginationPrev,
|
PaginationPrev,
|
||||||
} from '@/components/ui/pagination'
|
} from '@/components/ui/pagination'
|
||||||
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
} from '@/components/ui/button'
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,6 @@ import { Separator } from '@/components/ui/separator'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Separator />
|
<Separator label="Or" />
|
||||||
</template>
|
</template>
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ import {
|
||||||
<SheetTrigger>Open</SheetTrigger>
|
<SheetTrigger>Open</SheetTrigger>
|
||||||
<SheetContent>
|
<SheetContent>
|
||||||
<SheetHeader>
|
<SheetHeader>
|
||||||
<SheetTitle>Are you sure absolutely sure?</SheetTitle>
|
<SheetTitle>Are you absolutely sure?</SheetTitle>
|
||||||
<SheetDescription>
|
<SheetDescription>
|
||||||
This action cannot be undone. This will permanently delete your account
|
This action cannot be undone. This will permanently delete your account
|
||||||
and remove your data from our servers.
|
and remove your data from our servers.
|
||||||
|
|
@ -61,7 +61,7 @@ You can adjust the size of the sheet using CSS classes:
|
||||||
<SheetTrigger>Open</SheetTrigger>
|
<SheetTrigger>Open</SheetTrigger>
|
||||||
<SheetContent class="w-[400px] sm:w-[540px]">
|
<SheetContent class="w-[400px] sm:w-[540px]">
|
||||||
<SheetHeader>
|
<SheetHeader>
|
||||||
<SheetTitle>Are you sure absolutely sure?</SheetTitle>
|
<SheetTitle>Are you absolutely sure?</SheetTitle>
|
||||||
<SheetDescription>
|
<SheetDescription>
|
||||||
This action cannot be undone. This will permanently delete your account
|
This action cannot be undone. This will permanently delete your account
|
||||||
and remove your data from our servers.
|
and remove your data from our servers.
|
||||||
|
|
|
||||||
12
apps/www/src/content/docs/components/sidebar.md
Normal file
12
apps/www/src/content/docs/components/sidebar.md
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
---
|
||||||
|
title: Sidebar
|
||||||
|
description: A composable, themeable and customizable sidebar component.
|
||||||
|
---
|
||||||
|
|
||||||
|
<BlockPreview name="Sidebar07" ></BlockPreview>
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn-vue@latest add sidebar
|
||||||
|
```
|
||||||
|
|
@ -41,8 +41,8 @@ import { Toaster } from '@/components/ui/sonner'
|
||||||
|
|
||||||
```vue
|
```vue
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { toast } from 'vue-sonner'
|
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { toast } from 'vue-sonner'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -61,3 +61,23 @@ import { Button } from '@/components/ui/button'
|
||||||
</Button>
|
</Button>
|
||||||
</template>
|
</template>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Sonner with Dialog
|
||||||
|
|
||||||
|
Related issue https://github.com/radix-vue/shadcn-vue/issues/462
|
||||||
|
|
||||||
|
Add `pointer-events-auto` class to Toaster component in your `App.vue` file:
|
||||||
|
|
||||||
|
```vue {6}
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Toaster } from '@/components/ui/sonner'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Toaster class="pointer-events-auto" />
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
<ComponentPreview name="SonnerWithDialog" />
|
||||||
|
|
|
||||||
64
apps/www/src/content/docs/components/stepper.md
Normal file
64
apps/www/src/content/docs/components/stepper.md
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
---
|
||||||
|
title: Stepper
|
||||||
|
description: A set of steps that are used to indicate progress through a multi-step process.
|
||||||
|
source: apps/www/src/lib/registry/default/ui/stepper
|
||||||
|
primitive: https://www.radix-vue.com/components/stepper.html
|
||||||
|
---
|
||||||
|
|
||||||
|
<ComponentPreview name="StepperDemo" />
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn-vue@latest add stepper
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
Stepper,
|
||||||
|
StepperDescription,
|
||||||
|
StepperIndicator,
|
||||||
|
StepperItem,
|
||||||
|
StepperSeparator,
|
||||||
|
StepperTitle,
|
||||||
|
StepperTrigger,
|
||||||
|
} from '@/components/ui/stepper'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Stepper>
|
||||||
|
<StepperItem :step="1">
|
||||||
|
<StepperTrigger>
|
||||||
|
<StepperIndicator>1</StepperIndicator>
|
||||||
|
<StepperTitle>Step 1</StepperTitle>
|
||||||
|
<StepperDescription>This is the first step</StepperDescription>
|
||||||
|
</StepperTrigger>
|
||||||
|
<StepperSeparator />
|
||||||
|
</StepperItem>
|
||||||
|
<StepperItem :step="2">
|
||||||
|
<StepperTrigger>
|
||||||
|
<StepperIndicator>2</StepperIndicator>
|
||||||
|
<StepperTitle>Step 2</StepperTitle>
|
||||||
|
<StepperDescription>This is the second step</StepperDescription>
|
||||||
|
</StepperTrigger>
|
||||||
|
</StepperItem>
|
||||||
|
</Stepper>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Horizontal
|
||||||
|
|
||||||
|
<ComponentPreview name="StepperHorizental" />
|
||||||
|
|
||||||
|
### Vertical
|
||||||
|
|
||||||
|
<ComponentPreview name="StepperVertical" />
|
||||||
|
|
||||||
|
### Form
|
||||||
|
|
||||||
|
<ComponentPreview name="StepperForm" />
|
||||||
|
|
@ -48,6 +48,19 @@ import { Switch } from '@/components/ui/switch'
|
||||||
</template>
|
</template>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
# Add icon inside switch thumb
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<Switch :checked="isDark" @update:checked="toggleTheme">
|
||||||
|
<template #thumb>
|
||||||
|
<Icon v-if="isDark" icon="lucide:moon" class="size-3" />
|
||||||
|
<Icon v-else icon="lucide:sun" class="size-3" />
|
||||||
|
</template>
|
||||||
|
</Switch>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
### Form
|
### Form
|
||||||
|
|
|
||||||
|
|
@ -39,3 +39,9 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</template>
|
</template>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Vertical
|
||||||
|
|
||||||
|
<ComponentPreview name="TabsVerticalDemo" />
|
||||||
|
|
|
||||||
|
|
@ -18,3 +18,7 @@ npx shadcn-vue@latest add tags-input
|
||||||
### Tags with Combobox
|
### Tags with Combobox
|
||||||
|
|
||||||
<ComponentPreview name="TagsInputComboboxDemo" />
|
<ComponentPreview name="TagsInputComboboxDemo" />
|
||||||
|
|
||||||
|
### Form
|
||||||
|
|
||||||
|
<ComponentPreview name="TagsInputFormDemo" />
|
||||||
|
|
|
||||||
|
|
@ -44,8 +44,8 @@ import { useToast } from '@/components/ui/toast/use-toast'
|
||||||
```vue
|
```vue
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { Toaster } from '@/components/ui/toast'
|
||||||
import { useToast } from '@/components/ui/toast/use-toast'
|
import { useToast } from '@/components/ui/toast/use-toast'
|
||||||
import { Toaster } from "@/components/ui/toast"
|
|
||||||
|
|
||||||
const { toast } = useToast()
|
const { toast } = useToast()
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -15,19 +15,19 @@ See installation instructions for the [Popover](/docs/components/popover#install
|
||||||
|
|
||||||
```vue
|
```vue
|
||||||
<script setup lang="ts">
|
<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 { Button } from '@/components/ui/button'
|
||||||
import { Calendar } from '@/components/ui/v-calendar'
|
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from '@/components/ui/popover'
|
} from '@/components/ui/popover'
|
||||||
|
|
||||||
|
import { Calendar } from '@/components/ui/v-calendar'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { format } from 'date-fns'
|
||||||
|
import { Calendar as CalendarIcon } from 'lucide-vue-next'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
const date = ref<Date>()
|
const date = ref<Date>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,15 @@
|
||||||
title: Contribution
|
title: Contribution
|
||||||
description: Learn on how to contribute to shadcn/vue.
|
description: Learn on how to contribute to shadcn/vue.
|
||||||
---
|
---
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Button } from "@/lib/registry/new-york/ui/button"
|
||||||
|
|
||||||
|
const latestSyncCommitTag = "06cc0cdf3d080555d26abbe6639f2d7f6341ec73"
|
||||||
|
|
||||||
|
const latestSyncCommitUrl = `https://github.com/shadcn-ui/ui/commit/${latestSyncCommitTag}`
|
||||||
|
const diffUrl = `https://github.com/shadcn-ui/ui/compare/${latestSyncCommitTag}...main`
|
||||||
|
</script>
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
Thanks for your interest in contributing to shadcn-vue.com. We're happy to have you here.
|
Thanks for your interest in contributing to shadcn-vue.com. We're happy to have you here.
|
||||||
|
|
@ -92,13 +101,13 @@ You can use the `pnpm --filter=[WORKSPACE]` command to start the development pro
|
||||||
|
|
||||||
1. To run the `shadcn-vue.com` website:
|
1. To run the `shadcn-vue.com` website:
|
||||||
|
|
||||||
```
|
```bash
|
||||||
pnpm dev
|
pnpm dev
|
||||||
```
|
```
|
||||||
|
|
||||||
2. To run the `shadcn-vue` cli package:
|
2. To run the `shadcn-vue` cli package:
|
||||||
|
|
||||||
```
|
```bash
|
||||||
pnpm dev:cli
|
pnpm dev:cli
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -176,7 +185,7 @@ To do so, we have a helper function named [`useForwardPropsEmits`](https://www.r
|
||||||
To be more clear, the function `useForwardPropsEmits` takes in props and an optional emit function, and returns a
|
To be more clear, the function `useForwardPropsEmits` takes in props and an optional emit function, and returns a
|
||||||
computed object that combines the parsed props and emits as props.
|
computed object that combines the parsed props and emits as props.
|
||||||
|
|
||||||
Here's an example from `Accordian` root component.
|
Here's an example from `Accordion` root component.
|
||||||
|
|
||||||
```vue
|
```vue
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
@ -200,7 +209,7 @@ const forwarded = useForwardPropsEmits(props, emits)
|
||||||
</template>
|
</template>
|
||||||
```
|
```
|
||||||
|
|
||||||
As you can see, `AccordionRootEmits` and `AccordionRootProps` types are imported from radix, combined with `useForwardPropsEmits` and then are binded using `v-bind` syntaxt.
|
As you can see, `AccordionRootEmits` and `AccordionRootProps` types are imported from radix, combined with `useForwardPropsEmits` and then are binded using `v-bind` syntax.
|
||||||
|
|
||||||
### CSS Classes
|
### CSS Classes
|
||||||
There are cases when we want to accept `class` as a prop in our `shadcn/vue` component and then combine it with a default tailwind class on our `radix-vue` component via `cn` utility function.
|
There are cases when we want to accept `class` as a prop in our `shadcn/vue` component and then combine it with a default tailwind class on our `radix-vue` component via `cn` utility function.
|
||||||
|
|
@ -212,9 +221,9 @@ Take a look at `DrawerDescription.vue`.
|
||||||
```vue
|
```vue
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { DrawerDescriptionProps } from 'vaul-vue'
|
import type { DrawerDescriptionProps } from 'vaul-vue'
|
||||||
import { DrawerDescription } from 'vaul-vue'
|
|
||||||
import { type HtmlHTMLAttributes, computed } from 'vue'
|
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { DrawerDescription } from 'vaul-vue'
|
||||||
|
import { computed, type HtmlHTMLAttributes } from 'vue'
|
||||||
|
|
||||||
const props = defineProps<DrawerDescriptionProps & { class?: HtmlHTMLAttributes['class'] }>()
|
const props = defineProps<DrawerDescriptionProps & { class?: HtmlHTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
|
@ -261,9 +270,9 @@ Take a look at `AccordionItem.vue`
|
||||||
|
|
||||||
```vue
|
```vue
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type HTMLAttributes, computed } from 'vue'
|
|
||||||
import { AccordionItem, type AccordionItemProps, useForwardProps } from 'radix-vue'
|
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { AccordionItem, type AccordionItemProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { computed, type HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
const props = defineProps<AccordionItemProps & { class?: HTMLAttributes['class'] }>()
|
const props = defineProps<AccordionItemProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
|
@ -298,9 +307,9 @@ Let's take a look at `Button.vue`
|
||||||
```vue
|
```vue
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { HTMLAttributes } from 'vue'
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
import { Primitive, type PrimitiveProps } from 'radix-vue'
|
import { Primitive, type PrimitiveProps } from 'radix-vue'
|
||||||
import { type ButtonVariants, buttonVariants } from '.'
|
import { type ButtonVariants, buttonVariants } from '.'
|
||||||
import { cn } from '@/lib/utils'
|
|
||||||
|
|
||||||
interface Props extends PrimitiveProps {
|
interface Props extends PrimitiveProps {
|
||||||
variant?: ButtonVariants['variant']
|
variant?: ButtonVariants['variant']
|
||||||
|
|
@ -326,6 +335,25 @@ const props = withDefaults(defineProps<Props>(), {
|
||||||
|
|
||||||
You'll need to extend `PrimitiveProps` in your props to support `Primitive` component. In most cases you would also need a default value for [`as`](https://www.radix-vue.com/utilities/primitive.html#changing-as-value) property.
|
You'll need to extend `PrimitiveProps` in your props to support `Primitive` component. In most cases you would also need a default value for [`as`](https://www.radix-vue.com/utilities/primitive.html#changing-as-value) property.
|
||||||
|
|
||||||
|
## Updating with `shadcn/ui`
|
||||||
|
|
||||||
|
`shadcn/vue` is an unofficial, community-led Vue port of `shadcn/ui`, as time goes by, they might get out of sync.
|
||||||
|
|
||||||
|
As of today, we are in sync with this <a :href="latestSyncCommitUrl" target="_blank">commit</a> of `shadcn/ui`.
|
||||||
|
|
||||||
|
Click on the following link to check if there are newer commits that we should be synced with.
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<a :href="diffUrl" target="_blank">
|
||||||
|
<Button>
|
||||||
|
Check Diff
|
||||||
|
</Button>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
1. There are no changes - If you see "There isn’t anything to compare", nothing needs to be done as we are synced with latest version.
|
||||||
|
2. If there are changes, you should review thoese changes and try to apply them on `shadcn/vue` codebase and create a PR, remember to update the `latestSyncCommitTag` in [this file](https://github.com/radix-vue/shadcn-vue/blob/dev/apps/www/src/content/docs/contribution.md) too.
|
||||||
|
|
||||||
## Debugging
|
## Debugging
|
||||||
Here are some tools and techniques that can help you debug more effectively while contributing to `shadcn/vue` or developing your own projects.
|
Here are some tools and techniques that can help you debug more effectively while contributing to `shadcn/vue` or developing your own projects.
|
||||||
|
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user