feat: vitepress docs

This commit is contained in:
zernonia 2023-08-30 15:42:00 +08:00
parent 90e2e2f7c0
commit 4017bc628c
212 changed files with 5268 additions and 605 deletions

View File

@ -1,5 +1,4 @@
const process = require('node:process')
// const process = require('node:process')
// process.env.ESLINT_TSCONFIG = 'tsconfig.json'
module.exports = {
@ -8,6 +7,7 @@ module.exports = {
rules: {
'vue/one-component-per-file': 'off',
'vue/no-reserved-component-names': 'off',
'vue/no-useless-v-bind': 'off',
'symbol-description': 'off',
'no-console': 'warn',
'no-tabs': 'off',

View File

@ -1,15 +1,22 @@
import path from 'node:path'
import { defineConfig } from 'vitepress'
import Icons from 'unplugin-icons/vite'
// https://vitepress.dev/reference/site-config
export default defineConfig({
title: 'shadcn-vue',
description: 'A VitePress Site',
srcDir: path.resolve(__dirname, '../src/content'),
markdown: {
theme: 'css-variables',
},
vite: {
plugins: [
Icons({ compiler: 'vue3', autoInstall: true }) as any,
],
resolve: {
alias: {
'@': path.resolve(__dirname, '../src/lib'),
'@': path.resolve(__dirname, '../src'),
},
},
},

View File

@ -0,0 +1,54 @@
<script setup lang="ts">
import { defineAsyncComponent } from 'vue'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/lib/registry/default/ui/tabs'
import { cn } from '@/lib/utils'
import LucideSpinner from '~icons/lucide/loader-2'
const props = withDefaults(defineProps<{
name: string
align?: 'center' | 'start' | 'end'
}>(), { align: 'center' })
const Component = defineAsyncComponent({
loadingComponent: LucideSpinner,
loader: () => import(`../../../src/lib/registry/default/examples/${props.name}.vue`),
timeout: 5000,
})
</script>
<template>
<div class="group relative my-4 flex flex-col space-y-2">
<Tabs default-value="preview" class="relative mr-auto w-full">
<div class="flex items-center justify-between pb-3">
<TabsList class="w-full justify-start rounded-none border-b bg-transparent p-0">
<TabsTrigger
value="preview"
class="relative h-9 rounded-none border-b-2 border-b-transparent bg-transparent px-4 pb-3 pt-2 font-semibold text-muted-foreground shadow-none transition-none data-[state=active]:border-b-primary data-[state=active]:text-foreground data-[state=active]:shadow-none"
>
Preview
</TabsTrigger>
<TabsTrigger
value="code"
class="relative h-9 rounded-none border-b-2 border-b-transparent bg-transparent px-4 pb-3 pt-2 font-semibold text-muted-foreground shadow-none transition-none data-[state=active]:border-b-primary data-[state=active]:text-foreground data-[state=active]:shadow-none"
>
Code
</TabsTrigger>
</TabsList>
</div>
<TabsContent value="preview" class="relative rounded-md border">
<div
:class="cn('preview flex min-h-[350px] w-full justify-center p-10', {
'items-center': align === 'center',
'items-start': align === 'start',
'items-end': align === 'end',
})"
>
<Component :is="Component" />
</div>
</TabsContent>
<TabsContent value="code">
<slot />
</TabsContent>
</Tabs>
</div>
</template>

View File

@ -0,0 +1,16 @@
<script setup lang="ts">
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/lib/registry/default/ui/accordion'
</script>
<template>
<Accordion type="single" :collapsible="true">
<AccordionItem value="manual-installation" class="border-none">
<AccordionTrigger>
Manual Installation
</AccordionTrigger>
<AccordionContent>
<slot />
</AccordionContent>
</AccordionItem>
</Accordion>
</template>

View File

@ -0,0 +1,59 @@
<script setup lang="ts">
import { onContentUpdated } from 'vitepress'
import { shallowRef } from 'vue'
import type { TableOfContents, TableOfContentsItem } from '../types/docs'
import TableOfContentTree from './TableOfContentTree.vue'
const headers = shallowRef<TableOfContents>()
function getHeadingsWithHierarchy(divId: string) {
const div = document.querySelector(divId)
if (!div)
return { items: [] }
const headings: HTMLHeadingElement[] = Array.from(
div.querySelectorAll('h2, h3'),
)
const hierarchy: TableOfContents = { items: [] }
let currentLevel: TableOfContentsItem | undefined
headings.forEach((heading: HTMLHeadingElement) => {
const level = Number.parseInt(heading.tagName.charAt(1))
if (!heading.id) {
const newId = heading.innerText
.replaceAll(/[^a-zA-Z0-9 ]/g, '')
.replaceAll(' ', '-')
.toLowerCase()
heading.id = `${newId}`
}
const item: TableOfContentsItem = {
title: heading.textContent || '',
url: `#${heading.id}`,
items: [],
}
if (level === 2) {
hierarchy.items.push(item)
currentLevel = item
}
else if (level === 3 && currentLevel?.items) {
currentLevel.items.push(item)
}
})
return hierarchy
}
onContentUpdated(() => {
headers.value = getHeadingsWithHierarchy('.vp-doc')
})
</script>
<template>
<div class="space-y-2">
<p class="font-medium">
On This Page
</p>
<TableOfContentTree :tree="headers" :level="1" />
</div>
</template>

View File

@ -0,0 +1,51 @@
<script setup lang="ts">
import { onMounted, onUnmounted, ref, watch } from 'vue'
import { useRoute } from 'vitepress'
import type { TableOfContentsItem } from '../types/docs'
import { cn } from '@/lib/utils'
withDefaults(defineProps<{
level: number
tree: TableOfContentsItem
}>(), {
level: 1,
tree: () => ({
items: [],
}),
})
const route = useRoute()
const hash = ref('')
function setHash() {
hash.value = location.hash
}
onMounted(() => {
window.addEventListener('hashchange', setHash)
setHash()
})
onUnmounted(() => {
window.removeEventListener('hashchange', setHash)
})
watch(() => route.path, () => {
setHash()
})
</script>
<template>
<ul :class="cn('m-0 list-none', { 'pl-4': level !== 1 })">
<template v-if="tree.items?.length">
<li v-for="item in tree.items" :key="item.title" class="mt-0 pt-2">
<a
:href="item.url"
:class="
cn('inline-block no-underline transition-colors hover:text-foreground',
item.url === hash
? 'font-medium text-foreground'
: 'text-muted-foreground')"
>{{ item.title }} </a>
<TableOfContentTree v-if="item.items?.length" :tree="item" :level="level + 1" />
</li>
</template>
</ul>
</template>

View File

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

View File

@ -0,0 +1,365 @@
export interface NavItem {
title: string
href?: string
disabled?: boolean
external?: boolean
icon?: string
label?: string
}
export type SidebarNavItem = NavItem & {
items: SidebarNavItem[]
}
export type NavItemWithChildren = NavItem & {
items: NavItemWithChildren[]
}
interface DocsConfig {
mainNav: NavItem[]
sidebarNav: SidebarNavItem[]
}
export const docsConfig: DocsConfig = {
mainNav: [
{
title: 'Documentation',
href: '/docs',
},
{
title: 'Components',
href: '/docs/components/accordion',
},
{
title: 'Themes',
href: '/themes',
},
{
title: 'Examples',
href: '/examples/dashboard',
},
{
title: 'GitHub',
href: 'https://github.com/huntabyte/shadcn-svelte',
external: true,
},
],
sidebarNav: [
{
title: 'Getting Started',
items: [
{
title: 'Introduction',
href: '/docs',
items: [],
},
{
title: 'Installation',
href: '/docs/installation',
items: [],
},
{
title: 'components.json',
href: '/docs/components-json',
items: [],
},
{
title: 'Theming',
href: '/docs/theming',
items: [],
},
{
title: 'CLI',
href: '/docs/cli',
items: [],
},
{
title: 'Typography',
href: '/docs/typography',
items: [],
},
{
title: 'Figma',
href: '/docs/figma',
items: [],
},
{
title: 'Changelog',
href: '/docs/changelog',
items: [],
},
{
title: 'About',
href: '/docs/about',
items: [],
},
],
},
{
title: 'Components',
items: [
{
title: 'Accordion',
href: '/docs/components/accordion',
items: [],
},
{
title: 'Alert',
href: '/docs/components/alert',
items: [],
},
{
title: 'Alert Dialog',
href: '/docs/components/alert-dialog',
items: [],
},
{
title: 'Aspect Ratio',
href: '/docs/components/aspect-ratio',
items: [],
},
{
title: 'Avatar',
href: '/docs/components/avatar',
items: [],
},
{
title: 'Badge',
href: '/docs/components/badge',
items: [],
},
{
title: 'Button',
href: '/docs/components/button',
items: [],
},
// {
// title: "Calendar",
// href: "#",
// label: "Soon",
// disabled: true,
// items: []
// },
{
title: 'Card',
href: '/docs/components/card',
items: [],
},
{
title: 'Checkbox',
href: '/docs/components/checkbox',
items: [],
},
{
title: 'Collapsible',
href: '/docs/components/collapsible',
items: [],
},
{
title: 'Combobox',
disabled: true,
label: 'Soon',
href: '#',
items: [],
},
// {
// title: "Command",
// href: "#",
// label: "Soon",
// disabled: true,
// items: []
// },
{
title: 'Context Menu',
href: '/docs/components/context-menu',
items: [],
},
{
title: 'Data Table',
href: '/docs/components/data-table',
items: [],
},
// {
// title: "Date Picker",
// href: "#",
// label: "Soon",
// disabled: true,
// items: []
// },
{
title: 'Dialog',
href: '/docs/components/dialog',
items: [],
},
{
title: 'Dropdown Menu',
href: '/docs/components/dropdown-menu',
items: [],
},
{
title: 'Form',
href: '#',
label: 'Soon',
disabled: true,
items: [],
},
{
title: 'Hover Card',
href: '/docs/components/hover-card',
items: [],
},
{
title: 'Input',
href: '/docs/components/input',
items: [],
},
{
title: 'Label',
href: '/docs/components/label',
items: [],
},
{
title: 'Menubar',
href: '/docs/components/menubar',
items: [],
},
// {
// title: "Navigation Menu",
// href: "#",
// label: "Soon",
// disabled: true,
// items: []
// },
{
title: 'Popover',
href: '/docs/components/popover',
items: [],
},
{
title: 'Progress',
href: '/docs/components/progress',
items: [],
},
{
title: 'Radio Group',
href: '/docs/components/radio-group',
items: [],
},
// {
// title: "Scroll Area",
// href: "#",
// label: "Soon",
// disabled: true,
// items: []
// },
{
title: 'Select',
href: '/docs/components/select',
items: [],
},
{
title: 'Separator',
href: '/docs/components/separator',
items: [],
},
{
title: 'Sheet',
href: '/docs/components/sheet',
items: [],
},
{
title: 'Skeleton',
href: '/docs/components/skeleton',
items: [],
},
{
title: 'Slider',
href: '/docs/components/slider',
items: [],
},
{
title: 'Switch',
href: '/docs/components/switch',
items: [],
},
{
title: 'Table',
href: '/docs/components/table',
items: [],
},
{
title: 'Tabs',
href: '/docs/components/tabs',
items: [],
},
{
title: 'Textarea',
href: '/docs/components/textarea',
items: [],
},
// {
// title: "Toast",
// href: "#",
// label: "Soon",
// disabled: true,
// items: []
// },
{
title: 'Toggle',
href: '/docs/components/toggle',
items: [],
},
{
title: 'Tooltip',
href: '/docs/components/tooltip',
items: [],
},
],
},
],
}
interface Example {
name: string
href: string
label?: string
code: string
}
export const examples: Example[] = [
{
name: 'Dashboard',
href: '/examples/dashboard',
code: 'https://github.com/huntabyte/shadcn-svelte/tree/main/apps/www/src/lib/components/docs/dashboard',
},
{
name: 'Cards',
href: '/examples/cards',
code: 'https://github.com/huntabyte/shadcn-svelte/tree/main/apps/www/src/routes/examples/cards',
},
// {
// name: "Tasks",
// href: "/examples/tasks",
// label: "New",
// code: "https://github.com/huntabyte/shadcn-svelte/tree/main/apps/www/apps/www/app/examples/tasks"
// },
{
name: 'Playground',
href: '/examples/playground',
code: 'https://github.com/huntabyte/shadcn-svelte/tree/main/apps/www/apps/www/app/examples/playground',
},
{
name: 'Music',
href: '/examples/music',
code: 'https://github.com/huntabyte/shadcn-svelte/tree/main/apps/www/apps/www/app/examples/music',
},
{
name: 'Authentication',
href: '/examples/authentication',
code: 'https://github.com/huntabyte/shadcn-svelte/tree/main/apps/www/src/routes/examples/authentication',
},
{
name: 'Forms',
href: '/examples/forms',
code: 'https://github.com/huntabyte/shadcn-svelte/tree/main/apps/www/src/routes/examples/forms',
},
]

View File

@ -0,0 +1,14 @@
export const siteConfig = {
name: 'shadcn-vue',
url: 'https://shadcn-vue.com',
ogImage: 'https://shadcn-vue.com/og.png',
description:
'Beautifully designed components built with Radix Vue and Tailwind CSS.',
links: {
twitter: 'https://twitter.com/huntabyte',
github: 'https://github.com/huntabyte/shadcn-vue',
shadTwitter: 'https://twitter.com/shadcn',
shadGithub: 'https://github.com/shadcn/ui',
},
keywords: 'shadcn,Vue,Nuxt,Vue Components,TailwindCSS,Radix Vue',
}

View File

@ -1,10 +1,19 @@
/* eslint-disable vue/component-definition-name-casing */
// https://vitepress.dev/guide/custom-theme
import Layout from './Layout.vue'
import Layout from './layout/MainLayout.vue'
import DocsLayout from './layout/DocsLayout.vue'
import * as components from './components'
import './style.css'
import './styles/vp-doc.css'
import './styles/shiki.css'
export default {
Layout,
enhanceApp({ app, router, siteData }) {
enhanceApp({ app }) {
// ...
app.component('docs', DocsLayout)
for (const component of Object.keys(components))
app.component(component, components[component])
},
}

View File

@ -0,0 +1,81 @@
<script setup lang="ts">
import { useData, useRoute } from 'vitepress'
import { docsConfig } from '../config/docs'
import TableOfContentVue from '../components/TableOfContent.vue'
import { ScrollArea } from '@/lib/registry/default/ui/scroll-area'
import RadixIconsCode from '~icons/radix-icons/code'
const $route = useRoute()
const { frontmatter } = useData()
</script>
<template>
<div class="container flex-1 items-start md:grid md:grid-cols-[220px_minmax(0,1fr)] md:gap-6 lg:grid-cols-[240px_minmax(0,1fr)] lg:gap-10">
<aside
class="fixed top-14 z-30 -ml-2 hidden h-[calc(100vh-3.5rem)] w-full shrink-0 md:sticky md:block overflow-y-auto"
>
<ScrollArea orientation="vertical" class="h-full py-6 pl-8 pr-6 lg:py-8" :type="'auto'">
<div class="w-full">
<div v-for="docsGroup in docsConfig.sidebarNav" :key="docsGroup.title" class="pb-4">
<h4
class="mb-1 rounded-md px-2 py-1 text-sm font-semibold"
>
{{ docsGroup.title }}
</h4>
<div
v-for="doc in docsGroup.items "
:key="doc.title"
class="grid grid-flow-row auto-rows-max text-sm"
>
<a
v-if="doc.href"
:disabled="doc.disabled"
:href="doc.href"
class="group flex w-full items-center rounded-md border border-transparent px-2 py-1 hover:underline text-muted-foreground"
:class="{
'!font-semibold !text-foreground': $route.path === `${doc.href}.html`,
}"
>
{{ doc.title }}
</a>
</div>
</div>
</div>
</ScrollArea>
</aside>
<main class="relative py-6 lg:gap-10 lg:py-8 xl:grid xl:grid-cols-[1fr_300px]">
<div class="mx-auto w-full min-w-0">
<div class="space-y-2">
<h1 class="scroll-m-20 text-4xl font-bold tracking-tight">
{{ frontmatter.title }}
</h1>
<p class="text-lg text-muted-foreground">
{{ frontmatter.description }}
</p>
</div>
<div class="flex items-center space-x-2 pt-4">
<a v-if="frontmatter.source" :href="frontmatter.source" target="_blank" class="inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 select-none border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80">
<RadixIconsCode class="mr-1" />
Component Source
</a>
<a v-if="frontmatter.primitive" :href="frontmatter.primitive" target="_blank" class="inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 select-none border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80">
Primitive API Reference
</a>
</div>
<div class="vp-doc">
<slot />
</div>
</div>
<div class="hidden text-sm xl:block">
<div class="sticky top-16 -mt-10 h-[calc(100vh-3.5rem)] overflow-hidden pt-6">
<TableOfContentVue />
</div>
</div>
</main>
</div>
</template>

View File

@ -0,0 +1,182 @@
<script setup lang="ts">
import { useDark, useToggle } from '@vueuse/core'
import { Content, useData, useRoute } from 'vitepress'
import { Button } from '@/lib/registry/default/ui/button'
import { Kbd } from '@/lib/registry/default/ui/kbd'
import RadixIconsGithubLogo from '~icons/radix-icons/github-logo'
import TablerBrandX from '~icons/tabler/brand-x'
import RadixIconsMoon from '~icons/radix-icons/moon'
import RadixIconsSun from '~icons/radix-icons/sun'
import LucidePanelRightOpen from '~icons/lucide/panel-right-open'
import LucideSearch from '~icons/lucide/search'
const { frontmatter } = useData()
const $route = useRoute()
const isDark = useDark()
const toggleDark = useToggle(isDark)
const routes = [
{
name: 'Documentation',
path: '/docs',
},
{
name: 'Components',
path: '/docs/components/accordion',
},
{
name: 'Themes',
path: '/themes',
},
{
name: 'Examples',
path: '/examples/dashboard',
},
{
name: 'GitHub',
path: 'https://github.com/radix-vue/shadcn-vue',
target: '_blank',
},
]
const links = [
{
name: 'GitHub',
href: 'https://github.com/radix-vue',
icon: RadixIconsGithubLogo,
},
{
name: 'X',
href: 'https://x.com',
icon: TablerBrandX,
},
]
</script>
<template>
<div class="flex min-h-screen flex-col bg-background">
<header class="sticky z-40 top-0 bg-background border-b border-border">
<div
class="max-w-8xl flex h-[58px] items-center justify-between p-4 mx-auto"
>
<div class="flex gap-6 md:gap-8">
<a href="/" class="text-md font-bold"> shadcn-vue </a>
<nav
v-for="route in routes"
:key="route.name"
class="flex items-center space-x-6 text-sm font-medium"
>
<a
:href="route.path"
:target="route.target"
class="transition-colors hover:text-foreground/80 text-foreground/60"
:class="{
'font-semibold !text-foreground': $route.path === `${route.path}.html`,
}"
>
{{ route.name }}
</a>
</nav>
</div>
<div class="md:flex flex-1 items-center justify-end space-x-4 hidden">
<Button
variant="outline"
class="w-72 h-8 px-3 hidden lg:flex lg:justify-between lg:items-center"
>
<div class="flex items-center">
<LucideSearch class="w-4 h-4 mr-2 text-muted" />
<span class="text-muted"> Search for anything... </span>
</div>
<div class="flex items-center gap-x-1">
<Kbd></Kbd>
<Kbd>K</Kbd>
</div>
</Button>
<div
v-for="link in links"
:key="link.name"
class="flex items-center space-x-1"
>
<a :href="link.href" target="_blank" class="text-foreground">
<component :is="link.icon" class="w-5 h-5" />
</a>
</div>
<button
class="flex items-center justify-center"
aria-label="Toggle dark mode"
@click="toggleDark()"
>
<component
:is="isDark ? RadixIconsSun : RadixIconsMoon"
class="w-5 h-5 text-foreground"
/>
</button>
<button class="flex md:hidden items-center justify-center">
<LucidePanelRightOpen class="w-5 h-5 text-foreground" />
</button>
</div>
<button class="flex md:hidden items-center justify-center">
<LucidePanelRightOpen class="w-5 h-5 text-foreground" />
</button>
</div>
</header>
<div class="flex-1">
<component :is="'docs'" v-if="$route.path.includes('docs')">
<Content />
</component>
<component :is="frontmatter.layout" v-else-if="frontmatter.layout">
<slot />
</component>
<main v-else>
<Content />
</main>
</div>
<footer class="bg-background z-40 border-t border-border text-foreground">
<div class="max-w-8xl h-20 flex items-center justify-between p-4 mx-auto">
<div class="flex justify-center items-center">
<span class="text-sm">
Built and designed by {{ " " }}
<a
href="https://twitter.com/shadcn"
target="_blank"
class="underline underline-offset-4 font-bold decoration-foreground"
>
shadcn
</a>
</span>
<span class="text-sm ml-0.5"> . </span>
<span class="text-sm ml-2">
Ported to Vue by {{ " " }}
<a
href="https://twitter.com"
target="_blank"
class="underline underline-offset-4 font-bold decoration-foreground"
>
Radix Vue
</a>
</span>
<span class="text-sm ml-0.5"> . </span>
<span class="text-sm ml-2">
The code source is available on {{ " " }}
<a
href="https://github.com/radix-vue/shadcn-vue"
target="_blank"
class="underline underline-offset-4 font-bold decoration-foreground"
>
GitHub
</a>
</span>
</div>
</div>
</footer>
</div>
</template>

View File

@ -1,5 +1,160 @@
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap");
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--primary: 240 5.9% 10%;
--primary-foreground: 0 0% 98%;
--secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--ring: 240 5% 64.9%;
--radius: 0.5rem;
}
.dark {
--background: 240 10% 3.9%;
--foreground: 0 0% 98%;
--muted: 240 3.7% 15.9%;
--muted-foreground: 240 5% 64.9%;
--popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%;
--card: 240 10% 3.9%;
--card-foreground: 0 0% 98%;
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--primary: 0 0% 98%;
--primary-foreground: 240 5.9% 10%;
--secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%;
--accent: 240 3.7% 15.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 85.7% 97.3%;
--ring: 240 3.7% 15.9%;
}
}
@layer base {
* {
@apply border-border;
}
html {
-webkit-text-size-adjust: 100%;
font-variation-settings: normal;
}
body {
@apply bg-background text-foreground min-h-screen antialiased font-sans;
font-feature-settings: "rlig" 1, "calt" 1;
}
/* Mobile tap highlight */
/* https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-tap-highlight-color */
html {
-webkit-tap-highlight-color: rgba(128, 128, 128, 0.5);
}
/* === Scrollbars === */
::-webkit-scrollbar {
@apply w-2;
@apply h-2;
}
::-webkit-scrollbar-track {
@apply !bg-muted;
}
::-webkit-scrollbar-thumb {
@apply rounded-sm !bg-muted-foreground/30;
}
/* Firefox */
/* https://developer.mozilla.org/en-US/docs/Web/CSS/scrollbar-color#browser_compatibility */
html {
scrollbar-color: hsl(215.4 16.3% 46.9% / 0.3);
}
html.dark {
scrollbar-color: hsl(215.4 16.3% 56.9% / 0.3);
}
.hide-scrollbar::-webkit-scrollbar {
display: none;
}
.hide-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
.antialised {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
pre {
@apply mb-4 mt-6 max-h-[650px] overflow-x-auto rounded-lg border !bg-zinc-950 py-4 dark:!bg-zinc-900
}
pre code {
@apply relative rounded font-mono text-sm ;
}
pre code .line {
@apply px-4 min-h-[1.5rem] py-0.5 w-full inline-block;
}
html {
font-family: Arial, Helvetica;
}
@layer utilities {
.step {
counter-increment: step;
}
.step:before {
@apply absolute w-9 h-9 bg-muted rounded-full font-mono font-medium text-center text-base inline-flex items-center justify-center -indent-px border-4 border-background;
@apply ml-[-50px] mt-[-4px];
content: counter(step);
}
}
@media (max-width: 640px) {
.container {
@apply px-4;
}
}

View File

@ -0,0 +1,13 @@
:root {
--shiki-color-text: #EEEEEE;
--shiki-color-background: #ffffff;
--shiki-token-constant: #ffffff;
--shiki-token-string: #ffffff88;
--shiki-token-comment: #880000;
--shiki-token-keyword: #ffffff88;
--shiki-token-parameter: #AA0000;
--shiki-token-function: #ffffff;
--shiki-token-string-expression: #ffffff88;
--shiki-token-punctuation: #ffffff;
--shiki-token-link: #EE0000;
}

View File

@ -0,0 +1,570 @@
:root {
--vp-icon-copy: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' height='20' width='20' stroke='rgba(128,128,128,1)' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2M9 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M9 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2'/%3E%3C/svg%3E");
--vp-icon-copied: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' height='20' width='20' stroke='rgba(128,128,128,1)' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2M9 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M9 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2m-6 9 2 2 4-4'/%3E%3C/svg%3E");
--vp-code-bg: hsl(240 3.7% 15.9%);
--vp-c-divider: hsl(240 3.7% 15.9%);
}
/**
* Headings
* -------------------------------------------------------------------------- */
.vp-doc h1,
.vp-doc h2,
.vp-doc h3,
.vp-doc h4,
.vp-doc h5,
.vp-doc h6 {
position: relative;
font-weight: 600;
outline: none;
}
.vp-doc h1 {
letter-spacing: -0.02em;
line-height: 40px;
font-size: 28px;
}
.vp-doc h2 {
/* margin: 48px 0 16px; */
margin: 16px 0 16px;
border-top: 1px solid var(--vp-c-divider);
padding-top: 24px;
letter-spacing: -0.02em;
line-height: 32px;
font-size: 24px;
}
.vp-doc h3 {
margin: 32px 0 0;
letter-spacing: -0.01em;
line-height: 28px;
font-size: 20px;
}
.vp-doc .header-anchor {
position: absolute;
top: 0;
left: 0;
margin-left: -0.87em;
font-weight: 500;
user-select: none;
opacity: 0;
text-decoration: none;
transition:
color 0.25s,
opacity 0.25s;
}
.vp-doc .header-anchor:before {
content: var(--vp-header-anchor-symbol);
}
.vp-doc h1:hover .header-anchor,
.vp-doc h1 .header-anchor:focus,
.vp-doc h2:hover .header-anchor,
.vp-doc h2 .header-anchor:focus,
.vp-doc h3:hover .header-anchor,
.vp-doc h3 .header-anchor:focus,
.vp-doc h4:hover .header-anchor,
.vp-doc h4 .header-anchor:focus,
.vp-doc h5:hover .header-anchor,
.vp-doc h5 .header-anchor:focus,
.vp-doc h6:hover .header-anchor,
.vp-doc h6 .header-anchor:focus {
opacity: 1;
}
@media (min-width: 768px) {
.vp-doc h1 {
letter-spacing: -0.02em;
line-height: 40px;
font-size: 32px;
}
}
.vp-doc h2 .header-anchor {
top: 24px;
}
/**
* Paragraph and inline elements
* -------------------------------------------------------------------------- */
/* .vp-doc p,
.vp-doc summary {
margin: 16px 0;
}
.vp-doc p {
line-height: 28px;
}
.vp-doc blockquote {
margin: 16px 0;
border-left: 2px solid var(--vp-c-divider);
padding-left: 16px;
transition: border-color 0.5s;
}
.vp-doc blockquote > p {
margin: 0;
font-size: 16px;
color: var(--vp-c-text-2);
transition: color 0.5s;
}
.vp-doc a {
font-weight: 500;
color: var(--vp-c-brand-1);
text-decoration: underline;
text-underline-offset: 2px;
transition:
color 0.25s,
opacity 0.25s;
}
.vp-doc a:hover {
color: var(--vp-c-brand-2);
}
.vp-doc strong {
font-weight: 600;
} */
/**
* Lists
* -------------------------------------------------------------------------- */
.vp-doc ul,
.vp-doc ol {
padding-left: 1.25rem;
margin: 16px 0;
}
.vp-doc ul {
list-style: disc;
}
.vp-doc ol {
list-style: decimal;
}
.vp-doc li + li {
margin-top: 8px;
}
.vp-doc li > ol,
.vp-doc li > ul {
margin: 8px 0 0;
}
/**
* Table
* -------------------------------------------------------------------------- */
.vp-doc table {
display: block;
border-collapse: collapse;
margin: 20px 0;
overflow-x: auto;
}
.vp-doc tr {
border-top: 1px solid var(--vp-c-divider);
transition: background-color 0.5s;
}
.vp-doc tr:nth-child(2n) {
background-color: var(--vp-c-bg-soft);
}
.vp-doc th,
.vp-doc td {
border: 1px solid var(--vp-c-divider);
padding: 8px 16px;
}
.vp-doc th {
text-align: left;
font-size: 14px;
font-weight: 600;
color: var(--vp-c-text-2);
background-color: var(--vp-c-bg-soft);
}
.vp-doc td {
font-size: 14px;
}
/**
* Decorational elements
* -------------------------------------------------------------------------- */
.vp-doc hr {
margin: 16px 0;
border: none;
border-top: 1px solid var(--vp-c-divider);
}
/**
* Custom Block
* -------------------------------------------------------------------------- */
.vp-doc .custom-block {
margin: 16px 0;
}
.vp-doc .custom-block p {
margin: 8px 0;
line-height: 24px;
}
.vp-doc .custom-block p:first-child {
margin: 0;
}
.vp-doc .custom-block div[class*='language-'] {
margin: 8px 0;
border-radius: 8px;
}
.vp-doc .custom-block div[class*='language-'] code {
font-weight: 400;
background-color: transparent;
}
.vp-doc .custom-block .vp-code-group .tabs {
margin: 0;
border-radius: 8px 8px 0 0;
}
/**
* Code
* -------------------------------------------------------------------------- */
/* inline code */
.vp-doc :not(pre, h1, h2, h3, h4, h5, h6) > code {
font-size: var(--vp-code-font-size);
color: var(--vp-code-color);
}
.vp-doc :not(pre) > code {
border-radius: 4px;
padding: 3px 6px;
background-color: var(--vp-code-bg);
transition:
color 0.25s,
background-color 0.5s;
}
.vp-doc a > code {
color: var(--vp-code-link-color);
}
.vp-doc a:hover > code {
color: var(--vp-code-link-hover-color);
}
.vp-doc h1 > code,
.vp-doc h2 > code,
.vp-doc h3 > code {
font-size: 0.9em;
}
.vp-doc div[class*='language-'],
.vp-block {
position: relative;
margin: 16px -24px;
background-color: var(--vp-code-block-bg);
overflow-x: auto;
transition: background-color 0.5s;
}
@media (min-width: 640px) {
.vp-doc div[class*='language-'],
.vp-block {
border-radius: 8px;
margin: 16px 0;
}
}
@media (max-width: 639px) {
.vp-doc li div[class*='language-'] {
border-radius: 8px 0 0 8px;
}
}
.vp-doc div[class*='language-'] + div[class*='language-'],
.vp-doc div[class$='-api'] + div[class*='language-'],
.vp-doc div[class*='language-'] + div[class$='-api'] > div[class*='language-'] {
margin-top: -8px;
}
.vp-doc [class*='language-'] pre,
.vp-doc [class*='language-'] code {
/*rtl:ignore*/
direction: ltr;
/*rtl:ignore*/
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
.vp-doc [class*='language-'] pre {
position: relative;
z-index: 1;
margin: 0;
/* padding: 20px 0; */
background: transparent;
overflow-x: auto;
}
.vp-doc [class*='language-'] code {
display: block;
/* padding: 0 24px; */
width: fit-content;
min-width: 100%;
line-height: var(--vp-code-line-height);
/* font-size: var(--vp-code-font-size); */
color: var(--vp-code-block-color);
transition: color 0.5s;
}
.vp-doc [class*='language-'] code .highlighted {
background-color: var(--vp-code-line-highlight-color);
transition: background-color 0.5s;
margin: 0 -24px;
padding: 0 24px;
width: calc(100% + 2 * 24px);
display: inline-block;
}
.vp-doc [class*='language-'] code .highlighted.error {
background-color: var(--vp-code-line-error-color);
}
.vp-doc [class*='language-'] code .highlighted.warning {
background-color: var(--vp-code-line-warning-color);
}
.vp-doc [class*='language-'] code .diff {
transition: background-color 0.5s;
margin: 0 -24px;
padding: 0 24px;
width: calc(100% + 2 * 24px);
display: inline-block;
}
.vp-doc [class*='language-'] code .diff::before {
position: absolute;
left: 10px;
}
.vp-doc [class*='language-'] .has-focused-lines .line:not(.has-focus) {
filter: blur(0.095rem);
opacity: 0.4;
transition:
filter 0.35s,
opacity 0.35s;
}
.vp-doc [class*='language-'] .has-focused-lines .line:not(.has-focus) {
opacity: 0.7;
transition:
filter 0.35s,
opacity 0.35s;
}
.vp-doc [class*='language-']:hover .has-focused-lines .line:not(.has-focus) {
filter: blur(0);
opacity: 1;
}
.vp-doc [class*='language-'] code .diff.remove {
background-color: var(--vp-code-line-diff-remove-color);
opacity: 0.7;
}
.vp-doc [class*='language-'] code .diff.remove::before {
content: '-';
color: var(--vp-code-line-diff-remove-symbol-color);
}
.vp-doc [class*='language-'] code .diff.add {
background-color: var(--vp-code-line-diff-add-color);
}
.vp-doc [class*='language-'] code .diff.add::before {
content: '+';
color: var(--vp-code-line-diff-add-symbol-color);
}
.vp-doc div[class*='language-'].line-numbers-mode {
/*rtl:ignore*/
padding-left: 32px;
}
.vp-doc .line-numbers-wrapper {
position: absolute;
top: 0;
bottom: 0;
/*rtl:ignore*/
left: 0;
z-index: 3;
/*rtl:ignore*/
border-right: 1px solid var(--vp-code-block-divider-color);
padding-top: 20px;
width: 32px;
text-align: center;
font-family: var(--vp-font-family-mono);
line-height: var(--vp-code-line-height);
font-size: var(--vp-code-font-size);
color: var(--vp-code-line-number-color);
transition:
border-color 0.5s,
color 0.5s;
}
.vp-doc [class*='language-'] > button.copy {
/*rtl:ignore*/
direction: ltr;
position: absolute;
top: 12px;
/*rtl:ignore*/
right: 12px;
z-index: 3;
border: 1px solid var(--vp-code-copy-code-border-color);
border-radius: 4px;
width: 40px;
height: 40px;
background-color: var(--vp-code-copy-code-bg);
opacity: 0;
cursor: pointer;
background-image: var(--vp-icon-copy);
background-position: 50%;
background-size: 20px;
background-repeat: no-repeat;
transition:
border-color 0.25s,
background-color 0.25s,
opacity 0.25s;
}
.vp-doc [class*='language-']:hover > button.copy,
.vp-doc [class*='language-'] > button.copy:focus {
opacity: 1;
}
.vp-doc [class*='language-'] > button.copy:hover,
.vp-doc [class*='language-'] > button.copy.copied {
border-color: var(--vp-code-copy-code-hover-border-color);
background-color: var(--vp-code-copy-code-hover-bg);
}
.vp-doc [class*='language-'] > button.copy.copied,
.vp-doc [class*='language-'] > button.copy:hover.copied {
/*rtl:ignore*/
border-radius: 0 4px 4px 0;
background-color: var(--vp-code-copy-code-hover-bg);
background-image: var(--vp-icon-copied);
}
.vp-doc [class*='language-'] > button.copy.copied::before,
.vp-doc [class*='language-'] > button.copy:hover.copied::before {
position: relative;
top: -1px;
/*rtl:ignore*/
transform: translateX(calc(-100% - 1px));
display: flex;
justify-content: center;
align-items: center;
border: 1px solid var(--vp-code-copy-code-hover-border-color);
/*rtl:ignore*/
border-right: 0;
border-radius: 4px 0 0 4px;
padding: 0 10px;
width: fit-content;
height: 40px;
text-align: center;
font-size: 12px;
font-weight: 500;
color: var(--vp-code-copy-code-active-text);
background-color: var(--vp-code-copy-code-hover-bg);
white-space: nowrap;
content: var(--vp-code-copy-copied-text-content);
}
.vp-doc [class*='language-'] > span.lang {
position: absolute;
top: 2px;
/*rtl:ignore*/
right: 8px;
z-index: 2;
font-size: 12px;
font-weight: 500;
color: var(--vp-code-lang-color);
transition:
color 0.4s,
opacity 0.4s;
@apply text-gray-600;
}
.vp-doc [class*='language-']:hover > button.copy + span.lang,
.vp-doc [class*='language-'] > button.copy:focus + span.lang {
opacity: 0;
}
/**
* Component: Team
* -------------------------------------------------------------------------- */
.vp-doc .VPTeamMembers {
margin-top: 24px;
}
.vp-doc .VPTeamMembers.small.count-1 .container {
margin: 0 !important;
max-width: calc((100% - 24px) / 2) !important;
}
.vp-doc .VPTeamMembers.small.count-2 .container,
.vp-doc .VPTeamMembers.small.count-3 .container {
max-width: 100% !important;
}
.vp-doc .VPTeamMembers.medium.count-1 .container {
margin: 0 !important;
max-width: calc((100% - 24px) / 2) !important;
}
/* prettier-ignore */
:is(.vp-external-link-icon, .vp-doc a[href*='://'], .vp-doc a[target='_blank']):not(.no-icon)::after {
display: inline-block;
margin-top: -1px;
margin-left: 4px;
width: 11px;
height: 11px;
background: currentColor;
color: var(--vp-c-text-3);
flex-shrink: 0;
--icon: url("data:image/svg+xml, %3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' %3E%3Cpath d='M0 0h24v24H0V0z' fill='none' /%3E%3Cpath d='M9 5v2h6.59L4 18.59 5.41 20 17 8.41V15h2V5H9z' /%3E%3C/svg%3E");
-webkit-mask-image: var(--icon);
mask-image: var(--icon);
}
.vp-external-link-icon::after {
content: '';
}

View File

@ -0,0 +1,9 @@
export interface TableOfContentsItem {
title?: string
url?: string
items?: TableOfContentsItem[]
}
export interface TableOfContents {
items: TableOfContentsItem[]
}

View File

@ -1,13 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Vue + TS</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@ -23,6 +23,8 @@
"vue": "^3.3.4"
},
"devDependencies": {
"@iconify-json/radix-icons": "^1.1.11",
"@iconify-json/tabler": "^1.1.89",
"@iconify/vue": "^4.1.1",
"@vitejs/plugin-vue": "^4.1.0",
"autoprefixer": "^10.4.14",
@ -30,6 +32,7 @@
"tailwind-merge": "^1.14.0",
"tailwindcss": "^3.3.2",
"typescript": "^5.0.2",
"unplugin-icons": "^0.16.6",
"vite": "^4.3.9",
"vue-tsc": "^1.4.2"
}

View File

@ -1,21 +0,0 @@
<script setup lang="ts">
import AccordionDemo from '@/registry/default/examples/AccordionDemo.vue'
import PopoverDemo from '@/registry/default/examples/PopoverDemo.vue'
import DialogDemo from '@/registry/default/examples/DialogDemo.vue'
import AlertDialogDemo from '@/registry/default/examples/AlertDialogDemo.vue'
import SelectDemo from '@/registry/default/examples/SelectDemo.vue'
</script>
<template>
<div class="p-8 grid gap-12 grid-cols-3 place-items-center">
<AccordionDemo class="max-w-[20rem]" />
<PopoverDemo />
<DialogDemo />
<AlertDialogDemo />
<SelectDemo />
</div>
</template>

View File

@ -1,16 +0,0 @@
<script setup>
const props = defineProps({
title: String,
})
</script>
<template>
<div class="flex items-center justify-center bg-white border border-neutral-200 shadow-sm rounded-xl h-[280px] overflow-hidden relative px-6">
<p class="text-xl font-semibold absolute left-4 top-3 rounded-lg bg-neutral-100 px-2 py-1 text-neutral-600">
{{ props.title }}
</p>
<div class="overflow-y-scroll flex-grow h-full max-h-full flex items-center justify-center pb-4 pt-12">
<slot />
</div>
</div>
</template>

View File

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

View File

@ -0,0 +1,5 @@
---
layout: docs
---
docs laytou please?

View File

@ -0,0 +1,49 @@
---
title: Accordion
description: A vertically stacked set of interactive headings that each reveal a section of content.
source: https://github.com/radix-vue/shadcn-vue/tree/main/apps/www/src/lib/registry/default/ui/accordion
primitive: https://www.radix-vue.com/components/accordion.html
---
<ComponentPreview name="AccordionDemo" class="[&_.accordion]:sm:max-w-[70%]">
<<< ../../../lib/registry/default/examples/AccordionDemo.vue
</ComponentPreview>
## Installation
```bash
npx shadcn-vue@latest add accordion
```
<ManualInstall>
1. Install `radix-vue`:
```bash
npm install radix-vue
```
2. Copy and paste the component source files linked at the top of this page into your project.
</ManualInstall>
## Usage
```vue
<script setup lang="ts">
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/lib/registry/default/ui/accordion'
</script>
<AccordionRoot>
<AccordionItem value="item-1">
<AccordionTrigger>Is it accessible?</AccordionTrigger>
<AccordionContent>
Yes. It adheres to the WAI-ARIA design pattern.
</AccordionContent>
</AccordionItem>
</AccordionRoot>
```

View File

@ -0,0 +1 @@
hi

View File

@ -2,3 +2,4 @@
home: true
---
this is main content

View File

View File

@ -1,24 +1,12 @@
<script setup>
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/registry/default/ui/accordion'
<script setup lang="ts">
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/lib/registry/default/ui/accordion'
const defaultValue = 'item-1'
const accordionItems = [
{
value: 'item-1',
title: 'Is it accessible?',
content: 'Yes. It adheres to the WAI-ARIA design pattern.',
},
{
value: 'item-2',
title: 'Is it unstyled?',
content: 'Yes. It\'s unstyled by default, giving you freedom over the look and feel.',
},
{
value: 'item-3',
title: 'Can it be animated?',
content: 'Yes! You can use the transition prop to configure the animation.',
},
{ value: 'item-1', title: 'Is it accessible?', content: 'Yes. It adheres to the WAI-ARIA design pattern.' },
{ value: 'item-2', title: 'Is it unstyled?', content: 'Yes. It\'s unstyled by default, giving you freedom over the look and feel.' },
{ value: 'item-3', title: 'Can it be animated?', content: 'Yes! You can use the transition prop to configure the animation.' },
]
</script>

View File

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

View File

@ -1,45 +0,0 @@
import { defineComponent, h } from 'vue'
import {
AccordionContent as AccordionContentPrimitive,
type AccordionContentProps, AccordionHeader as AccordionHeaderPrimitive,
type AccordionHeaderProps, AccordionItem as AccordionItemPrimitive,
type AccordionItemProps, AccordionRoot as AccordionRootPrimitive,
AccordionTrigger as AccordionTriggerPrimitive, type AccordionTriggerProps,
} from 'radix-vue'
import { ChevronDown } from 'lucide-vue-next'
import { cn } from '@/utils'
export const Accordion = AccordionRootPrimitive
export const AccordionContent = defineComponent<AccordionContentProps>(
(props, { attrs, slots }) => {
return () => h(AccordionContentPrimitive,
{ class: cn('overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down', attrs.class), ...props },
() => h('div', { class: 'pb-4 pt-0' }, slots),
)
},
)
export const AccordionTrigger = defineComponent<AccordionTriggerProps>(
(_props, { attrs, slots }) => {
return () => h(AccordionHeaderPrimitive, { class: 'flex' },
() => h(AccordionTriggerPrimitive,
{ class: cn('flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180', attrs.class) },
() => [slots.default(), h(ChevronDown, { class: 'h-4 w-4 shrink-0 transition-transform duration-200' })],
),
)
},
)
export const AccordionItem = defineComponent<AccordionItemProps>(
(props, { attrs, slots }) => {
return () => h(AccordionItemPrimitive,
{ class: cn('border-b', attrs.class), ...props }, slots)
},
)
export const AccordionHeader = defineComponent<AccordionHeaderProps>(
(props, { attrs, slots }) => {
return () => h(AccordionHeaderPrimitive, { class: cn('flex', attrs.class), ...props }, slots)
},
)

View File

@ -0,0 +1,14 @@
<script setup lang="ts">
import {
AccordionRoot,
type AccordionRootProps,
} from 'radix-vue'
const props = defineProps<AccordionRootProps>()
</script>
<template>
<AccordionRoot v-bind="props" class="accordion">
<slot />
</AccordionRoot>
</template>

View File

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

View File

@ -0,0 +1,15 @@
<script setup lang="ts">
import { AccordionItem, type AccordionItemProps } from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<AccordionItemProps & { class?: string }>()
</script>
<template>
<AccordionItem
v-bind="props"
:class="cn('border-b text-foreground border-border', props.class ?? '')"
>
<slot />
</AccordionItem>
</template>

View File

@ -0,0 +1,30 @@
<script setup lang="ts">
import {
AccordionHeader,
AccordionTrigger,
type AccordionTriggerProps,
} from 'radix-vue'
import { ChevronDown } from 'lucide-vue-next'
import { cn } from '@/lib/utils'
const props = defineProps<AccordionTriggerProps & { class?: string }>()
</script>
<template>
<AccordionHeader class="flex" as="div">
<AccordionTrigger
v-bind="props"
:class="
cn(
'flex flex-1 group items-center justify-between py-4 font-medium transition-all hover:underline',
props.class,
)
"
>
<slot />
<ChevronDown
class="w-4 h-4 shrink-0 transition-transform duration-300 group-data-[state=open]:rotate-180"
/>
</AccordionTrigger>
</AccordionHeader>
</template>

View File

@ -0,0 +1,4 @@
export { default as Accordion } from './Accordion.vue'
export { default as AccordionContent } from './AccordionContent.vue'
export { default as AccordionItem } from './AccordionItem.vue'
export { default as AccordionTrigger } from './AccordionTrigger.vue'

View File

@ -1,97 +0,0 @@
import { defineComponent, h } from 'vue'
import {
AlertDialogAction as AlertDialogActionPrimitive, type AlertDialogActionProps,
AlertDialogCancel as AlertDialogCancelPrimitive, type AlertDialogCancelProps,
type AlertDialogContentEmits, AlertDialogContent as AlertDialogContentPrimitive, type AlertDialogContentProps,
AlertDialogDescription as AlertDialogDescriptionPrimitive, type AlertDialogDescriptionProps,
AlertDialogOverlay as AlertDialogOverlayPrimitive, type AlertDialogOverlayProps,
AlertDialogPortal as AlertDialogPortalPrimitive,
AlertDialogRoot, AlertDialogTitle as AlertDialogTitlePrimitive,
type AlertDialogTitleProps, AlertDialogTrigger as AlertDialogTriggerPrimitive,
} from 'radix-vue'
import type { ParseEmits } from '@/utils'
import { cn, useEmitAsProps } from '@/utils'
import { buttonVariants } from '@/registry/default/ui/button'
export const AlertDialog = AlertDialogRoot
export const AlertDialogTrigger = AlertDialogTriggerPrimitive
export const AlertDialogPortal = AlertDialogPortalPrimitive
export const AlertDialogOverlay = defineComponent<AlertDialogOverlayProps>(
(props, { attrs, slots }) => {
return () => h(AlertDialogOverlayPrimitive,
{ class: cn('fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0', attrs.class), ...props },
slots,
)
}, { name: 'AlertDialogOverlay' },
)
export const AlertDialogContent = defineComponent<AlertDialogContentProps, ParseEmits<AlertDialogContentEmits>>(
(props, { emit, attrs, slots }) => {
const emitsAsProps = useEmitAsProps(emit)
return () => h(AlertDialogPortal, () => [
h(AlertDialogOverlay),
h(AlertDialogContentPrimitive, {
class: cn('fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full', attrs.class ?? ''),
...props,
...emitsAsProps,
}, slots),
])
}, { name: 'AlertDialogContent', emits: AlertDialogContentPrimitive.emits },
)
export const AlertDialogHeader = defineComponent(
(_props, { attrs, slots }) => {
return () => h('div',
{ class: cn('flex flex-col space-y-2 text-center sm:text-left', attrs.class) },
slots,
)
}, { name: 'AlertDialogHeader' },
)
export const AlertDialogFooter = defineComponent(
(_props, { attrs, slots }) => {
return () => h('div',
{ class: cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', attrs.class) },
slots,
)
}, { name: 'AlertDialogFooter' },
)
export const AlertDialogTitle = defineComponent<AlertDialogTitleProps>(
(props, { attrs, slots }) => {
return () => h(AlertDialogTitlePrimitive,
{ class: cn('text-lg font-semibold', attrs.class), ...props },
slots,
)
}, { name: 'AlertDialogTitle' },
)
export const AlertDialogDescription = defineComponent<AlertDialogDescriptionProps>(
(props, { attrs, slots }) => {
return () => h(AlertDialogDescriptionPrimitive,
{ class: cn('text-sm text-muted-foreground', attrs.class), ...props },
slots,
)
}, { name: 'AlertDialogDescription' },
)
export const AlertDialogAction = defineComponent<AlertDialogActionProps>(
(props, { attrs, slots }) => {
return () => h(AlertDialogActionPrimitive,
{ class: cn(buttonVariants(), attrs.class), ...props },
slots,
)
}, { name: 'AlertDialogAction' },
)
export const AlertDialogCancel = defineComponent<AlertDialogCancelProps>(
(props, { attrs, slots }) => {
return () => h(AlertDialogCancelPrimitive,
{ class: cn(buttonVariants({ variant: 'outline' }), 'mt-2 sm:mt-0', attrs.class), ...props },
slots,
)
}, { name: 'AlertDialogCancel' },
)

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import { type AlertDialogProps, AlertDialogRoot } from 'radix-vue'
const props = defineProps<AlertDialogProps>()
</script>
<template>
<AlertDialogRoot :default-open="props.defaultOpen" :open="props.open">
<slot />
</AlertDialogRoot>
</template>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import { AlertDialogAction, type AlertDialogActionProps } from 'radix-vue'
const props = defineProps<AlertDialogActionProps>()
</script>
<template>
<AlertDialogAction v-bind="props">
<slot />
</AlertDialogAction>
</template>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import { AlertDialogCancel, type AlertDialogCancelProps } from 'radix-vue'
const props = defineProps<AlertDialogCancelProps>()
</script>
<template>
<AlertDialogCancel v-bind="props">
<slot />
</AlertDialogCancel>
</template>

View File

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

View File

@ -0,0 +1,18 @@
<script setup lang="ts">
import {
AlertDialogDescription,
type AlertDialogDescriptionProps,
} from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<AlertDialogDescriptionProps & { class?: string }>()
</script>
<template>
<AlertDialogDescription
:class="cn('text-muted text-sm', props.class)"
:as-child="props.asChild"
>
<slot />
</AlertDialogDescription>
</template>

View File

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

View File

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

View File

@ -0,0 +1,15 @@
<script setup lang="ts">
import { AlertDialogTitle, type AlertDialogTitleProps } from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<AlertDialogTitleProps & { class?: string }>()
</script>
<template>
<AlertDialogTitle
:as-child="props.asChild"
:class="cn('text-lg text-foreground font-semibold', props.class)"
>
<slot />
</AlertDialogTitle>
</template>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import { AlertDialogTrigger, type AlertDialogTriggerProps } from 'radix-vue'
const props = defineProps<AlertDialogTriggerProps>()
</script>
<template>
<AlertDialogTrigger v-bind="props">
<slot />
</AlertDialogTrigger>
</template>

View File

@ -0,0 +1,9 @@
export { default as AlertDialog } from './AlertDialog.vue'
export { default as AlertDialogTrigger } from './AlertDialogTrigger.vue'
export { default as AlertDialogContent } from './AlertDialogContent.vue'
export { default as AlertDialogHeader } from './AlertDialogHeader.vue'
export { default as AlertDialogTitle } from './AlertDialogTitle.vue'
export { default as AlertDialogDescription } from './AlertDialogDescription.vue'
export { default as AlertDialogFooter } from './AlertDialogFooter.vue'
export { default as AlertDialogAction } from './AlertDialogAction.vue'
export { default as AlertDialogCancel } from './AlertDialogCancel.vue'

View File

@ -0,0 +1,78 @@
<script setup lang="ts">
import { computed } from 'vue'
import { cva } from 'class-variance-authority'
import { X } from 'lucide-vue-next'
import { Button } from '../button'
interface AlertProps {
variant?: 'primary' | 'success' | 'warning' | 'destructive' | 'info'
dismissible?: boolean
show?: boolean
}
const props = withDefaults(defineProps<AlertProps>(), {
variant: 'primary',
dismissible: false,
show: true,
})
const emits = defineEmits<{
(e: 'update:show', value: boolean): void
(e: 'close'): void
}>()
const alertClass = computed(() => {
return cva(
'relative w-full rounded-lg p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4',
{
variants: {
variant: {
primary: 'border border-border text-foreground',
success:
'border dark:border-green-600 dark:text-green-600 text-green-500 border-green-500',
warning:
'border dark:border-yellow-600 dark:text-yellow-600 text-yellow-500 border-yellow-500',
destructive: 'border border-destructive text-destructive',
info: 'border dark:border-blue-600 dark:text-blue-600 text-blue-500 border-blue-500',
},
},
},
)({
variant: props.variant,
})
})
const show = computed({
get() {
return props.show
},
set(value) {
emits('update:show', value)
},
})
</script>
<template>
<Transition
enter-active-class="transition ease-out duration-300"
enter-from-class="opacity-0"
enter-to-class="opacity-100"
leave-active-class="transition ease-in duration-300"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
>
<div v-if="show" :class="alertClass" role="alert">
<Button
v-if="props.dismissible"
variant="neutral"
class="absolute top-1.5 -right-0.5"
@click="emits('close')"
>
<X
class="w-4 h-4 text-muted transition-colors ease-in-out duration-300"
/>
</Button>
<slot />
</div>
</Transition>
</template>

View File

@ -0,0 +1,13 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
const props = defineProps({
class: String,
})
</script>
<template>
<div :class="cn('text-sm text-left leading-relaxed', props.class)">
<slot />
</div>
</template>

View File

@ -0,0 +1,5 @@
<template>
<h5 class="mb-1 font-medium leading-none tracking-tight">
<slot />
</h5>
</template>

View File

@ -0,0 +1,3 @@
export { default as Alert } from './Alert.vue'
export { default as AlertTitle } from './AlertTitle.vue'
export { default as AlertDescription } from './AlertDescription.vue'

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import { AspectRatio, type AspectRatioProps } from 'radix-vue'
const props = defineProps<AspectRatioProps>()
</script>
<template>
<AspectRatio v-bind="props">
<slot />
</AspectRatio>
</template>

View File

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

View File

@ -0,0 +1,46 @@
<script setup lang="ts">
import { computed } from 'vue'
import { AvatarRoot } from 'radix-vue'
import { cva } from 'class-variance-authority'
import { cn } from '@/lib/utils'
interface AvatarProps {
size?: 'sm' | 'base' | 'lg'
shape?: 'circle' | 'square'
class?: string
}
const props = withDefaults(defineProps<AvatarProps>(), {
name: 'Anonymous',
size: 'base',
shape: 'circle',
})
const avatarClass = computed(() => {
return cva(
'inline-flex items-center justify-center font-normal text-foregorund select-none shrink-0 bg-secondary overflow-hidden',
{
variants: {
size: {
sm: 'h-10 w-10 text-xs',
base: 'h-16 w-16 text-2xl',
lg: 'h-32 w-32 text-5xl',
},
shape: {
circle: 'rounded-full',
square: 'rounded-md',
},
},
},
)({
size: props.size,
shape: props.shape,
})
})
</script>
<template>
<AvatarRoot :class="cn(avatarClass, props.class)">
<slot />
</AvatarRoot>
</template>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import { AvatarFallback, type AvatarFallbackProps } from 'radix-vue'
const props = defineProps<AvatarFallbackProps>()
</script>
<template>
<AvatarFallback v-bind="props">
<slot />
</AvatarFallback>
</template>

View File

@ -0,0 +1,9 @@
<script setup lang="ts">
import { AvatarImage, type AvatarImageProps } from 'radix-vue'
const props = defineProps<AvatarImageProps>()
</script>
<template>
<AvatarImage v-bind="props" class="h-full w-full object-cover" />
</template>

View File

@ -0,0 +1,3 @@
export { default as Avatar } from './Avatar.vue'
export { default as AvatarImage } from './AvatarImage.vue'
export { default as AvatarFallback } from './AvatarFallback.vue'

View File

@ -0,0 +1,42 @@
<script setup lang="ts">
import { computed } from 'vue'
import { cva } from 'class-variance-authority'
import { cn } from '@/lib/utils'
interface BadgeProps {
variant?: 'primary' | 'secondary' | 'outline' | 'destructive'
as?: string
class?: string
}
const props = withDefaults(defineProps<BadgeProps>(), {
variant: 'primary',
as: 'span',
})
const badgeClass = computed(() => {
return cva(
'inline-flex items-center cursor-default text-xs font-semibold px-2.5 py-0.5 rounded-md transition-colors ease-in-out duration-300',
{
variants: {
variant: {
primary: 'bg-primary text-primary-foreground hover:bg-primary-hover',
secondary: 'bg-secondary text-foreground hover:bg-secondary-hover',
outline:
'border border-border text-foreground shadow-sm hover:bg-outline-hover',
destructive:
'bg-destructive text-destructive-foreground hover:bg-destructive-hover',
},
},
},
)({
variant: props.variant,
})
})
</script>
<template>
<component :is="props.as" :class="cn(badgeClass, props.class)">
<slot />
</component>
</template>

View File

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

View File

@ -0,0 +1,5 @@
<template>
<ol class="flex items-center whitespace-nowrap min-w-0">
<slot />
</ol>
</template>

View File

@ -0,0 +1,32 @@
<script setup lang="ts">
import { ChevronRight } from 'lucide-vue-next'
interface BreadCrumbItemProps {
path?: string
lastItem?: boolean
as?: string | object
}
const props = withDefaults(defineProps<BreadCrumbItemProps>(), {
as: 'span',
})
</script>
<template>
<li class="text-sm text-muted">
<component
:is="props.as"
:to="props.path"
class="flex items-center"
:class="{
'!font-semibold !text-foreground': $route.path === props.path,
}"
>
<slot />
<ChevronRight
v-if="!props.lastItem"
class="flex-shrink-0 h-3 w-3 text-muted mx-2"
/>
</component>
</li>
</template>

View File

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

View File

@ -1,41 +0,0 @@
import { defineComponent, h } from 'vue'
import { type VariantProps, cva } from 'class-variance-authority'
import { cn } from '@/utils'
export const buttonVariants = cva(
'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive:
'bg-destructive text-destructive-foreground hover:bg-destructive/90',
outline:
'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
secondary:
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
default: 'h-10 px-4 py-2',
sm: 'h-9 rounded-md px-3',
lg: 'h-11 rounded-md px-8',
icon: 'h-10 w-10',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
},
)
interface ButtonProps extends VariantProps<typeof buttonVariants> {}
export const Button = defineComponent<ButtonProps>(
(props, { attrs, slots }) => {
return () => h('button', { class: cn(buttonVariants(props), attrs.class ?? '') }, slots)
},
{ name: 'Button', props: ['size', 'variant'] },
)

View File

@ -0,0 +1,64 @@
<script setup lang="ts">
import { computed } from 'vue'
import { cva } from 'class-variance-authority'
import { cn } from '@/lib/utils'
interface ButtonProps {
class?: string
variant?:
| 'primary'
| 'secondary'
| 'destructive'
| 'outline'
| 'ghost'
| 'link'
| 'neutral'
disabled?: boolean | undefined
as?: string
}
const props = withDefaults(defineProps<ButtonProps>(), {
variant: 'primary',
disabled: false,
as: 'button',
})
const buttonClass = computed(() => {
return cva(
'inline-flex items-center justify-center text-sm px-4 py-2.5 rounded-md font-medium transition-colors ease-in-out duration-300',
{
variants: {
variant: {
primary:
'bg-primary text-primary-foreground enabled:hover:bg-primary-hover',
secondary:
'bg-secondary text-secondary-foreground enabled:hover:bg-secondary-hover',
destructive:
'bg-destructive text-destructive-foreground hover:bg-destructive-hover',
outline:
'border border-border text-foreground shadow-sm hover:bg-outline-hover',
ghost: 'text-foreground hover:bg-outline-hover',
link: 'text-foreground hover:underline hover:underline-offset-4',
neutral: '',
},
disabled: {
true: '!opacity-50 !cursor-not-allowed',
},
},
},
)({
variant: props.variant,
disabled: props.disabled,
})
})
</script>
<template>
<component
:is="props.as"
:class="cn(buttonClass, props.class)"
:disabled="props.disabled"
>
<slot />
</component>
</template>

View File

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

View File

@ -0,0 +1,63 @@
<script setup lang="ts">
import { useDark } from '@vueuse/core'
import { Calendar } from 'v-calendar'
import 'v-calendar/style.css'
const isDark = useDark()
</script>
<template>
<Calendar :is-dark="isDark" borderless trim-weeks expanded />
</template>
<style>
:root {
--vc-font-family: "Inter", sans-serif;
--vc-rounded-full: var(--radius);
--vc-font-bold: 500;
--vc-font-semibold: 600;
--vc-text-lg: 14px;
}
.vc-light,
.vc-dark {
--vc-bg: var(--background);
--vc-border: var(--border);
--vc-focus-ring: 0 0 0 3px rgba(0, 0, 0, 0.2);
--vc-weekday-color: var(--muted);
--vc-popover-content-color: var(--muted);
--vc-popover-content-bg: var(--background);
--vc-popover-content-border: var(--border);
&.vc-attr,
& .vc-attr {
--vc-content-color: var(--primary);
--vc-highlight-outline-bg: var(--primary);
--vc-highlight-outline-border: var(--primary);
--vc-highlight-outline-content-color: var(--primary-foreground);
--vc-highlight-light-bg: var(
--vc-accent-200
); /* Highlighted color between two dates */
--vc-highlight-light-content-color: var(--secondary-foreground);
--vc-highlight-solid-bg: var(--primary);
--vc-highlight-solid-content-color: var(--primary-foreground);
--vc-dot-bg: var(--primary);
--vc-bar-bg: var(--primary);
}
}
.vc-blue {
--vc-accent-200: var(--secondary);
--vc-accent-400: var(--primary);
--vc-accent-500: var(--primary);
--vc-accent-600: var(--primary);
}
.dark {
.vc-blue {
--vc-accent-200: var(--secondary);
--vc-accent-400: var(--primary);
--vc-accent-500: var(--secondary);
}
}
</style>

View File

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

View File

@ -0,0 +1,23 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
const props = defineProps({
class: {
type: String,
default: '',
},
})
</script>
<template>
<div
:class="
cn(
'bg-background text-foreground border border-border rounded-xl shadow-md',
props.class,
)
"
>
<slot />
</div>
</template>

View File

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

View File

@ -0,0 +1,16 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
const props = defineProps({
class: {
type: String,
default: '',
},
})
</script>
<template>
<p :class="cn('text-muted text-sm', props.class)">
<slot />
</p>
</template>

View File

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

View File

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

View File

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

View File

@ -0,0 +1,6 @@
export { default as Card } from './Card.vue'
export { default as CardHeader } from './CardHeader.vue'
export { default as CardTitle } from './CardTitle.vue'
export { default as CardDescription } from './CardDescription.vue'
export { default as CardContent } from './CardContent.vue'
export { default as CardFooter } from './CardFooter.vue'

View File

@ -0,0 +1,42 @@
<script setup lang="ts">
import RadixIconsCheck from '~icons/radix-icons/check'
interface CheckBoxProps {
id?: string
modelValue?: boolean
required?: boolean
disabled?: boolean
invalid?: boolean
checked?: boolean
}
const props = defineProps<CheckBoxProps>()
const emit = defineEmits(['update:modelValue'])
</script>
<template>
<div class="flex items-center">
<input
:id="props.id"
type="checkbox"
:value="props.modelValue"
:required="props.required"
:disabled="props.disabled"
:class="{
'ring-destructive-light dark:ring-destructive': props.invalid,
'!cursor-not-allowed opacity-50': props.disabled,
}"
:checked="props.checked"
class="w-4 h-4 peer cursor-pointer shrink-0 relative checked:bg-primary appearance-none text-foreground border border-primary rounded"
@input="
($event) =>
emit('update:modelValue', ($event.target as HTMLInputElement).checked)
"
>
<RadixIconsCheck
class="absolute pointer-events-none hidden peer-checked:block w-4 h-3 text-background"
/>
</div>
</template>

