Merge branch 'dev' into fix/nuxt-build-dir
This commit is contained in:
commit
0a2a68ac47
3
.github/workflows/publish.yaml
vendored
3
.github/workflows/publish.yaml
vendored
|
|
@ -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
|
||||
|
|
|
|||
4
.github/workflows/test.yaml
vendored
4
.github/workflows/test.yaml
vendored
|
|
@ -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
|
||||
|
|
|
|||
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
|
|
@ -1,6 +1,5 @@
|
|||
{
|
||||
"vue.server.hybridMode": true,
|
||||
"eslint.experimental.useFlatConfig": true,
|
||||
"prettier.enable": false,
|
||||
"editor.formatOnSave": false,
|
||||
"editor.codeActionsOnSave": {
|
||||
|
|
|
|||
26
apps/www/.vitepress/theme/components/APITable.vue
Normal file
26
apps/www/.vitepress/theme/components/APITable.vue
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<script setup lang="ts">
|
||||
import { capitalize } from 'vue'
|
||||
|
||||
defineProps<{
|
||||
type: 'prop' | 'emit' | 'slot' | 'method'
|
||||
data: { name: string, description: string, type: string, required: boolean }[]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h3>{{ capitalize(type) }}</h3>
|
||||
|
||||
<div v-for="(item, index) in data" :key="index" class="py-4 border-b text-sm">
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
<h5 class="text-sm">
|
||||
<code>{{ item.name }}</code>
|
||||
</h5>
|
||||
<code>{{ item.type }}</code>
|
||||
<span v-if="item.required" class="font-normal text-red-500 text-xs">Required*</span>
|
||||
</div>
|
||||
|
||||
<div class="[&_p]:!my-2 ml-1" v-html="item.description" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -19,7 +19,7 @@ defineProps<CalloutProps>()
|
|||
<AlertTitle v-if="title">
|
||||
{{ title }}
|
||||
</AlertTitle>
|
||||
<AlertDescription>
|
||||
<AlertDescription class="[&_a]:underline">
|
||||
<slot />
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
|
|
|||
54
apps/www/.vitepress/theme/components/DocsBreadcrumb.vue
Normal file
54
apps/www/.vitepress/theme/components/DocsBreadcrumb.vue
Normal 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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
47
apps/www/.vitepress/theme/components/ThemePopover.vue
Normal file
47
apps/www/.vitepress/theme/components/ThemePopover.vue
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<script setup lang="ts">
|
||||
import { 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>
|
||||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
150
apps/www/scripts/autogen.ts
Normal 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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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!!')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
107
apps/www/src/content/docs/charts.md
Normal file
107
apps/www/src/content/docs/charts.md
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
---
|
||||
title: Charts
|
||||
description: Versatile visualization tool, allowing users to represent data using various types of charts for effective analysis.
|
||||
label: Alpha
|
||||
---
|
||||
|
||||
<script setup>
|
||||
import Area from '~icons/gravity-ui/chart-area-stacked'
|
||||
import Bar from '~icons/gravity-ui/chart-column'
|
||||
import Line from '~icons/gravity-ui/chart-line'
|
||||
import Pie from '~icons/gravity-ui/chart-pie'
|
||||
</script>
|
||||
|
||||
<Callout>
|
||||
Only works with Vue >3.3
|
||||
</Callout>
|
||||
|
||||
`Charts` components were built on top of [Unovis](https://unovis.dev/) (a modular data visualization framework), and inspired by [tremor](https://www.tremor.so).
|
||||
|
||||
## Chart type
|
||||
|
||||
<div class="grid gap-4 mt-8 sm:grid-cols-2 sm:gap-6 not-docs">
|
||||
<LinkedCard href="/docs/charts/area">
|
||||
<Area class="text-foreground/80 w-11 h-11" />
|
||||
<p class="mt-2 font-medium">Area</p>
|
||||
</LinkedCard>
|
||||
|
||||
<LinkedCard href="/docs/charts/line">
|
||||
<Line class="text-foreground/80 w-11 h-11" />
|
||||
<p class="mt-2 font-medium">Line</p>
|
||||
</LinkedCard>
|
||||
|
||||
<LinkedCard href="/docs/charts/bar">
|
||||
<Bar class="text-foreground/80 w-11 h-11" />
|
||||
<p class="mt-2 font-medium">Bar</p>
|
||||
</LinkedCard>
|
||||
|
||||
<LinkedCard href="/docs/charts/donut">
|
||||
<Pie class="text-foreground/80 w-11 h-11" />
|
||||
<p class="mt-2 font-medium">Donut</p>
|
||||
</LinkedCard>
|
||||
</div>
|
||||
|
||||
## Installation
|
||||
|
||||
<Steps>
|
||||
|
||||
### Update `css`
|
||||
|
||||
Add the following tooltip styling to your `tailwind.css` file:
|
||||
|
||||
```css
|
||||
@layer base {
|
||||
:root {
|
||||
/* ... */
|
||||
--vis-tooltip-background-color: none !important;
|
||||
--vis-tooltip-border-color: none !important;
|
||||
--vis-tooltip-text-color: none !important;
|
||||
--vis-tooltip-shadow-color: none !important;
|
||||
--vis-tooltip-backdrop-filter: none !important;
|
||||
--vis-tooltip-padding: none !important;
|
||||
|
||||
--vis-primary-color: var(--primary);
|
||||
/* change to any hsl value you want */
|
||||
--vis-secondary-color: 160 81% 40%;
|
||||
--vis-text-color: var(--muted-foreground);
|
||||
}
|
||||
```
|
||||
|
||||
If you are not using `css-variables` for your component, you need to update the `--vis-primary-color` and `--vis-text-color` to your desired hsl value.
|
||||
|
||||
You may use [this tool](https://redpixelthemes.com/blog/tailwindcss-colors-different-formats/) to help you find the hsl value for your primary color and text color. Be sure to provide `dark` mode styling as well.
|
||||
|
||||
</Steps>
|
||||
|
||||
## Colors
|
||||
|
||||
By default, we construct the primary theme color, and secondary (`--vis-secondary-color`) color with different opacity for the graph.
|
||||
|
||||
However, you can always pass in the desired `color` into each chart.
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<AreaChart
|
||||
:data="data"
|
||||
:colors="['blue', 'pink', 'orange', 'red']"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Custom tooltip
|
||||
|
||||
If you want to customize the `Tooltip` for the chart, you can pass `customTooltip` prop with a custom Vue component.
|
||||
The custom component would receive `title` and `data` props, check out [ChartTooltip.vue component](https://github.com/radix-vue/shadcn-vue/tree/dev/apps/www/src/lib/registry/default/ui/chart/ChartTooltip.vue) for example.
|
||||
|
||||
The expected prop definition would be:
|
||||
|
||||
```ts
|
||||
defineProps<{
|
||||
title?: string
|
||||
data: {
|
||||
name: string
|
||||
color: string
|
||||
value: any
|
||||
}[]
|
||||
}>()
|
||||
```
|
||||
46
apps/www/src/content/docs/charts/area.md
Normal file
46
apps/www/src/content/docs/charts/area.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
title: Area
|
||||
description: An area chart visually represents data over time, displaying trends and patterns through filled-in areas under a line graph.
|
||||
source: apps/www/src/lib/registry/default/ui/chart-area
|
||||
label: Alpha
|
||||
---
|
||||
|
||||
<ComponentPreview name="AreaChartDemo" />
|
||||
|
||||
## Installation
|
||||
|
||||
<Callout>
|
||||
Only works with Vue >3.3
|
||||
</Callout>
|
||||
|
||||
<Steps>
|
||||
|
||||
### Run the following command
|
||||
|
||||
```bash
|
||||
npx shadcn-vue@latest add chart-area
|
||||
```
|
||||
|
||||
### Setup
|
||||
|
||||
Follow the [guide](/docs/charts.html#installation) to complete the setup.
|
||||
|
||||
</Steps>
|
||||
|
||||
## API
|
||||
|
||||
<!-- @include: @/content/meta/AreaChart.md -->
|
||||
|
||||
## Example
|
||||
|
||||
### Sparkline
|
||||
|
||||
We can turn the chart into sparkline chart by hiding axis, gridline and legends.
|
||||
|
||||
<ComponentPreview name="AreaChartSparkline" />
|
||||
|
||||
### Custom Tooltip
|
||||
|
||||
If you want to render custom tooltip, you can easily pass in a custom component. Refer to prop definition [here](/docs/charts.html#custom-tooltip).
|
||||
|
||||
<ComponentPreview name="AreaChartCustomTooltip" />
|
||||
50
apps/www/src/content/docs/charts/bar.md
Normal file
50
apps/www/src/content/docs/charts/bar.md
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
---
|
||||
title: Bar
|
||||
description: 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" />
|
||||
52
apps/www/src/content/docs/charts/donut.md
Normal file
52
apps/www/src/content/docs/charts/donut.md
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
---
|
||||
title: Donut
|
||||
description: 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" />
|
||||
46
apps/www/src/content/docs/charts/line.md
Normal file
46
apps/www/src/content/docs/charts/line.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
title: Line
|
||||
description: 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" />
|
||||
6
apps/www/src/content/docs/components.md
Normal file
6
apps/www/src/content/docs/components.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<script setup>
|
||||
import { useRouter } from 'vitepress'
|
||||
|
||||
const router = useRouter()
|
||||
router.go('/docs/components/accordion')
|
||||
</script>
|
||||
550
apps/www/src/content/docs/components/auto-form.md
Normal file
550
apps/www/src/content/docs/components/auto-form.md
Normal 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 Zod’s `refine` and `superRefine` not executing whenever some object keys are missing.
|
||||
[Read more](https://github.com/logaretm/vee-validate/issues/4338)
|
||||
|
||||
</Callout>
|
||||
|
||||
### Descriptions
|
||||
|
||||
You can use the `describe` method to set a label for each field. If no label is set, the field name will be used and un-camel-cased.
|
||||
|
||||
```ts
|
||||
const formSchema = z.object({
|
||||
username: z.string().describe('Your username'),
|
||||
someValue: z.string(), // Will be "Some Value"
|
||||
})
|
||||
```
|
||||
|
||||
You can also configure the label with [`fieldConfig`](#label) too.
|
||||
|
||||
### Optional fields
|
||||
|
||||
By default, all fields are required. You can make a field optional by using the `optional` method.
|
||||
|
||||
```ts
|
||||
const formSchema = z.object({
|
||||
username: z.string().optional(),
|
||||
})
|
||||
```
|
||||
|
||||
### Default values
|
||||
|
||||
You can set a default value for a field using the `default` method.
|
||||
|
||||
```ts
|
||||
const formSchema = z.object({
|
||||
favouriteNumber: z.number().default(5),
|
||||
})
|
||||
```
|
||||
|
||||
If you want to set default value of date, convert it to Date first using `new Date(val)`.
|
||||
|
||||
### Sub-objects
|
||||
|
||||
You can nest objects to create accordion sections.
|
||||
|
||||
```ts
|
||||
const formSchema = z.object({
|
||||
address: z.object({
|
||||
street: z.string(),
|
||||
city: z.string(),
|
||||
zip: z.string(),
|
||||
|
||||
// You can nest objects as deep as you want
|
||||
nested: z.object({
|
||||
foo: z.string(),
|
||||
bar: z.string(),
|
||||
|
||||
nested: z.object({
|
||||
foo: z.string(),
|
||||
bar: z.string(),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
```
|
||||
|
||||
Like with normal objects, you can use the `describe` method to set a label and description for the section:
|
||||
|
||||
```ts
|
||||
const formSchema = z.object({
|
||||
address: z
|
||||
.object({
|
||||
street: z.string(),
|
||||
city: z.string(),
|
||||
zip: z.string(),
|
||||
})
|
||||
.describe('Your address'),
|
||||
})
|
||||
```
|
||||
|
||||
### Select/Enums
|
||||
|
||||
AutoForm supports `enum` and `nativeEnum` to create select fields.
|
||||
|
||||
```ts
|
||||
const formSchema = z.object({
|
||||
color: z.enum(['red', 'green', 'blue']),
|
||||
})
|
||||
|
||||
enum BreadTypes {
|
||||
// For native enums, you can alternatively define a backed enum to set a custom label
|
||||
White = 'White bread',
|
||||
Brown = 'Brown bread',
|
||||
Wholegrain = 'Wholegrain bread',
|
||||
Other,
|
||||
}
|
||||
// Keep in mind that zod will validate and return the enum labels, not the enum values!
|
||||
const formSchema = z.object({
|
||||
bread: z.nativeEnum(BreadTypes),
|
||||
})
|
||||
```
|
||||
|
||||
### Arrays
|
||||
|
||||
AutoForm supports arrays _of objects_. Because inferring things like field labels from arrays of strings/numbers/etc. is difficult, only objects are supported.
|
||||
|
||||
```ts
|
||||
const formSchema = z.object({
|
||||
guestListName: z.string(),
|
||||
invitedGuests: z
|
||||
.array(
|
||||
// Define the fields for each item
|
||||
z.object({
|
||||
name: z.string(),
|
||||
age: z.number(),
|
||||
})
|
||||
)
|
||||
// Optionally set a custom label - otherwise this will be inferred from the field name
|
||||
.describe('Guests invited to the party'),
|
||||
})
|
||||
```
|
||||
|
||||
Arrays are not supported as the root element of the form schema.
|
||||
|
||||
You also can set default value of an array using .default(), but please make sure the array element has same structure with the schema.
|
||||
|
||||
```ts
|
||||
const formSchema = z.object({
|
||||
guestListName: z.string(),
|
||||
invitedGuests: z
|
||||
.array(
|
||||
// Define the fields for each item
|
||||
z.object({
|
||||
name: z.string(),
|
||||
age: z.number(),
|
||||
})
|
||||
)
|
||||
.describe('Guests invited to the party')
|
||||
.default([
|
||||
{ name: 'John', age: 24, },
|
||||
{ name: 'Jane', age: 20, },
|
||||
]),
|
||||
})
|
||||
```
|
||||
|
||||
## Field configuration
|
||||
|
||||
As zod doesn't allow adding other properties to the schema, you can use the `fieldConfig` prop to add additional configuration for the UI of each field.
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<AutoForm
|
||||
:field-config="{
|
||||
username: {
|
||||
// fieldConfig
|
||||
},
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
|
||||
### Label
|
||||
|
||||
You can use the `label` property to customize label if you want to overwrite the pre-defined label via [Zod's description](#descriptions).
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<AutoForm
|
||||
:field-config="{
|
||||
username: {
|
||||
label: 'Custom username',
|
||||
},
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
|
||||
### Description
|
||||
|
||||
You can use the `description` property to add a description below the field.
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<AutoForm
|
||||
:field-config="{
|
||||
username: {
|
||||
description: 'Enter a unique username. This will be shown to other users.',
|
||||
},
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
|
||||
### Input props
|
||||
|
||||
You can use the `inputProps` property to pass props to the input component. You can use any props that the HTML component accepts.
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<AutoForm
|
||||
:field-config="{
|
||||
username: {
|
||||
inputProps: {
|
||||
type: 'text',
|
||||
placeholder: 'Username',
|
||||
},
|
||||
},
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
|
||||
// This will be rendered as:
|
||||
<input type="text" placeholder="Username" />
|
||||
```
|
||||
|
||||
Disabling the label of an input can be done by using the `showLabel` property in `inputProps`.
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<AutoForm
|
||||
:field-config="{
|
||||
username: {
|
||||
inputProps: {
|
||||
type: 'text',
|
||||
placeholder: 'Username',
|
||||
showLabel: false,
|
||||
},
|
||||
},
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
|
||||
### Component
|
||||
|
||||
By default, AutoForm will use the Zod type to determine which input component to use. You can override this by using the `component` property.
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<AutoForm
|
||||
:field-config="{
|
||||
acceptTerms: {
|
||||
// Booleans use a checkbox by default, use a switch instead
|
||||
component: 'switch',
|
||||
},
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
|
||||
The complete list of supported field types is typed. Current supported types are:
|
||||
|
||||
- `checkbox` (default for booleans)
|
||||
- `switch`
|
||||
- `date` (default for dates)
|
||||
- `select` (default for enums)
|
||||
- `radio`
|
||||
- `textarea`
|
||||
|
||||
Alternatively, you can pass a Vue component to the `component` property to use a custom component.
|
||||
|
||||
In `CustomField.vue`
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { 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" />
|
||||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -29,7 +29,9 @@ import {
|
|||
<PopoverTrigger>
|
||||
Open popover
|
||||
</PopoverTrigger>
|
||||
<PopoverContent />
|
||||
<PopoverContent>
|
||||
Some popover content
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</template>
|
||||
```
|
||||
|
|
|
|||
18
apps/www/src/content/docs/components/range-calendar.md
Normal file
18
apps/www/src/content/docs/components/range-calendar.md
Normal 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
|
||||
```
|
||||
|
|
@ -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({
|
||||
|
|
|
|||
91
apps/www/src/content/docs/components/v-calendar.md
Normal file
91
apps/www/src/content/docs/components/v-calendar.md
Normal 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>
|
||||
```
|
||||
79
apps/www/src/content/docs/components/v-date-picker.md
Normal file
79
apps/www/src/content/docs/components/v-date-picker.md
Normal 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" />
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
116
apps/www/src/content/meta/AreaChart.md
Normal file
116
apps/www/src/content/meta/AreaChart.md
Normal 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]'
|
||||
}
|
||||
]" />
|
||||
116
apps/www/src/content/meta/BarChart.md
Normal file
116
apps/www/src/content/meta/BarChart.md
Normal 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]'
|
||||
}
|
||||
]" />
|
||||
29
apps/www/src/content/meta/ChartCrosshair.md
Normal file
29
apps/www/src/content/meta/ChartCrosshair.md
Normal 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
|
||||
}
|
||||
]" />
|
||||
22
apps/www/src/content/meta/ChartLegend.md
Normal file
22
apps/www/src/content/meta/ChartLegend.md
Normal 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[]]'
|
||||
}
|
||||
]" />
|
||||
43
apps/www/src/content/meta/ChartSingleTooltip.md
Normal file
43
apps/www/src/content/meta/ChartSingleTooltip.md
Normal 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'
|
||||
}
|
||||
]" />
|
||||
16
apps/www/src/content/meta/ChartTooltip.md
Normal file
16
apps/www/src/content/meta/ChartTooltip.md
Normal 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
|
||||
}
|
||||
]" />
|
||||
82
apps/www/src/content/meta/DonutChart.md
Normal file
82
apps/www/src/content/meta/DonutChart.md
Normal 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
|
||||
}
|
||||
]" />
|
||||
109
apps/www/src/content/meta/LineChart.md
Normal file
109
apps/www/src/content/meta/LineChart.md
Normal 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]'
|
||||
}
|
||||
]" />
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -218,7 +218,7 @@ import {
|
|||
</MenubarRadioGroup>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem inset>
|
||||
Manage Famliy...
|
||||
Manage Family...
|
||||
</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem inset>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
17
apps/www/src/lib/registry/default/example/AreaChartDemo.vue
Normal file
17
apps/www/src/lib/registry/default/example/AreaChartDemo.vue
Normal 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>
|
||||
|
|
@ -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>
|
||||
45
apps/www/src/lib/registry/default/example/AutoFormApi.vue
Normal file
45
apps/www/src/lib/registry/default/example/AutoFormApi.vue
Normal 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>
|
||||
38
apps/www/src/lib/registry/default/example/AutoFormArray.vue
Normal file
38
apps/www/src/lib/registry/default/example/AutoFormArray.vue
Normal 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>
|
||||
161
apps/www/src/lib/registry/default/example/AutoFormBasic.vue
Normal file
161
apps/www/src/lib/registry/default/example/AutoFormBasic.vue
Normal 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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
26
apps/www/src/lib/registry/default/example/BarChartDemo.vue
Normal file
26
apps/www/src/lib/registry/default/example/BarChartDemo.vue
Normal 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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
105
apps/www/src/lib/registry/default/example/CalendarForm.vue
Normal file
105
apps/www/src/lib/registry/default/example/CalendarForm.vue
Normal 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>
|
||||
127
apps/www/src/lib/registry/default/example/CalendarWithSelect.vue
Normal file
127
apps/www/src/lib/registry/default/example/CalendarWithSelect.vue
Normal 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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
20
apps/www/src/lib/registry/default/example/DonutChartDemo.vue
Normal file
20
apps/www/src/lib/registry/default/example/DonutChartDemo.vue
Normal 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>
|
||||
21
apps/www/src/lib/registry/default/example/DonutChartPie.vue
Normal file
21
apps/www/src/lib/registry/default/example/DonutChartPie.vue
Normal 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>
|
||||
|
|
@ -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>
|
||||
279
apps/www/src/lib/registry/default/example/LineChartDemo.vue
Normal file
279
apps/www/src/lib/registry/default/example/LineChartDemo.vue
Normal 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>
|
||||
285
apps/www/src/lib/registry/default/example/LineChartSparkline.vue
Normal file
285
apps/www/src/lib/registry/default/example/LineChartSparkline.vue
Normal 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>
|
||||
|
|
@ -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>
|
||||
10
apps/www/src/lib/registry/default/example/VCalendarDemo.vue
Normal file
10
apps/www/src/lib/registry/default/example/VCalendarDemo.vue
Normal 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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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,
|
||||
|
|
@ -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,
|
||||
105
apps/www/src/lib/registry/default/ui/auto-form/AutoForm.vue
Normal file
105
apps/www/src/lib/registry/default/ui/auto-form/AutoForm.vue
Normal 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>
|
||||
|
|
@ -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
Loading…
Reference in New Issue
Block a user