Compare commits

...

47 Commits
v0.11.0 ... dev

Author SHA1 Message Date
N1kl8s
47f53cb985 Migrate gitlab to gitea 2024-12-28 22:39:40 +01:00
1e1f335853 README.md aktualisiert 2024-12-28 22:32:52 +01:00
32d6b8b397 README.md aktualisiert 2024-12-28 22:26:05 +01:00
Boris
3eaef4a748
docs: fix broken link in footer (#950)
* Fix 404 link in footer

* Change link

Co-authored-by: zernonia <59365435+zernonia@users.noreply.github.com>

---------

Co-authored-by: zernonia <59365435+zernonia@users.noreply.github.com>
2024-12-17 16:35:47 +08:00
slow-groovin
5b1f952e0a
docs: missing .value for combobox usage (#947) 2024-12-17 12:32:11 +08:00
zernonia
693b0d2a08 fix(Sidebar): component import and registry dependencies 2024-12-17 12:21:45 +08:00
Fabian Bernhart
8a24d11a65
fix: add missing useMediaQuery in SidebarProvider.vue new-york style (#927) 2024-12-04 22:36:12 +03:30
Leo Chiu
e4fea78b33
docs: update code highlight and step configurations in vite.md (#936) 2024-12-04 10:22:52 +03:30
Stephan Koglin-Fischer
5869165a84
docs: added missing import in auto-form example (#919) 2024-12-03 12:36:41 +03:30
Damien Roche
857f10de51
docs: update responsive breadcrumb example (#933) 2024-12-03 12:26:41 +03:30
Thomas La Salmonie
5ada562803
build(cli): update proxy agent using undici agent to match ofetch requirements (#934) 2024-12-03 12:25:37 +03:30
zernonia
6c0ab55e92 chore: release v0.11.3 2024-11-19 16:13:22 +08:00
Fatih Solhan
3ddd70dd6b
docs: prevent shrinking theme customizer circles (#914)
* fix: prevent shrinking theme customizer circles

* use `shrink-0` instead of deprecated `flex-shrink-0`
2024-11-19 14:51:56 +08:00
Whbbit1999
2d5ad5b962
fix: mobile sidebar component #840 (#884)
* fix: sidebar component mobile #840

* chore: update new-york style too

* chore: run registry build

* revert: change in templates.ts

---------

Co-authored-by: zernonia <zernonia@gmail.com>
2024-11-19 14:51:24 +08:00
Leo Chiu
16f1d19460
fix(CLI): temporarily downgrade package to 3.2.0 due to TSInterfaceHeritage issue (#913)
Co-authored-by: leochiu <leochiu@hahow.in>
2024-11-19 14:31:12 +08:00
Louis Van Aken
ac69980ac9
fix: Error in the build of the auto form with Vite and TypeScript #870 (#896)
* fix: Error in the build of the auto form with Vite and TypeScript #870

* fix: new york registry
2024-11-16 17:40:29 +08:00
Titus Kirch
58fc125974
docs: fix primitive link in data-table.md (#898) 2024-11-16 17:34:26 +08:00
sinsky
383c846c02
docs: input components use "default-value" instead of "value" (#904)
* docs(example): input components used in SheetDemo use default-value instead of value

* docs(example): input components used in DialogDemo use default-value instead of value
2024-11-16 17:32:00 +08:00
sea
b7ef4653f7
docs: update * scrollbar style (#885) 2024-11-11 12:24:52 +03:30
Brian Le
384c87a91c
fix: add missing classes to button (#877) 2024-11-10 08:20:18 +03:30
Tony Zhang
75a5bce92f
docs: fix invisible code highlight in light mode (#875)
Signed-off-by: ZTL-UwU <zhangtianli2006@163.com>
2024-11-09 19:23:43 +03:30
Rhanlin
3d0db2de7b
docs: fix dialog form example onsubmit (#873) 2024-11-09 08:52:17 +03:30
Damien Roche
a5b25a9c43
fix: switch from placeholder: to data-placeholder: prefix class for SelectValue (#867) 2024-11-09 08:33:01 +03:30
zernonia
be888c80b6 chore: run changelogithub first 2024-11-04 15:20:54 +08:00
zernonia
93be758257 fix: module build 2024-11-04 15:17:59 +08:00
zernonia
26df827d33 chore: release v0.11.2 2024-11-04 14:53:22 +08:00
zernonia
f267b0ba4a chore: improve cd 2024-11-04 14:52:23 +08:00
zernonia
3c506ef188 chore: release v0.11.1 2024-11-04 14:45:18 +08:00
zernonia
df60e15a97 chore: improve cd 2024-11-04 14:43:40 +08:00
Selemon Brahanu
3646203d4f
refactor(CLI): base dependencies not installing when using shadcn-nuxt (#821)
* refactor: 815 shadcn-nuxt dependencies and CLI init

Closes: 815

* chore: remove unnecessary dependencies

* chore: update pnpm-lock.yaml file

* chore: cleanup

* chore: update pnpm-lock

---------

Co-authored-by: zernonia <zernonia@gmail.com>
2024-11-04 14:20:14 +08:00
zernonia
68c40f6908 chore: codegen 2024-11-03 23:52:53 +08:00
Hookin.
ff1b5f0a1b
fix(Sidebar): use open.value when setting the cookie (#862) 2024-11-03 17:11:46 +08:00
苗大
15f3eb305b
fix: missing NavigationMenuViewport component (#860) 2024-11-02 23:38:57 +08:00
Rnz Brngn
baceb4ce91
docs(blocks): correct tooltip prop for dynamic binding (#850) 2024-11-02 23:37:12 +08:00
IllustrisJack
2cb767122a
fix(Nuxt): component override warnings (#838) 2024-11-02 23:33:09 +08:00
Maxim Kim
eff3e75466
fix(Sidebar): missing slot into component (#847)
* fix(SidebarMenuAction): add slot into component

* fix(SidebarMenuAction): add changes in new-york style
2024-11-02 23:31:39 +08:00
xiaomo
83419c4dc3
docs: replace unexported cssVariables with highlight (#841) 2024-10-29 08:33:28 +03:30
xiaomo
52b9b20b3c
docs: add theme to highlight code block (#831) 2024-10-28 15:32:30 +03:30
zernonia
e0d4980e31
feat: Sidebar (1/2) (#827)
* feat: add default sidebar

* chore: add images, docs item

* refactor: rename and fix styling`

* feat: add new-york style

* chore: move typescript to catalog

* docs: fix block preview

* chore: build registry, add sidebar block

* docs: update sidebar demo

* chore: bump radix-vue

* chore: fix build
2024-10-28 01:05:55 +08:00
Mohammed Essam Helewa
6760ebb5ae
feat: enhance the behavior of Switch component (#835)
* Add named slot inside SwitchThumb

* Add documentation for switch thumb slot

* chore: re-build registry
2024-10-26 21:52:03 +03:30
Ciro Lo Sapio
d143272fb8
docs: update footer links (#820) 2024-10-22 20:17:17 +03:30
Otabek
4f3bb61283
docs: update vite.md for latest Vite configuration in Vue projects (#817) 2024-10-22 20:09:47 +03:30
zernonia
f9615d3657 chore: use format=cover 2024-10-17 15:22:02 +02:00
sea
d48ffcfffb
chore(vscode): add tailwindcss extensions (#810)
* chore(vscode): add tailwindcss extensions

* chore: add tailwind setting
2024-10-17 09:16:39 +03:30
sea
e63d5553e3
docs: fix navigation menu example link (#809) 2024-10-16 12:22:53 +03:30
zernonia
a01a1bd94d docs: fix carbon 2024-10-16 14:11:36 +08:00
zernonia
f5b02256bc chore: add carbon ads 2024-10-15 22:24:13 +08:00
217 changed files with 5078 additions and 1549 deletions

View File

@ -14,3 +14,4 @@ runs:
with: with:
node-version: lts/* node-version: lts/*
cache: pnpm cache: pnpm
registry-url: 'https://registry.npmjs.org'

View File

@ -21,6 +21,19 @@ jobs:
- name: Setup (Install Node & pnpm) - name: Setup (Install Node & pnpm)
uses: ./.github/actions/setup uses: ./.github/actions/setup
- name: Install dependencies
run: pnpm i --frozen-lockfile
- run: pnpm dlx changelogithub - run: pnpm dlx changelogithub
env: env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 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 }}

View File

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

View File

@ -34,5 +34,9 @@
"json", "json",
"jsonc", "jsonc",
"yaml" "yaml"
],
"tailwindCSS.experimental.classRegex": [
["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
["cn\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]
] ]
} }

View File

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

View File

@ -4,7 +4,6 @@ import autoprefixer from 'autoprefixer'
import tailwind from 'tailwindcss' import tailwind from 'tailwindcss'
import Icons from 'unplugin-icons/vite' import Icons from 'unplugin-icons/vite'
import { defineConfig } from 'vitepress' import { defineConfig } from 'vitepress'
import { cssVariables } from './theme/config/shiki'
import { siteConfig } from './theme/config/site' import { siteConfig } from './theme/config/site'
import CodeWrapperPlugin from './theme/plugins/codewrapper' import CodeWrapperPlugin from './theme/plugins/codewrapper'
@ -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,11 +48,14 @@ 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(),
], ],

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

View File

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

View File

@ -1,245 +1,44 @@
<script setup lang="ts"> <script setup lang="ts">
import { useConfigStore } from '@/stores/config' import { computed, ref } from 'vue'
import { CircleHelp, Info, Monitor, Smartphone, Tablet } from 'lucide-vue-next'
import MagicString from 'magic-string'
import { codeToHtml } from 'shiki'
import { reactive, ref, watch } from 'vue'
import { compileScript, parse, walk } from 'vue/compiler-sfc'
import { cssVariables } from '../config/shiki'
import BlockCopyButton from './BlockCopyButton.vue'
import Spinner from './Spinner.vue' import Spinner from './Spinner.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'
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"
v-model="tabValue"
class="relative grid w-full scroll-m-20 gap-4"
:style=" {
'--container-height': metadata.iframeHeight ?? '600px',
}"
>
<div class="flex flex-col items-center gap-4 sm:flex-row">
<div class="flex items-center gap-2">
<TabsList class="hidden sm:flex">
<TabsTrigger value="preview">
Preview
</TabsTrigger>
<TabsTrigger value="code">
Code
</TabsTrigger>
</TabsList>
<div class="hidden items-center gap-2 sm:flex">
<Separator
orientation="vertical"
class="mx-2 hidden h-4 md:flex"
/>
<div class="flex items-center gap-2">
<a :href="`#${name}`">
<Badge variant="outline">{{ name }}</Badge>
</a>
<Popover>
<PopoverTrigger class="hidden text-muted-foreground hover:text-foreground sm:flex">
<Info class="h-3.5 w-3.5" />
<span class="sr-only">Block description</span>
</PopoverTrigger>
<PopoverContent
side="right"
:side-offset="10"
class="text-sm"
>
{{ metadata.description }}
</PopoverContent>
</Popover>
</div>
</div>
</div>
<div class="flex items-center gap-2 pr-[14px] sm:ml-auto">
<div class="hidden h-[28px] items-center gap-1.5 rounded-md border p-[2px] shadow-sm md:flex">
<ToggleGroup
type="single"
default-value="100"
@update:model-value="(value) => {
resizableRef?.resize(parseInt(value))
}"
>
<ToggleGroupItem
value="100"
class="h-[22px] w-[22px] rounded-sm p-0"
>
<Monitor class="h-3.5 w-3.5" />
</ToggleGroupItem>
<ToggleGroupItem
value="60"
class="h-[22px] w-[22px] rounded-sm p-0"
>
<Tablet class="h-3.5 w-3.5" />
</ToggleGroupItem>
<ToggleGroupItem
value="30"
class="h-[22px] w-[22px] rounded-sm p-0"
>
<Smartphone class="h-3.5 w-3.5" />
</ToggleGroupItem>
</ToggleGroup>
</div>
<Separator
orientation="vertical"
class="mx-2 hidden h-4 md:flex"
/>
<StyleSwitcher class="h-7" />
<Popover>
<PopoverTrigger class="hidden text-muted-foreground hover:text-foreground sm:flex">
<CircleHelp class="h-3.5 w-3.5" />
<span class="sr-only">Block description</span>
</PopoverTrigger>
<PopoverContent
side="top"
:side-offset="20"
class="space-y-3 rounded-[0.5rem] text-sm"
>
<p class="font-medium">
What is the difference between the New York and Default style?
</p>
<p>
A style comes with its own set of components, animations,
icons and more.
</p>
<p>
The <span class="font-medium">Default</span> style has
larger inputs, uses lucide-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"
class="relative rounded-lg border bg-background transition-all "
:default-size="100"
:min-size="30"
>
<div v-if="isLoading" class="flex items-center justify-center h-full"> <div v-if="isLoading" class="flex items-center justify-center h-full">
<Spinner /> <Spinner />
</div> </div>
<div
:class="[container ? 'w-full' : 'absolute inset-0 hidden w-[1600px] bg-background md:block']"
>
<iframe <iframe
v-show="!isLoading" v-show="!isLoading"
:src="`/blocks/renderer#name=${name}&style=${style}&containerClass=${encodeURIComponent(metadata.containerClass ?? '')}`" :src="iframeURL"
class="relative z-20 w-full bg-background h-[--container-height]" class="relative z-20 w-full bg-background" :class="[container ? 'h-[--container-height]' : 'size-full']"
@load="isLoading = false" @load="isLoading = false"
/> />
</ResizablePanel> </div>
<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" /> </div>
<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> </template>

View File

@ -9,7 +9,7 @@ import PageHeader from '../components/PageHeader.vue'
import PageHeaderDescription from '../components/PageHeaderDescription.vue' import PageHeaderDescription from '../components/PageHeaderDescription.vue'
import PageHeaderHeading from '../components/PageHeaderHeading.vue' import PageHeaderHeading from '../components/PageHeaderHeading.vue'
import BlockPreview from './BlockPreview.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>

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

View File

@ -4,9 +4,8 @@ import { cn } from '@/lib/utils'
import { useConfigStore } from '@/stores/config' import { useConfigStore } from '@/stores/config'
import { useClipboard } from '@vueuse/core' import { useClipboard } from '@vueuse/core'
import MagicString from 'magic-string' import MagicString from 'magic-string'
import { codeToHtml } from 'shiki'
import { computed, ref, watch } from 'vue' import { computed, ref, watch } from 'vue'
import { cssVariables } from '../config/shiki' import { highlight } from '../config/shiki'
import CodeSandbox from './CodeSandbox.vue' 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'
@ -37,10 +36,7 @@ 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(transformedRawString.value, { codeHtml.value = highlight(transformedRawString.value, 'vue')
lang: 'vue',
theme: cssVariables,
})
} }
catch (err) { catch (err) {
console.error(err) console.error(err)

View File

@ -5,8 +5,13 @@ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/lib/regis
import { ScrollArea } from '@/lib/registry/default/ui/scroll-area' 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 CarbonAds from '../components/CarbonAds.vue'
import TableOfContentTree from './TableOfContentTree.vue' import TableOfContentTree from './TableOfContentTree.vue'
defineProps<{
showCarbonAds?: boolean
}>()
const headers = shallowRef<TableOfContents>() const headers = shallowRef<TableOfContents>()
function getHeadingsWithHierarchy(divId: string) { function getHeadingsWithHierarchy(divId: string) {
@ -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-z0-9 ]/gi, '') ?.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>

View File

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

View File

@ -1,4 +1,5 @@
export { default as APITable } from './APITable.vue' export { default as APITable } from './APITable.vue'
export { default as BlockPreview } from './BlockPreview.vue'
export { default as Callout } from './Callout.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'

View File

@ -128,12 +128,10 @@ export const docsConfig: DocsConfig = {
title: 'Auto Form', title: 'Auto Form',
href: '/docs/components/auto-form', href: '/docs/components/auto-form',
items: [], items: [],
label: 'New',
}, },
{ {
title: 'Charts', title: 'Charts',
href: '/docs/charts', href: '/docs/charts',
label: 'New Alpha',
items: [], items: [],
}, },
], ],
@ -141,6 +139,11 @@ 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',
@ -178,7 +181,6 @@ export const docsConfig: DocsConfig = {
title: 'Calendar', title: 'Calendar',
href: '/docs/components/calendar', href: '/docs/components/calendar',
items: [], items: [],
label: 'Updated',
}, },
{ {
title: 'Card', title: 'Card',
@ -217,7 +219,6 @@ export const docsConfig: DocsConfig = {
title: 'Date Picker', title: 'Date Picker',
href: '/docs/components/date-picker', href: '/docs/components/date-picker',
items: [], items: [],
label: 'Updated',
}, },
{ {
title: 'Dialog', title: 'Dialog',
@ -259,7 +260,6 @@ export const docsConfig: DocsConfig = {
{ {
title: 'Number Field', title: 'Number Field',
href: '/docs/components/number-field', href: '/docs/components/number-field',
label: 'New Alpha',
}, },
{ {
title: 'Pagination', title: 'Pagination',
@ -324,7 +324,6 @@ export const docsConfig: DocsConfig = {
{ {
title: 'Stepper', title: 'Stepper',
href: '/docs/components/stepper', href: '/docs/components/stepper',
label: 'New',
}, },
{ {
title: 'Switch', title: 'Switch',

View File

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

View File

@ -5,7 +5,7 @@ import RadixIconsExternalLink from '~icons/radix-icons/external-link'
import { useData, useRoute } from 'vitepress' import { useData, useRoute } from 'vitepress'
import DocsBreadcrumb from '../components/DocsBreadcrumb.vue' import DocsBreadcrumb from '../components/DocsBreadcrumb.vue'
import EditLink from '../components/EditLink.vue' import EditLink from '../components/EditLink.vue'
import TableOfContentVue from '../components/TableOfContent.vue' import TableOfContent from '../components/TableOfContent.vue'
import { docsConfig } from '../config/docs' import { docsConfig } from '../config/docs'
const $route = useRoute() const $route = useRoute()
@ -62,7 +62,7 @@ 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>
<DocsBreadcrumb class="mb-4" /> <DocsBreadcrumb class="mb-4" />
@ -104,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>

View File

@ -41,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,
}, },
// { // {
@ -209,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"
> >
@ -220,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"
> >

View File

@ -28,6 +28,16 @@
--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-primary-color: var(--primary);
--vis-secondary-color: 160 81% 40%; --vis-secondary-color: 160 81% 40%;
--vis-text-color: var(--muted-foreground); --vis-text-color: var(--muted-foreground);
@ -64,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%;
@ -97,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 */
@ -121,13 +129,13 @@
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);
} }
html:not(.dark) .shiki,
.hide-scrollbar { html:not(.dark) .shiki span {
-ms-overflow-style: none; color: var(--shiki-light);
scrollbar-width: none;
} }
.antialised { .antialised {
@ -157,7 +165,7 @@
} }
div[class^="language-"] { div[class^="language-"] {
@apply mb-4 mt-6 max-h-[650px] overflow-x-auto md:rounded-lg border !bg-secondary-foreground dark:!bg-secondary @apply mb-4 mt-6 max-h-[650px] overflow-x-auto md:rounded-lg border
} }
pre { pre {
@apply py-4; @apply py-4;

View File

@ -350,7 +350,7 @@
padding: 0 24px; */ padding: 0 24px; */
width: calc(100% + 2 * 24px); width: calc(100% + 2 * 24px);
display: inline-block; display: inline-block;
@apply bg-[hsl(var(--foreground))] dark:bg-[hsl(var(--background)_/_50%)] @apply bg-[hsl(var(--muted))] dark:bg-[hsl(var(--muted))]
} }
.vp-doc [class*='language-'] code .highlighted.error { .vp-doc [class*='language-'] code .highlighted.error {

View File

@ -1501,6 +1501,20 @@ export const Index = {
component: () => import("../src/lib/registry/default/block/Dashboard07.vue").then((m) => m.default), component: () => import("../src/lib/registry/default/block/Dashboard07.vue").then((m) => m.default),
files: ["../src/lib/registry/default/block/Dashboard07.vue"], files: ["../src/lib/registry/default/block/Dashboard07.vue"],
}, },
"Sidebar01": {
name: "Sidebar01",
type: "components:block",
registryDependencies: ["breadcrumb","dropdown-menu","label","separator","sidebar"],
component: () => import("../src/lib/registry/default/block/Sidebar01.vue").then((m) => m.default),
files: ["../src/lib/registry/default/block/Sidebar01.vue"],
},
"Sidebar07": {
name: "Sidebar07",
type: "components:block",
registryDependencies: ["avatar","breadcrumb","collapsible","dropdown-menu","separator","sidebar"],
component: () => import("../src/lib/registry/default/block/Sidebar07.vue").then((m) => m.default),
files: ["../src/lib/registry/default/block/Sidebar07.vue"],
},
}, "new-york": { }, "new-york": {
"AccordionDemo": { "AccordionDemo": {
name: "AccordionDemo", name: "AccordionDemo",
@ -3000,5 +3014,19 @@ export const Index = {
component: () => import("../src/lib/registry/new-york/block/Dashboard07.vue").then((m) => m.default), component: () => import("../src/lib/registry/new-york/block/Dashboard07.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/block/Dashboard07.vue"], files: ["../src/lib/registry/new-york/block/Dashboard07.vue"],
}, },
"Sidebar01": {
name: "Sidebar01",
type: "components:block",
registryDependencies: ["breadcrumb","dropdown-menu","label","separator","sidebar"],
component: () => import("../src/lib/registry/new-york/block/Sidebar01.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/block/Sidebar01.vue"],
},
"Sidebar07": {
name: "Sidebar07",
type: "components:block",
registryDependencies: ["avatar","breadcrumb","collapsible","dropdown-menu","separator","sidebar"],
component: () => import("../src/lib/registry/new-york/block/Sidebar07.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/block/Sidebar07.vue"],
},
}, },
} }

View File

@ -1,7 +1,7 @@
{ {
"name": "www", "name": "www",
"type": "module", "type": "module",
"version": "0.11.0", "version": "0.11.3",
"files": [ "files": [
"dist" "dist"
], ],
@ -68,11 +68,11 @@
"markdown-it": "^14.1.0", "markdown-it": "^14.1.0",
"pathe": "^1.1.2", "pathe": "^1.1.2",
"rimraf": "^6.0.1", "rimraf": "^6.0.1",
"shiki": "^1.17.7", "shiki": "^1.22.1",
"tailwind-merge": "^2.5.2", "tailwind-merge": "^2.5.2",
"tailwindcss": "^3.4.12", "tailwindcss": "^3.4.12",
"tsx": "^4.19.1", "tsx": "^4.19.1",
"typescript": "^5.6.2", "typescript": "catalog:",
"unplugin-icons": "^0.19.3", "unplugin-icons": "^0.19.3",
"vitepress": "^1.3.4", "vitepress": "^1.3.4",
"vue-component-meta": "^2.1.6", "vue-component-meta": "^2.1.6",

View File

@ -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 = ''
try {
content = fs.readFileSync(
path.join(process.cwd(), 'src/lib/registry', style.name, file), path.join(process.cwd(), 'src/lib/registry', style.name, file),
'utf8', '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`),

View File

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

View File

@ -403,6 +403,7 @@ By passing the `form` as props, you can control and use the method provided by `
```vue ```vue
<script setup lang="ts"> <script setup lang="ts">
import { AutoForm } from '@/components/ui/auto-form'
import { toTypedSchema } from '@vee-validate/zod' import { toTypedSchema } from '@vee-validate/zod'
import { useForm } from 'vee-validate' import { useForm } from 'vee-validate'
import * as z from 'zod' import * as z from 'zod'

View File

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

View File

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

View 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
```

View File

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

View File

@ -101,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
``` ```

View File

@ -17,7 +17,7 @@ npm create astro@latest
You will be asked a few questions to configure your project: You will be asked a few questions to configure your project:
```txt:line-numbers ```ansi:line-numbers
- Where should we create your new project? - Where should we create your new project?
./your-app-name ./your-app-name
- How would you like to start your new project? - How would you like to start your new project?
@ -99,7 +99,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? Astro Which framework are you using? Astro
Which style would you like to use? Default Which style would you like to use? Default

View File

@ -25,7 +25,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

View File

@ -213,7 +213,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
@ -231,7 +231,7 @@ Write configuration to components.json. Proceed? > Y/n
Here's the default structure of Nuxt app. You can use this as a reference: Here's the default structure of Nuxt app. You can use this as a reference:
```txt {6-16,20-21} ```ansi {6-16,20-21}
. .
├── pages ├── pages
│ ├── index.vue │ ├── index.vue

View File

@ -44,14 +44,13 @@ Install `tailwindcss` and its peer dependencies, then generate your `tailwind.co
#### `vite.config` #### `vite.config`
```typescript {5,6,9-13} ```typescript {2,3,8-12}
import path from 'node:path'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import autoprefixer from 'autoprefixer' import autoprefixer from 'autoprefixer'
import tailwind from 'tailwindcss' import tailwind from 'tailwindcss'
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
// https://vite.dev/config/
export default defineConfig({ export default defineConfig({
css: { css: {
postcss: { postcss: {
@ -59,11 +58,6 @@ Install `tailwindcss` and its peer dependencies, then generate your `tailwind.co
}, },
}, },
plugins: [vue()], plugins: [vue()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
}) })
``` ```
@ -121,14 +115,14 @@ Add the code below to the vite.config.ts so your app can resolve paths without e
npm i -D @types/node npm i -D @types/node
``` ```
```typescript {15-19} ```typescript {1,15-19}
import path from 'node:path' import { fileURLToPath, URL } from 'node:url'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import autoprefixer from 'autoprefixer' import autoprefixer from 'autoprefixer'
import tailwind from 'tailwindcss' import tailwind from 'tailwindcss'
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
// https://vite.dev/config/
export default defineConfig({ export default defineConfig({
css: { css: {
postcss: { postcss: {
@ -138,9 +132,9 @@ export default defineConfig({
plugins: [vue()], plugins: [vue()],
resolve: { resolve: {
alias: { alias: {
'@': path.resolve(__dirname, './src'), '@': fileURLToPath(new URL('./src', import.meta.url))
}, }
}, }
}) })
``` ```
@ -160,7 +154,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

View File

@ -0,0 +1,217 @@
<script lang="ts">
export const iframeHeight = '800px'
export const description
= 'A simple sidebar with navigation grouped by section.'
</script>
<script setup lang="ts">
import { Check, ChevronsUpDown, GalleryVerticalEnd, Search } from 'lucide-vue-next'
import { ref } from 'vue'
// Import components from the custom library
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from '@/lib/registry/default/ui/breadcrumb'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/lib/registry/default/ui/dropdown-menu'
import { Label } from '@/lib/registry/default/ui/label'
import { Separator } from '@/lib/registry/default/ui/separator'
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarInput,
SidebarInset,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarProvider,
SidebarRail,
SidebarTrigger,
} from '@/lib/registry/default/ui/sidebar'
const data = {
versions: ['1.0.1', '1.1.0-alpha', '2.0.0-beta1'],
navMain: [
{
title: 'Getting Started',
url: '#',
items: [
{ title: 'Installation', url: '#' },
{ title: 'Project Structure', url: '#' },
],
},
{
title: 'Building Your Application',
url: '#',
items: [
{ title: 'Routing', url: '#' },
{ title: 'Data Fetching', url: '#', isActive: true },
{ title: 'Rendering', url: '#' },
{ title: 'Caching', url: '#' },
{ title: 'Styling', url: '#' },
{ title: 'Optimizing', url: '#' },
{ title: 'Configuring', url: '#' },
{ title: 'Testing', url: '#' },
{ title: 'Authentication', url: '#' },
{ title: 'Deploying', url: '#' },
{ title: 'Upgrading', url: '#' },
{ title: 'Examples', url: '#' },
],
},
{
title: 'API Reference',
url: '#',
items: [
{ title: 'Components', url: '#' },
{ title: 'File Conventions', url: '#' },
{ title: 'Functions', url: '#' },
{ title: 'next.config.js Options', url: '#' },
{ title: 'CLI', url: '#' },
{ title: 'Edge Runtime', url: '#' },
],
},
{
title: 'Architecture',
url: '#',
items: [
{ title: 'Accessibility', url: '#' },
{ title: 'Fast Refresh', url: '#' },
{ title: 'Next.js Compiler', url: '#' },
{ title: 'Supported Browsers', url: '#' },
{ title: 'Turbopack', url: '#' },
],
},
],
}
const selectedVersion = ref(data.versions[0])
const dropdownOpen = ref(false)
const search = ref('')
function toggleDropdown() {
dropdownOpen.value = !dropdownOpen.value
}
function setSelectedVersion(version: string) {
selectedVersion.value = version
}
</script>
<template>
<SidebarProvider>
<Sidebar>
<SidebarHeader>
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<SidebarMenuButton
size="lg"
:class="{ 'data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground': dropdownOpen }"
@click="toggleDropdown"
>
<div class="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground">
<GalleryVerticalEnd class="size-4" />
</div>
<div class="flex flex-col gap-0.5 leading-none">
<span class="font-semibold">Documentation</span>
<span>v{{ selectedVersion }}</span>
</div>
<ChevronsUpDown class="ml-auto" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
v-if="dropdownOpen"
class="w-[--radix-dropdown-menu-trigger-width]"
align="start"
>
<DropdownMenuItem
v-for="version in data.versions"
:key="version"
@click="setSelectedVersion(version)"
>
v{{ version }}
<Check v-if="version === selectedVersion" class="ml-auto" />
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
<form @submit.prevent>
<SidebarGroup class="py-0">
<SidebarGroupContent class="relative">
<Label for="search" class="sr-only">Search</Label>
<SidebarInput
id="search"
v-model="search"
placeholder="Search the docs..."
class="pl-8"
/>
<Search class="pointer-events-none absolute left-2 top-1/2 size-4 -translate-y-1/2 select-none opacity-50" />
</SidebarGroupContent>
</SidebarGroup>
</form>
</SidebarHeader>
<SidebarContent>
<SidebarGroup v-for="item in data.navMain" :key="item.title">
<SidebarGroupLabel>{{ item.title }}</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem v-for="subItem in item.items" :key="subItem.title">
<SidebarMenuButton :class="{ 'is-active': subItem.isActive }" as-child>
<a :href="subItem.url">{{ subItem.title }}</a>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
<SidebarRail />
</Sidebar>
<SidebarInset>
<header class="flex h-16 shrink-0 items-center gap-2 border-b px-4">
<SidebarTrigger class="-ml-1" />
<Separator orientation="vertical" class="mr-2 h-4" />
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem class="hidden md:block">
<BreadcrumbLink href="#">
Building Your Application
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator class="hidden md:block" />
<BreadcrumbItem>
<BreadcrumbPage>Data Fetching</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
</header>
<div class="flex flex-1 flex-col gap-4 p-4">
<div class="grid auto-rows-min gap-4 md:grid-cols-3">
<div class="aspect-video rounded-xl bg-muted/50" />
<div class="aspect-video rounded-xl bg-muted/50" />
<div class="aspect-video rounded-xl bg-muted/50" />
</div>
<div class="min-h-[100vh] flex-1 rounded-xl bg-muted/50 md:min-h-min" />
</div>
</SidebarInset>
</SidebarProvider>
</template>

View File

@ -0,0 +1,463 @@
<script lang="ts">
export const description
= 'A sidebar that collapses to icons.'
export const iframeHeight = '800px'
export const containerClass = 'w-full h-full'
</script>
<script setup lang=ts>
import {
Avatar,
AvatarFallback,
AvatarImage,
} from '@/lib/registry/default/ui/avatar'
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from '@/lib/registry/default/ui/breadcrumb'
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from '@/lib/registry/default/ui/collapsible'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuTrigger,
} from '@/lib/registry/default/ui/dropdown-menu'
import { Separator } from '@/lib/registry/default/ui/separator'
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupLabel,
SidebarHeader,
SidebarInset,
SidebarMenu,
SidebarMenuAction,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
SidebarProvider,
SidebarRail,
SidebarTrigger,
} from '@/lib/registry/default/ui/sidebar'
import {
AudioWaveform,
BadgeCheck,
Bell,
BookOpen,
Bot,
ChevronRight,
ChevronsUpDown,
Command,
CreditCard,
Folder,
Forward,
Frame,
GalleryVerticalEnd,
LogOut,
Map,
MoreHorizontal,
PieChart,
Plus,
Settings2,
Sparkles,
SquareTerminal,
Trash2,
} from 'lucide-vue-next'
import { ref } from 'vue'
// This is sample data.
const data = {
user: {
name: 'shadcn',
email: 'm@example.com',
avatar: '/avatars/shadcn.jpg',
},
teams: [
{
name: 'Acme Inc',
logo: GalleryVerticalEnd,
plan: 'Enterprise',
},
{
name: 'Acme Corp.',
logo: AudioWaveform,
plan: 'Startup',
},
{
name: 'Evil Corp.',
logo: Command,
plan: 'Free',
},
],
navMain: [
{
title: 'Playground',
url: '#',
icon: SquareTerminal,
isActive: true,
items: [
{
title: 'History',
url: '#',
},
{
title: 'Starred',
url: '#',
},
{
title: 'Settings',
url: '#',
},
],
},
{
title: 'Models',
url: '#',
icon: Bot,
items: [
{
title: 'Genesis',
url: '#',
},
{
title: 'Explorer',
url: '#',
},
{
title: 'Quantum',
url: '#',
},
],
},
{
title: 'Documentation',
url: '#',
icon: BookOpen,
items: [
{
title: 'Introduction',
url: '#',
},
{
title: 'Get Started',
url: '#',
},
{
title: 'Tutorials',
url: '#',
},
{
title: 'Changelog',
url: '#',
},
],
},
{
title: 'Settings',
url: '#',
icon: Settings2,
items: [
{
title: 'General',
url: '#',
},
{
title: 'Team',
url: '#',
},
{
title: 'Billing',
url: '#',
},
{
title: 'Limits',
url: '#',
},
],
},
],
projects: [
{
name: 'Design Engineering',
url: '#',
icon: Frame,
},
{
name: 'Sales & Marketing',
url: '#',
icon: PieChart,
},
{
name: 'Travel',
url: '#',
icon: Map,
},
],
}
const activeTeam = ref(data.teams[0])
function setActiveTeam(team: typeof data.teams[number]) {
activeTeam.value = team
}
</script>
<template>
<SidebarProvider>
<Sidebar collapsible="icon">
<SidebarHeader>
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<SidebarMenuButton
size="lg"
class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
>
<div class="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground">
<component :is="activeTeam.logo" class="size-4" />
</div>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-semibold">{{ activeTeam.name }}</span>
<span class="truncate text-xs">{{ activeTeam.plan }}</span>
</div>
<ChevronsUpDown class="ml-auto" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
class="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"
align="start"
side="bottom"
:side-offset="4"
>
<DropdownMenuLabel class="text-xs text-muted-foreground">
Teams
</DropdownMenuLabel>
<DropdownMenuItem
v-for="(team, index) in data.teams"
:key="team.name"
class="gap-2 p-2"
@click="setActiveTeam(team)"
>
<div class="flex size-6 items-center justify-center rounded-sm border">
<component :is="team.logo" class="size-4 shrink-0" />
</div>
{{ team.name }}
<DropdownMenuShortcut>{{ index + 1 }}</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem class="gap-2 p-2">
<div class="flex size-6 items-center justify-center rounded-md border bg-background">
<Plus class="size-4" />
</div>
<div class="font-medium text-muted-foreground">
Add team
</div>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Platform</SidebarGroupLabel>
<SidebarMenu>
<Collapsible
v-for="item in data.navMain"
:key="item.title"
as-child
:default-open="item.isActive"
class="group/collapsible"
>
<SidebarMenuItem>
<CollapsibleTrigger as-child>
<SidebarMenuButton :tooltip="item.title">
<component :is="item.icon" />
<span>{{ item.title }}</span>
<ChevronRight class="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>
<SidebarMenuSubItem
v-for="subItem in item.items"
:key="subItem.title"
>
<SidebarMenuSubButton as-child>
<a :href="subItem.url">
<span>{{ subItem.title }}</span>
</a>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
</SidebarMenuSub>
</CollapsibleContent>
</SidebarMenuItem>
</Collapsible>
</SidebarMenu>
</SidebarGroup>
<SidebarGroup class="group-data-[collapsible=icon]:hidden">
<SidebarGroupLabel>Projects</SidebarGroupLabel>
<SidebarMenu>
<SidebarMenuItem
v-for="item in data.projects"
:key="item.name"
>
<SidebarMenuButton as-child>
<a :href="item.url">
<component :is="item.icon" />
<span>{{ item.name }}</span>
</a>
</SidebarMenuButton>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<SidebarMenuAction show-on-hover>
<MoreHorizontal />
<span class="sr-only">More</span>
</SidebarMenuAction>
</DropdownMenuTrigger>
<DropdownMenuContent class="w-48 rounded-lg" side="bottom" align="end">
<DropdownMenuItem>
<Folder class="text-muted-foreground" />
<span>View Project</span>
</DropdownMenuItem>
<DropdownMenuItem>
<Forward class="text-muted-foreground" />
<span>Share Project</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Trash2 class="text-muted-foreground" />
<span>Delete Project</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton class="text-sidebar-foreground/70">
<MoreHorizontal class="text-sidebar-foreground/70" />
<span>More</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<SidebarMenuButton
size="lg"
class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
>
<Avatar class="h-8 w-8 rounded-lg">
<AvatarImage :src="data.user.avatar" :alt="data.user.name" />
<AvatarFallback class="rounded-lg">
CN
</AvatarFallback>
</Avatar>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-semibold">{{ data.user.name }}</span>
<span class="truncate text-xs">{{ data.user.email }}</span>
</div>
<ChevronsUpDown class="ml-auto size-4" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent class="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg" side="bottom" align="end" :side-offset="4">
<DropdownMenuLabel class="p-0 font-normal">
<div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<Avatar class="h-8 w-8 rounded-lg">
<AvatarImage :src="data.user.avatar" :alt="data.user.name" />
<AvatarFallback class="rounded-lg">
CN
</AvatarFallback>
</Avatar>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-semibold">{{ data.user.name }}</span>
<span class="truncate text-xs">{{ data.user.email }}</span>
</div>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<Sparkles />
Upgrade to Pro
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<BadgeCheck />
Account
</DropdownMenuItem>
<DropdownMenuItem>
<CreditCard />
Billing
</DropdownMenuItem>
<DropdownMenuItem>
<Bell />
Notifications
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>
<LogOut />
Log out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
</SidebarFooter>
<SidebarRail />
</Sidebar>
<SidebarInset>
<header class="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12">
<div class="flex items-center gap-2 px-4">
<SidebarTrigger class="-ml-1" />
<Separator orientation="vertical" class="mr-2 h-4" />
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem class="hidden md:block">
<BreadcrumbLink href="#">
Building Your Application
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator class="hidden md:block" />
<BreadcrumbItem>
<BreadcrumbPage>Data Fetching</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
</div>
</header>
<div class="flex flex-1 flex-col gap-4 p-4 pt-0">
<div class="grid auto-rows-min gap-4 md:grid-cols-3">
<div class="aspect-video rounded-xl bg-muted/50" />
<div class="aspect-video rounded-xl bg-muted/50" />
<div class="aspect-video rounded-xl bg-muted/50" />
</div>
<div class="min-h-[100vh] flex-1 rounded-xl bg-muted/50 md:min-h-min" />
</div>
</SidebarInset>
</SidebarProvider>
</template>

View File

@ -42,7 +42,7 @@ const itemsToDisplay = 3
const firstLabel = computed(() => items.value[0]?.label) const firstLabel = computed(() => items.value[0]?.label)
const allButLastTwoItems = computed(() => items.value.slice(1, -2)) const allButLastTwoItems = computed(() => items.value.slice(1, -2))
const remainingItems = computed(() => items.value.slice(-itemsToDisplay + 1)) const remainingItems = computed(() => items.value.slice(-Math.min(itemsToDisplay, items.value.length) + 1))
</script> </script>
<template> <template>

View File

@ -32,13 +32,13 @@ import { Label } from '@/lib/registry/default/ui/label'
<Label for="name" class="text-right"> <Label for="name" class="text-right">
Name Name
</Label> </Label>
<Input id="name" value="Pedro Duarte" class="col-span-3" /> <Input id="name" default-value="Pedro Duarte" class="col-span-3" />
</div> </div>
<div class="grid grid-cols-4 items-center gap-4"> <div class="grid grid-cols-4 items-center gap-4">
<Label for="username" class="text-right"> <Label for="username" class="text-right">
Username Username
</Label> </Label>
<Input id="username" value="@peduarte" class="col-span-3" /> <Input id="username" default-value="@peduarte" class="col-span-3" />
</div> </div>
</div> </div>
<DialogFooter> <DialogFooter>

View File

@ -38,7 +38,7 @@ function onSubmit(values: any) {
</script> </script>
<template> <template>
<Form v-slot="{ submitForm }" as="" :validation-schema="formSchema" @submit="onSubmit"> <Form v-slot="{ handleSubmit }" as="" keep-values :validation-schema="formSchema">
<Dialog> <Dialog>
<DialogTrigger as-child> <DialogTrigger as-child>
<Button variant="outline"> <Button variant="outline">
@ -53,7 +53,7 @@ function onSubmit(values: any) {
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<form @submit="submitForm"> <form id="dialogForm" @submit="handleSubmit($event, onSubmit)">
<FormField v-slot="{ componentField }" name="username"> <FormField v-slot="{ componentField }" name="username">
<FormItem> <FormItem>
<FormLabel>Username</FormLabel> <FormLabel>Username</FormLabel>

View File

@ -12,36 +12,36 @@ import {
const components: { title: string, href: string, description: string }[] = [ const components: { title: string, href: string, description: string }[] = [
{ {
title: 'Alert Dialog', title: 'Alert Dialog',
href: '/docs/primitives/alert-dialog', href: '/docs/components/alert-dialog',
description: description:
'A modal dialog that interrupts the user with important content and expects a response.', 'A modal dialog that interrupts the user with important content and expects a response.',
}, },
{ {
title: 'Hover Card', title: 'Hover Card',
href: '/docs/primitives/hover-card', href: '/docs/components/hover-card',
description: description:
'For sighted users to preview content available behind a link.', 'For sighted users to preview content available behind a link.',
}, },
{ {
title: 'Progress', title: 'Progress',
href: '/docs/primitives/progress', href: '/docs/components/progress',
description: description:
'Displays an indicator showing the completion progress of a task, typically displayed as a progress bar.', 'Displays an indicator showing the completion progress of a task, typically displayed as a progress bar.',
}, },
{ {
title: 'Scroll-area', title: 'Scroll-area',
href: '/docs/primitives/scroll-area', href: '/docs/components/scroll-area',
description: 'Visually or semantically separates content.', description: 'Visually or semantically separates content.',
}, },
{ {
title: 'Tabs', title: 'Tabs',
href: '/docs/primitives/tabs', href: '/docs/components/tabs',
description: description:
'A set of layered sections of content—known as tab panels—that are displayed one at a time.', 'A set of layered sections of content—known as tab panels—that are displayed one at a time.',
}, },
{ {
title: 'Tooltip', title: 'Tooltip',
href: '/docs/primitives/tooltip', href: '/docs/components/tooltip',
description: description:
'A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.', 'A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.',
}, },
@ -76,7 +76,7 @@ const components: { title: string, href: string, description: string }[] = [
<li> <li>
<NavigationMenuLink as-child> <NavigationMenuLink as-child>
<a <a
href="/docs" href="/docs/introduction"
class="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground" class="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground"
> >
<div class="text-sm font-medium leading-none">Introduction</div> <div class="text-sm font-medium leading-none">Introduction</div>
@ -102,7 +102,7 @@ const components: { title: string, href: string, description: string }[] = [
<li> <li>
<NavigationMenuLink as-child> <NavigationMenuLink as-child>
<a <a
href="/docs/primitives/typography" href="/docs/typography"
class="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground" class="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground"
> >
<div class="text-sm font-medium leading-none">Typography</div> <div class="text-sm font-medium leading-none">Typography</div>
@ -136,7 +136,7 @@ const components: { title: string, href: string, description: string }[] = [
</NavigationMenuContent> </NavigationMenuContent>
</NavigationMenuItem> </NavigationMenuItem>
<NavigationMenuItem> <NavigationMenuItem>
<NavigationMenuLink href="/docs" :class="navigationMenuTriggerStyle()"> <NavigationMenuLink href="/docs/introduction" :class="navigationMenuTriggerStyle()">
Documentation Documentation
</NavigationMenuLink> </NavigationMenuLink>
</NavigationMenuItem> </NavigationMenuItem>

View File

@ -33,13 +33,13 @@ import {
<Label for="name" class="text-right"> <Label for="name" class="text-right">
Name Name
</Label> </Label>
<Input id="name" value="Pedro Duarte" class="col-span-3" /> <Input id="name" default-value="Pedro Duarte" class="col-span-3" />
</div> </div>
<div class="grid grid-cols-4 items-center gap-4"> <div class="grid grid-cols-4 items-center gap-4">
<Label for="username" class="text-right"> <Label for="username" class="text-right">
Username Username
</Label> </Label>
<Input id="username" value="@peduarte" class="col-span-3" /> <Input id="username" default-value="@peduarte" class="col-span-3" />
</div> </div>
</div> </div>
<SheetFooter> <SheetFooter>

View File

@ -1,3 +1,4 @@
import type { InputComponents } from './interface'
import AutoFormFieldArray from './AutoFormFieldArray.vue' import AutoFormFieldArray from './AutoFormFieldArray.vue'
import AutoFormFieldBoolean from './AutoFormFieldBoolean.vue' import AutoFormFieldBoolean from './AutoFormFieldBoolean.vue'
import AutoFormFieldDate from './AutoFormFieldDate.vue' import AutoFormFieldDate from './AutoFormFieldDate.vue'
@ -7,7 +8,7 @@ import AutoFormFieldInput from './AutoFormFieldInput.vue'
import AutoFormFieldNumber from './AutoFormFieldNumber.vue' import AutoFormFieldNumber from './AutoFormFieldNumber.vue'
import AutoFormFieldObject from './AutoFormFieldObject.vue' import AutoFormFieldObject from './AutoFormFieldObject.vue'
export const INPUT_COMPONENTS = { export const INPUT_COMPONENTS: InputComponents = {
date: AutoFormFieldDate, date: AutoFormFieldDate,
select: AutoFormFieldEnum, select: AutoFormFieldEnum,
radio: AutoFormFieldEnum, radio: AutoFormFieldEnum,

View File

@ -18,6 +18,20 @@ export interface Shape {
schema?: ZodAny schema?: ZodAny
} }
export interface InputComponents {
date: Component;
select: Component;
radio: Component;
checkbox: Component;
switch: Component;
textarea: Component;
number: Component;
string: Component;
file: Component;
array: Component;
object: Component;
};
export interface ConfigItem { export interface ConfigItem {
/** Value for the `FormLabel` */ /** Value for the `FormLabel` */
label?: string label?: string

View File

@ -12,7 +12,7 @@ const props = defineProps<{
<li <li
role="presentation" role="presentation"
aria-hidden="true" aria-hidden="true"
:class="cn('[&>svg]:size-3.5', props.class)" :class="cn('[&>svg]:w-3.5 [&>svg]:h-3.5', props.class)"
> >
<slot> <slot>
<ChevronRight /> <ChevronRight />

View File

@ -3,26 +3,26 @@ import { cva, type VariantProps } from 'class-variance-authority'
export { default as Button } from './Button.vue' export { default as Button } from './Button.vue'
export const buttonVariants = cva( export const buttonVariants = cva(
'inline-flex items-center justify-center whitespace-nowrap 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 gap-2 whitespace-nowrap 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 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
{ {
variants: { variants: {
variant: { variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90', default:
'bg-primary text-primary-foreground shadow hover:bg-primary/90',
destructive: destructive:
'bg-destructive text-destructive-foreground hover:bg-destructive/90', 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
outline: outline:
'border border-input bg-background hover:bg-accent hover:text-accent-foreground', 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
secondary: secondary:
'bg-secondary text-secondary-foreground hover:bg-secondary/80', 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground', ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'text-primary underline-offset-4 hover:underline', link: 'text-primary underline-offset-4 hover:underline',
}, },
size: { size: {
default: 'h-10 px-4 py-2', default: 'h-9 px-4 py-2',
xs: 'h-7 rounded px-2', sm: 'h-8 rounded-md px-3 text-xs',
sm: 'h-9 rounded-md px-3', lg: 'h-10 rounded-md px-8',
lg: 'h-11 rounded-md px-8', icon: 'h-9 w-9',
icon: 'h-10 w-10',
}, },
}, },
defaultVariants: { defaultVariants: {

View File

@ -18,7 +18,7 @@ const forwardedProps = useForwardProps(delegatedProps)
<DropdownMenuItem <DropdownMenuItem
v-bind="forwardedProps" v-bind="forwardedProps"
:class="cn( :class="cn(
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50', 'relative flex cursor-default select-none items-center rounded-sm gap-2 px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0',
inset && 'pl-8', inset && 'pl-8',
props.class, props.class,
)" )"

View File

@ -6,6 +6,7 @@ export { default as NavigationMenuItem } from './NavigationMenuItem.vue'
export { default as NavigationMenuLink } from './NavigationMenuLink.vue' export { default as NavigationMenuLink } from './NavigationMenuLink.vue'
export { default as NavigationMenuList } from './NavigationMenuList.vue' export { default as NavigationMenuList } from './NavigationMenuList.vue'
export { default as NavigationMenuTrigger } from './NavigationMenuTrigger.vue' export { default as NavigationMenuTrigger } from './NavigationMenuTrigger.vue'
export { default as NavigationMenuViewport } from './NavigationMenuViewport.vue'
export const navigationMenuTriggerStyle = cva( export const navigationMenuTriggerStyle = cva(
'group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50', 'group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50',

View File

@ -19,7 +19,7 @@ const forwardedProps = useForwardProps(delegatedProps)
<SelectTrigger <SelectTrigger
v-bind="forwardedProps" v-bind="forwardedProps"
:class="cn( :class="cn(
'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:truncate text-start', 'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:truncate text-start',
props.class, props.class,
)" )"
> >

View File

@ -0,0 +1,85 @@
<script setup lang="ts">
import type { SidebarProps } from '.'
import { Sheet, SheetContent } from '@/lib/registry/default/ui/sheet'
import { cn } from '@/lib/utils'
import { SIDEBAR_WIDTH_MOBILE, useSidebar } from './utils'
defineOptions({
inheritAttrs: false,
})
const props = withDefaults(defineProps<SidebarProps>(), {
side: 'left',
variant: 'sidebar',
collapsible: 'offcanvas',
})
const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
</script>
<template>
<div
v-if="collapsible === 'none'"
:class="cn('flex h-full w-[--sidebar-width] flex-col bg-sidebar text-sidebar-foreground', props.class)"
v-bind="$attrs"
>
<slot />
</div>
<Sheet v-else-if="isMobile" :open="openMobile" v-bind="$attrs" @update:open="setOpenMobile">
<SheetContent
data-sidebar="sidebar"
data-mobile="true"
:side="side"
class="w-[--sidebar-width] bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden"
:style="{
'--sidebar-width': SIDEBAR_WIDTH_MOBILE,
}"
>
<div class="flex h-full w-full flex-col">
<slot />
</div>
</SheetContent>
</Sheet>
<div
v-else class="group peer hidden md:block"
:data-state="state"
:data-collapsible="state === 'collapsed' ? collapsible : ''"
:data-variant="variant"
:data-side="side"
>
<!-- This is what handles the sidebar gap on desktop -->
<div
:class="cn(
'duration-200 relative h-svh w-[--sidebar-width] bg-transparent transition-[width] ease-linear',
'group-data-[collapsible=offcanvas]:w-0',
'group-data-[side=right]:rotate-180',
variant === 'floating' || variant === 'inset'
? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]'
: 'group-data-[collapsible=icon]:w-[--sidebar-width-icon]',
)"
/>
<div
:class="cn(
'duration-200 fixed inset-y-0 z-10 hidden h-svh w-[--sidebar-width] transition-[left,right,width] ease-linear md:flex',
side === 'left'
? 'left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]'
: 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',
// Adjust the padding for floating and inset variants.
variant === 'floating' || variant === 'inset'
? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]'
: 'group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r group-data-[side=right]:border-l',
props.class,
)"
v-bind="$attrs"
>
<div
data-sidebar="sidebar"
class="flex h-full w-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow"
>
<slot />
</div>
</div>
</div>
</template>

View File

@ -0,0 +1,17 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<div
data-sidebar="content"
:class="cn('flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden', props.class)"
>
<slot />
</div>
</template>

View File

@ -0,0 +1,17 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<div
data-sidebar="footer"
:class="cn('flex flex-col gap-2 p-2', props.class)"
>
<slot />
</div>
</template>

View File

@ -0,0 +1,17 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<div
data-sidebar="group"
:class="cn('relative flex w-full min-w-0 flex-col p-2', props.class)"
>
<slot />
</div>
</template>

View File

@ -0,0 +1,27 @@
<script setup lang="ts">
import type { PrimitiveProps } from 'radix-vue'
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
import { Primitive } from 'radix-vue'
const props = defineProps<PrimitiveProps & {
class?: HTMLAttributes['class']
}>()
</script>
<template>
<Primitive
data-sidebar="group-action"
:as="as"
:as-child="asChild"
:class="cn(
'absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
// Increases the hit area of the button on mobile.
'after:absolute after:-inset-2 after:md:hidden',
'group-data-[collapsible=icon]:hidden',
props.class,
)"
>
<slot />
</Primitive>
</template>

View File

@ -0,0 +1,17 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<div
data-sidebar="group-content"
:class="cn('w-full text-sm', props.class)"
>
<slot />
</div>
</template>

View File

@ -0,0 +1,24 @@
<script setup lang="ts">
import type { PrimitiveProps } from 'radix-vue'
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
import { Primitive } from 'radix-vue'
const props = defineProps<PrimitiveProps & {
class?: HTMLAttributes['class']
}>()
</script>
<template>
<Primitive
data-sidebar="group-label"
:as="as"
:as-child="asChild"
:class="cn(
'duration-200 flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opa] ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0',
props.class)"
>
<slot />
</Primitive>
</template>

View File

@ -0,0 +1,17 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<div
data-sidebar="header"
:class="cn('flex flex-col gap-2 p-2', props.class)"
>
<slot />
</div>
</template>

View File

@ -0,0 +1,21 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { Input } from '@/lib/registry/default/ui/input'
import { cn } from '@/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<Input
data-sidebar="input"
:class="cn(
'h-8 w-full bg-background shadow-none focus-visible:ring-2 focus-visible:ring-sidebar-ring',
props.class,
)"
>
<slot />
</Input>
</template>

View File

@ -0,0 +1,20 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<main
:class="cn(
'relative flex min-h-svh flex-1 flex-col bg-background',
'peer-data-[variant=inset]:min-h-[calc(100svh-theme(spacing.4))] md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow',
props.class,
)"
>
<slot />
</main>
</template>

View File

@ -0,0 +1,17 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<ul
data-sidebar="menu"
:class="cn('flex w-full min-w-0 flex-col gap-1', props.class)"
>
<slot />
</ul>
</template>

View File

@ -0,0 +1,34 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
import { Primitive, type PrimitiveProps } from 'radix-vue'
const props = withDefaults(defineProps<PrimitiveProps & {
showOnHover?: boolean
class?: HTMLAttributes['class']
}>(), {
as: 'button',
})
</script>
<template>
<Primitive
data-sidebar="menu-action"
:class="cn(
'absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0',
// Increases the hit area of the button on mobile.
'after:absolute after:-inset-2 after:md:hidden',
'peer-data-[size=sm]/menu-button:top-1',
'peer-data-[size=default]/menu-button:top-1.5',
'peer-data-[size=lg]/menu-button:top-2.5',
'group-data-[collapsible=icon]:hidden',
showOnHover
&& 'group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0',
props.class,
)"
:as="as"
:as-child="asChild"
>
<slot />
</Primitive>
</template>

View File

@ -0,0 +1,25 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<div
data-sidebar="menu-badge"
:class="cn(
'absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums text-sidebar-foreground select-none pointer-events-none',
'peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground',
'peer-data-[size=sm]/menu-button:top-1',
'peer-data-[size=default]/menu-button:top-1.5',
'peer-data-[size=lg]/menu-button:top-2.5',
'group-data-[collapsible=icon]:hidden',
props.class,
)"
>
<slot />
</div>
</template>

View File

@ -0,0 +1,49 @@
<script setup lang="ts">
import { Tooltip, TooltipContent, TooltipTrigger } from '@/lib/registry/default/ui/tooltip'
import { type Component, computed } from 'vue'
import SidebarMenuButtonChild, { type SidebarMenuButtonProps } from './SidebarMenuButtonChild.vue'
import { useSidebar } from './utils'
defineOptions({
inheritAttrs: false,
})
const props = withDefaults(defineProps<SidebarMenuButtonProps & {
tooltip?: string | Component
}>(), {
as: 'button',
variant: 'default',
size: 'default',
})
const { isMobile, state } = useSidebar()
const delegatedProps = computed(() => {
const { tooltip, ...delegated } = props
return delegated
})
</script>
<template>
<SidebarMenuButtonChild v-if="!tooltip" v-bind="{ ...delegatedProps, ...$attrs }">
<slot />
</SidebarMenuButtonChild>
<Tooltip v-else>
<TooltipTrigger as-child>
<SidebarMenuButtonChild v-bind="{ ...delegatedProps, ...$attrs }">
<slot />
</SidebarMenuButtonChild>
</TooltipTrigger>
<TooltipContent
side="right"
align="center"
:hidden="state !== 'collapsed' || isMobile"
>
<template v-if="typeof tooltip === 'string'">
{{ tooltip }}
</template>
<component :is="tooltip" v-else />
</TooltipContent>
</Tooltip>
</template>

View File

@ -0,0 +1,33 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
import { Primitive, type PrimitiveProps } from 'radix-vue'
import { type SidebarMenuButtonVariants, sidebarMenuButtonVariants } from '.'
export interface SidebarMenuButtonProps extends PrimitiveProps {
variant?: SidebarMenuButtonVariants['variant']
size?: SidebarMenuButtonVariants['size']
isActive?: boolean
class?: HTMLAttributes['class']
}
const props = withDefaults(defineProps<SidebarMenuButtonProps>(), {
as: 'button',
variant: 'default',
size: 'default',
})
</script>
<template>
<Primitive
data-sidebar="menu-button"
:data-size="size"
:data-active="isActive"
:class="cn(sidebarMenuButtonVariants({ variant, size }), props.class)"
:as="as"
:as-child="asChild"
v-bind="$attrs"
>
<slot />
</Primitive>
</template>

View File

@ -0,0 +1,17 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<li
data-sidebar="menu-item"
:class="cn('group/menu-item relative', props.class)"
>
<slot />
</li>
</template>

View File

@ -0,0 +1,33 @@
<script setup lang="ts">
import { Skeleton } from '@/lib/registry/default/ui/skeleton'
import { cn } from '@/lib/utils'
import { computed, type HTMLAttributes } from 'vue'
const props = defineProps<{
showIcon?: boolean
class?: HTMLAttributes['class']
}>()
const width = computed(() => {
return `${Math.floor(Math.random() * 40) + 50}%`
})
</script>
<template>
<div
data-sidebar="menu-skeleton"
:class="cn('rounded-md h-8 flex gap-2 px-2 items-center', props.class)"
>
<Skeleton
v-if="showIcon"
class="size-4 rounded-md"
data-sidebar="menu-skeleton-icon"
/>
<Skeleton
class="h-4 flex-1 max-w-[--skeleton-width]"
data-sidebar="menu-skeleton-text"
:style="{ '--skeleton-width': width }"
/>
</div>
</template>

View File

@ -0,0 +1,21 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<ul
data-sidebar="menu-badge"
:class="cn(
'mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5',
'group-data-[collapsible=icon]:hidden',
props.class,
)"
>
<slot />
</ul>
</template>

View File

@ -0,0 +1,35 @@
<script setup lang="ts">
import type { PrimitiveProps } from 'radix-vue'
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
import { Primitive } from 'radix-vue'
const props = withDefaults(defineProps<PrimitiveProps & {
size?: 'sm' | 'md'
isActive?: boolean
class?: HTMLAttributes['class']
}>(), {
as: 'a',
size: 'md',
})
</script>
<template>
<Primitive
data-sidebar="menu-sub-button"
:as="as"
:as-child="asChild"
:data-size="size"
:data-active="isActive"
:class="cn(
'flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-none ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground',
'data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground',
size === 'sm' && 'text-xs',
size === 'md' && 'text-sm',
'group-data-[collapsible=icon]:hidden',
props.class,
)"
>
<slot />
</Primitive>
</template>

View File

@ -0,0 +1,8 @@
<script setup lang="ts">
</script>
<template>
<li>
<slot />
</li>
</template>

View File

@ -0,0 +1,80 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
import { useEventListener, useMediaQuery, useVModel } from '@vueuse/core'
import { TooltipProvider } from 'radix-vue'
import { computed, type HTMLAttributes, type Ref, ref } from 'vue'
import { provideSidebarContext, SIDEBAR_COOKIE_MAX_AGE, SIDEBAR_COOKIE_NAME, SIDEBAR_KEYBOARD_SHORTCUT, SIDEBAR_WIDTH, SIDEBAR_WIDTH_ICON } from './utils'
const props = withDefaults(defineProps<{
defaultOpen?: boolean
open?: boolean
class?: HTMLAttributes['class']
}>(), {
defaultOpen: true,
open: undefined,
})
const emits = defineEmits<{
'update:open': [open: boolean]
}>()
const isMobile = useMediaQuery('(max-width: 768px)')
const openMobile = ref(false)
const open = useVModel(props, 'open', emits, {
defaultValue: props.defaultOpen ?? false,
passive: (props.open === undefined) as false,
}) as Ref<boolean>
function setOpen(value: boolean) {
open.value = value // emits('update:open', value)
// This sets the cookie to keep the sidebar state.
document.cookie = `${SIDEBAR_COOKIE_NAME}=${open.value}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
}
function setOpenMobile(value: boolean) {
openMobile.value = value
}
// Helper to toggle the sidebar.
function toggleSidebar() {
return isMobile.value ? setOpenMobile(!openMobile.value) : setOpen(!open.value)
}
useEventListener('keydown', (event: KeyboardEvent) => {
if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {
event.preventDefault()
toggleSidebar()
}
})
// We add a state so that we can do data-state="expanded" or "collapsed".
// This makes it easier to style the sidebar with Tailwind classes.
const state = computed(() => open.value ? 'expanded' : 'collapsed')
provideSidebarContext({
state,
open,
setOpen,
isMobile,
openMobile,
setOpenMobile,
toggleSidebar,
})
</script>
<template>
<TooltipProvider :delay-duration="0">
<div
:style="{
'--sidebar-width': SIDEBAR_WIDTH,
'--sidebar-width-icon': SIDEBAR_WIDTH_ICON,
}"
:class="cn('group/sidebar-wrapper flex min-h-svh w-full text-sidebar-foreground has-[[data-variant=inset]]:bg-sidebar', props.class)"
v-bind="$attrs"
>
<slot />
</div>
</TooltipProvider>
</template>

View File

@ -0,0 +1,32 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
import { useSidebar } from './utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
const { toggleSidebar } = useSidebar()
</script>
<template>
<button
data-sidebar="rail"
aria-label="Toggle Sidebar"
:tabindex="-1"
title="Toggle Sidebar"
:class="cn(
'absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex',
'[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize',
'[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize',
'group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-sidebar',
'[[data-side=left][data-collapsible=offcanvas]_&]:-right-2',
'[[data-side=right][data-collapsible=offcanvas]_&]:-left-2',
props.class,
)"
@click="toggleSidebar"
>
<slot />
</button>
</template>

View File

@ -0,0 +1,18 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { Separator } from '@/lib/registry/default/ui/separator'
import { cn } from '@/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<Separator
data-sidebar="separator"
:class="cn('mx-2 w-auto bg-sidebar-border', props.class)"
>
<slot />
</Separator>
</template>

View File

@ -0,0 +1,26 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { Button } from '@/lib/registry/default/ui/button'
import { cn } from '@/lib/utils'
import { PanelLeft } from 'lucide-vue-next'
import { useSidebar } from './utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
const { toggleSidebar } = useSidebar()
</script>
<template>
<Button
data-sidebar="trigger"
variant="ghost"
size="icon"
:class="cn('h-7 w-7', props.class)"
@click="toggleSidebar"
>
<PanelLeft />
<span class="sr-only">Toggle Sidebar</span>
</Button>
</template>

View File

@ -0,0 +1,59 @@
import type { HTMLAttributes } from 'vue'
import { cva, type VariantProps } from 'class-variance-authority'
export interface SidebarProps {
side?: 'left' | 'right'
variant?: 'sidebar' | 'floating' | 'inset'
collapsible?: 'offcanvas' | 'icon' | 'none'
class?: HTMLAttributes['class']
}
export { default as Sidebar } from './Sidebar.vue'
export { default as SidebarContent } from './SidebarContent.vue'
export { default as SidebarFooter } from './SidebarFooter.vue'
export { default as SidebarGroup } from './SidebarGroup.vue'
export { default as SidebarGroupAction } from './SidebarGroupAction.vue'
export { default as SidebarGroupContent } from './SidebarGroupContent.vue'
export { default as SidebarGroupLabel } from './SidebarGroupLabel.vue'
export { default as SidebarHeader } from './SidebarHeader.vue'
export { default as SidebarInput } from './SidebarInput.vue'
export { default as SidebarInset } from './SidebarInset.vue'
export { default as SidebarMenu } from './SidebarMenu.vue'
export { default as SidebarMenuAction } from './SidebarMenuAction.vue'
export { default as SidebarMenuBadge } from './SidebarMenuBadge.vue'
export { default as SidebarMenuButton } from './SidebarMenuButton.vue'
export { default as SidebarMenuItem } from './SidebarMenuItem.vue'
export { default as SidebarMenuSkeleton } from './SidebarMenuSkeleton.vue'
export { default as SidebarMenuSub } from './SidebarMenuSub.vue'
export { default as SidebarMenuSubButton } from './SidebarMenuSubButton.vue'
export { default as SidebarMenuSubItem } from './SidebarMenuSubItem.vue'
export { default as SidebarProvider } from './SidebarProvider.vue'
export { default as SidebarRail } from './SidebarRail.vue'
export { default as SidebarSeparator } from './SidebarSeparator.vue'
export { default as SidebarTrigger } from './SidebarTrigger.vue'
export { useSidebar } from './utils'
export const sidebarMenuButtonVariants = cva(
'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0',
{
variants: {
variant: {
default: 'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',
outline:
'bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]',
},
size: {
default: 'h-8 text-sm',
sm: 'h-7 text-xs',
lg: 'h-12 text-sm group-data-[collapsible=icon]:!p-0',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
},
)
export type SidebarMenuButtonVariants = VariantProps<typeof sidebarMenuButtonVariants>

View File

@ -0,0 +1,19 @@
import type { ComputedRef, Ref } from 'vue'
import { createContext } from 'radix-vue'
export const SIDEBAR_COOKIE_NAME = 'sidebar:state'
export const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
export const SIDEBAR_WIDTH = '16rem'
export const SIDEBAR_WIDTH_MOBILE = '18rem'
export const SIDEBAR_WIDTH_ICON = '3rem'
export const SIDEBAR_KEYBOARD_SHORTCUT = 'b'
export const [useSidebar, provideSidebarContext] = createContext<{
state: ComputedRef<'expanded' | 'collapsed'>
open: Ref<boolean>
setOpen: (value: boolean) => void
isMobile: Ref<boolean>
openMobile: Ref<boolean>
setOpenMobile: (value: boolean) => void
toggleSidebar: () => void
}>('Sidebar')

View File

@ -31,7 +31,9 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
)" )"
> >
<SwitchThumb <SwitchThumb
:class="cn('pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0')" :class="cn('pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5')"
/> >
<slot name="thumb" />
</SwitchThumb>
</SwitchRoot> </SwitchRoot>
</template> </template>

View File

@ -0,0 +1,217 @@
<script lang="ts">
export const iframeHeight = '800px'
export const description
= 'A simple sidebar with navigation grouped by section.'
</script>
<script setup lang="ts">
import { Check, ChevronsUpDown, GalleryVerticalEnd, Search } from 'lucide-vue-next'
import { ref } from 'vue'
// Import components from the custom library
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from '@/lib/registry/new-york/ui/breadcrumb'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/lib/registry/new-york/ui/dropdown-menu'
import { Label } from '@/lib/registry/new-york/ui/label'
import { Separator } from '@/lib/registry/new-york/ui/separator'
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarInput,
SidebarInset,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarProvider,
SidebarRail,
SidebarTrigger,
} from '@/lib/registry/new-york/ui/sidebar'
const data = {
versions: ['1.0.1', '1.1.0-alpha', '2.0.0-beta1'],
navMain: [
{
title: 'Getting Started',
url: '#',
items: [
{ title: 'Installation', url: '#' },
{ title: 'Project Structure', url: '#' },
],
},
{
title: 'Building Your Application',
url: '#',
items: [
{ title: 'Routing', url: '#' },
{ title: 'Data Fetching', url: '#', isActive: true },
{ title: 'Rendering', url: '#' },
{ title: 'Caching', url: '#' },
{ title: 'Styling', url: '#' },
{ title: 'Optimizing', url: '#' },
{ title: 'Configuring', url: '#' },
{ title: 'Testing', url: '#' },
{ title: 'Authentication', url: '#' },
{ title: 'Deploying', url: '#' },
{ title: 'Upgrading', url: '#' },
{ title: 'Examples', url: '#' },
],
},
{
title: 'API Reference',
url: '#',
items: [
{ title: 'Components', url: '#' },
{ title: 'File Conventions', url: '#' },
{ title: 'Functions', url: '#' },
{ title: 'next.config.js Options', url: '#' },
{ title: 'CLI', url: '#' },
{ title: 'Edge Runtime', url: '#' },
],
},
{
title: 'Architecture',
url: '#',
items: [
{ title: 'Accessibility', url: '#' },
{ title: 'Fast Refresh', url: '#' },
{ title: 'Next.js Compiler', url: '#' },
{ title: 'Supported Browsers', url: '#' },
{ title: 'Turbopack', url: '#' },
],
},
],
}
const selectedVersion = ref(data.versions[0])
const dropdownOpen = ref(false)
const search = ref('')
function toggleDropdown() {
dropdownOpen.value = !dropdownOpen.value
}
function setSelectedVersion(version: string) {
selectedVersion.value = version
}
</script>
<template>
<SidebarProvider>
<Sidebar>
<SidebarHeader>
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger>
<SidebarMenuButton
size="lg"
:class="{ 'data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground': dropdownOpen }"
@click="toggleDropdown"
>
<div class="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground">
<GalleryVerticalEnd class="size-4" />
</div>
<div class="flex flex-col gap-0.5 leading-none">
<span class="font-semibold">Documentation</span>
<span>v{{ selectedVersion }}</span>
</div>
<ChevronsUpDown class="ml-auto" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
v-if="dropdownOpen"
class="w-[--radix-dropdown-menu-trigger-width]"
align="start"
>
<DropdownMenuItem
v-for="version in data.versions"
:key="version"
@click="setSelectedVersion(version)"
>
v{{ version }}
<Check v-if="version === selectedVersion" class="ml-auto" />
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
<form @submit.prevent>
<SidebarGroup class="py-0">
<SidebarGroupContent class="relative">
<Label for="search" class="sr-only">Search</Label>
<SidebarInput
id="search"
v-model="search"
placeholder="Search the docs..."
class="pl-8"
/>
<Search class="pointer-events-none absolute left-2 top-1/2 size-4 -translate-y-1/2 select-none opacity-50" />
</SidebarGroupContent>
</SidebarGroup>
</form>
</SidebarHeader>
<SidebarContent>
<SidebarGroup v-for="item in data.navMain" :key="item.title">
<SidebarGroupLabel>{{ item.title }}</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem v-for="subItem in item.items" :key="subItem.title">
<SidebarMenuButton :class="{ 'is-active': subItem.isActive }" as-child>
<a :href="subItem.url">{{ subItem.title }}</a>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
<SidebarRail />
</Sidebar>
<SidebarInset>
<header class="flex h-16 shrink-0 items-center gap-2 border-b px-4">
<SidebarTrigger class="-ml-1" />
<Separator orientation="vertical" class="mr-2 h-4" />
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem class="hidden md:block">
<BreadcrumbLink href="#">
Building Your Application
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator class="hidden md:block" />
<BreadcrumbItem>
<BreadcrumbPage>Data Fetching</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
</header>
<div class="flex flex-1 flex-col gap-4 p-4">
<div class="grid auto-rows-min gap-4 md:grid-cols-3">
<div class="aspect-video rounded-xl bg-muted/50" />
<div class="aspect-video rounded-xl bg-muted/50" />
<div class="aspect-video rounded-xl bg-muted/50" />
</div>
<div class="min-h-[100vh] flex-1 rounded-xl bg-muted/50 md:min-h-min" />
</div>
</SidebarInset>
</SidebarProvider>
</template>

View File

@ -0,0 +1,463 @@
<script lang="ts">
export const description
= 'A sidebar that collapses to icons.'
export const iframeHeight = '800px'
export const containerClass = 'w-full h-full'
</script>
<script setup lang=ts>
import {
Avatar,
AvatarFallback,
AvatarImage,
} from '@/lib/registry/new-york/ui/avatar'
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from '@/lib/registry/new-york/ui/breadcrumb'
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from '@/lib/registry/new-york/ui/collapsible'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuTrigger,
} from '@/lib/registry/new-york/ui/dropdown-menu'
import { Separator } from '@/lib/registry/new-york/ui/separator'
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupLabel,
SidebarHeader,
SidebarInset,
SidebarMenu,
SidebarMenuAction,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
SidebarProvider,
SidebarRail,
SidebarTrigger,
} from '@/lib/registry/new-york/ui/sidebar'
import {
AudioWaveform,
BadgeCheck,
Bell,
BookOpen,
Bot,
ChevronRight,
ChevronsUpDown,
Command,
CreditCard,
Folder,
Forward,
Frame,
GalleryVerticalEnd,
LogOut,
Map,
MoreHorizontal,
PieChart,
Plus,
Settings2,
Sparkles,
SquareTerminal,
Trash2,
} from 'lucide-vue-next'
import { ref } from 'vue'
// This is sample data.
const data = {
user: {
name: 'shadcn',
email: 'm@example.com',
avatar: '/avatars/shadcn.jpg',
},
teams: [
{
name: 'Acme Inc',
logo: GalleryVerticalEnd,
plan: 'Enterprise',
},
{
name: 'Acme Corp.',
logo: AudioWaveform,
plan: 'Startup',
},
{
name: 'Evil Corp.',
logo: Command,
plan: 'Free',
},
],
navMain: [
{
title: 'Playground',
url: '#',
icon: SquareTerminal,
isActive: true,
items: [
{
title: 'History',
url: '#',
},
{
title: 'Starred',
url: '#',
},
{
title: 'Settings',
url: '#',
},
],
},
{
title: 'Models',
url: '#',
icon: Bot,
items: [
{
title: 'Genesis',
url: '#',
},
{
title: 'Explorer',
url: '#',
},
{
title: 'Quantum',
url: '#',
},
],
},
{
title: 'Documentation',
url: '#',
icon: BookOpen,
items: [
{
title: 'Introduction',
url: '#',
},
{
title: 'Get Started',
url: '#',
},
{
title: 'Tutorials',
url: '#',
},
{
title: 'Changelog',
url: '#',
},
],
},
{
title: 'Settings',
url: '#',
icon: Settings2,
items: [
{
title: 'General',
url: '#',
},
{
title: 'Team',
url: '#',
},
{
title: 'Billing',
url: '#',
},
{
title: 'Limits',
url: '#',
},
],
},
],
projects: [
{
name: 'Design Engineering',
url: '#',
icon: Frame,
},
{
name: 'Sales & Marketing',
url: '#',
icon: PieChart,
},
{
name: 'Travel',
url: '#',
icon: Map,
},
],
}
const activeTeam = ref(data.teams[0])
function setActiveTeam(team: typeof data.teams[number]) {
activeTeam.value = team
}
</script>
<template>
<SidebarProvider>
<Sidebar collapsible="icon">
<SidebarHeader>
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<SidebarMenuButton
size="lg"
class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
>
<div class="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground">
<component :is="activeTeam.logo" class="size-4" />
</div>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-semibold">{{ activeTeam.name }}</span>
<span class="truncate text-xs">{{ activeTeam.plan }}</span>
</div>
<ChevronsUpDown class="ml-auto" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
class="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"
align="start"
side="bottom"
:side-offset="4"
>
<DropdownMenuLabel class="text-xs text-muted-foreground">
Teams
</DropdownMenuLabel>
<DropdownMenuItem
v-for="(team, index) in data.teams"
:key="team.name"
class="gap-2 p-2"
@click="setActiveTeam(team)"
>
<div class="flex size-6 items-center justify-center rounded-sm border">
<component :is="team.logo" class="size-4 shrink-0" />
</div>
{{ team.name }}
<DropdownMenuShortcut>{{ index + 1 }}</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem class="gap-2 p-2">
<div class="flex size-6 items-center justify-center rounded-md border bg-background">
<Plus class="size-4" />
</div>
<div class="font-medium text-muted-foreground">
Add team
</div>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Platform</SidebarGroupLabel>
<SidebarMenu>
<Collapsible
v-for="item in data.navMain"
:key="item.title"
as-child
:default-open="item.isActive"
class="group/collapsible"
>
<SidebarMenuItem>
<CollapsibleTrigger as-child>
<SidebarMenuButton :tooltip="item.title">
<component :is="item.icon" />
<span>{{ item.title }}</span>
<ChevronRight class="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>
<SidebarMenuSubItem
v-for="subItem in item.items"
:key="subItem.title"
>
<SidebarMenuSubButton as-child>
<a :href="subItem.url">
<span>{{ subItem.title }}</span>
</a>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
</SidebarMenuSub>
</CollapsibleContent>
</SidebarMenuItem>
</Collapsible>
</SidebarMenu>
</SidebarGroup>
<SidebarGroup class="group-data-[collapsible=icon]:hidden">
<SidebarGroupLabel>Projects</SidebarGroupLabel>
<SidebarMenu>
<SidebarMenuItem
v-for="item in data.projects"
:key="item.name"
>
<SidebarMenuButton as-child>
<a :href="item.url">
<component :is="item.icon" />
<span>{{ item.name }}</span>
</a>
</SidebarMenuButton>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<SidebarMenuAction show-on-hover>
<MoreHorizontal />
<span class="sr-only">More</span>
</SidebarMenuAction>
</DropdownMenuTrigger>
<DropdownMenuContent class="w-48 rounded-lg" side="bottom" align="end">
<DropdownMenuItem>
<Folder class="text-muted-foreground" />
<span>View Project</span>
</DropdownMenuItem>
<DropdownMenuItem>
<Forward class="text-muted-foreground" />
<span>Share Project</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Trash2 class="text-muted-foreground" />
<span>Delete Project</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton class="text-sidebar-foreground/70">
<MoreHorizontal class="text-sidebar-foreground/70" />
<span>More</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<SidebarMenuButton
size="lg"
class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
>
<Avatar class="h-8 w-8 rounded-lg">
<AvatarImage :src="data.user.avatar" :alt="data.user.name" />
<AvatarFallback class="rounded-lg">
CN
</AvatarFallback>
</Avatar>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-semibold">{{ data.user.name }}</span>
<span class="truncate text-xs">{{ data.user.email }}</span>
</div>
<ChevronsUpDown class="ml-auto size-4" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent class="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg" side="bottom" align="end" :side-offset="4">
<DropdownMenuLabel class="p-0 font-normal">
<div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<Avatar class="h-8 w-8 rounded-lg">
<AvatarImage :src="data.user.avatar" :alt="data.user.name" />
<AvatarFallback class="rounded-lg">
CN
</AvatarFallback>
</Avatar>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-semibold">{{ data.user.name }}</span>
<span class="truncate text-xs">{{ data.user.email }}</span>
</div>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<Sparkles />
Upgrade to Pro
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<BadgeCheck />
Account
</DropdownMenuItem>
<DropdownMenuItem>
<CreditCard />
Billing
</DropdownMenuItem>
<DropdownMenuItem>
<Bell />
Notifications
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>
<LogOut />
Log out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
</SidebarFooter>
<SidebarRail />
</Sidebar>
<SidebarInset>
<header class="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12">
<div class="flex items-center gap-2 px-4">
<SidebarTrigger class="-ml-1" />
<Separator orientation="vertical" class="mr-2 h-4" />
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem class="hidden md:block">
<BreadcrumbLink href="#">
Building Your Application
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator class="hidden md:block" />
<BreadcrumbItem>
<BreadcrumbPage>Data Fetching</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
</div>
</header>
<div class="flex flex-1 flex-col gap-4 p-4 pt-0">
<div class="grid auto-rows-min gap-4 md:grid-cols-3">
<div class="aspect-video rounded-xl bg-muted/50" />
<div class="aspect-video rounded-xl bg-muted/50" />
<div class="aspect-video rounded-xl bg-muted/50" />
</div>
<div class="min-h-[100vh] flex-1 rounded-xl bg-muted/50 md:min-h-min" />
</div>
</SidebarInset>
</SidebarProvider>
</template>

View File

@ -42,7 +42,7 @@ const itemsToDisplay = 3
const firstLabel = computed(() => items.value[0]?.label) const firstLabel = computed(() => items.value[0]?.label)
const allButLastTwoItems = computed(() => items.value.slice(1, -2)) const allButLastTwoItems = computed(() => items.value.slice(1, -2))
const remainingItems = computed(() => items.value.slice(-itemsToDisplay + 1)) const remainingItems = computed(() => items.value.slice(-Math.min(itemsToDisplay, items.value.length) + 1))
</script> </script>
<template> <template>

View File

@ -38,7 +38,7 @@ function onSubmit(values: any) {
</script> </script>
<template> <template>
<Form v-slot="{ submitForm }" as="" keep-values :validation-schema="formSchema" @submit="onSubmit"> <Form v-slot="{ handleSubmit }" as="" keep-values :validation-schema="formSchema">
<Dialog> <Dialog>
<DialogTrigger as-child> <DialogTrigger as-child>
<Button variant="outline"> <Button variant="outline">
@ -53,7 +53,7 @@ function onSubmit(values: any) {
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<form @submit="submitForm"> <form id="dialogForm" @submit="handleSubmit($event, onSubmit)">
<FormField v-slot="{ componentField }" name="username"> <FormField v-slot="{ componentField }" name="username">
<FormItem> <FormItem>
<FormLabel>Username</FormLabel> <FormLabel>Username</FormLabel>

View File

@ -12,36 +12,36 @@ import {
const components: { title: string, href: string, description: string }[] = [ const components: { title: string, href: string, description: string }[] = [
{ {
title: 'Alert Dialog', title: 'Alert Dialog',
href: '/docs/primitives/alert-dialog', href: '/docs/components/alert-dialog',
description: description:
'A modal dialog that interrupts the user with important content and expects a response.', 'A modal dialog that interrupts the user with important content and expects a response.',
}, },
{ {
title: 'Hover Card', title: 'Hover Card',
href: '/docs/primitives/hover-card', href: '/docs/components/hover-card',
description: description:
'For sighted users to preview content available behind a link.', 'For sighted users to preview content available behind a link.',
}, },
{ {
title: 'Progress', title: 'Progress',
href: '/docs/primitives/progress', href: '/docs/components/progress',
description: description:
'Displays an indicator showing the completion progress of a task, typically displayed as a progress bar.', 'Displays an indicator showing the completion progress of a task, typically displayed as a progress bar.',
}, },
{ {
title: 'Scroll-area', title: 'Scroll-area',
href: '/docs/primitives/scroll-area', href: '/docs/components/scroll-area',
description: 'Visually or semantically separates content.', description: 'Visually or semantically separates content.',
}, },
{ {
title: 'Tabs', title: 'Tabs',
href: '/docs/primitives/tabs', href: '/docs/components/tabs',
description: description:
'A set of layered sections of content—known as tab panels—that are displayed one at a time.', 'A set of layered sections of content—known as tab panels—that are displayed one at a time.',
}, },
{ {
title: 'Tooltip', title: 'Tooltip',
href: '/docs/primitives/tooltip', href: '/docs/components/tooltip',
description: description:
'A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.', 'A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.',
}, },
@ -72,10 +72,11 @@ const components: { title: string, href: string, description: string }[] = [
</a> </a>
</NavigationMenuLink> </NavigationMenuLink>
</li> </li>
<li> <li>
<NavigationMenuLink as-child> <NavigationMenuLink as-child>
<a <a
href="/docs" href="/docs/introduction"
class="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground" class="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground"
> >
<div class="text-sm font-medium leading-none">Introduction</div> <div class="text-sm font-medium leading-none">Introduction</div>
@ -101,7 +102,7 @@ const components: { title: string, href: string, description: string }[] = [
<li> <li>
<NavigationMenuLink as-child> <NavigationMenuLink as-child>
<a <a
href="/docs/primitives/typography" href="/docs/typography"
class="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground" class="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground"
> >
<div class="text-sm font-medium leading-none">Typography</div> <div class="text-sm font-medium leading-none">Typography</div>
@ -135,7 +136,7 @@ const components: { title: string, href: string, description: string }[] = [
</NavigationMenuContent> </NavigationMenuContent>
</NavigationMenuItem> </NavigationMenuItem>
<NavigationMenuItem> <NavigationMenuItem>
<NavigationMenuLink href="/docs" :class="navigationMenuTriggerStyle()"> <NavigationMenuLink href="/docs/introduction" :class="navigationMenuTriggerStyle()">
Documentation Documentation
</NavigationMenuLink> </NavigationMenuLink>
</NavigationMenuItem> </NavigationMenuItem>

View File

@ -1,3 +1,4 @@
import type { InputComponents } from './interface'
import AutoFormFieldArray from './AutoFormFieldArray.vue' import AutoFormFieldArray from './AutoFormFieldArray.vue'
import AutoFormFieldBoolean from './AutoFormFieldBoolean.vue' import AutoFormFieldBoolean from './AutoFormFieldBoolean.vue'
import AutoFormFieldDate from './AutoFormFieldDate.vue' import AutoFormFieldDate from './AutoFormFieldDate.vue'
@ -7,7 +8,7 @@ import AutoFormFieldInput from './AutoFormFieldInput.vue'
import AutoFormFieldNumber from './AutoFormFieldNumber.vue' import AutoFormFieldNumber from './AutoFormFieldNumber.vue'
import AutoFormFieldObject from './AutoFormFieldObject.vue' import AutoFormFieldObject from './AutoFormFieldObject.vue'
export const INPUT_COMPONENTS = { export const INPUT_COMPONENTS: InputComponents = {
date: AutoFormFieldDate, date: AutoFormFieldDate,
select: AutoFormFieldEnum, select: AutoFormFieldEnum,
radio: AutoFormFieldEnum, radio: AutoFormFieldEnum,

View File

@ -18,6 +18,20 @@ export interface Shape {
schema?: ZodAny schema?: ZodAny
} }
export interface InputComponents {
date: Component;
select: Component;
radio: Component;
checkbox: Component;
switch: Component;
textarea: Component;
number: Component;
string: Component;
file: Component;
array: Component;
object: Component;
};
export interface ConfigItem { export interface ConfigItem {
/** Value for the `FormLabel` */ /** Value for the `FormLabel` */
label?: string label?: string

View File

@ -3,7 +3,7 @@ import { cva, type VariantProps } from 'class-variance-authority'
export { default as Button } from './Button.vue' export { default as Button } from './Button.vue'
export const buttonVariants = cva( export const buttonVariants = cva(
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50', 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
{ {
variants: { variants: {
variant: { variant: {

View File

@ -18,7 +18,7 @@ const forwardedProps = useForwardProps(delegatedProps)
<DropdownMenuItem <DropdownMenuItem
v-bind="forwardedProps" v-bind="forwardedProps"
:class="cn( :class="cn(
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50', 'relative flex cursor-default select-none items-center rounded-sm gap-2 px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0',
inset && 'pl-8', inset && 'pl-8',
props.class, props.class,
)" )"

View File

@ -6,6 +6,7 @@ export { default as NavigationMenuItem } from './NavigationMenuItem.vue'
export { default as NavigationMenuLink } from './NavigationMenuLink.vue' export { default as NavigationMenuLink } from './NavigationMenuLink.vue'
export { default as NavigationMenuList } from './NavigationMenuList.vue' export { default as NavigationMenuList } from './NavigationMenuList.vue'
export { default as NavigationMenuTrigger } from './NavigationMenuTrigger.vue' export { default as NavigationMenuTrigger } from './NavigationMenuTrigger.vue'
export { default as NavigationMenuViewport } from './NavigationMenuViewport.vue'
export const navigationMenuTriggerStyle = cva( export const navigationMenuTriggerStyle = cva(
'group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50', 'group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50',

View File

@ -19,7 +19,7 @@ const forwardedProps = useForwardProps(delegatedProps)
<SelectTrigger <SelectTrigger
v-bind="forwardedProps" v-bind="forwardedProps"
:class="cn( :class="cn(
'flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:truncate text-start', 'flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:truncate text-start',
props.class, props.class,
)" )"
> >

View File

@ -0,0 +1,85 @@
<script setup lang="ts">
import type { SidebarProps } from '.'
import { Sheet, SheetContent } from '@/lib/registry/new-york/ui/sheet'
import { cn } from '@/lib/utils'
import { SIDEBAR_WIDTH_MOBILE, useSidebar } from './utils'
defineOptions({
inheritAttrs: false,
})
const props = withDefaults(defineProps<SidebarProps>(), {
side: 'left',
variant: 'sidebar',
collapsible: 'offcanvas',
})
const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
</script>
<template>
<div
v-if="collapsible === 'none'"
:class="cn('flex h-full w-[--sidebar-width] flex-col bg-sidebar text-sidebar-foreground', props.class)"
v-bind="$attrs"
>
<slot />
</div>
<Sheet v-else-if="isMobile" :open="openMobile" v-bind="$attrs" @update:open="setOpenMobile">
<SheetContent
data-sidebar="sidebar"
data-mobile="true"
:side="side"
class="w-[--sidebar-width] bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden"
:style="{
'--sidebar-width': SIDEBAR_WIDTH_MOBILE,
}"
>
<div class="flex h-full w-full flex-col">
<slot />
</div>
</SheetContent>
</Sheet>
<div
v-else class="group peer hidden md:block"
:data-state="state"
:data-collapsible="state === 'collapsed' ? collapsible : ''"
:data-variant="variant"
:data-side="side"
>
<!-- This is what handles the sidebar gap on desktop -->
<div
:class="cn(
'duration-200 relative h-svh w-[--sidebar-width] bg-transparent transition-[width] ease-linear',
'group-data-[collapsible=offcanvas]:w-0',
'group-data-[side=right]:rotate-180',
variant === 'floating' || variant === 'inset'
? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]'
: 'group-data-[collapsible=icon]:w-[--sidebar-width-icon]',
)"
/>
<div
:class="cn(
'duration-200 fixed inset-y-0 z-10 hidden h-svh w-[--sidebar-width] transition-[left,right,width] ease-linear md:flex',
side === 'left'
? 'left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]'
: 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',
// Adjust the padding for floating and inset variants.
variant === 'floating' || variant === 'inset'
? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]'
: 'group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r group-data-[side=right]:border-l',
props.class,
)"
v-bind="$attrs"
>
<div
data-sidebar="sidebar"
class="flex h-full w-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow"
>
<slot />
</div>
</div>
</div>
</template>

View File

@ -0,0 +1,17 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<div
data-sidebar="content"
:class="cn('flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden', props.class)"
>
<slot />
</div>
</template>

View File

@ -0,0 +1,17 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<div
data-sidebar="footer"
:class="cn('flex flex-col gap-2 p-2', props.class)"
>
<slot />
</div>
</template>

View File

@ -0,0 +1,17 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<div
data-sidebar="group"
:class="cn('relative flex w-full min-w-0 flex-col p-2', props.class)"
>
<slot />
</div>
</template>

View File

@ -0,0 +1,27 @@
<script setup lang="ts">
import type { PrimitiveProps } from 'radix-vue'
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
import { Primitive } from 'radix-vue'
const props = defineProps<PrimitiveProps & {
class?: HTMLAttributes['class']
}>()
</script>
<template>
<Primitive
data-sidebar="group-action"
:as="as"
:as-child="asChild"
:class="cn(
'absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
// Increases the hit area of the button on mobile.
'after:absolute after:-inset-2 after:md:hidden',
'group-data-[collapsible=icon]:hidden',
props.class,
)"
>
<slot />
</Primitive>
</template>

View File

@ -0,0 +1,17 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<div
data-sidebar="group-content"
:class="cn('w-full text-sm', props.class)"
>
<slot />
</div>
</template>

View File

@ -0,0 +1,24 @@
<script setup lang="ts">
import type { PrimitiveProps } from 'radix-vue'
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
import { Primitive } from 'radix-vue'
const props = defineProps<PrimitiveProps & {
class?: HTMLAttributes['class']
}>()
</script>
<template>
<Primitive
data-sidebar="group-label"
:as="as"
:as-child="asChild"
:class="cn(
'duration-200 flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opa] ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0',
props.class)"
>
<slot />
</Primitive>
</template>

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