View File

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

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import { CollapsibleRoot, type CollapsibleRootProps } from 'radix-vue'
const props = defineProps<CollapsibleRootProps>()
</script>
<template>
<CollapsibleRoot v-bind="props">
<slot />
</CollapsibleRoot>
</template>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import { CollapsibleContent, type CollapsibleContentProps } from 'radix-vue'
const props = defineProps<CollapsibleContentProps>()
</script>
<template>
<CollapsibleContent v-bind="props">
<slot />
</CollapsibleContent>
</template>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import { CollapsibleTrigger, type CollapsibleTriggerProps } from 'radix-vue'
const props = defineProps<CollapsibleTriggerProps>()
</script>
<template>
<CollapsibleTrigger v-bind="props">
<slot />
</CollapsibleTrigger>
</template>

View File

@ -0,0 +1,3 @@
export { default as Collapsible } from './Collapsible.vue'
export { default as CollapsibleTrigger } from './CollapsibleTrigger.vue'
export { default as CollapsibleContent } from './CollapsibleContent.vue'

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import { ContextMenuRoot, type ContextMenuRootProps } from 'radix-vue'
const props = defineProps<ContextMenuRootProps>()
</script>
<template>
<ContextMenuRoot v-bind="props">
<slot />
</ContextMenuRoot>
</template>

