Merge branch 'dev' into fix/nuxt-build-dir

This commit is contained in:
Sadegh Barati 2024-05-06 10:18:46 +03:30 committed by GitHub
commit 0a2a68ac47
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
298 changed files with 24810 additions and 11389 deletions

View File

@ -18,7 +18,6 @@ on:
# When a labeled '🚀request-deploy' pull request from forked repo, it will be deploy to Cloudflare Pages
- labeled
# Allows you to run this workflow manually from the Actions tab
# eslint-disable-next-line yml/no-empty-mapping-value
workflow_dispatch:
permissions:
@ -60,7 +59,7 @@ jobs:
- uses: pnpm/action-setup@v2
name: Install pnpm
with:
version: 8
version: 9.0.5
run_install: false
- name: Get pnpm store directory

View File

@ -24,12 +24,12 @@ jobs:
- name: Setup Node.js environment
uses: actions/setup-node@v2
with:
node-version: 16
node-version: 18
- uses: pnpm/action-setup@v2
name: Install pnpm
with:
version: 8
version: 9.0.5
run_install: false
- name: Get pnpm store directory

View File

@ -1,6 +1,5 @@
{
"vue.server.hybridMode": true,
"eslint.experimental.useFlatConfig": true,
"prettier.enable": false,
"editor.formatOnSave": false,
"editor.codeActionsOnSave": {

View File

@ -0,0 +1,26 @@
<script setup lang="ts">
import { capitalize } from 'vue'
defineProps<{
type: 'prop' | 'emit' | 'slot' | 'method'
data: { name: string, description: string, type: string, required: boolean }[]
}>()
</script>
<template>
<div>
<h3>{{ capitalize(type) }}</h3>
<div v-for="(item, index) in data" :key="index" class="py-4 border-b text-sm">
<div class="flex items-center gap-2 flex-wrap">
<h5 class="text-sm">
<code>{{ item.name }}</code>
</h5>
<code>{{ item.type }}</code>
<span v-if="item.required" class="font-normal text-red-500 text-xs">Required*</span>
</div>
<div class="[&_p]:!my-2 ml-1" v-html="item.description" />
</div>
</div>
</template>

View File

@ -19,7 +19,7 @@ defineProps<CalloutProps>()
<AlertTitle v-if="title">
{{ title }}
</AlertTitle>
<AlertDescription>
<AlertDescription class="[&_a]:underline">
<slot />
</AlertDescription>
</Alert>

View File

@ -0,0 +1,54 @@
<script setup lang="ts">
import { useRoute } from 'vitepress'
import { computed } from 'vue'
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from '@/lib/registry/new-york/ui/breadcrumb'
const route = useRoute()
interface Item {
title: string
href: string
}
function generateBreadcrumb(url: string): Item[] {
const breadcrumbItems: Item[] = []
const segments = url.split('/').filter(segment => segment !== '') // Remove empty segments
// Construct breadcrumb for each segment
let href = ''
for (let i = 0; i < segments.length; i++) {
const segment = segments[i].replace('.html', '')
href += `/${segment}`
breadcrumbItems.push({ title: segment, href })
}
return breadcrumbItems
}
const breadcrumbs = computed(() => generateBreadcrumb(route.path))
</script>
<template>
<Breadcrumb>
<BreadcrumbList>
<template v-for="(breadcrumb, index) in breadcrumbs" :key="breadcrumb.title">
<BreadcrumbItem>
<BreadcrumbLink
class="capitalize"
:href="index === 0 ? undefined : breadcrumb.href"
:class="{ 'text-foreground': index === breadcrumbs.length - 1 }"
>
{{ breadcrumb.title }}
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator v-if="index !== breadcrumbs.length - 1" />
</template>
</BreadcrumbList>
</Breadcrumb>
</template>

View File

@ -5,6 +5,7 @@ import Logo from './Logo.vue'
import { Sheet, SheetContent, SheetTrigger } from '@/lib/registry/default/ui/sheet'
import { Button } from '@/lib/registry/default/ui/button'
import { ScrollArea } from '@/lib/registry/default/ui/scroll-area'
import { Badge } from '@/lib/registry/new-york/ui/badge'
const open = ref(false)
</script>
@ -63,17 +64,26 @@ const open = ref(false)
</div>
<div class="flex flex-col space-y-2">
<div v-for="(items, index) in docsConfig.sidebarNav" :key="index" class="flex flex-col space-y-3 pt-6">
<h4 class="font-medium">
{{ items.title }}
</h4>
<div class="flex items-center">
<h4 class="font-medium">
{{ items.title }}
</h4>
<span v-if="items.label" class="ml-2 rounded-md bg-[#adfa1d] px-1.5 py-0.5 text-xs leading-none text-[#000000] no-underline group-hover:no-underline">
{{ items.label }}
</span>
</div>
<a
v-for="item in items.items" :key="item.href"
:href="item.href"
class="text-muted-foreground"
class="text-muted-foreground inline-flex items-center"
@click="open = false"
>
{{ item.title }}
<span v-if="item.label" class="ml-2 rounded-md bg-[#adfa1d] px-1.5 py-0.5 text-xs leading-none text-[#000000] no-underline group-hover:no-underline">
{{ item.label }}
</span>
</a>
</div>
</div>

View File

@ -0,0 +1,47 @@
<script setup lang="ts">
import { Paintbrush } from 'lucide-vue-next'
import { onMounted, watch } from 'vue'
import { allColors } from './theming/utils/data'
import ThemeCustomizer from './ThemeCustomizer.vue'
import { Button } from '@/lib/registry/new-york/ui/button'
import { Popover, PopoverContent, PopoverTrigger } from '@/lib/registry/new-york/ui/popover'
import { useConfigStore } from '@/stores/config'
const { theme, radius } = useConfigStore()
// Whenever the component is mounted, update the document class list
onMounted(() => {
document.documentElement.style.setProperty('--radius', `${radius.value}rem`)
document.documentElement.classList.add(`theme-${theme.value}`)
})
// Whenever the theme value changes, update the document class list
watch(theme, (theme) => {
document.documentElement.classList.remove(
...allColors.map(color => `theme-${color}`),
)
document.documentElement.classList.add(`theme-${theme}`)
})
// Whenever the radius value changes, update the document style
watch(radius, (radius) => {
document.documentElement.style.setProperty('--radius', `${radius}rem`)
})
</script>
<template>
<Popover>
<PopoverTrigger as-child>
<Button
class="w-9 h-9"
:variant="'ghost'"
:size="'icon'"
>
<Paintbrush class="w-4 h-4" />
</Button>
</PopoverTrigger>
<PopoverContent :side-offset="8" align="end" class="w-96">
<ThemeCustomizer :all-colors="allColors" />
</PopoverContent>
</Popover>
</template>

View File

@ -1,5 +1,6 @@
export { default as CodeWrapper } from './CodeWrapper'
export { default as ComponentPreview } from './ComponentPreview.vue'
export { default as APITable } from './APITable.vue'
export { default as TabPreview } from './TabPreview.vue'
export { default as TabMarkdown } from './TabMarkdown.vue'
export { default as TabsMarkdown } from './TabsMarkdown.vue'

View File

@ -1,6 +1,8 @@
<script setup lang="ts">
import { ref } from 'vue'
import { addDays, startOfToday } from 'date-fns'
import { type Ref, ref } from 'vue'
import type { DateRange } from 'radix-vue'
import { getLocalTimeZone, today } from '@internationalized/date'
import ThemingLayout from './../../layout/ThemingLayout.vue'
import CookieSettings from '@/examples/cards/components/CookieSettings.vue'
@ -16,15 +18,15 @@ import Metric from '@/lib/registry/new-york/example/Cards/Metric.vue'
import DataTable from '@/lib/registry/new-york/example/Cards/DataTable.vue'
import CardStats from '@/lib/registry/new-york/example/CardStats.vue'
import {
Card,
} from '@/lib/registry/new-york/ui/card'
import { Calendar } from '@/lib/registry/new-york/ui/calendar'
import { Card } from '@/lib/registry/new-york/ui/card'
import { RangeCalendar } from '@/lib/registry/new-york/ui/range-calendar'
const now = today(getLocalTimeZone())
const range = ref({
start: startOfToday(),
end: addDays(startOfToday(), 8),
})
start: now,
end: now.add({ days: 8 }),
}) as Ref<DateRange>
</script>
<template>
@ -52,7 +54,7 @@ const range = ref({
<div class="space-y-4 lg:col-span-6 xl:col-span-5 xl:space-y-4">
<div class="hidden gap-1 sm:grid-cols-[280px_1fr] md:grid">
<Card class="max-w-[280px]">
<Calendar v-model.range="range" />
<RangeCalendar v-model="range" />
</Card>
<div class="pt-3 sm:pl-2 sm:pt-0 xl:pl-3">

View File

@ -2,6 +2,36 @@ import { CreditCard } from 'lucide-vue-next'
import RiAppleFill from '~icons/ri/apple-fill'
import RiPaypalFill from '~icons/ri/paypal-fill'
type Color =
| 'zinc'
| 'slate'
| 'stone'
| 'gray'
| 'neutral'
| 'red'
| 'rose'
| 'orange'
| 'green'
| 'blue'
| 'yellow'
| 'violet'
// Create an array of color values
export const allColors: Color[] = [
'zinc',
'rose',
'blue',
'green',
'orange',
'red',
'slate',
'stone',
'gray',
'neutral',
'yellow',
'violet',
]
interface Payment {
status: string
email: string

View File

@ -8,11 +8,11 @@ export interface NavItem {
}
export type SidebarNavItem = NavItem & {
items: SidebarNavItem[]
items?: SidebarNavItem[]
}
export type NavItemWithChildren = NavItem & {
items: NavItemWithChildren[]
items?: NavItemWithChildren[]
}
interface DocsConfig {
@ -55,22 +55,18 @@ export const docsConfig: DocsConfig = {
{
title: 'Introduction',
href: '/docs/introduction',
items: [],
},
{
title: 'Installation',
href: '/docs/installation',
items: [],
},
{
title: 'components.json',
href: '/docs/components-json',
items: [],
},
{
title: 'Theming',
href: '/docs/theming',
items: [],
},
{
title: 'Dark Mode',
@ -80,33 +76,27 @@ export const docsConfig: DocsConfig = {
{
title: 'CLI',
href: '/docs/cli',
items: [],
},
{
title: 'Typography',
href: '/docs/typography',
items: [],
},
{
title: 'Figma',
href: '/docs/figma',
items: [],
},
{
title: 'Changelog',
href: '/docs/changelog',
items: [],
},
{
title: 'About',
href: '/docs/about',
items: [],
},
{
title: 'Contribution',
href: '/docs/contribution',
items: [],
label: 'New',
},
],
},
@ -116,21 +106,34 @@ export const docsConfig: DocsConfig = {
{
title: 'Vite',
href: '/docs/installation/vite',
items: [],
},
{
title: 'Nuxt',
href: '/docs/installation/nuxt',
items: [],
},
{
title: 'Astro',
href: '/docs/installation/astro',
items: [],
},
{
title: 'Laravel',
href: '/docs/installation/laravel',
},
],
},
{
title: 'Extended',
items: [
{
title: 'Auto Form',
href: '/docs/components/auto-form',
items: [],
label: 'New',
},
{
title: 'Charts',
href: '/docs/charts',
label: 'New Alpha',
items: [],
},
],
@ -141,254 +144,214 @@ export const docsConfig: DocsConfig = {
{
title: 'Accordion',
href: '/docs/components/accordion',
items: [],
},
{
title: 'Alert',
href: '/docs/components/alert',
items: [],
},
{
title: 'Alert Dialog',
href: '/docs/components/alert-dialog',
items: [],
},
{
title: 'Aspect Ratio',
href: '/docs/components/aspect-ratio',
items: [],
},
{
title: 'Avatar',
href: '/docs/components/avatar',
items: [],
},
{
title: 'Badge',
href: '/docs/components/badge',
items: [],
},
{
title: 'Breadcrumb',
href: '/docs/components/breadcrumb',
items: [],
label: 'New',
},
{
title: 'Button',
href: '/docs/components/button',
items: [],
},
{
title: 'Calendar',
href: '/docs/components/calendar',
items: [],
label: 'Updated',
},
{
title: 'Card',
href: '/docs/components/card',
items: [],
},
{
title: 'Carousel',
href: '/docs/components/carousel',
label: 'New',
items: [],
},
{
title: 'Checkbox',
href: '/docs/components/checkbox',
items: [],
},
{
title: 'Collapsible',
href: '/docs/components/collapsible',
items: [],
},
{
title: 'Combobox',
href: '/docs/components/combobox',
items: [],
},
{
title: 'Command',
href: '/docs/components/command',
items: [],
},
{
title: 'Context Menu',
href: '/docs/components/context-menu',
items: [],
},
{
title: 'Data Table',
href: '/docs/components/data-table',
items: [],
},
{
title: 'Date Picker',
href: '/docs/components/date-picker',
items: [],
label: 'Updated',
},
{
title: 'Dialog',
href: '/docs/components/dialog',
items: [],
},
{
title: 'Drawer',
href: '/docs/components/drawer',
items: [],
label: 'New',
},
{
title: 'Dropdown Menu',
href: '/docs/components/dropdown-menu',
items: [],
},
{
title: 'Form',
href: '/docs/components/form',
items: [],
},
{
title: 'Hover Card',
href: '/docs/components/hover-card',
items: [],
},
{
title: 'Input',
href: '/docs/components/input',
items: [],
},
{
title: 'Label',
href: '/docs/components/label',
items: [],
},
{
title: 'Menubar',
href: '/docs/components/menubar',
items: [],
},
{
title: 'Navigation Menu',
href: '/docs/components/navigation-menu',
items: [],
},
{
title: 'Pagination',
href: '/docs/components/pagination',
items: [],
},
{
title: 'Pin Input',
href: '/docs/components/pin-input',
label: 'New',
items: [],
},
{
title: 'Popover',
href: '/docs/components/popover',
items: [],
},
{
title: 'Progress',
href: '/docs/components/progress',
items: [],
},
{
title: 'Radio Group',
href: '/docs/components/radio-group',
},
{
title: 'Range Calendar',
href: '/docs/components/range-calendar',
items: [],
},
{
title: 'Resizable',
href: '/docs/components/resizable',
label: 'New',
items: [],
},
{
title: 'Scroll Area',
href: '/docs/components/scroll-area',
items: [],
},
{
title: 'Select',
href: '/docs/components/select',
items: [],
},
{
title: 'Separator',
href: '/docs/components/separator',
items: [],
},
{
title: 'Sheet',
href: '/docs/components/sheet',
items: [],
},
{
title: 'Skeleton',
href: '/docs/components/skeleton',
items: [],
},
{
title: 'Slider',
href: '/docs/components/slider',
items: [],
},
{
title: 'Sonner',
href: '/docs/components/sonner',
label: 'New',
items: [],
},
{
title: 'Switch',
href: '/docs/components/switch',
items: [],
},
{
title: 'Table',
href: '/docs/components/table',
items: [],
},
{
title: 'Tabs',
href: '/docs/components/tabs',
items: [],
},
{
title: 'Tags Input',
href: '/docs/components/tags-input',
label: 'New',
items: [],
},
{
title: 'Textarea',
href: '/docs/components/textarea',
items: [],
},
{
title: 'Toast',
href: '/docs/components/toast',
items: [],
},
{
title: 'Toggle',
href: '/docs/components/toggle',
items: [],
},
{
title: 'Toggle Group',
href: '/docs/components/toggle-group',
items: [],
},
{
title: 'Tooltip',
href: '/docs/components/tooltip',
items: [],
},
],
},

View File

@ -15,6 +15,6 @@ export const siteConfig = {
export const announcementConfig = {
icon: '✨',
title: 'Introducing Blocks!',
link: '/blocks',
title: 'Extended: Auto Form, Charts',
link: '/docs/components/auto-form.html',
}

View File

@ -3,6 +3,7 @@ import { useData, useRoute } from 'vitepress'
import { docsConfig } from '../config/docs'
import TableOfContentVue from '../components/TableOfContent.vue'
import EditLink from '../components/EditLink.vue'
import DocsBreadcrumb from '../components/DocsBreadcrumb.vue'
import { ScrollArea } from '@/lib/registry/default/ui/scroll-area'
import RadixIconsCode from '~icons/radix-icons/code'
import RadixIconsExternalLink from '~icons/radix-icons/external-link'
@ -27,6 +28,10 @@ const sourceLink = 'https://github.com/radix-vue/shadcn-vue/tree/dev/'
class="mb-1 rounded-md px-2 py-1 text-sm font-semibold"
>
{{ docsGroup.title }}
<span v-if="docsGroup.label" class="ml-2 font-normal rounded-md bg-[#adfa1d] px-1.5 py-0.5 text-xs leading-none text-[#000000] no-underline group-hover:no-underline">
{{ docsGroup.label }}
</span>
</h4>
<div
@ -61,20 +66,17 @@ const sourceLink = 'https://github.com/radix-vue/shadcn-vue/tree/dev/'
<TableOfContentVue />
</div>
<div class="mb-4 flex items-center space-x-1 text-sm text-muted-foreground">
<div class="overflow-hidden text-ellipsis whitespace-nowrap">
Docs
</div>
<ChevronRightIcon class="h-4 w-4" />
<div class="font-medium text-foreground">
{{ frontmatter.title }}
</div>
</div>
<DocsBreadcrumb class="mb-4" />
<div class="space-y-2">
<h1 class="scroll-m-20 text-4xl font-bold tracking-tight">
{{ frontmatter.title }}
</h1>
<div class="flex items-center space-x-4">
<h1 class="scroll-m-20 text-4xl font-bold tracking-tight">
{{ frontmatter.title }}
</h1>
<span v-if="frontmatter.label" class="ml-2 rounded-md bg-[#adfa1d] px-1.5 py-0.5 text-xs leading-none text-[#000000] no-underline group-hover:no-underline">
{{ frontmatter.label }}
</span>
</div>
<p class="text-lg text-muted-foreground">
{{ frontmatter.description }}
</p>

View File

@ -8,6 +8,7 @@ import MobileNav from '../components/MobileNav.vue'
import CodeConfigCustomizer from '../components/CodeConfigCustomizer.vue'
import Kbd from '../components/Kbd.vue'
import ThemePopover from '../components/ThemePopover.vue'
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator } from '@/lib/registry/default/ui/command'
import { Button } from '@/lib/registry/default/ui/button'
@ -133,6 +134,8 @@ watch(() => $route.path, (n) => {
</div>
<nav class="flex items-center">
<ThemePopover />
<CodeConfigCustomizer />
<Button

View File

@ -27,6 +27,21 @@
--input: 240 5.9% 90%;
--ring: 240 5% 64.9%;
--radius: 0.5rem;
--vis-primary-color: var(--primary);
--vis-secondary-color: 160 81% 40%;
--vis-text-color: var(--muted-foreground);
--vis-font-family: inherit !important;
--vis-area-stroke-width: 2px !important;
--vis-donut-central-label-text-color: hsl(var(--muted-foreground)) !important;
--vis-tooltip-background-color: none !important;
--vis-tooltip-border-color: none !important;
--vis-tooltip-text-color: none !important;
--vis-tooltip-shadow-color: none !important;
--vis-tooltip-backdrop-filter: none !important;
--vis-tooltip-padding: none !important;
}
.dark {
@ -142,7 +157,7 @@
}
div[class^="language-"] {
@apply mb-4 mt-6 max-h-[650px] overflow-x-auto md:rounded-lg border !bg-zinc-950 dark:!bg-zinc-900
@apply mb-4 mt-6 max-h-[650px] overflow-x-auto md:rounded-lg border !bg-secondary-foreground dark:!bg-secondary
}
pre {
@apply py-4;

View File

@ -345,14 +345,14 @@
}
.vp-doc [class*='language-'] code .highlighted {
background-color: hsl(240 3.7% 15.9%);
transition: background-color 0.5s;
/* margin: 0 -24px;
padding: 0 24px; */
width: calc(100% + 2 * 24px);
display: inline-block;
@apply bg-[hsl(var(--foreground))] dark:bg-[hsl(var(--background)_/_50%)]
}
hsl(var(--foreground) / 50%)
.vp-doc [class*='language-'] code .highlighted.error {
background-color: var(--vp-code-line-error-color);
}

View File

@ -123,7 +123,7 @@ function constructFiles(componentName: string, style: Style, sources: Record<str
}
})
// @ts-expect-error componentName migth not exist in Index
// @ts-expect-error componentName might not exist in Index
const registryDependencies = demoIndex[style][componentName as any]?.registryDependencies?.filter(i => i !== 'utils')
const files = {

View File

@ -31,6 +31,27 @@ export const Index = {
component: () => import("../src/lib/registry/default/example/AlertDialogDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/AlertDialogDemo.vue"],
},
"AreaChartCustomTooltip": {
name: "AreaChartCustomTooltip",
type: "components:example",
registryDependencies: ["chart-area"],
component: () => import("../src/lib/registry/default/example/AreaChartCustomTooltip.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/AreaChartCustomTooltip.vue"],
},
"AreaChartDemo": {
name: "AreaChartDemo",
type: "components:example",
registryDependencies: ["chart-area"],
component: () => import("../src/lib/registry/default/example/AreaChartDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/AreaChartDemo.vue"],
},
"AreaChartSparkline": {
name: "AreaChartSparkline",
type: "components:example",
registryDependencies: ["chart-area"],
component: () => import("../src/lib/registry/default/example/AreaChartSparkline.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/AreaChartSparkline.vue"],
},
"AspectRatioDemo": {
name: "AspectRatioDemo",
type: "components:example",
@ -38,6 +59,62 @@ export const Index = {
component: () => import("../src/lib/registry/default/example/AspectRatioDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/AspectRatioDemo.vue"],
},
"AutoFormApi": {
name: "AutoFormApi",
type: "components:example",
registryDependencies: ["button","toast","auto-form"],
component: () => import("../src/lib/registry/default/example/AutoFormApi.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/AutoFormApi.vue"],
},
"AutoFormArray": {
name: "AutoFormArray",
type: "components:example",
registryDependencies: ["button","toast","auto-form"],
component: () => import("../src/lib/registry/default/example/AutoFormArray.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/AutoFormArray.vue"],
},
"AutoFormBasic": {
name: "AutoFormBasic",
type: "components:example",
registryDependencies: ["button","toast","auto-form"],
component: () => import("../src/lib/registry/default/example/AutoFormBasic.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/AutoFormBasic.vue"],
},
"AutoFormConfirmPassword": {
name: "AutoFormConfirmPassword",
type: "components:example",
registryDependencies: ["button","toast","auto-form"],
component: () => import("../src/lib/registry/default/example/AutoFormConfirmPassword.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/AutoFormConfirmPassword.vue"],
},
"AutoFormControlled": {
name: "AutoFormControlled",
type: "components:example",
registryDependencies: ["button","toast","auto-form"],
component: () => import("../src/lib/registry/default/example/AutoFormControlled.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/AutoFormControlled.vue"],
},
"AutoFormDependencies": {
name: "AutoFormDependencies",
type: "components:example",
registryDependencies: ["button","toast","auto-form"],
component: () => import("../src/lib/registry/default/example/AutoFormDependencies.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/AutoFormDependencies.vue"],
},
"AutoFormInputWithoutLabel": {
name: "AutoFormInputWithoutLabel",
type: "components:example",
registryDependencies: ["button","toast","auto-form"],
component: () => import("../src/lib/registry/default/example/AutoFormInputWithoutLabel.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/AutoFormInputWithoutLabel.vue"],
},
"AutoFormSubObject": {
name: "AutoFormSubObject",
type: "components:example",
registryDependencies: ["button","toast","auto-form"],
component: () => import("../src/lib/registry/default/example/AutoFormSubObject.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/AutoFormSubObject.vue"],
},
"AvatarDemo": {
name: "AvatarDemo",
type: "components:example",
@ -73,6 +150,34 @@ export const Index = {
component: () => import("../src/lib/registry/default/example/BadgeSecondaryDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/BadgeSecondaryDemo.vue"],
},
"BarChartCustomTooltip": {
name: "BarChartCustomTooltip",
type: "components:example",
registryDependencies: ["chart-bar"],
component: () => import("../src/lib/registry/default/example/BarChartCustomTooltip.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/BarChartCustomTooltip.vue"],
},
"BarChartDemo": {
name: "BarChartDemo",
type: "components:example",
registryDependencies: ["chart-bar"],
component: () => import("../src/lib/registry/default/example/BarChartDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/BarChartDemo.vue"],
},
"BarChartRounded": {
name: "BarChartRounded",
type: "components:example",
registryDependencies: ["chart-bar"],
component: () => import("../src/lib/registry/default/example/BarChartRounded.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/BarChartRounded.vue"],
},
"BarChartStacked": {
name: "BarChartStacked",
type: "components:example",
registryDependencies: ["chart-bar"],
component: () => import("../src/lib/registry/default/example/BarChartStacked.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/BarChartStacked.vue"],
},
"BreadcrumbDemo": {
name: "BreadcrumbDemo",
type: "components:example",
@ -192,6 +297,20 @@ export const Index = {
component: () => import("../src/lib/registry/default/example/CalendarDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/CalendarDemo.vue"],
},
"CalendarForm": {
name: "CalendarForm",
type: "components:example",
registryDependencies: ["calendar","button","form","popover","toast","utils"],
component: () => import("../src/lib/registry/default/example/CalendarForm.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/CalendarForm.vue"],
},
"CalendarWithSelect": {
name: "CalendarWithSelect",
type: "components:example",
registryDependencies: ["calendar","select","utils"],
component: () => import("../src/lib/registry/default/example/CalendarWithSelect.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/CalendarWithSelect.vue"],
},
"CardChat": {
name: "CardChat",
type: "components:example",
@ -374,6 +493,13 @@ export const Index = {
component: () => import("../src/lib/registry/default/example/ContextMenuDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/ContextMenuDemo.vue"],
},
"CustomChartTooltip": {
name: "CustomChartTooltip",
type: "components:example",
registryDependencies: ["card"],
component: () => import("../src/lib/registry/default/example/CustomChartTooltip.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/CustomChartTooltip.vue"],
},
"DataTableColumnPinningDemo": {
name: "DataTableColumnPinningDemo",
type: "components:example",
@ -398,38 +524,38 @@ export const Index = {
"DatePickerDemo": {
name: "DatePickerDemo",
type: "components:example",
registryDependencies: ["utils","button","calendar","popover"],
registryDependencies: ["calendar","button","popover","utils"],
component: () => import("../src/lib/registry/default/example/DatePickerDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/DatePickerDemo.vue"],
},
"DatePickerForm": {
name: "DatePickerForm",
type: "components:example",
registryDependencies: ["utils","button","calendar","form","popover","toast"],
registryDependencies: ["calendar","button","form","popover","toast","utils"],
component: () => import("../src/lib/registry/default/example/DatePickerForm.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/DatePickerForm.vue"],
},
"DatePickerWithIndependentMonths": {
name: "DatePickerWithIndependentMonths",
type: "components:example",
registryDependencies: ["range-calendar","button","popover","utils"],
component: () => import("../src/lib/registry/default/example/DatePickerWithIndependentMonths.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/DatePickerWithIndependentMonths.vue"],
},
"DatePickerWithPresets": {
name: "DatePickerWithPresets",
type: "components:example",
registryDependencies: ["utils","button","calendar","popover","select"],
registryDependencies: ["calendar","button","popover","select","utils"],
component: () => import("../src/lib/registry/default/example/DatePickerWithPresets.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/DatePickerWithPresets.vue"],
},
"DatePickerWithRange": {
name: "DatePickerWithRange",
type: "components:example",
registryDependencies: ["utils","button","calendar","popover"],
registryDependencies: ["range-calendar","button","popover","utils"],
component: () => import("../src/lib/registry/default/example/DatePickerWithRange.vue").then((m) => m.default),
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": {
name: "DialogCustomCloseButton",
type: "components:example",
@ -458,6 +584,34 @@ export const Index = {
component: () => import("../src/lib/registry/default/example/DialogScrollOverlayDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/DialogScrollOverlayDemo.vue"],
},
"DonutChartColor": {
name: "DonutChartColor",
type: "components:example",
registryDependencies: ["chart-donut"],
component: () => import("../src/lib/registry/default/example/DonutChartColor.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/DonutChartColor.vue"],
},
"DonutChartCustomTooltip": {
name: "DonutChartCustomTooltip",
type: "components:example",
registryDependencies: ["chart-donut"],
component: () => import("../src/lib/registry/default/example/DonutChartCustomTooltip.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/DonutChartCustomTooltip.vue"],
},
"DonutChartDemo": {
name: "DonutChartDemo",
type: "components:example",
registryDependencies: ["chart-donut"],
component: () => import("../src/lib/registry/default/example/DonutChartDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/DonutChartDemo.vue"],
},
"DonutChartPie": {
name: "DonutChartPie",
type: "components:example",
registryDependencies: ["chart-donut"],
component: () => import("../src/lib/registry/default/example/DonutChartPie.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/DonutChartPie.vue"],
},
"DrawerDemo": {
name: "DrawerDemo",
type: "components:example",
@ -563,6 +717,27 @@ export const Index = {
component: () => import("../src/lib/registry/default/example/LabelDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/LabelDemo.vue"],
},
"LineChartCustomTooltip": {
name: "LineChartCustomTooltip",
type: "components:example",
registryDependencies: ["chart-line"],
component: () => import("../src/lib/registry/default/example/LineChartCustomTooltip.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/LineChartCustomTooltip.vue"],
},
"LineChartDemo": {
name: "LineChartDemo",
type: "components:example",
registryDependencies: ["chart-line"],
component: () => import("../src/lib/registry/default/example/LineChartDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/LineChartDemo.vue"],
},
"LineChartSparkline": {
name: "LineChartSparkline",
type: "components:example",
registryDependencies: ["chart-line"],
component: () => import("../src/lib/registry/default/example/LineChartSparkline.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/LineChartSparkline.vue"],
},
"MenubarDemo": {
name: "MenubarDemo",
type: "components:example",
@ -654,12 +829,12 @@ export const Index = {
component: () => import("../src/lib/registry/default/example/RadioGroupForm.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/RadioGroupForm.vue"],
},
"RangePickerWithSlot": {
name: "RangePickerWithSlot",
"RangeCalendarDemo": {
name: "RangeCalendarDemo",
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"],
registryDependencies: ["range-calendar"],
component: () => import("../src/lib/registry/default/example/RangeCalendarDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/RangeCalendarDemo.vue"],
},
"ResizableDemo": {
name: "ResizableDemo",
@ -1081,6 +1256,55 @@ export const Index = {
component: () => import("../src/lib/registry/default/example/TypographyTable.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/TypographyTable.vue"],
},
"VCalendarDemo": {
name: "VCalendarDemo",
type: "components:example",
registryDependencies: ["v-calendar"],
component: () => import("../src/lib/registry/default/example/VCalendarDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/VCalendarDemo.vue"],
},
"VDatePickerDemo": {
name: "VDatePickerDemo",
type: "components:example",
registryDependencies: ["utils","button","v-calendar","popover"],
component: () => import("../src/lib/registry/default/example/VDatePickerDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/VDatePickerDemo.vue"],
},
"VDatePickerForm": {
name: "VDatePickerForm",
type: "components:example",
registryDependencies: ["utils","button","v-calendar","form","popover","toast"],
component: () => import("../src/lib/registry/default/example/VDatePickerForm.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/VDatePickerForm.vue"],
},
"VDatePickerWithPresets": {
name: "VDatePickerWithPresets",
type: "components:example",
registryDependencies: ["utils","button","v-calendar","popover","select"],
component: () => import("../src/lib/registry/default/example/VDatePickerWithPresets.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/VDatePickerWithPresets.vue"],
},
"VDatePickerWithRange": {
name: "VDatePickerWithRange",
type: "components:example",
registryDependencies: ["utils","button","v-calendar","popover"],
component: () => import("../src/lib/registry/default/example/VDatePickerWithRange.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/VDatePickerWithRange.vue"],
},
"VDateTimePickerDemo": {
name: "VDateTimePickerDemo",
type: "components:example",
registryDependencies: ["utils","button","v-calendar","popover"],
component: () => import("../src/lib/registry/default/example/VDateTimePickerDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/VDateTimePickerDemo.vue"],
},
"VRangePickerWithSlot": {
name: "VRangePickerWithSlot",
type: "components:example",
registryDependencies: ["utils","button","v-calendar","popover"],
component: () => import("../src/lib/registry/default/example/VRangePickerWithSlot.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/VRangePickerWithSlot.vue"],
},
"ActivityGoal": {
name: "ActivityGoal",
type: "components:example",
@ -1208,6 +1432,27 @@ export const Index = {
component: () => import("../src/lib/registry/new-york/example/AlertDialogDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/AlertDialogDemo.vue"],
},
"AreaChartCustomTooltip": {
name: "AreaChartCustomTooltip",
type: "components:example",
registryDependencies: ["chart-area"],
component: () => import("../src/lib/registry/new-york/example/AreaChartCustomTooltip.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/AreaChartCustomTooltip.vue"],
},
"AreaChartDemo": {
name: "AreaChartDemo",
type: "components:example",
registryDependencies: ["chart-area"],
component: () => import("../src/lib/registry/new-york/example/AreaChartDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/AreaChartDemo.vue"],
},
"AreaChartSparkline": {
name: "AreaChartSparkline",
type: "components:example",
registryDependencies: ["chart-area"],
component: () => import("../src/lib/registry/new-york/example/AreaChartSparkline.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/AreaChartSparkline.vue"],
},
"AspectRatioDemo": {
name: "AspectRatioDemo",
type: "components:example",
@ -1215,6 +1460,62 @@ export const Index = {
component: () => import("../src/lib/registry/new-york/example/AspectRatioDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/AspectRatioDemo.vue"],
},
"AutoFormApi": {
name: "AutoFormApi",
type: "components:example",
registryDependencies: ["button","toast","auto-form"],
component: () => import("../src/lib/registry/new-york/example/AutoFormApi.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/AutoFormApi.vue"],
},
"AutoFormArray": {
name: "AutoFormArray",
type: "components:example",
registryDependencies: ["button","toast","auto-form"],
component: () => import("../src/lib/registry/new-york/example/AutoFormArray.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/AutoFormArray.vue"],
},
"AutoFormBasic": {
name: "AutoFormBasic",
type: "components:example",
registryDependencies: ["button","toast","auto-form"],
component: () => import("../src/lib/registry/new-york/example/AutoFormBasic.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/AutoFormBasic.vue"],
},
"AutoFormConfirmPassword": {
name: "AutoFormConfirmPassword",
type: "components:example",
registryDependencies: ["button","toast","auto-form"],
component: () => import("../src/lib/registry/new-york/example/AutoFormConfirmPassword.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/AutoFormConfirmPassword.vue"],
},
"AutoFormControlled": {
name: "AutoFormControlled",
type: "components:example",
registryDependencies: ["button","toast","auto-form"],
component: () => import("../src/lib/registry/new-york/example/AutoFormControlled.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/AutoFormControlled.vue"],
},
"AutoFormDependencies": {
name: "AutoFormDependencies",
type: "components:example",
registryDependencies: ["button","toast","auto-form"],
component: () => import("../src/lib/registry/new-york/example/AutoFormDependencies.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/AutoFormDependencies.vue"],
},
"AutoFormInputWithoutLabel": {
name: "AutoFormInputWithoutLabel",
type: "components:example",
registryDependencies: ["button","toast","auto-form"],
component: () => import("../src/lib/registry/new-york/example/AutoFormInputWithoutLabel.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/AutoFormInputWithoutLabel.vue"],
},
"AutoFormSubObject": {
name: "AutoFormSubObject",
type: "components:example",
registryDependencies: ["button","toast","auto-form"],
component: () => import("../src/lib/registry/new-york/example/AutoFormSubObject.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/AutoFormSubObject.vue"],
},
"AvatarDemo": {
name: "AvatarDemo",
type: "components:example",
@ -1250,6 +1551,34 @@ export const Index = {
component: () => import("../src/lib/registry/new-york/example/BadgeSecondaryDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/BadgeSecondaryDemo.vue"],
},
"BarChartCustomTooltip": {
name: "BarChartCustomTooltip",
type: "components:example",
registryDependencies: ["chart-bar"],
component: () => import("../src/lib/registry/new-york/example/BarChartCustomTooltip.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/BarChartCustomTooltip.vue"],
},
"BarChartDemo": {
name: "BarChartDemo",
type: "components:example",
registryDependencies: ["chart-bar"],
component: () => import("../src/lib/registry/new-york/example/BarChartDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/BarChartDemo.vue"],
},
"BarChartRounded": {
name: "BarChartRounded",
type: "components:example",
registryDependencies: ["chart-bar"],
component: () => import("../src/lib/registry/new-york/example/BarChartRounded.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/BarChartRounded.vue"],
},
"BarChartStacked": {
name: "BarChartStacked",
type: "components:example",
registryDependencies: ["chart-bar"],
component: () => import("../src/lib/registry/new-york/example/BarChartStacked.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/BarChartStacked.vue"],
},
"BreadcrumbDemo": {
name: "BreadcrumbDemo",
type: "components:example",
@ -1369,6 +1698,20 @@ export const Index = {
component: () => import("../src/lib/registry/new-york/example/CalendarDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/CalendarDemo.vue"],
},
"CalendarForm": {
name: "CalendarForm",
type: "components:example",
registryDependencies: ["calendar","button","form","popover","toast","utils"],
component: () => import("../src/lib/registry/new-york/example/CalendarForm.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/CalendarForm.vue"],
},
"CalendarWithSelect": {
name: "CalendarWithSelect",
type: "components:example",
registryDependencies: ["calendar","select","utils"],
component: () => import("../src/lib/registry/new-york/example/CalendarWithSelect.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/CalendarWithSelect.vue"],
},
"CardChat": {
name: "CardChat",
type: "components:example",
@ -1551,6 +1894,13 @@ export const Index = {
component: () => import("../src/lib/registry/new-york/example/ContextMenuDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/ContextMenuDemo.vue"],
},
"CustomChartTooltip": {
name: "CustomChartTooltip",
type: "components:example",
registryDependencies: ["card"],
component: () => import("../src/lib/registry/new-york/example/CustomChartTooltip.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/CustomChartTooltip.vue"],
},
"DataTableColumnPinningDemo": {
name: "DataTableColumnPinningDemo",
type: "components:example",
@ -1575,38 +1925,38 @@ export const Index = {
"DatePickerDemo": {
name: "DatePickerDemo",
type: "components:example",
registryDependencies: ["utils","button","calendar","popover"],
registryDependencies: ["calendar","button","popover","utils"],
component: () => import("../src/lib/registry/new-york/example/DatePickerDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/DatePickerDemo.vue"],
},
"DatePickerForm": {
name: "DatePickerForm",
type: "components:example",
registryDependencies: ["utils","button","calendar","form","popover","toast"],
registryDependencies: ["calendar","button","form","popover","toast","utils"],
component: () => import("../src/lib/registry/new-york/example/DatePickerForm.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/DatePickerForm.vue"],
},
"DatePickerWithIndependentMonths": {
name: "DatePickerWithIndependentMonths",
type: "components:example",
registryDependencies: ["range-calendar","button","popover","utils"],
component: () => import("../src/lib/registry/new-york/example/DatePickerWithIndependentMonths.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/DatePickerWithIndependentMonths.vue"],
},
"DatePickerWithPresets": {
name: "DatePickerWithPresets",
type: "components:example",
registryDependencies: ["utils","button","calendar","popover","select"],
registryDependencies: ["calendar","button","popover","select","utils"],
component: () => import("../src/lib/registry/new-york/example/DatePickerWithPresets.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/DatePickerWithPresets.vue"],
},
"DatePickerWithRange": {
name: "DatePickerWithRange",
type: "components:example",
registryDependencies: ["utils","button","calendar","popover"],
registryDependencies: ["range-calendar","button","popover","utils"],
component: () => import("../src/lib/registry/new-york/example/DatePickerWithRange.vue").then((m) => m.default),
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": {
name: "DialogCustomCloseButton",
type: "components:example",
@ -1635,6 +1985,34 @@ export const Index = {
component: () => import("../src/lib/registry/new-york/example/DialogScrollOverlayDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/DialogScrollOverlayDemo.vue"],
},
"DonutChartColor": {
name: "DonutChartColor",
type: "components:example",
registryDependencies: ["chart-donut"],
component: () => import("../src/lib/registry/new-york/example/DonutChartColor.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/DonutChartColor.vue"],
},
"DonutChartCustomTooltip": {
name: "DonutChartCustomTooltip",
type: "components:example",
registryDependencies: ["chart-donut"],
component: () => import("../src/lib/registry/new-york/example/DonutChartCustomTooltip.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/DonutChartCustomTooltip.vue"],
},
"DonutChartDemo": {
name: "DonutChartDemo",
type: "components:example",
registryDependencies: ["chart-donut"],
component: () => import("../src/lib/registry/new-york/example/DonutChartDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/DonutChartDemo.vue"],
},
"DonutChartPie": {
name: "DonutChartPie",
type: "components:example",
registryDependencies: ["chart-donut"],
component: () => import("../src/lib/registry/new-york/example/DonutChartPie.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/DonutChartPie.vue"],
},
"DrawerDemo": {
name: "DrawerDemo",
type: "components:example",
@ -1740,6 +2118,27 @@ export const Index = {
component: () => import("../src/lib/registry/new-york/example/LabelDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/LabelDemo.vue"],
},
"LineChartCustomTooltip": {
name: "LineChartCustomTooltip",
type: "components:example",
registryDependencies: ["chart-line"],
component: () => import("../src/lib/registry/new-york/example/LineChartCustomTooltip.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/LineChartCustomTooltip.vue"],
},
"LineChartDemo": {
name: "LineChartDemo",
type: "components:example",
registryDependencies: ["chart-line"],
component: () => import("../src/lib/registry/new-york/example/LineChartDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/LineChartDemo.vue"],
},
"LineChartSparkline": {
name: "LineChartSparkline",
type: "components:example",
registryDependencies: ["chart-line"],
component: () => import("../src/lib/registry/new-york/example/LineChartSparkline.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/LineChartSparkline.vue"],
},
"MenubarDemo": {
name: "MenubarDemo",
type: "components:example",
@ -1831,12 +2230,12 @@ export const Index = {
component: () => import("../src/lib/registry/new-york/example/RadioGroupForm.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/RadioGroupForm.vue"],
},
"RangePickerWithSlot": {
name: "RangePickerWithSlot",
"RangeCalendarDemo": {
name: "RangeCalendarDemo",
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"],
registryDependencies: ["range-calendar"],
component: () => import("../src/lib/registry/new-york/example/RangeCalendarDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/RangeCalendarDemo.vue"],
},
"ResizableDemo": {
name: "ResizableDemo",
@ -2258,6 +2657,55 @@ export const Index = {
component: () => import("../src/lib/registry/new-york/example/TypographyTable.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/TypographyTable.vue"],
},
"VCalendarDemo": {
name: "VCalendarDemo",
type: "components:example",
registryDependencies: ["v-calendar"],
component: () => import("../src/lib/registry/new-york/example/VCalendarDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/VCalendarDemo.vue"],
},
"VDatePickerDemo": {
name: "VDatePickerDemo",
type: "components:example",
registryDependencies: ["utils","button","v-calendar","popover"],
component: () => import("../src/lib/registry/new-york/example/VDatePickerDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/VDatePickerDemo.vue"],
},
"VDatePickerForm": {
name: "VDatePickerForm",
type: "components:example",
registryDependencies: ["utils","button","v-calendar","form","popover","toast"],
component: () => import("../src/lib/registry/new-york/example/VDatePickerForm.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/VDatePickerForm.vue"],
},
"VDatePickerWithPresets": {
name: "VDatePickerWithPresets",
type: "components:example",
registryDependencies: ["utils","button","v-calendar","popover","select"],
component: () => import("../src/lib/registry/new-york/example/VDatePickerWithPresets.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/VDatePickerWithPresets.vue"],
},
"VDatePickerWithRange": {
name: "VDatePickerWithRange",
type: "components:example",
registryDependencies: ["utils","button","v-calendar","popover"],
component: () => import("../src/lib/registry/new-york/example/VDatePickerWithRange.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/VDatePickerWithRange.vue"],
},
"VDateTimePickerDemo": {
name: "VDateTimePickerDemo",
type: "components:example",
registryDependencies: ["utils","button","v-calendar","popover"],
component: () => import("../src/lib/registry/new-york/example/VDateTimePickerDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/VDateTimePickerDemo.vue"],
},
"VRangePickerWithSlot": {
name: "VRangePickerWithSlot",
type: "components:example",
registryDependencies: ["utils","button","v-calendar","popover"],
component: () => import("../src/lib/registry/new-york/example/VRangePickerWithSlot.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/VRangePickerWithSlot.vue"],
},
"ActivityGoal": {
name: "ActivityGoal",
type: "components:example",

View File

@ -1,7 +1,7 @@
{
"name": "www",
"type": "module",
"version": "0.10.3",
"version": "0.10.4",
"files": [
"dist"
],
@ -12,63 +12,71 @@
"typecheck": "vue-tsc",
"typecheck:registry": "vue-tsc -p tsconfig.registry.json",
"build:registry": "tsx ./scripts/build-registry.ts",
"build:registry-strict": "pnpm typecheck:registry && tsx ./scripts/build-registry.ts"
"build:registry-strict": "pnpm typecheck:registry && tsx ./scripts/build-registry.ts",
"docs:gen": "tsx ./scripts/autogen.ts"
},
"dependencies": {
"@formkit/auto-animate": "^0.8.1",
"@formkit/auto-animate": "^0.8.2",
"@internationalized/date": "^3.5.2",
"@radix-icons/vue": "^1.0.0",
"@stackblitz/sdk": "^1.9.0",
"@tanstack/vue-table": "^8.14.0",
"@unovis/ts": "^1.3.5",
"@unovis/vue": "^1.3.5",
"@tanstack/vue-table": "^8.16.0",
"@unovis/ts": "^1.4.0",
"@unovis/vue": "^1.4.0",
"@vee-validate/zod": "^4.12.6",
"@vueuse/core": "^10.9.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"clsx": "^2.1.1",
"codesandbox": "^2.2.3",
"date-fns": "^3.6.0",
"embla-carousel": "^8.0.0",
"embla-carousel-autoplay": "^8.0.0",
"embla-carousel-vue": "^8.0.0",
"embla-carousel": "^8.0.2",
"embla-carousel-autoplay": "^8.0.2",
"embla-carousel-vue": "^8.0.2",
"lucide-vue-next": "^0.359.0",
"magic-string": "^0.30.8",
"radix-vue": "^1.6.2",
"magic-string": "^0.30.10",
"radix-vue": "^1.7.2",
"tailwindcss-animate": "^1.0.7",
"v-calendar": "^3.1.2",
"vaul-vue": "^0.1.0",
"vee-validate": "4.12.5",
"vue": "^3.4.21",
"vee-validate": "4.12.6",
"vue": "^3.4.24",
"vue-sonner": "^1.1.2",
"vue-wrap-balancer": "^1.1.3",
"zod": "^3.22.4"
"zod": "^3.23.3"
},
"devDependencies": {
"@babel/traverse": "^7.24.1",
"@iconify-json/gravity-ui": "^1.1.2",
"@iconify-json/lucide": "^1.1.180",
"@iconify-json/ph": "^1.1.12",
"@iconify-json/radix-icons": "^1.1.14",
"@iconify-json/ri": "^1.1.18",
"@iconify-json/simple-icons": "^1.1.94",
"@iconify-json/tabler": "^1.1.106",
"@iconify/json": "^2.2.189",
"@iconify/vue": "^4.1.1",
"@shikijs/transformers": "^1.2.0",
"@types/lodash.template": "^4.5.3",
"@types/node": "^20.11.30",
"@iconify/vue": "^4.1.2",
"@oxc-parser/wasm": "^0.1.0",
"@shikijs/transformers": "^1.3.0",
"@types/lodash-es": "^4.17.12",
"@types/node": "^20.12.7",
"@vitejs/plugin-vue": "^5.0.4",
"@vitejs/plugin-vue-jsx": "^3.1.0",
"@vue/compiler-core": "^3.4.21",
"@vue/compiler-dom": "^3.4.21",
"@vue/compiler-core": "^3.4.24",
"@vue/compiler-dom": "^3.4.24",
"@vue/tsconfig": "^0.5.1",
"autoprefixer": "^10.4.18",
"lodash.template": "^4.5.0",
"oxc-parser": "^0.8.0",
"autoprefixer": "^10.4.19",
"fast-glob": "^3.3.2",
"lodash-es": "^4.17.21",
"markdown-it": "^14.1.0",
"pathe": "^1.1.2",
"rimraf": "^5.0.5",
"shiki": "^1.2.0",
"tailwind-merge": "^2.2.2",
"tailwindcss": "^3.4.1",
"tsx": "^4.7.1",
"typescript": "^5.4.2",
"shiki": "^1.3.0",
"tailwind-merge": "^2.3.0",
"tailwindcss": "^3.4.3",
"tsx": "^4.7.2",
"typescript": "^5.4.5",
"unplugin-icons": "^0.18.5",
"vite": "^5.2.2",
"vitepress": "^1.0.0-rc.45",
"vue-tsc": "^2.0.7"
"vitepress": "^1.1.3",
"vue-component-meta": "^2.0.13",
"vue-tsc": "^2.0.14"
}
}

150
apps/www/scripts/autogen.ts Normal file
View File

@ -0,0 +1,150 @@
import { join, parse, resolve } from 'node:path'
import { existsSync, mkdirSync, writeFileSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import fg from 'fast-glob'
import MarkdownIt from 'markdown-it'
import type { ComponentMeta, MetaCheckerOptions, PropertyMeta, PropertyMetaSchema } from 'vue-component-meta'
import { createComponentMetaChecker } from 'vue-component-meta'
const __dirname = fileURLToPath(new URL('.', import.meta.url))
const md = new MarkdownIt()
const ROOTPATH = '../'
const OUTPUTPATH = '../src/content/meta'
const checkerOptions: MetaCheckerOptions = {
forceUseTs: true,
printer: { newLine: 1 },
}
const tsconfigChecker = createComponentMetaChecker(
resolve(__dirname, ROOTPATH, 'tsconfig.registry.json'),
checkerOptions,
)
const components = fg.sync(['chart/**/*.vue', 'chart*/**/*.vue'], {
cwd: resolve(__dirname, ROOTPATH, 'src/lib/registry/default/ui/'),
absolute: true,
})
components.forEach((componentPath) => {
try {
const componentName = parse(componentPath).name
const meta = parseMeta(tsconfigChecker.getComponentMeta(componentPath))
const metaDirPath = resolve(__dirname, OUTPUTPATH)
// if meta dir doesn't exist create
if (!existsSync(metaDirPath))
mkdirSync(metaDirPath)
const metaMdFilePath = join(metaDirPath, `${componentName}.md`)
let parsedString = '<!-- This file was automatic generated. Do not edit it manually -->\n\n'
if (meta.props.length)
parsedString += `<APITable :type="'prop'" :data="${JSON.stringify(meta.props, null, 2).replace(/"/g, '\'')}" />\n`
if (meta.events.length)
parsedString += `\n<APITable :type="'emit'" :data="${JSON.stringify(meta.events, null, 2).replace(/"/g, '\'')}" />\n`
if (meta.slots.length)
parsedString += `\n<APITable :type="'slot'" :data="${JSON.stringify(meta.slots, null, 2).replace(/"/g, '\'')}" />\n`
if (meta.methods.length)
parsedString += `\n<APITable :type="'method'" :data="${JSON.stringify(meta.methods, null, 2).replace(/"/g, '\'')}" />\n`
writeFileSync(metaMdFilePath, parsedString)
}
catch (err) {
console.log(err)
}
})
function parseTypeFromSchema(schema: PropertyMetaSchema): string {
if (typeof schema === 'object' && (schema.kind === 'enum' || schema.kind === 'array')) {
const isFlatEnum = schema.schema?.every(val => typeof val === 'string')
const enumValue = schema?.schema?.filter(i => i !== 'undefined') ?? []
if (isFlatEnum && /^[A-Z]/.test(schema.type))
return enumValue.join(' | ')
else if (typeof schema.schema?.[0] === 'object' && schema.schema?.[0].kind === 'enum')
return schema.schema.map((s: PropertyMetaSchema) => parseTypeFromSchema(s)).join(' | ')
else
return schema.type
}
else if (typeof schema === 'object' && schema.kind === 'object') {
return schema.type
}
else if (typeof schema === 'string') {
return schema
}
else {
return ''
}
}
// Utilities
function parseMeta(meta: ComponentMeta) {
const props = meta.props
// Exclude global props
.filter(prop => !prop.global)
.map((prop) => {
let defaultValue = prop.default
const { name, description, required } = prop
if (name === 'as')
defaultValue = defaultValue ?? '"div"'
if (defaultValue === 'undefined')
defaultValue = undefined
return ({
name,
description: md.render(description),
type: prop.type.replace(/\s*\|\s*undefined/g, ''),
required,
default: defaultValue ?? undefined,
})
})
const events = meta.events
.map((event) => {
const { name, type } = event
return ({
name,
type: type.replace(/\s*\|\s*undefined/g, ''),
})
})
const defaultSlot = meta.slots?.[0]
const slots: { name: string, description: string, type: string }[] = []
if (defaultSlot && defaultSlot.type !== '{}') {
const schema = defaultSlot.schema
if (typeof schema === 'object' && schema.schema) {
Object.values(schema.schema).forEach((childMeta: PropertyMeta) => {
slots.push({
name: childMeta.name,
description: md.render(childMeta.description),
type: parseTypeFromSchema(childMeta.schema),
})
})
}
}
// exposed method
const methods = meta.exposed
.filter(expose => typeof expose.schema === 'object' && expose.schema.kind === 'event')
.map(expose => ({
name: expose.name,
description: md.render(expose.description),
type: expose.type,
}))
return {
props,
events,
slots,
methods,
}
}

View File

@ -1,6 +1,6 @@
import fs from 'node:fs'
import path, { basename } from 'node:path'
import template from 'lodash.template'
import { template } from 'lodash-es'
import { rimraf } from 'rimraf'
import { colorMapping, colors } from '../src/lib/registry/colors'
@ -40,7 +40,7 @@ for (const style of styles) {
file => `../src/lib/registry/${style.name}/${file}`,
)
const type = item.type.split(':')[1]
// const type = item.type.split(':')[1]
index += `
"${item.name}": {
name: "${item.name}",
@ -396,4 +396,4 @@ fs.writeFileSync(
'utf8',
)
console.log('✅ Done!')
console.log('✅ Done!!')

View File

@ -47,3 +47,5 @@ const { site, theme, page, frontmatter } = useData()
## More
Check out the documentation for the [full list of runtime APIs](https://vitepress.dev/reference/runtime-api#usedata).
kick

View File

@ -0,0 +1,107 @@
---
title: Charts
description: Versatile visualization tool, allowing users to represent data using various types of charts for effective analysis.
label: Alpha
---
<script setup>
import Area from '~icons/gravity-ui/chart-area-stacked'
import Bar from '~icons/gravity-ui/chart-column'
import Line from '~icons/gravity-ui/chart-line'
import Pie from '~icons/gravity-ui/chart-pie'
</script>
<Callout>
Only works with Vue >3.3
</Callout>
`Charts` components were built on top of [Unovis](https://unovis.dev/) (a modular data visualization framework), and inspired by [tremor](https://www.tremor.so).
## Chart type
<div class="grid gap-4 mt-8 sm:grid-cols-2 sm:gap-6 not-docs">
<LinkedCard href="/docs/charts/area">
<Area class="text-foreground/80 w-11 h-11" />
<p class="mt-2 font-medium">Area</p>
</LinkedCard>
<LinkedCard href="/docs/charts/line">
<Line class="text-foreground/80 w-11 h-11" />
<p class="mt-2 font-medium">Line</p>
</LinkedCard>
<LinkedCard href="/docs/charts/bar">
<Bar class="text-foreground/80 w-11 h-11" />
<p class="mt-2 font-medium">Bar</p>
</LinkedCard>
<LinkedCard href="/docs/charts/donut">
<Pie class="text-foreground/80 w-11 h-11" />
<p class="mt-2 font-medium">Donut</p>
</LinkedCard>
</div>
## Installation
<Steps>
### Update `css`
Add the following tooltip styling to your `tailwind.css` file:
```css
@layer base {
:root {
/* ... */
--vis-tooltip-background-color: none !important;
--vis-tooltip-border-color: none !important;
--vis-tooltip-text-color: none !important;
--vis-tooltip-shadow-color: none !important;
--vis-tooltip-backdrop-filter: none !important;
--vis-tooltip-padding: none !important;
--vis-primary-color: var(--primary);
/* change to any hsl value you want */
--vis-secondary-color: 160 81% 40%;
--vis-text-color: var(--muted-foreground);
}
```
If you are not using `css-variables` for your component, you need to update the `--vis-primary-color` and `--vis-text-color` to your desired hsl value.
You may use [this tool](https://redpixelthemes.com/blog/tailwindcss-colors-different-formats/) to help you find the hsl value for your primary color and text color. Be sure to provide `dark` mode styling as well.
</Steps>
## Colors
By default, we construct the primary theme color, and secondary (`--vis-secondary-color`) color with different opacity for the graph.
However, you can always pass in the desired `color` into each chart.
```vue
<template>
<AreaChart
:data="data"
:colors="['blue', 'pink', 'orange', 'red']"
/>
</template>
```
## Custom tooltip
If you want to customize the `Tooltip` for the chart, you can pass `customTooltip` prop with a custom Vue component.
The custom component would receive `title` and `data` props, check out [ChartTooltip.vue component](https://github.com/radix-vue/shadcn-vue/tree/dev/apps/www/src/lib/registry/default/ui/chart/ChartTooltip.vue) for example.
The expected prop definition would be:
```ts
defineProps<{
title?: string
data: {
name: string
color: string
value: any
}[]
}>()
```

View File

@ -0,0 +1,46 @@
---
title: Area
description: An area chart visually represents data over time, displaying trends and patterns through filled-in areas under a line graph.
source: apps/www/src/lib/registry/default/ui/chart-area
label: Alpha
---
<ComponentPreview name="AreaChartDemo" />
## Installation
<Callout>
Only works with Vue >3.3
</Callout>
<Steps>
### Run the following command
```bash
npx shadcn-vue@latest add chart-area
```
### Setup
Follow the [guide](/docs/charts.html#installation) to complete the setup.
</Steps>
## API
<!-- @include: @/content/meta/AreaChart.md -->
## Example
### Sparkline
We can turn the chart into sparkline chart by hiding axis, gridline and legends.
<ComponentPreview name="AreaChartSparkline" />
### Custom Tooltip
If you want to render custom tooltip, you can easily pass in a custom component. Refer to prop definition [here](/docs/charts.html#custom-tooltip).
<ComponentPreview name="AreaChartCustomTooltip" />

View File

@ -0,0 +1,50 @@
---
title: Bar
description: An line chart visually represents data using rectangular bars of varying lengths to compare quantities across different categories or groups.
source: apps/www/src/lib/registry/default/ui/chart-bar
label: Alpha
---
<ComponentPreview name="BarChartDemo" />
## Installation
<Callout>
Only works with Vue >3.3
</Callout>
<Steps>
### Run the following command
```bash
npx shadcn-vue@latest add chart-bar
```
### Setup
Follow the [guide](/docs/charts.html#installation) to complete the setup.
</Steps>
## API
<!-- @include: @/content/meta/BarChart.md -->
## Example
### Stacked
You can stack the bar chart by settings prop `type` to `stacked`.
<ComponentPreview name="BarChartStacked" />
### Rounded
<ComponentPreview name="BarChartRounded" />
### Custom Tooltip
If you want to render custom tooltip, you can easily pass in a custom component. Refer to prop definition [here](/docs/charts.html#custom-tooltip).
<ComponentPreview name="BarChartCustomTooltip" />

View File

@ -0,0 +1,52 @@
---
title: Donut
description: An line chart visually represents data in a circular form, similar to a pie chart but with a central void, emphasizing proportions within categories.
source: apps/www/src/lib/registry/default/ui/chart-donut
label: Alpha
---
<ComponentPreview name="DonutChartDemo" />
## Installation
<Callout>
Only works with Vue >3.3
</Callout>
<Steps>
### Run the following command
```bash
npx shadcn-vue@latest add chart-donut
```
### Setup
Follow the [guide](/docs/charts.html#installation) to complete the setup.
</Steps>
## API
<!-- @include: @/content/meta/DonutChart.md -->
## Example
### Pie Chart
If you want to render pie chart instead, pass `type` as `pie`.
<ComponentPreview name="DonutChartPie" />
### Color
We generate colors automatically based on the primary and secondary color and assigned them accordingly. Feel free to pass in your own array of colors.
<ComponentPreview name="DonutChartColor" />
### Custom Tooltip
If you want to render custom tooltip, you can easily pass in a custom component. Refer to prop definition [here](/docs/charts.html#custom-tooltip).
<ComponentPreview name="DonutChartCustomTooltip" />

View File

@ -0,0 +1,46 @@
---
title: Line
description: An line chart visually displays data points connected by straight lines, illustrating trends or relationships over a continuous axis.
source: apps/www/src/lib/registry/default/ui/chart-line
label: Alpha
---
<ComponentPreview name="LineChartDemo" />
## Installation
<Callout>
Only works with Vue >3.3
</Callout>
<Steps>
### Run the following command
```bash
npx shadcn-vue@latest add chart-line
```
### Setup
Follow the [guide](/docs/charts.html#installation) to complete the setup.
</Steps>
## API
<!-- @include: @/content/meta/LineChart.md -->
## Example
### Sparkline
We can turn the chart into sparkline chart by hiding axis, gridline and legends.
<ComponentPreview name="LineChartSparkline" />
### Custom Tooltip
If you want to render custom tooltip, you can easily pass in a custom component. Refer to prop definition [here](/docs/charts.html#custom-tooltip).
<ComponentPreview name="LineChartCustomTooltip" />

View File

@ -0,0 +1,6 @@
<script setup>
import { useRouter } from 'vitepress'
const router = useRouter()
router.go('/docs/components/accordion')
</script>

View File

@ -0,0 +1,550 @@
---
title: AutoForm
description: Automatically generate a form from Zod schema.
primitive: https://vee-validate.logaretm.com/v4/guide/overview/
---
<Callout class="mt-6">
Credit: Heavily inspired by [AutoForm](https://github.com/vantezzen/auto-form) by Vantezzen
</Callout>
## What is AutoForm
AutoForm is a drop-in form builder for your internal and low-priority forms with existing zod schemas. For example, if you already have zod schemas for your API and want to create a simple admin panel to edit user profiles, simply pass the schema to AutoForm and you're done.
## Installation
<Steps>
### Run the following command
```bash
npx shadcn-vue@latest update form
npx shadcn-vue@latest add auto-form
```
</Steps>
## Field types
Currently, these field types are supported out of the box:
- boolean (checkbox, switch)
- date (date picker)
- enum (select, radio group)
- number (input)
- string (input, textfield)
- file (file)
You can add support for other field types by adding them to the `INPUT_COMPONENTS` object in `auto-form/constants.ts`.
## Zod configuration
### Validations
Your form schema can use any of zod's validation methods including refine.
<Callout>
⚠️ However, there's a known issue with Zods `refine` and `superRefine` not executing whenever some object keys are missing.
[Read more](https://github.com/logaretm/vee-validate/issues/4338)
</Callout>
### Descriptions
You can use the `describe` method to set a label for each field. If no label is set, the field name will be used and un-camel-cased.
```ts
const formSchema = z.object({
username: z.string().describe('Your username'),
someValue: z.string(), // Will be "Some Value"
})
```
You can also configure the label with [`fieldConfig`](#label) too.
### Optional fields
By default, all fields are required. You can make a field optional by using the `optional` method.
```ts
const formSchema = z.object({
username: z.string().optional(),
})
```
### Default values
You can set a default value for a field using the `default` method.
```ts
const formSchema = z.object({
favouriteNumber: z.number().default(5),
})
```
If you want to set default value of date, convert it to Date first using `new Date(val)`.
### Sub-objects
You can nest objects to create accordion sections.
```ts
const formSchema = z.object({
address: z.object({
street: z.string(),
city: z.string(),
zip: z.string(),
// You can nest objects as deep as you want
nested: z.object({
foo: z.string(),
bar: z.string(),
nested: z.object({
foo: z.string(),
bar: z.string(),
}),
}),
}),
})
```
Like with normal objects, you can use the `describe` method to set a label and description for the section:
```ts
const formSchema = z.object({
address: z
.object({
street: z.string(),
city: z.string(),
zip: z.string(),
})
.describe('Your address'),
})
```
### Select/Enums
AutoForm supports `enum` and `nativeEnum` to create select fields.
```ts
const formSchema = z.object({
color: z.enum(['red', 'green', 'blue']),
})
enum BreadTypes {
// For native enums, you can alternatively define a backed enum to set a custom label
White = 'White bread',
Brown = 'Brown bread',
Wholegrain = 'Wholegrain bread',
Other,
}
// Keep in mind that zod will validate and return the enum labels, not the enum values!
const formSchema = z.object({
bread: z.nativeEnum(BreadTypes),
})
```
### Arrays
AutoForm supports arrays _of objects_. Because inferring things like field labels from arrays of strings/numbers/etc. is difficult, only objects are supported.
```ts
const formSchema = z.object({
guestListName: z.string(),
invitedGuests: z
.array(
// Define the fields for each item
z.object({
name: z.string(),
age: z.number(),
})
)
// Optionally set a custom label - otherwise this will be inferred from the field name
.describe('Guests invited to the party'),
})
```
Arrays are not supported as the root element of the form schema.
You also can set default value of an array using .default(), but please make sure the array element has same structure with the schema.
```ts
const formSchema = z.object({
guestListName: z.string(),
invitedGuests: z
.array(
// Define the fields for each item
z.object({
name: z.string(),
age: z.number(),
})
)
.describe('Guests invited to the party')
.default([
{ name: 'John', age: 24, },
{ name: 'Jane', age: 20, },
]),
})
```
## Field configuration
As zod doesn't allow adding other properties to the schema, you can use the `fieldConfig` prop to add additional configuration for the UI of each field.
```vue
<template>
<AutoForm
:field-config="{
username: {
// fieldConfig
},
}"
/>
</template>
```
### Label
You can use the `label` property to customize label if you want to overwrite the pre-defined label via [Zod's description](#descriptions).
```vue
<template>
<AutoForm
:field-config="{
username: {
label: 'Custom username',
},
}"
/>
</template>
```
### Description
You can use the `description` property to add a description below the field.
```vue
<template>
<AutoForm
:field-config="{
username: {
description: 'Enter a unique username. This will be shown to other users.',
},
}"
/>
</template>
```
### Input props
You can use the `inputProps` property to pass props to the input component. You can use any props that the HTML component accepts.
```vue
<template>
<AutoForm
:field-config="{
username: {
inputProps: {
type: 'text',
placeholder: 'Username',
},
},
}"
/>
</template>
// This will be rendered as:
<input type="text" placeholder="Username" />
```
Disabling the label of an input can be done by using the `showLabel` property in `inputProps`.
```vue
<template>
<AutoForm
:field-config="{
username: {
inputProps: {
type: 'text',
placeholder: 'Username',
showLabel: false,
},
},
}"
/>
</template>
```
### Component
By default, AutoForm will use the Zod type to determine which input component to use. You can override this by using the `component` property.
```vue
<template>
<AutoForm
:field-config="{
acceptTerms: {
// Booleans use a checkbox by default, use a switch instead
component: 'switch',
},
}"
/>
</template>
```
The complete list of supported field types is typed. Current supported types are:
- `checkbox` (default for booleans)
- `switch`
- `date` (default for dates)
- `select` (default for enums)
- `radio`
- `textarea`
Alternatively, you can pass a Vue component to the `component` property to use a custom component.
In `CustomField.vue`
```vue
<script setup lang="ts">
import { computed } from 'vue'
import AutoFormLabel from './AutoFormLabel.vue'
import type { FieldProps } from './interface'
import { FormControl, FormDescription, FormField, FormItem, FormMessage } from '@/ui/form'
import { Input } from '@/ui/input'
import { AutoFormLabel } from '@/ui/auto-form'
const props = defineProps<FieldProps>()
</script>
<template>
<FormField v-slot="slotProps" :name="fieldName">
<FormItem v-bind="$attrs">
<AutoFormLabel v-if="!config?.hideLabel" :required="required">
{{ config?.label }}
</AutoFormLabel>
<FormControl>
<CustomInput v-bind="slotProps" />
</FormControl>
<FormDescription v-if="config?.description">
{{ config.description }}
</FormDescription>
<FormMessage />
</FormItem>
</FormField>
</template>
```
Pass the above component in `fieldConfig`.
```vue
<template>
<AutoForm
:field-config="{
username: {
component: CustomField,
},
}"
/>
</template>
```
### Named slot
You can use Vue named slot to customize the rendered `AutoFormField`.
```vue
<template>
<AutoForm
:field-config="{
customParent: {
label: 'Wrapper',
},
}"
>
<template #customParent="slotProps">
<div class="flex items-end space-x-2">
<AutoFormField v-bind="slotProps" class="w-full" />
<Button type="button">
Check
</Button>
</div>
</template>
</AutoForm>
</template>
```
### Accessing the form data
There are two ways to access the form data:
### @submit
The preferred way is to use the `submit` emit. This will be called when the form is submitted and the data is valid.
```vue
<template>
<AutoForm
@submit="(data) => {
// Do something with the data
}"
/>
</template>
```
### Controlled form
By passing the `form` as props, you can control and use the method provided by `Form`.
```vue
<script setup lang="ts">
import * as z from 'zod'
import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/zod'
const schema = z.object({
username: z.string(),
})
const form = useForm({
validationSchema: toTypedSchema(schema),
})
form.setValues({
username: 'foo'
})
</script>
<template>
<AutoForm :form="form" :schema="schema" />
</template>
```
### Submitting the form
You can use any `button` component to create a submit button. Most importantly is to add attributes `type="submit"`.
```vue
<template>
<AutoForm>
<CustomButton type="submit">
Send now
</CustomButton>
</AutoForm>
// or
<AutoForm>
<button type="submit">
Send now
</button>
</AutoForm>
</template>
```
### Adding other elements
All children passed to the `AutoForm` component will be rendered below the form.
```vue
<template>
<AutoForm>
<Button>Send now</Button>
<p class="text-gray-500 text-sm">
By submitting this form, you agree to our
<a href="#" class="text-primary underline">
terms and conditions
</a>.
</p>
</AutoForm>
</template>
```
### Dependencies
AutoForm allows you to add dependencies between fields to control fields based on the value of other fields. For this, a `dependencies` array can be passed to the `AutoForm` component.
```vue
<template>
<AutoForm
:dependencies="[
{
// 'age' hides 'parentsAllowed' when the age is 18 or older
sourceField: 'age',
type: DependencyType.HIDES,
targetField: 'parentsAllowed',
when: age => age >= 18,
},
{
// 'vegetarian' checkbox hides the 'Beef Wellington' option from 'mealOptions'
// if its not already selected
sourceField: 'vegetarian',
type: DependencyType.SETS_OPTIONS,
targetField: 'mealOptions',
when: (vegetarian, mealOption) =>
vegetarian && mealOption !== 'Beef Wellington',
options: ['Pasta', 'Salad'],
},
]"
/>
</template>
```
The following dependency types are supported:
- `DependencyType.HIDES`: Hides the target field when the `when` function returns true
- `DependencyType.DISABLES`: Disables the target field when the `when` function returns true
- `DependencyType.REQUIRES`: Sets the target field to required when the `when` function returns true
- `DependencyType.SETS_OPTIONS`: Sets the options of the target field to the `options` array when the `when` function returns true
The `when` function is called with the value of the source field and the value of the target field and should return a boolean to indicate if the dependency should be applied.
Please note that dependencies will not cause the inverse action when returning `false` - for example, if you mark a field as required in your zod schema (i.e. by not explicitly setting `optional`), returning `false` in your `REQURIES` dependency will not mark it as optional. You should instead use zod's `optional` method to mark as optional by default and use the `REQURIES` dependency to mark it as required when the dependency is met.
Please note that dependencies do not have any effect on the validation of the form. You should use zod's `refine` method to validate the form based on the value of other fields.
You can create multiple dependencies for the same field and dependency type - for example to hide a field based on multiple other fields. This will then hide the field when any of the dependencies are met.
## Example
### Basic
<ComponentPreview name="AutoFormBasic" />
### Input Without Label
This example shows how to use AutoForm input without label.
<ComponentPreview name="AutoFormInputWithoutLabel" />
### Sub Object
Automatically generate a form from a Zod schema.
<ComponentPreview name="AutoFormSubObject" />
### Controlled
This example shows how to use AutoForm in a controlled way.
<ComponentPreview name="AutoFormControlled" />
### Confirm Password
Refined schema to validate that two fields match.
<ComponentPreview name="AutoFormConfirmPassword" />
### API Example
The form select options are fetched from an API.
<ComponentPreview name="AutoFormApi" />
### Array support
You can use arrays in your schemas to create dynamic forms.
<ComponentPreview name="AutoFormArray" />
### Dependencies
Create dependencies between fields.
<ComponentPreview name="AutoFormDependencies" />

View File

@ -2,90 +2,41 @@
title: Calendar
description: A date field component that allows users to enter and edit date.
source: apps/www/src/lib/registry/default/ui/calendar
primitive: https://vcalendar.io/
primitive: https://www.radix-vue.com/components/calendar.html
---
<ComponentPreview name="CalendarDemo" />
<ComponentPreview name="CalendarDemo" />
<Callout class="text-base mt-12">
If you're looking for **previous** Calendar implementation, checkout to <span class="font-bold underline">[VCalendar](/docs/components/v-calendar)</span> component
</Callout>
## About
The `Calendar` component is built on top of [VCalendar](https://vcalendar.io/getting-started/installation.html).
The `<Calendar />` component is built on top of the [RadixVue Calendar](https://www.radix-vue.com/components/calendar.html) component, which uses the [@internationalized/date](https://react-spectrum.adobe.com/internationalized/date/index.html) package to handle dates.
If you're looking for a range calendar, check out the [Range Calendar](/docs/components/range-calendar) component.
## Installation
<TabPreview name="CLI">
<template #CLI>
```bash
npx shadcn-vue@latest add calendar
```
</template>
<template #Manual>
## Datepicker
<Steps>
You can use the `<Calendar />` component to build a date picker. See the [Date Picker](/docs/components/date-picker) page for more information.
### Install the following dependency
## Examples
```bash
npm install v-calendar
```
### Form
### Copy and paste the following code into your project
<ComponentPreview name="CalendarForm" />
<<< @/lib/registry/default/ui/calendar/Calendar.vue
## Advanced Customization
</Steps>
### Month & Year Selects
</template>
</TabPreview>
## Usage
```vue
<script setup lang="ts">
import { Calendar } from '@/components/ui/calendar'
</script>
<template>
<Calendar />
</template>
```
The API is essentially the same, i.e. props and slots. See the [VCalendar](https://vcalendar.io/getting-started/installation.html) documentation for more information.
### Slots
The slots available are [those currently supported](https://github.com/nathanreyes/v-calendar/blob/v3.1.2/src/components/Calendar/CalendarSlot.vue#L16-L28) by VCalendar, namely :
- `day-content`
- `day-popover`
- `dp-footer`
- `footer`
- `header-title-wrapper`
- `header-title`
- `header-prev-button`
- `header-next-button`
- `nav`
- `nav-prev-button`
- `nav-next-button`
- `page`
- `time-header`
Example using the `day-content` slot:
```vue
<script setup lang="ts">
import { Calendar } from '@/components/ui/calendar'
</script>
<template>
<Calendar>
<template #day-content="{ day, dayProps, dayEvents }">
<div v-bind="dayProps" v-on="dayEvents">
{{ day.label }}
</div>
</template>
</Calendar>
</template>
```
<ComponentPreview name="CalendarWithSelect" />

View File

@ -1,79 +1,42 @@
---
title: Date Picker
description: A date picker component with range and presets.
source: apps/www/src/lib/registry/default/example/DatePickerDemo.vue
primitive: https://www.radix-vue.com/components/calendar.html
---
<ComponentPreview name="DatePickerDemo" />
<ComponentPreview name="DatePickerDemo" />
<Callout class="text-base mt-12">
If you're looking for **previous** Date Picker implementation, checkout to <span class="font-bold underline">[VCalendar Datepicker](/docs/components/v-date-picker)</span> component
</Callout>
## Installation
The Date Picker is built using a composition of the `<Popover />` and the `<Calendar />` components.
The Date Picker is built using a composition of the `<Popover />` and either the `<Calendar />` or `<RangeCalendar />` components.
See installation instructions for the [Popover](/docs/components/popover#installation) and the [Calendar](/docs/components/calendar#installation) components.
## Usage
```vue
<script setup lang="ts">
import { format } from 'date-fns'
import { Calendar as CalendarIcon } from 'lucide-vue-next'
import { ref } from 'vue'
import { cn } from '@/lib/utils'
import { Button } from '@/components/ui/button'
import { Calendar } from '@/components/ui/calendar'
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/popover'
const date = ref<Date>()
</script>
<template>
<Popover>
<PopoverTrigger as-child>
<Button
:variant="'outline'"
:class="cn(
'w-[280px] justify-start text-left font-normal',
!date && 'text-muted-foreground',
)"
>
<CalendarIcon class="mr-2 h-4 w-4" />
<span>{{ date ? format(date, "PPP") : "Pick a date" }}</span>
</Button>
</PopoverTrigger>
<PopoverContent class="w-auto p-0">
<Calendar v-model="date" />
</PopoverContent>
</Popover>
</template>
```
See installations instructions for the [Popover](/docs/components/popover), [Calendar](/docs/components/calendar), and [Range Calendar](/docs/components/range-calendar) components.
## Examples
### Date Picker
<ComponentPreview name="DatePickerDemo" />
<ComponentPreview name="DatePickerDemo" />
### Date Range Picker
<ComponentPreview name="DatePickerWithRange" />
<ComponentPreview name="DatePickerWithRange" />
### Date Time Picker
### Date Range Picker with Independent Months
<ComponentPreview name="DateTimePickerDemo" />
<ComponentPreview name="DatePickerWithIndependentMonths" />
### With Presets
<ComponentPreview name="DatePickerWithPresets" />
### With Slot
<ComponentPreview name="RangePickerWithSlot" />
<ComponentPreview name="DatePickerWithPresets" />
### Form
<ComponentPreview name="DatePickerForm" />
<ComponentPreview name="DatePickerForm" />

View File

@ -53,6 +53,13 @@ import {
</Drawer>
</template>
```
### Scale Background
If you want the background to have a zoom effect, you need to add the `vaul-drawer-wrapper` attribute to the root component.
```html
<div vaul-drawer-wrapper id="app"></div>
```
## Examples

View File

@ -23,7 +23,7 @@ The `<Form />` component is a wrapper around the `vee-validate` library. It prov
- Composable components for building forms.
- A `<FormField />` component for building controlled form fields.
- Form validation using `zod`.
- Applies the correct `aria` attributes to form fields based on states, handle unqiue IDs
- Applies the correct `aria` attributes to form fields based on states, handle unique IDs
- Built to work with all Radix Vue components.
- Bring your own schema library. We use `zod` but you can use any other supported schema validation you want, like [`yup`](https://github.com/jquense/yup) or [`valibot`](https://valibot.dev/).
- **You have full control over the markup and styling.**
@ -249,7 +249,7 @@ function onSubmit(values) {
### Build your form
Based on last step we can either use `<Form />` component or `useForm` composable
`useForm` is recommended cause values are typed automatically
`useForm` is recommended because values are typed automatically
```vue:line-numbers {2}
<script setup lang="ts">

View File

@ -29,7 +29,9 @@ import {
<PopoverTrigger>
Open popover
</PopoverTrigger>
<PopoverContent />
<PopoverContent>
Some popover content
</PopoverContent>
</Popover>
</template>
```

View File

@ -0,0 +1,18 @@
---
title: Range Calendar
description: A calendar component that allows users to select a range of dates.
source: apps/www/src/lib/registry/default/ui/range-calendar
primitive: https://www.radix-vue.com/components/range-calendar.html
---
<ComponentPreview name="RangeCalendarDemo" />
## About
The `<RangeCalendar />` component is built on top of the [RadixVue Range Calendar](https://www.radix-vue.com/components/date-range-picker.html) component, which uses the [@internationalized/date](https://react-spectrum.adobe.com/internationalized/date/index.html) package to handle dates.
## Installation
```bash
npx shadcn-vue@latest add range-calendar
```

View File

@ -45,11 +45,13 @@ import { useToast } from '@/components/ui/toast/use-toast'
<script setup lang="ts">
import { Button } from '@/components/ui/button'
import { useToast } from '@/components/ui/toast/use-toast'
import { Toaster } from '@/components/ui/toast'
const { toast } = useToast()
</script>
<template>
<Toaster />
<Button
@click="() => {
toast({

View File

@ -0,0 +1,91 @@
---
title: VCalendar
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/
---
<ComponentPreview name="VCalendarDemo" />
## About
The `Calendar` component is built on top of [VCalendar](https://vcalendar.io/getting-started/installation.html).
## Installation
<TabPreview name="CLI">
<template #CLI>
```bash
npx shadcn-vue@latest add v-calendar
```
</template>
<template #Manual>
<Steps>
### Install the following dependency
```bash
npm install v-calendar
```
### Copy and paste the following code into your project
<<< @/lib/registry/default/ui/v-calendar/Calendar.vue
</Steps>
</template>
</TabPreview>
## Usage
```vue
<script setup lang="ts">
import { Calendar } from '@/components/ui/v-calendar'
</script>
<template>
<Calendar />
</template>
```
The API is essentially the same, i.e. props and slots. See the [VCalendar](https://vcalendar.io/getting-started/installation.html) documentation for more information.
### Slots
The slots available are [those currently supported](https://github.com/nathanreyes/v-calendar/blob/v3.1.2/src/components/Calendar/CalendarSlot.vue#L16-L28) by VCalendar, namely :
- `day-content`
- `day-popover`
- `dp-footer`
- `footer`
- `header-title-wrapper`
- `header-title`
- `header-prev-button`
- `header-next-button`
- `nav`
- `nav-prev-button`
- `nav-next-button`
- `page`
- `time-header`
Example using the `day-content` slot:
```vue
<script setup lang="ts">
import { Calendar } from '@/components/ui/v-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,79 @@
---
title: VCalendar Date Picker
description: A date picker component with range and presets.
---
<ComponentPreview name="VDatePickerDemo" />
## Installation
The Date Picker is built using a composition of the `<Popover />` and the `<Calendar />` components.
See installation instructions for the [Popover](/docs/components/popover#installation) and the [Calendar](/docs/components/calendar#installation) components.
## Usage
```vue
<script setup lang="ts">
import { format } from 'date-fns'
import { Calendar as CalendarIcon } from 'lucide-vue-next'
import { ref } from 'vue'
import { cn } from '@/lib/utils'
import { Button } from '@/components/ui/button'
import { Calendar } from '@/components/ui/v-calendar'
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/popover'
const date = ref<Date>()
</script>
<template>
<Popover>
<PopoverTrigger as-child>
<Button
:variant="'outline'"
:class="cn(
'w-[280px] justify-start text-left font-normal',
!date && 'text-muted-foreground',
)"
>
<CalendarIcon class="mr-2 h-4 w-4" />
<span>{{ date ? format(date, "PPP") : "Pick a date" }}</span>
</Button>
</PopoverTrigger>
<PopoverContent class="w-auto p-0">
<Calendar v-model="date" />
</PopoverContent>
</Popover>
</template>
```
## Examples
### Date Picker
<ComponentPreview name="VDatePickerDemo" />
### Date Range Picker
<ComponentPreview name="VDatePickerWithRange" />
### Date Time Picker
<ComponentPreview name="VDateTimePickerDemo" />
### With Presets
<ComponentPreview name="VDatePickerWithPresets" />
### With Slot
<ComponentPreview name="VRangePickerWithSlot" />
### Form
<ComponentPreview name="VDatePickerForm" />

View File

@ -176,7 +176,7 @@ To do so, we have a helper function named [`useForwardPropsEmits`](https://www.r
To be more clear, the function `useForwardPropsEmits` takes in props and an optional emit function, and returns a
computed object that combines the parsed props and emits as props.
Here's an example from `Accordian` root component.
Here's an example from `Accordion` root component.
```vue
<script setup lang="ts">
@ -200,7 +200,7 @@ const forwarded = useForwardPropsEmits(props, emits)
</template>
```
As you can see, `AccordionRootEmits` and `AccordionRootProps` types are imported from radix, combined with `useForwardPropsEmits` and then are binded using `v-bind` syntaxt.
As you can see, `AccordionRootEmits` and `AccordionRootProps` types are imported from radix, combined with `useForwardPropsEmits` and then are binded using `v-bind` syntax.
### CSS Classes
There are cases when we want to accept `class` as a prop in our `shadcn/vue` component and then combine it with a default tailwind class on our `radix-vue` component via `cn` utility function.

View File

@ -24,7 +24,7 @@ npm install -D typescript
### Install TailwindCSS module
```bash
npm install -D @nuxtjs/tailwindcss
npx nuxi@latest module add @nuxtjs/tailwindcss
```
### Add `Nuxt` module
@ -37,7 +37,7 @@ npm install -D @nuxtjs/tailwindcss
Install the package below.
```bash
npm install -D shadcn-nuxt
npx nuxi@latest module add shadcn-nuxt
```
</TabMarkdown>

View File

@ -9,6 +9,12 @@ description: Install and configure Vite.
Start by creating a new Vue project using `vite`:
<Callout>
If you're using the JS template, `jsconfig.json` must exist for the CLI to run without errors.
</Callout>
```bash
# npm 6.x
npm create vite@latest my-vue-app --template vue-ts
@ -24,13 +30,19 @@ Install `tailwindcss` and its peer dependencies, then generate your `tailwind.co
<TabsMarkdown>
<TabMarkdown title="vite.config">
Vite already has [`postcss`](https://github.com/vitejs/vite/blob/main/packages/vite/package.json#L78) dependency so you don't have to install it again in your package.json
Vite already has [`postcss`](https://github.com/vitejs/vite/blob/main/packages/vite/package.json#89) dependency so you don't have to install it again in your package.json
```bash
npm install -D tailwindcss autoprefixer
```
#### `vite.config`
<Callout>
If you're utilizing `postcss.config.js`, these changes will be inconsequential.
</Callout>
#### `vite.config`
```typescript {5,6,9-13}
import path from "path"
@ -77,9 +89,9 @@ Install `tailwindcss` and its peer dependencies, then generate your `tailwind.co
</TabMarkdown>
</TabsMarkdown>
### Edit tsconfig.json
### Edit tsconfig/jsconfig.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` or `jsconfig.json` so your app can resolve paths without error
```json {4-7}
{
@ -126,6 +138,10 @@ export default defineConfig({
})
```
### Delete default Vite styles
Delete the default Vite stylesheet `./src/style.css`
### Run the CLI
Run the `shadcn-vue` init command to setup your project:
@ -151,6 +167,19 @@ Configure the import alias for components: @/components
Configure the import alias for utils: @/lib/utils
```
### Update main.ts
Remove import for style.css and add tailwind style import `import './assets/index.css'`
```diff typescript {2,4}
import { createApp } from 'vue'
- import './style.css'
import App from './App.vue'
+ import './assets/index.css'
createApp(App).mount('#app')
```
### That's it
You can now start adding components to your project.

View File

@ -0,0 +1,116 @@
<!-- This file was automatic generated. Do not edit it manually -->
<APITable :type="'prop'" :data="[
{
'name': 'data',
'description': '<p>The source data, in which each entry is a dictionary.</p>\n',
'type': 'Record<string, any>[]',
'required': true
},
{
'name': 'categories',
'description': '<p>Select the categories from your data. Used to populate the legend and toolip.</p>\n',
'type': 'string[]',
'required': true
},
{
'name': 'index',
'description': '<p>Sets the key to map the data to the axis.</p>\n',
'type': 'string',
'required': true
},
{
'name': 'colors',
'description': '<p>Change the default colors.</p>\n',
'type': 'string[]',
'required': false
},
{
'name': 'margin',
'description': '<p>Margin of each the container</p>\n',
'type': 'Spacing',
'required': false,
'default': '{ top: 0, bottom: 0, left: 0, right: 0 }'
},
{
'name': 'filterOpacity',
'description': '<p>Change the opacity of the non-selected field</p>\n',
'type': 'number',
'required': false,
'default': '0.2'
},
{
'name': 'xFormatter',
'description': '<p>Function to format X label</p>\n',
'type': '((tick: number | Date, i: number, ticks: number[] | Date[]) => string)',
'required': false
},
{
'name': 'yFormatter',
'description': '<p>Function to format Y label</p>\n',
'type': '((tick: number | Date, i: number, ticks: number[] | Date[]) => string)',
'required': false
},
{
'name': 'showXAxis',
'description': '<p>Controls the visibility of the X axis.</p>\n',
'type': 'boolean',
'required': false,
'default': 'true'
},
{
'name': 'showYAxis',
'description': '<p>Controls the visibility of the Y axis.</p>\n',
'type': 'boolean',
'required': false,
'default': 'true'
},
{
'name': 'showTooltip',
'description': '<p>Controls the visibility of tooltip.</p>\n',
'type': 'boolean',
'required': false,
'default': 'true'
},
{
'name': 'showLegend',
'description': '<p>Controls the visibility of legend.</p>\n',
'type': 'boolean',
'required': false,
'default': 'true'
},
{
'name': 'showGridLine',
'description': '<p>Controls the visibility of gridline.</p>\n',
'type': 'boolean',
'required': false,
'default': 'true'
},
{
'name': 'customTooltip',
'description': '<p>Render custom tooltip component.</p>\n',
'type': 'Component',
'required': false
},
{
'name': 'curveType',
'description': '<p>Type of curve</p>\n',
'type': 'CurveType',
'required': false,
'default': 'CurveType.MonotoneX'
},
{
'name': 'showGradiant',
'description': '<p>Controls the visibility of gradient.</p>\n',
'type': 'boolean',
'required': false,
'default': 'true'
}
]" />
<APITable :type="'emit'" :data="[
{
'name': 'legendItemClick',
'type': '[d: BulletLegendItemInterface, i: number]'
}
]" />

View File

@ -0,0 +1,116 @@
<!-- This file was automatic generated. Do not edit it manually -->
<APITable :type="'prop'" :data="[
{
'name': 'data',
'description': '<p>The source data, in which each entry is a dictionary.</p>\n',
'type': 'Record<string, any>[]',
'required': true
},
{
'name': 'categories',
'description': '<p>Select the categories from your data. Used to populate the legend and toolip.</p>\n',
'type': 'string[]',
'required': true
},
{
'name': 'index',
'description': '<p>Sets the key to map the data to the axis.</p>\n',
'type': 'string',
'required': true
},
{
'name': 'colors',
'description': '<p>Change the default colors.</p>\n',
'type': 'string[]',
'required': false
},
{
'name': 'margin',
'description': '<p>Margin of each the container</p>\n',
'type': 'Spacing',
'required': false,
'default': '{ top: 0, bottom: 0, left: 0, right: 0 }'
},
{
'name': 'filterOpacity',
'description': '<p>Change the opacity of the non-selected field</p>\n',
'type': 'number',
'required': false,
'default': '0.2'
},
{
'name': 'xFormatter',
'description': '<p>Function to format X label</p>\n',
'type': '((tick: number | Date, i: number, ticks: number[] | Date[]) => string)',
'required': false
},
{
'name': 'yFormatter',
'description': '<p>Function to format Y label</p>\n',
'type': '((tick: number | Date, i: number, ticks: number[] | Date[]) => string)',
'required': false
},
{
'name': 'showXAxis',
'description': '<p>Controls the visibility of the X axis.</p>\n',
'type': 'boolean',
'required': false,
'default': 'true'
},
{
'name': 'showYAxis',
'description': '<p>Controls the visibility of the Y axis.</p>\n',
'type': 'boolean',
'required': false,
'default': 'true'
},
{
'name': 'showTooltip',
'description': '<p>Controls the visibility of tooltip.</p>\n',
'type': 'boolean',
'required': false,
'default': 'true'
},
{
'name': 'showLegend',
'description': '<p>Controls the visibility of legend.</p>\n',
'type': 'boolean',
'required': false,
'default': 'true'
},
{
'name': 'showGridLine',
'description': '<p>Controls the visibility of gridline.</p>\n',
'type': 'boolean',
'required': false,
'default': 'true'
},
{
'name': 'customTooltip',
'description': '<p>Render custom tooltip component.</p>\n',
'type': 'Component',
'required': false
},
{
'name': 'type',
'description': '<p>Change the type of the chart</p>\n',
'type': '\'stacked\' | \'grouped\'',
'required': false,
'default': '\'grouped\''
},
{
'name': 'roundedCorners',
'description': '<p>Rounded bar corners</p>\n',
'type': 'number',
'required': false,
'default': '0'
}
]" />
<APITable :type="'emit'" :data="[
{
'name': 'legendItemClick',
'type': '[d: BulletLegendItemInterface, i: number]'
}
]" />

View File

@ -0,0 +1,29 @@
<!-- This file was automatic generated. Do not edit it manually -->
<APITable :type="'prop'" :data="[
{
'name': 'colors',
'description': '',
'type': 'string[]',
'required': false,
'default': '[]'
},
{
'name': 'index',
'description': '',
'type': 'string',
'required': true
},
{
'name': 'items',
'description': '',
'type': 'BulletLegendItemInterface[]',
'required': true
},
{
'name': 'customTooltip',
'description': '',
'type': 'Component',
'required': false
}
]" />

View File

@ -0,0 +1,22 @@
<!-- This file was automatic generated. Do not edit it manually -->
<APITable :type="'prop'" :data="[
{
'name': 'items',
'description': '',
'type': 'BulletLegendItemInterface[]',
'required': false,
'default': '[]'
}
]" />
<APITable :type="'emit'" :data="[
{
'name': 'legendItemClick',
'type': '[d: BulletLegendItemInterface, i: number]'
},
{
'name': 'update:items',
'type': '[payload: BulletLegendItemInterface[]]'
}
]" />

View File

@ -0,0 +1,43 @@
<!-- This file was automatic generated. Do not edit it manually -->
<APITable :type="'prop'" :data="[
{
'name': 'valueFormatter',
'description': '',
'type': '((tick: number, i?: number, ticks?: number[]) => string)',
'required': false,
'default': '`${tick}`'
},
{
'name': 'index',
'description': '',
'type': 'string',
'required': true
},
{
'name': 'items',
'description': '',
'type': 'BulletLegendItemInterface[]',
'required': false
},
{
'name': 'customTooltip',
'description': '',
'type': 'Component',
'required': false
},
{
'name': 'selector',
'description': '',
'type': 'string',
'required': true
}
]" />
<APITable :type="'method'" :data="[
{
'name': 'valueFormatter',
'description': '',
'type': '(tick: number, i?: number | undefined, ticks?: number[] | undefined) => string'
}
]" />

View File

@ -0,0 +1,16 @@
<!-- This file was automatic generated. Do not edit it manually -->
<APITable :type="'prop'" :data="[
{
'name': 'title',
'description': '',
'type': 'string',
'required': false
},
{
'name': 'data',
'description': '',
'type': '{ name: string; color: string; value: any; }[]',
'required': true
}
]" />

View File

@ -0,0 +1,82 @@
<!-- This file was automatic generated. Do not edit it manually -->
<APITable :type="'prop'" :data="[
{
'name': 'colors',
'description': '<p>Change the default colors.</p>\n',
'type': 'string[]',
'required': false
},
{
'name': 'index',
'description': '<p>Sets the key to map the data to the axis.</p>\n',
'type': 'string',
'required': true
},
{
'name': 'data',
'description': '<p>The source data, in which each entry is a dictionary.</p>\n',
'type': 'Record<string, any>[]',
'required': true
},
{
'name': 'margin',
'description': '<p>Margin of each the container</p>\n',
'type': 'Spacing',
'required': false,
'default': '{ top: 0, bottom: 0, left: 0, right: 0 }'
},
{
'name': 'filterOpacity',
'description': '<p>Change the opacity of the non-selected field</p>\n',
'type': 'number',
'required': false,
'default': '0.2'
},
{
'name': 'showTooltip',
'description': '<p>Controls the visibility of tooltip.</p>\n',
'type': 'boolean',
'required': false,
'default': 'true'
},
{
'name': 'showLegend',
'description': '<p>Controls the visibility of legend.</p>\n',
'type': 'boolean',
'required': false,
'default': 'true'
},
{
'name': 'category',
'description': '<p>Sets the name of the key containing the quantitative chart values.</p>\n',
'type': 'string',
'required': true
},
{
'name': 'type',
'description': '<p>Change the type of the chart</p>\n',
'type': '\'donut\' | \'pie\'',
'required': false,
'default': '\'donut\''
},
{
'name': 'sortFunction',
'description': '<p>Function to sort the segment</p>\n',
'type': '((a: any, b: any) => number)',
'required': false
},
{
'name': 'valueFormatter',
'description': '<p>Controls the formatting for the label.</p>\n',
'type': '((tick: number, i?: number, ticks?: number[]) => string)',
'required': false,
'default': '`${tick}`'
},
{
'name': 'customTooltip',
'description': '<p>Render custom tooltip component.</p>\n',
'type': 'Component',
'required': false
}
]" />

View File

@ -0,0 +1,109 @@
<!-- This file was automatic generated. Do not edit it manually -->
<APITable :type="'prop'" :data="[
{
'name': 'data',
'description': '<p>The source data, in which each entry is a dictionary.</p>\n',
'type': 'Record<string, any>[]',
'required': true
},
{
'name': 'categories',
'description': '<p>Select the categories from your data. Used to populate the legend and toolip.</p>\n',
'type': 'string[]',
'required': true
},
{
'name': 'index',
'description': '<p>Sets the key to map the data to the axis.</p>\n',
'type': 'string',
'required': true
},
{
'name': 'colors',
'description': '<p>Change the default colors.</p>\n',
'type': 'string[]',
'required': false
},
{
'name': 'margin',
'description': '<p>Margin of each the container</p>\n',
'type': 'Spacing',
'required': false,
'default': '{ top: 0, bottom: 0, left: 0, right: 0 }'
},
{
'name': 'filterOpacity',
'description': '<p>Change the opacity of the non-selected field</p>\n',
'type': 'number',
'required': false,
'default': '0.2'
},
{
'name': 'xFormatter',
'description': '<p>Function to format X label</p>\n',
'type': '((tick: number | Date, i: number, ticks: number[] | Date[]) => string)',
'required': false
},
{
'name': 'yFormatter',
'description': '<p>Function to format Y label</p>\n',
'type': '((tick: number | Date, i: number, ticks: number[] | Date[]) => string)',
'required': false
},
{
'name': 'showXAxis',
'description': '<p>Controls the visibility of the X axis.</p>\n',
'type': 'boolean',
'required': false,
'default': 'true'
},
{
'name': 'showYAxis',
'description': '<p>Controls the visibility of the Y axis.</p>\n',
'type': 'boolean',
'required': false,
'default': 'true'
},
{
'name': 'showTooltip',
'description': '<p>Controls the visibility of tooltip.</p>\n',
'type': 'boolean',
'required': false,
'default': 'true'
},
{
'name': 'showLegend',
'description': '<p>Controls the visibility of legend.</p>\n',
'type': 'boolean',
'required': false,
'default': 'true'
},
{
'name': 'showGridLine',
'description': '<p>Controls the visibility of gridline.</p>\n',
'type': 'boolean',
'required': false,
'default': 'true'
},
{
'name': 'customTooltip',
'description': '<p>Render custom tooltip component.</p>\n',
'type': 'Component',
'required': false
},
{
'name': 'curveType',
'description': '<p>Type of curve</p>\n',
'type': 'CurveType',
'required': false,
'default': 'CurveType.MonotoneX'
}
]" />
<APITable :type="'emit'" :data="[
{
'name': 'legendItemClick',
'type': '[d: BulletLegendItemInterface, i: number]'
}
]" />

View File

@ -1,21 +1,28 @@
<script setup lang="ts">
import { addDays, format } from 'date-fns'
import { Calendar as CalendarIcon } from 'lucide-vue-next'
import type { DateRange } from 'radix-vue'
import { CalendarDate, DateFormatter, getLocalTimeZone } from '@internationalized/date'
import { ref } from 'vue'
import { type Ref, ref } from 'vue'
import { cn } from '@/lib/utils'
import { Button } from '@/lib/registry/new-york/ui/button'
import { Calendar } from '@/lib/registry/new-york/ui/calendar'
import { RangeCalendar } from '@/lib/registry/new-york/ui/range-calendar'
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/lib/registry/new-york/ui/popover'
const date = ref({
start: new Date(2023, 0, 20),
end: addDays(new Date(2023, 0, 20), 20),
const df = new DateFormatter('en-US', {
dateStyle: 'medium',
})
const calendarDate = new CalendarDate(2023, 0, 20)
const value = ref({
start: calendarDate,
end: calendarDate.add({ days: 20 }),
}) as Ref<DateRange>
</script>
<template>
@ -26,24 +33,34 @@ const date = ref({
id="date"
:variant="'outline'"
:class="cn(
'w-[260px] justify-start text-left font-normal',
!date && 'text-muted-foreground',
'w-[300px] justify-start text-left font-normal',
!value && '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>
<template v-if="value.start">
<template v-if="value.end">
{{ df.format(value.start.toDate(getLocalTimeZone())) }} - {{ df.format(value.end.toDate(getLocalTimeZone())) }}
</template>
<template v-else>
{{ df.format(value.start.toDate(getLocalTimeZone())) }}
</template>
</template>
<template v-else>
Pick a date
</template>
</Button>
</PopoverTrigger>
<PopoverContent class="w-auto p-0" :align="'end'">
<Calendar
v-model.range="date"
:columns="2"
<PopoverContent class="w-auto p-0" align="end">
<RangeCalendar
v-model="value"
weekday-format="short"
:number-of-months="2"
initial-focus
:placeholder="value.start"
@update:start-value="(startDate) => value.start = startDate"
/>
</PopoverContent>
</Popover>

View File

@ -1,7 +1,6 @@
<script setup lang="ts">
import { VisAxis, VisStackedBar, VisXYContainer } from '@unovis/vue'
import { BarChart } from '@/lib/registry/new-york/ui/chart-bar'
type Data = typeof data[number]
const data = [
{ name: 'Jan', total: Math.floor(Math.random() * 5000) + 1000 },
{ name: 'Feb', total: Math.floor(Math.random() * 5000) + 1000 },
@ -19,28 +18,5 @@ const data = [
</script>
<template>
<VisXYContainer height="350px" :margin="{ left: 20, right: 20 }" :data="data">
<VisStackedBar
:x="(d: Data, i: number) => i"
:y="(d: Data) => d.total"
color="#41b883"
:rounded-corners="4"
:bar-padding="0.15"
/>
<VisAxis
type="x"
:num-ticks="data.length"
:tick-format="(index: number) => data[index]?.name"
:grid-line="false"
:tick-line="false" color="#888888"
/>
<VisAxis
type="y"
:num-ticks="data.length"
:tick-format="(index: number) => data[index]?.name"
:grid-line="false"
:tick-line="false"
:domain-line="false" color="#888888"
/>
</VisXYContainer>
<BarChart :data="data" :categories="['total']" :index="'name'" :rounded-corners="4" />
</template>

View File

@ -1,14 +1,15 @@
<script setup lang="ts">
import { h, ref } from 'vue'
import * as z from 'zod'
import { format } from 'date-fns'
import { toDate } from 'radix-vue/date'
import { toTypedSchema } from '@vee-validate/zod'
import { Check, ChevronsUpDown } from 'lucide-vue-next'
import { CalendarDate, DateFormatter, getLocalTimeZone, today } from '@internationalized/date'
import { cn } from '@/lib/utils'
import RadixIconsCalendar from '~icons/radix-icons/calendar'
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/lib/registry/default/ui/form'
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/lib/registry/new-york/ui/form'
import { Input } from '@/lib/registry/new-york/ui/input'
import { Separator } from '@/lib/registry/new-york/ui/separator'
import {
@ -18,17 +19,19 @@ import {
CommandInput,
CommandItem,
CommandList,
} from '@/lib/registry/default/ui/command'
} from '@/lib/registry/new-york/ui/command'
import { Button } from '@/lib/registry/new-york/ui/button'
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/lib/registry/default/ui/popover'
} from '@/lib/registry/new-york/ui/popover'
import { Calendar } from '@/lib/registry/new-york/ui/calendar'
import { toast } from '@/lib/registry/new-york/ui/toast'
const open = ref(false)
const dateValue = ref()
const placeholder = ref()
const languages = [
{ label: 'English', value: 'en' },
@ -42,25 +45,25 @@ const languages = [
{ label: 'Chinese', value: 'zh' },
] as const
const df = new DateFormatter('en-US', {
dateStyle: 'long',
})
const accountFormSchema = toTypedSchema(z.object({
name: z
.string()
.string({
required_error: 'Required.',
})
.min(2, {
message: 'Name must be at least 2 characters.',
})
.max(30, {
message: 'Name must not be longer than 30 characters.',
}),
dob: z.date({
required_error: 'A date of birth is required.',
}),
language: z.string().nonempty({
message: 'Please select a language.',
}),
dob: z.string().datetime().optional().refine(date => date !== undefined, 'Please select a valid date.'),
language: z.string().min(1, 'Please select a language.'),
}))
const filterFunction = (list: typeof languages, search: string) => list.filter(i => i.value.toLowerCase().includes(search.toLowerCase()))
// https://github.com/logaretm/vee-validate/issues/3521
// https://github.com/logaretm/vee-validate/discussions/3571
async function onSubmit(values: any) {
@ -95,7 +98,7 @@ async function onSubmit(values: any) {
</FormItem>
</FormField>
<FormField v-slot="{ componentField, value }" name="dob">
<FormField v-slot="{ field, value }" name="dob">
<FormItem class="flex flex-col">
<FormLabel>Date of birth</FormLabel>
<Popover>
@ -103,17 +106,38 @@ async function onSubmit(values: any) {
<FormControl>
<Button
variant="outline" :class="cn(
'w-[280px] pl-3 text-left font-normal',
'w-[240px] justify-start text-left font-normal',
!value && 'text-muted-foreground',
)"
>
<span>{{ value ? format(value, "PPP") : "Pick a date" }}</span>
<RadixIconsCalendar class="ml-auto h-4 w-4 opacity-50" />
<RadixIconsCalendar class="mr-2 h-4 w-4 opacity-50" />
<span>{{ value ? df.format(toDate(dateValue, getLocalTimeZone())) : "Pick a date" }}</span>
</Button>
</FormControl>
</PopoverTrigger>
<PopoverContent class="p-0">
<Calendar v-bind="componentField" />
<Calendar
v-model:placeholder="placeholder"
v-model="dateValue"
calendar-label="Date of birth"
initial-focus
:min-value="new CalendarDate(1900, 1, 1)"
:max-value="today(getLocalTimeZone())"
@update:model-value="(v) => {
if (v) {
dateValue = v
setValues({
dob: toDate(v).toISOString(),
}, false)
}
else {
dateValue = undefined
setValues({
dob: undefined,
}, false)
}
}"
/>
</PopoverContent>
</Popover>
<FormDescription>
@ -121,6 +145,7 @@ async function onSubmit(values: any) {
</FormDescription>
<FormMessage />
</FormItem>
<input type="hidden" v-bind="field">
</FormField>
<FormField v-slot="{ value }" name="language">
@ -155,7 +180,7 @@ async function onSubmit(values: any) {
@select="() => {
setValues({
language: language.value,
})
}, false)
open = false
}"
>

View File

@ -41,7 +41,7 @@ const onSubmit = handleSubmit((values) => {
<template>
<div>
<h3 class="text-lg font-medium">
Appearence
Appearance
</h3>
<p class="text-sm text-muted-foreground">
Customize the appearance of the app. Automatically switch between day and night themes.

View File

@ -218,7 +218,7 @@ import {
</MenubarRadioGroup>
<MenubarSeparator />
<MenubarItem inset>
Manage Famliy...
Manage Family...
</MenubarItem>
<MenubarSeparator />
<MenubarItem inset>

View File

@ -0,0 +1,23 @@
<script setup lang="ts">
import CustomChartTooltip from './CustomChartTooltip.vue'
import { AreaChart } from '@/lib/registry/default/ui/chart-area'
const data = [
{ name: 'Jan', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Feb', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Mar', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Apr', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'May', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jun', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jul', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
]
</script>
<template>
<AreaChart
index="name"
:data="data"
:categories="['total', 'predicted']"
:custom-tooltip="CustomChartTooltip"
/>
</template>

View File

@ -0,0 +1,17 @@
<script setup lang="ts">
import { AreaChart } from '@/lib/registry/default/ui/chart-area'
const data = [
{ name: 'Jan', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Feb', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Mar', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Apr', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'May', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jun', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jul', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
]
</script>
<template>
<AreaChart :data="data" index="name" :categories="['total', 'predicted']" />
</template>

View File

@ -0,0 +1,33 @@
<script setup lang="ts">
import { CurveType } from '@unovis/ts'
import { AreaChart } from '@/lib/registry/default/ui/chart-area'
const data = [
{ name: 'Jan', total: Math.floor(Math.random() * 2000) + 1000 },
{ name: 'Feb', total: Math.floor(Math.random() * 2000) + 1000 },
{ name: 'Mar', total: Math.floor(Math.random() * 2000) + 1000 },
{ name: 'Apr', total: Math.floor(Math.random() * 2000) + 1000 },
{ name: 'May', total: Math.floor(Math.random() * 2000) + 1000 },
{ name: 'Jun', total: Math.floor(Math.random() * 2000) + 1000 },
{ name: 'Jul', total: Math.floor(Math.random() * 2000) + 1000 },
{ name: 'Aug', total: Math.floor(Math.random() * 2000) + 1000 },
{ name: 'Sep', total: Math.floor(Math.random() * 2000) + 1000 },
{ name: 'Oct', total: Math.floor(Math.random() * 2000) + 1000 },
{ name: 'Nov', total: Math.floor(Math.random() * 2000) + 1000 },
{ name: 'Dec', total: Math.floor(Math.random() * 2000) + 1000 },
]
</script>
<template>
<AreaChart
class="h-[100px] w-[400px]"
index="name"
:data="data"
:categories="['total']"
:show-grid-line="false"
:show-legend="false"
:show-x-axis="false"
:show-y-axis="false"
:curve-type="CurveType.Linear"
/>
</template>

View File

@ -0,0 +1,45 @@
<script setup lang="ts">
import * as z from 'zod'
import { h, onMounted, ref, shallowRef } from 'vue'
import { Button } from '@/lib/registry/default/ui/button'
import { toast } from '@/lib/registry/default/ui/toast'
import { AutoForm } from '@/lib/registry/default/ui/auto-form'
const schema = shallowRef<z.ZodObject< any, any, any > | null>(null)
onMounted(() => {
fetch('https://jsonplaceholder.typicode.com/users')
.then(response => response.json())
.then((data) => {
schema.value = z.object({
user: z.enum(data.map((user: any) => user.name)),
})
})
})
function onSubmit(values: Record<string, any>) {
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(values, null, 2))),
})
}
</script>
<template>
<div class="flex justify-center w-full">
<AutoForm
v-if="schema"
class="w-2/3 space-y-6"
:schema="schema"
@submit="onSubmit"
>
<Button type="submit">
Submit
</Button>
</AutoForm>
<div v-else>
Loading...
</div>
</div>
</template>

View File

@ -0,0 +1,38 @@
<script setup lang="ts">
import * as z from 'zod'
import { h } from 'vue'
import { Button } from '@/lib/registry/default/ui/button'
import { toast } from '@/lib/registry/default/ui/toast'
import { AutoForm } from '@/lib/registry/default/ui/auto-form'
const schema = z.object({
guestListName: z.string(),
invitedGuests: z
.array(
z.object({
name: z.string(),
age: z.coerce.number(),
}),
)
.describe('Guests invited to the party'),
})
function onSubmit(values: Record<string, any>) {
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(values, null, 2))),
})
}
</script>
<template>
<AutoForm
class="w-2/3 space-y-6"
:schema="schema"
@submit="onSubmit"
>
<Button type="submit">
Submit
</Button>
</AutoForm>
</template>

View File

@ -0,0 +1,161 @@
<script setup lang="ts">
import * as z from 'zod'
import { h, reactive, ref } from 'vue'
import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/zod'
import { DependencyType } from '../ui/auto-form/interface'
import { Button } from '@/lib/registry/default/ui/button'
import { toast } from '@/lib/registry/default/ui/toast'
import type { Config } from '@/lib/registry/default/ui/auto-form'
import { AutoForm, AutoFormField } from '@/lib/registry/default/ui/auto-form'
enum Sports {
Football = 'Football/Soccer',
Basketball = 'Basketball',
Baseball = 'Baseball',
Hockey = 'Hockey (Ice)',
None = 'I don\'t like sports',
}
const schema = z.object({
username: z
.string({
required_error: 'Username is required.',
})
.min(2, {
message: 'Username must be at least 2 characters.',
}),
password: z
.string({
required_error: 'Password is required.',
})
.min(8, {
message: 'Password must be at least 8 characters.',
}),
favouriteNumber: z.coerce
.number({
invalid_type_error: 'Favourite number must be a number.',
})
.min(1, {
message: 'Favourite number must be at least 1.',
})
.max(10, {
message: 'Favourite number must be at most 10.',
})
.default(1)
.optional(),
acceptTerms: z
.boolean()
.refine(value => value, {
message: 'You must accept the terms and conditions.',
path: ['acceptTerms'],
}),
sendMeMails: z.boolean().optional(),
birthday: z.coerce.date().optional(),
color: z.enum(['red', 'green', 'blue']).optional(),
// Another enum example
marshmallows: z
.enum(['not many', 'a few', 'a lot', 'too many']),
// Native enum example
sports: z.nativeEnum(Sports).describe('What is your favourite sport?'),
bio: z
.string()
.min(10, {
message: 'Bio must be at least 10 characters.',
})
.max(160, {
message: 'Bio must not be longer than 30 characters.',
})
.optional(),
customParent: z.string().optional(),
file: z.string().optional(),
})
function onSubmit(values: Record<string, any>) {
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(values, null, 2))),
})
}
</script>
<template>
<AutoForm
class="w-2/3 space-y-6"
:schema="schema"
:field-config="{
password: {
label: 'Your secure password',
inputProps: {
type: 'password',
placeholder: '••••••••',
},
},
favouriteNumber: {
description: 'Your favourite number between 1 and 10.',
},
acceptTerms: {
label: 'Accept terms and conditions.',
inputProps: {
required: true,
},
},
birthday: {
description: 'We need your birthday to send you a gift.',
},
sendMeMails: {
component: 'switch',
},
bio: {
component: 'textarea',
},
marshmallows: {
label: 'How many marshmallows fit in your mouth?',
component: 'radio',
},
file: {
label: 'Text file',
component: 'file',
},
}"
@submit="onSubmit"
>
<template #acceptTerms="slotProps">
<AutoFormField v-bind="slotProps" />
<div class="!mt-2 text-sm">
I agree to the <button class="text-primary underline">
terms and conditions
</button>.
</div>
</template>
<template #customParent="slotProps">
<div class="flex items-end space-x-2">
<AutoFormField v-bind="slotProps" class="w-full" />
<Button type="button">
Check
</Button>
</div>
</template>
<Button type="submit">
Submit
</Button>
</AutoForm>
</template>

View File

@ -0,0 +1,36 @@
<script setup lang="ts">
import * as z from 'zod'
import { h } from 'vue'
import { Button } from '@/lib/registry/default/ui/button'
import { toast } from '@/lib/registry/default/ui/toast'
import { AutoForm } from '@/lib/registry/default/ui/auto-form'
const schema = z
.object({
password: z.string(),
confirm: z.string(),
})
.refine(data => data.password === data.confirm, {
message: 'Passwords must match.',
path: ['confirm'],
})
function onSubmit(values: Record<string, any>) {
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(values, null, 2))),
})
}
</script>
<template>
<AutoForm
class="w-2/3 space-y-6"
:schema="schema"
@submit="onSubmit"
>
<Button type="submit">
Submit
</Button>
</AutoForm>
</template>

View File

@ -0,0 +1,37 @@
<script setup lang="ts">
import * as z from 'zod'
import { h } from 'vue'
import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/zod'
import { Button } from '@/lib/registry/default/ui/button'
import { toast } from '@/lib/registry/default/ui/toast'
import { AutoForm } from '@/lib/registry/default/ui/auto-form'
const schema = z.object({
username: z.string(),
})
const form = useForm({
validationSchema: toTypedSchema(schema),
})
function onSubmit(values: Record<string, any>) {
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(values, null, 2))),
})
}
</script>
<template>
<AutoForm
class="w-2/3 space-y-6"
:schema="schema"
:form="form"
@submit="onSubmit"
>
<Button type="submit">
Submit
</Button>
</AutoForm>
</template>

View File

@ -0,0 +1,72 @@
<script setup lang="ts">
import * as z from 'zod'
import { h } from 'vue'
import { DependencyType } from '../ui/auto-form/interface'
import { Button } from '@/lib/registry/default/ui/button'
import { toast } from '@/lib/registry/default/ui/toast'
import { AutoForm } from '@/lib/registry/default/ui/auto-form'
const schema = z.object({
age: z.number(),
parentsAllowed: z.boolean().optional(),
vegetarian: z.boolean().optional(),
mealOptions: z.enum(['Pasta', 'Salad', 'Beef Wellington']).optional(),
})
function onSubmit(values: Record<string, any>) {
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(values, null, 2))),
})
}
</script>
<template>
<AutoForm
class="w-2/3 space-y-6"
:schema="schema"
:field-config="{
age: {
description:
'Setting this below 18 will require parents consent.',
},
parentsAllowed: {
label: 'Did your parents allow you to register?',
},
vegetarian: {
label: 'Are you a vegetarian?',
description:
'Setting this to true will remove non-vegetarian food options.',
},
mealOptions: {
component: 'radio',
},
}"
:dependencies="[
{
sourceField: 'age',
type: DependencyType.HIDES,
targetField: 'parentsAllowed',
when: (age) => age >= 18,
},
{
sourceField: 'age',
type: DependencyType.REQUIRES,
targetField: 'parentsAllowed',
when: (age) => age < 18,
},
{
sourceField: 'vegetarian',
type: DependencyType.SETS_OPTIONS,
targetField: 'mealOptions',
when: (vegetarian) => vegetarian,
options: ['Pasta', 'Salad'],
},
]"
@submit="onSubmit"
>
<Button type="submit">
Submit
</Button>
</AutoForm>
</template>

View File

@ -0,0 +1,44 @@
<script setup lang="ts">
import * as z from 'zod'
import { h } from 'vue'
import { Button } from '@/lib/registry/default/ui/button'
import { toast } from '@/lib/registry/default/ui/toast'
import { AutoForm, AutoFormField } from '@/lib/registry/default/ui/auto-form'
const schema = z.object({
username: z.string(),
})
function onSubmit(values: Record<string, any>) {
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(values, null, 2))),
})
}
</script>
<template>
<AutoForm
class="w-2/3 space-y-6"
:schema="schema"
:field-config="{
username: {
hideLabel: true,
},
}"
@submit="onSubmit"
>
<template #username="slotProps">
<div class="flex items-start gap-3">
<div class="flex-1">
<AutoFormField v-bind="slotProps" />
</div>
<div>
<Button type="submit">
Update
</Button>
</div>
</div>
</template>
</AutoForm>
</template>

View File

@ -0,0 +1,54 @@
<script setup lang="ts">
import * as z from 'zod'
import { h } from 'vue'
import { Button } from '@/lib/registry/default/ui/button'
import { toast } from '@/lib/registry/default/ui/toast'
import { AutoForm, AutoFormField } from '@/lib/registry/default/ui/auto-form'
const schema = z.object({
subObject: z.object({
subField: z.string().optional().default('Sub Field'),
numberField: z.number().optional().default(1),
subSubObject: z
.object({
subSubField: z.string().default('Sub Sub Field'),
})
.describe('Sub Sub Object Description'),
}),
optionalSubObject: z
.object({
optionalSubField: z.string(),
otherOptionalSubField: z.string(),
})
.optional(),
})
function onSubmit(values: Record<string, any>) {
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(values, null, 2))),
})
}
</script>
<template>
<AutoForm
class="w-2/3 space-y-6"
:schema="schema"
:field-config="{
subObject: {
numberField: {
inputProps: {
type: 'number',
},
},
},
}"
@submit="onSubmit"
>
<Button type="submit">
Submit
</Button>
</AutoForm>
</template>

View File

@ -0,0 +1,28 @@
<script setup lang="ts">
import CustomChartTooltip from './CustomChartTooltip.vue'
import { BarChart } from '@/lib/registry/default/ui/chart-bar'
const data = [
{ name: 'Jan', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Feb', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Mar', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Apr', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'May', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jun', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jul', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
]
</script>
<template>
<BarChart
:data="data"
index="name"
:categories="['total', 'predicted']"
:y-formatter="(tick, i) => {
return typeof tick === 'number'
? `$ ${new Intl.NumberFormat('us').format(tick).toString()}`
: ''
}"
:custom-tooltip="CustomChartTooltip"
/>
</template>

View File

@ -0,0 +1,26 @@
<script setup lang="ts">
import { BarChart } from '@/lib/registry/default/ui/chart-bar'
const data = [
{ name: 'Jan', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Feb', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Mar', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Apr', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'May', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jun', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jul', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
]
</script>
<template>
<BarChart
:data="data"
index="name"
:categories="['total', 'predicted']"
:y-formatter="(tick, i) => {
return typeof tick === 'number'
? `$ ${new Intl.NumberFormat('us').format(tick).toString()}`
: ''
}"
/>
</template>

View File

@ -0,0 +1,27 @@
<script setup lang="ts">
import { BarChart } from '@/lib/registry/default/ui/chart-bar'
const data = [
{ name: 'Jan', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Feb', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Mar', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Apr', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'May', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jun', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jul', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
]
</script>
<template>
<BarChart
index="name"
:data="data"
:categories="['total', 'predicted']"
:y-formatter="(tick, i) => {
return typeof tick === 'number'
? `$ ${new Intl.NumberFormat('us').format(tick).toString()}`
: ''
}"
:rounded-corners="4"
/>
</template>

View File

@ -0,0 +1,27 @@
<script setup lang="ts">
import { BarChart } from '@/lib/registry/default/ui/chart-bar'
const data = [
{ name: 'Jan', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Feb', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Mar', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Apr', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'May', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jun', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jul', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
]
</script>
<template>
<BarChart
index="name"
:data="data"
:categories="['total', 'predicted']"
:y-formatter="(tick, i) => {
return typeof tick === 'number'
? `$ ${new Intl.NumberFormat('us').format(tick).toString()}`
: ''
}"
:type="'stacked'"
/>
</template>

View File

@ -1,10 +1,11 @@
<script setup lang="ts">
import { ref } from 'vue'
import { type Ref, ref } from 'vue'
import { type DateValue, getLocalTimeZone, today } from '@internationalized/date'
import { Calendar } from '@/lib/registry/default/ui/calendar'
const date = ref(new Date())
const value = ref(today(getLocalTimeZone())) as Ref<DateValue>
</script>
<template>
<Calendar v-model="date" class="rounded-md border" />
<Calendar v-model="value" :weekday-format="'short'" class="rounded-md border" />
</template>

View File

@ -0,0 +1,105 @@
<script setup lang="ts">
import { computed, h, ref } from 'vue'
import { CalendarDate, DateFormatter, getLocalTimeZone, parseDate, today } from '@internationalized/date'
import { toDate } from 'radix-vue/date'
import { Calendar as CalendarIcon } from 'lucide-vue-next'
import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/zod'
import { z } from 'zod'
import { Calendar } from '@/lib/registry/default/ui/calendar'
import { Button } from '@/lib/registry/default/ui/button'
import {
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/lib/registry/default/ui/form'
import { Popover, PopoverContent, PopoverTrigger } from '@/lib/registry/default/ui/popover'
import { toast } from '@/lib/registry/default/ui/toast'
import { cn } from '@/lib/utils'
const df = new DateFormatter('en-US', {
dateStyle: 'long',
})
const formSchema = toTypedSchema(z.object({
dob: z
.string()
.refine(v => v, { message: 'A date of birth is required.' }),
}))
const placeholder = ref()
const { handleSubmit, setValues, values } = useForm({
validationSchema: formSchema,
})
const value = computed({
get: () => values.dob ? parseDate(values.dob) : undefined,
set: val => val,
})
const onSubmit = handleSubmit((values) => {
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(values, null, 2))),
})
})
</script>
<template>
<form class="space-y-8" @submit="onSubmit">
<FormField name="dob">
<FormItem class="flex flex-col">
<FormLabel>Date of birth</FormLabel>
<Popover>
<PopoverTrigger as-child>
<FormControl>
<Button
variant="outline" :class="cn(
'w-[240px] ps-3 text-start font-normal',
!value && 'text-muted-foreground',
)"
>
<span>{{ value ? df.format(toDate(value)) : "Pick a date" }}</span>
<CalendarIcon class="ms-auto h-4 w-4 opacity-50" />
</Button>
<input hidden>
</FormControl>
</PopoverTrigger>
<PopoverContent class="w-auto p-0">
<Calendar
v-model:placeholder="placeholder"
v-model="value"
calendar-label="Date of birth"
initial-focus
:min-value="new CalendarDate(1900, 1, 1)"
:max-value="today(getLocalTimeZone())"
@update:model-value="(v) => {
if (v) {
setValues({
dob: v.toString(),
})
}
else {
setValues({
dob: '',
})
}
}"
/>
</PopoverContent>
</Popover>
<FormDescription>
Your date of birth is used to calculate your age.
</FormDescription>
<FormMessage />
</FormItem>
</FormField>
<Button type="submit">
Submit
</Button>
</Form>
</template>

View File

@ -0,0 +1,127 @@
<script setup lang="ts">
import { type HTMLAttributes, type Ref, computed, toRef } from 'vue'
import { CalendarRoot, type CalendarRootEmits, type CalendarRootProps, useDateFormatter, useForwardPropsEmits } from 'radix-vue'
import { createDecade, createYear, toDate } from 'radix-vue/date'
import { type DateValue, getLocalTimeZone, today } from '@internationalized/date'
import { useVModel } from '@vueuse/core'
import { CalendarCell, CalendarCellTrigger, CalendarGrid, CalendarGridBody, CalendarGridHead, CalendarGridRow, CalendarHeadCell, CalendarHeader, CalendarHeading } from '@/lib/registry/default/ui/calendar'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/lib/registry/default/ui/select'
import { cn } from '@/lib/utils'
const props = withDefaults(defineProps<CalendarRootProps & { class?: HTMLAttributes['class'] }>(), {
modelValue: undefined,
placeholder() {
return today(getLocalTimeZone())
},
weekdayFormat: 'short',
})
const emits = defineEmits<CalendarRootEmits>()
const delegatedProps = computed(() => {
const { class: _, placeholder: __, ...delegated } = props
return delegated
})
const placeholder = useVModel(props, 'modelValue', emits, {
passive: true,
defaultValue: today(getLocalTimeZone()),
}) as Ref<DateValue>
const forwarded = useForwardPropsEmits(delegatedProps, emits)
const formatter = useDateFormatter('en')
</script>
<template>
<CalendarRoot
v-slot="{ date, grid, weekDays }"
v-model:placeholder="placeholder"
v-bind="forwarded"
:class="cn('rounded-md border p-3', props.class)"
>
<CalendarHeader>
<CalendarHeading class="flex w-full items-center justify-between gap-2">
<Select
:default-value="placeholder.month.toString()"
@update:model-value="(v) => {
if (!v || !placeholder) return;
if (Number(v) === placeholder?.month) return;
placeholder = placeholder.set({
month: Number(v),
})
}"
>
<SelectTrigger aria-label="Select month" class="w-[60%]">
<SelectValue placeholder="Select month" />
</SelectTrigger>
<SelectContent class="max-h-[200px]">
<SelectItem
v-for="month in createYear({ dateObj: date })"
:key="month.toString()" :value="month.month.toString()"
>
{{ formatter.custom(toDate(month), { month: 'long' }) }}
</SelectItem>
</SelectContent>
</Select>
<Select
:default-value="props.placeholder.year.toString()"
@update:model-value="(v) => {
if (!v || !placeholder) return;
if (Number(v) === placeholder?.year) return;
placeholder = placeholder.set({
year: Number(v),
})
}"
>
<SelectTrigger aria-label="Select year" class="w-[40%]">
<SelectValue placeholder="Select year" />
</SelectTrigger>
<SelectContent class="max-h-[200px]">
<SelectItem
v-for="yearValue in createDecade({ dateObj: date, startIndex: -10, endIndex: 10 })"
:key="yearValue.toString()" :value="yearValue.year.toString()"
>
{{ yearValue.year }}
</SelectItem>
</SelectContent>
</Select>
</CalendarHeading>
</CalendarHeader>
<div class="flex flex-col space-y-4 pt-4 sm:flex-row sm:gap-x-4 sm:gap-y-0">
<CalendarGrid v-for="month in grid" :key="month.value.toString()">
<CalendarGridHead>
<CalendarGridRow>
<CalendarHeadCell
v-for="day in weekDays" :key="day"
>
{{ day }}
</CalendarHeadCell>
</CalendarGridRow>
</CalendarGridHead>
<CalendarGridBody class="grid">
<CalendarGridRow v-for="(weekDates, index) in month.rows" :key="`weekDate-${index}`" class="mt-2 w-full">
<CalendarCell
v-for="weekDate in weekDates"
:key="weekDate.toString()"
:date="weekDate"
>
<CalendarCellTrigger
:day="weekDate"
:month="month.value"
/>
</CalendarCell>
</CalendarGridRow>
</CalendarGridBody>
</CalendarGrid>
</div>
</CalendarRoot>
</template>

View File

@ -52,14 +52,9 @@ const lineY = (d: Data) => d.revenue
left: 10,
bottom: 0,
}"
:style="{
'--theme-primary': `hsl(${
theme?.cssVars[isDark ? 'dark' : 'light'].primary
})`,
}"
>
<VisLine :x="lineX" :y="lineY" color="var(--theme-primary)" />
<VisScatter :x="lineX" :y="lineY" :size="6" stroke-color="var(--theme-primary)" :stroke-width="2" color="white" />
<VisLine :x="lineX" :y="lineY" color="hsl(var(--primary))" />
<VisScatter :x="lineX" :y="lineY" :size="6" stroke-color="hsl(var(--primary))" :stroke-width="2" color="white" />
</VisXYContainer>
</div>
</CardContent>
@ -91,7 +86,7 @@ const lineY = (d: Data) => d.revenue
:x="lineX"
:y="(d: Data) => d.subscription"
:bar-padding="0.1"
:rounded-corners="0" color="var(--theme-primary)"
:rounded-corners="0" color="hsl(var(--primary))"
/>
</VisXYContainer>
</div>

View File

@ -16,10 +16,6 @@ import {
import { themes } from '@/lib/registry/themes'
import { useConfigStore } from '@/stores/config'
const { isDark } = useData()
const cfg = useConfigStore()
const theme = computed(() => themes.find(theme => theme.name === cfg.config.value.theme))
const goal = ref(350)
type Data = typeof data[number]
@ -84,16 +80,13 @@ const data = [
:data="data"
height="60px"
:style="{
'opacity': 0.2,
'--theme-primary': `hsl(${
theme?.cssVars[isDark ? 'dark' : 'light'].primary
})`,
opacity: 0.2,
}"
>
<VisStackedBar
:x="(d: Data, i :number) => i"
:y="(d: Data) => d.goal"
color="var(--theme-primary)"
color="hsl(var(--primary))"
:bar-padding="0.1"
:rounded-corners="0"
/>

View File

@ -10,8 +10,6 @@ import {
} from '@/lib/registry/default/ui/card'
import { useConfigStore } from '@/stores/config'
const { themePrimary } = useConfigStore()
type Data = typeof data[number]
const data = [
{ average: 400, today: 240 },
@ -59,7 +57,7 @@ function computeLineOpacity(val: any, index: number) {
<CardHeader>
<CardTitle>Exercise Minutes</CardTitle>
<CardDescription>
Your excercise minutes are ahead of where you normally are.
Your exercise minutes are ahead of where you normally are.
</CardDescription>
</CardHeader>
<CardContent class="pb-4">
@ -74,15 +72,14 @@ function computeLineOpacity(val: any, index: number) {
bottom: 0,
}"
:style="{
'--theme-primary': themePrimary,
'--vis-tooltip-padding': '0px',
'--vis-tooltip-background-color': 'transparent',
'--vis-tooltip-border-color': 'transparent',
}"
>
<VisTooltip />
<VisLine :x="x" :y="[(d: Data) => d.average, (d: Data) => d.today]" :stroke-width="2" color="var(--theme-primary)" :attributes="{ [Line.selectors.linePath]: { opacity: computeLineOpacity } }" />
<VisScatter :x="x" :y="[(d: Data) => d.average, (d: Data) => d.today]" :size="6" :stroke-width="2" stroke-color="var(--theme-primary)" color="white" />
<VisLine :x="x" :y="[(d: Data) => d.average, (d: Data) => d.today]" :stroke-width="2" color="hsl(var(--primary))" :attributes="{ [Line.selectors.linePath]: { opacity: computeLineOpacity } }" />
<VisScatter :x="x" :y="[(d: Data) => d.average, (d: Data) => d.today]" :size="6" :stroke-width="2" stroke-color="hsl(var(--primary))" color="white" />
<VisCrosshair :template="template" />
</VisXYContainer>
</div>

View File

@ -15,7 +15,7 @@ const plugin = Autoplay({
class="relative w-full max-w-xs"
:plugins="[plugin]"
@mouseenter="plugin.stop"
@mouseleave="[plugin.reset(), plugin.play(), console.log('Runing')];"
@mouseleave="[plugin.reset(), plugin.play(), console.log('Running')];"
>
<CarouselContent>
<CarouselItem v-for="(_, index) in 5" :key="index">

View File

@ -0,0 +1,26 @@
<script setup lang="ts">
import { Card, CardContent } from '@/lib/registry/default/ui/card'
defineProps<{
title?: string
data: {
name: string
color: string
value: any
}[]
}>()
</script>
<template>
<Card class="text-sm">
<CardContent class="p-3 min-w-[180px] flex flex-col gap-2">
<div v-for="(item, key) in data" :key="key" class="flex justify-between items-center">
<div class="flex items-center">
<span class="w-1 h-7 mr-4 rounded-full" :style="{ background: item.color }" />
<span>{{ item.name }}</span>
</div>
<span class="font-semibold ml-4">{{ item.value }}</span>
</div>
</CardContent>
</Card>
</template>

View File

@ -1,36 +1,40 @@
<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'
DateFormatter,
type DateValue,
getLocalTimeZone,
} from '@internationalized/date'
const date = ref<Date>()
import { Calendar as CalendarIcon } from 'lucide-vue-next'
import { Calendar } from '@/lib/registry/default/ui/calendar'
import { Button } from '@/lib/registry/default/ui/button'
import { Popover, PopoverContent, PopoverTrigger } from '@/lib/registry/default/ui/popover'
import { cn } from '@/lib/utils'
const df = new DateFormatter('en-US', {
dateStyle: 'long',
})
const value = ref<DateValue>()
</script>
<template>
<Popover>
<PopoverTrigger as-child>
<Button
:variant="'outline'"
variant="outline"
:class="cn(
'w-[280px] justify-start text-left font-normal',
!date && 'text-muted-foreground',
!value && 'text-muted-foreground',
)"
>
<CalendarIcon class="mr-2 h-4 w-4" />
<span>{{ date ? format(date, "PPP") : "Pick a date" }}</span>
{{ value ? df.format(value.toDate(getLocalTimeZone())) : "Pick a date" }}
</Button>
</PopoverTrigger>
<PopoverContent class="w-auto p-0">
<Calendar v-model="date" />
<Calendar v-model="value" initial-focus />
</PopoverContent>
</Popover>
</template>

View File

@ -1,14 +1,13 @@
<script setup lang="ts">
import { h } from 'vue'
import { format } from 'date-fns'
import { computed, h, ref } from 'vue'
import { CalendarDate, DateFormatter, getLocalTimeZone, parseDate, today } from '@internationalized/date'
import { toDate } from 'radix-vue/date'
import { Calendar as CalendarIcon } from 'lucide-vue-next'
import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/zod'
import * as z from 'zod'
import { cn } from '@/lib/utils'
import { Button } from '@/lib/registry/default/ui/button'
import { z } from 'zod'
import { Calendar } from '@/lib/registry/default/ui/calendar'
import { Button } from '@/lib/registry/default/ui/button'
import {
FormControl,
FormDescription,
@ -17,22 +16,32 @@ import {
FormLabel,
FormMessage,
} from '@/lib/registry/default/ui/form'
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/lib/registry/default/ui/popover'
import { Popover, PopoverContent, PopoverTrigger } from '@/lib/registry/default/ui/popover'
import { toast } from '@/lib/registry/default/ui/toast'
import { cn } from '@/lib/utils'
const df = new DateFormatter('en-US', {
dateStyle: 'long',
})
const formSchema = toTypedSchema(z.object({
dob: z.date({
required_error: 'A date of birth is required.',
}),
dob: z
.string()
.refine(v => v, { message: 'A date of birth is required.' }),
}))
const { handleSubmit } = useForm({
const placeholder = ref()
const { handleSubmit, setValues, values } = useForm({
validationSchema: formSchema,
initialValues: {
},
})
const value = computed({
get: () => values.dob ? parseDate(values.dob) : undefined,
set: val => val,
})
const onSubmit = handleSubmit((values) => {
@ -45,7 +54,7 @@ const onSubmit = handleSubmit((values) => {
<template>
<form class="space-y-8" @submit="onSubmit">
<FormField v-slot="{ componentField, value }" name="dob">
<FormField name="dob">
<FormItem class="flex flex-col">
<FormLabel>Date of birth</FormLabel>
<Popover>
@ -57,13 +66,34 @@ const onSubmit = handleSubmit((values) => {
!value && 'text-muted-foreground',
)"
>
<span>{{ value ? format(value, "PPP") : "Pick a date" }}</span>
<span>{{ value ? df.format(toDate(value)) : "Pick a date" }}</span>
<CalendarIcon class="ms-auto h-4 w-4 opacity-50" />
</Button>
<input hidden>
</FormControl>
</PopoverTrigger>
<PopoverContent class="p-0">
<Calendar v-bind="componentField" />
<PopoverContent class="w-auto p-0">
<Calendar
v-model:placeholder="placeholder"
v-model="value"
calendar-label="Date of birth"
initial-focus
:min-value="new CalendarDate(1900, 1, 1)"
:max-value="today(getLocalTimeZone())"
@update:model-value="(v) => {
if (v) {
setValues({
dob: v.toString(),
})
}
else {
setValues({
dob: '',
})
}
}"
/>
</PopoverContent>
</Popover>
<FormDescription>

View File

@ -0,0 +1,286 @@
<script setup lang="ts">
import { type Ref, ref, watch } from 'vue'
import {
Calendar as CalendarIcon,
ChevronLeft,
ChevronRight,
} from 'lucide-vue-next'
import {
CalendarDate,
type DateValue,
isEqualMonth,
} from '@internationalized/date'
import { type DateRange, RangeCalendarRoot, useDateFormatter } from 'radix-vue'
import { type Grid, createMonth, toDate } from 'radix-vue/date'
import {
RangeCalendarCell,
RangeCalendarCellTrigger,
RangeCalendarGrid,
RangeCalendarGridBody,
RangeCalendarGridHead,
RangeCalendarGridRow,
RangeCalendarHeadCell,
} from '@/lib/registry/default/ui/range-calendar'
import { Button, buttonVariants } from '@/lib/registry/default/ui/button'
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/lib/registry/default/ui/popover'
import { cn } from '@/lib/utils'
const value = ref({
start: new CalendarDate(2022, 1, 20),
end: new CalendarDate(2022, 1, 20).add({ days: 20 }),
}) as Ref<DateRange>
const locale = ref('en-US')
const formatter = useDateFormatter(locale.value)
const placeholder = ref(value.value.start) as Ref<DateValue>
const secondMonthPlaceholder = ref(value.value.end) as Ref<DateValue>
const firstMonth = ref(
createMonth({
dateObj: placeholder.value,
locale: locale.value,
fixedWeeks: true,
weekStartsOn: 0,
}),
) as Ref<Grid<DateValue>>
const secondMonth = ref(
createMonth({
dateObj: secondMonthPlaceholder.value,
locale: locale.value,
fixedWeeks: true,
weekStartsOn: 0,
}),
) as Ref<Grid<DateValue>>
function updateMonth(reference: 'first' | 'second', months: number) {
if (reference === 'first') {
placeholder.value = placeholder.value.add({ months })
}
else {
secondMonthPlaceholder.value = secondMonthPlaceholder.value.add({
months,
})
}
}
watch(placeholder, (_placeholder) => {
firstMonth.value = createMonth({
dateObj: _placeholder,
weekStartsOn: 0,
fixedWeeks: false,
locale: locale.value,
})
if (isEqualMonth(secondMonthPlaceholder.value, _placeholder)) {
secondMonthPlaceholder.value = secondMonthPlaceholder.value.add({
months: 1,
})
}
})
watch(secondMonthPlaceholder, (_secondMonthPlaceholder) => {
secondMonth.value = createMonth({
dateObj: _secondMonthPlaceholder,
weekStartsOn: 0,
fixedWeeks: false,
locale: locale.value,
})
if (isEqualMonth(_secondMonthPlaceholder, placeholder.value))
placeholder.value = placeholder.value.subtract({ months: 1 })
})
</script>
<template>
<Popover>
<PopoverTrigger as-child>
<Button
variant="outline"
:class="
cn(
'w-[280px] justify-start text-left font-normal',
!value && 'text-muted-foreground',
)
"
>
<CalendarIcon class="mr-2 h-4 w-4" />
<template v-if="value.start">
<template v-if="value.end">
{{
formatter.custom(toDate(value.start), {
dateStyle: "medium",
})
}}
-
{{
formatter.custom(toDate(value.end), {
dateStyle: "medium",
})
}}
</template>
<template v-else>
{{
formatter.custom(toDate(value.start), {
dateStyle: "medium",
})
}}
</template>
</template>
<template v-else>
Pick a date
</template>
</Button>
</PopoverTrigger>
<PopoverContent class="w-auto p-0">
<RangeCalendarRoot v-slot="{ weekDays }" v-model="value" v-model:placeholder="placeholder" class="p-3">
<div
class="flex flex-col gap-y-4 mt-4 sm:flex-row sm:gap-x-4 sm:gap-y-0"
>
<div class="flex flex-col gap-4">
<div class="flex items-center justify-between">
<button
:class="
cn(
buttonVariants({ variant: 'outline' }),
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
)
"
@click="updateMonth('first', -1)"
>
<ChevronLeft class="h-4 w-4" />
</button>
<div
:class="cn('text-sm font-medium')"
>
{{
formatter.fullMonthAndYear(
toDate(firstMonth.value),
)
}}
</div>
<button
:class="
cn(
buttonVariants({ variant: 'outline' }),
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
)
"
@click="updateMonth('first', 1)"
>
<ChevronRight class="h-4 w-4" />
</button>
</div>
<RangeCalendarGrid>
<RangeCalendarGridHead>
<RangeCalendarGridRow>
<RangeCalendarHeadCell
v-for="day in weekDays"
:key="day"
class="w-full"
>
{{ day }}
</RangeCalendarHeadCell>
</RangeCalendarGridRow>
</RangeCalendarGridHead>
<RangeCalendarGridBody>
<RangeCalendarGridRow
v-for="(
weekDates, index
) in firstMonth.rows"
:key="`weekDate-${index}`"
class="mt-2 w-full"
>
<RangeCalendarCell
v-for="weekDate in weekDates"
:key="weekDate.toString()"
:date="weekDate"
>
<RangeCalendarCellTrigger
:day="weekDate"
:month="firstMonth.value"
/>
</RangeCalendarCell>
</RangeCalendarGridRow>
</RangeCalendarGridBody>
</RangeCalendarGrid>
</div>
<div class="flex flex-col gap-4">
<div class="flex items-center justify-between">
<button
:class="
cn(
buttonVariants({ variant: 'outline' }),
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
)
"
@click="updateMonth('second', -1)"
>
<ChevronLeft class="h-4 w-4" />
</button>
<div
:class="cn('text-sm font-medium')"
>
{{
formatter.fullMonthAndYear(
toDate(secondMonth.value),
)
}}
</div>
<button
:class="
cn(
buttonVariants({ variant: 'outline' }),
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
)
"
@click="updateMonth('second', 1)"
>
<ChevronRight class="h-4 w-4" />
</button>
</div>
<RangeCalendarGrid>
<RangeCalendarGridHead>
<RangeCalendarGridRow>
<RangeCalendarHeadCell
v-for="day in weekDays"
:key="day"
class="w-full"
>
{{ day }}
</RangeCalendarHeadCell>
</RangeCalendarGridRow>
</RangeCalendarGridHead>
<RangeCalendarGridBody>
<RangeCalendarGridRow
v-for="(
weekDates, index
) in secondMonth.rows"
:key="`weekDate-${index}`"
class="mt-2 w-full"
>
<RangeCalendarCell
v-for="weekDate in weekDates"
:key="weekDate.toString()"
:date="weekDate"
>
<RangeCalendarCellTrigger
:day="weekDate"
:month="secondMonth.value"
/>
</RangeCalendarCell>
</RangeCalendarGridRow>
</RangeCalendarGridBody>
</RangeCalendarGrid>
</div>
</div>
</RangeCalendarRoot>
</PopoverContent>
</Popover>
</template>

View File

@ -1,25 +1,31 @@
<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'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/lib/registry/default/ui/select'
DateFormatter,
type DateValue,
getLocalTimeZone,
today,
} from '@internationalized/date'
const date = ref<Date>()
import { Calendar as CalendarIcon } from 'lucide-vue-next'
import { Calendar } from '@/lib/registry/default/ui/calendar'
import { Button } from '@/lib/registry/default/ui/button'
import { Popover, PopoverContent, PopoverTrigger } from '@/lib/registry/default/ui/popover'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/lib/registry/default/ui/select'
import { cn } from '@/lib/utils'
const df = new DateFormatter('en-US', {
dateStyle: 'long',
})
const items = [
{ value: 0, label: 'Today' },
{ value: 1, label: 'Tomorrow' },
{ value: 3, label: 'In 3 days' },
{ value: 7, label: 'In a week' },
]
const value = ref<DateValue>()
</script>
<template>
@ -29,45 +35,30 @@ const date = ref<Date>()
variant="outline"
:class="cn(
'w-[280px] justify-start text-left font-normal',
!date && 'text-muted-foreground',
!value && 'text-muted-foreground',
)"
>
<CalendarIcon class="mr-2 h-4 w-4" />
<template v-if="date">
{{ format(date, "PPP") }}
</template>
<template v-else>
<span>Pick a date</span>
</template>
{{ value ? df.format(value.toDate(getLocalTimeZone())) : "Pick a date" }}
</Button>
</PopoverTrigger>
<PopoverContent class="flex w-auto flex-col space-y-2 p-2">
<PopoverContent class="flex w-auto flex-col gap-y-2 p-2">
<Select
@update:model-value="(value) => {
date = addDays(new Date(), parseInt(value))
@update:model-value="(v) => {
if (!v) return;
value = today(getLocalTimeZone()).add({ days: Number(v) });
}"
>
<SelectTrigger>
<SelectValue placeholder="Select" />
</SelectTrigger>
<SelectContent position="popper">
<SelectItem value="0">
Today
</SelectItem>
<SelectItem value="1">
Tomorrow
</SelectItem>
<SelectItem value="3">
In 3 days
</SelectItem>
<SelectItem value="7">
In a week
<SelectContent>
<SelectItem v-for="item in items" :key="item.value" :value="item.value.toString()">
{{ item.label }}
</SelectItem>
</SelectContent>
</Select>
<div class="rounded-md border">
<Calendar v-model="date" mode="single" />
</div>
<Calendar v-model="value" />
</PopoverContent>
</Popover>
</template>

View File

@ -1,51 +1,55 @@
<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 { type Ref, ref } from 'vue'
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/lib/registry/default/ui/popover'
CalendarDate,
DateFormatter,
getLocalTimeZone,
} from '@internationalized/date'
const date = ref({
start: new Date(2022, 0, 20),
end: addDays(new Date(2022, 0, 20), 20),
import { Calendar as CalendarIcon } from 'lucide-vue-next'
import type { DateRange } from 'radix-vue'
import { RangeCalendar } from '@/lib/registry/default/ui/range-calendar'
import { Button } from '@/lib/registry/default/ui/button'
import { Popover, PopoverContent, PopoverTrigger } from '@/lib/registry/default/ui/popover'
import { cn } from '@/lib/utils'
const df = new DateFormatter('en-US', {
dateStyle: 'medium',
})
const value = ref({
start: new CalendarDate(2022, 1, 20),
end: new CalendarDate(2022, 1, 20).add({ days: 20 }),
}) as Ref<DateRange>
</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" />
<Popover>
<PopoverTrigger as-child>
<Button
variant="outline"
:class="cn(
'w-[280px] justify-start text-left font-normal',
!value && 'text-muted-foreground',
)"
>
<CalendarIcon class="mr-2 h-4 w-4" />
<template v-if="value.start">
<template v-if="value.end">
{{ df.format(value.start.toDate(getLocalTimeZone())) }} - {{ df.format(value.end.toDate(getLocalTimeZone())) }}
</template>
<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"
:columns="2"
/>
</PopoverContent>
</Popover>
</div>
<template v-else>
{{ df.format(value.start.toDate(getLocalTimeZone())) }}
</template>
</template>
<template v-else>
Pick a date
</template>
</Button>
</PopoverTrigger>
<PopoverContent class="w-auto p-0">
<RangeCalendar v-model="value" initial-focus :number-of-months="2" @update:start-value="(startDate) => value.start = startDate" />
</PopoverContent>
</Popover>
</template>

View File

@ -0,0 +1,24 @@
<script setup lang="ts">
import { DonutChart } from '@/lib/registry/new-york/ui/chart-donut'
const data = [
{ name: 'Jan', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Feb', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Mar', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Apr', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'May', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jun', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
]
const valueFormatter = (tick: number | Date) => typeof tick === 'number' ? `$ ${new Intl.NumberFormat('us').format(tick).toString()}` : ''
</script>
<template>
<DonutChart
index="name"
:category="'total'"
:data="data"
:value-formatter="valueFormatter"
:colors="['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'purple']"
/>
</template>

View File

@ -0,0 +1,22 @@
<script setup lang="ts">
import CustomChartTooltip from './CustomChartTooltip.vue'
import { DonutChart } from '@/lib/registry/default/ui/chart-donut'
const data = [
{ name: 'Jan', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Feb', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Mar', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Apr', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'May', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jun', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
]
</script>
<template>
<DonutChart
index="name"
:category="'total'"
:data="data"
:custom-tooltip="CustomChartTooltip"
/>
</template>

View File

@ -0,0 +1,20 @@
<script setup lang="ts">
import { DonutChart } from '@/lib/registry/default/ui/chart-donut'
const data = [
{ name: 'Jan', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Feb', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Mar', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Apr', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'May', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jun', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
]
</script>
<template>
<DonutChart
index="name"
:category="'total'"
:data="data"
/>
</template>

View File

@ -0,0 +1,21 @@
<script setup lang="ts">
import { DonutChart } from '@/lib/registry/default/ui/chart-donut'
const data = [
{ name: 'Jan', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Feb', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Mar', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Apr', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'May', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jun', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
]
</script>
<template>
<DonutChart
index="name"
:category="'total'"
:data="data"
:type="'pie'"
/>
</template>

View File

@ -0,0 +1,281 @@
<script setup lang="ts">
import CustomChartTooltip from './CustomChartTooltip.vue'
import { LineChart } from '@/lib/registry/default/ui/chart-line'
const data = [
{
'year': 1970,
'Export Growth Rate': 2.04,
'Import Growth Rate': 1.53,
},
{
'year': 1971,
'Export Growth Rate': 1.96,
'Import Growth Rate': 1.58,
},
{
'year': 1972,
'Export Growth Rate': 1.96,
'Import Growth Rate': 1.61,
},
{
'year': 1973,
'Export Growth Rate': 1.93,
'Import Growth Rate': 1.61,
},
{
'year': 1974,
'Export Growth Rate': 1.88,
'Import Growth Rate': 1.67,
},
{
'year': 1975,
'Export Growth Rate': 1.79,
'Import Growth Rate': 1.64,
},
{
'year': 1976,
'Export Growth Rate': 1.77,
'Import Growth Rate': 1.62,
},
{
'year': 1977,
'Export Growth Rate': 1.74,
'Import Growth Rate': 1.69,
},
{
'year': 1978,
'Export Growth Rate': 1.74,
'Import Growth Rate': 1.7,
},
{
'year': 1979,
'Export Growth Rate': 1.77,
'Import Growth Rate': 1.67,
},
{
'year': 1980,
'Export Growth Rate': 1.79,
'Import Growth Rate': 1.7,
},
{
'year': 1981,
'Export Growth Rate': 1.81,
'Import Growth Rate': 1.72,
},
{
'year': 1982,
'Export Growth Rate': 1.84,
'Import Growth Rate': 1.73,
},
{
'year': 1983,
'Export Growth Rate': 1.77,
'Import Growth Rate': 1.73,
},
{
'year': 1984,
'Export Growth Rate': 1.78,
'Import Growth Rate': 1.78,
},
{
'year': 1985,
'Export Growth Rate': 1.78,
'Import Growth Rate': 1.81,
},
{
'year': 1986,
'Export Growth Rate': 1.82,
'Import Growth Rate': 1.89,
},
{
'year': 1987,
'Export Growth Rate': 1.82,
'Import Growth Rate': 1.91,
},
{
'year': 1988,
'Export Growth Rate': 1.77,
'Import Growth Rate': 1.94,
},
{
'year': 1989,
'Export Growth Rate': 1.76,
'Import Growth Rate': 1.94,
},
{
'year': 1990,
'Export Growth Rate': 1.75,
'Import Growth Rate': 1.97,
},
{
'year': 1991,
'Export Growth Rate': 1.62,
'Import Growth Rate': 1.99,
},
{
'year': 1992,
'Export Growth Rate': 1.56,
'Import Growth Rate': 2.12,
},
{
'year': 1993,
'Export Growth Rate': 1.5,
'Import Growth Rate': 2.13,
},
{
'year': 1994,
'Export Growth Rate': 1.46,
'Import Growth Rate': 2.15,
},
{
'year': 1995,
'Export Growth Rate': 1.43,
'Import Growth Rate': 2.17,
},
{
'year': 1996,
'Export Growth Rate': 1.4,
'Import Growth Rate': 2.2,
},
{
'year': 1997,
'Export Growth Rate': 1.37,
'Import Growth Rate': 2.15,
},
{
'year': 1998,
'Export Growth Rate': 1.34,
'Import Growth Rate': 2.07,
},
{
'year': 1999,
'Export Growth Rate': 1.32,
'Import Growth Rate': 2.05,
},
{
'year': 2000,
'Export Growth Rate': 1.33,
'Import Growth Rate': 2.07,
},
{
'year': 2001,
'Export Growth Rate': 1.31,
'Import Growth Rate': 2.08,
},
{
'year': 2002,
'Export Growth Rate': 1.29,
'Import Growth Rate': 2.1,
},
{
'year': 2003,
'Export Growth Rate': 1.27,
'Import Growth Rate': 2.15,
},
{
'year': 2004,
'Export Growth Rate': 1.27,
'Import Growth Rate': 2.21,
},
{
'year': 2005,
'Export Growth Rate': 1.26,
'Import Growth Rate': 2.23,
},
{
'year': 2006,
'Export Growth Rate': 1.26,
'Import Growth Rate': 2.29,
},
{
'year': 2007,
'Export Growth Rate': 1.27,
'Import Growth Rate': 2.34,
},
{
'year': 2008,
'Export Growth Rate': 1.26,
'Import Growth Rate': 2.36,
},
{
'year': 2009,
'Export Growth Rate': 1.26,
'Import Growth Rate': 2.36,
},
{
'year': 2010,
'Export Growth Rate': 1.25,
'Import Growth Rate': 2.35,
},
{
'year': 2011,
'Export Growth Rate': 1.24,
'Import Growth Rate': 2.34,
},
{
'year': 2012,
'Export Growth Rate': 1.25,
'Import Growth Rate': 2.39,
},
{
'year': 2013,
'Export Growth Rate': 1.22,
'Import Growth Rate': 2.3,
},
{
'year': 2014,
'Export Growth Rate': 1.2,
'Import Growth Rate': 2.35,
},
{
'year': 2015,
'Export Growth Rate': 1.17,
'Import Growth Rate': 2.39,
},
{
'year': 2016,
'Export Growth Rate': 1.16,
'Import Growth Rate': 2.41,
},
{
'year': 2017,
'Export Growth Rate': 1.13,
'Import Growth Rate': 2.44,
},
{
'year': 2018,
'Export Growth Rate': 1.07,
'Import Growth Rate': 2.45,
},
{
'year': 2019,
'Export Growth Rate': 1.03,
'Import Growth Rate': 2.47,
},
{
'year': 2020,
'Export Growth Rate': 0.92,
'Import Growth Rate': 2.48,
},
{
'year': 2021,
'Export Growth Rate': 0.82,
'Import Growth Rate': 2.51,
},
]
</script>
<template>
<LineChart
:data="data"
index="year"
:categories="['Export Growth Rate', 'Import Growth Rate']"
:y-formatter="(tick, i) => {
return typeof tick === 'number'
? `$ ${new Intl.NumberFormat('us').format(tick).toString()}`
: ''
}"
:custom-tooltip="CustomChartTooltip"
/>
</template>

View File

@ -0,0 +1,279 @@
<script setup lang="ts">
import { LineChart } from '@/lib/registry/default/ui/chart-line'
const data = [
{
'year': 1970,
'Export Growth Rate': 2.04,
'Import Growth Rate': 1.53,
},
{
'year': 1971,
'Export Growth Rate': 1.96,
'Import Growth Rate': 1.58,
},
{
'year': 1972,
'Export Growth Rate': 1.96,
'Import Growth Rate': 1.61,
},
{
'year': 1973,
'Export Growth Rate': 1.93,
'Import Growth Rate': 1.61,
},
{
'year': 1974,
'Export Growth Rate': 1.88,
'Import Growth Rate': 1.67,
},
{
'year': 1975,
'Export Growth Rate': 1.79,
'Import Growth Rate': 1.64,
},
{
'year': 1976,
'Export Growth Rate': 1.77,
'Import Growth Rate': 1.62,
},
{
'year': 1977,
'Export Growth Rate': 1.74,
'Import Growth Rate': 1.69,
},
{
'year': 1978,
'Export Growth Rate': 1.74,
'Import Growth Rate': 1.7,
},
{
'year': 1979,
'Export Growth Rate': 1.77,
'Import Growth Rate': 1.67,
},
{
'year': 1980,
'Export Growth Rate': 1.79,
'Import Growth Rate': 1.7,
},
{
'year': 1981,
'Export Growth Rate': 1.81,
'Import Growth Rate': 1.72,
},
{
'year': 1982,
'Export Growth Rate': 1.84,
'Import Growth Rate': 1.73,
},
{
'year': 1983,
'Export Growth Rate': 1.77,
'Import Growth Rate': 1.73,
},
{
'year': 1984,
'Export Growth Rate': 1.78,
'Import Growth Rate': 1.78,
},
{
'year': 1985,
'Export Growth Rate': 1.78,
'Import Growth Rate': 1.81,
},
{
'year': 1986,
'Export Growth Rate': 1.82,
'Import Growth Rate': 1.89,
},
{
'year': 1987,
'Export Growth Rate': 1.82,
'Import Growth Rate': 1.91,
},
{
'year': 1988,
'Export Growth Rate': 1.77,
'Import Growth Rate': 1.94,
},
{
'year': 1989,
'Export Growth Rate': 1.76,
'Import Growth Rate': 1.94,
},
{
'year': 1990,
'Export Growth Rate': 1.75,
'Import Growth Rate': 1.97,
},
{
'year': 1991,
'Export Growth Rate': 1.62,
'Import Growth Rate': 1.99,
},
{
'year': 1992,
'Export Growth Rate': 1.56,
'Import Growth Rate': 2.12,
},
{
'year': 1993,
'Export Growth Rate': 1.5,
'Import Growth Rate': 2.13,
},
{
'year': 1994,
'Export Growth Rate': 1.46,
'Import Growth Rate': 2.15,
},
{
'year': 1995,
'Export Growth Rate': 1.43,
'Import Growth Rate': 2.17,
},
{
'year': 1996,
'Export Growth Rate': 1.4,
'Import Growth Rate': 2.2,
},
{
'year': 1997,
'Export Growth Rate': 1.37,
'Import Growth Rate': 2.15,
},
{
'year': 1998,
'Export Growth Rate': 1.34,
'Import Growth Rate': 2.07,
},
{
'year': 1999,
'Export Growth Rate': 1.32,
'Import Growth Rate': 2.05,
},
{
'year': 2000,
'Export Growth Rate': 1.33,
'Import Growth Rate': 2.07,
},
{
'year': 2001,
'Export Growth Rate': 1.31,
'Import Growth Rate': 2.08,
},
{
'year': 2002,
'Export Growth Rate': 1.29,
'Import Growth Rate': 2.1,
},
{
'year': 2003,
'Export Growth Rate': 1.27,
'Import Growth Rate': 2.15,
},
{
'year': 2004,
'Export Growth Rate': 1.27,
'Import Growth Rate': 2.21,
},
{
'year': 2005,
'Export Growth Rate': 1.26,
'Import Growth Rate': 2.23,
},
{
'year': 2006,
'Export Growth Rate': 1.26,
'Import Growth Rate': 2.29,
},
{
'year': 2007,
'Export Growth Rate': 1.27,
'Import Growth Rate': 2.34,
},
{
'year': 2008,
'Export Growth Rate': 1.26,
'Import Growth Rate': 2.36,
},
{
'year': 2009,
'Export Growth Rate': 1.26,
'Import Growth Rate': 2.36,
},
{
'year': 2010,
'Export Growth Rate': 1.25,
'Import Growth Rate': 2.35,
},
{
'year': 2011,
'Export Growth Rate': 1.24,
'Import Growth Rate': 2.34,
},
{
'year': 2012,
'Export Growth Rate': 1.25,
'Import Growth Rate': 2.39,
},
{
'year': 2013,
'Export Growth Rate': 1.22,
'Import Growth Rate': 2.3,
},
{
'year': 2014,
'Export Growth Rate': 1.2,
'Import Growth Rate': 2.35,
},
{
'year': 2015,
'Export Growth Rate': 1.17,
'Import Growth Rate': 2.39,
},
{
'year': 2016,
'Export Growth Rate': 1.16,
'Import Growth Rate': 2.41,
},
{
'year': 2017,
'Export Growth Rate': 1.13,
'Import Growth Rate': 2.44,
},
{
'year': 2018,
'Export Growth Rate': 1.07,
'Import Growth Rate': 2.45,
},
{
'year': 2019,
'Export Growth Rate': 1.03,
'Import Growth Rate': 2.47,
},
{
'year': 2020,
'Export Growth Rate': 0.92,
'Import Growth Rate': 2.48,
},
{
'year': 2021,
'Export Growth Rate': 0.82,
'Import Growth Rate': 2.51,
},
]
</script>
<template>
<LineChart
:data="data"
index="year"
:categories="['Export Growth Rate', 'Import Growth Rate']"
:y-formatter="(tick, i) => {
return typeof tick === 'number'
? `$ ${new Intl.NumberFormat('us').format(tick).toString()}`
: ''
}"
/>
</template>

View File

@ -0,0 +1,285 @@
<script setup lang="ts">
import { LineChart } from '@/lib/registry/default/ui/chart-line'
const data = [
{
'year': 1970,
'Export Growth Rate': 2.04,
'Import Growth Rate': 1.53,
},
{
'year': 1971,
'Export Growth Rate': 1.96,
'Import Growth Rate': 1.58,
},
{
'year': 1972,
'Export Growth Rate': 1.96,
'Import Growth Rate': 1.61,
},
{
'year': 1973,
'Export Growth Rate': 1.93,
'Import Growth Rate': 1.61,
},
{
'year': 1974,
'Export Growth Rate': 1.88,
'Import Growth Rate': 1.67,
},
{
'year': 1975,
'Export Growth Rate': 1.79,
'Import Growth Rate': 1.64,
},
{
'year': 1976,
'Export Growth Rate': 1.77,
'Import Growth Rate': 1.62,
},
{
'year': 1977,
'Export Growth Rate': 1.74,
'Import Growth Rate': 1.69,
},
{
'year': 1978,
'Export Growth Rate': 1.74,
'Import Growth Rate': 1.7,
},
{
'year': 1979,
'Export Growth Rate': 1.77,
'Import Growth Rate': 1.67,
},
{
'year': 1980,
'Export Growth Rate': 1.79,
'Import Growth Rate': 1.7,
},
{
'year': 1981,
'Export Growth Rate': 1.81,
'Import Growth Rate': 1.72,
},
{
'year': 1982,
'Export Growth Rate': 1.84,
'Import Growth Rate': 1.73,
},
{
'year': 1983,
'Export Growth Rate': 1.77,
'Import Growth Rate': 1.73,
},
{
'year': 1984,
'Export Growth Rate': 1.78,
'Import Growth Rate': 1.78,
},
{
'year': 1985,
'Export Growth Rate': 1.78,
'Import Growth Rate': 1.81,
},
{
'year': 1986,
'Export Growth Rate': 1.82,
'Import Growth Rate': 1.89,
},
{
'year': 1987,
'Export Growth Rate': 1.82,
'Import Growth Rate': 1.91,
},
{
'year': 1988,
'Export Growth Rate': 1.77,
'Import Growth Rate': 1.94,
},
{
'year': 1989,
'Export Growth Rate': 1.76,
'Import Growth Rate': 1.94,
},
{
'year': 1990,
'Export Growth Rate': 1.75,
'Import Growth Rate': 1.97,
},
{
'year': 1991,
'Export Growth Rate': 1.62,
'Import Growth Rate': 1.99,
},
{
'year': 1992,
'Export Growth Rate': 1.56,
'Import Growth Rate': 2.12,
},
{
'year': 1993,
'Export Growth Rate': 1.5,
'Import Growth Rate': 2.13,
},
{
'year': 1994,
'Export Growth Rate': 1.46,
'Import Growth Rate': 2.15,
},
{
'year': 1995,
'Export Growth Rate': 1.43,
'Import Growth Rate': 2.17,
},
{
'year': 1996,
'Export Growth Rate': 1.4,
'Import Growth Rate': 2.2,
},
{
'year': 1997,
'Export Growth Rate': 1.37,
'Import Growth Rate': 2.15,
},
{
'year': 1998,
'Export Growth Rate': 1.34,
'Import Growth Rate': 2.07,
},
{
'year': 1999,
'Export Growth Rate': 1.32,
'Import Growth Rate': 2.05,
},
{
'year': 2000,
'Export Growth Rate': 1.33,
'Import Growth Rate': 2.07,
},
{
'year': 2001,
'Export Growth Rate': 1.31,
'Import Growth Rate': 2.08,
},
{
'year': 2002,
'Export Growth Rate': 1.29,
'Import Growth Rate': 2.1,
},
{
'year': 2003,
'Export Growth Rate': 1.27,
'Import Growth Rate': 2.15,
},
{
'year': 2004,
'Export Growth Rate': 1.27,
'Import Growth Rate': 2.21,
},
{
'year': 2005,
'Export Growth Rate': 1.26,
'Import Growth Rate': 2.23,
},
{
'year': 2006,
'Export Growth Rate': 1.26,
'Import Growth Rate': 2.29,
},
{
'year': 2007,
'Export Growth Rate': 1.27,
'Import Growth Rate': 2.34,
},
{
'year': 2008,
'Export Growth Rate': 1.26,
'Import Growth Rate': 2.36,
},
{
'year': 2009,
'Export Growth Rate': 1.26,
'Import Growth Rate': 2.36,
},
{
'year': 2010,
'Export Growth Rate': 1.25,
'Import Growth Rate': 2.35,
},
{
'year': 2011,
'Export Growth Rate': 1.24,
'Import Growth Rate': 2.34,
},
{
'year': 2012,
'Export Growth Rate': 1.25,
'Import Growth Rate': 2.39,
},
{
'year': 2013,
'Export Growth Rate': 1.22,
'Import Growth Rate': 2.3,
},
{
'year': 2014,
'Export Growth Rate': 1.2,
'Import Growth Rate': 2.35,
},
{
'year': 2015,
'Export Growth Rate': 1.17,
'Import Growth Rate': 2.39,
},
{
'year': 2016,
'Export Growth Rate': 1.16,
'Import Growth Rate': 2.41,
},
{
'year': 2017,
'Export Growth Rate': 1.13,
'Import Growth Rate': 2.44,
},
{
'year': 2018,
'Export Growth Rate': 1.07,
'Import Growth Rate': 2.45,
},
{
'year': 2019,
'Export Growth Rate': 1.03,
'Import Growth Rate': 2.47,
},
{
'year': 2020,
'Export Growth Rate': 0.92,
'Import Growth Rate': 2.48,
},
{
'year': 2021,
'Export Growth Rate': 0.82,
'Import Growth Rate': 2.51,
},
]
</script>
<template>
<LineChart
index="year"
class="h-[100px] w-[400px]"
:data="data"
:categories="['Export Growth Rate']"
:y-formatter="(tick, i) => {
return typeof tick === 'number'
? `$ ${new Intl.NumberFormat('us').format(tick).toString()}`
: ''
}"
:show-tooltip="false"
:show-grid-line="false"
:show-legend="false"
:show-x-axis="false"
:show-y-axis="false"
/>
</template>

View File

@ -0,0 +1,18 @@
<script setup lang="ts">
import { type Ref, ref } from 'vue'
import type { DateRange } from 'radix-vue'
import { getLocalTimeZone, today } from '@internationalized/date'
import { RangeCalendar } from '@/lib/registry/default/ui/range-calendar'
const start = today(getLocalTimeZone())
const end = start.add({ days: 7 })
const value = ref({
start,
end,
}) as Ref<DateRange>
</script>
<template>
<RangeCalendar v-model="value" class="rounded-md border" />
</template>

View File

@ -0,0 +1,10 @@
<script setup lang="ts">
import { ref } from 'vue'
import { Calendar } from '@/lib/registry/default/ui/v-calendar'
const date = ref(new Date())
</script>
<template>
<Calendar v-model="date" class="rounded-md border" />
</template>

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/v-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") : "Pick a date" }}</span>
</Button>
</PopoverTrigger>
<PopoverContent class="w-auto p-0">
<Calendar v-model="date" />
</PopoverContent>
</Popover>
</template>

View File

@ -0,0 +1,79 @@
<script setup lang="ts">
import { h } from 'vue'
import { format } from 'date-fns'
import { Calendar as CalendarIcon } from 'lucide-vue-next'
import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/zod'
import * as z from 'zod'
import { cn } from '@/lib/utils'
import { Button } from '@/lib/registry/default/ui/button'
import { Calendar } from '@/lib/registry/default/ui/v-calendar'
import {
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/lib/registry/default/ui/form'
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/lib/registry/default/ui/popover'
import { toast } from '@/lib/registry/default/ui/toast'
const formSchema = toTypedSchema(z.object({
dob: z.date({
required_error: 'A date of birth is required.',
}),
}))
const { handleSubmit } = useForm({
validationSchema: formSchema,
})
const onSubmit = handleSubmit((values) => {
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(values, null, 2))),
})
})
</script>
<template>
<form class="space-y-8" @submit="onSubmit">
<FormField v-slot="{ componentField, value }" name="dob">
<FormItem class="flex flex-col">
<FormLabel>Date of birth</FormLabel>
<Popover>
<PopoverTrigger as-child>
<FormControl>
<Button
variant="outline" :class="cn(
'w-[240px] ps-3 text-start font-normal',
!value && 'text-muted-foreground',
)"
>
<span>{{ value ? format(value, "PPP") : "Pick a date" }}</span>
<CalendarIcon class="ms-auto h-4 w-4 opacity-50" />
</Button>
</FormControl>
</PopoverTrigger>
<PopoverContent class="p-0">
<Calendar v-bind="componentField" />
</PopoverContent>
</Popover>
<FormDescription>
Your date of birth is used to calculate your age.
</FormDescription>
<FormMessage />
</FormItem>
</FormField>
<Button type="submit">
Submit
</Button>
</Form>
</template>

View File

@ -0,0 +1,73 @@
<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/v-calendar'
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/lib/registry/default/ui/popover'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/lib/registry/default/ui/select'
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" />
<template v-if="date">
{{ format(date, "PPP") }}
</template>
<template v-else>
<span>Pick a date</span>
</template>
</Button>
</PopoverTrigger>
<PopoverContent class="flex w-auto flex-col space-y-2 p-2">
<Select
@update:model-value="(value) => {
date = addDays(new Date(), parseInt(value))
}"
>
<SelectTrigger>
<SelectValue placeholder="Select" />
</SelectTrigger>
<SelectContent position="popper">
<SelectItem value="0">
Today
</SelectItem>
<SelectItem value="1">
Tomorrow
</SelectItem>
<SelectItem value="3">
In 3 days
</SelectItem>
<SelectItem value="7">
In a week
</SelectItem>
</SelectContent>
</Select>
<div class="rounded-md border">
<Calendar v-model="date" mode="single" />
</div>
</PopoverContent>
</Popover>
</template>

View File

@ -0,0 +1,51 @@
<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/v-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"
:columns="2"
/>
</PopoverContent>
</Popover>
</div>
</template>

View File

@ -5,7 +5,7 @@ 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 { Calendar } from '@/lib/registry/default/ui/v-calendar'
import {
Popover,
PopoverContent,

View File

@ -5,7 +5,7 @@ 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 { Calendar } from '@/lib/registry/default/ui/v-calendar'
import {
Popover,
PopoverContent,

View File

@ -0,0 +1,105 @@
<script setup lang="ts" generic="T extends ZodObjectOrWrapped">
import { computed, toRefs } from 'vue'
import type { ZodAny, z } from 'zod'
import { toTypedSchema } from '@vee-validate/zod'
import type { FormContext, GenericObject } from 'vee-validate'
import { type ZodObjectOrWrapped, getBaseSchema, getBaseType, getDefaultValueInZodStack, getObjectFormSchema } from './utils'
import type { Config, ConfigItem, Dependency, Shape } from './interface'
import AutoFormField from './AutoFormField.vue'
import { provideDependencies } from './dependencies'
import { Form } from '@/lib/registry/default/ui/form'
const props = defineProps<{
schema: T
form?: FormContext<GenericObject>
fieldConfig?: Config<z.infer<T>>
dependencies?: Dependency<z.infer<T>>[]
}>()
const emits = defineEmits<{
submit: [event: GenericObject]
}>()
const { dependencies } = toRefs(props)
provideDependencies(dependencies)
const shapes = computed(() => {
// @ts-expect-error ignore {} not assignable to object
const val: { [key in keyof T]: Shape } = {}
const baseSchema = getObjectFormSchema(props.schema)
const shape = baseSchema.shape
Object.keys(shape).forEach((name) => {
const item = shape[name] as ZodAny
const baseItem = getBaseSchema(item) as ZodAny
let options = (baseItem && 'values' in baseItem._def) ? baseItem._def.values as string[] : undefined
if (!Array.isArray(options) && typeof options === 'object')
options = Object.values(options)
val[name as keyof T] = {
type: getBaseType(item),
default: getDefaultValueInZodStack(item),
options,
required: !['ZodOptional', 'ZodNullable'].includes(item._def.typeName),
schema: baseItem,
}
})
return val
})
const fields = computed(() => {
// @ts-expect-error ignore {} not assignable to object
const val: { [key in keyof z.infer<T>]: { shape: Shape, fieldName: string, config: ConfigItem } } = {}
for (const key in shapes.value) {
const shape = shapes.value[key]
val[key as keyof z.infer<T>] = {
shape,
config: props.fieldConfig?.[key] as ConfigItem,
fieldName: key,
}
}
return val
})
const formComponent = computed(() => props.form ? 'form' : Form)
const formComponentProps = computed(() => {
if (props.form) {
return {
onSubmit: props.form.handleSubmit(val => emits('submit', val)),
}
}
else {
const formSchema = toTypedSchema(props.schema)
return {
keepValues: true,
validationSchema: formSchema,
onSubmit: (val: GenericObject) => emits('submit', val),
}
}
})
</script>
<template>
<component
:is="formComponent"
v-bind="formComponentProps"
>
<slot name="customAutoForm" :fields="fields">
<template v-for="(shape, key) of shapes" :key="key">
<slot
:shape="shape"
:name="key.toString() as keyof z.infer<T>"
:field-name="key.toString()"
:config="fieldConfig?.[key as keyof typeof fieldConfig] as ConfigItem"
>
<AutoFormField
:config="fieldConfig?.[key as keyof typeof fieldConfig] as ConfigItem"
:field-name="key.toString()"
:shape="shape"
/>
</slot>
</template>
</slot>
<slot :shapes="shapes" />
</component>
</template>

View File

@ -0,0 +1,45 @@
<script setup lang="ts" generic="U extends ZodAny">
import type { ZodAny } from 'zod'
import { computed } from 'vue'
import type { Config, ConfigItem, Shape } from './interface'
import { DEFAULT_ZOD_HANDLERS, INPUT_COMPONENTS } from './constant'
import useDependencies from './dependencies'
const props = defineProps<{
fieldName: string
shape: Shape
config?: ConfigItem | Config<U>
}>()
function isValidConfig(config: any): config is ConfigItem {
return !!config?.component
}
const delegatedProps = computed(() => {
if (['ZodObject', 'ZodArray'].includes(props.shape?.type))
return { schema: props.shape?.schema }
return undefined
})
const { isDisabled, isHidden, isRequired, overrideOptions } = useDependencies(props.fieldName)
</script>
<template>
<component
:is="isValidConfig(config)
? typeof config.component === 'string'
? INPUT_COMPONENTS[config.component!]
: config.component
: INPUT_COMPONENTS[DEFAULT_ZOD_HANDLERS[shape.type]] "
v-if="!isHidden"
:field-name="fieldName"
:label="shape.schema?.description"
:required="isRequired || shape.required"
:options="overrideOptions || shape.options"
:disabled="isDisabled"
:config="config"
v-bind="delegatedProps"
>
<slot />
</component>
</template>

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