Merge branch 'dev' into feature/dialog-with-scroll

This commit is contained in:
hrynevych.romann 2024-02-14 21:43:08 +02:00
commit 952a18d898
483 changed files with 7677 additions and 5882 deletions

View File

@ -6,62 +6,27 @@ body:
- type: markdown
attributes:
value: |
**Before You Start...**
Thanks for taking the time to fill out this bug report!
This form is only for submitting bug reports. If you have a usage question
or are unsure if this is really a bug, make sure to:
- Read the [docs](https://radix-vue.com/)
- Ask on [Discord Chat](https://chat.radix-vue.com/)
- Ask on [GitHub Discussions](https://github.com/shadcn-vue/shadcn-vue/discussions)
Also try to search for your issue - it may have already been answered or even fixed.
However, if you find that an old, closed issue still persists in the latest version,
you should open a new issue using the form below instead of commenting on the old issue.
- type: textarea
id: bug-env
attributes:
label: Environment
description: Please provide the following information about your environment.
value: |
Developement/Production OS: Windows 10 19043.1110
Node version: 16.0.0
Package manager: pnpm@8.6.0
Radix Vue version: 1.0.0
Shadcn Vue version: 1.0.0
Vue version: 3.0.0
Nuxt version: 3.0.0
Nuxt mode: universal
Nuxt target: server
CSS framework: tailwindcss@3.3.3
Client OS: Windows 10 19043.1110
Browser: Chrome 90.0.4430.212
render: bash
validations:
required: true
- type: input
id: reproduction-link
id: reproduction
attributes:
label: Link to minimal reproduction
label: Reproduction
description: |
Please provide a link to a minimal reproduction of the bug.
A minimal reproduction is a CodeSandbox, CodePen, or a StackBlitz that contains the bare minimum amount of code needed to show the bug.
A minimal reproduction is required unless you are absolutely sure that the issue is obvious and the provided information is enough to understand the problem
A [minimal reproduction](https://stackoverflow.com/help/minimal-reproducible-example) is **required**, otherwise the issue might be closed without further notice. [**Why?**](https://antfu.me/posts/why-reproductions-are-required)
This is **required** for us to be able to triage your issue in a timely manner.
To get started, you can use the StackBlitz and CodeSandbox playgrounds in shadcn-vue demos:
https://www.shadcn-vue.com/docs/components/accordion.html
Please do not just fill in a random link. The issue will be closed if no valid reproduction is provided.
placeholder: Reproduction Link
validations:
required: true
- type: textarea
id: steps-to-reproduce
attributes:
label: Steps to reproduce
description: |
How do you trigger this bug? Please walk us through it step by step.
Note that you can use [Markdown](https://guides.github.com/features/mastering-markdown/) to format lists and code.
placeholder: Steps to reproduce
or use template presets
https://vite.new
https://nuxt.new
placeholder: Reproduction
validations:
required: true
- type: textarea
@ -73,14 +38,18 @@ body:
validations:
required: true
- type: textarea
id: expected-behavior
id: system-info
attributes:
label: Expected behavior
description: A clear and concise description of what you expected to happen.
- type: textarea
id: screenshots
label: System Info
description: Output of `npx envinfo --system --npmPackages vue,@vueuse/core,radix-vue,nuxt,shadcn-vue,shadcn-nuxt,unplugin-auto-import --binaries --browsers`
render: bash
placeholder: System, Binaries, Browsers
validations:
required: true
- type: checkboxes
id: contributes
attributes:
label: Conext & Screenshots (if applicable)
description: |
If applicable, provide any additional context or screenshots of the bug.
You can drag and drop images here to add them to the issue.
label: Contributes
options:
- label: I am willing to submit a PR to fix this issue
- label: I am willing to submit a PR with failing tests

View File

@ -17,8 +17,6 @@ on:
- opened
# When a labeled '🚀request-deploy' pull request from forked repo, it will be deploy to Cloudflare Pages
- labeled
paths:
- 'apps/www/**'
# Allows you to run this workflow manually from the Actions tab
# eslint-disable-next-line yml/no-empty-mapping-value
workflow_dispatch:
@ -52,9 +50,6 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha || github.ref }}
fetch-depth: 0
# Run a build step here
- name: Setup Node.js environment

View File

@ -3,15 +3,15 @@ import { defineConfig } from 'vitepress'
import Icons from 'unplugin-icons/vite'
import tailwind from 'tailwindcss'
import autoprefixer from 'autoprefixer'
import { createCssVariablesTheme } from 'shikiji'
import { createCssVariablesTheme } from 'shiki'
// import { transformerMetaWordHighlight, transformerNotationWordHighlight } from '@shikijs/transformers'
import { siteConfig } from './theme/config/site'
import ComponentPreviewPlugin from './theme/plugins/previewer'
const cssVariables = createCssVariablesTheme({
name: 'css-variables',
variablePrefix: '--shiki-',
variableDefaults: {},
fontStyle: true,
})
// https://vitepress.dev/reference/site-config
@ -59,6 +59,10 @@ export default defineConfig({
srcDir: path.resolve(__dirname, '../src'),
markdown: {
theme: cssVariables,
codeTransformers: [
// transformerMetaWordHighlight(),
// transformerNotationWordHighlight(),
],
config(md) {
md.use(ComponentPreviewPlugin)
},

View File

@ -18,9 +18,9 @@ const kbdClass = computed(() => {
{
variants: {
size: {
xs: 'min-h-[16px] text-[10px] h-4 px-1',
sm: 'min-h-[20px] text-[11px] h-5 px-1',
md: 'min-h-[24px] text-[12px] h-6 px-1.5',
xs: 'min-h-4 text-[10px] h-4 px-1',
sm: 'min-h-5 text-[11px] h-5 px-1',
md: 'min-h-6 text-[12px] h-6 px-1.5',
},
},
},

View File

@ -2,6 +2,7 @@
import PageHeader from '../components/PageHeader.vue'
import PageHeaderHeading from '../components/PageHeaderHeading.vue'
import PageHeaderDescription from '../components/PageHeaderDescription.vue'
import PageAction from '../components/PageAction.vue'
import ExamplesNav from '../components/ExamplesNav.vue'
import { announcementConfig } from '../config/site'
import GitHubIcon from '~icons/radix-icons/github-logo'
@ -31,7 +32,7 @@ import DashboardExample from '@/examples/dashboard/Example.vue'
apps. Accessible. Customizable. Open Source.
</PageHeaderDescription>
<section class="flex w-full items-center space-x-4 pb-8 pt-4 md:pb-10">
<PageAction>
<a
href="/docs/introduction"
:class="cn(buttonVariants(), 'rounded-[6px]')"
@ -49,7 +50,7 @@ import DashboardExample from '@/examples/dashboard/Example.vue'
<GitHubIcon class="mr-2 h-4 w-4" />
GitHub
</a>
</section>
</PageAction>
</PageHeader>
<ExamplesNav />
<section class="space-y-8 overflow-hidden rounded-lg border-2 border-primary dark:border-muted md:hidden">

View File

@ -0,0 +1,14 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
</script>
<template>
<section
:class="cn(
'flex w-full items-center justify-center space-x-4 py-4 md:pb-10',
$attrs.class ?? '',
)"
>
<slot />
</section>
</template>

View File

@ -7,7 +7,7 @@ import { cn } from '@/lib/utils'
<template>
<section
:class="cn(
'flex max-w-[980px] flex-col items-start gap-2 px-4 pt-8 md:pt-12',
'mx-auto flex max-w-[980px] flex-col items-center gap-2 py-8 md:py-12 md:pb-8 lg:py-24 lg:pb-20',
$attrs.class ?? '',
)"
>

View File

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

View File

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

View File

@ -254,6 +254,12 @@ export const docsConfig: DocsConfig = {
href: '/docs/components/pagination',
items: [],
},
{
title: 'Pin Input',
href: '/docs/components/pin-input',
label: 'New',
items: [],
},
{
title: 'Popover',
href: '/docs/components/popover',
@ -299,6 +305,12 @@ export const docsConfig: DocsConfig = {
href: '/docs/components/slider',
items: [],
},
{
title: 'Sonner',
href: '/docs/components/sonner',
label: 'New',
items: [],
},
{
title: 'Switch',
href: '/docs/components/switch',
@ -322,7 +334,6 @@ export const docsConfig: DocsConfig = {
{
title: 'Toast',
href: '/docs/components/toast',
label: 'New',
items: [],
},
{

View File

@ -15,6 +15,6 @@ export const siteConfig = {
export const announcementConfig = {
icon: '✨',
title: 'VSCode extension',
link: '/docs/installation.html#vscode-extension',
title: 'New components!',
link: '/docs/components/carousel.html',
}

View File

@ -6,6 +6,7 @@ import EditLink from '../components/EditLink.vue'
import { ScrollArea } from '@/lib/registry/default/ui/scroll-area'
import { Badge } from '@/lib/registry/default/ui/badge'
import RadixIconsCode from '~icons/radix-icons/code'
import RadixIconsExternalLink from '~icons/radix-icons/external-link'
import ChevronRightIcon from '~icons/lucide/chevron-right'
const $route = useRoute()
@ -20,7 +21,7 @@ const sourceLink = 'https://github.com/radix-vue/shadcn-vue/tree/dev/'
<aside
class="fixed top-14 z-30 -ml-2 hidden h-[calc(100vh-3.5rem)] w-full shrink-0 md:sticky md:block overflow-y-auto"
>
<ScrollArea orientation="vertical" class="h-full py-6 pl-8 pr-6 lg:py-8" :type="'auto'">
<ScrollArea orientation="vertical" class="relative overflow-hidden h-full py-6 pr-6 lg:py-8" :type="'auto'">
<div class="w-full">
<div v-for="docsGroup in docsConfig.sidebarNav" :key="docsGroup.title" class="pb-4">
<h4
@ -45,9 +46,9 @@ const sourceLink = 'https://github.com/radix-vue/shadcn-vue/tree/dev/'
>
{{ doc.title }}
<Badge v-if="doc.label" class="ml-2" :variant="'secondary'">
<span v-if="doc.label" class="ml-2 rounded-md bg-[#adfa1d] px-1.5 py-0.5 text-xs leading-none text-[#000000] no-underline group-hover:no-underline">
{{ doc.label }}
</Badge>
</span>
</a>
</div>
</div>
@ -81,6 +82,10 @@ const sourceLink = 'https://github.com/radix-vue/shadcn-vue/tree/dev/'
</div>
<div class="flex items-center space-x-2 pt-4">
<a v-if="frontmatter.docs" :href="frontmatter.docs" target="_blank" class="inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 select-none border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80">
<RadixIconsExternalLink class="mr-1" />
Docs
</a>
<a v-if="frontmatter.source" :href="sourceLink + frontmatter.source" target="_blank" class="inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 select-none border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80">
<RadixIconsCode class="mr-1" />
Component Source

View File

@ -2,6 +2,7 @@
import PageHeader from '../components/PageHeader.vue'
import PageHeaderHeading from '../components/PageHeaderHeading.vue'
import PageHeaderDescription from '../components/PageHeaderDescription.vue'
import PageAction from '../components/PageAction.vue'
import ExamplesNav from '../components/ExamplesNav.vue'
import { announcementConfig } from '../config/site'
import ArrowRightIcon from '~icons/radix-icons/arrow-right'
@ -36,7 +37,7 @@ import { cn } from '@/lib/utils'
components. Use this as a guide to build your own.
</PageHeaderDescription>
<section class="flex w-full items-center space-x-4 pb-8 pt-4 md:pb-10">
<PageAction>
<a
href="/docs/introduction"
:class="cn(buttonVariants(), 'rounded-[6px]')"
@ -52,7 +53,7 @@ import { cn } from '@/lib/utils'
>
Components
</a>
</section>
</PageAction>
</PageHeader>
<section>
<ExamplesNav />

View File

@ -17,6 +17,7 @@ import RadixIconsSun from '~icons/radix-icons/sun'
import { useConfigStore } from '@/stores/config'
import { Dialog, DialogContent } from '@/lib/registry/default/ui/dialog'
import { Toaster as DefaultToaster } from '@/lib/registry/default/ui/toast'
import { Toaster as NewYorkSonner } from '@/lib/registry/new-york/ui/sonner'
import { Toaster as NewYorkToaster } from '@/lib/registry/new-york/ui/toast'
import File from '~icons/radix-icons/file'
@ -86,7 +87,7 @@ watch(() => $route.path, (n) => {
<div class="flex min-h-screen flex-col bg-background">
<header class="sticky z-40 top-0 bg-background/80 backdrop-blur-lg border-b border-border">
<div
class="container flex justify-between h-14 items-center"
class="container flex justify-between h-14 max-w-screen-2xl items-center"
>
<MobileNav />
@ -134,20 +135,22 @@ watch(() => $route.path, (n) => {
:href="link.href" target="_blank"
:variant="'ghost'" :size="'icon'"
>
<component :is="link.icon" class="w-[20px] h-[20px]" />
<component :is="link.icon" class="w-[20px] h-5" />
</Button>
<Button
class="flex items-center justify-center"
aria-label="Toggle dark mode"
:variant="'ghost'"
:size="'icon'" @click="toggleDark()"
>
<component
:is="isDark ? RadixIconsSun : RadixIconsMoon"
class="w-[20px] h-[20px] text-foreground"
/>
</Button>
<ClientOnly>
<Button
class="flex items-center justify-center"
aria-label="Toggle dark mode"
:variant="'ghost'"
:size="'icon'" @click="toggleDark()"
>
<component
:is="isDark ? RadixIconsSun : RadixIconsMoon"
class="w-[20px] h-5 text-foreground"
/>
</Button>
</ClientOnly>
</div>
</div>
</div>
@ -287,6 +290,9 @@ watch(() => $route.path, (n) => {
</DialogContent>
</Dialog>
<DefaultToaster />
<ClientOnly>
<NewYorkSonner :theme="isDark ? 'dark' : 'light'" />
</ClientOnly>
<NewYorkToaster />
</div>
</template>

View File

@ -5,49 +5,49 @@
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
--primary: 240 5.9% 10%;
--primary-foreground: 0 0% 98%;
--secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 240 5% 64.9%;
--radius: 0.5rem;
}
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
--primary: 240 5.9% 10%;
--primary-foreground: 0 0% 98%;
--secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--destructive: 0 72.22% 50.59%;
--destructive-foreground: 0 0% 98%;
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 240 5% 64.9%;
--radius: 0.5rem;
}
.dark {
--background: 240 10% 3.9%;
--foreground: 0 0% 98%;
--card: 240 10% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 240 5.9% 10%;
--secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%;
--muted: 240 3.7% 15.9%;
--muted-foreground: 240 5% 64.9%;
--accent: 240 3.7% 15.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 85.7% 97.3%;
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--ring: 240 4.9% 83.9%;
}
.dark {
--background: 240 10% 3.9%;
--foreground: 0 0% 98%;
--card: 240 10% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 240 5.9% 10%;
--secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%;
--muted: 240 3.7% 15.9%;
--muted-foreground: 240 5% 64.9%;
--accent: 240 3.7% 15.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 85.7% 97.3%;
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--ring: 240 4.9% 83.9%;
}
* {
@ -116,7 +116,7 @@
.step:before {
@apply absolute w-9 h-9 bg-muted rounded-full font-mono font-medium text-center text-base inline-flex items-center justify-center -indent-px border-4 border-background;
@apply ml-[-50px] mt-[-4px];
@apply -ml-[50px] -mt-1;
content: counter(step);
}
}
@ -139,7 +139,7 @@ pre code {
}
pre code .line {
@apply px-4 min-h-[1.5rem] !py-0.5 w-full inline-block;
@apply px-4 min-h-6 !py-0.5 w-full inline-block;
}
.line-number {
@ -149,4 +149,4 @@ pre code .line {
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 0.3s;
}
}

View File

@ -1,5 +1,5 @@
:root {
--shiki-foreground: #EEEEEE;
--shiki-foreground: #FFF8;
--shiki-color-background: #ffffff;
--shiki-token-constant: #ffffff;
--shiki-token-string: #ffffff88;
@ -7,7 +7,14 @@
--shiki-token-keyword: #ffffff88;
--shiki-token-parameter: #AA0000;
--shiki-token-function: #ffffff;
--shiki-token-string-expression: #ffffff88;
--shiki-token-string-expression: #ebebeb;
--shiki-token-punctuation: #ffffff;
--shiki-token-link: #EE0000;
}
.shiki .highlighted-word {
border-radius: calc(var(--radius) - 2px);
border-color: rgba(63,63,70,.7);
background-color: rgba(63,63,70,.5);
padding: 0.25rem;
}

View File

@ -3,6 +3,7 @@
--vp-icon-copied: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' height='20' width='20' stroke='rgba(128,128,128,1)' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2M9 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M9 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2m-6 9 2 2 4-4'/%3E%3C/svg%3E");
--vp-code-bg: hsl(var(--muted));
--vp-c-divider: hsl(var(--muted));
--vp-code-block-color: #fff
}
@ -414,7 +415,7 @@
.vp-doc div[class*='language-'].line-numbers-mode {
/*rtl:ignore*/
padding-left: 32px;
padding-left: 0px;
}
.vp-doc .line-numbers-wrapper {
@ -566,4 +567,12 @@
.vp-external-link-icon::after {
content: '';
}
.vp-doc [class*=language-] code {
color: var(--vp-code-block-color);
}
.line-numbers-mode pre code .line {
padding-left: 3rem !important;
}

View File

@ -317,6 +317,13 @@ export const Index = {
component: () => import('../src/lib/registry/default/example/ContextMenuDemo.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/ContextMenuDemo.vue'],
},
DataTableColumnPinningDemo: {
name: 'DataTableColumnPinningDemo',
type: 'components:example',
registryDependencies: ['button', 'checkbox', 'dropdown-menu', 'input', 'table', 'utils'],
component: () => import('../src/lib/registry/default/example/DataTableColumnPinningDemo.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/DataTableColumnPinningDemo.vue'],
},
DataTableDemo: {
name: 'DataTableDemo',
type: 'components:example',
@ -492,6 +499,20 @@ export const Index = {
component: () => import('../src/lib/registry/default/example/PaginationDemo.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/PaginationDemo.vue'],
},
PinInputDemo: {
name: 'PinInputDemo',
type: 'components:example',
registryDependencies: ['pin-input'],
component: () => import('../src/lib/registry/default/example/PinInputDemo.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/PinInputDemo.vue'],
},
PinInputFormDemo: {
name: 'PinInputFormDemo',
type: 'components:example',
registryDependencies: ['pin-input', 'button', 'form', 'toast'],
component: () => import('../src/lib/registry/default/example/PinInputFormDemo.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/PinInputFormDemo.vue'],
},
PopoverDemo: {
name: 'PopoverDemo',
type: 'components:example',
@ -520,6 +541,13 @@ export const Index = {
component: () => import('../src/lib/registry/default/example/RadioGroupForm.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/RadioGroupForm.vue'],
},
RangePickerWithSlot: {
name: 'RangePickerWithSlot',
type: 'components:example',
registryDependencies: ['utils', 'button', 'calendar', 'popover'],
component: () => import('../src/lib/registry/default/example/RangePickerWithSlot.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/RangePickerWithSlot.vue'],
},
ScrollAreaDemo: {
name: 'ScrollAreaDemo',
type: 'components:example',
@ -583,6 +611,13 @@ export const Index = {
component: () => import('../src/lib/registry/default/example/SliderDemo.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/SliderDemo.vue'],
},
SonnerDemo: {
name: 'SonnerDemo',
type: 'components:example',
registryDependencies: ['button'],
component: () => import('../src/lib/registry/default/example/SonnerDemo.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/SonnerDemo.vue'],
},
SwitchDemo: {
name: 'SwitchDemo',
type: 'components:example',
@ -1215,6 +1250,13 @@ export const Index = {
component: () => import('../src/lib/registry/new-york/example/ContextMenuDemo.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/ContextMenuDemo.vue'],
},
DataTableColumnPinningDemo: {
name: 'DataTableColumnPinningDemo',
type: 'components:example',
registryDependencies: ['button', 'checkbox', 'dropdown-menu', 'input', 'table', 'utils'],
component: () => import('../src/lib/registry/new-york/example/DataTableColumnPinningDemo.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/DataTableColumnPinningDemo.vue'],
},
DataTableDemo: {
name: 'DataTableDemo',
type: 'components:example',
@ -1390,6 +1432,20 @@ export const Index = {
component: () => import('../src/lib/registry/new-york/example/PaginationDemo.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/PaginationDemo.vue'],
},
PinInputDemo: {
name: 'PinInputDemo',
type: 'components:example',
registryDependencies: ['pin-input'],
component: () => import('../src/lib/registry/new-york/example/PinInputDemo.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/PinInputDemo.vue'],
},
PinInputFormDemo: {
name: 'PinInputFormDemo',
type: 'components:example',
registryDependencies: ['pin-input', 'button', 'form', 'toast'],
component: () => import('../src/lib/registry/new-york/example/PinInputFormDemo.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/PinInputFormDemo.vue'],
},
PopoverDemo: {
name: 'PopoverDemo',
type: 'components:example',
@ -1418,6 +1474,13 @@ export const Index = {
component: () => import('../src/lib/registry/new-york/example/RadioGroupForm.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/RadioGroupForm.vue'],
},
RangePickerWithSlot: {
name: 'RangePickerWithSlot',
type: 'components:example',
registryDependencies: ['utils', 'button', 'calendar', 'popover'],
component: () => import('../src/lib/registry/new-york/example/RangePickerWithSlot.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/RangePickerWithSlot.vue'],
},
ScrollAreaDemo: {
name: 'ScrollAreaDemo',
type: 'components:example',
@ -1481,6 +1544,13 @@ export const Index = {
component: () => import('../src/lib/registry/new-york/example/SliderDemo.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/SliderDemo.vue'],
},
SonnerDemo: {
name: 'SonnerDemo',
type: 'components:example',
registryDependencies: ['button'],
component: () => import('../src/lib/registry/new-york/example/SonnerDemo.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/SonnerDemo.vue'],
},
SwitchDemo: {
name: 'SwitchDemo',
type: 'components:example',

View File

@ -1,7 +1,7 @@
{
"name": "www",
"type": "module",
"version": "0.8.7",
"version": "0.9.0",
"files": [
"dist"
],
@ -19,24 +19,25 @@
"@morev/vue-transitions": "^2.3.6",
"@radix-icons/vue": "^1.0.0",
"@stackblitz/sdk": "^1.9.0",
"@tanstack/vue-table": "^8.11.6",
"@unovis/ts": "^1.3.1",
"@unovis/vue": "^1.3.1",
"@vee-validate/zod": "^4.12.4",
"@tanstack/vue-table": "^8.11.8",
"@unovis/ts": "^1.3.3",
"@unovis/vue": "^1.3.3",
"@vee-validate/zod": "^4.12.5",
"@vueuse/core": "^10.7.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"codesandbox": "^2.2.3",
"date-fns": "^2.30.0",
"embla-carousel": "8.0.0-rc19",
"embla-carousel-autoplay": "8.0.0-rc19",
"embla-carousel-vue": "8.0.0-rc19",
"embla-carousel": "^8.0.0-rc22",
"embla-carousel-autoplay": "^8.0.0-rc22",
"embla-carousel-vue": "^8.0.0-rc22",
"lucide-vue-next": "^0.276.0",
"radix-vue": "^1.3.2",
"radix-vue": "^1.4.1",
"tailwindcss-animate": "^1.0.7",
"v-calendar": "^3.1.2",
"vee-validate": "4.12.4",
"vue": "^3.4.14",
"vee-validate": "4.12.5",
"vue": "^3.4.15",
"vue-sonner": "^1.0.3",
"vue-wrap-balancer": "^1.1.3",
"zod": "^3.22.4"
},
@ -45,25 +46,27 @@
"@iconify-json/tabler": "^1.1.89",
"@iconify/json": "^2.2.108",
"@iconify/vue": "^4.1.1",
"@shikijs/transformers": "^1.0.0-beta.3",
"@types/lodash.template": "^4.5.2",
"@types/node": "^20.8.10",
"@vitejs/plugin-vue": "^5.0.3",
"@vitejs/plugin-vue-jsx": "^3.1.0",
"@vue/compiler-core": "^3.4.14",
"@vue/compiler-dom": "^3.4.14",
"@vue/compiler-core": "^3.4.15",
"@vue/compiler-dom": "^3.4.15",
"@vue/tsconfig": "^0.5.1",
"autoprefixer": "^10.4.16",
"autoprefixer": "^10.4.17",
"lodash.template": "^4.5.0",
"oxc-parser": "^0.2.0",
"pathe": "^1.1.2",
"rimraf": "^5.0.5",
"shikiji": "^0.10.0-beta.2",
"tailwind-merge": "^2.2.0",
"shiki": "^1.0.0-beta.3",
"tailwind-merge": "^2.2.1",
"tailwindcss": "^3.4.1",
"tsx": "^4.7.0",
"typescript": "^5.3.3",
"unplugin-icons": "^0.17.1",
"vite": "^5.0.11",
"vitepress": "^1.0.0-rc.37",
"unplugin-icons": "^0.18.3",
"vite": "^5.0.12",
"vitepress": "^1.0.0-rc.41",
"vue-tsc": "^1.8.27"
}
}

View File

@ -56,5 +56,40 @@ import { Calendar } from '@/components/ui/calendar'
</template>
```
See the [VCalendar](https://vcalendar.io/getting-started/installation.html) documentation for more information.
The API is essentially the same, i.e. props and slots. See the [VCalendar](https://vcalendar.io/getting-started/installation.html) documentation for more information.
### Slots
The slots available are [those currently supported](https://github.com/nathanreyes/v-calendar/blob/v3.1.2/src/components/Calendar/CalendarSlot.vue#L16-L28) by VCalendar, namely :
- `day-content`
- `day-popover`
- `dp-footer`
- `footer`
- `header-title-wrapper`
- `header-title`
- `header-prev-button`
- `header-next-button`
- `nav`
- `nav-prev-button`
- `nav-next-button`
- `page`
- `time-header`
Example using the `day-content` slot:
```vue
<script setup lang="ts">
import { Calendar } from '@/components/ui/calendar'
</script>
<template>
<Calendar>
<template #day-content="{ day, dayProps, dayEvents }">
<div v-bind="dayProps" v-on="dayEvents">
{{ day.label }}
</div>
</template>
</Calendar>
</template>
```

View File

@ -57,7 +57,7 @@ To set the size of the items, you can use the `basis` utility class on the `<Car
Example
```vue title="Example" showLineNumbers {4-6}
```vue:line-numbers title="Example" {4-6}
// 33% of the carousel width.
<Carousel>
<CarouselContent>
@ -71,7 +71,7 @@ Example
Responsive
```vue title="Responsive" showLineNumbers {4-6}
```vue:line-numbers title="Responsive" {4-6}
// 50% on small screens and 33% on larger screens.
<Carousel>
<CarouselContent>
@ -101,7 +101,7 @@ You can always adjust this in your own project if you need to.
Example
```vue showLineNumbers /-ml-4/ /pl-4/
```vue:line-numbers /-ml-4/ /pl-4/
<template>
<Carousel>
<CarouselContent class="-ml-4">
@ -121,7 +121,7 @@ Example
Responsive
```vue showLineNumbers /-ml-2/ /pl-2/ /md:-ml-4/ /md:pl-4/
```vue:line-numbers /-ml-2/ /pl-2/ /md:-ml-4/ /md:pl-4/
<template>
<Carousel>
<CarouselContent class="-ml-2 md:-ml-4">
@ -155,7 +155,7 @@ Use the `orientation` prop to set the orientation of the carousel.
You can pass options to the carousel using the `opts` prop. See the [Embla Carousel docs](https://www.embla-carousel.com/api/options/) for more information.
```vue showLineNumbers {3-6}
```vue:line-numbers {3-6}
<template>
<Carousel
:opts="{
@ -184,7 +184,7 @@ Use the `@init-api` emit method on `<Carousel />` component to set the instance
You can access it through setting a template ref on the `<Carousel />` component.
```vue showLineNumbers {2,5,9}
```vue:line-numbers {2,5,9}
<script setup>
const carouselContainerRef = ref<InstanceType<typeof Carousel> | null>(null)
@ -204,7 +204,7 @@ function accessApi() {
You can listen to events using the API. To get the API instance use the `@init-api` emit method on the `<Carousel />` component
```vue showLineNumbers {5,7-9,25}
```vue:line-numbers {5,7-9,25}
<script setup>
import { nextTick, ref, watch } from 'vue'
import { useCarousel } from '@/components/ui/carousel'
@ -241,7 +241,7 @@ See the [Embla Carousel docs](https://www.embla-carousel.com/api/events/) for mo
You can get the reactive slot props like `carouselRef, canScrollNext..Prev, scrollNext..Prev` using the `v-slot` directive in the `<Carousel v-slot="slotProps" />` component to extend the functionality.
```vue showLineNumbers {2}
```vue:line-numbers {2}
<template>
<Carousel v-slot="{ canScrollNext, canScrollPrev }">
...
@ -260,7 +260,7 @@ npm i embla-carousel-autoplay
```
```vue showLineNumbers {2,8-10}
```vue:line-numbers {2,8-10}
<script setup lang="ts">
import Autoplay from 'embla-carousel-autoplay'
</script>

View File

@ -5,7 +5,7 @@ primitive: https://tanstack.com/table/v8/docs/guide/introduction
---
<ComponentPreview name="DataTableDemo" />
<ComponentPreview name="DataTableDemo" />
## Introduction
@ -25,7 +25,7 @@ We'll start with the basic `<Table />` component and build a complex data table
## Table of Contents
This guide will show you how to use [TanStack Table](https://tanstack.com/table/v8) and the <Table /> component to build your own custom data table. We'll cover the following topics:
This guide will show you how to use [TanStack Table](https://tanstack.com/table/v8) and the `<Table />` component to build your own custom data table. We'll cover the following topics:
- [Basic Table](#basic-table)
- [Row Actions](#row-actions)
@ -50,6 +50,13 @@ npx shadcn-vue@latest add table
npm install @tanstack/vue-table
```
## Examples
### Column Pinning
<ComponentPreview name="DataTableColumnPinningDemo" />
## Prerequisites
We are going to build a table to show recent payments. Here's what our data looks like:
@ -109,31 +116,23 @@ Let's start by building a basic table.
First, we'll define our columns in the `columns.ts` file.
```ts:line-numbers title="components/payments/columns.ts" {1,12-27}
import type { ColumnDef } from '@tanstack/vue-table'
// This type is used to define the shape of our data.
// You can use a Zod schema here if you want.
export interface Payment {
id: string
amount: number
status: 'pending' | 'processing' | 'success' | 'failed'
email: string
}
```ts:line-numbers {1,12-27}
import { h } from 'vue'
export const columns: ColumnDef<Payment>[] = [
{
accessorKey: 'status',
header: 'Status',
},
{
accessorKey: 'email',
header: 'Email',
},
{
accessorKey: 'amount',
header: 'Amount',
},
header: () => h('div', { class: 'text-right' }, 'Amount'),
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
}).format(amount)
return h('div', { class: 'text-right font-medium' }, formatted)
},
}
]
```
@ -149,7 +148,7 @@ formatted, sorted and filtered.
Next, we'll create a `<DataTable />` component to render our table.
```ts:line-numbers
```vue:line-numbers
<script setup lang="ts" generic="TData, TValue">
import type { ColumnDef } from '@tanstack/vue-table'
import {
@ -225,7 +224,7 @@ const table = useVueTable({
Finally, we'll render our table in our index component.
```ts:line-numbers showLineNumbers{28}
```vue:line-numbers {28}
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { columns } from "./components/columns"
@ -272,7 +271,7 @@ Let's format the amount cell to display the dollar amount. We'll also align the
Update the `header` and `cell` definitions for amount as follows:
```ts:line-numbers showLineNumbers title="components/payments/columns.ts" {5-17}
```ts:line-numbers title="components/payments/columns.ts" {5-17}
import { h } from 'vue'
export const columns: ColumnDef<Payment>[] = [
@ -302,7 +301,7 @@ Let's add row actions to our table. We'll use a `<Dropdown />` component for thi
### Add the following into your `DataTableDropDown.vue` component:
```ts:line-numbers
```vue:line-numbers
// DataTableDropDown.vue
<script setup lang="ts">
import { MoreHorizontal } from 'lucide-vue-next'
@ -380,7 +379,7 @@ Next, we'll add pagination to our table.
### Update `<DataTable>`
```ts:line-numbers showLineNumbers{4,12}
```ts:line-numbers {4,12}
import {
FlexRender,
getCoreRowModel,
@ -402,8 +401,7 @@ This will automatically paginate your rows into pages of 10. See the [pagination
We can add pagination controls to our table using the `<Button />` component and the `table.previousPage()`, `table.nextPage()` API methods.
```ts:line-numbers showLineNumbers{3,15,21-39}
// components/payments/DataTable.vue
```vue:line-numbers {3,15,21-39}
<script lang="ts" generic="TData, TValue">
import { Button } from "@/components/ui/button"
@ -458,7 +456,7 @@ Let's make the email column sortable.
### Add the following into your `utils` file:
```ts:line-numbers showLineNumbers{5,6,12-17}
```ts:line-numbers {5,6,12-17}
import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
import { camelize, getCurrentInstance, toHandlerKey } from 'vue'
@ -482,7 +480,7 @@ The `valueUpdater` function updates a Vue `ref` object's value. It handles both
### Update `<DataTable>`
```ts:line-numbers showLineNumbers{4,7,16,34,41-44}
```vue:line-numbers {4,7,16,34,41-44}
<script setup lang="ts" generic="TData, TValue">
import type {
ColumnDef,
@ -545,7 +543,7 @@ const table = useVueTable({
We can now update the `email` header cell to add sorting controls.
```ts:line-numbers showLineNumbers{5,10-17}
```ts:line-numbers {5,10-17}
// components/payments/columns.ts
import type {
ColumnDef,
@ -579,7 +577,7 @@ Let's add a search input to filter emails in our table.
### Update `<DataTable>`
```ts:line-numbers showLineNumbers{4,11,19,39,48-49,52,60-64}
```vue:line-numbers {4,11,19,39,48-49,52,60-64}
<script setup lang="ts" generic="TData, TValue">
import type {
ColumnDef,
@ -664,7 +662,7 @@ Adding column visibility is fairly simple using `@tanstack/vue-table` visibility
### Update `<DataTable>`
```ts:line-numbers showLineNumbers{6,9-14,48,59,63,75-91}
```vue:line-numbers {6,9-14,48,59,63,75-91}
<script setup lang="ts" generic="TData, TValue">
import type {
ColumnDef,
@ -803,7 +801,7 @@ Next, we're going to add row selection to our table.
### Update column definitions
```ts:line-numbers showLineNumbers{3,6-20}
```ts:line-numbers {3,6-20}
import type { ColumnDef } from '@tanstack/vue-table'
import { Checkbox } from '@/components/ui/checkbox'
@ -829,7 +827,7 @@ export const columns: ColumnDef<Payment>[] = [
### Update `<DataTable>`
```ts:line-numbers showLineNumbers{10,22,27}
```vue:line-numbers {10,22,27}
<script setup lang="ts" generic="TData, TValue">
const props = defineProps<{
columns: ColumnDef<TData, TValue>[]
@ -895,7 +893,7 @@ Here are some components you can use to build your data tables. This is from the
Make any column header sortable and hideable.
```ts:line-numbers
```vue:line-numbers
<script setup lang="ts">
import type { Column } from '@tanstack/vue-table'
import { type Task } from '../data/schema'
@ -988,7 +986,7 @@ export const columns = [
Add pagination controls to your table including page size and selection count.
```ts:line-numbers
```vue:line-numbers
<script setup lang="ts">
import { type Table } from '@tanstack/vue-table'
import { type Task } from '../data/schema'
@ -1093,8 +1091,7 @@ defineProps<DataTablePaginationProps>()
A component to toggle column visibility.
```ts:line-numbers
```vue:line-numbers
<script setup lang="ts">
import type { Table } from '@tanstack/vue-table'
import { computed } from 'vue'

View File

@ -72,6 +72,10 @@ const date = ref<Date>()
<ComponentPreview name="DatePickerWithPresets" />
### With Slot
<ComponentPreview name="RangePickerWithSlot" />
### Form
<ComponentPreview name="DatePickerForm" />

View File

@ -159,7 +159,7 @@ Use `@vee-validate/zod` to integrate Zod schema validation with `vee-validate`
`toTypedSchema` also makes the form values and submitted values typed automatically and caters for both input and output types of that schema.
```vue showLineNumbers {2-3,5-7}
```vue:line-numbers {2-3,5-7}
<script setup lang="ts">
import { toTypedSchema } from '@vee-validate/zod'
import * as z from 'zod'
@ -179,7 +179,7 @@ Use the `useForm` composable from `vee-validate` or use `<Form />` component to
<TabPreview name="Composition" :names="['Composition', 'Component']">
<template #Composition>
```vue showLineNumbers {2,19-21}
```vue:line-numbers {2,19-21}
<script setup lang="ts">
import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/zod'
@ -218,7 +218,7 @@ const onSubmit = form.handleSubmit((values) => {
<template #Component>
```vue showLineNumbers {5,24-26}
```vue:line-numbers {5,24-26}
<script setup lang="ts">
import { toTypedSchema } from '@vee-validate/zod'
import * as z from 'zod'
@ -256,7 +256,7 @@ function onSubmit(values) {
Based on last step we can either use `<Form />` component or `useForm` composable
`useForm` is recommended cause values are typed automatically
```vue showLineNumbers {2}
```vue:line-numbers {2}
<script setup lang="ts">
import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/zod'

View File

@ -47,7 +47,7 @@ import {
### Link Component
When using the Nuxt.js <NuxtLink /> component, you can use `navigationMenuTriggerStyle()` to apply the correct styles to the trigger.
When using the Nuxt.js `<NuxtLink />` component, you can use `navigationMenuTriggerStyle()` to apply the correct styles to the trigger.
```ts
import { navigationMenuTriggerStyle } from '@/components/ui/navigation-menu'

View File

@ -0,0 +1,21 @@
---
title: PIN Input
description: Allows users to input a sequence of one-character alphanumeric inputs.
source: apps/www/src/lib/registry/default/ui/pin-input
primitive: https://www.radix-vue.com/components/pin-input.html
---
<ComponentPreview name="PinInputDemo" />
## Installation
```bash
npx shadcn-vue@latest add pin-input
```
## Usage
### Form
<ComponentPreview name="PinInputFormDemo" />

View File

@ -57,7 +57,7 @@ Use the `side` property to `<SheetContent />` to indicate the edge of the screen
You can adjust the size of the sheet using CSS classes:
```vue:line-numbers showLineNumbers{4}
```vue:line-numbers {4}
<template>
<Sheet>
<SheetTrigger>Open</SheetTrigger>

View File

@ -37,6 +37,6 @@ import { Skeleton } from '@/components/ui/skeleton'
</script>
<template>
<Skeleton class="w-[100px] h-[20px] rounded-full" />
<Skeleton class="w-[100px] h-5 rounded-full" />
</template>
```
```

View File

@ -0,0 +1,63 @@
---
title: Sonner
description: An opinionated toast component for Vue.
docs: https://vue-sonner.vercel.app
source: apps/www/src/lib/registry/default/ui/sonner
---
<ComponentPreview name="SonnerDemo" />
## About
The Sonner component is provided by [vue-sonner](https://vue-sonner.vercel.app/), which is a Vue port of Sonner, originally created by [Emil Kowalski](https://twitter.com/emilkowalski_) for React.
## Installation
<Steps>
### Run the following command
```bash
npx shadcn-vue@latest add sonner
```
### Add the Toaster component
Add the following `Toaster` component to your `App.vue` file:
```vue title="App.vue" {2,6}
<script setup lang="ts">
import { Toaster } from '@/components/ui/sonner'
</script>
<template>
<Toaster />
</template>
```
</Steps>
## Usage
```vue
<script setup lang="ts">
import { toast } from 'vue-sonner'
import { Button } from '@/components/ui/button'
</script>
<template>
<Button
variant="outline" @click="() => {
toast('Event has been created', {
description: 'Sunday, December 03, 2023 at 9:00 AM',
action: {
label: 'Undo',
onClick: () => console.log('Undo'),
},
})
}"
>
Add to calendar
</Button>
</template>
```

View File

@ -61,7 +61,7 @@ const { toast } = useToast()
});
}"
>
Add to calander
Add to calendar
</Button>
</template>
```

View File

@ -17,7 +17,7 @@ npm create astro@latest
You will be asked a few questions to configure your project:
```txt showLineNumbers
```txt:line-numbers
- Where should we create your new project?
./your-app-name
- How would you like to start your new project?
@ -76,7 +76,7 @@ This will install `tailwindcss` and `@astrojs/tailwind` as dependencies and set
Add the code below to the tsconfig.json file to resolve paths:
```json {2-7} showLineNumbers
```json:line-numbers {2-7}
{
"compilerOptions": {
"baseUrl": ".",
@ -99,7 +99,7 @@ npx shadcn-vue@latest init
You will be asked a few questions to configure `components.json`:
```txt showLineNumbers
```txt:line-numbers
Would you like to use TypeScript (recommended)? no / yes
Which framework are you using? Astro
Which style would you like to use? Default
@ -116,15 +116,17 @@ Write configuration to components.json. Proceed? > Y/n
Import the `globals.css` file in the `src/index.astro` file:
```ts {2} showLineNumbers
```ts:line-numbers {2}
---
import '@/styles/globals.css'
---
```
### Update astro tailwind config
To prevent serving the Tailwind base styles twice, we need to tell Astro not to apply the base styles, since we already include them in our own `globals.css` file. To do this, set the `applyBaseStyles` config option for the tailwind plugin in `astro.config.mjs` to `false`.
```ts {3-5} showLineNumbers
```ts:line-numbers {3-5}
export default defineConfig({
integrations: [
tailwind({
@ -144,7 +146,7 @@ npx shadcn-vue@latest add button
The command above will add the `Button` component to your project. You can then import it like this:
```astro {2,10} showLineNumbers
```astro:line-numbers {2,10}
---
import { Button } from "@/components/ui/button"
---

View File

@ -25,7 +25,7 @@ npx shadcn-vue@latest init
You will be asked a few questions to configure `components.json`:
```txt showLineNumbers
```txt:line-numbers
Would you like to use TypeScript (recommended)? no / yes
Which framework are you using? Vite / Nuxt / Laravel
Which style would you like to use? Default

View File

@ -180,7 +180,7 @@ npx shadcn-vue@latest init
You will be asked a few questions to configure `components.json`:
```txt showLineNumbers
```txt:line-numbers
Would you like to use TypeScript (recommended)? no / yes
Which framework are you using? Vite / Nuxt / Laravel
Which style would you like to use? Default

View File

@ -19,22 +19,82 @@ npm create vite@latest my-vue-app -- --template vue-ts
### Add Tailwind and its configuration
Install `tailwindcss` and its peer dependencies, then generate your `tailwind.config.js` and `postcss.config.js` files:
Install `tailwindcss` and its peer dependencies, then generate your `tailwind.config.js` and configure `postcss` plugins
```bash
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
```
<TabsMarkdown>
<TabMarkdown title="vite.config">
Vite already has [`postcss`](https://github.com/vitejs/vite/blob/main/packages/vite/package.json#L78) dependency so you don't have to install it again in your package.json
```bash
npm install -D tailwindcss autoprefixer
```
#### `vite.config`
```typescript {5,6,9-13}
import path from "path"
import { defineConfig } from "vite"
import vue from "@vitejs/plugin-vue"
import tailwind from "tailwindcss"
import autoprefixer from "autoprefixer"
export default defineConfig({
css: {
postcss: {
plugins: [tailwind(), autoprefixer()],
},
},
plugins: [vue()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
})
```
</TabMarkdown>
<TabMarkdown title="postcss.config.js">
```bash
npm install -D tailwindcss autoprefixer postcss
```
#### `postcss.config.js`
```js
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
```
</TabMarkdown>
</TabsMarkdown>
### Edit tsconfig.json
Add the code below to the compilerOptions of your tsconfig.json so your app can resolve paths without error
```typescript
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
```json {4-7}
{
"compilerOptions": {
// ...
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
// ...
}
}
```
@ -42,12 +102,22 @@ Add the code below to the compilerOptions of your tsconfig.json so your app can
Add the code below to the vite.config.ts so your app can resolve paths without error
```typescript
```bash
# (so you can import "path" without error)
npm i -D @types/node
```
```typescript {12-16}
import path from "path"
import vue from "@vitejs/plugin-vue"
import { defineConfig } from "vite"
export default defineConfig({
css: {
postcss: {
plugins: [tailwind(), autoprefixer()],
},
},
plugins: [vue()],
resolve: {
alias: {
@ -69,7 +139,7 @@ npx shadcn-vue@latest init
You will be asked a few questions to configure `components.json`:
```txt showLineNumbers
```txt:line-numbers
Would you like to use TypeScript (recommended)? no / yes
Which framework are you using? Vite / Nuxt / Laravel
Which style would you like to use? Default

View File

@ -26,7 +26,7 @@ import { Separator } from '@/lib/registry/new-york/ui/separator'
<template>
<Card>
<CardHeader class="grid grid-cols-[1fr_110px] items-start gap-4 space-y-0">
<CardHeader class="grid grid-cols-[minmax(0,1fr)_110px] items-start gap-4 space-y-0">
<div class="space-y-1">
<CardTitle>shadcn/ui</CardTitle>
<CardDescription>
@ -39,7 +39,7 @@ import { Separator } from '@/lib/registry/new-york/ui/separator'
<StarIcon class="mr-2 h-4 w-4" />
Star
</Button>
<Separator orientation="vertical" class="h-[20px]" />
<Separator orientation="vertical" class="h-5" />
<DropdownMenu>
<DropdownMenuTrigger as-child>
<Button variant="secondary" class="px-2 shadow-none">

View File

@ -40,7 +40,7 @@ const date = ref({
</span>
</Button>
</PopoverTrigger>
<PopoverContent class="w-auto p-0" :align="'end'" :avoid-collisions="true">
<PopoverContent class="w-auto p-0" :align="'end'">
<Calendar
v-model.range="date"
:columns="2"

View File

@ -95,7 +95,7 @@ async function onSubmit(values: any) {
</FormField>
<FormField v-slot="{ componentField, value }" name="dob">
<FormItem>
<FormItem class="flex flex-col">
<FormLabel>Date of birth</FormLabel>
<Popover>
<PopoverTrigger as-child>
@ -123,7 +123,7 @@ async function onSubmit(values: any) {
</FormField>
<FormField v-slot="{ value }" name="language">
<FormItem>
<FormItem class="flex flex-col">
<FormLabel>Language</FormLabel>
<Popover v-model:open="open">

View File

@ -101,7 +101,7 @@ const onSubmit = handleSubmit((values) => {
<div class="items-center rounded-md border-2 border-muted p-1 hover:border-accent">
<div class="space-y-2 rounded-sm bg-[#ecedef] p-2">
<div class="space-y-2 rounded-md bg-white p-2 shadow-sm">
<div class="h-2 w-[80px] rounded-lg bg-[#ecedef]" />
<div class="h-2 w-20 rounded-lg bg-[#ecedef]" />
<div class="h-2 w-[100px] rounded-lg bg-[#ecedef]" />
</div>
<div class="flex items-center space-x-2 rounded-md bg-white p-2 shadow-sm">
@ -127,7 +127,7 @@ const onSubmit = handleSubmit((values) => {
<div class="items-center rounded-md border-2 border-muted bg-popover p-1 hover:bg-accent hover:text-accent-foreground">
<div class="space-y-2 rounded-sm bg-slate-950 p-2">
<div class="space-y-2 rounded-md bg-slate-800 p-2 shadow-sm">
<div class="h-2 w-[80px] rounded-lg bg-slate-400" />
<div class="h-2 w-20 rounded-lg bg-slate-400" />
<div class="h-2 w-[100px] rounded-lg bg-slate-400" />
</div>
<div class="flex items-center space-x-2 rounded-md bg-slate-800 p-2 shadow-sm">

View File

@ -57,7 +57,7 @@ import CounterClockwiseClockIcon from '~icons/radix-icons/counter-clockwise-cloc
<Separator />
<Tabs default-value="complete" class="flex-1">
<div class="container h-full py-6">
<div class="grid h-full items-stretch gap-6 md:grid-cols-[1fr_200px]">
<div class="grid h-full items-stretch gap-6 md:grid-cols-[minmax(0,1fr)_200px]">
<div class="hidden flex-col space-y-4 sm:flex md:order-2">
<div class="grid gap-2">
<HoverCard :open-delay="200">

View File

@ -12,16 +12,16 @@ export const columns: ColumnDef<Task>[] = [
{
id: 'select',
header: ({ table }) => h(Checkbox,
{ 'checked': table.getIsAllPageRowsSelected(), 'onUpdate:checked': value => table.toggleAllPageRowsSelected(!!value), 'ariaLabel': 'Select all', 'class': 'translate-y-[2px]' }),
{ 'checked': table.getIsAllPageRowsSelected(), 'onUpdate:checked': value => table.toggleAllPageRowsSelected(!!value), 'ariaLabel': 'Select all', 'class': 'translate-y-0.5' }),
cell: ({ row }) => h(Checkbox,
{ 'checked': row.getIsSelected(), 'onUpdate:checked': value => row.toggleSelected(!!value), 'ariaLabel': 'Select row', 'class': 'translate-y-[2px]' }),
{ 'checked': row.getIsSelected(), 'onUpdate:checked': value => row.toggleSelected(!!value), 'ariaLabel': 'Select row', 'class': 'translate-y-0.5' }),
enableSorting: false,
enableHiding: false,
},
{
accessorKey: 'id',
header: ({ column }) => h(DataTableColumnHeader, { column, title: 'Task' }),
cell: ({ row }) => h('div', { class: 'w-[80px]' }, row.getValue('id')),
cell: ({ row }) => h('div', { class: 'w-20' }, row.getValue('id')),
enableSorting: false,
enableHiding: false,
},

View File

@ -51,7 +51,7 @@ const notifications = [
<div>
<div
v-for="(notification, index) in notifications" :key="index"
class="mb-4 grid grid-cols-[25px_1fr] items-start pb-4 last:mb-0 last:pb-0"
class="mb-4 grid grid-cols-[25px_minmax(0,1fr)] items-start pb-4 last:mb-0 last:pb-0"
>
<span class="flex h-2 w-2 translate-y-1 rounded-full bg-sky-500" />
<div class="space-y-1">

View File

@ -1,5 +1,12 @@
<script setup lang='ts'>
import { Card, CardContent, CardHeader, CardTitle } from '@/lib/registry/default/ui/card'
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from '@/lib/registry/default/ui/card'
import {
Select,
SelectContent,

View File

@ -43,7 +43,7 @@ const lineY = (d: Data) => d.revenue
+20.1% from last month
</p>
<div class="h-[80px]">
<div class="h-20">
<VisXYContainer
height="80px"
:data="data" :margin="{
@ -79,7 +79,7 @@ const lineY = (d: Data) => d.revenue
+54.8% from last month
</p>
<div class="mt-4 h-[80px]">
<div class="mt-4 h-20">
<VisXYContainer
height="80px" :data="data" :style="{
'--theme-primary': `hsl(${

View File

@ -14,7 +14,7 @@ import {
useVueTable,
} from '@tanstack/vue-table'
import { h, ref } from 'vue'
import { CaretSortIcon, ChevronDownIcon } from '@radix-icons/vue'
import { ChevronDown, ChevronsUpDown } from 'lucide-vue-next'
import DropdownAction from '../DataTableDemoColumn.vue'
import { Button } from '@/lib/registry/default/ui/button'
@ -104,7 +104,7 @@ const columns: ColumnDef<Payment>[] = [
return h(Button, {
variant: 'ghost',
onClick: () => column.toggleSorting(column.getIsSorted() === 'asc'),
}, ['Email', h(CaretSortIcon, { class: 'ml-2 h-4 w-4' })])
}, ['Email', h(ChevronsUpDown, { class: 'ml-2 h-4 w-4' })])
},
cell: ({ row }) => h('div', { class: 'lowercase' }, row.getValue('email')),
},
@ -179,7 +179,7 @@ const table = useVueTable({
<DropdownMenu>
<DropdownMenuTrigger as-child>
<Button variant="outline" class="ml-auto">
Columns <ChevronDownIcon class="ml-2 h-4 w-4" />
Columns <ChevronDown class="ml-2 h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
import { ref } from 'vue'
import { CaretSortIcon, CheckIcon } from '@radix-icons/vue'
import { Check, ChevronsUpDown } from 'lucide-vue-next'
import { cn } from '@/lib/utils'
import { Button } from '@/lib/registry/default/ui/button'
@ -44,7 +44,7 @@ const value = ref<string>('')
{{ value
? frameworks.find((framework) => framework.value === value)?.label
: "Select framework..." }}
<CaretSortIcon class="ml-2 h-4 w-4 shrink-0 opacity-50" />
<ChevronsUpDown class="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent class="w-[200px] p-0">
@ -65,7 +65,7 @@ const value = ref<string>('')
}"
>
{{ framework.label }}
<CheckIcon
<Check
:class="cn(
'ml-auto h-4 w-4',
value === framework.value ? 'opacity-100' : 'opacity-0',

View File

@ -0,0 +1,273 @@
<script setup lang="ts">
import type {
ColumnDef,
ColumnFiltersState,
SortingState,
VisibilityState,
} from '@tanstack/vue-table'
import {
FlexRender,
createColumnHelper,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useVueTable,
} from '@tanstack/vue-table'
import { ArrowUpDown, ChevronDown } from 'lucide-vue-next'
import { h, ref } from 'vue'
import DropdownAction from './DataTableDemoColumn.vue'
import { Button } from '@/lib/registry/default/ui/button'
import { Checkbox } from '@/lib/registry/default/ui/checkbox'
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuTrigger,
} from '@/lib/registry/default/ui/dropdown-menu'
import { Input } from '@/lib/registry/default/ui/input'
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/lib/registry/default/ui/table'
import { cn, valueUpdater } from '@/lib/utils'
export interface Payment {
id: string
amount: number
status: 'pending' | 'processing' | 'success' | 'failed'
email: string
}
const data: Payment[] = [
{
id: 'm5gr84i9',
amount: 316,
status: 'success',
email: 'ken99@yahoo.com',
},
{
id: '3u1reuv4',
amount: 242,
status: 'success',
email: 'Abe45@gmail.com',
},
{
id: 'derv1ws0',
amount: 837,
status: 'processing',
email: 'Monserrat44@gmail.com',
},
{
id: '5kma53ae',
amount: 874,
status: 'success',
email: 'Silas22@gmail.com',
},
{
id: 'bhqecj4p',
amount: 721,
status: 'failed',
email: 'carmella@hotmail.com',
},
]
const columnHelper = createColumnHelper<Payment>()
const columns = [
columnHelper.display({
id: 'select',
header: ({ table }) => h(Checkbox, {
'checked': table.getIsAllPageRowsSelected(),
'onUpdate:checked': value => table.toggleAllPageRowsSelected(!!value),
'ariaLabel': 'Select all',
}),
cell: ({ row, column }) => {
return h(Checkbox, {
'checked': row.getIsSelected(),
'onUpdate:checked': value => row.toggleSelected(!!value),
'ariaLabel': 'Select row',
})
},
enableSorting: false,
enableHiding: false,
}),
columnHelper.accessor('status', {
enablePinning: true,
header: 'Status',
cell: ({ row }) => h('div', { class: 'capitalize' }, row.getValue('status')),
}),
columnHelper.accessor('email', {
header: ({ column }) => {
return h(Button, {
variant: 'ghost',
onClick: () => column.toggleSorting(column.getIsSorted() === 'asc'),
}, () => ['Email', h(ArrowUpDown, { class: 'ml-2 h-4 w-4' })])
},
cell: ({ row }) => h('div', { class: 'lowercase' }, row.getValue('email')),
}),
columnHelper.accessor('amount', {
header: () => h('div', { class: 'text-right' }, 'Amount'),
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
// Format the amount as a dollar amount
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
}).format(amount)
return h('div', { class: 'text-right font-medium' }, formatted)
},
}),
columnHelper.display({
id: 'actions',
enableHiding: false,
cell: ({ row }) => {
const payment = row.original
return h('div', { class: 'relative' }, h(DropdownAction, {
payment,
}))
},
}),
]
const sorting = ref<SortingState>([])
const columnFilters = ref<ColumnFiltersState>([])
const columnVisibility = ref<VisibilityState>({})
const rowSelection = ref({})
const table = useVueTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting),
onColumnFiltersChange: updaterOrValue => valueUpdater(updaterOrValue, columnFilters),
onColumnVisibilityChange: updaterOrValue => valueUpdater(updaterOrValue, columnVisibility),
onRowSelectionChange: updaterOrValue => valueUpdater(updaterOrValue, rowSelection),
state: {
get sorting() { return sorting.value },
get columnFilters() { return columnFilters.value },
get columnVisibility() { return columnVisibility.value },
get rowSelection() { return rowSelection.value },
columnPinning: {
left: ['status'],
},
},
})
const getState = table.getState()
</script>
<template>
<div class="w-full">
<div class="flex gap-2 items-center py-4">
<Input
class="max-w-sm"
placeholder="Filter emails..."
:model-value="table.getColumn('email')?.getFilterValue() as string"
@update:model-value=" table.getColumn('email')?.setFilterValue($event)"
/>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<Button variant="outline" class="ml-auto">
Columns <ChevronDown class="ml-2 h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuCheckboxItem
v-for="column in table.getAllColumns().filter((column) => column.getCanHide())"
:key="column.id"
class="capitalize"
:checked="column.getIsVisible()"
@update:checked="(value) => {
column.toggleVisibility(!!value)
}"
>
{{ column.id }}
</DropdownMenuCheckboxItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
<div class="rounded-md border">
<Table>
<TableHeader>
<TableRow v-for="headerGroup in table.getHeaderGroups()" :key="headerGroup.id">
<TableHead
v-for="header in headerGroup.headers" :key="header.id" :data-pinned="header.column.getIsPinned()"
:class="cn(
{ 'sticky bg-background/95': header.column.getIsPinned() },
header.column.getIsPinned() === 'left' ? 'left-0' : 'right-0',
)"
>
<FlexRender v-if="!header.isPlaceholder" :render="header.column.columnDef.header" :props="header.getContext()" />
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<template v-if="table.getRowModel().rows?.length">
<TableRow
v-for="row in table.getRowModel().rows"
:key="row.id"
:data-state="row.getIsSelected() && 'selected'"
>
<TableCell
v-for="cell in row.getVisibleCells()" :key="cell.id" :data-pinned="cell.column.getIsPinned()"
:class="cn(
{ 'sticky bg-background/95': cell.column.getIsPinned() },
cell.column.getIsPinned() === 'left' ? 'left-0' : 'right-0',
)"
>
<FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
</TableCell>
</TableRow>
</template>
<TableRow v-else>
<TableCell
col-span="{columns.length}"
class="h-24 text-center"
>
No results.
</TableCell>
</TableRow>
</TableBody>
</Table>
</div>
<div class="flex items-center justify-end space-x-2 py-4">
<div class="flex-1 text-sm text-muted-foreground">
{{ table.getFilteredSelectedRowModel().rows.length }} of
{{ table.getFilteredRowModel().rows.length }} row(s) selected.
</div>
<div class="space-x-2">
<Button
variant="outline"
size="sm"
:disabled="!table.getCanPreviousPage()"
@click="table.previousPage()"
>
Previous
</Button>
<Button
variant="outline"
size="sm"
:disabled="!table.getCanNextPage()"
@click="table.nextPage()"
>
Next
</Button>
</div>
</div>
</div>
</template>

View File

@ -40,7 +40,7 @@ const date = ref({
</span>
</Button>
</PopoverTrigger>
<PopoverContent class="w-auto p-0" align="start" :avoid-collisions="true">
<PopoverContent class="w-auto p-0" align="start">
<Calendar
v-model.range="date"
:columns="2"

View File

@ -55,7 +55,7 @@ const components: { title: string; href: string; description: string }[] = [
<NavigationMenuItem>
<NavigationMenuTrigger>Getting started</NavigationMenuTrigger>
<NavigationMenuContent>
<ul class="grid gap-3 p-6 md:w-[400px] lg:w-[500px] lg:grid-cols-[.75fr_1fr]">
<ul class="grid gap-3 p-6 md:w-[400px] lg:w-[500px] lg:grid-cols-[minmax(0,.75fr)_minmax(0,1fr)]">
<li class="row-span-3">
<NavigationMenuLink as-child>
<a

View File

@ -0,0 +1,28 @@
<script setup lang="ts">
import { ref } from 'vue'
import {
PinInput,
PinInputInput,
} from '@/lib/registry/default/ui/pin-input'
const value = ref<string[]>([])
const handleComplete = (e: string[]) => alert(e.join(''))
</script>
<template>
<div>
<PinInput
id="pin-input"
v-model="value"
placeholder="○"
class="flex gap-2 items-center mt-1"
@complete="handleComplete"
>
<PinInputInput
v-for="(id, index) in 5"
:key="id"
:index="index"
/>
</PinInput>
</div>
</template>

View File

@ -0,0 +1,78 @@
<script setup lang="ts">
import { h } from 'vue'
import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/zod'
import * as z from 'zod'
import {
PinInput,
PinInputInput,
} from '@/lib/registry/new-york/ui/pin-input'
import { Button } from '@/lib/registry/default/ui/button'
import {
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/lib/registry/default/ui/form'
import { toast } from '@/lib/registry/default/ui/toast'
const formSchema = toTypedSchema(z.object({
pin: z.array(z.coerce.string()).length(5, { message: 'Invalid input' }),
}))
const { handleSubmit, setValues } = useForm({
validationSchema: formSchema,
initialValues: {
pin: [],
},
})
const onSubmit = handleSubmit(({ pin }) => {
toast({
title: 'You submitted the following values:',
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(pin.join(''), null, 2))),
})
})
const handleComplete = (e: string[]) => console.log(e.join(''))
</script>
<template>
<form class="w-2/3 space-y-6 mx-auto" @submit="onSubmit">
<FormField v-slot="{ componentField }" name="pin">
<FormItem>
<FormLabel>OTP</FormLabel>
<FormControl>
<PinInput
id="pin-input"
placeholder="○"
class="flex gap-2 items-center mt-1"
otp
type="number"
:name="componentField.name"
@complete="handleComplete"
@update:model-value="(arrStr) => {
setValues({
pin: arrStr.filter(Boolean),
})
}"
>
<PinInputInput
v-for="(id, index) in 5"
:key="id"
:index="index"
/>
</PinInput>
</FormControl>
<FormDescription>
Allows users to input a sequence of one-character alphanumeric inputs.
</FormDescription>
<FormMessage />
</FormItem>
</FormField>
<Button>Submit</Button>
</form>
</template>

View File

@ -11,5 +11,5 @@ watchEffect((cleanupFn) => {
</script>
<template>
<Progress v-model="progress" class="w-[60%]" />
<Progress v-model="progress" class="w-3/5" />
</template>

View File

@ -0,0 +1,69 @@
<script setup lang="ts">
import { addDays, format } from 'date-fns'
import { Calendar as CalendarIcon } from 'lucide-vue-next'
import { ref } from 'vue'
import { cn } from '@/lib/utils'
import { Button } from '@/lib/registry/default/ui/button'
import { Calendar } from '@/lib/registry/default/ui/calendar'
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/lib/registry/default/ui/popover'
const date = ref({
start: new Date(2022, 0, 20),
end: addDays(new Date(2022, 0, 20), 20),
})
</script>
<template>
<div :class="cn('grid gap-2', $attrs.class ?? '')">
<Popover>
<PopoverTrigger as-child>
<Button
id="date"
:variant="'outline'"
:class="cn(
'w-[300px] justify-start text-left font-normal',
!date && 'text-muted-foreground',
)"
>
<CalendarIcon class="mr-2 h-4 w-4" />
<span>
{{ date.start ? (
date.end ? `${format(date.start, 'LLL dd, y')} - ${format(date.end, 'LLL dd, y')}`
: format(date.start, 'LLL dd, y')
) : 'Pick a date' }}
</span>
</Button>
</PopoverTrigger>
<PopoverContent class="w-auto p-0" align="start">
<Calendar
v-model.range="date"
mode="date"
:columns="2"
>
<template #footer>
<div class="w-full px-3 pb-3">
Entry time
<Calendar
v-model="date.start"
mode="time"
hide-time-header
/>
Exit time
<Calendar
v-model="date.end"
mode="time"
hide-time-header
/>
</div>
</template>
</Calendar>
</PopoverContent>
</Popover>
</div>
</template>

View File

@ -11,6 +11,6 @@ const modelValue = ref([50])
v-model="modelValue"
:max="100"
:step="1"
:class="cn('w-[60%]', $attrs.class ?? '')"
:class="cn('w-3/5', $attrs.class ?? '')"
/>
</template>

View File

@ -0,0 +1,20 @@
<script setup lang="ts">
import { toast } from 'vue-sonner'
import { Button } from '@/lib/registry/default/ui/button'
</script>
<template>
<Button
variant="outline" @click="() => {
toast('Event has been created', {
description: 'Sunday, December 03, 2023 at 9:00 AM',
action: {
label: 'Undo',
onClick: () => console.log('Undo'),
},
})
}"
>
Add to calendar
</Button>
</template>

View File

@ -14,6 +14,6 @@ const { toast } = useToast()
});
}"
>
Add to calander
Add to calendar
</Button>
</template>

View File

@ -3,15 +3,17 @@ import {
AccordionRoot,
type AccordionRootEmits,
type AccordionRootProps,
useEmitAsProps,
useForwardPropsEmits,
} from 'radix-vue'
const props = defineProps<AccordionRootProps>()
const emits = defineEmits<AccordionRootEmits>()
const forwarded = useForwardPropsEmits(props, emits)
</script>
<template>
<AccordionRoot v-bind="{ ...props, ...useEmitAsProps(emits) }">
<AccordionRoot v-bind="forwarded">
<slot />
</AccordionRoot>
</template>

View File

@ -1,21 +1,23 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import { AccordionContent, type AccordionContentProps } from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<AccordionContentProps & { class?: string }>()
const props = defineProps<AccordionContentProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
</script>
<template>
<AccordionContent
v-bind="props"
:class="
cn(
'overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down',
props.class,
)
"
v-bind="delegatedProps"
class="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
>
<div class="pb-4 pt-0">
<div :class="cn('pb-4 pt-0', props.class)">
<slot />
</div>
</AccordionContent>

View File

@ -1,14 +1,23 @@
<script setup lang="ts">
import { AccordionItem, type AccordionItemProps } from 'radix-vue'
import { type HTMLAttributes, computed } from 'vue'
import { AccordionItem, type AccordionItemProps, useForwardProps } from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<AccordionItemProps & { class?: string }>()
const props = defineProps<AccordionItemProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<AccordionItem
v-bind="props"
:class="cn('border-b', props.class ?? '')"
v-bind="forwardedProps"
:class="cn('border-b', props.class)"
>
<slot />
</AccordionItem>

View File

@ -1,4 +1,5 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import {
AccordionHeader,
AccordionTrigger,
@ -7,13 +8,19 @@ import {
import { ChevronDown } from 'lucide-vue-next'
import { cn } from '@/lib/utils'
const props = defineProps<AccordionTriggerProps & { class?: string }>()
const props = defineProps<AccordionTriggerProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
</script>
<template>
<AccordionHeader class="flex" as="div">
<AccordionHeader class="flex">
<AccordionTrigger
v-bind="props"
v-bind="delegatedProps"
:class="
cn(
'flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180',
@ -22,9 +29,11 @@ const props = defineProps<AccordionTriggerProps & { class?: string }>()
"
>
<slot />
<ChevronDown
class="h-4 w-4 shrink-0 transition-transform duration-200"
/>
<slot name="icon">
<ChevronDown
class="h-4 w-4 shrink-0 transition-transform duration-200"
/>
</slot>
</AccordionTrigger>
</AccordionHeader>
</template>

View File

@ -1,13 +1,20 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import { AlertDialogAction, type AlertDialogActionProps } from 'radix-vue'
import { cn } from '@/lib/utils'
import { buttonVariants } from '@/lib/registry/default/ui/button'
const props = defineProps<AlertDialogActionProps>()
const props = defineProps<AlertDialogActionProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
</script>
<template>
<AlertDialogAction v-bind="props" :class="cn(buttonVariants(), $attrs.class ?? '')">
<AlertDialogAction v-bind="delegatedProps" :class="cn(buttonVariants(), props.class)">
<slot />
</AlertDialogAction>
</template>

View File

@ -1,13 +1,20 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import { AlertDialogCancel, type AlertDialogCancelProps } from 'radix-vue'
import { cn } from '@/lib/utils'
import { buttonVariants } from '@/lib/registry/default/ui/button'
const props = defineProps<AlertDialogCancelProps>()
const props = defineProps<AlertDialogCancelProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
</script>
<template>
<AlertDialogCancel v-bind="props" :class="cn(buttonVariants({ variant: 'outline' }), 'mt-2 sm:mt-0', $attrs.class ?? '')">
<AlertDialogCancel v-bind="delegatedProps" :class="cn(buttonVariants({ variant: 'outline' }), 'mt-2 sm:mt-0', props.class)">
<slot />
</AlertDialogCancel>
</template>

View File

@ -1,30 +1,37 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import {
AlertDialogContent,
type AlertDialogContentEmits,
type AlertDialogContentProps,
AlertDialogOverlay,
AlertDialogPortal,
useEmitAsProps,
useForwardPropsEmits,
} from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<AlertDialogContentProps & { class?: string }>()
const props = defineProps<AlertDialogContentProps & { class?: HTMLAttributes['class'] }>()
const emits = defineEmits<AlertDialogContentEmits>()
const emitsAsProps = useEmitAsProps(emits)
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
<AlertDialogPortal>
<AlertDialogOverlay
class="fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0"
class="fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0"
/>
<AlertDialogContent
v-bind="{ ...props, ...emitsAsProps }"
v-bind="forwarded"
:class="
cn(
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full',
'fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
props.class,
)
"

View File

@ -1,17 +1,24 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import {
AlertDialogDescription,
type AlertDialogDescriptionProps,
} from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<AlertDialogDescriptionProps & { class?: string }>()
const props = defineProps<AlertDialogDescriptionProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
</script>
<template>
<AlertDialogDescription
:class="cn('text-muted-foreground text-sm', props.class)"
:as-child="props.asChild"
v-bind="delegatedProps"
:class="cn('text-sm text-muted-foreground', props.class)"
>
<slot />
</AlertDialogDescription>

View File

@ -1,19 +1,17 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps({
class: {
type: String,
default: '',
},
})
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<div
:class="
cn(
'flex flex-col space-y-2 sm:space-y-0 mt-3.5 sm:flex-row sm:justify-end sm:space-x-2',
'flex flex-col-reverse sm:flex-row sm:justify-end sm:gap-x-2',
props.class,
)
"

View File

@ -1,17 +1,15 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps({
class: {
type: String,
default: '',
},
})
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<div
:class="cn('flex flex-col space-y-2 text-center sm:text-left', props.class)"
:class="cn('flex flex-col gap-y-2 text-center sm:text-left', props.class)"
>
<slot />
</div>

View File

@ -1,14 +1,21 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import { AlertDialogTitle, type AlertDialogTitleProps } from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<AlertDialogTitleProps & { class?: string }>()
const props = defineProps<AlertDialogTitleProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
</script>
<template>
<AlertDialogTitle
:as-child="props.asChild"
:class="cn('text-lg text-foreground font-semibold', props.class)"
v-bind="delegatedProps"
:class="cn('text-lg font-semibold', props.class)"
>
<slot />
</AlertDialogTitle>

View File

@ -1,17 +1,16 @@
<script setup lang="ts">
import { alertVariants } from '.'
import type { HTMLAttributes } from 'vue'
import { type AlertVariants, alertVariants } from '.'
import { cn } from '@/lib/utils'
interface Props {
variant?: NonNullable<Parameters<typeof alertVariants>[0]>['variant']
class?: string
}
const props = defineProps<Props>()
const props = defineProps<{
class?: HTMLAttributes['class']
variant?: AlertVariants['variant']
}>()
</script>
<template>
<div :class="cn(alertVariants({ variant }), props.class ?? '')">
<div :class="cn(alertVariants({ variant }), props.class)" role="alert">
<slot />
</div>
</template>

View File

@ -1,9 +1,10 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps({
class: String,
})
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>

View File

@ -1,9 +1,14 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<h5 :class="cn('mb-1 font-medium leading-none tracking-tight', $attrs.class ?? '')">
<h5 :class="cn('mb-1 font-medium leading-none tracking-tight', props.class)">
<slot />
</h5>
</template>

View File

@ -1,4 +1,4 @@
import { cva } from 'class-variance-authority'
import { type VariantProps, cva } from 'class-variance-authority'
export { default as Alert } from './Alert.vue'
export { default as AlertTitle } from './AlertTitle.vue'
@ -19,3 +19,5 @@ export const alertVariants = cva(
},
},
)
export type AlertVariants = VariantProps<typeof alertVariants>

View File

@ -1,15 +1,14 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { AvatarRoot } from 'radix-vue'
import { avatarVariant } from '.'
import { type AvatarVariants, avatarVariant } from '.'
import { cn } from '@/lib/utils'
interface Props {
size?: NonNullable<Parameters<typeof avatarVariant>[0]>['size']
shape?: NonNullable<Parameters<typeof avatarVariant>[0]>['shape']
class?: string
}
const props = withDefaults(defineProps<Props>(), {
const props = withDefaults(defineProps<{
class?: HTMLAttributes['class']
size?: AvatarVariants['size']
shape?: AvatarVariants['shape']
}>(), {
size: 'sm',
shape: 'circle',
})

View File

@ -1,4 +1,4 @@
import { cva } from 'class-variance-authority'
import { type VariantProps, cva } from 'class-variance-authority'
export { default as Avatar } from './Avatar.vue'
export { default as AvatarImage } from './AvatarImage.vue'
@ -20,3 +20,5 @@ export const avatarVariant = cva(
},
},
)
export type AvatarVariants = VariantProps<typeof avatarVariant>

View File

@ -1,18 +1,16 @@
<script setup lang="ts">
import type { VariantProps } from 'class-variance-authority'
import { badgeVariants } from '.'
import type { HTMLAttributes } from 'vue'
import { type BadgeVariants, badgeVariants } from '.'
import { cn } from '@/lib/utils'
interface BadgeVariantProps extends VariantProps<typeof badgeVariants> {}
interface Props {
variant?: BadgeVariantProps['variant']
}
defineProps<Props>()
const props = defineProps<{
variant?: BadgeVariants['variant']
class?: HTMLAttributes['class']
}>()
</script>
<template>
<div :class="cn(badgeVariants({ variant }), $attrs.class ?? '')">
<div :class="cn(badgeVariants({ variant }), props.class)">
<slot />
</div>
</template>

View File

@ -1,4 +1,4 @@
import { cva } from 'class-variance-authority'
import { type VariantProps, cva } from 'class-variance-authority'
export { default as Badge } from './Badge.vue'
@ -21,3 +21,5 @@ export const badgeVariants = cva(
},
},
)
export type BadgeVariants = VariantProps<typeof badgeVariants>

View File

@ -1,20 +1,17 @@
<script setup lang="ts">
import type { VariantProps } from 'class-variance-authority'
import type { HTMLAttributes } from 'vue'
import { Primitive, type PrimitiveProps } from 'radix-vue'
import { buttonVariants } from '.'
import { type ButtonVariants, buttonVariants } from '.'
import { cn } from '@/lib/utils'
interface ButtonVariantProps extends VariantProps<typeof buttonVariants> {}
interface Props extends PrimitiveProps {
variant?: ButtonVariantProps['variant']
size?: ButtonVariantProps['size']
variant?: ButtonVariants['variant']
size?: ButtonVariants['size']
as?: string
class?: HTMLAttributes['class']
}
withDefaults(defineProps<Props>(), {
variant: 'default',
size: 'default',
const props = withDefaults(defineProps<Props>(), {
as: 'button',
})
</script>
@ -23,7 +20,7 @@ withDefaults(defineProps<Props>(), {
<Primitive
:as="as"
:as-child="asChild"
:class="cn(buttonVariants({ variant, size }), $attrs.class ?? '')"
:class="cn(buttonVariants({ variant, size }), props.class)"
>
<slot />
</Primitive>

View File

@ -1,9 +1,9 @@
import { cva } from 'class-variance-authority'
import { type VariantProps, cva } from 'class-variance-authority'
export { default as Button } from './Button.vue'
export const buttonVariants = cva(
'inline-flex items-center justify-center rounded-md whitespace-nowrap 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 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',
{
variants: {
variant: {
@ -30,3 +30,5 @@ export const buttonVariants = cva(
},
},
)
export type ButtonVariants = VariantProps<typeof buttonVariants>

View File

@ -1,11 +1,12 @@
<script setup lang="ts">
import { useVModel } from '@vueuse/core'
import { ChevronLeft, ChevronRight } from 'lucide-vue-next'
import type { Calendar } from 'v-calendar'
import { DatePicker } from 'v-calendar'
import { ChevronLeft, ChevronRight } from 'lucide-vue-next'
import { computed, nextTick, onMounted, ref } from 'vue'
import { buttonVariants } from '@/lib/registry/default/ui/button'
import { computed, nextTick, onMounted, ref, useSlots } from 'vue'
import { isVCalendarSlot } from '.'
import { cn } from '@/lib/utils'
import { buttonVariants } from '@/lib/registry/default/ui/button'
/* Extracted from v-calendar */
type DatePickerModel = DatePickerDate | DatePickerRangeObject
@ -28,8 +29,7 @@ interface SimpleDateParts {
defineOptions({
inheritAttrs: false,
})
const props = withDefaults(defineProps<{
const props = withDefaults(defineProps< {
modelValue?: string | number | Date | DatePickerModel
modelModifiers?: object
columns?: number
@ -64,6 +64,16 @@ onMounted(async () => {
if (modelValue.value instanceof Date && calendarRef.value)
calendarRef.value.focusDate(modelValue.value)
})
const $slots = useSlots()
const vCalendarSlots = computed(() => {
return Object.keys($slots)
.filter(name => isVCalendarSlot(name))
.reduce((obj: Record<string, any>, key: string) => {
obj[key] = $slots[key]
return obj
}, {})
})
</script>
<template>
@ -79,18 +89,30 @@ onMounted(async () => {
<DatePicker
ref="datePicker"
v-model="modelValue"
v-bind="$attrs"
v-model="modelValue"
:model-modifiers="modelModifiers"
class="calendar"
trim-weeks
:transition="'none'"
:columns="columns"
/>
>
<template v-for="(_, slot) of vCalendarSlots" #[slot]="scope">
<slot :name="slot" v-bind="scope" />
</template>
<template #nav-prev-button>
<ChevronLeft />
</template>
<template #nav-next-button>
<ChevronRight />
</template>
</DatePicker>
</div>
</template>
<style lang="postcss">
<style lang="css">
.calendar {
@apply p-3 text-center;
}
@ -98,7 +120,25 @@ onMounted(async () => {
@apply grid gap-4;
}
.calendar .vc-title {
@apply text-sm font-medium pointer-events-none;
@apply text-sm font-medium relative z-20;
}
.vc-popover-content-wrapper .vc-popover-content {
@apply mt-3 rounded-md max-w-xs border bg-background;
}
.vc-popover-content-wrapper .vc-nav-header {
@apply flex justify-between items-center p-2;
}
.vc-popover-content-wrapper .vc-nav-items {
@apply grid grid-cols-4 gap-2 p-2;
}
.vc-popover-content-wrapper .vc-nav-items .vc-nav-item {
@apply rounded-md px-2 py-1;
}
.vc-popover-content-wrapper .vc-nav-items .vc-nav-item:hover {
@apply text-muted-foreground bg-muted;
}
.vc-popover-content-wrapper .vc-nav-items .vc-nav-item.is-active {
@apply bg-primary text-primary-foreground;
}
.calendar .vc-pane-header-wrapper {
@apply hidden;
@ -110,16 +150,22 @@ onMounted(async () => {
@apply flex;
}
.calendar .vc-weekday {
@apply text-muted-foreground rounded-md w-9 font-normal text-[0.8rem];
@apply text-muted-foreground rounded-md w-full font-normal text-[0.8rem];
}
.calendar .vc-weekday-1 {
@apply pr-3;
}
.calendar .vc-weekday-7 {
@apply pl-3;
}
.calendar .vc-weeks {
@apply w-full space-y-2 flex flex-col [&>_div]:grid [&>_div]:grid-cols-7;
}
.calendar .vc-day:has(.vc-highlights) {
@apply bg-accent first:rounded-l-md last:rounded-r-md overflow-hidden;
@apply first:rounded-l-md last:rounded-r-md;
}
.calendar .vc-day.is-today:not(:has(.vc-day-layer)) {
@apply bg-secondary rounded-md;
.calendar .vc-day.is-today:not(:has(.vc-day-layer)) .vc-day-content {
@apply bg-secondary text-primary rounded-md;
}
.calendar .vc-day:has(.vc-highlight-base-start) {
@apply rounded-l-md;
@ -131,7 +177,7 @@ onMounted(async () => {
@apply rounded-md;
}
.calendar .vc-day-content {
@apply text-center text-sm p-0 relative focus-within:relative focus-within:z-20 inline-flex items-center justify-center ring-offset-background hover:transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 hover:bg-accent hover:text-accent-foreground h-9 w-9 font-normal aria-selected:opacity-100 select-none;
@apply text-center text-sm p-0 relative focus-within:relative focus-within:z-20 inline-flex items-center justify-center ring-offset-background hover:transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 hover:bg-accent hover:text-accent-foreground h-9 w-9 font-normal aria-selected:opacity-100 select-none;
}
.calendar .vc-day-content:not(.vc-highlight-content-light) {
@apply rounded-md;
@ -157,6 +203,7 @@ onMounted(async () => {
--vc-slide-duration: 0.15s;
--vc-slide-timing: ease;
}
.calendar .vc-fade-enter-active,
.calendar .vc-fade-leave-active,
.calendar .vc-slide-left-enter-active,
@ -183,6 +230,7 @@ onMounted(async () => {
backface-visibility: hidden;
pointer-events: none;
}
.calendar .vc-none-leave-active,
.calendar .vc-fade-leave-active,
.calendar .vc-slide-left-leave-active,
@ -192,6 +240,7 @@ onMounted(async () => {
position: absolute !important;
width: 100%;
}
.calendar .vc-none-enter-from,
.calendar .vc-none-leave-to,
.calendar .vc-fade-enter-from,
@ -208,6 +257,7 @@ onMounted(async () => {
.calendar .vc-slide-fade-leave-to {
opacity: 0;
}
.calendar .vc-slide-left-enter-from,
.calendar .vc-slide-right-leave-to,
.calendar .vc-slide-fade-enter-from.direction-left,
@ -215,6 +265,7 @@ onMounted(async () => {
-webkit-transform: translateX(var(--vc-slide-translate));
transform: translateX(var(--vc-slide-translate));
}
.calendar .vc-slide-right-enter-from,
.calendar .vc-slide-left-leave-to,
.calendar .vc-slide-fade-enter-from.direction-right,
@ -222,6 +273,7 @@ onMounted(async () => {
-webkit-transform: translateX(calc(-1 * var(--vc-slide-translate)));
transform: translateX(calc(-1 * var(--vc-slide-translate)));
}
.calendar .vc-slide-up-enter-from,
.calendar .vc-slide-down-leave-to,
.calendar .vc-slide-fade-enter-from.direction-top,
@ -229,6 +281,7 @@ onMounted(async () => {
-webkit-transform: translateY(var(--vc-slide-translate));
transform: translateY(var(--vc-slide-translate));
}
.calendar .vc-slide-down-enter-from,
.calendar .vc-slide-up-leave-to,
.calendar .vc-slide-fade-enter-from.direction-bottom,

View File

@ -1 +1,22 @@
export { default as Calendar } from './Calendar.vue'
import type { CalendarSlotName } from 'v-calendar/dist/types/src/components/Calendar/CalendarSlot.vue.d.ts'
export function isVCalendarSlot(slotName: string): slotName is CalendarSlotName {
const validSlots: CalendarSlotName[] = [
'day-content',
'day-popover',
'dp-footer',
'footer',
'header-title-wrapper',
'header-title',
'header-prev-button',
'header-next-button',
'nav',
'nav-prev-button',
'nav-next-button',
'page',
'time-header',
]
return validSlots.includes(slotName as CalendarSlotName)
}

View File

@ -1,12 +1,10 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps({
class: {
type: String,
default: '',
},
})
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>

View File

@ -1,12 +1,10 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps({
class: {
type: String,
default: '',
},
})
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>

View File

@ -1,12 +1,10 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps({
class: {
type: String,
default: '',
},
})
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>

View File

@ -1,16 +1,14 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps({
class: {
type: String,
default: '',
},
})
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<div :class="cn('p-6 pt-0', props.class)">
<div :class="cn('flex items-center p-6 pt-0', props.class)">
<slot />
</div>
</template>

View File

@ -1,16 +1,14 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps({
class: {
type: String,
default: '',
},
})
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<div :class="cn('flex flex-col space-y-1.5 p-6', props.class)">
<div :class="cn('flex flex-col gap-y-1.5 p-6', props.class)">
<slot />
</div>
</template>

View File

@ -1,18 +1,16 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps({
class: {
type: String,
default: '',
},
})
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<h3
:class="
cn('text-2xl font-semibold leading-none tracking-tighter', props.class)
cn('text-2xl font-semibold leading-none tracking-tight', props.class)
"
>
<slot />

View File

@ -1,5 +1,4 @@
<script setup lang="ts">
import emblaCarouselVue from 'embla-carousel-vue'
import { useProvideCarousel } from './useCarousel'
import type { CarouselEmits, CarouselProps, WithClassAsProps } from './interface'
import { cn } from '@/lib/utils'

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
import type { WithClassAsProps } from './interface'
import { useCarousel } from './useCarousel'
import type { WithClassAsProps } from './interface'
import { cn } from '@/lib/utils'
defineOptions({

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
import type { WithClassAsProps } from './interface'
import { useCarousel } from './useCarousel'
import type { WithClassAsProps } from './interface'
import { cn } from '@/lib/utils'
const props = defineProps<WithClassAsProps>()

View File

@ -1,13 +1,20 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import type { CheckboxRootEmits, CheckboxRootProps } from 'radix-vue'
import { CheckboxIndicator, CheckboxRoot, useForwardPropsEmits } from 'radix-vue'
import { Check } from 'lucide-vue-next'
import { cn } from '@/lib/utils'
const props = defineProps<CheckboxRootProps>()
const props = defineProps<CheckboxRootProps & { class?: HTMLAttributes['class'] }>()
const emits = defineEmits<CheckboxRootEmits>()
const forwarded = useForwardPropsEmits(props, emits)
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
@ -15,10 +22,12 @@ const forwarded = useForwardPropsEmits(props, emits)
v-bind="forwarded"
:class="
cn('peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',
$attrs.class ?? '')"
props.class)"
>
<CheckboxIndicator class="flex h-full w-full items-center justify-center text-current">
<Check class="h-4 w-4" />
<slot>
<Check class="h-4 w-4" />
</slot>
</CheckboxIndicator>
</CheckboxRoot>
</template>

View File

@ -1,13 +1,15 @@
<script setup lang="ts">
import { CollapsibleRoot, useEmitAsProps } from 'radix-vue'
import { CollapsibleRoot, useForwardPropsEmits } from 'radix-vue'
import type { CollapsibleRootEmits, CollapsibleRootProps } from 'radix-vue'
const props = defineProps<CollapsibleRootProps>()
const emits = defineEmits<CollapsibleRootEmits>()
const forwarded = useForwardPropsEmits(props, emits)
</script>
<template>
<CollapsibleRoot v-slot="{ open }" v-bind="{ ...props, ...useEmitAsProps(emits) }">
<CollapsibleRoot v-slot="{ open }" v-bind="forwarded">
<slot :open="open" />
</CollapsibleRoot>
</template>

View File

@ -1,20 +1,29 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import type { ComboboxRootEmits, ComboboxRootProps } from 'radix-vue'
import { ComboboxRoot, useForwardPropsEmits } from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<ComboboxRootProps>()
const props = withDefaults(defineProps<ComboboxRootProps & { class?: HTMLAttributes['class'] }>(), {
open: true,
modelValue: '',
})
const emits = defineEmits<ComboboxRootEmits>()
const forwarded = useForwardPropsEmits(props, emits)
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
<ComboboxRoot
v-bind="forwarded"
:open="true"
:model-value="''"
:class="cn('flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground', $attrs.class ?? '')"
:class="cn('flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground', props.class)"
>
<slot />
</ComboboxRoot>

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { useEmitAsProps } from 'radix-vue'
import { useForwardPropsEmits } from 'radix-vue'
import type { DialogRootEmits, DialogRootProps } from 'radix-vue'
import Command from './Command.vue'
import { Dialog, DialogContent } from '@/lib/registry/default/ui/dialog'
@ -7,12 +7,12 @@ import { Dialog, DialogContent } from '@/lib/registry/default/ui/dialog'
const props = defineProps<DialogRootProps>()
const emits = defineEmits<DialogRootEmits>()
const emitsAsProps = useEmitAsProps(emits)
const forwarded = useForwardPropsEmits(props, emits)
</script>
<template>
<Dialog v-bind="{ ...props, ...emitsAsProps }">
<DialogContent class="p-0 overflow-hidden shadow-lg">
<Dialog v-bind="forwarded">
<DialogContent class="overflow-hidden p-0 shadow-lg">
<Command class="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
<slot />
</Command>

View File

@ -1,13 +1,20 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import type { ComboboxEmptyProps } from 'radix-vue'
import { ComboboxEmpty } from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<ComboboxEmptyProps>()
const props = defineProps<ComboboxEmptyProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
</script>
<template>
<ComboboxEmpty v-bind="props" :class="cn('py-6 text-center text-sm', $attrs.class ?? '')">
<ComboboxEmpty v-bind="delegatedProps" :class="cn('py-6 text-center text-sm', props.class)">
<slot />
</ComboboxEmpty>
</template>

View File

@ -1,17 +1,25 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import type { ComboboxGroupProps } from 'radix-vue'
import { ComboboxGroup, ComboboxLabel } from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<ComboboxGroupProps & {
class?: HTMLAttributes['class']
heading?: string
}>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
</script>
<template>
<ComboboxGroup
v-bind="props"
:class="cn('overflow-hidden p-1 text-foreground', $attrs.class ?? '')"
v-bind="delegatedProps"
:class="cn('overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground', props.class)"
>
<ComboboxLabel v-if="heading" class="px-2 py-1.5 text-xs font-medium text-muted-foreground">
{{ heading }}

View File

@ -1,24 +1,33 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import { Search } from 'lucide-vue-next'
import { ComboboxInput, type ComboboxInputProps } from 'radix-vue'
import { ComboboxInput, type ComboboxInputProps, useForwardProps } from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<ComboboxInputProps>()
</script>
<script lang="ts">
export default {
defineOptions({
inheritAttrs: false,
}
})
const props = defineProps<ComboboxInputProps & {
class?: HTMLAttributes['class']
}>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<div class="flex items-center border-b px-3" cmdk-input-wrapper>
<Search class="mr-2 h-4 w-4 shrink-0 opacity-50" />
<ComboboxInput
v-bind="{ ...props, ...$attrs }"
v-bind="{ ...forwardedProps, ...$attrs }"
auto-focus
:class="cn('flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50', $attrs.class ?? '')"
:class="cn('flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50', props.class)"
/>
</div>
</template>

View File

@ -1,19 +1,25 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import type { ComboboxItemEmits, ComboboxItemProps } from 'radix-vue'
import { ComboboxItem, useEmitAsProps } from 'radix-vue'
import { ComboboxItem, useForwardPropsEmits } from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<ComboboxItemProps>()
const props = defineProps<ComboboxItemProps & { class?: HTMLAttributes['class'] }>()
const emits = defineEmits<ComboboxItemEmits>()
const emitsAsProps = useEmitAsProps(emits)
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
<ComboboxItem
v-bind="{ ...props, ...emitsAsProps }"
:class="cn('relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50', $attrs.class ?? '')"
@select.prevent
v-bind="forwarded"
:class="cn('relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[highlighted]:bg-accent data-[disabled]:pointer-events-none data-[disabled]:opacity-50', props.class)"
>
<slot />
</ComboboxItem>

View File

@ -1,16 +1,23 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import type { ComboboxContentEmits, ComboboxContentProps } from 'radix-vue'
import { ComboboxContent, useForwardPropsEmits } from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<ComboboxContentProps>()
const props = defineProps<ComboboxContentProps & { class?: HTMLAttributes['class'] }>()
const emits = defineEmits<ComboboxContentEmits>()
const forwarded = useForwardPropsEmits(props, emits)
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
<ComboboxContent v-bind="forwarded" :class="cn('max-h-[300px] overflow-y-auto overflow-x-hidden', $attrs.class ?? '')">
<ComboboxContent v-bind="forwarded" :class="cn('max-h-[300px] overflow-y-auto overflow-x-hidden', props.class)">
<div role="presentation">
<slot />
</div>

View File

@ -1,15 +1,22 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import type { ComboboxSeparatorProps } from 'radix-vue'
import { ComboboxSeparator } from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<ComboboxSeparatorProps>()
const props = defineProps<ComboboxSeparatorProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
</script>
<template>
<ComboboxSeparator
v-bind="props"
:class="cn('-mx-1 h-px bg-border', $attrs.class ?? '')"
v-bind="delegatedProps"
:class="cn('-mx-1 h-px bg-border', props.class)"
>
<slot />
</ComboboxSeparator>

View File

@ -1,9 +1,14 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<span :class="cn('ml-auto text-xs tracking-widest text-muted-foreground', $attrs.class ?? '')">
<span :class="cn('ml-auto text-xs tracking-widest text-muted-foreground', props.class)">
<slot />
</span>
</template>

View File

@ -1,33 +1,38 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import {
ContextMenuCheckboxItem,
type ContextMenuCheckboxItemEmits,
type ContextMenuCheckboxItemProps,
ContextMenuItemIndicator,
useEmitAsProps,
useForwardPropsEmits,
} from 'radix-vue'
import { Check } from 'lucide-vue-next'
import { cn } from '@/lib/utils'
const props = defineProps<ContextMenuCheckboxItemProps & { class?: string }>()
const props = defineProps<ContextMenuCheckboxItemProps & { class?: HTMLAttributes['class'] }>()
const emits = defineEmits<ContextMenuCheckboxItemEmits>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
<ContextMenuCheckboxItem
v-bind="{ ...props, ...useEmitAsProps(emits) }"
:class="[
cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
props.class,
),
]"
v-bind="forwarded"
:class="cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
props.class,
)"
>
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<ContextMenuItemIndicator
class="absolute left-1.5 inline-flex w-4 h-4 items-center justify-center"
>
<Check class="h-4 h-w" />
<ContextMenuItemIndicator>
<Check class="h-4 w-4" />
</ContextMenuItemIndicator>
</span>
<slot />

View File

@ -1,4 +1,5 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import {
ContextMenuContent,
type ContextMenuContentEmits,
@ -8,23 +9,26 @@ import {
} from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<ContextMenuContentProps & { class?: string }>()
const props = defineProps<ContextMenuContentProps & { class?: HTMLAttributes['class'] }>()
const emits = defineEmits<ContextMenuContentEmits>()
const forwarded = useForwardPropsEmits(props, emits)
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
<ContextMenuPortal>
<ContextMenuContent
:align-offset="props.alignOffset"
:class="[
cn(
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in fade-in-80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
props.class,
),
]"
v-bind="forwarded"
:class="cn(
'z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in fade-in-80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
props.class,
)"
>
<slot />
</ContextMenuContent>

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