View File

@ -0,0 +1,34 @@
<script setup lang="ts">
import {
ContextMenuCheckboxItem,
type ContextMenuCheckboxItemEmits,
type ContextMenuCheckboxItemProps,
ContextMenuItemIndicator,
} from 'radix-vue'
import { cn } from '@/lib/utils'
import RadixIconsCheck from '~icons/radix-icons/check'
const props = defineProps<ContextMenuCheckboxItemProps & { class?: string }>()
const emits = defineEmits<ContextMenuCheckboxItemEmits>()
</script>
<template>
<ContextMenuCheckboxItem
v-bind="props"
:class="[
cn(
'flex relative items-center rounded-md transition-colors data-[disabled]:opacity-50 data-[disabled]:pointer-events-none data-[highlighted]:bg-outline-hover pl-7 py-1.5 text-sm outline-none select-none cursor-default',
props.class,
),
]"
@update:checked="emits('update:checked', $event)"
>
<ContextMenuItemIndicator
class="absolute left-1.5 inline-flex w-4 h-4 items-center justify-center"
>
<RadixIconsCheck />
</ContextMenuItemIndicator>
<slot />
</ContextMenuCheckboxItem>
</template>

View File

@ -0,0 +1,31 @@
<script setup lang="ts">
import {
ContextMenuContent,
type ContextMenuContentEmits,
type ContextMenuContentProps,
ContextMenuPortal,
} from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<ContextMenuContentProps & { class?: string }>()
const emits = defineEmits<ContextMenuContentEmits>()
</script>
<template>
<ContextMenuPortal force-mount>
<ContextMenuContent
:loop="props.loop"
:align-offset="props.alignOffset"
:as-child="props.asChild"
:class="[
cn(
'bg-background focus:outline-none outline-none border border-border p-1 z-50 min-w-[8rem] rounded-md shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
props.class,
),
]"
>
<slot />
</ContextMenuContent>
</ContextMenuPortal>
</template>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import { ContextMenuGroup, type ContextMenuGroupProps } from 'radix-vue'
const props = defineProps<ContextMenuGroupProps>()
</script>
<template>
<ContextMenuGroup v-bind="props">
<slot />
</ContextMenuGroup>
</template>

