docs: block preview and codes
|
|
@ -1,31 +1,33 @@
|
|||
<script setup lang="ts">
|
||||
import { Badge } from '@/registry/new-york/ui/badge'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/registry/new-york/ui/popover'
|
||||
import type { Block } from '@/registry/schema'
|
||||
import Button from '@/registry/new-york/ui/button/Button.vue'
|
||||
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@/registry/new-york/ui/resizable'
|
||||
import { Separator } from '@/registry/new-york/ui/separator'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/registry/new-york/ui/tabs'
|
||||
import { ToggleGroup, ToggleGroupItem } from '@/registry/new-york/ui/toggle-group'
|
||||
import { useConfigStore } from '@/stores/config'
|
||||
import { CircleHelp, Info, Monitor, Smartphone, Tablet } from 'lucide-vue-next'
|
||||
import MagicString from 'magic-string'
|
||||
|
||||
import { reactive, ref, watch } from 'vue'
|
||||
import { useClipboard } from '@vueuse/core'
|
||||
import { Check, Fullscreen, Monitor, Smartphone, Tablet, Terminal } from 'lucide-vue-next'
|
||||
import MagicString from 'magic-string'
|
||||
import { computed, reactive, ref, watch } from 'vue'
|
||||
import { compileScript, parse, walk } from 'vue/compiler-sfc'
|
||||
import { Index } from '../../../__registry__'
|
||||
import { Index } from '../../../__registry__/block'
|
||||
import { highlight } from '../config/shiki'
|
||||
import BlockCopyButton from './BlockCopyButton.vue'
|
||||
import BlockPreview from './BlockPreview.vue'
|
||||
import StyleSwitcher from './StyleSwitcher.vue'
|
||||
import BlockViewerCode from './BlockViewerCode.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
name: string
|
||||
}>()
|
||||
|
||||
const { style, codeConfig } = useConfigStore()
|
||||
const { copied, copy } = useClipboard()
|
||||
|
||||
const isLoading = ref(true)
|
||||
const tabValue = ref('preview')
|
||||
const resizableRef = ref<InstanceType<typeof ResizablePanel>>()
|
||||
const componentRegistry = ref<Block>()
|
||||
|
||||
const rawString = ref('')
|
||||
const codeHtml = ref('')
|
||||
|
|
@ -35,6 +37,19 @@ const metadata = reactive({
|
|||
containerClass: null as string | null,
|
||||
})
|
||||
|
||||
const iframeURL = computed(() => {
|
||||
// @ts-expect-error env available in import.meta
|
||||
if (import.meta.env.SSR)
|
||||
return ''
|
||||
|
||||
const url = new URL(`${window.location.origin}/blocks/renderer`)
|
||||
url.searchParams.append('name', props.name)
|
||||
url.searchParams.append('styles', 'new-york')
|
||||
url.searchParams.append('containerClass', metadata.containerClass ?? '')
|
||||
|
||||
return url.href
|
||||
})
|
||||
|
||||
function removeScript(code: string) {
|
||||
const s = new MagicString(code)
|
||||
const scriptTagRegex = /<script\s+lang="ts"\s*>[\s\S]+?<\/script>/g
|
||||
|
|
@ -58,9 +73,11 @@ function transformImportPath(code: string) {
|
|||
watch([style, codeConfig], async () => {
|
||||
try {
|
||||
const styleIndex = Index[style.value]
|
||||
const componentRegistry = styleIndex[props.name]
|
||||
componentRegistry.value = styleIndex[props.name]
|
||||
if (!componentRegistry.value)
|
||||
return
|
||||
|
||||
const rawString = await componentRegistry.raw()
|
||||
const rawString = await componentRegistry.value.raw()
|
||||
if (!metadata.description) {
|
||||
const { descriptor } = parse(rawString)
|
||||
const ast = compileScript(descriptor, { id: '' })
|
||||
|
|
@ -91,48 +108,47 @@ watch([style, codeConfig], async () => {
|
|||
<Tabs
|
||||
:id="name"
|
||||
v-model="tabValue"
|
||||
class="relative grid w-full scroll-m-20 gap-4"
|
||||
class="group/block-view-wrapper flex min-w-0 flex-col items-stretch gap-4"
|
||||
:style=" {
|
||||
'--container-height': metadata.iframeHeight ?? '600px',
|
||||
'--height': metadata.iframeHeight ?? '600px',
|
||||
}"
|
||||
>
|
||||
<div class="flex flex-col items-center gap-4 sm:flex-row">
|
||||
<div class="flex items-center gap-2">
|
||||
<TabsList class="hidden sm:flex">
|
||||
<TabsTrigger value="preview">
|
||||
<div class="hidden items-center gap-2 sm:flex">
|
||||
<TabsList class="h-7 items-center rounded-md p-0 px-[calc(theme(spacing.1)_-_2px)] py-[theme(spacing.1)]">
|
||||
<TabsTrigger class="h-[1.45rem] rounded-sm px-2 text-xs" value="preview">
|
||||
Preview
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="code">
|
||||
<TabsTrigger class="h-[1.45rem] rounded-sm px-2 text-xs" value="code">
|
||||
Code
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<div class="hidden items-center gap-2 sm:flex">
|
||||
<Separator
|
||||
orientation="vertical"
|
||||
class="mx-2 hidden h-4 md:flex"
|
||||
/>
|
||||
<div class="flex items-center gap-2">
|
||||
<a :href="`#${name}`">
|
||||
<Badge variant="outline">{{ name }}</Badge>
|
||||
</a>
|
||||
<Popover>
|
||||
<PopoverTrigger class="hidden text-muted-foreground hover:text-foreground sm:flex">
|
||||
<Info class="h-3.5 w-3.5" />
|
||||
<span class="sr-only">Block description</span>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
side="right"
|
||||
:side-offset="10"
|
||||
class="text-sm"
|
||||
>
|
||||
{{ metadata.description }}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
|
||||
<Separator
|
||||
orientation="vertical"
|
||||
class="mx-2 hidden h-4 md:flex"
|
||||
/>
|
||||
<div class="text-sm font-medium underline-offset-2 hover:underline">
|
||||
<a :href="`#${name}`">{{ metadata.description }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 pr-[14px] sm:ml-auto">
|
||||
<div class="hidden h-[28px] items-center gap-1.5 rounded-md border p-[2px] shadow-sm md:flex">
|
||||
<Button
|
||||
variant="ghost"
|
||||
class="hidden h-7 w-7 rounded-md border bg-transparent shadow-none md:flex lg:w-auto"
|
||||
size="sm"
|
||||
@click="copy(`npx shadcn-vue@latest add ${name}`)"
|
||||
>
|
||||
<Check v-if="copied" />
|
||||
<Terminal v-else />
|
||||
<span class="hidden lg:inline">npx shadcn-vue add {{ name }}</span>
|
||||
</Button>
|
||||
<Separator
|
||||
orientation="vertical"
|
||||
class="mx-2 hidden h-4 md:flex"
|
||||
/>
|
||||
<div class="hidden h-7 items-center gap-1.5 rounded-md border p-[2px] shadow-none lg:flex">
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
default-value="100"
|
||||
|
|
@ -158,44 +174,22 @@ watch([style, codeConfig], async () => {
|
|||
>
|
||||
<Smartphone class="h-3.5 w-3.5" />
|
||||
</ToggleGroupItem>
|
||||
<Separator orientation="vertical" class="h-4" />
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
class="h-[22px] w-[22px] rounded-sm p-0"
|
||||
as-child
|
||||
title="Open in New Tab"
|
||||
>
|
||||
<a :href="iframeURL" target="_blank">
|
||||
<span class="sr-only">Open in New Tab</span>
|
||||
<Fullscreen class="h-3.5 w-3.5" />
|
||||
</a>
|
||||
</Button>
|
||||
</ToggleGroup>
|
||||
</div>
|
||||
<Separator
|
||||
orientation="vertical"
|
||||
class="mx-2 hidden h-4 md:flex"
|
||||
/>
|
||||
<StyleSwitcher class="h-7" />
|
||||
<Popover>
|
||||
<PopoverTrigger class="hidden text-muted-foreground hover:text-foreground sm:flex">
|
||||
<CircleHelp class="h-3.5 w-3.5" />
|
||||
<span class="sr-only">Block description</span>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
side="top"
|
||||
:side-offset="20"
|
||||
class="space-y-3 rounded-[0.5rem] text-sm"
|
||||
>
|
||||
<p class="font-medium">
|
||||
What is the difference between the New York and Default style?
|
||||
</p>
|
||||
<p>
|
||||
A style comes with its own set of components, animations,
|
||||
icons and more.
|
||||
</p>
|
||||
<p>
|
||||
The <span class="font-medium">Default</span> style has
|
||||
larger inputs, uses lucide-vue-next for icons and
|
||||
tailwindcss-animate for animations.
|
||||
</p>
|
||||
<p>
|
||||
The <span class="font-medium">New York</span> style ships
|
||||
with smaller buttons and inputs. It also uses shadows on cards
|
||||
and buttons.
|
||||
</p>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<Separator orientation="vertical" class="mx-2 h-4" />
|
||||
<BlockCopyButton :code="rawString" />
|
||||
<!-- <BlockCopyButton :code="rawString" /> -->
|
||||
<!-- <V0Button
|
||||
name="{block.name}"
|
||||
description="{block.description" || "Edit in v0"}
|
||||
|
|
@ -208,7 +202,7 @@ watch([style, codeConfig], async () => {
|
|||
v-show="tabValue === 'preview'"
|
||||
force-mount
|
||||
value="preview"
|
||||
class="relative after:absolute after:inset-0 after:right-3 after:z-0 after:rounded-lg after:bg-muted h-[--container-height] px-0"
|
||||
class="relative after:absolute after:inset-0 after:right-3 after:z-0 after:rounded-lg after:bg-muted h-[--height] px-0"
|
||||
>
|
||||
<ResizablePanelGroup id="block-resizable" direction="horizontal" class="relative z-10">
|
||||
<ResizablePanel
|
||||
|
|
@ -218,17 +212,18 @@ watch([style, codeConfig], async () => {
|
|||
:min-size="30"
|
||||
:as-child="true"
|
||||
>
|
||||
<BlockPreview :name="name" styles="default" :container-class="metadata.containerClass ?? ''" container />
|
||||
<BlockPreview :url="iframeURL" container />
|
||||
</ResizablePanel>
|
||||
<ResizableHandle id="block-resizable-handle" class="relative hidden w-3 bg-transparent p-0 after:absolute after:right-0 after:top-1/2 after:h-8 after:w-[6px] after:-translate-y-1/2 after:translate-x-[-1px] after:rounded-full after:bg-border after:transition-all after:hover:h-10 sm:block" />
|
||||
<ResizablePanel id="block-resizable-panel-2" :default-size="0" :min-size="0" />
|
||||
</ResizablePanelGroup>
|
||||
</TabsContent>
|
||||
<TabsContent value="code" class="h-[--container-height]">
|
||||
<div
|
||||
<TabsContent value="code" class="h-[--height]">
|
||||
<BlockViewerCode v-if="componentRegistry" :item="componentRegistry" />
|
||||
<!-- <div
|
||||
class="language-vue !h-full !max-h-[none] !mt-0"
|
||||
v-html="codeHtml"
|
||||
/>
|
||||
/> -->
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,28 +1,13 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
import Spinner from './Spinner.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
name: string
|
||||
styles?: string
|
||||
containerClass?: string
|
||||
container?: boolean
|
||||
url: string
|
||||
}>()
|
||||
|
||||
const isLoading = ref(true)
|
||||
|
||||
const iframeURL = computed(() => {
|
||||
// @ts-expect-error env available in import.meta
|
||||
if (import.meta.env.SSR)
|
||||
return ''
|
||||
|
||||
const url = new URL(`${window.location.origin}/blocks/renderer`)
|
||||
Object.entries(props).forEach(([key, value]) => {
|
||||
if (value)
|
||||
url.searchParams.append(key, value as string)
|
||||
})
|
||||
return url.href
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -35,8 +20,8 @@ const iframeURL = computed(() => {
|
|||
>
|
||||
<iframe
|
||||
v-show="!isLoading"
|
||||
:src="iframeURL"
|
||||
class="relative z-20 w-full bg-background" :class="[container ? 'h-[--container-height]' : 'size-full']"
|
||||
:src="url"
|
||||
class="relative z-20 w-full bg-background" :class="[container ? 'h-[--height]' : 'size-full']"
|
||||
@load="isLoading = false"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
70
apps/www/.vitepress/theme/components/BlockViewerCode.vue
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
<script setup lang="ts">
|
||||
import type { Block } from '@/registry/schema'
|
||||
import { File } from 'lucide-vue-next'
|
||||
import { computed, onBeforeMount, ref } from 'vue'
|
||||
import { highlight } from '../config/shiki'
|
||||
import BlockCopyCodeButton from './BlockCopyCodeButton.vue'
|
||||
import BlockViewerFileTree, { type FileTree } from './BlockViewerFileTree.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
item: Block
|
||||
}>()
|
||||
|
||||
const activeFile = ref<FileTree>()
|
||||
|
||||
const cacheCodes = ref<Map<string, string>>(new Map<string, string>())
|
||||
const activeCode = computed(() => cacheCodes.value.get(activeFile.value?.path ?? ''))
|
||||
|
||||
onBeforeMount(async () => {
|
||||
for (const file of (props.item.files ?? [])) {
|
||||
const raw = await file.raw()
|
||||
const highlighted = highlight(raw, 'vue')
|
||||
cacheCodes.value.set(file.target || file.path.split(`${props.item.name}/`)[1], highlighted)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mr-[14px] flex h-[--height] overflow-hidden rounded-xl bg-zinc-950 text-white group-data-[view=preview]/block-view-wrapper:hidden">
|
||||
<div class="w-[280px]">
|
||||
<BlockViewerFileTree v-model="activeFile" :item />
|
||||
</div>
|
||||
<div class="flex min-w-0 flex-1 flex-col">
|
||||
<div class="flex h-12 flex-shrink-0 items-center gap-2 border-b border-zinc-700 bg-zinc-900 px-4 text-sm font-medium">
|
||||
<File class="size-4" />
|
||||
{{ activeFile?.path }}
|
||||
<div class="ml-auto flex items-center gap-2">
|
||||
<BlockCopyCodeButton :code="activeCode" />
|
||||
</div>
|
||||
</div>
|
||||
<div :key="activeFile?.path" data-line-codeblock class="relative flex-1 overflow-hidden after:absolute after:inset-y-0 after:left-0 after:w-10 after:bg-zinc-950 [&_.line:before]:sticky [&_.line:before]:left-2 [&_.line:before]:z-10 [&_.line:before]:translate-y-[-1px] [&_.line:before]:pr-1 [&_pre]:h-[--height] [&_pre]:overflow-auto [&_pre]:!bg-transparent [&_pre]:pb-20 [&_pre]:pt-4 [&_pre]:font-mono [&_pre]:text-sm [&_pre]:leading-relaxed" v-html="activeCode" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
[data-line-codeblock] code {
|
||||
display: grid;
|
||||
min-width: 100%;
|
||||
overflow-wrap: break-word;
|
||||
border-radius: 0;
|
||||
border-width: 0;
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
counter-reset: line;
|
||||
-webkit-box-decoration-break: clone;
|
||||
box-decoration-break: clone;
|
||||
}
|
||||
|
||||
[data-line-codeblock] .line:before {
|
||||
font-size: .75rem;
|
||||
line-height: 1rem;
|
||||
color: hsla(0, 0%, 98%, .4);
|
||||
counter-increment: line;
|
||||
content: counter(line);
|
||||
display: inline-block;
|
||||
width: 1.8rem;
|
||||
margin-right: 1.4rem;
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
150
apps/www/.vitepress/theme/components/BlockViewerFileTree.vue
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
<script lang="ts">
|
||||
export interface FileTree {
|
||||
name: string
|
||||
path?: string
|
||||
children?: FileTree[]
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Block } from '@/registry/schema'
|
||||
import Button from '@/registry/new-york/ui/button/Button.vue'
|
||||
import { ChevronRight, File, Folder } from 'lucide-vue-next'
|
||||
import { TreeItem, TreeRoot } from 'reka-ui'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
item: Block
|
||||
}>()
|
||||
|
||||
const activeFile = defineModel<FileTree>()
|
||||
|
||||
// TODO: Improve the getter logics
|
||||
const flattenFiles = computed(() => {
|
||||
const root: FileTree[] = []
|
||||
|
||||
for (const file of props.item.files ?? []) {
|
||||
const path = file.target || file.path.split(`${props.item.name}/`)[1]
|
||||
const parts = path.split('/')
|
||||
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
const part = parts[i]
|
||||
const isFile = i === parts.length - 1
|
||||
const newNode: FileTree = isFile
|
||||
? { name: part, path }
|
||||
: { name: part, children: [] }
|
||||
|
||||
root.push(newNode)
|
||||
}
|
||||
}
|
||||
return root
|
||||
})
|
||||
|
||||
const treeItem = computed(() => {
|
||||
return createFileTreeForRegistryItemFiles([...(props.item.files ?? [])])
|
||||
})
|
||||
|
||||
const expandedKeys = ref<string[]>([])
|
||||
|
||||
function createFileTreeForRegistryItemFiles(
|
||||
files: Array<{ path: string, target?: string }>,
|
||||
) {
|
||||
const root: FileTree[] = []
|
||||
|
||||
for (const file of files) {
|
||||
const path = file.target || file.path.split(`${props.item.name}/`)[1]
|
||||
const parts = path.split('/')
|
||||
let currentLevel = root
|
||||
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
const part = parts[i]
|
||||
const isFile = i === parts.length - 1
|
||||
const existingNode = currentLevel.find(node => node.name === part)
|
||||
|
||||
if (existingNode) {
|
||||
if (isFile) {
|
||||
// Update existing file node with full path
|
||||
existingNode.path = path
|
||||
}
|
||||
else {
|
||||
// Move to next level in the tree
|
||||
currentLevel = existingNode.children!
|
||||
}
|
||||
}
|
||||
else {
|
||||
const newNode: FileTree = isFile
|
||||
? { name: part, path }
|
||||
: { name: part, children: [] }
|
||||
|
||||
currentLevel.push(newNode)
|
||||
|
||||
if (!isFile) {
|
||||
currentLevel = newNode.children!
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return root
|
||||
}
|
||||
|
||||
watch(flattenFiles, (n) => {
|
||||
activeFile.value = n.filter(i => i.path)[0]
|
||||
expandedKeys.value = n.map(i => i.name)
|
||||
}, { immediate: true })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-full w-full has-[[data-variant=inset]]:bg-sidebar flex flex-col">
|
||||
<div class="flex h-full flex-col w-full flex-1 border-r border-zinc-700 bg-zinc-900 text-white">
|
||||
<div class="duration-200 flex shrink-0 items-center font-medium outline-none ease-linear h-12 rounded-none border-b border-zinc-700 px-4 text-sm text-white">
|
||||
Files
|
||||
</div>
|
||||
<TreeRoot
|
||||
v-slot="{ flattenItems }"
|
||||
v-model="activeFile"
|
||||
v-model:expanded="expandedKeys"
|
||||
class="list-none select-none"
|
||||
:items="treeItem"
|
||||
:get-key="(item) => item.name"
|
||||
>
|
||||
<TreeItem
|
||||
v-for="item in flattenItems"
|
||||
:key="item._id"
|
||||
v-slot="{ isSelected, isExpanded }"
|
||||
v-bind="item.bind"
|
||||
as-child
|
||||
@select="(ev) => {
|
||||
if (item.hasChildren)
|
||||
ev.preventDefault()
|
||||
}"
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
:data-active="isSelected"
|
||||
class="flex w-full justify-start whitespace-nowrap rounded-none pl-[--index] hover:bg-zinc-700 hover:text-white focus-visible:bg-zinc-700 focus-visible:text-white active:bg-zinc-700 active:text-white data-[active=true]:bg-zinc-700 data-[active=true]:text-white"
|
||||
:style="{ 'padding-left': `${(item.level - 0.25) * 1.5}rem` }"
|
||||
>
|
||||
<template v-if="item.hasChildren">
|
||||
<ChevronRight
|
||||
icon="lucide:folder"
|
||||
class="h-4 w-4 transition-transform"
|
||||
:class="{ 'rotate-90': isExpanded } "
|
||||
/>
|
||||
<Folder class="h-4 w-4" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<ChevronRight
|
||||
icon="lucide:folder"
|
||||
class="invisible"
|
||||
/>
|
||||
<File class="h-4 w-4" />
|
||||
</template>
|
||||
<div>
|
||||
{{ item.value.name }}
|
||||
</div>
|
||||
</Button>
|
||||
</TreeItem>
|
||||
</TreeRoot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -12,6 +12,7 @@ import PageHeaderDescription from '../components/PageHeaderDescription.vue'
|
|||
import PageHeaderHeading from '../components/PageHeaderHeading.vue'
|
||||
import BlockContainer from './BlockContainer.vue'
|
||||
|
||||
// const blocks = ['Sidebar01', 'Sidebar02', 'Sidebar03', 'Sidebar04', 'Sidebar05', 'Sidebar06', 'Sidebar07', 'Sidebar08', 'Sidebar09', 'Sidebar10', 'Sidebar11', 'Sidebar12', 'Sidebar13', 'Sidebar14', 'Sidebar15', 'Login01']
|
||||
const blocks = Object.values(Index['new-york']).filter((i: any) => i.type === 'registry:block').map((i: any) => i.name)
|
||||
</script>
|
||||
|
||||
|
|
|
|||
1022
apps/www/__registry__/block.ts
Normal file
|
|
@ -303,7 +303,6 @@ export const Index: Record<string, any> = {
|
|||
}`
|
||||
})}],
|
||||
component: () => import("${componentPath}").then((m) => m.default),
|
||||
raw: () => import("${componentPath}?raw").then((m) => m.default),
|
||||
source: "${sourceFilename}",
|
||||
category: "${item.category ?? ''}",
|
||||
subcategory: "${item.subcategory ?? ''}"
|
||||
|
|
@ -357,6 +356,91 @@ export const Index: Record<string, any> = {
|
|||
await writeFile(path.join(process.cwd(), '__registry__/index.ts'), index)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Build __registry__/block.ts.
|
||||
// ----------------------------------------------------------------------------
|
||||
async function buildBlockRegistry(registry: Registry) {
|
||||
let index = `// @ts-nocheck
|
||||
// This file is autogenerated by scripts/build-registry.ts
|
||||
// Do not edit this file directly.
|
||||
|
||||
export const Index: Record<string, any> = {
|
||||
`
|
||||
|
||||
for (const style of styles) {
|
||||
index += ` "${style.name}": {`
|
||||
|
||||
// Build style index.
|
||||
for (const item of registry) {
|
||||
if (item.type !== 'registry:block')
|
||||
continue
|
||||
|
||||
const resolveFiles = item.files?.map(
|
||||
file =>
|
||||
`registry/${style.name}/${
|
||||
typeof file === 'string' ? file : file.path
|
||||
}`,
|
||||
)
|
||||
if (!resolveFiles) {
|
||||
continue
|
||||
}
|
||||
|
||||
const type = item.type.split(':')[1]
|
||||
|
||||
let componentPath = `@/registry/${style.name}/${type}/${item.name}`
|
||||
|
||||
if (item.files) {
|
||||
const files = item.files.map(file =>
|
||||
typeof file === 'string'
|
||||
? { type: 'registry:page', path: file }
|
||||
: file,
|
||||
)
|
||||
if (files?.length) {
|
||||
componentPath = `@/registry/${style.name}/${files[0].path}`
|
||||
}
|
||||
}
|
||||
|
||||
index += `
|
||||
"${item.name}": {
|
||||
name: "${item.name}",
|
||||
description: "${item.description ?? ''}",
|
||||
type: "${item.type}",
|
||||
registryDependencies: ${JSON.stringify(item.registryDependencies)},
|
||||
files: [${item.files?.map((file) => {
|
||||
const filePath = `registry/${style.name}/${
|
||||
typeof file === 'string' ? file : file.path
|
||||
}`
|
||||
const resolvedFilePath = path.resolve(filePath)
|
||||
return typeof file === 'string'
|
||||
? `"${resolvedFilePath}"`
|
||||
: `{
|
||||
path: "${filePath}",
|
||||
type: "${file.type}",
|
||||
target: "${file.target ?? ''}",
|
||||
raw: () => import("@/${filePath}?raw").then((m) => m.default)
|
||||
}`
|
||||
})}],
|
||||
component: () => import("${componentPath}").then((m) => m.default),
|
||||
raw: () => import("${componentPath}?raw").then((m) => m.default),
|
||||
source: "",
|
||||
category: "${item.category ?? ''}",
|
||||
subcategory: "${item.subcategory ?? ''}"
|
||||
},`
|
||||
}
|
||||
|
||||
index += `
|
||||
},`
|
||||
}
|
||||
|
||||
index += `
|
||||
}
|
||||
`
|
||||
|
||||
// Write style block.
|
||||
rimraf.sync(path.join(process.cwd(), '__registry__/block.ts'))
|
||||
await writeFile(path.join(process.cwd(), '__registry__/block.ts'), index)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Build registry/styles/[style]/[name].json.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
|
@ -886,6 +970,7 @@ try {
|
|||
}
|
||||
|
||||
await buildRegistry(result.data)
|
||||
await buildBlockRegistry(result.data)
|
||||
await buildStyles(result.data)
|
||||
await buildStylesIndex()
|
||||
await buildThemes()
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ title: Sidebar
|
|||
description: A composable, themeable and customizable sidebar component.
|
||||
---
|
||||
|
||||
<BlockPreview name="Sidebar07" ></BlockPreview>
|
||||
<BlockPreview url="/blocks/renderer?name=Sidebar07&styles=new-york" ></BlockPreview>
|
||||
|
||||
## Installation
|
||||
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 246 B After Width: | Height: | Size: 363 B |
|
Before Width: | Height: | Size: 409 B After Width: | Height: | Size: 628 B |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
|
@ -10,7 +10,7 @@
|
|||
"path": "example/AccordionDemo.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/registry/default/ui/accordion'\n\nconst defaultValue = 'item-1'\n\nconst accordionItems = [\n { value: 'item-1', title: 'Is it accessible?', content: 'Yes. It adheres to the WAI-ARIA design pattern.' },\n { value: 'item-2', title: 'Is it unstyled?', content: 'Yes. It\\'s unstyled by default, giving you freedom over the look and feel.' },\n { value: 'item-3', title: 'Can it be animated?', content: 'Yes! You can use the transition prop to configure the animation.' },\n]\n</script>\n\n<template>\n <Accordion type=\"single\" class=\"w-full\" collapsible :default-value=\"defaultValue\">\n <AccordionItem v-for=\"item in accordionItems\" :key=\"item.value\" :value=\"item.value\">\n <AccordionTrigger>{{ item.title }}</AccordionTrigger>\n <AccordionContent>\n {{ item.content }}\n </AccordionContent>\n </AccordionItem>\n </Accordion>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "AccordionDemo.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"path": "example/AlertDemo.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { Alert, AlertDescription, AlertTitle } from '@/registry/default/ui/alert'\nimport { Terminal } from 'lucide-vue-next'\n</script>\n\n<template>\n <Alert>\n <Terminal class=\"h-4 w-4\" />\n <AlertTitle>Heads up!</AlertTitle>\n <AlertDescription>\n You can add components to your app using the cli.\n </AlertDescription>\n </Alert>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "AlertDemo.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"path": "example/AlertDestructiveDemo.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { Alert, AlertDescription, AlertTitle } from '@/registry/default/ui/alert'\nimport { AlertCircle } from 'lucide-vue-next'\n</script>\n\n<template>\n <Alert variant=\"destructive\">\n <AlertCircle class=\"w-4 h-4\" />\n <AlertTitle>Error</AlertTitle>\n <AlertDescription>\n Your session has expired. Please log in again.\n </AlertDescription>\n </Alert>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "AlertDestructiveDemo.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
"path": "example/AlertDialogDemo.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n AlertDialogTrigger,\n} from '@/registry/default/ui/alert-dialog'\nimport { Button } from '@/registry/default/ui/button'\n</script>\n\n<template>\n <AlertDialog>\n <AlertDialogTrigger as-child>\n <Button variant=\"outline\">\n Show Dialog\n </Button>\n </AlertDialogTrigger>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>\n <AlertDialogDescription>\n This action cannot be undone. This will permanently delete your\n account and remove your data from our servers.\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Cancel</AlertDialogCancel>\n <AlertDialogAction>Continue</AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "AlertDialogDemo.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"path": "example/AreaChartCustomTooltip.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { AreaChart } from '@/registry/default/ui/chart-area'\nimport CustomChartTooltip from './CustomChartTooltip.vue'\n\nconst data = [\n { name: 'Jan', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n { name: 'Feb', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n { name: 'Mar', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n { name: 'Apr', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n { name: 'May', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n { name: 'Jun', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n { name: 'Jul', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n]\n</script>\n\n<template>\n <AreaChart\n index=\"name\"\n :data=\"data\"\n :categories=\"['total', 'predicted']\"\n :custom-tooltip=\"CustomChartTooltip\"\n />\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "AreaChartCustomTooltip.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"path": "example/AreaChartDemo.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { AreaChart } from '@/registry/default/ui/chart-area'\n\nconst data = [\n { name: 'Jan', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n { name: 'Feb', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n { name: 'Mar', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n { name: 'Apr', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n { name: 'May', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n { name: 'Jun', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n { name: 'Jul', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n]\n</script>\n\n<template>\n <AreaChart :data=\"data\" index=\"name\" :categories=\"['total', 'predicted']\" />\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "AreaChartDemo.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"path": "example/AreaChartSparkline.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { AreaChart } from '@/registry/default/ui/chart-area'\nimport { CurveType } from '@unovis/ts'\n\nconst data = [\n { name: 'Jan', total: Math.floor(Math.random() * 2000) + 1000 },\n { name: 'Feb', total: Math.floor(Math.random() * 2000) + 1000 },\n { name: 'Mar', total: Math.floor(Math.random() * 2000) + 1000 },\n { name: 'Apr', total: Math.floor(Math.random() * 2000) + 1000 },\n { name: 'May', total: Math.floor(Math.random() * 2000) + 1000 },\n { name: 'Jun', total: Math.floor(Math.random() * 2000) + 1000 },\n { name: 'Jul', total: Math.floor(Math.random() * 2000) + 1000 },\n { name: 'Aug', total: Math.floor(Math.random() * 2000) + 1000 },\n { name: 'Sep', total: Math.floor(Math.random() * 2000) + 1000 },\n { name: 'Oct', total: Math.floor(Math.random() * 2000) + 1000 },\n { name: 'Nov', total: Math.floor(Math.random() * 2000) + 1000 },\n { name: 'Dec', total: Math.floor(Math.random() * 2000) + 1000 },\n]\n</script>\n\n<template>\n <AreaChart\n class=\"h-[100px] w-[400px]\"\n index=\"name\"\n :data=\"data\"\n :categories=\"['total']\"\n :show-grid-line=\"false\"\n :show-legend=\"false\"\n :show-x-axis=\"false\"\n :show-y-axis=\"false\"\n :curve-type=\"CurveType.Linear\"\n />\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "AreaChartSparkline.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"path": "example/AspectRatioDemo.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { AspectRatio } from '@/registry/default/ui/aspect-ratio'\n</script>\n\n<template>\n <AspectRatio :ratio=\"16 / 9\" class=\"bg-muted\">\n <img\n src=\"https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80\"\n alt=\"Photo by Drew Beamer\"\n class=\"rounded-md object-cover w-full h-full\"\n >\n </AspectRatio>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "AspectRatioDemo.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
"path": "block/Authentication01.vue",
|
||||
"content": "<script lang=\"ts\">\nexport const description\n = 'A simple login form with email and password. The submit button says \\'Sign in\\'.'\nexport const iframeHeight = '600px'\nexport const containerClass = 'w-full h-screen flex items-center justify-center px-4'\n</script>\n\n<script setup lang=\"ts\">\nimport { Button } from '@/registry/default/ui/button'\nimport { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/registry/default/ui/card'\nimport { Input } from '@/registry/default/ui/input'\nimport { Label } from '@/registry/default/ui/label'\n</script>\n\n<template>\n <Card class=\"w-full max-w-sm\">\n <CardHeader>\n <CardTitle class=\"text-2xl\">\n Login\n </CardTitle>\n <CardDescription>\n Enter your email below to login to your account.\n </CardDescription>\n </CardHeader>\n <CardContent class=\"grid gap-4\">\n <div class=\"grid gap-2\">\n <Label for=\"email\">Email</Label>\n <Input id=\"email\" type=\"email\" placeholder=\"m@example.com\" required />\n </div>\n <div class=\"grid gap-2\">\n <Label for=\"password\">Password</Label>\n <Input id=\"password\" type=\"password\" required />\n </div>\n </CardContent>\n <CardFooter>\n <Button class=\"w-full\">\n Sign in\n </Button>\n </CardFooter>\n </Card>\n</template>\n",
|
||||
"type": "registry:block",
|
||||
"target": "Authentication01.vue"
|
||||
"target": "pages/dashboard/index.vue"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
"path": "block/Authentication02.vue",
|
||||
"content": "<script lang=\"ts\">\nexport const description\n = 'A login form with email and password. There\\'s an option to login with Google and a link to sign up if you don\\'t have an account.'\nexport const iframeHeight = '600px'\nexport const containerClass = 'w-full h-screen flex items-center justify-center px-4'\n</script>\n\n<script setup lang=\"ts\">\nimport { Button } from '@/registry/default/ui/button'\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/registry/default/ui/card'\nimport { Input } from '@/registry/default/ui/input'\nimport { Label } from '@/registry/default/ui/label'\n</script>\n\n<template>\n <Card class=\"mx-auto max-w-sm\">\n <CardHeader>\n <CardTitle class=\"text-2xl\">\n Login\n </CardTitle>\n <CardDescription>\n Enter your email below to login to your account\n </CardDescription>\n </CardHeader>\n <CardContent>\n <div class=\"grid gap-4\">\n <div class=\"grid gap-2\">\n <Label for=\"email\">Email</Label>\n <Input\n id=\"email\"\n type=\"email\"\n placeholder=\"m@example.com\"\n required\n />\n </div>\n <div class=\"grid gap-2\">\n <div class=\"flex items-center\">\n <Label for=\"password\">Password</Label>\n <a href=\"#\" class=\"ml-auto inline-block text-sm underline\">\n Forgot your password?\n </a>\n </div>\n <Input id=\"password\" type=\"password\" required />\n </div>\n <Button type=\"submit\" class=\"w-full\">\n Login\n </Button>\n <Button variant=\"outline\" class=\"w-full\">\n Login with Google\n </Button>\n </div>\n <div class=\"mt-4 text-center text-sm\">\n Don't have an account?\n <a href=\"#\" class=\"underline\">\n Sign up\n </a>\n </div>\n </CardContent>\n </Card>\n</template>\n",
|
||||
"type": "registry:block",
|
||||
"target": "Authentication02.vue"
|
||||
"target": "pages/dashboard/index.vue"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
"path": "block/Authentication03.vue",
|
||||
"content": "<script lang=\"ts\">\nexport const description\n = 'A sign up form with first name, last name, email and password inside a card. There\\'s an option to sign up with GitHub and a link to login if you already have an account'\nexport const iframeHeight = '600px'\nexport const containerClass = 'w-full h-screen flex items-center justify-center px-4'\n</script>\n\n<script setup lang=\"ts\">\nimport { Button } from '@/registry/default/ui/button'\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/registry/default/ui/card'\nimport { Input } from '@/registry/default/ui/input'\nimport { Label } from '@/registry/default/ui/label'\n</script>\n\n<template>\n <Card class=\"mx-auto max-w-sm\">\n <CardHeader>\n <CardTitle class=\"text-xl\">\n Sign Up\n </CardTitle>\n <CardDescription>\n Enter your information to create an account\n </CardDescription>\n </CardHeader>\n <CardContent>\n <div class=\"grid gap-4\">\n <div class=\"grid grid-cols-2 gap-4\">\n <div class=\"grid gap-2\">\n <Label for=\"first-name\">First name</Label>\n <Input id=\"first-name\" placeholder=\"Max\" required />\n </div>\n <div class=\"grid gap-2\">\n <Label for=\"last-name\">Last name</Label>\n <Input id=\"last-name\" placeholder=\"Robinson\" required />\n </div>\n </div>\n <div class=\"grid gap-2\">\n <Label for=\"email\">Email</Label>\n <Input\n id=\"email\"\n type=\"email\"\n placeholder=\"m@example.com\"\n required\n />\n </div>\n <div class=\"grid gap-2\">\n <Label for=\"password\">Password</Label>\n <Input id=\"password\" type=\"password\" />\n </div>\n <Button type=\"submit\" class=\"w-full\">\n Create an account\n </Button>\n <Button variant=\"outline\" class=\"w-full\">\n Sign up with GitHub\n </Button>\n </div>\n <div class=\"mt-4 text-center text-sm\">\n Already have an account?\n <a href=\"#\" class=\"underline\">\n Sign in\n </a>\n </div>\n </CardContent>\n </Card>\n</template>\n",
|
||||
"type": "registry:block",
|
||||
"target": "Authentication03.vue"
|
||||
"target": "pages/dashboard/index.vue"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"path": "block/Authentication04.vue",
|
||||
"content": "<script lang=\"ts\">\nexport const description\n = 'A login page with two columns. The first column has the login form with email and password. There\\'s a Forgot your passwork link and a link to sign up if you do not have an account. The second column has a cover image.'\nexport const iframeHeight = '800px'\nexport const containerClass = 'w-full h-full p-4 lg:p-0'\n</script>\n\n<script setup lang=\"ts\">\nimport { Button } from '@/registry/default/ui/button'\nimport { Input } from '@/registry/default/ui/input'\nimport { Label } from '@/registry/default/ui/label'\n</script>\n\n<template>\n <div class=\"w-full lg:grid lg:min-h-[600px] lg:grid-cols-2 xl:min-h-[800px]\">\n <div class=\"flex items-center justify-center py-12\">\n <div class=\"mx-auto grid w-[350px] gap-6\">\n <div class=\"grid gap-2 text-center\">\n <h1 class=\"text-3xl font-bold\">\n Login\n </h1>\n <p class=\"text-balance text-muted-foreground\">\n Enter your email below to login to your account\n </p>\n </div>\n <div class=\"grid gap-4\">\n <div class=\"grid gap-2\">\n <Label for=\"email\">Email</Label>\n <Input\n id=\"email\"\n type=\"email\"\n placeholder=\"m@example.com\"\n required\n />\n </div>\n <div class=\"grid gap-2\">\n <div class=\"flex items-center\">\n <Label for=\"password\">Password</Label>\n <a\n href=\"/forgot-password\"\n class=\"ml-auto inline-block text-sm underline\"\n >\n Forgot your password?\n </a>\n </div>\n <Input id=\"password\" type=\"password\" required />\n </div>\n <Button type=\"submit\" class=\"w-full\">\n Login\n </Button>\n <Button variant=\"outline\" class=\"w-full\">\n Login with Google\n </Button>\n </div>\n <div class=\"mt-4 text-center text-sm\">\n Don't have an account?\n <a href=\"#\" class=\"underline\">\n Sign up\n </a>\n </div>\n </div>\n </div>\n <div class=\"hidden bg-muted lg:block\">\n <img\n src=\"/placeholder.svg\"\n alt=\"Image\"\n width=\"1920\"\n height=\"1080\"\n class=\"h-full w-full object-cover dark:brightness-[0.2] dark:grayscale\"\n >\n </div>\n </div>\n</template>\n",
|
||||
"type": "registry:block",
|
||||
"target": "Authentication04.vue"
|
||||
"target": "pages/dashboard/index.vue"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"path": "example/AutoFormApi.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { AutoForm } from '@/registry/default/ui/auto-form'\nimport { Button } from '@/registry/default/ui/button'\nimport { toast } from '@/registry/default/ui/toast'\nimport { h, onMounted, shallowRef } from 'vue'\nimport * as z from 'zod'\n\nconst schema = shallowRef<z.ZodObject< any, any, any > | null>(null)\n\nonMounted(() => {\n fetch('https://jsonplaceholder.typicode.com/users')\n .then(response => response.json())\n .then((data) => {\n schema.value = z.object({\n user: z.enum(data.map((user: any) => user.name)),\n })\n })\n})\n\nfunction onSubmit(values: Record<string, any>) {\n toast({\n title: 'You submitted the following values:',\n 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))),\n })\n}\n</script>\n\n<template>\n <div class=\"flex justify-center w-full\">\n <AutoForm\n v-if=\"schema\"\n class=\"w-2/3 space-y-6\"\n :schema=\"schema\"\n @submit=\"onSubmit\"\n >\n <Button type=\"submit\">\n Submit\n </Button>\n </AutoForm>\n\n <div v-else>\n Loading...\n </div>\n </div>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "AutoFormApi.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"path": "example/AutoFormArray.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { AutoForm } from '@/registry/default/ui/auto-form'\nimport { Button } from '@/registry/default/ui/button'\nimport { toast } from '@/registry/default/ui/toast'\nimport { h } from 'vue'\nimport * as z from 'zod'\n\nconst schema = z.object({\n guestListName: z.string(),\n invitedGuests: z\n .array(\n z.object({\n name: z.string(),\n age: z.coerce.number(),\n }),\n )\n .describe('Guests invited to the party'),\n})\n\nfunction onSubmit(values: Record<string, any>) {\n toast({\n title: 'You submitted the following values:',\n 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))),\n })\n}\n</script>\n\n<template>\n <AutoForm\n class=\"w-2/3 space-y-6\"\n :schema=\"schema\"\n @submit=\"onSubmit\"\n >\n <Button type=\"submit\">\n Submit\n </Button>\n </AutoForm>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "AutoFormArray.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"path": "example/AutoFormBasic.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { AutoForm, AutoFormField } from '@/registry/default/ui/auto-form'\nimport { Button } from '@/registry/default/ui/button'\nimport { toast } from '@/registry/default/ui/toast'\nimport { h } from 'vue'\nimport * as z from 'zod'\n\nenum Sports {\n Football = 'Football/Soccer',\n Basketball = 'Basketball',\n Baseball = 'Baseball',\n Hockey = 'Hockey (Ice)',\n None = 'I don\\'t like sports',\n}\n\nconst schema = z.object({\n username: z\n .string({\n required_error: 'Username is required.',\n })\n .min(2, {\n message: 'Username must be at least 2 characters.',\n }),\n\n password: z\n .string({\n required_error: 'Password is required.',\n })\n .min(8, {\n message: 'Password must be at least 8 characters.',\n }),\n\n favouriteNumber: z.coerce\n .number({\n invalid_type_error: 'Favourite number must be a number.',\n })\n .min(1, {\n message: 'Favourite number must be at least 1.',\n })\n .max(10, {\n message: 'Favourite number must be at most 10.',\n })\n .default(1)\n .optional(),\n\n acceptTerms: z\n .boolean()\n .refine(value => value, {\n message: 'You must accept the terms and conditions.',\n path: ['acceptTerms'],\n }),\n\n sendMeMails: z.boolean().optional(),\n\n birthday: z.coerce.date().optional(),\n\n color: z.enum(['red', 'green', 'blue']).optional(),\n\n // Another enum example\n marshmallows: z\n .enum(['not many', 'a few', 'a lot', 'too many']),\n\n // Native enum example\n sports: z.nativeEnum(Sports).describe('What is your favourite sport?'),\n\n bio: z\n .string()\n .min(10, {\n message: 'Bio must be at least 10 characters.',\n })\n .max(160, {\n message: 'Bio must not be longer than 30 characters.',\n })\n .optional(),\n\n customParent: z.string().optional(),\n\n file: z.string().optional(),\n})\n\nfunction onSubmit(values: Record<string, any>) {\n toast({\n title: 'You submitted the following values:',\n 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))),\n })\n}\n</script>\n\n<template>\n <AutoForm\n class=\"w-2/3 space-y-6\"\n :schema=\"schema\"\n :field-config=\"{\n password: {\n label: 'Your secure password',\n inputProps: {\n type: 'password',\n placeholder: '••••••••',\n },\n },\n favouriteNumber: {\n description: 'Your favourite number between 1 and 10.',\n },\n acceptTerms: {\n label: 'Accept terms and conditions.',\n inputProps: {\n required: true,\n },\n },\n\n birthday: {\n description: 'We need your birthday to send you a gift.',\n },\n\n sendMeMails: {\n component: 'switch',\n },\n\n bio: {\n component: 'textarea',\n },\n\n marshmallows: {\n label: 'How many marshmallows fit in your mouth?',\n component: 'radio',\n },\n\n file: {\n label: 'Text file',\n component: 'file',\n },\n }\"\n @submit=\"onSubmit\"\n >\n <template #acceptTerms=\"slotProps\">\n <AutoFormField v-bind=\"slotProps\" />\n <div class=\"!mt-2 text-sm\">\n I agree to the <button class=\"text-primary underline\">\n terms and conditions\n </button>.\n </div>\n </template>\n\n <template #customParent=\"slotProps\">\n <div class=\"flex items-end space-x-2\">\n <AutoFormField v-bind=\"slotProps\" class=\"w-full\" />\n <Button type=\"button\">\n Check\n </Button>\n </div>\n </template>\n\n <Button type=\"submit\">\n Submit\n </Button>\n </AutoForm>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "AutoFormBasic.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"path": "example/AutoFormConfirmPassword.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { AutoForm } from '@/registry/default/ui/auto-form'\nimport { Button } from '@/registry/default/ui/button'\nimport { toast } from '@/registry/default/ui/toast'\nimport { h } from 'vue'\nimport * as z from 'zod'\n\nconst schema = z\n .object({\n password: z.string(),\n confirm: z.string(),\n })\n .refine(data => data.password === data.confirm, {\n message: 'Passwords must match.',\n path: ['confirm'],\n })\n\nfunction onSubmit(values: Record<string, any>) {\n toast({\n title: 'You submitted the following values:',\n 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))),\n })\n}\n</script>\n\n<template>\n <AutoForm\n class=\"w-2/3 space-y-6\"\n :schema=\"schema\"\n @submit=\"onSubmit\"\n >\n <Button type=\"submit\">\n Submit\n </Button>\n </AutoForm>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "AutoFormConfirmPassword.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
"path": "example/AutoFormControlled.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { AutoForm } from '@/registry/default/ui/auto-form'\nimport { Button } from '@/registry/default/ui/button'\nimport { toast } from '@/registry/default/ui/toast'\nimport { toTypedSchema } from '@vee-validate/zod'\nimport { useForm } from 'vee-validate'\nimport { h } from 'vue'\nimport * as z from 'zod'\n\nconst schema = z.object({\n username: z.string(),\n})\n\nconst form = useForm({\n validationSchema: toTypedSchema(schema),\n})\n\nfunction onSubmit(values: Record<string, any>) {\n toast({\n title: 'You submitted the following values:',\n 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))),\n })\n}\n</script>\n\n<template>\n <AutoForm\n class=\"w-2/3 space-y-6\"\n :schema=\"schema\"\n :form=\"form\"\n @submit=\"onSubmit\"\n >\n <Button type=\"submit\">\n Submit\n </Button>\n </AutoForm>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "AutoFormControlled.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"path": "example/AutoFormDependencies.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { AutoForm } from '@/registry/default/ui/auto-form'\nimport { Button } from '@/registry/default/ui/button'\nimport { toast } from '@/registry/default/ui/toast'\nimport { h } from 'vue'\nimport * as z from 'zod'\nimport { DependencyType } from '../ui/auto-form/interface'\n\nconst schema = z.object({\n age: z.number(),\n parentsAllowed: z.boolean().optional(),\n vegetarian: z.boolean().optional(),\n mealOptions: z.enum(['Pasta', 'Salad', 'Beef Wellington']).optional(),\n})\n\nfunction onSubmit(values: Record<string, any>) {\n toast({\n title: 'You submitted the following values:',\n 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))),\n })\n}\n</script>\n\n<template>\n <AutoForm\n class=\"w-2/3 space-y-6\"\n :schema=\"schema\"\n :field-config=\"{\n age: {\n description:\n 'Setting this below 18 will require parents consent.',\n },\n parentsAllowed: {\n label: 'Did your parents allow you to register?',\n },\n vegetarian: {\n label: 'Are you a vegetarian?',\n description:\n 'Setting this to true will remove non-vegetarian food options.',\n },\n mealOptions: {\n component: 'radio',\n },\n }\"\n :dependencies=\"[\n {\n sourceField: 'age',\n type: DependencyType.HIDES,\n targetField: 'parentsAllowed',\n when: (age) => age >= 18,\n },\n {\n sourceField: 'age',\n type: DependencyType.REQUIRES,\n targetField: 'parentsAllowed',\n when: (age) => age < 18,\n },\n {\n sourceField: 'vegetarian',\n type: DependencyType.SETS_OPTIONS,\n targetField: 'mealOptions',\n when: (vegetarian) => vegetarian,\n options: ['Pasta', 'Salad'],\n },\n ]\"\n @submit=\"onSubmit\"\n >\n <Button type=\"submit\">\n Submit\n </Button>\n </AutoForm>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "AutoFormDependencies.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"path": "example/AutoFormInputWithoutLabel.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { AutoForm, AutoFormField } from '@/registry/default/ui/auto-form'\nimport { Button } from '@/registry/default/ui/button'\nimport { toast } from '@/registry/default/ui/toast'\nimport { h } from 'vue'\nimport * as z from 'zod'\n\nconst schema = z.object({\n username: z.string(),\n})\n\nfunction onSubmit(values: Record<string, any>) {\n toast({\n title: 'You submitted the following values:',\n 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))),\n })\n}\n</script>\n\n<template>\n <AutoForm\n class=\"w-2/3 space-y-6\"\n :schema=\"schema\"\n :field-config=\"{\n username: {\n hideLabel: true,\n },\n }\"\n @submit=\"onSubmit\"\n >\n <template #username=\"slotProps\">\n <div class=\"flex items-start gap-3\">\n <div class=\"flex-1\">\n <AutoFormField v-bind=\"slotProps\" />\n </div>\n <div>\n <Button type=\"submit\">\n Update\n </Button>\n </div>\n </div>\n </template>\n </AutoForm>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "AutoFormInputWithoutLabel.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"path": "example/AutoFormSubObject.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { AutoForm } from '@/registry/default/ui/auto-form'\nimport { Button } from '@/registry/default/ui/button'\nimport { toast } from '@/registry/default/ui/toast'\nimport { h } from 'vue'\nimport * as z from 'zod'\n\nconst schema = z.object({\n subObject: z.object({\n subField: z.string().optional().default('Sub Field'),\n numberField: z.number().optional().default(1),\n\n subSubObject: z\n .object({\n subSubField: z.string().default('Sub Sub Field'),\n })\n .describe('Sub Sub Object Description'),\n }),\n optionalSubObject: z\n .object({\n optionalSubField: z.string(),\n otherOptionalSubField: z.string(),\n })\n .optional(),\n})\n\nfunction onSubmit(values: Record<string, any>) {\n toast({\n title: 'You submitted the following values:',\n 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))),\n })\n}\n</script>\n\n<template>\n <AutoForm\n class=\"w-2/3 space-y-6\"\n :schema=\"schema\"\n :field-config=\"{\n subObject: {\n numberField: {\n inputProps: {\n type: 'number',\n },\n },\n },\n }\"\n @submit=\"onSubmit\"\n >\n <Button type=\"submit\">\n Submit\n </Button>\n </AutoForm>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "AutoFormSubObject.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"path": "example/AvatarDemo.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { Avatar, AvatarFallback, AvatarImage } from '@/registry/default/ui/avatar'\n</script>\n\n<template>\n <Avatar>\n <AvatarImage src=\"https://github.com/unovue.png\" alt=\"@unovue\" />\n <AvatarFallback>CN</AvatarFallback>\n </Avatar>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "AvatarDemo.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"path": "example/BadgeDemo.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { Badge } from '@/registry/default/ui/badge'\n</script>\n\n<template>\n <Badge>Badge</Badge>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "BadgeDemo.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"path": "example/BadgeDestructiveDemo.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { Badge } from '@/registry/default/ui/badge'\n</script>\n\n<template>\n <Badge variant=\"destructive\">\n Destructive\n </Badge>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "BadgeDestructiveDemo.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"path": "example/BadgeOutlineDemo.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { Badge } from '@/registry/default/ui/badge'\n</script>\n\n<template>\n <Badge variant=\"outline\">\n Outline\n </Badge>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "BadgeOutlineDemo.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"path": "example/BadgeSecondaryDemo.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { Badge } from '@/registry/default/ui/badge'\n</script>\n\n<template>\n <Badge variant=\"secondary\">\n Secondary\n </Badge>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "BadgeSecondaryDemo.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"path": "example/BarChartCustomTooltip.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { BarChart } from '@/registry/default/ui/chart-bar'\nimport CustomChartTooltip from './CustomChartTooltip.vue'\n\nconst data = [\n { name: 'Jan', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n { name: 'Feb', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n { name: 'Mar', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n { name: 'Apr', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n { name: 'May', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n { name: 'Jun', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n { name: 'Jul', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n]\n</script>\n\n<template>\n <BarChart\n :data=\"data\"\n index=\"name\"\n :categories=\"['total', 'predicted']\"\n :y-formatter=\"(tick, i) => {\n return typeof tick === 'number'\n ? `$ ${new Intl.NumberFormat('us').format(tick).toString()}`\n : ''\n }\"\n :custom-tooltip=\"CustomChartTooltip\"\n />\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "BarChartCustomTooltip.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"path": "example/BarChartDemo.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { BarChart } from '@/registry/default/ui/chart-bar'\n\nconst data = [\n { name: 'Jan', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n { name: 'Feb', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n { name: 'Mar', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n { name: 'Apr', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n { name: 'May', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n { name: 'Jun', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n { name: 'Jul', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n]\n</script>\n\n<template>\n <BarChart\n :data=\"data\"\n index=\"name\"\n :categories=\"['total', 'predicted']\"\n :y-formatter=\"(tick, i) => {\n return typeof tick === 'number'\n ? `$ ${new Intl.NumberFormat('us').format(tick).toString()}`\n : ''\n }\"\n />\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "BarChartDemo.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"path": "example/BarChartRounded.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { BarChart } from '@/registry/default/ui/chart-bar'\n\nconst data = [\n { name: 'Jan', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n { name: 'Feb', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n { name: 'Mar', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n { name: 'Apr', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n { name: 'May', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n { name: 'Jun', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n { name: 'Jul', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n]\n</script>\n\n<template>\n <BarChart\n index=\"name\"\n :data=\"data\"\n :categories=\"['total', 'predicted']\"\n :y-formatter=\"(tick, i) => {\n return typeof tick === 'number'\n ? `$ ${new Intl.NumberFormat('us').format(tick).toString()}`\n : ''\n }\"\n :rounded-corners=\"4\"\n />\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "BarChartRounded.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"path": "example/BarChartStacked.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { BarChart } from '@/registry/default/ui/chart-bar'\n\nconst data = [\n { name: 'Jan', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n { name: 'Feb', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n { name: 'Mar', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n { name: 'Apr', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n { name: 'May', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n { name: 'Jun', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n { name: 'Jul', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },\n]\n</script>\n\n<template>\n <BarChart\n index=\"name\"\n :data=\"data\"\n :categories=\"['total', 'predicted']\"\n :y-formatter=\"(tick, i) => {\n return typeof tick === 'number'\n ? `$ ${new Intl.NumberFormat('us').format(tick).toString()}`\n : ''\n }\"\n :type=\"'stacked'\"\n />\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "BarChartStacked.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
"path": "example/BreadcrumbDemo.vue",
|
||||
"content": "<script lang=\"ts\" setup>\nimport {\n Breadcrumb,\n BreadcrumbEllipsis,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbList,\n BreadcrumbPage,\n BreadcrumbSeparator,\n} from '@/registry/default/ui/breadcrumb'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '@/registry/default/ui/dropdown-menu'\n</script>\n\n<template>\n <Breadcrumb>\n <BreadcrumbList>\n <BreadcrumbItem>\n <BreadcrumbLink href=\"/\">\n Home\n </BreadcrumbLink>\n </BreadcrumbItem>\n <BreadcrumbSeparator />\n <BreadcrumbItem>\n <DropdownMenu>\n <DropdownMenuTrigger class=\"flex items-center gap-1\">\n <BreadcrumbEllipsis class=\"h-4 w-4\" />\n <span class=\"sr-only\">Toggle menu</span>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\">\n <DropdownMenuItem>Documentation</DropdownMenuItem>\n <DropdownMenuItem>Themes</DropdownMenuItem>\n <DropdownMenuItem>GitHub</DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </BreadcrumbItem>\n <BreadcrumbSeparator />\n <BreadcrumbItem>\n <BreadcrumbLink href=\"/docs/components/accordion.html\">\n Components\n </BreadcrumbLink>\n </BreadcrumbItem>\n <BreadcrumbSeparator />\n <BreadcrumbItem>\n <BreadcrumbPage>Breadcrumb</BreadcrumbPage>\n </BreadcrumbItem>\n </BreadcrumbList>\n </Breadcrumb>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "BreadcrumbDemo.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
"path": "example/BreadcrumbDropdown.vue",
|
||||
"content": "<script lang=\"ts\" setup>\nimport {\n Breadcrumb,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbList,\n BreadcrumbPage,\n BreadcrumbSeparator,\n} from '@/registry/default/ui/breadcrumb'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '@/registry/default/ui/dropdown-menu'\nimport { ChevronDown, Slash } from 'lucide-vue-next'\n</script>\n\n<template>\n <Breadcrumb>\n <BreadcrumbList>\n <BreadcrumbItem>\n <BreadcrumbLink href=\"/\">\n Home\n </BreadcrumbLink>\n </BreadcrumbItem>\n <BreadcrumbSeparator>\n <Slash />\n </BreadcrumbSeparator>\n <BreadcrumbItem>\n <DropdownMenu>\n <DropdownMenuTrigger class=\"flex items-center gap-1\">\n Components\n <ChevronDown class=\"h-4 w-4\" />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\">\n <DropdownMenuItem>Documentation</DropdownMenuItem>\n <DropdownMenuItem>Themes</DropdownMenuItem>\n <DropdownMenuItem>GitHub</DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </BreadcrumbItem>\n <BreadcrumbSeparator>\n <Slash />\n </BreadcrumbSeparator>\n <BreadcrumbItem>\n <BreadcrumbPage>Breadcrumb</BreadcrumbPage>\n </BreadcrumbItem>\n </BreadcrumbList>\n </Breadcrumb>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "BreadcrumbDropdown.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"path": "example/BreadcrumbEllipsisDemo.vue",
|
||||
"content": "<script lang=\"ts\" setup>\nimport {\n Breadcrumb,\n BreadcrumbEllipsis,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbList,\n BreadcrumbPage,\n BreadcrumbSeparator,\n} from '@/registry/default/ui/breadcrumb'\n</script>\n\n<template>\n <Breadcrumb>\n <BreadcrumbList>\n <BreadcrumbItem>\n <BreadcrumbLink as-child>\n <a href=\"/\">\n Home\n </a>\n </BreadcrumbLink>\n </BreadcrumbItem>\n <BreadcrumbSeparator />\n <BreadcrumbItem>\n <BreadcrumbEllipsis />\n </BreadcrumbItem>\n <BreadcrumbSeparator />\n <BreadcrumbItem>\n <BreadcrumbLink as-child>\n <a href=\"/docs/components/accordion.html\">\n Components\n </a>\n </BreadcrumbLink>\n </BreadcrumbItem>\n <BreadcrumbSeparator />\n <BreadcrumbItem>\n <BreadcrumbPage>Breadcrumb</BreadcrumbPage>\n </BreadcrumbItem>\n </BreadcrumbList>\n </Breadcrumb>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "BreadcrumbEllipsisDemo.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"path": "example/BreadcrumbLinkDemo.vue",
|
||||
"content": "<script lang=\"ts\" setup>\nimport {\n Breadcrumb,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbList,\n BreadcrumbPage,\n BreadcrumbSeparator,\n} from '@/registry/default/ui/breadcrumb'\n</script>\n\n<template>\n <Breadcrumb>\n <BreadcrumbList>\n <BreadcrumbItem>\n <BreadcrumbLink>\n <a href=\"/\">\n Home\n </a>\n </BreadcrumbLink>\n </BreadcrumbItem>\n <BreadcrumbSeparator />\n <BreadcrumbItem>\n <BreadcrumbLink>\n <a href=\"/docs/components/accordion.html\">\n Components\n </a>\n </BreadcrumbLink>\n </BreadcrumbItem>\n <BreadcrumbSeparator />\n <BreadcrumbItem>\n <BreadcrumbPage>Breadcrumb</BreadcrumbPage>\n </BreadcrumbItem>\n </BreadcrumbList>\n </Breadcrumb>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "BreadcrumbLinkDemo.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
"path": "example/BreadcrumbResponsive.vue",
|
||||
"content": "<script lang=\"ts\" setup>\nimport {\n Breadcrumb,\n BreadcrumbEllipsis,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbList,\n BreadcrumbPage,\n BreadcrumbSeparator,\n} from '@/registry/default/ui/breadcrumb'\nimport { Button } from '@/registry/default/ui/button'\nimport {\n Drawer,\n DrawerClose,\n DrawerContent,\n DrawerDescription,\n DrawerFooter,\n DrawerHeader,\n DrawerTitle,\n DrawerTrigger,\n} from '@/registry/default/ui/drawer'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '@/registry/default/ui/dropdown-menu'\nimport { useMediaQuery } from '@vueuse/core'\nimport { computed, ref } from 'vue'\n\nconst isDesktop = useMediaQuery('(min-width: 768px)')\nconst isOpen = ref(false)\nconst items = ref([\n { href: '#', label: 'Home' },\n { href: '#', label: 'Documentation' },\n { href: '#', label: 'Building Your Application' },\n { href: '#', label: 'Data Fetching' },\n { label: 'Caching and Revalidating' },\n])\n\nconst itemsToDisplay = 3\nconst firstLabel = computed(() => items.value[0]?.label)\n\nconst allButLastTwoItems = computed(() => items.value.slice(1, -2))\nconst remainingItems = computed(() => items.value.slice(-itemsToDisplay + 1))\n</script>\n\n<template>\n <Breadcrumb>\n <BreadcrumbList>\n <BreadcrumbItem>\n <BreadcrumbLink href=\"{items[0].href}\">\n {{ firstLabel }}\n </BreadcrumbLink>\n </BreadcrumbItem>\n <BreadcrumbSeparator />\n <template v-if=\"items.length > itemsToDisplay\">\n <BreadcrumbItem>\n <DropdownMenu v-if=\"isDesktop\" v-model:open=\"isOpen\">\n <DropdownMenuTrigger\n class=\"flex items-center gap-1\"\n aria-label=\"Toggle menu\"\n >\n <BreadcrumbEllipsis class=\"h-4 w-4\" />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\">\n <DropdownMenuItem v-for=\"item of allButLastTwoItems\" :key=\"item.label\">\n <a :href=\"item.href || '#'\">\n {{ item.label }}\n </a>\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n <Drawer v-else v-model:open=\"isOpen\">\n <DrawerTrigger aria-label=\"Toggle Menu\">\n <BreadcrumbEllipsis class=\"h-4 w-4\" />\n </DrawerTrigger>\n <DrawerContent>\n <DrawerHeader class=\"text-left\">\n <DrawerTitle>Navigate to</DrawerTitle>\n <DrawerDescription>\n Select a page to navigate to.\n </DrawerDescription>\n </DrawerHeader>\n <div class=\"grid gap-1 px-4\">\n <a\n v-for=\"item of allButLastTwoItems\"\n :key=\"item.label\"\n :href=\"item.href\"\n class=\"py-1 text-sm\"\n >\n {{ item.label }}\n </a>\n </div>\n <DrawerFooter class=\"pt-4\">\n <DrawerClose as-child>\n <Button variant=\"outline\">\n Close\n </Button>\n </DrawerClose>\n </DrawerFooter>\n </DrawerContent>\n </Drawer>\n </BreadcrumbItem>\n <BreadcrumbSeparator />\n </template>\n <BreadcrumbItem v-for=\" item of remainingItems\" :key=\"item.label\">\n <template v-if=\"item.href\">\n <BreadcrumbLink\n as-child\n class=\"max-w-20 truncate md:max-w-none\"\n >\n <a :href=\"item.href\">\n {{ item.label }}\n </a>\n </BreadcrumbLink>\n <BreadcrumbSeparator />\n </template>\n <BreadcrumbPage v-else class=\"max-w-20 truncate md:max-w-none\">\n {{ item.label }}\n </BreadcrumbPage>\n </BreadcrumbItem>\n </BreadcrumbList>\n </Breadcrumb>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "BreadcrumbResponsive.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"path": "example/BreadcrumbSeparatorDemo.vue",
|
||||
"content": "<script lang=\"ts\" setup>\nimport {\n Breadcrumb,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbList,\n BreadcrumbPage,\n BreadcrumbSeparator,\n} from '@/registry/default/ui/breadcrumb'\nimport { Slash } from 'lucide-vue-next'\n</script>\n\n<template>\n <Breadcrumb>\n <BreadcrumbList>\n <BreadcrumbItem>\n <BreadcrumbLink href=\"/\">\n Home\n </BreadcrumbLink>\n </BreadcrumbItem>\n <BreadcrumbSeparator>\n <Slash />\n </BreadcrumbSeparator>\n <BreadcrumbItem>\n <BreadcrumbLink href=\"/docs/components/accordion.html\">\n Components\n </BreadcrumbLink>\n </BreadcrumbItem>\n <BreadcrumbSeparator>\n <Slash />\n </BreadcrumbSeparator>\n <BreadcrumbItem>\n <BreadcrumbPage>Breadcrumb</BreadcrumbPage>\n </BreadcrumbItem>\n </BreadcrumbList>\n </Breadcrumb>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "BreadcrumbSeparatorDemo.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"path": "example/ButtonAsChildDemo.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { Button } from '@/registry/default/ui/button'\n</script>\n\n<template>\n <Button as-child>\n <a href=\"/login\">\n Login\n </a>\n </Button>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "ButtonAsChildDemo.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"path": "example/ButtonDemo.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { Button } from '@/registry/default/ui/button'\n</script>\n\n<template>\n <Button>Button</Button>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "ButtonDemo.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"path": "example/ButtonDestructiveDemo.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { Button } from '@/registry/default/ui/button'\n</script>\n\n<template>\n <Button variant=\"destructive\">\n Destructive\n </Button>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "ButtonDestructiveDemo.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"path": "example/ButtonGhostDemo.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { Button } from '@/registry/default/ui/button'\n</script>\n\n<template>\n <Button variant=\"ghost\">\n Ghost\n </Button>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "ButtonGhostDemo.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"path": "example/ButtonIconDemo.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { Button } from '@/registry/default/ui/button'\nimport { ChevronRight } from 'lucide-vue-next'\n</script>\n\n<template>\n <Button variant=\"outline\" size=\"icon\">\n <ChevronRight class=\"w-4 h-4\" />\n </Button>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "ButtonIconDemo.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"path": "example/ButtonLinkDemo.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { Button } from '@/registry/default/ui/button'\n</script>\n\n<template>\n <Button variant=\"link\">\n Link\n </Button>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "ButtonLinkDemo.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"path": "example/ButtonLoadingDemo.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { Button } from '@/registry/default/ui/button'\nimport { Loader2 } from 'lucide-vue-next'\n</script>\n\n<template>\n <Button disabled>\n <Loader2 class=\"w-4 h-4 mr-2 animate-spin\" />\n Please wait\n </Button>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "ButtonLoadingDemo.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"path": "example/ButtonOutlineDemo.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { Button } from '@/registry/default/ui/button'\n</script>\n\n<template>\n <Button variant=\"outline\">\n Outline\n </Button>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "ButtonOutlineDemo.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"path": "example/ButtonSecondaryDemo.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { Button } from '@/registry/default/ui/button'\n</script>\n\n<template>\n <Button variant=\"secondary\">\n Secondary\n </Button>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "ButtonSecondaryDemo.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"path": "example/ButtonWithIconDemo.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { Button } from '@/registry/default/ui/button'\nimport { Mail } from 'lucide-vue-next'\n</script>\n\n<template>\n <Button>\n <Mail class=\"w-4 h-4 mr-2\" /> Login with Email\n </Button>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "ButtonWithIconDemo.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"path": "example/CalendarDemo.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { Calendar } from '@/registry/default/ui/calendar'\nimport { type DateValue, getLocalTimeZone, today } from '@internationalized/date'\nimport { type Ref, ref } from 'vue'\n\nconst value = ref(today(getLocalTimeZone())) as Ref<DateValue>\n</script>\n\n<template>\n <Calendar v-model=\"value\" :weekday-format=\"'short'\" class=\"rounded-md border\" />\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "CalendarDemo.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
"path": "example/CalendarForm.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { cn } from '@/lib/utils'\nimport { Button } from '@/registry/default/ui/button'\nimport { Calendar } from '@/registry/default/ui/calendar'\nimport {\n FormControl,\n FormDescription,\n FormField,\n FormItem,\n FormLabel,\n FormMessage,\n} from '@/registry/default/ui/form'\nimport { Popover, PopoverContent, PopoverTrigger } from '@/registry/default/ui/popover'\nimport { toast } from '@/registry/default/ui/toast'\nimport { CalendarDate, DateFormatter, getLocalTimeZone, parseDate, today } from '@internationalized/date'\nimport { toTypedSchema } from '@vee-validate/zod'\nimport { Calendar as CalendarIcon } from 'lucide-vue-next'\nimport { toDate } from 'reka-ui/date'\nimport { useForm } from 'vee-validate'\nimport { computed, h, ref } from 'vue'\nimport { z } from 'zod'\n\nconst df = new DateFormatter('en-US', {\n dateStyle: 'long',\n})\n\nconst formSchema = toTypedSchema(z.object({\n dob: z\n .string()\n .refine(v => v, { message: 'A date of birth is required.' }),\n}))\n\nconst placeholder = ref()\n\nconst { handleSubmit, setFieldValue, values } = useForm({\n validationSchema: formSchema,\n})\n\nconst value = computed({\n get: () => values.dob ? parseDate(values.dob) : undefined,\n set: val => val,\n})\n\nconst onSubmit = handleSubmit((values) => {\n toast({\n title: 'You submitted the following values:',\n 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))),\n })\n})\n</script>\n\n<template>\n <form class=\"space-y-8\" @submit=\"onSubmit\">\n <FormField name=\"dob\">\n <FormItem class=\"flex flex-col\">\n <FormLabel>Date of birth</FormLabel>\n <Popover>\n <PopoverTrigger as-child>\n <FormControl>\n <Button\n variant=\"outline\" :class=\"cn(\n 'w-[240px] ps-3 text-start font-normal',\n !value && 'text-muted-foreground',\n )\"\n >\n <span>{{ value ? df.format(toDate(value)) : \"Pick a date\" }}</span>\n <CalendarIcon class=\"ms-auto h-4 w-4 opacity-50\" />\n </Button>\n <input hidden>\n </FormControl>\n </PopoverTrigger>\n <PopoverContent class=\"w-auto p-0\">\n <Calendar\n v-model:placeholder=\"placeholder\"\n v-model=\"value\"\n calendar-label=\"Date of birth\"\n initial-focus\n :min-value=\"new CalendarDate(1900, 1, 1)\"\n :max-value=\"today(getLocalTimeZone())\"\n @update:model-value=\"(v) => {\n if (v) {\n setFieldValue('dob', v.toString())\n }\n else {\n setFieldValue('dob', undefined)\n }\n }\"\n />\n </PopoverContent>\n </Popover>\n <FormDescription>\n Your date of birth is used to calculate your age.\n </FormDescription>\n <FormMessage />\n </FormItem>\n </FormField>\n <Button type=\"submit\">\n Submit\n </Button>\n </Form>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "CalendarForm.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
"path": "example/CalendarWithSelect.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { cn } from '@/lib/utils'\nimport { CalendarCell, CalendarCellTrigger, CalendarGrid, CalendarGridBody, CalendarGridHead, CalendarGridRow, CalendarHeadCell, CalendarHeader, CalendarHeading } from '@/registry/default/ui/calendar'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@/registry/default/ui/select'\nimport { type DateValue, getLocalTimeZone, today } from '@internationalized/date'\nimport { useVModel } from '@vueuse/core'\nimport { CalendarRoot, type CalendarRootEmits, type CalendarRootProps, useDateFormatter, useForwardPropsEmits } from 'reka-ui'\nimport { createDecade, createYear, toDate } from 'reka-ui/date'\nimport { computed, type HTMLAttributes, type Ref } from 'vue'\n\nconst props = withDefaults(defineProps<CalendarRootProps & { class?: HTMLAttributes['class'] }>(), {\n modelValue: undefined,\n placeholder() {\n return today(getLocalTimeZone())\n },\n weekdayFormat: 'short',\n})\nconst emits = defineEmits<CalendarRootEmits>()\n\nconst delegatedProps = computed(() => {\n const { class: _, placeholder: __, ...delegated } = props\n\n return delegated\n})\n\nconst placeholder = useVModel(props, 'modelValue', emits, {\n passive: true,\n defaultValue: today(getLocalTimeZone()),\n}) as Ref<DateValue>\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits)\n\nconst formatter = useDateFormatter('en')\n</script>\n\n<template>\n <CalendarRoot\n v-slot=\"{ date, grid, weekDays }\"\n v-model:placeholder=\"placeholder\"\n v-bind=\"forwarded\"\n :class=\"cn('rounded-md border p-3', props.class)\"\n >\n <CalendarHeader>\n <CalendarHeading class=\"flex w-full items-center justify-between gap-2\">\n <Select\n :default-value=\"placeholder.month.toString()\"\n @update:model-value=\"(v) => {\n if (!v || !placeholder) return;\n if (Number(v) === placeholder?.month) return;\n placeholder = placeholder.set({\n month: Number(v),\n })\n }\"\n >\n <SelectTrigger aria-label=\"Select month\" class=\"w-[60%]\">\n <SelectValue placeholder=\"Select month\" />\n </SelectTrigger>\n <SelectContent class=\"max-h-[200px]\">\n <SelectItem\n v-for=\"month in createYear({ dateObj: date })\"\n :key=\"month.toString()\" :value=\"month.month.toString()\"\n >\n {{ formatter.custom(toDate(month), { month: 'long' }) }}\n </SelectItem>\n </SelectContent>\n </Select>\n\n <Select\n :default-value=\"placeholder.year.toString()\"\n @update:model-value=\"(v) => {\n if (!v || !placeholder) return;\n if (Number(v) === placeholder?.year) return;\n placeholder = placeholder.set({\n year: Number(v),\n })\n }\"\n >\n <SelectTrigger aria-label=\"Select year\" class=\"w-[40%]\">\n <SelectValue placeholder=\"Select year\" />\n </SelectTrigger>\n <SelectContent class=\"max-h-[200px]\">\n <SelectItem\n v-for=\"yearValue in createDecade({ dateObj: date, startIndex: -10, endIndex: 10 })\"\n :key=\"yearValue.toString()\" :value=\"yearValue.year.toString()\"\n >\n {{ yearValue.year }}\n </SelectItem>\n </SelectContent>\n </Select>\n </CalendarHeading>\n </CalendarHeader>\n\n <div class=\"flex flex-col space-y-4 pt-4 sm:flex-row sm:gap-x-4 sm:gap-y-0\">\n <CalendarGrid v-for=\"month in grid\" :key=\"month.value.toString()\">\n <CalendarGridHead>\n <CalendarGridRow>\n <CalendarHeadCell\n v-for=\"day in weekDays\" :key=\"day\"\n >\n {{ day }}\n </CalendarHeadCell>\n </CalendarGridRow>\n </CalendarGridHead>\n <CalendarGridBody class=\"grid\">\n <CalendarGridRow v-for=\"(weekDates, index) in month.rows\" :key=\"`weekDate-${index}`\" class=\"mt-2 w-full\">\n <CalendarCell\n v-for=\"weekDate in weekDates\"\n :key=\"weekDate.toString()\"\n :date=\"weekDate\"\n >\n <CalendarCellTrigger\n :day=\"weekDate\"\n :month=\"month.value\"\n />\n </CalendarCell>\n </CalendarGridRow>\n </CalendarGridBody>\n </CalendarGrid>\n </div>\n </CalendarRoot>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "CalendarWithSelect.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
"path": "example/CardDemo.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { cn } from '@/lib/utils'\n\nimport { Button } from '@/registry/default/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardFooter,\n CardHeader,\n CardTitle,\n} from '@/registry/default/ui/card'\nimport { Switch } from '@/registry/default/ui/switch'\nimport { BellRing, Check } from 'lucide-vue-next'\n\nconst notifications = [\n {\n title: 'Your call has been confirmed.',\n description: '1 hour ago',\n },\n {\n title: 'You have a new message!',\n description: '1 hour ago',\n },\n {\n title: 'Your subscription is expiring soon!',\n description: '2 hours ago',\n },\n]\n</script>\n\n<template>\n <Card :class=\"cn('w-[380px]', $attrs.class ?? '')\">\n <CardHeader>\n <CardTitle>Notifications</CardTitle>\n <CardDescription>You have 3 unread messages.</CardDescription>\n </CardHeader>\n <CardContent class=\"grid gap-4\">\n <div class=\" flex items-center space-x-4 rounded-md border p-4\">\n <BellRing />\n <div class=\"flex-1 space-y-1\">\n <p class=\"text-sm font-medium leading-none\">\n Push Notifications\n </p>\n <p class=\"text-sm text-muted-foreground\">\n Send notifications to device.\n </p>\n </div>\n <Switch />\n </div>\n <div>\n <div\n v-for=\"(notification, index) in notifications\" :key=\"index\"\n class=\"mb-4 grid grid-cols-[25px_minmax(0,1fr)] items-start pb-4 last:mb-0 last:pb-0\"\n >\n <span class=\"flex h-2 w-2 translate-y-1 rounded-full bg-sky-500\" />\n <div class=\"space-y-1\">\n <p class=\"text-sm font-medium leading-none\">\n {{ notification.title }}\n </p>\n <p class=\"text-sm text-muted-foreground\">\n {{ notification.description }}\n </p>\n </div>\n </div>\n </div>\n </CardContent>\n <CardFooter>\n <Button class=\"w-full\">\n <Check class=\"mr-2 h-4 w-4\" /> Mark all as read\n </Button>\n </CardFooter>\n </Card>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "CardDemo.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
"path": "example/CardFormDemo.vue",
|
||||
"content": "<script setup lang='ts'>\nimport { Button } from '@/registry/default/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardFooter,\n CardHeader,\n CardTitle,\n} from '@/registry/default/ui/card'\nimport { Input } from '@/registry/default/ui/input'\nimport { Label } from '@/registry/default/ui/label'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@/registry/default/ui/select'\n</script>\n\n<template>\n <Card class=\"w-[350px]\">\n <CardHeader>\n <CardTitle>Create project</CardTitle>\n <CardDescription>Deploy your new project in one-click.</CardDescription>\n </CardHeader>\n <CardContent>\n <form>\n <div class=\"grid items-center w-full gap-4\">\n <div class=\"flex flex-col space-y-1.5\">\n <Label for=\"name\">Name</Label>\n <Input id=\"name\" placeholder=\"Name of your project\" />\n </div>\n <div class=\"flex flex-col space-y-1.5\">\n <Label for=\"framework\">Framework</Label>\n <Select>\n <SelectTrigger id=\"framework\">\n <SelectValue placeholder=\"Select\" />\n </SelectTrigger>\n <SelectContent position=\"popper\">\n <SelectItem value=\"nuxt\">\n Nuxt\n </SelectItem>\n <SelectItem value=\"next\">\n Next.js\n </SelectItem>\n <SelectItem value=\"sveltekit\">\n SvelteKit\n </SelectItem>\n <SelectItem value=\"astro\">\n Astro\n </SelectItem>\n </SelectContent>\n </Select>\n </div>\n </div>\n </form>\n </CardContent>\n <CardFooter class=\"flex justify-between px-6 pb-6\">\n <Button variant=\"outline\">\n Cancel\n </Button>\n <Button>Deploy</Button>\n </CardFooter>\n </Card>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "CardFormDemo.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
"path": "example/CardStats.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { Card, CardContent, CardHeader, CardTitle } from '@/registry/default/ui/card'\nimport { themes } from '@/registry/registry-themes'\nimport { useConfigStore } from '@/stores/config'\nimport { VisLine, VisScatter, VisStackedBar, VisXYContainer } from '@unovis/vue'\nimport { useData } from 'vitepress'\nimport { computed } from 'vue'\n\ntype Data = typeof data[number]\nconst data = [\n { revenue: 10400, subscription: 240 },\n { revenue: 14405, subscription: 300 },\n { revenue: 9400, subscription: 200 },\n { revenue: 8200, subscription: 278 },\n { revenue: 7000, subscription: 189 },\n { revenue: 9600, subscription: 239 },\n { revenue: 11244, subscription: 278 },\n { revenue: 26475, subscription: 189 },\n]\n\nconst cfg = useConfigStore()\n\nconst { isDark } = useData()\nconst theme = computed(() => themes.find(theme => theme.name === cfg.config.value.theme))\n\nconst lineX = (d: Data, i: number) => i\nconst lineY = (d: Data) => d.revenue\n</script>\n\n<template>\n <div class=\"grid gap-4 sm:grid-cols-2 xl:grid-cols-2\">\n <Card>\n <CardHeader class=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle class=\"text-sm font-normal\">\n Total Revenue\n </CardTitle>\n </CardHeader>\n <CardContent>\n <div class=\"text-2xl font-bold\">\n $15,231.89\n </div>\n <p class=\"text-xs text-muted-foreground\">\n +20.1% from last month\n </p>\n\n <div class=\"h-20\">\n <VisXYContainer\n height=\"80px\"\n :data=\"data\" :margin=\"{\n top: 5,\n right: 10,\n left: 10,\n bottom: 0,\n }\"\n >\n <VisLine :x=\"lineX\" :y=\"lineY\" color=\"hsl(var(--primary))\" />\n <VisScatter :x=\"lineX\" :y=\"lineY\" :size=\"6\" stroke-color=\"hsl(var(--primary))\" :stroke-width=\"2\" color=\"white\" />\n </VisXYContainer>\n </div>\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader class=\"pb-2\">\n <CardTitle class=\"text-lg\">\n Subscriptions\n </CardTitle>\n </CardHeader>\n <CardContent>\n <div class=\"text-2xl font-bold\">\n +2,350\n </div>\n <p class=\"text-xs text-muted-foreground\">\n +54.8% from last month\n </p>\n\n <div class=\"mt-4 h-20\">\n <VisXYContainer\n height=\"80px\" :data=\"data\" :style=\"{\n '--theme-primary': `hsl(${\n theme?.cssVars?.[isDark ? 'dark' : 'light']?.primary\n })`,\n }\"\n >\n <VisStackedBar\n :x=\"lineX\"\n :y=\"(d: Data) => d.subscription\"\n :bar-padding=\"0.1\"\n :rounded-corners=\"0\" color=\"hsl(var(--primary))\"\n />\n </VisXYContainer>\n </div>\n </CardContent>\n </Card>\n </div>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "CardStats.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
"path": "example/CardWithForm.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { Button } from '@/registry/default/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardFooter,\n CardHeader,\n CardTitle,\n} from '@/registry/default/ui/card'\nimport { Input } from '@/registry/default/ui/input'\nimport { Label } from '@/registry/default/ui/label'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@/registry/default/ui/select'\n</script>\n\n<template>\n <Card class=\"w-[350px]\">\n <CardHeader>\n <CardTitle>Create project</CardTitle>\n <CardDescription>Deploy your new project in one-click.</CardDescription>\n </CardHeader>\n <CardContent>\n <form>\n <div class=\"grid w-full items-center gap-4\">\n <div class=\"flex flex-col space-y-1.5\">\n <Label for=\"name\">Name</Label>\n <Input id=\"name\" placeholder=\"Name of your project\" />\n </div>\n <div class=\"flex flex-col space-y-1.5\">\n <Label for=\"framework\">Framework</Label>\n <Select>\n <SelectTrigger id=\"framework\">\n <SelectValue placeholder=\"Select\" />\n </SelectTrigger>\n <SelectContent position=\"popper\">\n <SelectItem value=\"next\">\n Next.js\n </SelectItem>\n <SelectItem value=\"sveltekit\">\n SvelteKit\n </SelectItem>\n <SelectItem value=\"astro\">\n Astro\n </SelectItem>\n <SelectItem value=\"nuxt\">\n Nuxt\n </SelectItem>\n </SelectContent>\n </Select>\n </div>\n </div>\n </form>\n </CardContent>\n <CardFooter class=\"flex justify-between\">\n <Button variant=\"outline\">\n Cancel\n </Button>\n <Button>Deploy</Button>\n </CardFooter>\n </Card>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "CardWithForm.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
"path": "example/CarouselApi.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport type { CarouselApi } from '@/registry/default/ui/carousel'\nimport { Card, CardContent } from '@/registry/default/ui/card'\nimport { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from '@/registry/default/ui/carousel'\nimport { watchOnce } from '@vueuse/core'\nimport { ref } from 'vue'\n\nconst api = ref<CarouselApi>()\nconst totalCount = ref(0)\nconst current = ref(0)\n\nfunction setApi(val: CarouselApi) {\n api.value = val\n}\n\nwatchOnce(api, (api) => {\n if (!api)\n return\n\n totalCount.value = api.scrollSnapList().length\n current.value = api.selectedScrollSnap() + 1\n\n api.on('select', () => {\n current.value = api.selectedScrollSnap() + 1\n })\n})\n</script>\n\n<template>\n <div class=\"w-full sm:w-auto\">\n <Carousel class=\"relative w-full max-w-xs\" @init-api=\"setApi\">\n <CarouselContent>\n <CarouselItem v-for=\"(_, index) in 5\" :key=\"index\">\n <div class=\"p-1\">\n <Card>\n <CardContent class=\"flex aspect-square items-center justify-center p-6\">\n <span class=\"text-4xl font-semibold\">{{ index + 1 }}</span>\n </CardContent>\n </Card>\n </div>\n </CarouselItem>\n </CarouselContent>\n <CarouselPrevious />\n <CarouselNext />\n </Carousel>\n\n <div class=\"py-2 text-center text-sm text-muted-foreground\">\n Slide {{ current }} of {{ totalCount }}\n </div>\n </div>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "CarouselApi.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
"path": "example/CarouselDemo.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { Card, CardContent } from '@/registry/default/ui/card'\nimport { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from '@/registry/default/ui/carousel'\n</script>\n\n<template>\n <Carousel v-slot=\"{ canScrollNext }\" class=\"relative w-full max-w-xs\">\n <CarouselContent>\n <CarouselItem v-for=\"(_, index) in 5\" :key=\"index\">\n <div class=\"p-1\">\n <Card>\n <CardContent class=\"flex aspect-square items-center justify-center p-6\">\n <span class=\"text-4xl font-semibold\">{{ index + 1 }}</span>\n </CardContent>\n </Card>\n </div>\n </CarouselItem>\n </CarouselContent>\n <CarouselPrevious />\n <CarouselNext v-if=\"canScrollNext\" />\n </Carousel>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "CarouselDemo.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
"path": "example/CarouselOrientation.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { Card, CardContent } from '@/registry/default/ui/card'\nimport { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from '@/registry/default/ui/carousel'\n</script>\n\n<template>\n <Carousel\n orientation=\"vertical\"\n class=\"relative w-full max-w-xsw-full max-w-xs\"\n :opts=\"{\n align: 'start',\n }\"\n >\n <CarouselContent class=\"-mt-1 h-[200px]\">\n <CarouselItem v-for=\"(_, index) in 5\" :key=\"index\" class=\"p-1 md:basis-1/2\">\n <div class=\"p-1\">\n <Card>\n <CardContent class=\"flex items-center justify-center p-6\">\n <span class=\"text-3xl font-semibold\">{{ index + 1 }}</span>\n </CardContent>\n </Card>\n </div>\n </CarouselItem>\n </CarouselContent>\n <CarouselPrevious />\n <CarouselNext />\n </Carousel>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "CarouselOrientation.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
"path": "example/CarouselPlugin.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { Card, CardContent } from '@/registry/default/ui/card'\nimport { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from '@/registry/default/ui/carousel'\nimport Autoplay from 'embla-carousel-autoplay'\n\nconst plugin = Autoplay({\n delay: 2000,\n stopOnMouseEnter: true,\n stopOnInteraction: false,\n})\n</script>\n\n<template>\n <Carousel\n class=\"relative w-full max-w-xs\"\n :plugins=\"[plugin]\"\n @mouseenter=\"plugin.stop\"\n @mouseleave=\"[plugin.reset(), plugin.play(), console.log('Running')];\"\n >\n <CarouselContent>\n <CarouselItem v-for=\"(_, index) in 5\" :key=\"index\">\n <div class=\"p-1\">\n <Card>\n <CardContent class=\"flex aspect-square items-center justify-center p-6\">\n <span class=\"text-4xl font-semibold\">{{ index + 1 }}</span>\n </CardContent>\n </Card>\n </div>\n </CarouselItem>\n </CarouselContent>\n <CarouselPrevious />\n <CarouselNext />\n </Carousel>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "CarouselPlugin.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
"path": "example/CarouselSize.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { Card, CardContent } from '@/registry/default/ui/card'\nimport { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from '@/registry/default/ui/carousel'\n</script>\n\n<template>\n <Carousel\n class=\"relative w-full max-w-sm\"\n :opts=\"{\n align: 'start',\n }\"\n >\n <CarouselContent>\n <CarouselItem v-for=\"(_, index) in 5\" :key=\"index\" class=\"md:basis-1/2 lg:basis-1/3\">\n <div class=\"p-1\">\n <Card>\n <CardContent class=\"flex aspect-square items-center justify-center p-6\">\n <span class=\"text-3xl font-semibold\">{{ index + 1 }}</span>\n </CardContent>\n </Card>\n </div>\n </CarouselItem>\n </CarouselContent>\n <CarouselPrevious />\n <CarouselNext />\n </Carousel>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "CarouselSize.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
"path": "example/CarouselSpacing.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { Card, CardContent } from '@/registry/default/ui/card'\nimport { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from '@/registry/default/ui/carousel'\n</script>\n\n<template>\n <Carousel\n class=\"relative w-full max-w-sm\"\n :opts=\"{\n align: 'start',\n }\"\n >\n <CarouselContent class=\"-ml-1\">\n <CarouselItem v-for=\"(_, index) in 5\" :key=\"index\" class=\"pl-1 md:basis-1/2 lg:basis-1/3\">\n <div class=\"p-1\">\n <Card>\n <CardContent class=\"flex aspect-square items-center justify-center p-6\">\n <span class=\"text-2xl font-semibold\">{{ index + 1 }}</span>\n </CardContent>\n </Card>\n </div>\n </CarouselItem>\n </CarouselContent>\n <CarouselPrevious />\n <CarouselNext />\n </Carousel>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "CarouselSpacing.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
"path": "example/CarouselThumbnails.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { Card, CardContent } from '@/registry/default/ui/card'\nimport { Carousel, type CarouselApi, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from '@/registry/default/ui/carousel'\nimport { watchOnce } from '@vueuse/core'\nimport { ref } from 'vue'\n\nconst emblaMainApi = ref<CarouselApi>()\nconst emblaThumbnailApi = ref<CarouselApi>()\nconst selectedIndex = ref(0)\n\nfunction onSelect() {\n if (!emblaMainApi.value || !emblaThumbnailApi.value)\n return\n selectedIndex.value = emblaMainApi.value.selectedScrollSnap()\n emblaThumbnailApi.value.scrollTo(emblaMainApi.value.selectedScrollSnap())\n}\n\nfunction onThumbClick(index: number) {\n if (!emblaMainApi.value || !emblaThumbnailApi.value)\n return\n emblaMainApi.value.scrollTo(index)\n}\n\nwatchOnce(emblaMainApi, (emblaMainApi) => {\n if (!emblaMainApi)\n return\n\n onSelect()\n emblaMainApi.on('select', onSelect)\n emblaMainApi.on('reInit', onSelect)\n})\n</script>\n\n<template>\n <div class=\"w-full sm:w-auto\">\n <Carousel\n class=\"relative w-full max-w-xs\"\n @init-api=\"(val) => emblaMainApi = val\"\n >\n <CarouselContent>\n <CarouselItem v-for=\"(_, index) in 10\" :key=\"index\">\n <div class=\"p-1\">\n <Card>\n <CardContent class=\"flex aspect-square items-center justify-center p-6\">\n <span class=\"text-4xl font-semibold\">{{ index + 1 }}</span>\n </CardContent>\n </Card>\n </div>\n </CarouselItem>\n </CarouselContent>\n <CarouselPrevious />\n <CarouselNext />\n </Carousel>\n\n <Carousel\n class=\"relative w-full max-w-xs\"\n @init-api=\"(val) => emblaThumbnailApi = val\"\n >\n <CarouselContent class=\"flex gap-1 ml-0\">\n <CarouselItem v-for=\"(_, index) in 10\" :key=\"index\" class=\"pl-0 basis-1/4 cursor-pointer\" @click=\"onThumbClick(index)\">\n <div class=\"p-1\" :class=\"index === selectedIndex ? '' : 'opacity-50'\">\n <Card>\n <CardContent class=\"flex aspect-square items-center justify-center p-6\">\n <span class=\"text-4xl font-semibold\">{{ index + 1 }}</span>\n </CardContent>\n </Card>\n </div>\n </CarouselItem>\n </CarouselContent>\n </Carousel>\n </div>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "CarouselThumbnails.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"path": "example/CheckboxDemo.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { Checkbox } from '@/registry/default/ui/checkbox'\n</script>\n\n<template>\n <div class=\"flex items-center space-x-2\">\n <Checkbox id=\"terms\" />\n <label\n for=\"terms\"\n class=\"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70\"\n >\n Accept terms and conditions\n </label>\n </div>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "CheckboxDemo.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"path": "example/CheckboxDisabled.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { Checkbox } from '@/registry/default/ui/checkbox'\n</script>\n\n<template>\n <div class=\"items-top flex space-x-2\">\n <Checkbox id=\"terms1\" disabled />\n <label\n for=\"terms2\"\n class=\"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70\"\n >\n Accept terms and conditions\n </label>\n </div>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "CheckboxDisabled.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
"path": "example/CheckboxFormMultiple.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { Button } from '@/registry/default/ui/button'\nimport { Checkbox } from '@/registry/default/ui/checkbox'\nimport {\n FormControl,\n FormDescription,\n FormField,\n FormItem,\n FormLabel,\n FormMessage,\n} from '@/registry/default/ui/form'\nimport { toast } from '@/registry/default/ui/toast'\n\nimport { toTypedSchema } from '@vee-validate/zod'\nimport { useForm } from 'vee-validate'\nimport { h } from 'vue'\nimport * as z from 'zod'\n\nconst items = [\n {\n id: 'recents',\n label: 'Recents',\n },\n {\n id: 'home',\n label: 'Home',\n },\n {\n id: 'applications',\n label: 'Applications',\n },\n {\n id: 'desktop',\n label: 'Desktop',\n },\n {\n id: 'downloads',\n label: 'Downloads',\n },\n {\n id: 'documents',\n label: 'Documents',\n },\n] as const\n\nconst formSchema = toTypedSchema(z.object({\n items: z.array(z.string()).refine(value => value.some(item => item), {\n message: 'You have to select at least one item.',\n }),\n}))\n\nconst { handleSubmit } = useForm({\n validationSchema: formSchema,\n initialValues: {\n items: ['recents', 'home'],\n },\n})\n\nconst onSubmit = handleSubmit((values) => {\n toast({\n title: 'You submitted the following values:',\n 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))),\n })\n})\n</script>\n\n<template>\n <form @submit=\"onSubmit\">\n <FormField name=\"items\">\n <FormItem>\n <div class=\"mb-4\">\n <FormLabel class=\"text-base\">\n Sidebar\n </FormLabel>\n <FormDescription>\n Select the items you want to display in the sidebar.\n </FormDescription>\n </div>\n\n <FormField v-for=\"item in items\" v-slot=\"{ value, handleChange }\" :key=\"item.id\" type=\"checkbox\" :value=\"item.id\" :unchecked-value=\"false\" name=\"items\">\n <FormItem class=\"flex flex-row items-start space-x-3 space-y-0\">\n <FormControl>\n <Checkbox\n :model-value=\"value.includes(item.id)\"\n @update:model-value=\"handleChange\"\n />\n </FormControl>\n <FormLabel class=\"font-normal\">\n {{ item.label }}\n </FormLabel>\n </FormItem>\n </FormField>\n <FormMessage />\n </FormItem>\n </FormField>\n\n <div class=\"flex justify-start mt-4\">\n <Button type=\"submit\">\n Submit\n </Button>\n </div>\n </form>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "CheckboxFormMultiple.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
"path": "example/CheckboxFormSingle.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { Button } from '@/registry/default/ui/button'\nimport { Checkbox } from '@/registry/default/ui/checkbox'\nimport {\n FormControl,\n FormDescription,\n FormField,\n FormItem,\n FormLabel,\n FormMessage,\n} from '@/registry/default/ui/form'\nimport { toast } from '@/registry/default/ui/toast'\n\nimport { toTypedSchema } from '@vee-validate/zod'\nimport { useForm } from 'vee-validate'\nimport { h } from 'vue'\nimport * as z from 'zod'\n\nconst formSchema = toTypedSchema(z.object({\n mobile: z.boolean().default(false).optional(),\n}))\n\nconst { handleSubmit } = useForm({\n validationSchema: formSchema,\n initialValues: {\n mobile: true,\n },\n})\n\nconst onSubmit = handleSubmit((values) => {\n toast({\n title: 'You submitted the following values:',\n 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))),\n })\n})\n</script>\n\n<template>\n <form class=\"space-y-6\" @submit=\"onSubmit\">\n <FormField v-slot=\"{ value, handleChange }\" type=\"checkbox\" name=\"mobile\">\n <FormItem class=\"flex flex-row items-start gap-x-3 space-y-0 rounded-md border p-4\">\n <FormControl>\n <Checkbox :model-value=\"value\" @update:model-value=\"handleChange\" />\n </FormControl>\n <div class=\"space-y-1 leading-none\">\n <FormLabel>Use different settings for my mobile devices</FormLabel>\n <FormDescription>\n You can manage your mobile notifications in the\n <a href=\"/examples/forms\">mobile settings</a> page.\n </FormDescription>\n <FormMessage />\n </div>\n </FormItem>\n </FormField>\n <Button type=\"submit\">\n Submit\n </Button>\n </Form>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "CheckboxFormSingle.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"path": "example/CheckboxWithText.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { Checkbox } from '@/registry/default/ui/checkbox'\n</script>\n\n<template>\n <div class=\"items-top flex gap-x-2\">\n <Checkbox id=\"terms1\" />\n <div class=\"grid gap-1.5 leading-none\">\n <label\n for=\"terms1\"\n class=\"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70\"\n >\n Accept terms and conditions\n </label>\n <p class=\"text-sm text-muted-foreground\">\n You agree to our Terms of Service and Privacy Policy.\n </p>\n </div>\n </div>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "CheckboxWithText.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
"path": "example/CollapsibleDemo.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { Button } from '@/registry/default/ui/button'\n\nimport {\n Collapsible,\n CollapsibleContent,\n CollapsibleTrigger,\n} from '@/registry/default/ui/collapsible'\nimport { ChevronsUpDown } from 'lucide-vue-next'\nimport { ref } from 'vue'\n\nconst isOpen = ref(false)\n</script>\n\n<template>\n <Collapsible\n v-model:open=\"isOpen\"\n class=\"w-[350px] space-y-2\"\n >\n <div class=\"flex items-center justify-between space-x-4 px-4\">\n <h4 class=\"text-sm font-semibold\">\n @peduarte starred 3 repositories\n </h4>\n <CollapsibleTrigger as-child>\n <Button variant=\"ghost\" size=\"sm\" class=\"w-9 p-0\">\n <ChevronsUpDown class=\"h-4 w-4\" />\n <span class=\"sr-only\">Toggle</span>\n </Button>\n </CollapsibleTrigger>\n </div>\n <div class=\"rounded-md border px-4 py-3 font-mono text-sm\">\n @radix-ui/primitives\n </div>\n <CollapsibleContent class=\"space-y-2\">\n <div class=\"rounded-md border px-4 py-3 font-mono text-sm\">\n @radix-ui/colors\n </div>\n <div class=\"rounded-md border px-4 py-3 font-mono text-sm\">\n @stitches/react\n </div>\n </CollapsibleContent>\n </Collapsible>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "CollapsibleDemo.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
"path": "example/ComboboxDemo.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { cn } from '@/lib/utils'\nimport { Button } from '@/registry/default/ui/button'\n\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n} from '@/registry/default/ui/command'\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from '@/registry/default/ui/popover'\nimport { Check, ChevronsUpDown } from 'lucide-vue-next'\nimport { ref } from 'vue'\n\nconst frameworks = [\n { value: 'next.js', label: 'Next.js' },\n { value: 'sveltekit', label: 'SvelteKit' },\n { value: 'nuxt', label: 'Nuxt' },\n { value: 'remix', label: 'Remix' },\n { value: 'astro', label: 'Astro' },\n]\n\nconst open = ref(false)\nconst value = ref('')\n</script>\n\n<template>\n <Popover v-model:open=\"open\">\n <PopoverTrigger as-child>\n <Button\n variant=\"outline\"\n role=\"combobox\"\n :aria-expanded=\"open\"\n class=\"w-[200px] justify-between\"\n >\n {{ value\n ? frameworks.find((framework) => framework.value === value)?.label\n : \"Select framework...\" }}\n <ChevronsUpDown class=\"ml-2 h-4 w-4 shrink-0 opacity-50\" />\n </Button>\n </PopoverTrigger>\n <PopoverContent class=\"w-[200px] p-0\">\n <Command>\n <CommandInput class=\"h-9\" placeholder=\"Search framework...\" />\n <CommandEmpty>No framework found.</CommandEmpty>\n <CommandList>\n <CommandGroup>\n <CommandItem\n v-for=\"framework in frameworks\"\n :key=\"framework.value\"\n :value=\"framework.value\"\n @select=\"(ev) => {\n if (typeof ev.detail.value === 'string') {\n value = ev.detail.value\n }\n open = false\n }\"\n >\n {{ framework.label }}\n <Check\n :class=\"cn(\n 'ml-auto h-4 w-4',\n value === framework.value ? 'opacity-100' : 'opacity-0',\n )\"\n />\n </CommandItem>\n </CommandGroup>\n </CommandList>\n </Command>\n </PopoverContent>\n </Popover>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "ComboboxDemo.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"path": "example/ComboboxDropdownMenu.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { Button } from '@/registry/default/ui/button'\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n} from '@/registry/default/ui/command'\n\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuShortcut,\n DropdownMenuSub,\n DropdownMenuSubContent,\n DropdownMenuSubTrigger,\n DropdownMenuTrigger,\n} from '@/registry/default/ui/dropdown-menu'\nimport { Calendar, MoreHorizontal, Tags, Trash, User } from 'lucide-vue-next'\nimport { ref } from 'vue'\n\nconst labels = [\n 'feature',\n 'bug',\n 'enhancement',\n 'documentation',\n 'design',\n 'question',\n 'maintenance',\n]\n\nconst labelRef = ref('feature')\nconst open = ref(false)\n</script>\n\n<template>\n <div class=\"flex w-full flex-col items-start justify-between rounded-md border px-4 py-3 sm:flex-row sm:items-center\">\n <p class=\"text-sm font-medium leading-none\">\n <span class=\"mr-2 rounded-lg bg-primary px-2 py-1 text-xs text-primary-foreground\">\n {{ labelRef }}\n </span>\n <span class=\"text-muted-foreground\">Create a new project</span>\n </p>\n <DropdownMenu v-model:open=\"open\">\n <DropdownMenuTrigger as-child>\n <Button variant=\"ghost\" size=\"sm\">\n <MoreHorizontal />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" class=\"w-[200px]\">\n <DropdownMenuLabel>Actions</DropdownMenuLabel>\n <DropdownMenuGroup>\n <DropdownMenuItem>\n <User class=\"mr-2 h-4 w-4\" />\n Assign to...\n </DropdownMenuItem>\n <DropdownMenuItem>\n <Calendar class=\"mr-2 h-4 w-4\" />\n Set due date...\n </DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuSub>\n <DropdownMenuSubTrigger>\n <Tags class=\"mr-2 h-4 w-4\" />\n Apply label\n </DropdownMenuSubTrigger>\n <DropdownMenuSubContent class=\"p-0\">\n <Command>\n <CommandInput\n placeholder=\"Filter label...\"\n auto-focus\n />\n <CommandList>\n <CommandEmpty>No label found.</CommandEmpty>\n <CommandGroup>\n <CommandItem\n v-for=\"label in labels\"\n :key=\"label\"\n :value=\"label\"\n @select=\"(ev) => {\n labelRef = ev.detail.value as string\n open = false\n }\"\n >\n {{ label }}\n </CommandItem>\n </CommandGroup>\n </CommandList>\n </Command>\n </DropdownMenuSubContent>\n </DropdownMenuSub>\n <DropdownMenuSeparator />\n <DropdownMenuItem class=\"text-red-600\">\n <Trash class=\"mr-2 h-4 w-4\" />\n Delete\n <DropdownMenuShortcut>⌘⌫</DropdownMenuShortcut>\n </DropdownMenuItem>\n </DropdownMenuGroup>\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "ComboboxDropdownMenu.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
"path": "example/ComboboxForm.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { cn } from '@/lib/utils'\nimport { Button } from '@/registry/default/ui/button'\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n} from '@/registry/default/ui/command'\nimport {\n FormControl,\n FormDescription,\n FormField,\n FormItem,\n FormLabel,\n FormMessage,\n} from '@/registry/default/ui/form'\n\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from '@/registry/default/ui/popover'\nimport { toast } from '@/registry/default/ui/toast'\nimport { toTypedSchema } from '@vee-validate/zod'\nimport { Check, ChevronsUpDown } from 'lucide-vue-next'\nimport { useForm } from 'vee-validate'\nimport { h } from 'vue'\nimport * as z from 'zod'\n\nconst languages = [\n { label: 'English', value: 'en' },\n { label: 'French', value: 'fr' },\n { label: 'German', value: 'de' },\n { label: 'Spanish', value: 'es' },\n { label: 'Portuguese', value: 'pt' },\n { label: 'Russian', value: 'ru' },\n { label: 'Japanese', value: 'ja' },\n { label: 'Korean', value: 'ko' },\n { label: 'Chinese', value: 'zh' },\n]\n\nconst formSchema = toTypedSchema(z.object({\n language: z.string({\n required_error: 'Please select a language.',\n }),\n}))\n\nconst { handleSubmit, setFieldValue, values } = useForm({\n validationSchema: formSchema,\n})\n\nconst onSubmit = handleSubmit((values) => {\n toast({\n title: 'You submitted the following values:',\n 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))),\n })\n})\n</script>\n\n<template>\n <form class=\"space-y-6\" @submit=\"onSubmit\">\n <FormField name=\"language\">\n <FormItem class=\"flex flex-col\">\n <FormLabel>Language</FormLabel>\n <Popover>\n <PopoverTrigger as-child>\n <FormControl>\n <Button\n variant=\"outline\"\n role=\"combobox\"\n :class=\"cn('w-[200px] justify-between', !values.language && 'text-muted-foreground')\"\n >\n {{ values.language ? languages.find(\n (language) => language.value === values.language,\n )?.label : 'Select language...' }}\n <ChevronsUpDown class=\"ml-2 h-4 w-4 shrink-0 opacity-50\" />\n </Button>\n </FormControl>\n </PopoverTrigger>\n <PopoverContent class=\"w-[200px] p-0\">\n <Command>\n <CommandInput placeholder=\"Search language...\" />\n <CommandEmpty>Nothing found.</CommandEmpty>\n <CommandList>\n <CommandGroup>\n <CommandItem\n v-for=\"language in languages\"\n :key=\"language.value\"\n :value=\"language.label\"\n @select=\"() => {\n setFieldValue('language', language.value)\n }\"\n >\n <Check\n :class=\"cn('mr-2 h-4 w-4', language.value === values.language ? 'opacity-100' : 'opacity-0')\"\n />\n {{ language.label }}\n </CommandItem>\n </CommandGroup>\n </CommandList>\n </Command>\n </PopoverContent>\n </Popover>\n <FormDescription>\n This is the language that will be used in the dashboard.\n </FormDescription>\n <FormMessage />\n </FormItem>\n </FormField>\n\n <Button type=\"submit\">\n Submit\n </Button>\n </form>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "ComboboxForm.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
"path": "example/ComboboxPopover.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport type { Icon } from 'lucide-vue-next'\nimport { cn } from '@/lib/utils'\nimport { Button } from '@/registry/default/ui/button'\n\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n} from '@/registry/default/ui/command'\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from '@/registry/default/ui/popover'\nimport {\n ArrowUpCircle,\n CheckCircle2,\n Circle,\n HelpCircle,\n XCircle,\n} from 'lucide-vue-next'\nimport { ref } from 'vue'\n\ninterface Status {\n value: string\n label: string\n icon: Icon\n}\n\nconst statuses: Status[] = [\n {\n value: 'backlog',\n label: 'Backlog',\n icon: HelpCircle,\n },\n {\n value: 'todo',\n label: 'Todo',\n icon: Circle,\n },\n {\n value: 'in progress',\n label: 'In Progress',\n icon: ArrowUpCircle,\n },\n {\n value: 'done',\n label: 'Done',\n icon: CheckCircle2,\n },\n {\n value: 'canceled',\n label: 'Canceled',\n icon: XCircle,\n },\n]\n\nconst open = ref(false)\n// const value = ref<typeof statuses[number]>()\n\nconst selectedStatus = ref<Status>()\n</script>\n\n<template>\n <div class=\"flex items-center space-x-4\">\n <p class=\"text-sm text-muted-foreground\">\n Status\n </p>\n <Popover v-model:open=\"open\">\n <PopoverTrigger as-child>\n <Button\n variant=\"outline\"\n size=\"sm\"\n class=\"w-[150px] justify-start\"\n >\n <template v-if=\"selectedStatus\">\n <component :is=\"selectedStatus?.icon\" class=\"mr-2 h-4 w-4 shrink-0\" />\n {{ selectedStatus?.label }}\n </template>\n <template v-else>\n + Set status\n </template>\n </Button>\n </PopoverTrigger>\n <PopoverContent class=\"p-0\" side=\"right\" align=\"start\">\n <Command>\n <CommandInput placeholder=\"Change status...\" />\n <CommandList>\n <CommandEmpty>No results found.</CommandEmpty>\n <CommandGroup>\n <CommandItem\n v-for=\"status in statuses\"\n :key=\"status.value\"\n :value=\"status.value\"\n @select=\"(value) => {\n selectedStatus = status\n open = false\n }\"\n >\n <component\n :is=\"status.icon\"\n :key=\"status.value\"\n :class=\"cn('mr-2 h-4 w-4', status.value === selectedStatus?.value ? 'opacity-100' : 'opacity-40',\n )\"\n />\n <span>{{ status.label }}</span>\n </CommandItem>\n </CommandGroup>\n </CommandList>\n </Command>\n </PopoverContent>\n </Popover>\n </div>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "ComboboxPopover.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
"path": "example/ComboboxResponsive.vue",
|
||||
"content": "<script lang=\"ts\" setup>\nimport { Button } from '@/registry/default/ui/button'\nimport { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '@/registry/default/ui/command'\nimport { Drawer, DrawerContent, DrawerTrigger } from '@/registry/default/ui/drawer'\nimport { Popover, PopoverContent, PopoverTrigger } from '@/registry/default/ui/popover'\nimport { createReusableTemplate, useMediaQuery } from '@vueuse/core'\nimport { ref } from 'vue'\n\ninterface Status {\n value: string\n label: string\n}\n\nconst statuses: Status[] = [\n {\n value: 'backlog',\n label: 'Backlog',\n },\n {\n value: 'todo',\n label: 'Todo',\n },\n {\n value: 'in progress',\n label: 'In Progress',\n },\n {\n value: 'done',\n label: 'Done',\n },\n {\n value: 'canceled',\n label: 'Canceled',\n },\n]\n\nconst [UseTemplate, StatusList] = createReusableTemplate()\nconst isDesktop = useMediaQuery('(min-width: 768px)')\n\nconst isOpen = ref(false)\nconst selectedStatus = ref<Status | null>(null)\n\nfunction onStatusSelect(status: Status) {\n selectedStatus.value = status\n isOpen.value = false\n}\n</script>\n\n<template>\n <div>\n <UseTemplate>\n <Command>\n <CommandInput placeholder=\"Filter status...\" />\n <CommandList>\n <CommandEmpty>No results found.</CommandEmpty>\n <CommandGroup>\n <CommandItem\n v-for=\"status of statuses\"\n :key=\"status.value\"\n :value=\"status.value\"\n @select=\"onStatusSelect(status)\"\n >\n {{ status.label }}\n </CommandItem>\n </CommandGroup>\n </CommandList>\n </Command>\n </UseTemplate>\n\n <Popover v-if=\"isDesktop\" v-model:open=\"isOpen\">\n <PopoverTrigger as-child>\n <Button variant=\"outline\" class=\"w-[150px] justify-start\">\n {{ selectedStatus ? selectedStatus.label : \"+ Set status\" }}\n </Button>\n </PopoverTrigger>\n <PopoverContent class=\"w-[200px] p-0\" align=\"start\">\n <StatusList />\n </PopoverContent>\n </Popover>\n\n <Drawer v-else :open=\"isOpen\" @update:open=\"(newOpenValue) => isOpen = newOpenValue\">\n <DrawerTrigger as-child>\n <Button variant=\"outline\" class=\"w-[150px] justify-start\">\n {{ selectedStatus ? selectedStatus.label : \"+ Set status\" }}\n </Button>\n </DrawerTrigger>\n <DrawerContent>\n <div class=\"mt-4 border-t\">\n <StatusList />\n </div>\n </DrawerContent>\n </Drawer>\n </div>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "ComboboxResponsive.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"path": "example/CommandDemo.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n CommandSeparator,\n CommandShortcut,\n} from '@/registry/default/ui/command'\n\nimport {\n Calculator,\n Calendar,\n CreditCard,\n Settings,\n Smile,\n User,\n} from 'lucide-vue-next'\n</script>\n\n<template>\n <Command class=\"rounded-lg border shadow-md max-w-[450px]\">\n <CommandInput placeholder=\"Type a command or search...\" />\n <CommandList>\n <CommandEmpty>No results found.</CommandEmpty>\n <CommandGroup heading=\"Suggestions\">\n <CommandItem value=\"Calendar\">\n <Calendar class=\"mr-2 h-4 w-4\" />\n <span>Calendar</span>\n </CommandItem>\n <CommandItem value=\"Search Emoji\">\n <Smile class=\"mr-2 h-4 w-4\" />\n <span>Search Emoji</span>\n </CommandItem>\n <CommandItem value=\"Calculator\">\n <Calculator class=\"mr-2 h-4 w-4\" />\n <span>Calculator</span>\n </CommandItem>\n </CommandGroup>\n <CommandSeparator />\n <CommandGroup heading=\"Settings\">\n <CommandItem value=\"Profile\">\n <User class=\"mr-2 h-4 w-4\" />\n <span>Profile</span>\n <CommandShortcut>⌘P</CommandShortcut>\n </CommandItem>\n <CommandItem value=\"Billing\">\n <CreditCard class=\"mr-2 h-4 w-4\" />\n <span>Billing</span>\n <CommandShortcut>⌘B</CommandShortcut>\n </CommandItem>\n <CommandItem value=\"Settings\">\n <Settings class=\"mr-2 h-4 w-4\" />\n <span>Settings</span>\n <CommandShortcut>⌘S</CommandShortcut>\n </CommandItem>\n </CommandGroup>\n </CommandList>\n </Command>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "CommandDemo.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"path": "example/CommandDialogDemo.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport {\n CommandDialog,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n CommandSeparator,\n} from '@/registry/default/ui/command'\n\nimport { useMagicKeys } from '@vueuse/core'\nimport { ref, watch } from 'vue'\n\nconst open = ref(false)\n\nconst { Meta_J, Ctrl_J } = useMagicKeys({\n passive: false,\n onEventFired(e) {\n if (e.key === 'j' && (e.metaKey || e.ctrlKey))\n e.preventDefault()\n },\n})\n\nwatch([Meta_J, Ctrl_J], (v) => {\n if (v[0] || v[1])\n handleOpenChange()\n})\n\nfunction handleOpenChange() {\n open.value = !open.value\n}\n</script>\n\n<template>\n <div>\n <p class=\"text-sm text-muted-foreground\">\n Press\n <kbd\n class=\"pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground opacity-100\"\n >\n <span class=\"text-xs\">⌘</span>J\n </kbd>\n </p>\n <CommandDialog v-model:open=\"open\">\n <CommandInput placeholder=\"Type a command or search...\" />\n <CommandList>\n <CommandEmpty>No results found.</CommandEmpty>\n <CommandGroup heading=\"Suggestions\">\n <CommandItem value=\"calendar\">\n Calendar\n </CommandItem>\n <CommandItem value=\"search-emoji\">\n Search Emoji\n </CommandItem>\n <CommandItem value=\"calculator\">\n Calculator\n </CommandItem>\n </CommandGroup>\n <CommandSeparator />\n <CommandGroup heading=\"Settings\">\n <CommandItem value=\"profile\">\n Profile\n </CommandItem>\n <CommandItem value=\"billing\">\n Billing\n </CommandItem>\n <CommandItem value=\"settings\">\n Settings\n </CommandItem>\n </CommandGroup>\n </CommandList>\n </CommandDialog>\n </div>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "CommandDialogDemo.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"path": "example/ContextMenuDemo.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport {\n ContextMenu,\n ContextMenuCheckboxItem,\n ContextMenuContent,\n ContextMenuItem,\n ContextMenuLabel,\n ContextMenuRadioGroup,\n ContextMenuRadioItem,\n ContextMenuSeparator,\n ContextMenuShortcut,\n ContextMenuSub,\n ContextMenuSubContent,\n ContextMenuSubTrigger,\n ContextMenuTrigger,\n} from '@/registry/default/ui/context-menu'\n</script>\n\n<template>\n <ContextMenu>\n <ContextMenuTrigger class=\"flex h-[150px] w-[300px] items-center justify-center rounded-md border border-dashed text-sm\">\n Right click here\n </ContextMenuTrigger>\n <ContextMenuContent class=\"w-64\">\n <ContextMenuItem inset>\n Back\n <ContextMenuShortcut>⌘[</ContextMenuShortcut>\n </ContextMenuItem>\n <ContextMenuItem inset disabled>\n Forward\n <ContextMenuShortcut>⌘]</ContextMenuShortcut>\n </ContextMenuItem>\n <ContextMenuItem inset>\n Reload\n <ContextMenuShortcut>⌘R</ContextMenuShortcut>\n </ContextMenuItem>\n <ContextMenuSub>\n <ContextMenuSubTrigger inset>\n More Tools\n </ContextMenuSubTrigger>\n <ContextMenuSubContent class=\"w-48\">\n <ContextMenuItem>\n Save Page As...\n <ContextMenuShortcut>⇧⌘S</ContextMenuShortcut>\n </ContextMenuItem>\n <ContextMenuItem>Create Shortcut...</ContextMenuItem>\n <ContextMenuItem>Name Window...</ContextMenuItem>\n <ContextMenuSeparator />\n <ContextMenuItem>Developer Tools</ContextMenuItem>\n </ContextMenuSubContent>\n </ContextMenuSub>\n <ContextMenuSeparator />\n <ContextMenuCheckboxItem checked>\n Show Bookmarks Bar\n <ContextMenuShortcut>⌘⇧B</ContextMenuShortcut>\n </ContextMenuCheckboxItem>\n <ContextMenuCheckboxItem>Show Full URLs</ContextMenuCheckboxItem>\n <ContextMenuSeparator />\n <ContextMenuRadioGroup model-value=\"pedro\">\n <ContextMenuLabel inset>\n People\n </ContextMenuLabel>\n <ContextMenuSeparator />\n <ContextMenuRadioItem value=\"pedro\">\n Pedro Duarte\n </ContextMenuRadioItem>\n <ContextMenuRadioItem value=\"colm\">\n Colm Tuite\n </ContextMenuRadioItem>\n </ContextMenuRadioGroup>\n </ContextMenuContent>\n </ContextMenu>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "ContextMenuDemo.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"path": "example/CustomChartTooltip.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { Card, CardContent } from '@/registry/default/ui/card'\n\ndefineProps<{\n title?: string\n data: {\n name: string\n color: string\n value: any\n }[]\n}>()\n</script>\n\n<template>\n <Card class=\"text-sm\">\n <CardContent class=\"p-3 min-w-[180px] flex flex-col gap-2\">\n <div v-for=\"(item, key) in data\" :key=\"key\" class=\"flex justify-between items-center\">\n <div class=\"flex items-center\">\n <span class=\"w-1 h-7 mr-4 rounded-full\" :style=\"{ background: item.color }\" />\n <span>{{ item.name }}</span>\n </div>\n <span class=\"font-semibold ml-4\">{{ item.value }}</span>\n </div>\n </CardContent>\n </Card>\n</template>\n",
|
||||
"type": "registry:example",
|
||||
"target": "CustomChartTooltip.vue"
|
||||
"target": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||