Merge remote-tracking branch 'origin/dev' into charting

This commit is contained in:
zernonia 2024-02-07 22:30:01 +08:00
commit 595bbe04ea
565 changed files with 11169 additions and 5958 deletions

View File

@ -6,62 +6,27 @@ body:
- type: markdown - type: markdown
attributes: attributes:
value: | 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 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: or are unsure if this is really a bug, make sure to:
- Read the [docs](https://radix-vue.com/) - Read the [docs](https://radix-vue.com/)
- Ask on [Discord Chat](https://chat.radix-vue.com/) - Ask on [Discord Chat](https://chat.radix-vue.com/)
- Ask on [GitHub Discussions](https://github.com/shadcn-vue/shadcn-vue/discussions) - 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 - type: input
id: reproduction-link id: reproduction
attributes: attributes:
label: Link to minimal reproduction label: Reproduction
description: | description: |
Please provide a link to a minimal reproduction of the bug. 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)
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
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. or use template presets
placeholder: Reproduction Link https://vite.new
validations: https://nuxt.new
required: true placeholder: Reproduction
- 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
validations: validations:
required: true required: true
- type: textarea - type: textarea
@ -73,14 +38,18 @@ body:
validations: validations:
required: true required: true
- type: textarea - type: textarea
id: expected-behavior id: system-info
attributes: attributes:
label: Expected behavior label: System Info
description: A clear and concise description of what you expected to happen. description: Output of `npx envinfo --system --npmPackages vue,@vueuse/core,radix-vue,nuxt,shadcn-vue,shadcn-nuxt,unplugin-auto-import --binaries --browsers`
- type: textarea render: bash
id: screenshots placeholder: System, Binaries, Browsers
validations:
required: true
- type: checkboxes
id: contributes
attributes: attributes:
label: Conext & Screenshots (if applicable) label: Contributes
description: | options:
If applicable, provide any additional context or screenshots of the bug. - label: I am willing to submit a PR to fix this issue
You can drag and drop images here to add them to the issue. - label: I am willing to submit a PR with failing tests

View File

@ -11,14 +11,42 @@ on:
- dev - dev
paths: paths:
- 'apps/www/**' - 'apps/www/**'
pull_request_target:
types:
# When a created pull request from forked repo, it will be comment 'Should deploy to add label'
- opened
# When a labeled '🚀request-deploy' pull request from forked repo, it will be deploy to Cloudflare Pages
- labeled
# Allows you to run this workflow manually from the Actions tab
# eslint-disable-next-line yml/no-empty-mapping-value
workflow_dispatch:
permissions:
# default contents: read & write (in forked repos, only read)
contents: write
# default deployments: read & write (in forked repos, only read)
deployments: write
# default pull-requests: read & write (in forked repos, only read)
pull-requests: write
jobs: jobs:
publish: publish:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
deployments: write
name: Publish to Cloudflare Pages name: Publish to Cloudflare Pages
# push event in main branch
# workflow_dispatch event
# pull_request event from not forked repo
# pull_request_target event with label "🚀request-deploy" from forked repo
if: ${{
github.event_name == 'push' ||
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false) ||
(github.event_name == 'pull_request_target' &&
github.event.action == 'labeled' &&
github.event.pull_request.head.repo.fork == true &&
contains(github.event.label.name, '🚀request-deploy'))
}}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
@ -56,7 +84,7 @@ jobs:
# Run a action to publish docs # Run a action to publish docs
- name: Publish to Cloudflare Pages - name: Publish to Cloudflare Pages
uses: cloudflare/pages-action@v1.5.0 uses: zernonia/cloudflare-pages-action@v0.0.7
with: with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
@ -66,7 +94,20 @@ jobs:
gitHubToken: ${{ secrets.GITHUB_TOKEN }} gitHubToken: ${{ secrets.GITHUB_TOKEN }}
# Optional: Switch what branch you are publishing to. # Optional: Switch what branch you are publishing to.
# By default this will be the branch which triggered this workflow # By default this will be the branch which triggered this workflow
# branch: main branch: ${{ github.ref == 'refs/heads/dev' && 'dev' || format('refs/pull/{0}/merge', github.event.number) }}
# Optional: Change the working directory # Optional: Change the working directory
workingDirectory: apps/www workingDirectory: apps/www
wranglerVersion: '3' wranglerVersion: '3'
- name: Remove label
if: ${{ github.event_name == 'pull_request_target' && contains(github.event.label.name, '🚀request-deploy') }}
uses: actions/github-script@v6
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
github.rest.issues.removeLabel({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
name: ['🚀request-deploy']
})

1
.npmrc Normal file
View File

@ -0,0 +1 @@
shell-emulator=true

View File

@ -2,8 +2,8 @@
"prettier.enable": false, "prettier.enable": false,
"editor.formatOnSave": false, "editor.formatOnSave": false,
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": true, "source.fixAll.eslint": "explicit",
"source.organizeImports": false "source.organizeImports": "never"
}, },
"eslint.validate": [ "eslint.validate": [

View File

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

View File

@ -52,7 +52,7 @@ const { style } = useConfigStore()
</div> </div>
</div> </div>
<div <div
:class="cn('preview flex min-h-[350px] w-full justify-center p-6 lg:p-10', { :class="cn('preview flex min-h-[350px] w-full justify-center p-10 items-center', {
'items-center': align === 'center', 'items-center': align === 'center',
'items-start': align === 'start', 'items-start': align === 'start',
'items-end': align === 'end', 'items-end': align === 'end',

View File

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@ import { cn } from '@/lib/utils'
</script> </script>
<template> <template>
<a :class="cn('flex w-full flex-col items-center rounded-xl border bg-card p-6 text-card-foreground shadow transition-colors hover:bg-muted/50 sm:p-10', $attrs.class ?? '')"> <a :class="cn('flex w-full flex-col items-center rounded-lg border bg-card p-6 text-card-foreground shadow transition-colors hover:bg-muted/50 sm:p-10', $attrs.class ?? '')">
<slot /> <slot />
</a> </a>
</template> </template>

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> <template>
<section <section
:class="cn( :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 ?? '', $attrs.class ?? '',
)" )"
> >

View File

@ -4,7 +4,7 @@ import { cn } from '@/lib/utils'
</script> </script>
<template> <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 /> <slot />
</WrapBalancer> </WrapBalancer>
</template> </template>

View File

@ -5,7 +5,7 @@ import { cn } from '@/lib/utils'
<template> <template>
<h1 <h1
:class="cn( :class="cn(
'text-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 ?? '', $attrs.class ?? '',
)" )"
> >

View File

@ -0,0 +1,16 @@
<script setup lang="ts">
import { useSlots } from 'vue'
import { TabsContent, TabsTrigger } from '@/lib/registry/default/ui/tabs'
withDefaults(defineProps<{
title?: string
}>(), {
title: '',
})
</script>
<template>
<TabsContent :value="title" class="relative space-y-10">
<slot />
</TabsContent>
</template>

View File

@ -0,0 +1,22 @@
<script setup lang="ts">
import { computed, useSlots } from 'vue'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/lib/registry/default/ui/tabs'
const slots = useSlots()
const tabs = computed(() => slots.default?.()?.map(i => i?.props?.title as string) ?? [])
</script>
<template>
<Tabs :default-value="tabs[0]" class="relative mr-auto w-full">
<div class="flex items-center justify-between">
<TabsList class="w-full justify-start rounded-none border-b bg-transparent p-0">
<TabsTrigger v-for="tab in tabs" :key="tab" :value="tab" class="relative h-9 rounded-none border-b-2 border-b-transparent bg-transparent px-4 pb-3 pt-2 font-semibold text-muted-foreground shadow-none transition-none data-[state=active]:border-b-primary data-[state=active]:text-foreground data-[state=active]:shadow-none">
{{ tab }}
</TabsTrigger>
</TabsList>
</div>
<slot />
</Tabs>
</template>

View File

@ -1,5 +1,7 @@
export { default as ComponentPreview } from './ComponentPreview.vue' export { default as ComponentPreview } from './ComponentPreview.vue'
export { default as TabPreview } from './TabPreview.vue' export { default as TabPreview } from './TabPreview.vue'
export { default as TabMarkdown } from './TabMarkdown.vue'
export { default as TabsMarkdown } from './TabsMarkdown.vue'
export { default as Callout } from './Callout.vue' export { default as Callout } from './Callout.vue'
export { default as LinkedCard } from './LinkedCard.vue' export { default as LinkedCard } from './LinkedCard.vue'
export { default as ManualInstall } from './ManualInstall.vue' export { default as ManualInstall } from './ManualInstall.vue'

View File

@ -168,6 +168,12 @@ export const docsConfig: DocsConfig = {
title: 'Card', title: 'Card',
href: '/docs/components/card', href: '/docs/components/card',
}, },
{
title: 'Carousel',
href: '/docs/components/carousel',
label: 'New',
items: [],
},
{ {
title: 'Checkbox', title: 'Checkbox',
href: '/docs/components/checkbox', href: '/docs/components/checkbox',
@ -232,6 +238,12 @@ export const docsConfig: DocsConfig = {
title: 'Pagination', title: 'Pagination',
href: '/docs/components/pagination', href: '/docs/components/pagination',
}, },
{
title: 'Pin Input',
href: '/docs/components/pin-input',
label: 'New',
items: [],
},
{ {
title: 'Popover', title: 'Popover',
href: '/docs/components/popover', href: '/docs/components/popover',
@ -268,6 +280,12 @@ export const docsConfig: DocsConfig = {
title: 'Slider', title: 'Slider',
href: '/docs/components/slider', href: '/docs/components/slider',
}, },
{
title: 'Sonner',
href: '/docs/components/sonner',
label: 'New',
items: [],
},
{ {
title: 'Switch', title: 'Switch',
href: '/docs/components/switch', href: '/docs/components/switch',
@ -292,6 +310,10 @@ export const docsConfig: DocsConfig = {
title: 'Toggle', title: 'Toggle',
href: '/docs/components/toggle', href: '/docs/components/toggle',
}, },
{
title: 'Toggle Group',
href: '/docs/components/toggle-group',
},
{ {
title: 'Tooltip', title: 'Tooltip',
href: '/docs/components/tooltip', href: '/docs/components/tooltip',

View File

@ -15,6 +15,6 @@ export const siteConfig = {
export const announcementConfig = { export const announcementConfig = {
icon: '✨', icon: '✨',
title: 'VSCode extension', title: 'New components!',
link: '/docs/installation.html#vscode-extension', 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 { ScrollArea } from '@/lib/registry/default/ui/scroll-area'
import { Badge } from '@/lib/registry/default/ui/badge' import { Badge } from '@/lib/registry/default/ui/badge'
import RadixIconsCode from '~icons/radix-icons/code' import RadixIconsCode from '~icons/radix-icons/code'
import RadixIconsExternalLink from '~icons/radix-icons/external-link'
import ChevronRightIcon from '~icons/lucide/chevron-right' import ChevronRightIcon from '~icons/lucide/chevron-right'
const $route = useRoute() const $route = useRoute()
@ -20,7 +21,7 @@ const sourceLink = 'https://github.com/radix-vue/shadcn-vue/tree/dev/'
<aside <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" 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 class="w-full">
<div v-for="docsGroup in docsConfig.sidebarNav" :key="docsGroup.title" class="pb-4"> <div v-for="docsGroup in docsConfig.sidebarNav" :key="docsGroup.title" class="pb-4">
<h4 <h4
@ -49,9 +50,9 @@ const sourceLink = 'https://github.com/radix-vue/shadcn-vue/tree/dev/'
> >
{{ doc.title }} {{ doc.title }}
<Badge v-if="doc.label" class="ml-2"> <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 }} {{ doc.label }}
</Badge> </span>
</a> </a>
</div> </div>
</div> </div>
@ -90,6 +91,10 @@ const sourceLink = 'https://github.com/radix-vue/shadcn-vue/tree/dev/'
</div> </div>
<div class="flex items-center space-x-2 pt-4"> <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"> <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" /> <RadixIconsCode class="mr-1" />
Component Source Component Source

View File

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

View File

@ -18,6 +18,7 @@ import RadixIconsSun from '~icons/radix-icons/sun'
import { useConfigStore } from '@/stores/config' import { useConfigStore } from '@/stores/config'
import { Dialog, DialogContent } from '@/lib/registry/default/ui/dialog' import { Dialog, DialogContent } from '@/lib/registry/default/ui/dialog'
import { Toaster as DefaultToaster } from '@/lib/registry/default/ui/toast' import { Toaster as DefaultToaster } from '@/lib/registry/default/ui/toast'
import { Toaster as NewYorkSonner } from '@/lib/registry/new-york/ui/sonner'
import { Toaster as NewYorkToaster } from '@/lib/registry/new-york/ui/toast' import { Toaster as NewYorkToaster } from '@/lib/registry/new-york/ui/toast'
import File from '~icons/radix-icons/file' import File from '~icons/radix-icons/file'
@ -50,7 +51,13 @@ const links = [
] ]
const isOpen = ref(false) const isOpen = ref(false)
const { Meta_K, Ctrl_K } = useMagicKeys() const { Meta_K, Ctrl_K } = useMagicKeys({
passive: false,
onEventFired(e) {
if (e.key === 'k' && (e.metaKey || e.ctrlKey))
e.preventDefault()
},
})
watch([Meta_K, Ctrl_K], (v) => { watch([Meta_K, Ctrl_K], (v) => {
if (v[0] || v[1]) if (v[0] || v[1])
@ -81,7 +88,7 @@ watch(() => $route.path, (n) => {
<div class="flex min-h-screen flex-col bg-background"> <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"> <header class="sticky z-40 top-0 bg-background/80 backdrop-blur-lg border-b border-border">
<div <div
class="container flex justify-between h-14 items-center" class="container flex justify-between h-14 max-w-screen-2xl items-center"
> >
<MobileNav /> <MobileNav />
@ -131,21 +138,22 @@ watch(() => $route.path, (n) => {
:variant="'ghost'" :variant="'ghost'"
:size="'sm'" :size="'sm'"
> >
<component :is="link.icon" /> <component :is="link.icon" class="w-[20px] h-5" />
</Button> </Button>
<ClientOnly>
<Button <Button
class="flex items-center justify-center" class="flex items-center justify-center"
aria-label="Toggle dark mode" aria-label="Toggle dark mode"
:variant="'ghost'" :variant="'ghost'"
:size="'sm'" :size="'icon'" @click="toggleDark()"
@click="toggleDark()"
> >
<component <component
:is="isDark ? RadixIconsSun : RadixIconsMoon" :is="isDark ? RadixIconsSun : RadixIconsMoon"
class="text-foreground" class="w-[20px] h-5 text-foreground"
/> />
</Button> </Button>
</ClientOnly>
</div> </div>
</div> </div>
</div> </div>
@ -285,6 +293,9 @@ watch(() => $route.path, (n) => {
</DialogContent> </DialogContent>
</Dialog> </Dialog>
<DefaultToaster /> <DefaultToaster />
<ClientOnly>
<NewYorkSonner :theme="isDark ? 'dark' : 'light'" />
</ClientOnly>
<NewYorkToaster /> <NewYorkToaster />
</div> </div>
</template> </template>

View File

@ -19,14 +19,13 @@
--muted-foreground: 240 3.8% 46.1%; --muted-foreground: 240 3.8% 46.1%;
--accent: 240 4.8% 95.9%; --accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%; --accent-foreground: 240 5.9% 10%;
--destructive: 0 84.2% 60.2%; --destructive: 0 72.22% 50.59%;
--destructive-foreground: 0 0% 98%; --destructive-foreground: 0 0% 98%;
--border: 240 5.9% 90%; --border: 240 5.9% 90%;
--input: 240 5.9% 90%; --input: 240 5.9% 90%;
--ring: 240 5% 64.9%; --ring: 240 5% 64.9%;
--radius: 0.5rem; --radius: 0.5rem;
--vis-tooltip-background-color: none !important; --vis-tooltip-background-color: none !important;
--vis-tooltip-border-color: none !important; --vis-tooltip-border-color: none !important;
--vis-tooltip-text-color: none !important; --vis-tooltip-text-color: none !important;
@ -124,7 +123,7 @@
.step:before { .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 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); content: counter(step);
} }
} }
@ -147,7 +146,7 @@ pre code {
} }
pre code .line { 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 { .line-number {

View File

@ -1,5 +1,5 @@
:root { :root {
--shiki-color-text: #EEEEEE; --shiki-foreground: #FFF8;
--shiki-color-background: #ffffff; --shiki-color-background: #ffffff;
--shiki-token-constant: #ffffff; --shiki-token-constant: #ffffff;
--shiki-token-string: #ffffff88; --shiki-token-string: #ffffff88;
@ -7,7 +7,14 @@
--shiki-token-keyword: #ffffff88; --shiki-token-keyword: #ffffff88;
--shiki-token-parameter: #AA0000; --shiki-token-parameter: #AA0000;
--shiki-token-function: #ffffff; --shiki-token-function: #ffffff;
--shiki-token-string-expression: #ffffff88; --shiki-token-string-expression: #ebebeb;
--shiki-token-punctuation: #ffffff; --shiki-token-punctuation: #ffffff;
--shiki-token-link: #EE0000; --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-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-code-bg: hsl(var(--muted));
--vp-c-divider: hsl(var(--muted)); --vp-c-divider: hsl(var(--muted));
--vp-code-block-color: #fff
} }
@ -414,7 +415,7 @@ hsl(var(--foreground) / 50%)
.vp-doc div[class*='language-'].line-numbers-mode { .vp-doc div[class*='language-'].line-numbers-mode {
/*rtl:ignore*/ /*rtl:ignore*/
padding-left: 32px; padding-left: 0px;
} }
.vp-doc .line-numbers-wrapper { .vp-doc .line-numbers-wrapper {
@ -567,3 +568,11 @@ hsl(var(--foreground) / 50%)
.vp-external-link-icon::after { .vp-external-link-icon::after {
content: ''; content: '';
} }
.vp-doc [class*=language-] code {
color: var(--vp-code-block-color);
}
.line-numbers-mode pre code .line {
padding-left: 3rem !important;
}

View File

@ -198,6 +198,48 @@ export const Index = {
component: () => import('../src/lib/registry/default/example/CardWithForm.vue').then(m => m.default), component: () => import('../src/lib/registry/default/example/CardWithForm.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/CardWithForm.vue'], files: ['../src/lib/registry/default/example/CardWithForm.vue'],
}, },
CarouselApi: {
name: 'CarouselApi',
type: 'components:example',
registryDependencies: ['carousel', 'card'],
component: () => import('../src/lib/registry/default/example/CarouselApi.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/CarouselApi.vue'],
},
CarouselDemo: {
name: 'CarouselDemo',
type: 'components:example',
registryDependencies: ['carousel', 'card'],
component: () => import('../src/lib/registry/default/example/CarouselDemo.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/CarouselDemo.vue'],
},
CarouselOrientation: {
name: 'CarouselOrientation',
type: 'components:example',
registryDependencies: ['carousel', 'card'],
component: () => import('../src/lib/registry/default/example/CarouselOrientation.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/CarouselOrientation.vue'],
},
CarouselPlugin: {
name: 'CarouselPlugin',
type: 'components:example',
registryDependencies: ['carousel', 'card'],
component: () => import('../src/lib/registry/default/example/CarouselPlugin.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/CarouselPlugin.vue'],
},
CarouselSize: {
name: 'CarouselSize',
type: 'components:example',
registryDependencies: ['carousel', 'card'],
component: () => import('../src/lib/registry/default/example/CarouselSize.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/CarouselSize.vue'],
},
CarouselSpacing: {
name: 'CarouselSpacing',
type: 'components:example',
registryDependencies: ['carousel', 'card'],
component: () => import('../src/lib/registry/default/example/CarouselSpacing.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/CarouselSpacing.vue'],
},
CheckboxDemo: { CheckboxDemo: {
name: 'CheckboxDemo', name: 'CheckboxDemo',
type: 'components:example', type: 'components:example',
@ -289,6 +331,13 @@ export const Index = {
component: () => import('../src/lib/registry/default/example/ContextMenuDemo.vue').then(m => m.default), component: () => import('../src/lib/registry/default/example/ContextMenuDemo.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/ContextMenuDemo.vue'], 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: { DataTableDemo: {
name: 'DataTableDemo', name: 'DataTableDemo',
type: 'components:example', type: 'components:example',
@ -331,6 +380,13 @@ export const Index = {
component: () => import('../src/lib/registry/default/example/DatePickerWithRange.vue').then(m => m.default), component: () => import('../src/lib/registry/default/example/DatePickerWithRange.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/DatePickerWithRange.vue'], files: ['../src/lib/registry/default/example/DatePickerWithRange.vue'],
}, },
DateTimePickerDemo: {
name: 'DateTimePickerDemo',
type: 'components:example',
registryDependencies: ['utils', 'button', 'calendar', 'popover'],
component: () => import('../src/lib/registry/default/example/DateTimePickerDemo.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/DateTimePickerDemo.vue'],
},
DialogCustomCloseButton: { DialogCustomCloseButton: {
name: 'DialogCustomCloseButton', name: 'DialogCustomCloseButton',
type: 'components:example', type: 'components:example',
@ -457,6 +513,20 @@ export const Index = {
component: () => import('../src/lib/registry/default/example/PaginationDemo.vue').then(m => m.default), component: () => import('../src/lib/registry/default/example/PaginationDemo.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/PaginationDemo.vue'], 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: { PopoverDemo: {
name: 'PopoverDemo', name: 'PopoverDemo',
type: 'components:example', type: 'components:example',
@ -485,6 +555,13 @@ export const Index = {
component: () => import('../src/lib/registry/default/example/RadioGroupForm.vue').then(m => m.default), component: () => import('../src/lib/registry/default/example/RadioGroupForm.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/RadioGroupForm.vue'], 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: { ScrollAreaDemo: {
name: 'ScrollAreaDemo', name: 'ScrollAreaDemo',
type: 'components:example', type: 'components:example',
@ -548,6 +625,13 @@ export const Index = {
component: () => import('../src/lib/registry/default/example/SliderDemo.vue').then(m => m.default), component: () => import('../src/lib/registry/default/example/SliderDemo.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/SliderDemo.vue'], 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: { SwitchDemo: {
name: 'SwitchDemo', name: 'SwitchDemo',
type: 'components:example', type: 'components:example',
@ -667,6 +751,48 @@ export const Index = {
component: () => import('../src/lib/registry/default/example/ToggleDisabledDemo.vue').then(m => m.default), component: () => import('../src/lib/registry/default/example/ToggleDisabledDemo.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/ToggleDisabledDemo.vue'], files: ['../src/lib/registry/default/example/ToggleDisabledDemo.vue'],
}, },
ToggleGroupDemo: {
name: 'ToggleGroupDemo',
type: 'components:example',
registryDependencies: ['toggle-group'],
component: () => import('../src/lib/registry/default/example/ToggleGroupDemo.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/ToggleGroupDemo.vue'],
},
ToggleGroupDisabledDemo: {
name: 'ToggleGroupDisabledDemo',
type: 'components:example',
registryDependencies: ['toggle-group'],
component: () => import('../src/lib/registry/default/example/ToggleGroupDisabledDemo.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/ToggleGroupDisabledDemo.vue'],
},
ToggleGroupLargeDemo: {
name: 'ToggleGroupLargeDemo',
type: 'components:example',
registryDependencies: ['toggle-group'],
component: () => import('../src/lib/registry/default/example/ToggleGroupLargeDemo.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/ToggleGroupLargeDemo.vue'],
},
ToggleGroupOutlineDemo: {
name: 'ToggleGroupOutlineDemo',
type: 'components:example',
registryDependencies: ['toggle-group'],
component: () => import('../src/lib/registry/default/example/ToggleGroupOutlineDemo.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/ToggleGroupOutlineDemo.vue'],
},
ToggleGroupSingleDemo: {
name: 'ToggleGroupSingleDemo',
type: 'components:example',
registryDependencies: ['toggle-group'],
component: () => import('../src/lib/registry/default/example/ToggleGroupSingleDemo.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/ToggleGroupSingleDemo.vue'],
},
ToggleGroupSmallDemo: {
name: 'ToggleGroupSmallDemo',
type: 'components:example',
registryDependencies: ['toggle-group'],
component: () => import('../src/lib/registry/default/example/ToggleGroupSmallDemo.vue').then(m => m.default),
files: ['../src/lib/registry/default/example/ToggleGroupSmallDemo.vue'],
},
ToggleItalicDemo: { ToggleItalicDemo: {
name: 'ToggleItalicDemo', name: 'ToggleItalicDemo',
type: 'components:example', type: 'components:example',
@ -1019,6 +1145,48 @@ export const Index = {
component: () => import('../src/lib/registry/new-york/example/CardWithForm.vue').then(m => m.default), component: () => import('../src/lib/registry/new-york/example/CardWithForm.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/CardWithForm.vue'], files: ['../src/lib/registry/new-york/example/CardWithForm.vue'],
}, },
CarouselApi: {
name: 'CarouselApi',
type: 'components:example',
registryDependencies: ['carousel', 'card'],
component: () => import('../src/lib/registry/new-york/example/CarouselApi.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/CarouselApi.vue'],
},
CarouselDemo: {
name: 'CarouselDemo',
type: 'components:example',
registryDependencies: ['carousel', 'card'],
component: () => import('../src/lib/registry/new-york/example/CarouselDemo.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/CarouselDemo.vue'],
},
CarouselOrientation: {
name: 'CarouselOrientation',
type: 'components:example',
registryDependencies: ['carousel', 'card'],
component: () => import('../src/lib/registry/new-york/example/CarouselOrientation.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/CarouselOrientation.vue'],
},
CarouselPlugin: {
name: 'CarouselPlugin',
type: 'components:example',
registryDependencies: ['carousel', 'card'],
component: () => import('../src/lib/registry/new-york/example/CarouselPlugin.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/CarouselPlugin.vue'],
},
CarouselSize: {
name: 'CarouselSize',
type: 'components:example',
registryDependencies: ['carousel', 'card'],
component: () => import('../src/lib/registry/new-york/example/CarouselSize.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/CarouselSize.vue'],
},
CarouselSpacing: {
name: 'CarouselSpacing',
type: 'components:example',
registryDependencies: ['carousel', 'card'],
component: () => import('../src/lib/registry/new-york/example/CarouselSpacing.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/CarouselSpacing.vue'],
},
CheckboxDemo: { CheckboxDemo: {
name: 'CheckboxDemo', name: 'CheckboxDemo',
type: 'components:example', type: 'components:example',
@ -1110,6 +1278,13 @@ export const Index = {
component: () => import('../src/lib/registry/new-york/example/ContextMenuDemo.vue').then(m => m.default), component: () => import('../src/lib/registry/new-york/example/ContextMenuDemo.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/ContextMenuDemo.vue'], 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: { DataTableDemo: {
name: 'DataTableDemo', name: 'DataTableDemo',
type: 'components:example', type: 'components:example',
@ -1152,6 +1327,13 @@ export const Index = {
component: () => import('../src/lib/registry/new-york/example/DatePickerWithRange.vue').then(m => m.default), component: () => import('../src/lib/registry/new-york/example/DatePickerWithRange.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/DatePickerWithRange.vue'], files: ['../src/lib/registry/new-york/example/DatePickerWithRange.vue'],
}, },
DateTimePickerDemo: {
name: 'DateTimePickerDemo',
type: 'components:example',
registryDependencies: ['utils', 'button', 'calendar', 'popover'],
component: () => import('../src/lib/registry/new-york/example/DateTimePickerDemo.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/DateTimePickerDemo.vue'],
},
DialogCustomCloseButton: { DialogCustomCloseButton: {
name: 'DialogCustomCloseButton', name: 'DialogCustomCloseButton',
type: 'components:example', type: 'components:example',
@ -1278,6 +1460,20 @@ export const Index = {
component: () => import('../src/lib/registry/new-york/example/PaginationDemo.vue').then(m => m.default), component: () => import('../src/lib/registry/new-york/example/PaginationDemo.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/PaginationDemo.vue'], 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: { PopoverDemo: {
name: 'PopoverDemo', name: 'PopoverDemo',
type: 'components:example', type: 'components:example',
@ -1306,6 +1502,13 @@ export const Index = {
component: () => import('../src/lib/registry/new-york/example/RadioGroupForm.vue').then(m => m.default), component: () => import('../src/lib/registry/new-york/example/RadioGroupForm.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/RadioGroupForm.vue'], 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: { ScrollAreaDemo: {
name: 'ScrollAreaDemo', name: 'ScrollAreaDemo',
type: 'components:example', type: 'components:example',
@ -1369,6 +1572,13 @@ export const Index = {
component: () => import('../src/lib/registry/new-york/example/SliderDemo.vue').then(m => m.default), component: () => import('../src/lib/registry/new-york/example/SliderDemo.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/SliderDemo.vue'], 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: { SwitchDemo: {
name: 'SwitchDemo', name: 'SwitchDemo',
type: 'components:example', type: 'components:example',
@ -1488,6 +1698,48 @@ export const Index = {
component: () => import('../src/lib/registry/new-york/example/ToggleDisabledDemo.vue').then(m => m.default), component: () => import('../src/lib/registry/new-york/example/ToggleDisabledDemo.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/ToggleDisabledDemo.vue'], files: ['../src/lib/registry/new-york/example/ToggleDisabledDemo.vue'],
}, },
ToggleGroupDemo: {
name: 'ToggleGroupDemo',
type: 'components:example',
registryDependencies: ['toggle-group'],
component: () => import('../src/lib/registry/new-york/example/ToggleGroupDemo.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/ToggleGroupDemo.vue'],
},
ToggleGroupDisabledDemo: {
name: 'ToggleGroupDisabledDemo',
type: 'components:example',
registryDependencies: ['toggle-group'],
component: () => import('../src/lib/registry/new-york/example/ToggleGroupDisabledDemo.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/ToggleGroupDisabledDemo.vue'],
},
ToggleGroupLargeDemo: {
name: 'ToggleGroupLargeDemo',
type: 'components:example',
registryDependencies: ['toggle-group'],
component: () => import('../src/lib/registry/new-york/example/ToggleGroupLargeDemo.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/ToggleGroupLargeDemo.vue'],
},
ToggleGroupOutlineDemo: {
name: 'ToggleGroupOutlineDemo',
type: 'components:example',
registryDependencies: ['toggle-group'],
component: () => import('../src/lib/registry/new-york/example/ToggleGroupOutlineDemo.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/ToggleGroupOutlineDemo.vue'],
},
ToggleGroupSingleDemo: {
name: 'ToggleGroupSingleDemo',
type: 'components:example',
registryDependencies: ['toggle-group'],
component: () => import('../src/lib/registry/new-york/example/ToggleGroupSingleDemo.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/ToggleGroupSingleDemo.vue'],
},
ToggleGroupSmallDemo: {
name: 'ToggleGroupSmallDemo',
type: 'components:example',
registryDependencies: ['toggle-group'],
component: () => import('../src/lib/registry/new-york/example/ToggleGroupSmallDemo.vue').then(m => m.default),
files: ['../src/lib/registry/new-york/example/ToggleGroupSmallDemo.vue'],
},
ToggleItalicDemo: { ToggleItalicDemo: {
name: 'ToggleItalicDemo', name: 'ToggleItalicDemo',
type: 'components:example', type: 'components:example',

View File

@ -1,7 +1,7 @@
{ {
"name": "www", "name": "www",
"type": "module", "type": "module",
"version": "0.8.4", "version": "0.9.0",
"files": [ "files": [
"dist" "dist"
], ],
@ -9,30 +9,35 @@
"dev": "vitepress dev", "dev": "vitepress dev",
"build": "vitepress build", "build": "vitepress build",
"preview": "vitepress preview", "preview": "vitepress preview",
"typecheck": "vue-tsc --noEmit", "typecheck": "vue-tsc",
"typecheck:registry": "vue-tsc --noEmit -p tsconfig.registry.json", "typecheck:registry": "vue-tsc -p tsconfig.registry.json",
"build:registry": "pnpm typecheck:registry && tsx ./scripts/build-registry.ts" "build:registry": "tsx ./scripts/build-registry.ts",
"build:registry-strict": "pnpm typecheck:registry && tsx ./scripts/build-registry.ts"
}, },
"dependencies": { "dependencies": {
"@formkit/auto-animate": "^0.8.0", "@formkit/auto-animate": "^0.8.1",
"@morev/vue-transitions": "^2.3.6", "@morev/vue-transitions": "^2.3.6",
"@radix-icons/vue": "^1.0.0", "@radix-icons/vue": "^1.0.0",
"@stackblitz/sdk": "^1.9.0", "@stackblitz/sdk": "^1.9.0",
"@tanstack/vue-table": "^8.10.7", "@tanstack/vue-table": "^8.11.8",
"@unovis/ts": "^1.2.1", "@unovis/ts": "^1.3.3",
"@unovis/vue": "1.3.0", "@unovis/vue": "^1.3.3",
"@vee-validate/zod": "^4.11.8", "@vee-validate/zod": "^4.12.5",
"@vueuse/core": "^10.5.0", "@vueuse/core": "^10.7.2",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.0.0", "clsx": "^2.1.0",
"codesandbox": "^2.2.3", "codesandbox": "^2.2.3",
"date-fns": "^2.30.0", "date-fns": "^2.30.0",
"embla-carousel": "^8.0.0-rc22",
"embla-carousel-autoplay": "^8.0.0-rc22",
"embla-carousel-vue": "^8.0.0-rc22",
"lucide-vue-next": "^0.276.0", "lucide-vue-next": "^0.276.0",
"radix-vue": "^1.2.3", "radix-vue": "^1.4.1",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"v-calendar": "^3.1.2", "v-calendar": "^3.1.2",
"vee-validate": "4.11.8", "vee-validate": "4.12.5",
"vue": "^3.3.7", "vue": "^3.4.15",
"vue-sonner": "^1.0.3",
"vue-wrap-balancer": "^1.1.3", "vue-wrap-balancer": "^1.1.3",
"zod": "^3.22.4" "zod": "^3.22.4"
}, },
@ -41,24 +46,27 @@
"@iconify-json/tabler": "^1.1.89", "@iconify-json/tabler": "^1.1.89",
"@iconify/json": "^2.2.108", "@iconify/json": "^2.2.108",
"@iconify/vue": "^4.1.1", "@iconify/vue": "^4.1.1",
"@shikijs/transformers": "^1.0.0-beta.3",
"@types/lodash.template": "^4.5.2", "@types/lodash.template": "^4.5.2",
"@types/node": "^20.8.10", "@types/node": "^20.8.10",
"@vitejs/plugin-vue": "^4.4.0", "@vitejs/plugin-vue": "^5.0.3",
"@vitejs/plugin-vue-jsx": "^3.0.2", "@vitejs/plugin-vue-jsx": "^3.1.0",
"@vue/compiler-core": "^3.3.7", "@vue/compiler-core": "^3.4.15",
"@vue/compiler-dom": "^3.3.7", "@vue/compiler-dom": "^3.4.15",
"@vue/tsconfig": "^0.4.0", "@vue/tsconfig": "^0.5.1",
"autoprefixer": "^10.4.16", "autoprefixer": "^10.4.17",
"lodash.template": "^4.5.0", "lodash.template": "^4.5.0",
"pathe": "^1.1.1", "oxc-parser": "^0.2.0",
"pathe": "^1.1.2",
"rimraf": "^5.0.5", "rimraf": "^5.0.5",
"tailwind-merge": "^2.0.0", "shiki": "^1.0.0-beta.3",
"tailwindcss": "^3.3.5", "tailwind-merge": "^2.2.1",
"tsx": "^3.14.0", "tailwindcss": "^3.4.1",
"typescript": "^5.2.2", "tsx": "^4.7.0",
"unplugin-icons": "^0.17.1", "typescript": "^5.3.3",
"vite": "^4.5.0", "unplugin-icons": "^0.18.3",
"vitepress": "^1.0.0-rc.24", "vite": "^5.0.12",
"vue-tsc": "^1.8.22" "vitepress": "^1.0.0-rc.41",
"vue-tsc": "^1.8.27"
} }
} }

View File

@ -66,6 +66,8 @@ fs.writeFileSync(path.join(process.cwd(), '__registry__/index.ts'), index)
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Build registry/styles/[style]/[name].json. // Build registry/styles/[style]/[name].json.
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
const newLine = '\n'
for (const style of styles) { for (const style of styles) {
const targetPath = path.join(REGISTRY_PATH, 'styles', style.name) const targetPath = path.join(REGISTRY_PATH, 'styles', style.name)
@ -78,11 +80,14 @@ for (const style of styles) {
continue continue
const files = item.files?.map((file) => { const files = item.files?.map((file) => {
const content = fs.readFileSync( let content = fs.readFileSync(
path.join(process.cwd(), 'src/lib/registry', style.name, file), path.join(process.cwd(), 'src/lib/registry', style.name, file),
'utf8', 'utf8',
) )
// Replace Windows-style newlines with Unix-style newlines
content = content.replace(/\r\n/g, newLine)
return { return {
name: basename(file), name: basename(file),
content, content,
@ -94,9 +99,11 @@ for (const style of styles) {
files, files,
} }
const payloadStr = JSON.stringify(payload, null, 2).replace(/\r\n/g, newLine)
fs.writeFileSync( fs.writeFileSync(
path.join(targetPath, `${item.name}.json`), path.join(targetPath, `${item.name}.json`),
JSON.stringify(payload, null, 2), payloadStr,
'utf8', 'utf8',
) )
} }

View File

@ -5,7 +5,7 @@ description: Powered by amazing open source projects.
## About ## About
[shadcn-vue](https://shadcn-vuee.com) is a port of [shadcn/ui](https://ui.shadcn.com) for Vue/Nuxt. It's maintained by [radix-vue](https://github.com/radix-vue). [shadcn-vue](https://shadcn-vue.com) is a port of [shadcn/ui](https://ui.shadcn.com) for Vue/Nuxt. It's maintained by [radix-vue](https://github.com/radix-vue).
## Credits ## Credits

View File

@ -1,6 +1,8 @@
--- ---
title: Calendar title: Calendar
description: A date field component that allows users to enter and edit date. description: A date field component that allows users to enter and edit date.
source: apps/www/src/lib/registry/default/ui/calendar
primitive: https://vcalendar.io/
--- ---
@ -54,5 +56,40 @@ import { Calendar } from '@/components/ui/calendar'
</template> </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

@ -0,0 +1,282 @@
---
title: Carousel
description: A carousel with motion and swipe built using Embla.
source: apps/www/src/lib/registry/default/ui/carousel
primitive: https://www.embla-carousel.com/api
---
<ComponentPreview name="CarouselDemo" />
## About
The carousel component is built using the [Embla Carousel](https://www.embla-carousel.com/) library.
## Installation
```bash
npx shadcn-vue@latest add carousel
```
## Usage
```vue
<script setup lang="ts">
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from '@/components/ui/carousel'
</script>
<template>
<Carousel>
<CarouselContent>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
</template>
```
## Examples
### Sizes
To set the size of the items, you can use the `basis` utility class on the `<CarouselItem />`.
<ComponentPreview name="CarouselSize" />
Example
```vue:line-numbers title="Example" {4-6}
// 33% of the carousel width.
<Carousel>
<CarouselContent>
<CarouselItem class="basis-1/3">...</CarouselItem>
<CarouselItem class="basis-1/3">...</CarouselItem>
<CarouselItem class="basis-1/3">...</CarouselItem>
</CarouselContent>
</Carousel>
```
Responsive
```vue:line-numbers title="Responsive" {4-6}
// 50% on small screens and 33% on larger screens.
<Carousel>
<CarouselContent>
<CarouselItem class="md:basis-1/2 lg:basis-1/3">...</CarouselItem>
<CarouselItem class="md:basis-1/2 lg:basis-1/3">...</CarouselItem>
<CarouselItem class="md:basis-1/2 lg:basis-1/3">...</CarouselItem>
</CarouselContent>
</Carousel>
```
### Spacing
To set the spacing between the items, we use a `pl-[VALUE]` utility on the `<CarouselItem />` and a negative `-ml-[VALUE]` on the `<CarouselContent />`.
<Callout class="mt-6">
**Why:** I tried to use the `gap` property or a `grid` layout on the `
CarouselContent` but it required a lot of math and mental effort to get the
spacing right. I found `pl-[VALUE]` and `-ml-[VALUE]` utilities much easier to
use.
<br/><br/>
You can always adjust this in your own project if you need to.
</Callout>
<ComponentPreview name="CarouselSpacing" />
Example
```vue:line-numbers /-ml-4/ /pl-4/
<template>
<Carousel>
<CarouselContent class="-ml-4">
<CarouselItem class="pl-4">
...
</CarouselItem>
<CarouselItem class="pl-4">
...
</CarouselItem>
<CarouselItem class="pl-4">
...
</CarouselItem>
</CarouselContent>
</Carousel>
</template>
```
Responsive
```vue:line-numbers /-ml-2/ /pl-2/ /md:-ml-4/ /md:pl-4/
<template>
<Carousel>
<CarouselContent class="-ml-2 md:-ml-4">
<CarouselItem class="pl-2 md:pl-4">
...
</CarouselItem>
<CarouselItem class="pl-2 md:pl-4">
...
</CarouselItem>
<CarouselItem class="pl-2 md:pl-4">
...
</CarouselItem>
</CarouselContent>
</Carousel>
</template>
```
### Orientation
Use the `orientation` prop to set the orientation of the carousel.
<ComponentPreview name="CarouselOrientation" />
```vue
<Carousel orientation="vertical | horizontal">
...
</Carousel>
```
## Options
You can pass options to the carousel using the `opts` prop. See the [Embla Carousel docs](https://www.embla-carousel.com/api/options/) for more information.
```vue:line-numbers {3-6}
<template>
<Carousel
:opts="{
align: 'start',
loop: true,
}"
>
<CarouselContent>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
</CarouselContent>
</Carousel>
</template>
```
## API
### Method 1
Use the `@init-api` emit method on `<Carousel />` component to set the instance of the API.
<ComponentPreview name="CarouselApi" />
### Method 2
You can access it through setting a template ref on the `<Carousel />` component.
```vue:line-numbers {2,5,9}
<script setup>
const carouselContainerRef = ref<InstanceType<typeof Carousel> | null>(null)
function accessApi() {
carouselContainerRef.value?.carouselApi.on('select', () => {})
}
</script>
<template>
<Carousel ref="carouselContainerRef">
...
</Carousel>
</template>
```
## Events
You can listen to events using the API. To get the API instance use the `@init-api` emit method on the `<Carousel />` component
```vue:line-numbers {5,7-9,25}
<script setup>
import { nextTick, ref, watch } from 'vue'
import { useCarousel } from '@/components/ui/carousel'
const api = ref<CarouselApi>()
function setApi(val: CarouselApi) {
api.value = val
}
const stop = watch(api, (api) => {
if (!api)
return
// Watch only once or use watchOnce() in @vueuse/core
nextTick(() => stop())
api.on('select', () => {
// Do something on select.
})
})
</script>
<template>
<Carousel @init-api="setApi">
...
</Carousel>
</template>
```
See the [Embla Carousel docs](https://www.embla-carousel.com/api/events/) for more information on using events.
## Slot Props
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:line-numbers {2}
<template>
<Carousel v-slot="{ canScrollNext, canScrollPrev }">
...
<CarouselPrevious v-if="canScrollPrev" />
<CarouselNext v-if="canScrollNext" />
</Carousel>
</template>
```
## Plugins
You can use the `plugins` prop to add plugins to the carousel.
```bash
npm i embla-carousel-autoplay
```
```vue:line-numbers {2,8-10}
<script setup lang="ts">
import Autoplay from 'embla-carousel-autoplay'
</script>
<template>
<Carousel
class="w-full max-w-xs"
:plugins="[Autoplay({
delay: 2000,
})]"
>
...
</Carousel>
</template>
```
<ComponentPreview name="CarouselPlugin" />
See the [Embla Carousel docs](https://www.embla-carousel.com/api/plugins/) for more information on using plugins.

View File

@ -40,6 +40,8 @@ import { Checkbox } from '@/components/ui/checkbox'
### Form ### Form
Please first read `vee-validate` section for [Checkbox and Radio Inputs](https://vee-validate.logaretm.com/v4/examples/checkboxes-and-radio/)
<ComponentPreview name="CheckboxFormSingle" /> <ComponentPreview name="CheckboxFormSingle" />
<ComponentPreview name="CheckboxFormMultiple" /> <ComponentPreview name="CheckboxFormMultiple" />

View File

@ -1,7 +1,6 @@
--- ---
title: Combobox title: Combobox
description: Autocomplete input and command palette with a list of suggestions. description: Autocomplete input and command palette with a list of suggestions.
component: true
--- ---
<ComponentPreview name="ComboboxDemo" /> <ComponentPreview name="ComboboxDemo" />

View File

@ -1,6 +1,7 @@
--- ---
title: Data Table title: Data Table
description: Powerful table and datagrids built using TanStack Table. description: Powerful table and datagrids built using TanStack Table.
primitive: https://tanstack.com/table/v8/docs/guide/introduction
--- ---
@ -24,7 +25,7 @@ We'll start with the basic `<Table />` component and build a complex data table
## Table of Contents ## 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) - [Basic Table](#basic-table)
- [Row Actions](#row-actions) - [Row Actions](#row-actions)
@ -49,6 +50,13 @@ npx shadcn-vue@latest add table
npm install @tanstack/vue-table npm install @tanstack/vue-table
``` ```
## Examples
### Column Pinning
<ComponentPreview name="DataTableColumnPinningDemo" />
## Prerequisites ## Prerequisites
We are going to build a table to show recent payments. Here's what our data looks like: We are going to build a table to show recent payments. Here's what our data looks like:
@ -108,31 +116,23 @@ Let's start by building a basic table.
First, we'll define our columns in the `columns.ts` file. First, we'll define our columns in the `columns.ts` file.
```ts:line-numbers title="components/payments/columns.ts" {1,12-27} ```ts:line-numbers {1,12-27}
import type { ColumnDef } from '@tanstack/vue-table' import { h } from 'vue'
// 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
}
export const columns: ColumnDef<Payment>[] = [ export const columns: ColumnDef<Payment>[] = [
{
accessorKey: 'status',
header: 'Status',
},
{
accessorKey: 'email',
header: 'Email',
},
{ {
accessorKey: 'amount', 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)
}, },
}
] ]
``` ```
@ -148,7 +148,7 @@ formatted, sorted and filtered.
Next, we'll create a `<DataTable />` component to render our table. Next, we'll create a `<DataTable />` component to render our table.
```ts:line-numbers ```vue:line-numbers
<script setup lang="ts" generic="TData, TValue"> <script setup lang="ts" generic="TData, TValue">
import type { ColumnDef } from '@tanstack/vue-table' import type { ColumnDef } from '@tanstack/vue-table'
import { import {
@ -172,8 +172,8 @@ const props = defineProps<{
}>() }>()
const table = useVueTable({ const table = useVueTable({
data: props.data, get data() { return props.data },
columns: props.columns, get columns() { return props.columns },
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
}) })
</script> </script>
@ -224,7 +224,7 @@ const table = useVueTable({
Finally, we'll render our table in our index component. Finally, we'll render our table in our index component.
```ts:line-numbers showLineNumbers{28} ```vue:line-numbers {28}
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { columns } from "./components/columns" import { columns } from "./components/columns"
@ -271,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: 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' import { h } from 'vue'
export const columns: ColumnDef<Payment>[] = [ export const columns: ColumnDef<Payment>[] = [
@ -301,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: ### Add the following into your `DataTableDropDown.vue` component:
```ts:line-numbers ```vue:line-numbers
// DataTableDropDown.vue // DataTableDropDown.vue
<script setup lang="ts"> <script setup lang="ts">
import { MoreHorizontal } from 'lucide-vue-next' import { MoreHorizontal } from 'lucide-vue-next'
@ -379,7 +379,7 @@ Next, we'll add pagination to our table.
### Update `<DataTable>` ### Update `<DataTable>`
```ts:line-numbers showLineNumbers{4,12} ```ts:line-numbers {4,12}
import { import {
FlexRender, FlexRender,
getCoreRowModel, getCoreRowModel,
@ -388,8 +388,8 @@ import {
} from "@tanstack/vue-table" } from "@tanstack/vue-table"
const table = useVueTable({ const table = useVueTable({
data: props.data, get data() { return props.data },
columns: props.columns, get columns() { return props.columns },
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(), getPaginationRowModel: getPaginationRowModel(),
}) })
@ -401,14 +401,13 @@ This will automatically paginate your rows into pages of 10. See the [pagination
We can add pagination controls to our table using the `<Button />` component and the `table.previousPage()`, `table.nextPage()` API methods. We can add pagination controls to our table using the `<Button />` component and the `table.previousPage()`, `table.nextPage()` API methods.
```ts:line-numbers showLineNumbers{3,15,21-39} ```vue:line-numbers {3,15,21-39}
// components/payments/DataTable.vue
<script lang="ts" generic="TData, TValue"> <script lang="ts" generic="TData, TValue">
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
const table = useVueTable({ const table = useVueTable({
data: props.data, get data() { return props.data },
columns: props.columns, get columns() { return props.columns },
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(), getPaginationRowModel: getPaginationRowModel(),
}) })
@ -457,7 +456,7 @@ Let's make the email column sortable.
### Add the following into your `utils` file: ### 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 { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge' import { twMerge } from 'tailwind-merge'
import { camelize, getCurrentInstance, toHandlerKey } from 'vue' import { camelize, getCurrentInstance, toHandlerKey } from 'vue'
@ -481,7 +480,7 @@ The `valueUpdater` function updates a Vue `ref` object's value. It handles both
### Update `<DataTable>` ### 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"> <script setup lang="ts" generic="TData, TValue">
import type { import type {
ColumnDef, ColumnDef,
@ -518,8 +517,8 @@ const props = defineProps<{
const sorting = ref<SortingState>([]) const sorting = ref<SortingState>([])
const table = useVueTable({ const table = useVueTable({
data: props.data, get data() { return props.data },
columns: props.columns, get columns() { return props.columns },
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(), getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(), getSortedRowModel: getSortedRowModel(),
@ -544,7 +543,7 @@ const table = useVueTable({
We can now update the `email` header cell to add sorting controls. 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 // components/payments/columns.ts
import type { import type {
ColumnDef, ColumnDef,
@ -578,7 +577,7 @@ Let's add a search input to filter emails in our table.
### Update `<DataTable>` ### 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"> <script setup lang="ts" generic="TData, TValue">
import type { import type {
ColumnDef, ColumnDef,
@ -620,8 +619,8 @@ const sorting = ref<SortingState>([])
const columnFilters = ref<ColumnFiltersState>([]) const columnFilters = ref<ColumnFiltersState>([])
const table = useVueTable({ const table = useVueTable({
data: props.data, get data() { return props.data },
columns: props.columns, get columns() { return props.columns },
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(), getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(), getSortedRowModel: getSortedRowModel(),
@ -663,7 +662,7 @@ Adding column visibility is fairly simple using `@tanstack/vue-table` visibility
### Update `<DataTable>` ### 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"> <script setup lang="ts" generic="TData, TValue">
import type { import type {
ColumnDef, ColumnDef,
@ -714,8 +713,8 @@ const columnFilters = ref<ColumnFiltersState>([])
const columnVisibility = ref<VisibilityState>({}) const columnVisibility = ref<VisibilityState>({})
const table = useVueTable({ const table = useVueTable({
data: props.data, get data() { return props.data },
columns: props.columns, get columns() { return props.columns },
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(), getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(), getSortedRowModel: getSortedRowModel(),
@ -802,7 +801,7 @@ Next, we're going to add row selection to our table.
### Update column definitions ### Update column definitions
```ts:line-numbers showLineNumbers{3,6-20} ```ts:line-numbers {3,6-20}
import type { ColumnDef } from '@tanstack/vue-table' import type { ColumnDef } from '@tanstack/vue-table'
import { Checkbox } from '@/components/ui/checkbox' import { Checkbox } from '@/components/ui/checkbox'
@ -828,7 +827,7 @@ export const columns: ColumnDef<Payment>[] = [
### Update `<DataTable>` ### Update `<DataTable>`
```ts:line-numbers showLineNumbers{10,22,27} ```vue:line-numbers {10,22,27}
<script setup lang="ts" generic="TData, TValue"> <script setup lang="ts" generic="TData, TValue">
const props = defineProps<{ const props = defineProps<{
columns: ColumnDef<TData, TValue>[] columns: ColumnDef<TData, TValue>[]
@ -841,8 +840,8 @@ const columnVisibility = ref<VisibilityState>({})
const rowSelection = ref({}) const rowSelection = ref({})
const table = useVueTable({ const table = useVueTable({
data: props.data, get data() { return props.data },
columns: props.columns, get columns() { return props.columns },
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(), getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(), getSortedRowModel: getSortedRowModel(),
@ -894,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. Make any column header sortable and hideable.
```ts:line-numbers ```vue:line-numbers
<script setup lang="ts"> <script setup lang="ts">
import type { Column } from '@tanstack/vue-table' import type { Column } from '@tanstack/vue-table'
import { type Task } from '../data/schema' import { type Task } from '../data/schema'
@ -987,7 +986,7 @@ export const columns = [
Add pagination controls to your table including page size and selection count. Add pagination controls to your table including page size and selection count.
```ts:line-numbers ```vue:line-numbers
<script setup lang="ts"> <script setup lang="ts">
import { type Table } from '@tanstack/vue-table' import { type Table } from '@tanstack/vue-table'
import { type Task } from '../data/schema' import { type Task } from '../data/schema'
@ -1092,8 +1091,7 @@ defineProps<DataTablePaginationProps>()
A component to toggle column visibility. A component to toggle column visibility.
```ts:line-numbers ```vue:line-numbers
<script setup lang="ts"> <script setup lang="ts">
import type { Table } from '@tanstack/vue-table' import type { Table } from '@tanstack/vue-table'
import { computed } from 'vue' import { computed } from 'vue'

View File

@ -64,10 +64,18 @@ const date = ref<Date>()
<ComponentPreview name="DatePickerWithRange" /> <ComponentPreview name="DatePickerWithRange" />
### Date Time Picker
<ComponentPreview name="DateTimePickerDemo" />
### With Presets ### With Presets
<ComponentPreview name="DatePickerWithPresets" /> <ComponentPreview name="DatePickerWithPresets" />
### With Slot
<ComponentPreview name="RangePickerWithSlot" />
### Form ### Form
<ComponentPreview name="DatePickerForm" /> <ComponentPreview name="DatePickerForm" />

View File

@ -1,6 +1,7 @@
--- ---
title: VeeValidate title: VeeValidate
description: Building forms with VeeValidate and Zod. description: Building forms with VeeValidate and Zod.
primitive: https://vee-validate.logaretm.com/v4/guide/overview/
--- ---
Forms are tricky. They are one of the most common things you'll build in a web application, but also one of the most complex. Forms are tricky. They are one of the most common things you'll build in a web application, but also one of the most complex.
@ -158,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. `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"> <script setup lang="ts">
import { toTypedSchema } from '@vee-validate/zod' import { toTypedSchema } from '@vee-validate/zod'
import * as z from 'zod' import * as z from 'zod'
@ -172,13 +173,13 @@ const formSchema = toTypedSchema(z.object({
### Define a form ### Define a form
Use the `useForm` composable from `vee-validate` or use `<Form />` component to create a from. Use the `useForm` composable from `vee-validate` or use `<Form />` component to create a form.
<TabPreview name="Composition" :names="['Composition', 'Component']"> <TabPreview name="Composition" :names="['Composition', 'Component']">
<template #Composition> <template #Composition>
```vue showLineNumbers {2,19-21} ```vue:line-numbers {2,19-21}
<script setup lang="ts"> <script setup lang="ts">
import { useForm } from 'vee-validate' import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/zod' import { toTypedSchema } from '@vee-validate/zod'
@ -217,7 +218,7 @@ const onSubmit = form.handleSubmit((values) => {
<template #Component> <template #Component>
```vue showLineNumbers {5,24-26} ```vue:line-numbers {5,24-26}
<script setup lang="ts"> <script setup lang="ts">
import { toTypedSchema } from '@vee-validate/zod' import { toTypedSchema } from '@vee-validate/zod'
import * as z from 'zod' import * as z from 'zod'
@ -255,7 +256,7 @@ function onSubmit(values) {
Based on last step we can either use `<Form />` component or `useForm` composable Based on last step we can either use `<Form />` component or `useForm` composable
`useForm` is recommended cause values are typed automatically `useForm` is recommended cause values are typed automatically
```vue showLineNumbers {2} ```vue:line-numbers {2}
<script setup lang="ts"> <script setup lang="ts">
import { useForm } from 'vee-validate' import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/zod' import { toTypedSchema } from '@vee-validate/zod'

View File

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

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

@ -40,4 +40,6 @@ import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
### Form ### Form
Please first read `vee-validate` section for [Checkbox and Radio Inputs](https://vee-validate.logaretm.com/v4/examples/checkboxes-and-radio/)
<ComponentPreview name="RadioGroupForm" /> <ComponentPreview name="RadioGroupForm" />

View File

@ -1,7 +1,7 @@
--- ---
title: Sheet title: Sheet
description: Extends the Dialog component to display content that complements the main content of the screen. description: Extends the Dialog component to display content that complements the main content of the screen.
source: apps/www/src/lib/registry/default/ui/dialog source: apps/www/src/lib/registry/default/ui/sheet
primitive: https://www.radix-vue.com/components/dialog.html primitive: https://www.radix-vue.com/components/dialog.html
--- ---
@ -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: You can adjust the size of the sheet using CSS classes:
```vue:line-numbers showLineNumbers{4} ```vue:line-numbers {4}
<template> <template>
<Sheet> <Sheet>
<SheetTrigger>Open</SheetTrigger> <SheetTrigger>Open</SheetTrigger>

View File

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

View File

@ -0,0 +1,93 @@
---
title: Toggle Group
description: A set of two-state buttons that can be toggled on or off.
source: apps/www/src/lib/registry/default/ui/toggle-group
primitive: https://www.radix-vue.com/components/toggle-group.html
---
<ComponentPreview name="ToggleGroupDemo" />
## Installation
<TabPreview name="CLI">
<template #CLI>
```bash
npx shadcn-vue@latest add toggle-group
```
</template>
<template #Manual>
<Steps>
### Install the following dependencies:
```bash
npm install radix-vue
```
### Copy and paste the following code into your project
<<< @/lib/registry/default/ui/toggle-group/ToggleGroup.vue
</Steps>
</template>
</TabPreview>
## Usage
```vue
<script setup lang="ts">
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'
</script>
<template>
<ToggleGroup type="single">
<ToggleGroupItem value="a">
A
</ToggleGroupItem>
<ToggleGroupItem value="b">
B
</ToggleGroupItem>
<ToggleGroupItem value="c">
C
</ToggleGroupItem>
</ToggleGroup>
</template>
```
## Examples
### Default
<ComponentPreview name="ToggleGroupDemo" />
### Outline
<ComponentPreview name="ToggleGroupOutlineDemo" />
### Single
<ComponentPreview name="ToggleGroupSingleDemo" />
### Small
<ComponentPreview name="ToggleGroupSmallDemo" />
### Large
<ComponentPreview name="ToggleGroupLargeDemo" />
### Disabled
<ComponentPreview name="ToggleGroupDisabledDemo" />

View File

@ -17,7 +17,7 @@ npm create astro@latest
You will be asked a few questions to configure your project: You will be asked a few questions to configure your project:
```txt showLineNumbers ```txt:line-numbers
- Where should we create your new project? - Where should we create your new project?
./your-app-name ./your-app-name
- How would you like to start your new project? - How would you like to start your new project?
@ -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: Add the code below to the tsconfig.json file to resolve paths:
```json {2-7} showLineNumbers ```json:line-numbers {2-7}
{ {
"compilerOptions": { "compilerOptions": {
"baseUrl": ".", "baseUrl": ".",
@ -99,7 +99,7 @@ npx shadcn-vue@latest init
You will be asked a few questions to configure `components.json`: You will be asked a few questions to configure `components.json`:
```txt showLineNumbers ```txt:line-numbers
Would you like to use TypeScript (recommended)? no / yes Would you like to use TypeScript (recommended)? no / yes
Which framework are you using? Astro Which framework are you using? Astro
Which style would you like to use? Default Which style would you like to use? Default
@ -116,15 +116,17 @@ Write configuration to components.json. Proceed? > Y/n
Import the `globals.css` file in the `src/index.astro` file: Import the `globals.css` file in the `src/index.astro` file:
```ts {2} showLineNumbers ```ts:line-numbers {2}
---
import '@/styles/globals.css' import '@/styles/globals.css'
---
``` ```
### Update astro tailwind config ### 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`. 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({ export default defineConfig({
integrations: [ integrations: [
tailwind({ 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: 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" 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`: 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 Would you like to use TypeScript (recommended)? no / yes
Which framework are you using? Vite / Nuxt / Laravel Which framework are you using? Vite / Nuxt / Laravel
Which style would you like to use? Default Which style would you like to use? Default

View File

@ -7,7 +7,7 @@ description: Install and configure Nuxt.
### Create project ### Create project
Start by creating a new Nuxt project using `create-next-app`: Start by creating a new Nuxt project using `create-nuxt-app`:
```bash ```bash
npx nuxi@latest init my-app npx nuxi@latest init my-app
@ -26,12 +26,128 @@ npm install -D typescript
npm install -D @nuxtjs/tailwindcss npm install -D @nuxtjs/tailwindcss
``` ```
### Install `shadcn-nuxt` module (New ✨) ### Add `Nuxt` module
<br>
<TabsMarkdown>
<TabMarkdown title="shadcn-nuxt">
Install the package below.
```bash ```bash
npm install -D shadcn-nuxt npm install -D shadcn-nuxt
``` ```
</TabMarkdown>
<TabMarkdown title="manual">
Add the following code to `modules/shadcn.ts`.
```bash
import {
defineNuxtModule,
addComponent,
addComponentsDir,
tryResolveModule,
} from 'nuxt/kit';
export interface ShadcnVueOptions {
/**
* Prefix for all the imported component
*/
prefix: string;
/**
* Directory that the component lives in.
* @default "~/components/ui"
*/
componentDir: string;
}
export default defineNuxtModule<ShadcnVueOptions>({
defaults: {
prefix: 'Ui',
componentDir: '~/components/ui',
},
meta: {
name: 'ShadcnVue',
configKey: 'shadcn',
version: '0.0.1',
compatibility: {
nuxt: '^3.9.0',
bridge: false,
},
},
async setup({ componentDir, prefix }) {
const isVeeValidateExist = await tryResolveModule('vee-validate');
addComponentsDir(
{
path: componentDir,
extensions: ['.vue'],
prefix,
pathPrefix: false,
},
{
prepend: true,
}
);
if (isVeeValidateExist !== undefined) {
addComponent({
filePath: 'vee-validate',
export: 'Form',
name: `${prefix}Form`,
priority: 999,
});
addComponent({
filePath: 'vee-validate',
export: 'Field',
name: `${prefix}FormField`,
priority: 999,
});
}
addComponent({
filePath: 'radix-vue',
export: 'PaginationRoot',
name: `${prefix}Pagination`,
priority: 999,
});
addComponent({
filePath: 'radix-vue',
export: 'PaginationList',
name: `${prefix}PaginationList`,
priority: 999,
});
addComponent({
filePath: 'radix-vue',
export: 'PaginationListItem',
name: `${prefix}PaginationListItem`,
priority: 999,
});
},
});
declare module '@nuxt/schema' {
interface NuxtConfig {
shadcn?: ShadcnVueOptions;
}
interface NuxtOptions {
shadcn?: ShadcnVueOptions;
}
}
```
</TabMarkdown>
</TabsMarkdown>
### Configure `nuxt.config.ts` ### Configure `nuxt.config.ts`
@ -64,7 +180,7 @@ npx shadcn-vue@latest init
You will be asked a few questions to configure `components.json`: You will be asked a few questions to configure `components.json`:
```txt showLineNumbers ```txt:line-numbers
Would you like to use TypeScript (recommended)? no / yes Would you like to use TypeScript (recommended)? no / yes
Which framework are you using? Vite / Nuxt / Laravel Which framework are you using? Vite / Nuxt / Laravel
Which style would you like to use? Default Which style would you like to use? Default

View File

@ -19,14 +19,64 @@ npm create vite@latest my-vue-app -- --template vue-ts
### Add Tailwind and its configuration ### 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
<TabsMarkdown>
<TabMarkdown title="vite.config.ts">
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 ```bash
npm install -D tailwindcss postcss autoprefixer npm install -D tailwindcss autoprefixer
npx tailwindcss init -p
``` ```
#### `vite.config`
```typescript {5,6,10-14}
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({
plugins: [vue()],
css: {
postcss: {
plugins: [tailwind(), autoprefixer()],
},
},
resolve: {...}
})
```
</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 ### Edit tsconfig.json
Add the code below to the compilerOptions of your tsconfig.json so your app can resolve paths without error Add the code below to the compilerOptions of your tsconfig.json so your app can resolve paths without error
@ -42,6 +92,11 @@ 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 Add the code below to the vite.config.ts so your app can resolve paths without error
```bash
# (so you can import "path" without error)
npm i -D @types/node
```
```typescript ```typescript
import path from "path" import path from "path"
import vue from "@vitejs/plugin-vue" import vue from "@vitejs/plugin-vue"
@ -69,7 +124,7 @@ npx shadcn-vue@latest init
You will be asked a few questions to configure `components.json`: You will be asked a few questions to configure `components.json`:
```txt showLineNumbers ```txt:line-numbers
Would you like to use TypeScript (recommended)? no / yes Would you like to use TypeScript (recommended)? no / yes
Which framework are you using? Vite / Nuxt / Laravel Which framework are you using? Vite / Nuxt / Laravel
Which style would you like to use? Default Which style would you like to use? Default

View File

@ -26,7 +26,7 @@ import { Separator } from '@/lib/registry/new-york/ui/separator'
<template> <template>
<Card> <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"> <div class="space-y-1">
<CardTitle>shadcn/ui</CardTitle> <CardTitle>shadcn/ui</CardTitle>
<CardDescription> <CardDescription>
@ -39,7 +39,7 @@ import { Separator } from '@/lib/registry/new-york/ui/separator'
<StarIcon class="mr-2 h-4 w-4" /> <StarIcon class="mr-2 h-4 w-4" />
Star Star
</Button> </Button>
<Separator orientation="vertical" class="h-[20px]" /> <Separator orientation="vertical" class="h-5" />
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger as-child> <DropdownMenuTrigger as-child>
<Button variant="secondary" class="px-2 shadow-none"> <Button variant="secondary" class="px-2 shadow-none">

View File

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

View File

@ -95,7 +95,7 @@ async function onSubmit(values: any) {
</FormField> </FormField>
<FormField v-slot="{ componentField, value }" name="dob"> <FormField v-slot="{ componentField, value }" name="dob">
<FormItem> <FormItem class="flex flex-col">
<FormLabel>Date of birth</FormLabel> <FormLabel>Date of birth</FormLabel>
<Popover> <Popover>
<PopoverTrigger as-child> <PopoverTrigger as-child>
@ -123,7 +123,7 @@ async function onSubmit(values: any) {
</FormField> </FormField>
<FormField v-slot="{ value }" name="language"> <FormField v-slot="{ value }" name="language">
<FormItem> <FormItem class="flex flex-col">
<FormLabel>Language</FormLabel> <FormLabel>Language</FormLabel>
<Popover v-model:open="open"> <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="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-sm bg-[#ecedef] p-2">
<div class="space-y-2 rounded-md bg-white p-2 shadow-sm"> <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 class="h-2 w-[100px] rounded-lg bg-[#ecedef]" />
</div> </div>
<div class="flex items-center space-x-2 rounded-md bg-white p-2 shadow-sm"> <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="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-sm bg-slate-950 p-2">
<div class="space-y-2 rounded-md bg-slate-800 p-2 shadow-sm"> <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 class="h-2 w-[100px] rounded-lg bg-slate-400" />
</div> </div>
<div class="flex items-center space-x-2 rounded-md bg-slate-800 p-2 shadow-sm"> <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 /> <Separator />
<Tabs default-value="complete" class="flex-1"> <Tabs default-value="complete" class="flex-1">
<div class="container h-full py-6"> <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="hidden flex-col space-y-4 sm:flex md:order-2">
<div class="grid gap-2"> <div class="grid gap-2">
<HoverCard :open-delay="200"> <HoverCard :open-delay="200">

View File

@ -42,8 +42,8 @@ const columnVisibility = ref<VisibilityState>({})
const rowSelection = ref({}) const rowSelection = ref({})
const table = useVueTable({ const table = useVueTable({
data: props.data, get data() { return props.data },
columns: props.columns, get columns() { return props.columns },
state: { state: {
get sorting() { return sorting.value }, get sorting() { return sorting.value },
get columnFilters() { return columnFilters.value }, get columnFilters() { return columnFilters.value },

View File

@ -12,16 +12,16 @@ export const columns: ColumnDef<Task>[] = [
{ {
id: 'select', id: 'select',
header: ({ table }) => h(Checkbox, 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, 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, enableSorting: false,
enableHiding: false, enableHiding: false,
}, },
{ {
accessorKey: 'id', accessorKey: 'id',
header: ({ column }) => h(DataTableColumnHeader, { column, title: 'Task' }), 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, enableSorting: false,
enableHiding: false, enableHiding: false,
}, },

View File

@ -4,8 +4,8 @@ import { Button } from '@/lib/registry/default/ui/button'
<template> <template>
<Button as-child> <Button as-child>
<NuxtLink to="/login"> <a href="/login">
Login Login
</NuxtLink> </a>
</Button> </Button>
</template> </template>

View File

@ -51,7 +51,7 @@ const notifications = [
<div> <div>
<div <div
v-for="(notification, index) in notifications" :key="index" 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" /> <span class="flex h-2 w-2 translate-y-1 rounded-full bg-sky-500" />
<div class="space-y-1"> <div class="space-y-1">

View File

@ -1,5 +1,12 @@
<script setup lang='ts'> <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 { import {
Select, Select,
SelectContent, SelectContent,
@ -22,11 +29,11 @@ import { Button } from '@/lib/registry/default/ui/button'
<form> <form>
<div class="grid items-center w-full gap-4"> <div class="grid items-center w-full gap-4">
<div class="flex flex-col space-y-1.5"> <div class="flex flex-col space-y-1.5">
<Label html-for="name">Name</Label> <Label for="name">Name</Label>
<Input id="name" placeholder="Name of your project" /> <Input id="name" placeholder="Name of your project" />
</div> </div>
<div class="flex flex-col space-y-1.5"> <div class="flex flex-col space-y-1.5">
<Label html-for="framework">Framework</Label> <Label for="framework">Framework</Label>
<Select> <Select>
<SelectTrigger id="framework"> <SelectTrigger id="framework">
<SelectValue placeholder="Select" /> <SelectValue placeholder="Select" />

View File

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

View File

@ -14,7 +14,7 @@ import {
useVueTable, useVueTable,
} from '@tanstack/vue-table' } from '@tanstack/vue-table'
import { h, ref } from 'vue' 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 DropdownAction from '../DataTableDemoColumn.vue'
import { Button } from '@/lib/registry/default/ui/button' import { Button } from '@/lib/registry/default/ui/button'
@ -104,7 +104,7 @@ const columns: ColumnDef<Payment>[] = [
return h(Button, { return h(Button, {
variant: 'ghost', variant: 'ghost',
onClick: () => column.toggleSorting(column.getIsSorted() === 'asc'), 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')), cell: ({ row }) => h('div', { class: 'lowercase' }, row.getValue('email')),
}, },
@ -179,7 +179,7 @@ const table = useVueTable({
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger as-child> <DropdownMenuTrigger as-child>
<Button variant="outline" class="ml-auto"> <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> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">

View File

@ -0,0 +1,51 @@
<script setup lang="ts">
import { ref } from 'vue'
import { watchOnce } from '@vueuse/core'
import type { CarouselApi } from '@/lib/registry/default/ui/carousel'
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from '@/lib/registry/default/ui/carousel'
import { Card, CardContent } from '@/lib/registry/default/ui/card'
const api = ref<CarouselApi>()
const totalCount = ref(0)
const current = ref(0)
function setApi(val: CarouselApi) {
api.value = val
}
watchOnce(api, (api) => {
if (!api)
return
totalCount.value = api.scrollSnapList().length
current.value = api.selectedScrollSnap() + 1
api.on('select', () => {
current.value = api.selectedScrollSnap() + 1
})
})
</script>
<template>
<div class="w-full sm:w-auto">
<Carousel class="relative w-full max-w-xs" @init-api="setApi">
<CarouselContent>
<CarouselItem v-for="(_, index) in 5" :key="index">
<div class="p-1">
<Card>
<CardContent class="flex aspect-square items-center justify-center p-6">
<span class="text-4xl font-semibold">{{ index + 1 }}</span>
</CardContent>
</Card>
</div>
</CarouselItem>
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
<div class="py-2 text-center text-sm text-muted-foreground">
Slide {{ current }} of {{ totalCount }}
</div>
</div>
</template>

View File

@ -0,0 +1,22 @@
<script setup lang="ts">
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from '@/lib/registry/default/ui/carousel'
import { Card, CardContent } from '@/lib/registry/default/ui/card'
</script>
<template>
<Carousel class="relative w-full max-w-xs">
<CarouselContent>
<CarouselItem v-for="(_, index) in 5" :key="index">
<div class="p-1">
<Card>
<CardContent class="flex aspect-square items-center justify-center p-6">
<span class="text-4xl font-semibold">{{ index + 1 }}</span>
</CardContent>
</Card>
</div>
</CarouselItem>
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
</template>

View File

@ -0,0 +1,28 @@
<script setup lang="ts">
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from '@/lib/registry/default/ui/carousel'
import { Card, CardContent } from '@/lib/registry/default/ui/card'
</script>
<template>
<Carousel
orientation="vertical"
class="relative w-full max-w-xsw-full max-w-xs"
:opts="{
align: 'start',
}"
>
<CarouselContent class="-mt-1 h-[200px]">
<CarouselItem v-for="(_, index) in 5" :key="index" class="p-1 md:basis-1/2">
<div class="p-1">
<Card>
<CardContent class="flex items-center justify-center p-6">
<span class="text-3xl font-semibold">{{ index + 1 }}</span>
</CardContent>
</Card>
</div>
</CarouselItem>
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
</template>

View File

@ -0,0 +1,34 @@
<script setup lang="ts">
import Autoplay from 'embla-carousel-autoplay'
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from '@/lib/registry/default/ui/carousel'
import { Card, CardContent } from '@/lib/registry/default/ui/card'
const plugin = Autoplay({
delay: 2000,
stopOnMouseEnter: true,
stopOnInteraction: false,
})
</script>
<template>
<Carousel
class="relative w-full max-w-xs"
:plugins="[plugin]"
@mouseenter="plugin.stop"
@mouseleave="[plugin.reset(), plugin.play(), console.log('Runing')];"
>
<CarouselContent>
<CarouselItem v-for="(_, index) in 5" :key="index">
<div class="p-1">
<Card>
<CardContent class="flex aspect-square items-center justify-center p-6">
<span class="text-4xl font-semibold">{{ index + 1 }}</span>
</CardContent>
</Card>
</div>
</CarouselItem>
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
</template>

View File

@ -0,0 +1,27 @@
<script setup lang="ts">
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from '@/lib/registry/default/ui/carousel'
import { Card, CardContent } from '@/lib/registry/default/ui/card'
</script>
<template>
<Carousel
class="relative w-full max-w-sm"
:opts="{
align: 'start',
}"
>
<CarouselContent>
<CarouselItem v-for="(_, index) in 5" :key="index" class="md:basis-1/2 lg:basis-1/3">
<div class="p-1">
<Card>
<CardContent class="flex aspect-square items-center justify-center p-6">
<span class="text-3xl font-semibold">{{ index + 1 }}</span>
</CardContent>
</Card>
</div>
</CarouselItem>
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
</template>

View File

@ -0,0 +1,27 @@
<script setup lang="ts">
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from '@/lib/registry/default/ui/carousel'
import { Card, CardContent } from '@/lib/registry/default/ui/card'
</script>
<template>
<Carousel
class="relative w-full max-w-sm"
:opts="{
align: 'start',
}"
>
<CarouselContent class="-ml-1">
<CarouselItem v-for="(_, index) in 5" :key="index" class="pl-1 md:basis-1/2 lg:basis-1/3">
<div class="p-1">
<Card>
<CardContent class="flex aspect-square items-center justify-center p-6">
<span class="text-2xl font-semibold">{{ index + 1 }}</span>
</CardContent>
</Card>
</div>
</CarouselItem>
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
</template>

View File

@ -77,16 +77,12 @@ const onSubmit = handleSubmit((values) => {
</FormDescription> </FormDescription>
</div> </div>
<FormField v-for="item in items" v-slot="{ value, handleChange }" :key="item.id" name="items"> <FormField v-for="item in items" v-slot="{ value, handleChange }" :key="item.id" type="checkbox" :value="item.id" :unchecked-value="false" name="items">
<FormItem :key="item.id" class="flex flex-row items-start space-x-3 space-y-0"> <FormItem class="flex flex-row items-start space-x-3 space-y-0">
<FormControl> <FormControl>
<Checkbox <Checkbox
:checked="value.includes(item.id)" :checked="value.includes(item.id)"
@update:checked="(checked) => { @update:checked="handleChange"
if (Array.isArray(value)) {
handleChange(checked ? [...value, item.id] : value.filter(id => id !== item.id))
}
}"
/> />
</FormControl> </FormControl>
<FormLabel class="font-normal"> <FormLabel class="font-normal">

View File

@ -37,7 +37,7 @@ const onSubmit = handleSubmit((values) => {
<template> <template>
<form class="space-y-6" @submit="onSubmit"> <form class="space-y-6" @submit="onSubmit">
<FormField v-slot="{ value, handleChange }" name="mobile"> <FormField v-slot="{ value, handleChange }" type="checkbox" name="mobile">
<FormItem class="flex flex-row items-start gap-x-3 space-y-0 rounded-md border p-4"> <FormItem class="flex flex-row items-start gap-x-3 space-y-0 rounded-md border p-4">
<FormControl> <FormControl>
<Checkbox :checked="value" @update:checked="handleChange" /> <Checkbox :checked="value" @update:checked="handleChange" />

View File

@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'
import { Check, ChevronsUpDown } from 'lucide-vue-next' import { Check, ChevronsUpDown } from 'lucide-vue-next'
import { ref } from 'vue'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { Button } from '@/lib/registry/default/ui/button' import { Button } from '@/lib/registry/default/ui/button'
import { import {
@ -27,9 +27,9 @@ const frameworks = [
] ]
const open = ref(false) const open = ref(false)
const value = ref<typeof frameworks[number]>() const value = ref<string>('')
const filterFunction = (list: typeof frameworks, search: string) => list.filter(i => i.value.toLowerCase().includes(search.toLowerCase())) // const filterFunction = (list: typeof frameworks, search: string) => list.filter(i => i.value.toLowerCase().includes(search.toLowerCase()))
</script> </script>
<template> <template>
@ -41,33 +41,36 @@ const filterFunction = (list: typeof frameworks, search: string) => list.filter(
:aria-expanded="open" :aria-expanded="open"
class="w-[200px] justify-between" class="w-[200px] justify-between"
> >
{{ value ? value.label : 'Select framework...' }} {{ value
? frameworks.find((framework) => framework.value === value)?.label
: "Select framework..." }}
<ChevronsUpDown class="ml-2 h-4 w-4 shrink-0 opacity-50" /> <ChevronsUpDown class="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent class="w-[200px] p-0"> <PopoverContent class="w-[200px] p-0">
<Command :filter-function="filterFunction"> <Command>
<CommandInput placeholder="Search framework..." /> <CommandInput class="h-9" placeholder="Search framework..." />
<CommandEmpty>No framework found.</CommandEmpty> <CommandEmpty>No framework found.</CommandEmpty>
<CommandList> <CommandList>
<CommandGroup> <CommandGroup>
<CommandItem <CommandItem
v-for="framework in frameworks" v-for="framework in frameworks"
:key="framework.value" :key="framework.value"
:value="framework" :value="framework.value"
@select="(ev) => { @select="(ev) => {
if (typeof ev.detail.value === 'string') {
value = ev.detail.value value = ev.detail.value
console.log(ev) }
open = false open = false
}" }"
> >
{{ framework.label }}
<Check <Check
:class="cn( :class="cn(
'mr-2 h-4 w-4', 'ml-auto h-4 w-4',
value?.value === framework.value ? 'opacity-100' : 'opacity-0', value === framework.value ? 'opacity-100' : 'opacity-0',
)" )"
/> />
{{ framework.label }}
</CommandItem> </CommandItem>
</CommandGroup> </CommandGroup>
</CommandList> </CommandList>

View File

@ -14,17 +14,22 @@ import {
const open = ref(false) const open = ref(false)
const keys = useMagicKeys() const { Meta_J, Ctrl_J } = useMagicKeys({
const CmdJ = keys['Cmd+J'] passive: false,
onEventFired(e) {
if (e.key === 'j' && (e.metaKey || e.ctrlKey))
e.preventDefault()
},
})
watch([Meta_J, Ctrl_J], (v) => {
if (v[0] || v[1])
handleOpenChange()
})
function handleOpenChange() { function handleOpenChange() {
open.value = !open.value open.value = !open.value
} }
watch(CmdJ, (v) => {
if (v)
handleOpenChange()
})
</script> </script>
<template> <template>
@ -37,7 +42,7 @@ watch(CmdJ, (v) => {
<span class="text-xs"></span>J <span class="text-xs"></span>J
</kbd> </kbd>
</p> </p>
<CommandDialog :open="open" :on-open-change="handleOpenChange"> <CommandDialog v-model:open="open">
<CommandInput placeholder="Type a command or search..." /> <CommandInput placeholder="Type a command or search..." />
<CommandList> <CommandList>
<CommandEmpty>No results found.</CommandEmpty> <CommandEmpty>No results found.</CommandEmpty>

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> </span>
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent class="w-auto p-0" align="start" :avoid-collisions="true"> <PopoverContent class="w-auto p-0" align="start">
<Calendar <Calendar
v-model.range="date" v-model.range="date"
:columns="2" :columns="2"

View File

@ -0,0 +1,36 @@
<script setup lang="ts">
import { format } from 'date-fns'
import { Calendar as CalendarIcon } from 'lucide-vue-next'
import { ref } from 'vue'
import { cn } from '@/lib/utils'
import { Button } from '@/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<Date>()
</script>
<template>
<Popover>
<PopoverTrigger as-child>
<Button
:variant="'outline'"
:class="cn(
'w-[280px] justify-start text-left font-normal',
!date && 'text-muted-foreground',
)"
>
<CalendarIcon class="mr-2 h-4 w-4" />
<span>{{ date ? format(date, 'PPP - hh:mm') : "Pick a date" }}</span>
</Button>
</PopoverTrigger>
<PopoverContent class="w-auto p-0">
<Calendar v-model="date" mode="datetime" />
</PopoverContent>
</Popover>
</template>

View File

@ -31,7 +31,7 @@ import { Label } from '@/lib/registry/default/ui/label'
</DialogHeader> </DialogHeader>
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<div class="grid flex-1 gap-2"> <div class="grid flex-1 gap-2">
<Label html-for="link" class="sr-only"> <Label for="link" class="sr-only">
Link Link
</Label> </Label>
<Input <Input

View File

@ -55,7 +55,7 @@ const components: { title: string; href: string; description: string }[] = [
<NavigationMenuItem> <NavigationMenuItem>
<NavigationMenuTrigger>Getting started</NavigationMenuTrigger> <NavigationMenuTrigger>Getting started</NavigationMenuTrigger>
<NavigationMenuContent> <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"> <li class="row-span-3">
<NavigationMenuLink as-child> <NavigationMenuLink as-child>
<a <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> </script>
<template> <template>
<Progress v-model="progress" class="w-[60%]" /> <Progress v-model="progress" class="w-3/5" />
</template> </template>

View File

@ -35,7 +35,7 @@ const onSubmit = handleSubmit((values) => {
<template> <template>
<form class="w-2/3 space-y-6" @submit="onSubmit"> <form class="w-2/3 space-y-6" @submit="onSubmit">
<FormField v-slot="{ componentField }" name="type"> <FormField v-slot="{ componentField }" type="radio" name="type">
<FormItem class="space-y-3"> <FormItem class="space-y-3">
<FormLabel>Notify me about...</FormLabel> <FormLabel>Notify me about...</FormLabel>

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" v-model="modelValue"
:max="100" :max="100"
:step="1" :step="1"
:class="cn('w-[60%]', $attrs.class ?? '')" :class="cn('w-3/5', $attrs.class ?? '')"
/> />
</template> </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 calander
</Button>
</template>

View File

@ -0,0 +1,19 @@
<script setup lang="ts">
import { Bold, Italic, Underline } from 'lucide-vue-next'
import { ToggleGroup, ToggleGroupItem } from '@/lib/registry/default/ui/toggle-group'
</script>
<template>
<ToggleGroup type="multiple">
<ToggleGroupItem value="bold" aria-label="Toggle bold">
<Bold class="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="italic" aria-label="Toggle italic">
<Italic class="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="underline" aria-label="Toggle underline">
<Underline class="h-4 w-4" />
</ToggleGroupItem>
</ToggleGroup>
</template>

View File

@ -0,0 +1,19 @@
<script setup lang="ts">
import { Bold, Italic, Underline } from 'lucide-vue-next'
import { ToggleGroup, ToggleGroupItem } from '@/lib/registry/default/ui/toggle-group'
</script>
<template>
<ToggleGroup type="multiple" disabled>
<ToggleGroupItem value="bold" aria-label="Toggle bold">
<Bold class="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="italic" aria-label="Toggle italic">
<Italic class="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="underline" aria-label="Toggle underline">
<Underline class="h-4 w-4" />
</ToggleGroupItem>
</ToggleGroup>
</template>

View File

@ -0,0 +1,19 @@
<script setup lang="ts">
import { Bold, Italic, Underline } from 'lucide-vue-next'
import { ToggleGroup, ToggleGroupItem } from '@/lib/registry/default/ui/toggle-group'
</script>
<template>
<ToggleGroup type="multiple" size="lg">
<ToggleGroupItem value="bold" aria-label="Toggle bold">
<Bold class="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="italic" aria-label="Toggle italic">
<Italic class="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="underline" aria-label="Toggle underline">
<Underline class="h-4 w-4" />
</ToggleGroupItem>
</ToggleGroup>
</template>

View File

@ -0,0 +1,19 @@
<script setup lang="ts">
import { Bold, Italic, Underline } from 'lucide-vue-next'
import { ToggleGroup, ToggleGroupItem } from '@/lib/registry/default/ui/toggle-group'
</script>
<template>
<ToggleGroup type="multiple" variant="outline">
<ToggleGroupItem value="bold" aria-label="Toggle bold">
<Bold class="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="italic" aria-label="Toggle italic">
<Italic class="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="underline" aria-label="Toggle underline">
<Underline class="h-4 w-4" />
</ToggleGroupItem>
</ToggleGroup>
</template>

View File

@ -0,0 +1,19 @@
<script setup lang="ts">
import { Bold, Italic, Underline } from 'lucide-vue-next'
import { ToggleGroup, ToggleGroupItem } from '@/lib/registry/default/ui/toggle-group'
</script>
<template>
<ToggleGroup type="single">
<ToggleGroupItem value="bold" aria-label="Toggle bold">
<Bold class="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="italic" aria-label="Toggle italic">
<Italic class="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="underline" aria-label="Toggle underline">
<Underline class="h-4 w-4" />
</ToggleGroupItem>
</ToggleGroup>
</template>

View File

@ -0,0 +1,19 @@
<script setup lang="ts">
import { Bold, Italic, Underline } from 'lucide-vue-next'
import { ToggleGroup, ToggleGroupItem } from '@/lib/registry/default/ui/toggle-group'
</script>
<template>
<ToggleGroup type="multiple" size="sm">
<ToggleGroupItem value="bold" aria-label="Toggle bold">
<Bold class="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="italic" aria-label="Toggle italic">
<Italic class="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem value="underline" aria-label="Toggle underline">
<Underline class="h-4 w-4" />
</ToggleGroupItem>
</ToggleGroup>
</template>

View File

@ -1 +0,0 @@
export { default as AccordionDemo } from './AccordionDemo.vue'

View File

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

View File

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

View File

@ -1,14 +1,23 @@
<script setup lang="ts"> <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' 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> </script>
<template> <template>
<AccordionItem <AccordionItem
v-bind="props" v-bind="forwardedProps"
:class="cn('border-b', props.class ?? '')" :class="cn('border-b', props.class)"
> >
<slot /> <slot />
</AccordionItem> </AccordionItem>

View File

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

View File

@ -1,13 +1,20 @@
<script setup lang="ts"> <script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import { AlertDialogAction, type AlertDialogActionProps } from 'radix-vue' import { AlertDialogAction, type AlertDialogActionProps } from 'radix-vue'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { buttonVariants } from '@/lib/registry/default/ui/button' 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> </script>
<template> <template>
<AlertDialogAction v-bind="props" :class="cn(buttonVariants(), $attrs.class ?? '')"> <AlertDialogAction v-bind="delegatedProps" :class="cn(buttonVariants(), props.class)">
<slot /> <slot />
</AlertDialogAction> </AlertDialogAction>
</template> </template>

View File

@ -1,13 +1,20 @@
<script setup lang="ts"> <script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import { AlertDialogCancel, type AlertDialogCancelProps } from 'radix-vue' import { AlertDialogCancel, type AlertDialogCancelProps } from 'radix-vue'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { buttonVariants } from '@/lib/registry/default/ui/button' 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> </script>
<template> <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 /> <slot />
</AlertDialogCancel> </AlertDialogCancel>
</template> </template>

View File

@ -1,30 +1,37 @@
<script setup lang="ts"> <script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import { import {
AlertDialogContent, AlertDialogContent,
type AlertDialogContentEmits, type AlertDialogContentEmits,
type AlertDialogContentProps, type AlertDialogContentProps,
AlertDialogOverlay, AlertDialogOverlay,
AlertDialogPortal, AlertDialogPortal,
useEmitAsProps, useForwardPropsEmits,
} from 'radix-vue' } from 'radix-vue'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
const props = defineProps<AlertDialogContentProps & { class?: string }>() const props = defineProps<AlertDialogContentProps & { class?: HTMLAttributes['class'] }>()
const emits = defineEmits<AlertDialogContentEmits>() const emits = defineEmits<AlertDialogContentEmits>()
const emitsAsProps = useEmitAsProps(emits) const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script> </script>
<template> <template>
<AlertDialogPortal> <AlertDialogPortal>
<AlertDialogOverlay <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 <AlertDialogContent
v-bind="{ ...props, ...emitsAsProps }" v-bind="forwarded"
:class=" :class="
cn( 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, props.class,
) )
" "

View File

@ -1,17 +1,24 @@
<script setup lang="ts"> <script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import { import {
AlertDialogDescription, AlertDialogDescription,
type AlertDialogDescriptionProps, type AlertDialogDescriptionProps,
} from 'radix-vue' } from 'radix-vue'
import { cn } from '@/lib/utils' 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> </script>
<template> <template>
<AlertDialogDescription <AlertDialogDescription
:class="cn('text-muted-foreground text-sm', props.class)" v-bind="delegatedProps"
:as-child="props.asChild" :class="cn('text-sm text-muted-foreground', props.class)"
> >
<slot /> <slot />
</AlertDialogDescription> </AlertDialogDescription>

View File

@ -1,19 +1,17 @@
<script setup lang="ts"> <script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
const props = defineProps({ const props = defineProps<{
class: { class?: HTMLAttributes['class']
type: String, }>()
default: '',
},
})
</script> </script>
<template> <template>
<div <div
:class=" :class="
cn( 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, props.class,
) )
" "

View File

@ -1,17 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
const props = defineProps({ const props = defineProps<{
class: { class?: HTMLAttributes['class']
type: String, }>()
default: '',
},
})
</script> </script>
<template> <template>
<div <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 /> <slot />
</div> </div>

View File

@ -1,14 +1,21 @@
<script setup lang="ts"> <script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import { AlertDialogTitle, type AlertDialogTitleProps } from 'radix-vue' import { AlertDialogTitle, type AlertDialogTitleProps } from 'radix-vue'
import { cn } from '@/lib/utils' 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> </script>
<template> <template>
<AlertDialogTitle <AlertDialogTitle
:as-child="props.asChild" v-bind="delegatedProps"
:class="cn('text-lg text-foreground font-semibold', props.class)" :class="cn('text-lg font-semibold', props.class)"
> >
<slot /> <slot />
</AlertDialogTitle> </AlertDialogTitle>

View File

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

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