View File

@ -0,0 +1,27 @@
<script setup lang="ts">
import {
ContextMenuItem,
type ContextMenuItemEmits,
type ContextMenuItemProps,
} from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<ContextMenuItemProps & { class?: string }>()
const emits = defineEmits<ContextMenuItemEmits>()
</script>
<template>
<ContextMenuItem
v-bind="props"
:class="[
cn(
'flex items-center rounded-md transition-colors data-[disabled]:opacity-50 data-[disabled]:pointer-events-none focus:bg-outline-hover px-2 py-1.5 text-sm outline-none select-none cursor-default',
props.class,
),
]"
@select="emits('select', $event)"
>
<slot />
</ContextMenuItem>
</template>

View File

@ -0,0 +1,13 @@
<script setup lang="ts">
import { ContextMenuLabel, type ContextMenuLabelProps } from 'radix-vue'
const props = defineProps<ContextMenuLabelProps>()
</script>
<template>
<div class="px-2 py-1.5 text-sm font-semibold">
<ContextMenuLabel v-bind="props">
<slot />
</ContextMenuLabel>
</div>
</template>

View File

@ -0,0 +1,20 @@
<script setup lang="ts">
import {
ContextMenuRadioGroup,
type ContextMenuRadioGroupEmits,
type ContextMenuRadioGroupProps,
} from 'radix-vue'
const props = defineProps<ContextMenuRadioGroupProps>()
const emits = defineEmits<ContextMenuRadioGroupEmits>()
</script>
<template>
<ContextMenuRadioGroup
v-bind="props"
@update:model-value="emits('update:modelValue', $event)"
>
<slot />
</ContextMenuRadioGroup>
</template>

View File

@ -0,0 +1,34 @@
<script setup lang="ts">
import {
ContextMenuItemIndicator,
ContextMenuRadioItem,
type ContextMenuRadioItemEmits,
type ContextMenuRadioItemProps,
} from 'radix-vue'
import { cn } from '@/lib/utils'
import RiCheckboxBlankCircleFill from '~icons/ri/checkbox-blank-circle-fill'
const props = defineProps<ContextMenuRadioItemProps & { class?: string }>()
const emits = defineEmits<ContextMenuRadioItemEmits>()
</script>
<template>
<ContextMenuRadioItem
v-bind="props"
:class="[
cn(
'flex relative items-center rounded-md transition-colors data-[disabled]:opacity-50 data-[disabled]:pointer-events-none data-[highlighted]:bg-outline-hover pl-7 py-1.5 text-sm outline-none select-none cursor-default',
props.class,
),
]"
@select="emits('select', $event)"
>
<ContextMenuItemIndicator
class="absolute left-2 inline-flex w-2 h-2 items-center justify-center"
>
<RiCheckboxBlankCircleFill class="text-foreground" />
</ContextMenuItemIndicator>
<slot />
</ContextMenuRadioItem>
</template>

View File

@ -0,0 +1,12 @@
<script setup lang="ts">
import {
ContextMenuSeparator,
type ContextMenuSeparatorProps,
} from 'radix-vue'
const props = defineProps<ContextMenuSeparatorProps>()
</script>
<template>
<ContextMenuSeparator v-bind="props" class="-mx-1 my-1 h-px bg-secondary" />
</template>

View File

@ -0,0 +1,5 @@
<template>
<div class="text-xxs ml-auto tracking-widest opacity-50">
<slot />
</div>
</template>

View File

@ -0,0 +1,17 @@
<script setup lang="ts">
import {
ContextMenuSub,
type ContextMenuSubEmits,
type ContextMenuSubProps,
} from 'radix-vue'
const props = defineProps<ContextMenuSubProps>()
const emits = defineEmits<ContextMenuSubEmits>()
</script>
<template>
<ContextMenuSub v-bind="props" @update:open="emits('update:open', $event)">
<slot />
</ContextMenuSub>
</template>

View File

@ -0,0 +1,33 @@
<script setup lang="ts">
import {
ContextMenuPortal,
ContextMenuSubContent,
type DropdownMenuSubContentEmits,
type DropdownMenuSubContentProps,
} from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<DropdownMenuSubContentProps & { class?: string }>()
const emits = defineEmits<DropdownMenuSubContentEmits>()
</script>
<template>
<ContextMenuPortal force-mount>
<ContextMenuSubContent
:loop="props.loop"
:align-offset="props.alignOffset"
:side="props.side"
:side-offset="props.sideOffset"
:as-child="props.asChild"
:class="
cn(
'bg-background focus:outline-none outline-none text-foreground border border-border p-1 z-50 min-w-[10rem] rounded-md shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
props.class,
)
"
>
<slot />
</ContextMenuSubContent>
</ContextMenuPortal>
</template>

View File

@ -0,0 +1,23 @@
<script setup lang="ts">
import {
ContextMenuSubTrigger,
type ContextMenuSubTriggerProps,
} from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<ContextMenuSubTriggerProps & { class?: string }>()
</script>
<template>
<ContextMenuSubTrigger
v-bind="props"
:class="[
cn(
'flex items-center rounded-md transition-colors data-[disabled]:opacity-50 data-[disabled]:pointer-events-none data-[highlighted]:bg-outline-hover px-2 py-1.5 text-sm outline-none select-none cursor-default',
props.class,
),
]"
>
<slot />
</ContextMenuSubTrigger>
</template>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import { ContextMenuTrigger, type ContextMenuTriggerProps } from 'radix-vue'
const props = defineProps<ContextMenuTriggerProps>()
</script>
<template>
<ContextMenuTrigger v-bind="props">
<slot />
</ContextMenuTrigger>
</template>

View File

@ -0,0 +1,14 @@
export { default as ContextMenu } from './ContextMenu.vue'
export { default as ContextMenuTrigger } from './ContextMenuTrigger.vue'
export { default as ContextMenuContent } from './ContextMenuContent.vue'
export { default as ContextMenuGroup } from './ContextMenuGroup.vue'
export { default as ContextMenuRadioGroup } from './ContextMenuRadioGroup.vue'
export { default as ContextMenuItem } from './ContextMenuItem.vue'
export { default as ContextMenuCheckboxItem } from './ContextMenuCheckboxItem.vue'
export { default as ContextMenuRadioItem } from './ContextMenuRadioItem.vue'
export { default as ContextMenuShortcut } from './ContextMenuShortcut.vue'
export { default as ContextMenuSeparator } from './ContextMenuSeparator.vue'
export { default as ContextMenuLabel } from './ContextMenuLabel.vue'
export { default as ContextMenuSub } from './ContextMenuSub.vue'
export { default as ContextMenuSubTrigger } from './ContextMenuSubTrigger.vue'
export { default as ContextMenuSubContent } from './ContextMenuSubContent.vue'

View File

@ -0,0 +1,327 @@
<script setup lang="ts">
import {
type ColumnDef,
FlexRender,
type SortingState,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useVueTable,
} from '@tanstack/vue-table'
import { computed, ref } from 'vue'
import {
ArrowDownNarrowWide,
ArrowUpDown,
ArrowUpNarrowWide,
ChevronLeft,
ChevronRight,
ChevronsLeft,
ChevronsRight,
X,
} from 'lucide-vue-next'
import {
Table,
TableBody,
TableCell,
TableEmpty,
TableHead,
TableHeader,
TableRow,
} from '../table'
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '../dropdown-menu'
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
} from '../select'
import { Input } from '../input'
import { Button } from '../button'
import RadixIconsMixerHorizontal from '~icons/radix-icons/mixer-horizontal'
interface tableProps {
data: any[]
columns: ColumnDef<any>[]
}
const props = defineProps<tableProps>()
const sorting = ref<SortingState>([])
const globalFilter = ref('')
const columnVisibility = ref({})
const rowSelection = ref({})
const pageSizes = computed(() => [10, 20, 30, 40, 50])
const table = useVueTable({
data: props.data,
columns: props.columns,
state: {
get sorting() {
return sorting.value
},
get globalFilter() {
return globalFilter.value
},
get columnVisibility() {
return columnVisibility.value
},
get rowSelection() {
return rowSelection.value
},
},
onSortingChange: (updaterOrValue) => {
sorting.value
= typeof updaterOrValue === 'function'
? updaterOrValue(sorting.value)
: updaterOrValue
},
onGlobalFilterChange: (updaterOrValue) => {
globalFilter.value
= typeof updaterOrValue === 'function'
? updaterOrValue(globalFilter.value)
: updaterOrValue
},
onRowSelectionChange: (updaterOrValue) => {
rowSelection.value
= typeof updaterOrValue === 'function'
? updaterOrValue(rowSelection.value)
: updaterOrValue
},
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getFilteredRowModel: getFilteredRowModel(),
})
function toggleColumnVisibility(column: any) {
columnVisibility.value = {
...columnVisibility.value,
[column.id]: !column.getIsVisible(),
}
}
const pageSize = computed({
get() {
return table.getState().pagination.pageSize.toString()
},
set(value) {
table.setPageSize(Number(value))
},
})
</script>
<template>
<div class="flex items-center justify-between">
<div class="flex flex-1 items-center space-x-2">
<div class="w-full max-w-[18rem]">
<Input
v-model:value="globalFilter"
placeholder="Filter tasks..."
class="h-8"
/>
</div>
<Button
v-if="globalFilter"
variant="ghost"
class="h-8"
@click="globalFilter = ''"
>
<span class="text-xs">Reset</span>
<X class="w-4 h-4 ml-1" />
</Button>
</div>
<DropdownMenu>
<DropdownMenuTrigger>
<Button variant="outline" class="h-8">
<RadixIconsMixerHorizontal class="w-3.5 h-3.5 mr-2" />
<span class="text-xs">View</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" class="w-40">
<DropdownMenuLabel> Toggle Columns </DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuCheckboxItem
v-for="column in table
.getAllColumns()
.filter((column) => !column.getCanHide())"
:key="column.id"
:checked="column.getIsVisible()"
@update:checked="toggleColumnVisibility(column)"
>
<span class="text-sm">{{ column.columnDef.header }}</span>
</DropdownMenuCheckboxItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
<Table class="mt-4 rounded-md border border-border">
<TableHeader>
<TableRow
v-for="headerGroup in table.getHeaderGroups()"
:key="headerGroup.id"
>
<TableHead
v-for="header in headerGroup.headers"
:key="header.id"
class="h-10 px-2.5"
:col-span="header.colSpan"
>
<template v-if="!header.isPlaceholder">
<div class="flex items-center space-x-2">
<Button
v-if="header.column.getCanSort()"
variant="ghost"
class="h-8 -translate-x-3"
@click="header.column.getToggleSortingHandler()?.($event)"
>
<FlexRender
:render="header.column.columnDef.header"
:props="header.getContext()"
/>
<ArrowUpDown
v-if="
header.column.getCanSort() && !header.column.getIsSorted()
"
class="w-3.5 h-3.5 ml-1.5 text-muted"
/>
<ArrowUpNarrowWide
v-if="header.column.getIsSorted() === 'asc'"
class="w-3.5 h-3.5 ml-1.5 text-muted"
/>
<ArrowDownNarrowWide
v-if="header.column.getIsSorted() === 'desc'"
class="w-3.5 h-3.5 ml-1.5 text-muted"
/>
</Button>
<span
v-else
class="flex items-center justify-center h-8 text-foreground"
>
<FlexRender
:render="header.column.columnDef.header"
:props="header.getContext()"
/>
</span>
</div>
</template>
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow
v-for="row in table.getRowModel().rows"
:key="row.id"
:class="row.getIsSelected() ? 'bg-outline-hover' : ''"
>
<TableCell
v-for="cell in row.getVisibleCells()"
:key="cell.id"
class="p-2.5"
>
<FlexRender
:render="cell.column.columnDef.cell"
:props="cell.getContext()"
/>
</TableCell>
</TableRow>
<TableEmpty
v-if="table.getRowModel().rows.length === 0"
:colspan="table.getAllLeafColumns().length"
>
No data available.
</TableEmpty>
</TableBody>
</Table>
<div class="flex items-center justify-end px-2 my-6">
<div class="flex-1 text-sm text-muted">
<span>
{{ table.getFilteredSelectedRowModel().rows.length }} of {{ " " }}
{{ table.getFilteredRowModel().rows.length }} row(s) selected
</span>
</div>
<div class="flex items-center space-x-6 lg:space-x-8">
<div class="md:flex items-center space-x-2 hidden">
<p class="text-sm font-medium text-foreground">
Rows per page:
</p>
<Select v-model="pageSize">
<SelectTrigger class="h-8 w-[70px]">
{{ table.getState().pagination.pageSize }}
</SelectTrigger>
<SelectContent side="top" align="start">
<SelectGroup>
<SelectItem
v-for="pageSize in pageSizes"
:key="pageSize"
:value="`${pageSize}`"
>
{{ pageSize }}
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
<div
class="flex text-foreground items-center justify-center text-sm font-medium"
>
Page {{ table.getState().pagination.pageIndex + 1 }} of
{{ table.getPageCount() }}
</div>
<div class="flex items-center space-x-2">
<Button
variant="outline"
title="First page"
class="h-8 w-8 p-0"
:disabled="!table.getCanPreviousPage()"
@click="table.setPageIndex(0)"
>
<ChevronsLeft class="w-4 h-4" />
</Button>
<Button
variant="outline"
title="Previous page"
class="h-8 w-8 p-0"
:disabled="!table.getCanPreviousPage()"
@click="table.previousPage()"
>
<ChevronLeft class="w-4 h-4" />
</Button>
<Button
variant="outline"
title="Next page"
class="h-8 w-8 p-0"
:disabled="!table.getCanNextPage()"
@click="table.nextPage()"
>
<ChevronRight class="w-4 h-4" />
</Button>
<Button
variant="outline"
title="Last page"
class="h-8 w-8 p-0"
:disabled="!table.getCanNextPage()"
@click="table.setPageIndex(table.getPageCount() - 1)"
>
<ChevronsRight class="w-4 h-4" />
</Button>
</div>
</div>
</div>
</template>

View File

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

View File

@ -0,0 +1,63 @@
<script setup lang="ts">
import { useDark } from '@vueuse/core'
import { DatePicker } from 'v-calendar'
import 'v-calendar/style.css'
const isDark = useDark()
</script>
<template>
<DatePicker :is-dark="isDark" borderless trim-weeks expanded />
</template>
<style>
:root {
--vc-font-family: "Inter", sans-serif;
--vc-rounded-full: var(--radius);
--vc-font-bold: 500;
--vc-font-semibold: 600;
--vc-text-lg: 14px;
}
.vc-light,
.vc-dark {
--vc-bg: var(--background);
--vc-border: var(--border);
--vc-focus-ring: 0 0 0 3px rgba(0, 0, 0, 0.2);
--vc-weekday-color: var(--muted);
--vc-popover-content-color: var(--muted);
--vc-popover-content-bg: var(--background);
--vc-popover-content-border: var(--border);
&.vc-attr,
& .vc-attr {
--vc-content-color: var(--primary);
--vc-highlight-outline-bg: var(--primary);
--vc-highlight-outline-border: var(--primary);
--vc-highlight-outline-content-color: var(--primary-foreground);
--vc-highlight-light-bg: var(
--vc-accent-200
); /* Highlighted color between two dates */
--vc-highlight-light-content-color: var(--secondary-foreground);
--vc-highlight-solid-bg: var(--primary);
--vc-highlight-solid-content-color: var(--primary-foreground);
--vc-dot-bg: var(--primary);
--vc-bar-bg: var(--primary);
}
}
.vc-blue {
--vc-accent-200: var(--secondary);
--vc-accent-400: var(--primary);
--vc-accent-500: var(--primary);
--vc-accent-600: var(--primary);
}
.dark {
.vc-blue {
--vc-accent-200: var(--secondary);
--vc-accent-400: var(--primary);
--vc-accent-500: var(--secondary);
}
}
</style>

View File

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

View File

@ -1,82 +0,0 @@
import { defineComponent, h } from 'vue'
import {
DialogClose, type DialogContentEmits, DialogContent as DialogContentPrimitive, type DialogContentProps,
DialogDescription as DialogDescriptionPrimitive, type DialogDescriptionProps,
DialogOverlay as DialogOverlayPrimitive, type DialogOverlayProps,
DialogPortal as DialogPortalPrimitive, DialogRoot,
DialogTitle as DialogTitlePrimitive, type DialogTitleProps,
DialogTrigger as DialogTriggerPrimitive,
} from 'radix-vue'
import { X } from 'lucide-vue-next'
import type { ParseEmits } from '@/utils'
import { cn, useEmitAsProps } from '@/utils'
export const Dialog = DialogRoot
export const DialogTrigger = DialogTriggerPrimitive
export const DialogPortal = DialogPortalPrimitive
export const DialogOverlay = defineComponent<DialogOverlayProps>(
(props, { attrs, slots }) => {
return () => h(DialogOverlayPrimitive,
{ class: cn('fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0', attrs.class), ...props },
slots,
)
}, { name: 'DialogOverlay' },
)
export const DialogContent = defineComponent<DialogContentProps, ParseEmits<DialogContentEmits>>(
(props, { emit, attrs, slots }) => {
const emitsAsProps = useEmitAsProps(emit)
return () => h(DialogPortal, () => [
h(DialogOverlay),
h(DialogContentPrimitive, {
class: cn('fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full', attrs.class ?? ''),
...props,
...emitsAsProps,
}, () => [
slots.default(),
h(DialogClose,
{ class: 'absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground' },
() => [h(X, { class: 'h-4 w-4' }), h('span', { class: 'sr-only' }, 'Close')],
),
]),
])
}, { name: 'DialogContent', emits: DialogContentPrimitive.emits },
)
export const DialogHeader = defineComponent(
(_props, { attrs, slots }) => {
return () => h('div',
{ class: cn('flex flex-col space-y-1.5 text-center sm:text-left', attrs.class) },
slots,
)
}, { name: 'DialogHeader' },
)
export const DialogFooter = defineComponent(
(_props, { attrs, slots }) => {
return () => h('div',
{ class: cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', attrs.class) },
slots,
)
}, { name: 'DialogFooter' },
)
export const DialogTitle = defineComponent<DialogTitleProps>(
(props, { attrs, slots }) => {
return () => h(DialogTitlePrimitive,
{ class: cn('text-lg font-semibold leading-none tracking-tight', attrs.class), ...props },
slots,
)
}, { name: 'DialogTitle' },
)
export const DialogDescription = defineComponent<DialogDescriptionProps>(
(props, { attrs, slots }) => {
return () => h(DialogDescriptionPrimitive,
{ class: cn('text-lg font-semibold leading-none tracking-tight', attrs.class), ...props },
slots,
)
}, { name: 'DialogDescription' },
)

View File

@ -0,0 +1,9 @@
<script setup lang="ts">
import { DialogRoot } from 'radix-vue'
</script>
<template>
<DialogRoot>
<slot />
</DialogRoot>
</template>

View File

@ -0,0 +1,38 @@
<script setup lang="ts">
import {
DialogClose,
DialogContent,
type DialogContentProps,
DialogOverlay,
DialogPortal,
} from 'radix-vue'
import { X } from 'lucide-vue-next'
import { cn } from '@/lib/utils'
const props = defineProps<DialogContentProps & { class?: string }>()
</script>
<template>
<DialogPortal>
<DialogOverlay
class="fixed inset-0 z-50 bg-white/80 dark:bg-gray-950/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0"
/>
<DialogContent
:as-child="props.asChild"
:class="
cn(
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full',
props.class,
)
"
>
<slot />
<DialogClose
class="absolute top-4 right-4 p-0.5 transition-colors rounded-md hover:bg-secondary"
>
<X class="w-4 h-4 text-muted" />
</DialogClose>
</DialogContent>
</DialogPortal>
</template>

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