Merge remote-tracking branch 'origin/dev' into charting
This commit is contained in:
commit
73f1d8f2f0
1
.vscode/extensions.json
vendored
1
.vscode/extensions.json
vendored
|
|
@ -1,7 +1,6 @@
|
|||
{
|
||||
"recommendations": [
|
||||
"Vue.volar",
|
||||
"Vue.vscode-typescript-vue-plugin",
|
||||
"dbaeumer.vscode-eslint"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ export default defineConfig({
|
|||
css: {
|
||||
postcss: {
|
||||
plugins: [
|
||||
tailwind(),
|
||||
tailwind() as any,
|
||||
autoprefixer(),
|
||||
],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ const { copy, copied } = useClipboard()
|
|||
|
||||
const codeRef = ref<HTMLElement>()
|
||||
async function copyCode() {
|
||||
await copy(codeRef.value?.innerText.replace(/\u00A0/g, ' ') ?? '')
|
||||
await copy(codeRef.value?.textContent?.replace(/\u00A0/g, ' ') ?? '')
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,11 @@ import ArrowRightIcon from '~icons/radix-icons/arrow-right'
|
|||
const { path } = toRefs(useRoute())
|
||||
|
||||
const examples = [
|
||||
{
|
||||
name: 'Mail',
|
||||
href: '/examples/mail',
|
||||
code: 'https://github.com/radix-vue/shadcn-vue/tree/dev/apps/www/src/examples/mail',
|
||||
},
|
||||
{
|
||||
name: 'Dashboard',
|
||||
href: '/examples/dashboard',
|
||||
|
|
@ -58,7 +63,7 @@ const currentExample = computed(() => examples.find(ex => path.value.startsWith(
|
|||
:href="example.href"
|
||||
:class="cn(
|
||||
'flex items-center px-4',
|
||||
path?.startsWith(example.href) || (path === '/' && example.name === 'Dashboard')
|
||||
path?.startsWith(example.href) || (path === '/' && example.name === 'Mail')
|
||||
? 'font-bold text-primary'
|
||||
: 'font-medium text-muted-foreground',
|
||||
)"
|
||||
|
|
|
|||
54
apps/www/.vitepress/theme/components/InlineThemePicker.vue
Normal file
54
apps/www/.vitepress/theme/components/InlineThemePicker.vue
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
<script lang="ts" setup>
|
||||
import type { Color } from '../types/colors'
|
||||
import { useConfigStore } from '@/stores/config'
|
||||
import { colors } from '@/lib/registry'
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/lib/registry/new-york/ui/tooltip'
|
||||
import RadixIconsCheck from '~icons/radix-icons/check'
|
||||
|
||||
defineProps<{
|
||||
allColors: Color[]
|
||||
}>()
|
||||
|
||||
const { theme, setTheme } = useConfigStore()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<TooltipProvider
|
||||
v-for="(color, index) in allColors.slice(0, 5)"
|
||||
:key="index"
|
||||
>
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
<button
|
||||
:key="index"
|
||||
class="flex h-9 w-9 items-center justify-center rounded-full border-2 border-border text-xs"
|
||||
:class="
|
||||
color === theme
|
||||
? 'border-primary'
|
||||
: 'border-transparent'
|
||||
"
|
||||
@click="setTheme(color)"
|
||||
>
|
||||
<span
|
||||
class="flex h-6 w-6 items-center justify-center rounded-full"
|
||||
:style="{ backgroundColor: colors[color][6].rgb }"
|
||||
>
|
||||
<RadixIconsCheck
|
||||
v-if="color === theme"
|
||||
class="h-4 w-4 text-white"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
align="center"
|
||||
:side-offset="1"
|
||||
class="capitalize bg-zinc-900 text-zinc-50"
|
||||
>
|
||||
{{ allColors[index] }}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -11,7 +11,7 @@ import { buttonVariants } from '@/lib/registry/new-york/ui/button'
|
|||
import { Separator } from '@/lib/registry/new-york/ui/separator'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
import DashboardExample from '@/examples/dashboard/Example.vue'
|
||||
import MailExample from '@/examples/mail/Example.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -55,17 +55,17 @@ import DashboardExample from '@/examples/dashboard/Example.vue'
|
|||
<ExamplesNav />
|
||||
<section class="space-y-8 overflow-hidden rounded-lg border-2 border-primary dark:border-muted md:hidden">
|
||||
<VPImage
|
||||
alt="Dashboard"
|
||||
alt="Mail"
|
||||
width="1280"
|
||||
height="866" class="block" :image="{
|
||||
dark: '/examples/dashboard-dark.png',
|
||||
light: '/examples/dashboard-light.png',
|
||||
dark: '/examples/mail-dark.png',
|
||||
light: '/examples/mail-light.png',
|
||||
}"
|
||||
/>
|
||||
</section>
|
||||
<section class="hidden md:block">
|
||||
<div class="overflow-hidden rounded-[0.5rem] border bg-background shadow">
|
||||
<DashboardExample />
|
||||
<MailExample />
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<a href="/" class="mr-6 flex items-center space-x-2">
|
||||
<a href="/" class="mr-4 md:mr-2 xl:mr-6 flex items-center lg:space-x1 xl:space-x-2">
|
||||
<svg class="h-6 w-6" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_102_1338)">
|
||||
<path d="M208 128L128 208" stroke="#41B883" stroke-width="16" stroke-linecap="round" stroke-linejoin="round" />
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
</defs>
|
||||
</svg>
|
||||
|
||||
<span class="font-bold ">
|
||||
<span class="font-bold">
|
||||
shadcn-vue
|
||||
</span>
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ import Logo from './Logo.vue'
|
|||
import { Sheet, SheetContent, SheetTrigger } from '@/lib/registry/default/ui/sheet'
|
||||
import { Button } from '@/lib/registry/default/ui/button'
|
||||
import { ScrollArea } from '@/lib/registry/default/ui/scroll-area'
|
||||
import { Badge } from '@/lib/registry/default/ui/badge'
|
||||
import ViewVerticalIcon from '~icons/radix-icons/view-vertical'
|
||||
|
||||
const open = ref(false)
|
||||
</script>
|
||||
|
|
@ -18,7 +16,35 @@ const open = ref(false)
|
|||
variant="ghost"
|
||||
class="mr-2 px-2 text-base flex-shrink-0 hover:bg-transparent focus-visible:bg-transparent focus-visible:ring-0 focus-visible:ring-offset-0 md:hidden"
|
||||
>
|
||||
<ViewVerticalIcon class="h-5 w-5" />
|
||||
<svg
|
||||
strokeWidth="1.5"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5"
|
||||
>
|
||||
<path
|
||||
d="M3 5H11"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M3 12H16"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M3 19H21"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
<span class="sr-only">Toggle Menu</span>
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import type { TableOfContents, TableOfContentsItem } from '../types/docs'
|
|||
import TableOfContentTree from './TableOfContentTree.vue'
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/lib/registry/default/ui/collapsible'
|
||||
import { buttonVariants } from '@/lib/registry/default/ui/button'
|
||||
import { ScrollArea } from '@/lib/registry/default/ui/scroll-area'
|
||||
|
||||
const headers = shallowRef<TableOfContents>()
|
||||
|
||||
|
|
@ -22,7 +23,7 @@ function getHeadingsWithHierarchy(divId: string) {
|
|||
headings.forEach((heading: HTMLHeadingElement) => {
|
||||
const level = Number.parseInt(heading.tagName.charAt(1))
|
||||
if (!heading.id) {
|
||||
const newId = heading.innerText
|
||||
const newId = heading.textContent
|
||||
.replaceAll(/[^a-zA-Z0-9 ]/g, '')
|
||||
.replaceAll(' ', '-')
|
||||
.toLowerCase()
|
||||
|
|
@ -55,11 +56,15 @@ onContentUpdated(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-2 hidden xl:block">
|
||||
<p class="font-medium">
|
||||
On This Page
|
||||
</p>
|
||||
<TableOfContentTree :tree="headers" :level="1" />
|
||||
<div class="hidden xl:block">
|
||||
<ScrollArea orientation="vertical" class="h-[calc(100vh-6.5rem)] z-30 md:block overflow-y-auto" type="hover">
|
||||
<div class="space-y-2">
|
||||
<p class="font-medium">
|
||||
On This Page
|
||||
</p>
|
||||
<TableOfContentTree :tree="headers" :level="1" />
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
|
||||
<div class="block xl:hidden mb-6">
|
||||
|
|
|
|||
106
apps/www/.vitepress/theme/components/ThemeCustomizer.vue
Normal file
106
apps/www/.vitepress/theme/components/ThemeCustomizer.vue
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
<script lang="ts" setup>
|
||||
import { useData } from 'vitepress'
|
||||
import type { Color } from '../types/colors'
|
||||
import { RADII, useConfigStore } from '@/stores/config'
|
||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||
import { Label } from '@/lib/registry/new-york/ui/label'
|
||||
import { colors } from '@/lib/registry'
|
||||
import RadixIconsCheck from '~icons/radix-icons/check'
|
||||
import RadixIconsSun from '~icons/radix-icons/sun'
|
||||
import RadixIconsMoon from '~icons/radix-icons/moon'
|
||||
|
||||
defineProps<{
|
||||
allColors: Color[]
|
||||
}>()
|
||||
|
||||
const { theme, radius, setRadius, setTheme } = useConfigStore()
|
||||
const { isDark } = useData()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-4">
|
||||
<div class="grid space-y-1">
|
||||
<h1 class="text-md text-foreground font-semibold">
|
||||
Customize
|
||||
</h1>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
Pick a style and color for your components.
|
||||
</p>
|
||||
</div>
|
||||
<div class="space-y-1.5 pt-6">
|
||||
<Label for="color" class="text-xs"> Color </Label>
|
||||
<div class="grid grid-cols-3 gap-2 py-1.5">
|
||||
<Button
|
||||
v-for="(color, index) in allColors"
|
||||
:key="index"
|
||||
variant="outline"
|
||||
class="h-8 justify-start px-3"
|
||||
:class="
|
||||
color === theme
|
||||
? 'border-foreground border-2'
|
||||
: ''
|
||||
"
|
||||
@click="setTheme(color)"
|
||||
>
|
||||
<span
|
||||
class="h-5 w-5 rounded-full flex items-center justify-center"
|
||||
:style="{ backgroundColor: colors[color][7].rgb }"
|
||||
>
|
||||
<RadixIconsCheck
|
||||
v-if="color === theme"
|
||||
class="h-3 w-3 text-white"
|
||||
/>
|
||||
</span>
|
||||
<span class="ml-2 text-xs capitalize">
|
||||
{{ color }}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-1.5 pt-6">
|
||||
<Label for="radius" class="text-xs"> Radius </Label>
|
||||
<div class="grid grid-cols-5 gap-2 py-1.5">
|
||||
<Button
|
||||
v-for="(r, index) in RADII"
|
||||
:key="index"
|
||||
variant="outline"
|
||||
class="h-8 justify-center px-3"
|
||||
:class="
|
||||
r === radius
|
||||
? 'border-foreground border-2'
|
||||
: ''
|
||||
"
|
||||
@click="setRadius(r)"
|
||||
>
|
||||
<span class="text-xs">
|
||||
{{ r }}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-1.5 pt-6">
|
||||
<Label for="theme" class="text-xs"> Theme </Label>
|
||||
|
||||
<div class="flex space-x-2 py-1.5">
|
||||
<Button
|
||||
class="h-8"
|
||||
variant="outline"
|
||||
:class="{ 'border-2 border-foreground': !isDark }"
|
||||
@click="isDark = false"
|
||||
>
|
||||
<RadixIconsSun class="w-4 h-4 mr-2" />
|
||||
<span class="text-xs">Light</span>
|
||||
</Button>
|
||||
<Button
|
||||
class="h-8"
|
||||
variant="outline"
|
||||
:class="{ 'border-2 border-foreground': isDark }"
|
||||
@click="isDark = true"
|
||||
>
|
||||
<RadixIconsMoon class="w-4 h-4 mr-2" />
|
||||
<span class="text-xs">Dark</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -14,7 +14,7 @@ import CardChat from '@/lib/registry/new-york/example/CardChat.vue'
|
|||
import ActivityGoal from '@/lib/registry/new-york/example/Cards/ActivityGoal.vue'
|
||||
import Metric from '@/lib/registry/new-york/example/Cards/Metric.vue'
|
||||
import DataTable from '@/lib/registry/new-york/example/Cards/DataTable.vue'
|
||||
import CardStats from '@/lib/registry/default/example/CardStats.vue'
|
||||
import CardStats from '@/lib/registry/new-york/example/CardStats.vue'
|
||||
|
||||
import { Card } from '@/lib/registry/new-york/ui/card'
|
||||
import { Calendar } from '@/lib/registry/new-york/ui/calendar'
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ interface DocsConfig {
|
|||
export const docsConfig: DocsConfig = {
|
||||
mainNav: [
|
||||
{
|
||||
title: 'Documentation',
|
||||
title: 'Docs',
|
||||
href: '/docs/introduction',
|
||||
},
|
||||
{
|
||||
|
|
@ -36,7 +36,7 @@ export const docsConfig: DocsConfig = {
|
|||
},
|
||||
{
|
||||
title: 'Examples',
|
||||
href: '/examples/dashboard',
|
||||
href: '/examples/mail',
|
||||
},
|
||||
{
|
||||
title: 'GitHub',
|
||||
|
|
@ -64,6 +64,11 @@ export const docsConfig: DocsConfig = {
|
|||
title: 'Theming',
|
||||
href: '/docs/theming',
|
||||
},
|
||||
{
|
||||
title: 'Dark Mode',
|
||||
href: '/docs/dark-mode',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'CLI',
|
||||
href: '/docs/cli',
|
||||
|
|
@ -206,6 +211,12 @@ export const docsConfig: DocsConfig = {
|
|||
title: 'Dialog',
|
||||
href: '/docs/components/dialog',
|
||||
},
|
||||
{
|
||||
title: 'Drawer',
|
||||
href: '/docs/components/drawer',
|
||||
items: [],
|
||||
label: 'New',
|
||||
},
|
||||
{
|
||||
title: 'Dropdown Menu',
|
||||
href: '/docs/components/dropdown-menu',
|
||||
|
|
@ -256,6 +267,12 @@ export const docsConfig: DocsConfig = {
|
|||
title: 'Radio Group',
|
||||
href: '/docs/components/radio-group',
|
||||
},
|
||||
{
|
||||
title: 'Resizable',
|
||||
href: '/docs/components/resizable',
|
||||
label: 'New',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Scroll Area',
|
||||
href: '/docs/components/scroll-area',
|
||||
|
|
@ -336,6 +353,11 @@ interface Example {
|
|||
code: string
|
||||
}
|
||||
export const examples: Example[] = [
|
||||
{
|
||||
name: 'Mail',
|
||||
href: '/examples/mail',
|
||||
code: 'https://github.com/radix-vue/shadcn-vue/tree/dev/apps/www/src/examples/mail',
|
||||
},
|
||||
{
|
||||
name: 'Dashboard',
|
||||
href: '/examples/dashboard',
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
/* eslint-disable vue/component-definition-name-casing */
|
||||
// https://vitepress.dev/guide/custom-theme
|
||||
import Layout from './layout/MainLayout.vue'
|
||||
import DocsLayout from './layout/DocsLayout.vue'
|
||||
|
|
|
|||
|
|
@ -85,14 +85,12 @@ watch(() => $route.path, (n) => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex min-h-screen flex-col bg-background">
|
||||
<div vaul-drawer-wrapper class="flex min-h-screen flex-col bg-background">
|
||||
<header class="sticky z-40 top-0 bg-background/80 backdrop-blur-lg border-b border-border">
|
||||
<div
|
||||
class="container flex justify-between h-14 max-w-screen-2xl items-center"
|
||||
class="container flex h-14 max-w-screen-2xl items-center"
|
||||
>
|
||||
<MobileNav />
|
||||
|
||||
<div class="mr-4 hidden md:flex">
|
||||
<div class="mr-4 md:mr-1 hidden md:flex">
|
||||
<Logo />
|
||||
|
||||
<nav
|
||||
|
|
@ -106,30 +104,33 @@ watch(() => $route.path, (n) => {
|
|||
class="transition-colors hover:text-foreground/80 text-foreground/60"
|
||||
:class="{
|
||||
'font-semibold !text-foreground': $route.path === `${route.href}.html`,
|
||||
'hidden lg:block': route?.href?.includes('github'),
|
||||
}"
|
||||
>
|
||||
{{ route.title }}
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
<MobileNav />
|
||||
|
||||
<div class=" flex items-center justify-end space-x-2 ">
|
||||
<Button
|
||||
variant="outline"
|
||||
class="w-72 h-9 px-3 hidden lg:flex lg:justify-between lg:items-center"
|
||||
@click="isOpen = true"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<SearchIcon class="w-4 h-4 mr-2 text-muted-foreground" />
|
||||
<span class="text-muted-foreground"> Search for anything... </span>
|
||||
</div>
|
||||
<div class="flex items-center gap-x-1">
|
||||
<Kbd> <span>⌘</span>K </Kbd>
|
||||
</div>
|
||||
</Button>
|
||||
<ThemePopover />
|
||||
<div class="flex flex-1 items-center justify-between space-x-2 md:justify-end">
|
||||
<div class="w-full flex-1 md:w-auto md:flex-none">
|
||||
<Button
|
||||
variant="outline"
|
||||
class="relative h-8 w-full justify-start rounded-[0.5rem] bg-background text-sm font-normal text-muted-foreground shadow-none sm:pr-12 md:w-40 lg:w-64"
|
||||
@click="isOpen = true"
|
||||
>
|
||||
<span className="hidden lg:inline-flex">Search documentation...</span>
|
||||
<span className="inline-flex lg:hidden">Search...</span>
|
||||
<kbd className="pointer-events-none absolute right-[0.3rem] top-[0.3rem] hidden h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium opacity-100 sm:flex">
|
||||
<span className="text-xs">⌘</span>K
|
||||
</kbd>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<nav class="flex items-center gap-x-1">
|
||||
<ThemePopover />
|
||||
|
||||
<div class="flex items-center gap-x-1">
|
||||
<Button
|
||||
v-for="link in links"
|
||||
:key="link.name"
|
||||
|
|
@ -154,7 +155,7 @@ watch(() => $route.path, (n) => {
|
|||
/>
|
||||
</Button>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
|
@ -293,9 +294,7 @@ watch(() => $route.path, (n) => {
|
|||
</DialogContent>
|
||||
</Dialog>
|
||||
<DefaultToaster />
|
||||
<ClientOnly>
|
||||
<NewYorkSonner :theme="isDark ? 'dark' : 'light'" />
|
||||
</ClientOnly>
|
||||
<NewYorkSonner :theme="'system'" />
|
||||
<NewYorkToaster />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,96 +1,117 @@
|
|||
<script setup lang="ts">
|
||||
import { onMounted, watch } from 'vue'
|
||||
import { Paintbrush } from 'lucide-vue-next'
|
||||
import PageHeader from '../components/PageHeader.vue'
|
||||
import PageHeaderHeading from '../components/PageHeaderHeading.vue'
|
||||
import PageHeaderDescription from '../components/PageHeaderDescription.vue'
|
||||
import CustomizerCode from '../components/CustomizerCode.vue'
|
||||
import ThemePopover from '../components/ThemePopover.vue'
|
||||
import { allColors } from '../components/theming/utils/data'
|
||||
import type { Color } from '../types/colors'
|
||||
import ThemeCustomizer from '../components/ThemeCustomizer.vue'
|
||||
import InlineThemePicker from '../components/InlineThemePicker.vue'
|
||||
import PageAction from '../components/PageAction.vue'
|
||||
import { useConfigStore } from '@/stores/config'
|
||||
import { colors } from '@/lib/registry'
|
||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/lib/registry/new-york/ui/tooltip'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/lib/registry/new-york/ui/popover'
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '@/lib/registry/new-york/ui/dialog'
|
||||
import RadixIconsCheck from '~icons/radix-icons/check'
|
||||
import { Drawer, DrawerContent, DrawerTrigger } from '@/lib/registry/new-york/ui/drawer'
|
||||
|
||||
const { theme, setTheme } = useConfigStore()
|
||||
// Create an array of color values
|
||||
const allColors: Color[] = [
|
||||
'zinc',
|
||||
'rose',
|
||||
'blue',
|
||||
'green',
|
||||
'orange',
|
||||
'red',
|
||||
'slate',
|
||||
'stone',
|
||||
'gray',
|
||||
'neutral',
|
||||
'yellow',
|
||||
'violet',
|
||||
]
|
||||
|
||||
const { theme, radius } = useConfigStore()
|
||||
|
||||
// Whenever the component is mounted, update the document class list
|
||||
onMounted(() => {
|
||||
document.documentElement.style.setProperty('--radius', `${radius.value}rem`)
|
||||
document.documentElement.classList.add(`theme-${theme.value}`)
|
||||
})
|
||||
|
||||
// Whenever the theme value changes, update the document class list
|
||||
watch(theme, (theme) => {
|
||||
document.documentElement.classList.remove(
|
||||
...allColors.map(color => `theme-${color}`),
|
||||
)
|
||||
document.documentElement.classList.add(`theme-${theme}`)
|
||||
})
|
||||
|
||||
// Whenever the radius value changes, update the document style
|
||||
watch(radius, (radius) => {
|
||||
document.documentElement.style.setProperty('--radius', `${radius}rem`)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container relative">
|
||||
<div class="flex justify-between items-center">
|
||||
<div>
|
||||
<PageHeader class="page-header pb-8">
|
||||
<PageHeaderHeading class="hidden md:block">
|
||||
Make it yours.
|
||||
</PageHeaderHeading>
|
||||
<PageHeaderDescription>
|
||||
Hand-picked themes that you can copy and paste into your apps.
|
||||
</PageHeaderDescription>
|
||||
</PageHeader>
|
||||
</div>
|
||||
<div class="px-4 pb-8 md:ml-auto md:pb-0">
|
||||
<div class="flex items-center space-x-2">
|
||||
<div class="hidden md:flex">
|
||||
<div class="mr-4 hidden items-center space-x-1 lg:flex">
|
||||
<TooltipProvider
|
||||
v-for="(color, index) in allColors.slice(0, 5)"
|
||||
:key="index"
|
||||
>
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
<button
|
||||
:key="index"
|
||||
class="flex h-9 w-9 items-center justify-center rounded-full border-2 border-border text-xs"
|
||||
:class="
|
||||
color === theme
|
||||
? 'border-primary'
|
||||
: 'border-transparent'
|
||||
"
|
||||
@click="setTheme(color)"
|
||||
>
|
||||
<span
|
||||
class="flex h-6 w-6 items-center justify-center rounded-full"
|
||||
:style="{ backgroundColor: colors[color][6].rgb }"
|
||||
>
|
||||
<RadixIconsCheck
|
||||
v-if="color === theme"
|
||||
class="h-4 w-4 text-white"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
align="center"
|
||||
:side-offset="1"
|
||||
class="capitalize bg-zinc-900 text-zinc-50"
|
||||
>
|
||||
{{ allColors[index] }}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<ThemePopover title="Customize" />
|
||||
<Dialog>
|
||||
<DialogTrigger as-child>
|
||||
<Button class="h-9 ml-2 rounded-[0.5rem]">
|
||||
Copy code
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent class="sm:max-w-[625px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Theme</DialogTitle>
|
||||
<DialogDescription>
|
||||
Copy and paste the following code into your CSS file.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<CustomizerCode />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<PageHeader>
|
||||
<PageHeaderHeading class="hidden md:block">
|
||||
Add colors. Make it yours.
|
||||
</PageHeaderHeading>
|
||||
<PageHeaderHeading class="md:hidden">
|
||||
Make it yours
|
||||
</PageHeaderHeading>
|
||||
<PageHeaderDescription>
|
||||
Hand-picked themes that you can copy and paste into your apps.
|
||||
</PageHeaderDescription>
|
||||
|
||||
<PageAction>
|
||||
<InlineThemePicker class="gap-x-1 me-4 hidden lg:flex" :all-colors="allColors" />
|
||||
|
||||
<Drawer>
|
||||
<DrawerTrigger as-child>
|
||||
<Button variant="outline" class="md:hidden h-9 rounded-[0.5rem]">
|
||||
<Paintbrush class="w-4 h-4 mr-2" />
|
||||
Customize
|
||||
</Button>
|
||||
</DrawerTrigger>
|
||||
<DrawerContent class="p-6 pt-0">
|
||||
<ThemeCustomizer :all-colors="allColors" />
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
<Popover>
|
||||
<PopoverTrigger as-child>
|
||||
<Button variant="outline" class="hidden md:flex h-9 rounded-[0.5rem]">
|
||||
<Paintbrush class="w-4 h-4 mr-2" />
|
||||
Customize
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent :side-offset="8" align="end" class="w-96">
|
||||
<ThemeCustomizer :all-colors="allColors" />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
<Dialog>
|
||||
<DialogTrigger as-child>
|
||||
<Button class="h-9 ml-2 rounded-[0.5rem]">
|
||||
Copy code
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent class="sm:max-w-[625px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Theme</DialogTitle>
|
||||
<DialogDescription>
|
||||
Copy and paste the following code into your CSS file.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<CustomizerCode />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</PageAction>
|
||||
</PageHeader>
|
||||
|
||||
<section>
|
||||
<slot />
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -7,26 +7,26 @@
|
|||
--font-geist-sans: "geist-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||
Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 240 10% 3.9%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 240 10% 3.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 240 10% 3.9%;
|
||||
--primary: 240 5.9% 10%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
--secondary: 240 4.8% 95.9%;
|
||||
--secondary-foreground: 240 5.9% 10%;
|
||||
--muted: 240 4.8% 95.9%;
|
||||
--muted-foreground: 240 3.8% 46.1%;
|
||||
--accent: 240 4.8% 95.9%;
|
||||
--accent-foreground: 240 5.9% 10%;
|
||||
--destructive: 0 72.22% 50.59%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 240 5.9% 90%;
|
||||
--input: 240 5.9% 90%;
|
||||
--ring: 240 5% 64.9%;
|
||||
--radius: 0.5rem;
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 240 10% 3.9%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 240 10% 3.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 240 10% 3.9%;
|
||||
--primary: 240 5.9% 10%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
--secondary: 240 4.8% 95.9%;
|
||||
--secondary-foreground: 240 5.9% 10%;
|
||||
--muted: 240 4.8% 95.9%;
|
||||
--muted-foreground: 240 3.8% 46.1%;
|
||||
--accent: 240 4.8% 95.9%;
|
||||
--accent-foreground: 240 5.9% 10%;
|
||||
--destructive: 0 72.22% 50.59%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 240 5.9% 90%;
|
||||
--input: 240 5.9% 90%;
|
||||
--ring: 240 5% 64.9%;
|
||||
--radius: 0.5rem;
|
||||
|
||||
--vis-tooltip-background-color: none !important;
|
||||
--vis-tooltip-border-color: none !important;
|
||||
|
|
@ -68,7 +68,9 @@
|
|||
}
|
||||
body {
|
||||
@apply bg-background text-foreground min-h-screen antialiased font-sans;
|
||||
font-feature-settings: "rlig" 1, "calt" 1;
|
||||
/* font-feature-settings: "rlig" 1, "calt" 1; */
|
||||
font-synthesis-weight: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
/* Mobile tap highlight */
|
||||
|
|
|
|||
13
apps/www/.vitepress/theme/types/colors.ts
Normal file
13
apps/www/.vitepress/theme/types/colors.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
export type Color =
|
||||
| 'zinc'
|
||||
| 'slate'
|
||||
| 'stone'
|
||||
| 'gray'
|
||||
| 'neutral'
|
||||
| 'red'
|
||||
| 'rose'
|
||||
| 'orange'
|
||||
| 'green'
|
||||
| 'blue'
|
||||
| 'yellow'
|
||||
| 'violet'
|
||||
|
|
@ -4,7 +4,7 @@ import { dependencies as deps } from '../../../package.json'
|
|||
import { Index as demoIndex } from '../../../../www/__registry__'
|
||||
import tailwindConfigRaw from '../../../tailwind.config?raw'
|
||||
import cssRaw from '../../../../../packages/cli/test/fixtures/nuxt/assets/css/tailwind.css?raw'
|
||||
import { type Style } from '@/lib/registry/styles'
|
||||
import type { Style } from '@/lib/registry/styles'
|
||||
|
||||
export function makeCodeSandboxParams(componentName: string, style: Style, sources: Record<string, string>) {
|
||||
let files: Record<string, any> = {}
|
||||
|
|
@ -54,7 +54,7 @@ export default defineConfig({
|
|||
<title>Vite + Vue + TS</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<div vaul-drawer-wrapper id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -90,6 +90,9 @@ function constructFiles(componentName: string, style: Style, sources: Record<str
|
|||
[iconPackage]: 'latest',
|
||||
'shadcn-vue': 'latest',
|
||||
'typescript': 'latest',
|
||||
'vaul-vue': 'latest',
|
||||
'@unovis/vue': 'latest',
|
||||
'@unovis/ts': 'latest',
|
||||
}
|
||||
|
||||
const devDependencies = {
|
||||
|
|
@ -164,7 +167,6 @@ function constructFiles(componentName: string, style: Style, sources: Record<str
|
|||
isBinary: false,
|
||||
content: `import { type ClassValue, clsx } from 'clsx'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { camelize, getCurrentInstance, toHandlerKey } from 'vue'
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
|
|
|
|||
|
|
@ -241,6 +241,13 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/default/example/CarouselSpacing.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/CarouselSpacing.vue"],
|
||||
},
|
||||
"CarouselThumbnails": {
|
||||
name: "CarouselThumbnails",
|
||||
type: "components:example",
|
||||
registryDependencies: ["carousel","card"],
|
||||
component: () => import("../src/lib/registry/default/example/CarouselThumbnails.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/CarouselThumbnails.vue"],
|
||||
},
|
||||
"CheckboxDemo": {
|
||||
name: "CheckboxDemo",
|
||||
type: "components:example",
|
||||
|
|
@ -311,6 +318,13 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/default/example/ComboboxPopover.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/ComboboxPopover.vue"],
|
||||
},
|
||||
"ComboboxResponsive": {
|
||||
name: "ComboboxResponsive",
|
||||
type: "components:example",
|
||||
registryDependencies: ["button","command","drawer","popover"],
|
||||
component: () => import("../src/lib/registry/default/example/ComboboxResponsive.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/ComboboxResponsive.vue"],
|
||||
},
|
||||
"CommandDemo": {
|
||||
name: "CommandDemo",
|
||||
type: "components:example",
|
||||
|
|
@ -416,12 +430,26 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/default/example/DialogScrollOverlayDemo.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/DialogScrollOverlayDemo.vue"],
|
||||
},
|
||||
"DonutChartDemo": {
|
||||
name: "DonutChartDemo",
|
||||
"DrawerDemo": {
|
||||
name: "DrawerDemo",
|
||||
type: "components:example",
|
||||
registryDependencies: ["chart-donut"],
|
||||
component: () => import("../src/lib/registry/default/example/DonutChartDemo.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/DonutChartDemo.vue"],
|
||||
registryDependencies: ["button","drawer"],
|
||||
component: () => import("../src/lib/registry/default/example/DrawerDemo.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/DrawerDemo.vue"],
|
||||
},
|
||||
"DrawerDialog": {
|
||||
name: "DrawerDialog",
|
||||
type: "components:example",
|
||||
registryDependencies: ["button","dialog","drawer","label","input"],
|
||||
component: () => import("../src/lib/registry/default/example/DrawerDialog.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/DrawerDialog.vue"],
|
||||
},
|
||||
"DropdownMenuCheckboxes": {
|
||||
name: "DropdownMenuCheckboxes",
|
||||
type: "components:example",
|
||||
registryDependencies: ["button","dropdown-menu"],
|
||||
component: () => import("../src/lib/registry/default/example/DropdownMenuCheckboxes.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/DropdownMenuCheckboxes.vue"],
|
||||
},
|
||||
"DropdownMenuDemo": {
|
||||
name: "DropdownMenuDemo",
|
||||
|
|
@ -430,6 +458,13 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/default/example/DropdownMenuDemo.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/DropdownMenuDemo.vue"],
|
||||
},
|
||||
"DropdownMenuRadioGroup": {
|
||||
name: "DropdownMenuRadioGroup",
|
||||
type: "components:example",
|
||||
registryDependencies: ["button","dropdown-menu"],
|
||||
component: () => import("../src/lib/registry/default/example/DropdownMenuRadioGroup.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/DropdownMenuRadioGroup.vue"],
|
||||
},
|
||||
"HoverCardDemo": {
|
||||
name: "HoverCardDemo",
|
||||
type: "components:example",
|
||||
|
|
@ -535,6 +570,13 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/default/example/PaginationDemo.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/PaginationDemo.vue"],
|
||||
},
|
||||
"PinInputControlled": {
|
||||
name: "PinInputControlled",
|
||||
type: "components:example",
|
||||
registryDependencies: ["pin-input"],
|
||||
component: () => import("../src/lib/registry/default/example/PinInputControlled.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/PinInputControlled.vue"],
|
||||
},
|
||||
"PinInputDemo": {
|
||||
name: "PinInputDemo",
|
||||
type: "components:example",
|
||||
|
|
@ -542,6 +584,13 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/default/example/PinInputDemo.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/PinInputDemo.vue"],
|
||||
},
|
||||
"PinInputDisabled": {
|
||||
name: "PinInputDisabled",
|
||||
type: "components:example",
|
||||
registryDependencies: ["pin-input"],
|
||||
component: () => import("../src/lib/registry/default/example/PinInputDisabled.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/PinInputDisabled.vue"],
|
||||
},
|
||||
"PinInputFormDemo": {
|
||||
name: "PinInputFormDemo",
|
||||
type: "components:example",
|
||||
|
|
@ -549,6 +598,13 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/default/example/PinInputFormDemo.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/PinInputFormDemo.vue"],
|
||||
},
|
||||
"PinInputSeparatorDemo": {
|
||||
name: "PinInputSeparatorDemo",
|
||||
type: "components:example",
|
||||
registryDependencies: ["pin-input"],
|
||||
component: () => import("../src/lib/registry/default/example/PinInputSeparatorDemo.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/PinInputSeparatorDemo.vue"],
|
||||
},
|
||||
"PopoverDemo": {
|
||||
name: "PopoverDemo",
|
||||
type: "components:example",
|
||||
|
|
@ -584,6 +640,27 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/default/example/RangePickerWithSlot.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/RangePickerWithSlot.vue"],
|
||||
},
|
||||
"ResizableDemo": {
|
||||
name: "ResizableDemo",
|
||||
type: "components:example",
|
||||
registryDependencies: ["resizable"],
|
||||
component: () => import("../src/lib/registry/default/example/ResizableDemo.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/ResizableDemo.vue"],
|
||||
},
|
||||
"ResizableHandleDemo": {
|
||||
name: "ResizableHandleDemo",
|
||||
type: "components:example",
|
||||
registryDependencies: ["resizable"],
|
||||
component: () => import("../src/lib/registry/default/example/ResizableHandleDemo.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/ResizableHandleDemo.vue"],
|
||||
},
|
||||
"ResizableVerticalDemo": {
|
||||
name: "ResizableVerticalDemo",
|
||||
type: "components:example",
|
||||
registryDependencies: ["resizable"],
|
||||
component: () => import("../src/lib/registry/default/example/ResizableVerticalDemo.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/ResizableVerticalDemo.vue"],
|
||||
},
|
||||
"ScrollAreaDemo": {
|
||||
name: "ScrollAreaDemo",
|
||||
type: "components:example",
|
||||
|
|
@ -612,6 +689,13 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/default/example/SelectForm.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/SelectForm.vue"],
|
||||
},
|
||||
"SelectScrollable": {
|
||||
name: "SelectScrollable",
|
||||
type: "components:example",
|
||||
registryDependencies: ["select"],
|
||||
component: () => import("../src/lib/registry/default/example/SelectScrollable.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/SelectScrollable.vue"],
|
||||
},
|
||||
"SeparatorDemo": {
|
||||
name: "SeparatorDemo",
|
||||
type: "components:example",
|
||||
|
|
@ -633,6 +717,13 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/default/example/SheetSideDemo.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/SheetSideDemo.vue"],
|
||||
},
|
||||
"SkeletonCard": {
|
||||
name: "SkeletonCard",
|
||||
type: "components:example",
|
||||
registryDependencies: ["skeleton"],
|
||||
component: () => import("../src/lib/registry/default/example/SkeletonCard.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/SkeletonCard.vue"],
|
||||
},
|
||||
"SkeletonDemo": {
|
||||
name: "SkeletonDemo",
|
||||
type: "components:example",
|
||||
|
|
@ -647,6 +738,13 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/default/example/SliderDemo.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/SliderDemo.vue"],
|
||||
},
|
||||
"SliderForm": {
|
||||
name: "SliderForm",
|
||||
type: "components:example",
|
||||
registryDependencies: ["button","form","slider","toast"],
|
||||
component: () => import("../src/lib/registry/default/example/SliderForm.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/SliderForm.vue"],
|
||||
},
|
||||
"SonnerDemo": {
|
||||
name: "SonnerDemo",
|
||||
type: "components:example",
|
||||
|
|
@ -1222,6 +1320,13 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/new-york/example/CarouselSpacing.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/CarouselSpacing.vue"],
|
||||
},
|
||||
"CarouselThumbnails": {
|
||||
name: "CarouselThumbnails",
|
||||
type: "components:example",
|
||||
registryDependencies: ["carousel","card"],
|
||||
component: () => import("../src/lib/registry/new-york/example/CarouselThumbnails.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/CarouselThumbnails.vue"],
|
||||
},
|
||||
"CheckboxDemo": {
|
||||
name: "CheckboxDemo",
|
||||
type: "components:example",
|
||||
|
|
@ -1292,6 +1397,13 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/new-york/example/ComboboxPopover.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/ComboboxPopover.vue"],
|
||||
},
|
||||
"ComboboxResponsive": {
|
||||
name: "ComboboxResponsive",
|
||||
type: "components:example",
|
||||
registryDependencies: ["button","command","drawer","popover"],
|
||||
component: () => import("../src/lib/registry/new-york/example/ComboboxResponsive.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/ComboboxResponsive.vue"],
|
||||
},
|
||||
"CommandDemo": {
|
||||
name: "CommandDemo",
|
||||
type: "components:example",
|
||||
|
|
@ -1397,12 +1509,26 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/new-york/example/DialogScrollOverlayDemo.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/DialogScrollOverlayDemo.vue"],
|
||||
},
|
||||
"DonutChartDemo": {
|
||||
name: "DonutChartDemo",
|
||||
"DrawerDemo": {
|
||||
name: "DrawerDemo",
|
||||
type: "components:example",
|
||||
registryDependencies: ["chart-donut"],
|
||||
component: () => import("../src/lib/registry/new-york/example/DonutChartDemo.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/DonutChartDemo.vue"],
|
||||
registryDependencies: ["button","drawer"],
|
||||
component: () => import("../src/lib/registry/new-york/example/DrawerDemo.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/DrawerDemo.vue"],
|
||||
},
|
||||
"DrawerDialog": {
|
||||
name: "DrawerDialog",
|
||||
type: "components:example",
|
||||
registryDependencies: ["button","dialog","drawer","label","input"],
|
||||
component: () => import("../src/lib/registry/new-york/example/DrawerDialog.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/DrawerDialog.vue"],
|
||||
},
|
||||
"DropdownMenuCheckboxes": {
|
||||
name: "DropdownMenuCheckboxes",
|
||||
type: "components:example",
|
||||
registryDependencies: ["button","dropdown-menu"],
|
||||
component: () => import("../src/lib/registry/new-york/example/DropdownMenuCheckboxes.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/DropdownMenuCheckboxes.vue"],
|
||||
},
|
||||
"DropdownMenuDemo": {
|
||||
name: "DropdownMenuDemo",
|
||||
|
|
@ -1411,6 +1537,13 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/new-york/example/DropdownMenuDemo.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/DropdownMenuDemo.vue"],
|
||||
},
|
||||
"DropdownMenuRadioGroup": {
|
||||
name: "DropdownMenuRadioGroup",
|
||||
type: "components:example",
|
||||
registryDependencies: ["button","dropdown-menu"],
|
||||
component: () => import("../src/lib/registry/new-york/example/DropdownMenuRadioGroup.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/DropdownMenuRadioGroup.vue"],
|
||||
},
|
||||
"HoverCardDemo": {
|
||||
name: "HoverCardDemo",
|
||||
type: "components:example",
|
||||
|
|
@ -1516,6 +1649,13 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/new-york/example/PaginationDemo.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/PaginationDemo.vue"],
|
||||
},
|
||||
"PinInputControlled": {
|
||||
name: "PinInputControlled",
|
||||
type: "components:example",
|
||||
registryDependencies: ["pin-input"],
|
||||
component: () => import("../src/lib/registry/new-york/example/PinInputControlled.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/PinInputControlled.vue"],
|
||||
},
|
||||
"PinInputDemo": {
|
||||
name: "PinInputDemo",
|
||||
type: "components:example",
|
||||
|
|
@ -1523,6 +1663,13 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/new-york/example/PinInputDemo.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/PinInputDemo.vue"],
|
||||
},
|
||||
"PinInputDisabled": {
|
||||
name: "PinInputDisabled",
|
||||
type: "components:example",
|
||||
registryDependencies: ["pin-input"],
|
||||
component: () => import("../src/lib/registry/new-york/example/PinInputDisabled.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/PinInputDisabled.vue"],
|
||||
},
|
||||
"PinInputFormDemo": {
|
||||
name: "PinInputFormDemo",
|
||||
type: "components:example",
|
||||
|
|
@ -1530,6 +1677,13 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/new-york/example/PinInputFormDemo.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/PinInputFormDemo.vue"],
|
||||
},
|
||||
"PinInputSeparatorDemo": {
|
||||
name: "PinInputSeparatorDemo",
|
||||
type: "components:example",
|
||||
registryDependencies: ["pin-input"],
|
||||
component: () => import("../src/lib/registry/new-york/example/PinInputSeparatorDemo.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/PinInputSeparatorDemo.vue"],
|
||||
},
|
||||
"PopoverDemo": {
|
||||
name: "PopoverDemo",
|
||||
type: "components:example",
|
||||
|
|
@ -1565,6 +1719,27 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/new-york/example/RangePickerWithSlot.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/RangePickerWithSlot.vue"],
|
||||
},
|
||||
"ResizableDemo": {
|
||||
name: "ResizableDemo",
|
||||
type: "components:example",
|
||||
registryDependencies: ["resizable"],
|
||||
component: () => import("../src/lib/registry/new-york/example/ResizableDemo.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/ResizableDemo.vue"],
|
||||
},
|
||||
"ResizableHandleDemo": {
|
||||
name: "ResizableHandleDemo",
|
||||
type: "components:example",
|
||||
registryDependencies: ["resizable"],
|
||||
component: () => import("../src/lib/registry/new-york/example/ResizableHandleDemo.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/ResizableHandleDemo.vue"],
|
||||
},
|
||||
"ResizableVerticalDemo": {
|
||||
name: "ResizableVerticalDemo",
|
||||
type: "components:example",
|
||||
registryDependencies: ["resizable"],
|
||||
component: () => import("../src/lib/registry/new-york/example/ResizableVerticalDemo.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/ResizableVerticalDemo.vue"],
|
||||
},
|
||||
"ScrollAreaDemo": {
|
||||
name: "ScrollAreaDemo",
|
||||
type: "components:example",
|
||||
|
|
@ -1593,6 +1768,13 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/new-york/example/SelectForm.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/SelectForm.vue"],
|
||||
},
|
||||
"SelectScrollable": {
|
||||
name: "SelectScrollable",
|
||||
type: "components:example",
|
||||
registryDependencies: ["select"],
|
||||
component: () => import("../src/lib/registry/new-york/example/SelectScrollable.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/SelectScrollable.vue"],
|
||||
},
|
||||
"SeparatorDemo": {
|
||||
name: "SeparatorDemo",
|
||||
type: "components:example",
|
||||
|
|
@ -1614,6 +1796,13 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/new-york/example/SheetSideDemo.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/SheetSideDemo.vue"],
|
||||
},
|
||||
"SkeletonCard": {
|
||||
name: "SkeletonCard",
|
||||
type: "components:example",
|
||||
registryDependencies: ["skeleton"],
|
||||
component: () => import("../src/lib/registry/new-york/example/SkeletonCard.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/SkeletonCard.vue"],
|
||||
},
|
||||
"SkeletonDemo": {
|
||||
name: "SkeletonDemo",
|
||||
type: "components:example",
|
||||
|
|
@ -1628,6 +1817,13 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/new-york/example/SliderDemo.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/SliderDemo.vue"],
|
||||
},
|
||||
"SliderForm": {
|
||||
name: "SliderForm",
|
||||
type: "components:example",
|
||||
registryDependencies: ["button","form","slider","toast"],
|
||||
component: () => import("../src/lib/registry/new-york/example/SliderForm.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/SliderForm.vue"],
|
||||
},
|
||||
"SonnerDemo": {
|
||||
name: "SonnerDemo",
|
||||
type: "components:example",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "www",
|
||||
"type": "module",
|
||||
"version": "0.9.0",
|
||||
"version": "0.10.1",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
|
|
@ -16,57 +16,58 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@formkit/auto-animate": "^0.8.1",
|
||||
"@morev/vue-transitions": "^2.3.6",
|
||||
"@radix-icons/vue": "^1.0.0",
|
||||
"@stackblitz/sdk": "^1.9.0",
|
||||
"@tanstack/vue-table": "^8.11.8",
|
||||
"@unovis/ts": "^1.3.3",
|
||||
"@unovis/vue": "^1.3.3",
|
||||
"@tanstack/vue-table": "^8.13.2",
|
||||
"@unovis/ts": "^1.3.5",
|
||||
"@unovis/vue": "^1.3.5",
|
||||
"@vee-validate/zod": "^4.12.5",
|
||||
"@vueuse/core": "^10.7.2",
|
||||
"@vueuse/core": "^10.9.0",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.0",
|
||||
"codesandbox": "^2.2.3",
|
||||
"date-fns": "^2.30.0",
|
||||
"embla-carousel": "^8.0.0-rc22",
|
||||
"embla-carousel-autoplay": "^8.0.0-rc22",
|
||||
"embla-carousel-vue": "^8.0.0-rc22",
|
||||
"lucide-vue-next": "^0.276.0",
|
||||
"radix-vue": "^1.4.6",
|
||||
"date-fns": "^3.3.1",
|
||||
"embla-carousel": "^8.0.0",
|
||||
"embla-carousel-autoplay": "^8.0.0",
|
||||
"embla-carousel-vue": "^8.0.0",
|
||||
"lucide-vue-next": "^0.350.0",
|
||||
"radix-vue": "^1.5.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"v-calendar": "^3.1.2",
|
||||
"vaul-vue": "^0.1.0",
|
||||
"vee-validate": "4.12.5",
|
||||
"vue": "^3.4.15",
|
||||
"vue-sonner": "^1.0.3",
|
||||
"vue": "^3.4.21",
|
||||
"vue-sonner": "^1.1.2",
|
||||
"vue-wrap-balancer": "^1.1.3",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/radix-icons": "^1.1.11",
|
||||
"@iconify-json/tabler": "^1.1.89",
|
||||
"@iconify/json": "^2.2.108",
|
||||
"@iconify-json/radix-icons": "^1.1.14",
|
||||
"@iconify-json/simple-icons": "^1.1.94",
|
||||
"@iconify-json/tabler": "^1.1.106",
|
||||
"@iconify/json": "^2.2.189",
|
||||
"@iconify/vue": "^4.1.1",
|
||||
"@shikijs/transformers": "^1.0.0-beta.3",
|
||||
"@types/lodash.template": "^4.5.2",
|
||||
"@types/node": "^20.8.10",
|
||||
"@vitejs/plugin-vue": "^5.0.3",
|
||||
"@shikijs/transformers": "^1.1.7",
|
||||
"@types/lodash.template": "^4.5.3",
|
||||
"@types/node": "^20.11.25",
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"@vitejs/plugin-vue-jsx": "^3.1.0",
|
||||
"@vue/compiler-core": "^3.4.15",
|
||||
"@vue/compiler-dom": "^3.4.15",
|
||||
"@vue/compiler-core": "^3.4.21",
|
||||
"@vue/compiler-dom": "^3.4.21",
|
||||
"@vue/tsconfig": "^0.5.1",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"autoprefixer": "^10.4.18",
|
||||
"lodash.template": "^4.5.0",
|
||||
"oxc-parser": "^0.2.0",
|
||||
"oxc-parser": "^0.8.0",
|
||||
"pathe": "^1.1.2",
|
||||
"rimraf": "^5.0.5",
|
||||
"shiki": "^1.0.0-beta.3",
|
||||
"shiki": "^1.1.7",
|
||||
"tailwind-merge": "^2.2.1",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"tsx": "^4.7.0",
|
||||
"typescript": "^5.3.3",
|
||||
"unplugin-icons": "^0.18.3",
|
||||
"vite": "^5.0.12",
|
||||
"vitepress": "^1.0.0-rc.41",
|
||||
"vue-tsc": "^1.8.27"
|
||||
"tsx": "^4.7.1",
|
||||
"typescript": "^5.4.2",
|
||||
"unplugin-icons": "^0.18.5",
|
||||
"vite": "^5.1.5",
|
||||
"vitepress": "^1.0.0-rc.45",
|
||||
"vue-tsc": "^2.0.6"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,21 @@
|
|||
---
|
||||
title: Carousel
|
||||
description: A carousel with motion and swipe built using Embla.
|
||||
source: apps/www/src/lib/registry/default/ui/carousel
|
||||
source: apps/www/src/lib/registry/default/ui/carousel
|
||||
primitive: https://www.embla-carousel.com/api
|
||||
---
|
||||
|
||||
|
||||
<ComponentPreview name="CarouselDemo" />
|
||||
|
||||
<ComponentPreview name="CarouselDemo" />
|
||||
|
||||
## About
|
||||
|
||||
The carousel component is built using the [Embla Carousel](https://www.embla-carousel.com/) library.
|
||||
|
||||
## Installation
|
||||
|
||||
|
||||
```bash
|
||||
npx shadcn-vue@latest add carousel
|
||||
```
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
|
|
@ -54,7 +51,6 @@ To set the size of the items, you can use the `basis` utility class on the `<Car
|
|||
|
||||
<ComponentPreview name="CarouselSize" />
|
||||
|
||||
|
||||
Example
|
||||
|
||||
```vue:line-numbers title="Example" {4-6}
|
||||
|
|
@ -68,7 +64,6 @@ Example
|
|||
</Carousel>
|
||||
```
|
||||
|
||||
|
||||
Responsive
|
||||
|
||||
```vue:line-numbers title="Responsive" {4-6}
|
||||
|
|
@ -151,6 +146,10 @@ Use the `orientation` prop to set the orientation of the carousel.
|
|||
</Carousel>
|
||||
```
|
||||
|
||||
### Thumbnails
|
||||
|
||||
<ComponentPreview name="CarouselThumbnails" />
|
||||
|
||||
## Options
|
||||
|
||||
You can pass options to the carousel using the `opts` prop. See the [Embla Carousel docs](https://www.embla-carousel.com/api/options/) for more information.
|
||||
|
|
@ -259,7 +258,6 @@ You can use the `plugins` prop to add plugins to the carousel.
|
|||
npm i embla-carousel-autoplay
|
||||
```
|
||||
|
||||
|
||||
```vue:line-numbers {2,8-10}
|
||||
<script setup lang="ts">
|
||||
import Autoplay from 'embla-carousel-autoplay'
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
---
|
||||
title: Combobox
|
||||
description: Autocomplete input and command palette with a list of suggestions.
|
||||
description: Autocomplete input and command palette with a list of suggestions.
|
||||
---
|
||||
|
||||
<ComponentPreview name="ComboboxDemo" />
|
||||
<ComponentPreview name="ComboboxDemo" />
|
||||
|
||||
<br>
|
||||
<Callout title="Note" class="bg-destructive">
|
||||
|
|
@ -11,14 +11,13 @@ description: Autocomplete input and command palette with a list of suggestions.
|
|||
[Radix Vue](https://github.com/radix-vue/radix-vue/releases/tag/v1.2.0) introduced a breaking change. You will need to wrap `ComboboxGroup` and `ComboboxItem` inside of `ComboboxList` now.
|
||||
|
||||
</Callout>
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
The Combobox is built using a composition of the `<Popover />` and the `<Command />` components.
|
||||
|
||||
See installation instructions for the [Popover](/docs/components/popover#installation) and the [Command](/docs/components/command#installation) components.
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
```vue
|
||||
|
|
@ -110,6 +109,12 @@ const value = ref({})
|
|||
|
||||
<ComponentPreview name="ComboboxDropdownMenu" />
|
||||
|
||||
### Responsive
|
||||
|
||||
You can create a responsive combobox by using the `<Popover />` on desktop and the `<Drawer />` components on mobile.
|
||||
|
||||
<ComponentPreview name="ComboboxResponsive" />
|
||||
|
||||
### Form
|
||||
|
||||
<ComponentPreview name="ComboboxForm" />
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ description: Powerful table and datagrids built using TanStack Table.
|
|||
primitive: https://tanstack.com/table/v8/docs/guide/introduction
|
||||
---
|
||||
|
||||
|
||||
<ComponentPreview name="DataTableDemo" />
|
||||
|
||||
## Introduction
|
||||
|
|
@ -56,7 +55,6 @@ npm install @tanstack/vue-table
|
|||
|
||||
<ComponentPreview name="DataTableColumnPinningDemo" />
|
||||
|
||||
|
||||
## Prerequisites
|
||||
|
||||
We are going to build a table to show recent payments. Here's what our data looks like:
|
||||
|
|
@ -219,7 +217,6 @@ const table = useVueTable({
|
|||
|
||||
</Callout>
|
||||
|
||||
|
||||
### Render the table
|
||||
|
||||
Finally, we'll render our table in our index component.
|
||||
|
|
@ -270,7 +267,6 @@ Let's format the amount cell to display the dollar amount. We'll also align the
|
|||
|
||||
Update the `header` and `cell` definitions for amount as follows:
|
||||
|
||||
|
||||
```ts:line-numbers title="components/payments/columns.ts" {5-17}
|
||||
import { h } from 'vue'
|
||||
|
||||
|
|
@ -345,7 +341,6 @@ function copy(id: string) {
|
|||
|
||||
Update our columns definition to add a new `actions` column. The `actions` cell returns a `<Dropdown />` component.
|
||||
|
||||
|
||||
```ts:line-numbers showLineNumber{2,6-16}
|
||||
import { ColumnDef } from "@tanstack/vue-table"
|
||||
import DropdownAction from '@/components/DataTableDropDown.vue'
|
||||
|
|
@ -459,7 +454,6 @@ Let's make the email column sortable.
|
|||
```ts:line-numbers {5,6,12-17}
|
||||
import { type ClassValue, clsx } from 'clsx'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { camelize, getCurrentInstance, toHandlerKey } from 'vue'
|
||||
|
||||
import type { Updater } from '@tanstack/vue-table'
|
||||
import { type Ref } from 'vue'
|
||||
|
|
@ -971,11 +965,9 @@ export const columns = [
|
|||
{
|
||||
accessorKey: "email",
|
||||
header: ({ column }) => (
|
||||
return h(DataTableColumnHeader, {
|
||||
props: {
|
||||
column: column,
|
||||
title: 'Email'
|
||||
}
|
||||
h(DataTableColumnHeader, {
|
||||
column: column,
|
||||
title: 'Email'
|
||||
})
|
||||
),
|
||||
},
|
||||
|
|
|
|||
63
apps/www/src/content/docs/components/drawer.md
Normal file
63
apps/www/src/content/docs/components/drawer.md
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
---
|
||||
title: Drawer
|
||||
description: A drawer component for vue.
|
||||
source: apps/www/src/lib/registry/default/ui/drawer
|
||||
primitive: https://github.com/radix-vue/vaul-vue
|
||||
---
|
||||
|
||||
<ComponentPreview name="DrawerDemo" />
|
||||
|
||||
## About
|
||||
|
||||
Drawer is built on top of [Vaul Vue](https://github.com/radix-vue/vaul-vue).
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npx shadcn-vue@latest add drawer
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```vue showLineNumbers
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
Drawer,
|
||||
DrawerClose,
|
||||
DrawerContent,
|
||||
DrawerDescription,
|
||||
DrawerFooter,
|
||||
DrawerHeader,
|
||||
DrawerTitle,
|
||||
DrawerTrigger,
|
||||
} from '@/components/ui/drawer'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Drawer>
|
||||
<DrawerTrigger>Open</DrawerTrigger>
|
||||
<DrawerContent>
|
||||
<DrawerHeader>
|
||||
<DrawerTitle>Are you absolutely sure?</DrawerTitle>
|
||||
<DrawerDescription>This action cannot be undone.</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
<DrawerFooter>
|
||||
<Button>Submit</Button>
|
||||
<DrawerClose>
|
||||
<Button variant="outline">
|
||||
Cancel
|
||||
</Button>
|
||||
</DrawerClose>
|
||||
</DrawerFooter>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Responsive Dialog
|
||||
|
||||
You can combine the `Dialog` and `Drawer` components to create a responsive dialog. This renders a `Dialog` component on desktop and a `Drawer` on mobile.
|
||||
|
||||
<ComponentPreview name="DrawerDialog" />
|
||||
|
|
@ -1,18 +1,17 @@
|
|||
---
|
||||
title: Dropdown Menu
|
||||
description: Displays a menu to the user — such as a set of actions or functions — triggered by a button.
|
||||
source: apps/www/src/lib/registry/default/ui/dropdown-menu
|
||||
source: apps/www/src/lib/registry/default/ui/dropdown-menu
|
||||
primitive: https://www.radix-vue.com/components/dropdown-menu.html
|
||||
---
|
||||
|
||||
|
||||
<ComponentPreview name="DropdownMenuDemo" />
|
||||
<ComponentPreview name="DropdownMenuDemo" />
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npx shadcn-vue@latest add dropdown-menu
|
||||
```
|
||||
```
|
||||
## Usage
|
||||
|
||||
```vue
|
||||
|
|
@ -40,4 +39,14 @@ import {
|
|||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</template>
|
||||
```
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Checkboxes
|
||||
|
||||
<ComponentPreview name="DropdownMenuCheckboxes" />
|
||||
|
||||
### Radio Group
|
||||
|
||||
<ComponentPreview name="DropdownMenuRadioGroup" />
|
||||
|
|
|
|||
|
|
@ -16,12 +16,10 @@ Well-designed HTML forms are:
|
|||
|
||||
In this guide, we will take a look at building forms with [`vee-validate`](https://vee-validate.logaretm.com/v4/) and [`zod`](https://zod.dev). We're going to use a `<FormField>` component to compose accessible forms using Radix Vue components.
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
The `<Form />` component is a wrapper around the `vee-validate` library. It provides a few things:
|
||||
|
||||
|
||||
- Composable components for building forms.
|
||||
- A `<FormField />` component for building controlled form fields.
|
||||
- Form validation using `zod`.
|
||||
|
|
@ -53,7 +51,6 @@ The `<Form />` component is a wrapper around the `vee-validate` library. It prov
|
|||
|
||||
## Example
|
||||
|
||||
|
||||
<TabPreview name="Component" :names="['Component', 'Native']">
|
||||
<template #Component>
|
||||
|
||||
|
|
@ -170,12 +167,10 @@ const formSchema = toTypedSchema(z.object({
|
|||
</script>
|
||||
```
|
||||
|
||||
|
||||
### Define a form
|
||||
|
||||
Use the `useForm` composable from `vee-validate` or use `<Form />` component to create a form.
|
||||
|
||||
|
||||
<TabPreview name="Composition" :names="['Composition', 'Component']">
|
||||
<template #Composition>
|
||||
|
||||
|
|
@ -327,11 +322,11 @@ See the following links for more examples on how to use the `vee-validate` featu
|
|||
- [Input](/docs/components/input#form)
|
||||
- [Radio Group](/docs/components/radio-group#form)
|
||||
- [Select](/docs/components/select#form)
|
||||
- [Slider](/docs/components/slider#form)
|
||||
- [Switch](/docs/components/switch#form)
|
||||
- [Textarea](/docs/components/textarea#form)
|
||||
- [Combobox](/docs/components/combobox#form)
|
||||
|
||||
|
||||
## Extras
|
||||
|
||||
This example shows how to add motion to your forms with [Formkit AutoAnimate](https://auto-animate.formkit.com/)
|
||||
|
|
|
|||
|
|
@ -3,8 +3,7 @@ title: Input
|
|||
description: Displays a form input field or a component that looks like an input field.
|
||||
---
|
||||
|
||||
|
||||
<ComponentPreview name="InputDemo" class="max-w-xs" />
|
||||
<ComponentPreview name="InputDemo" class="max-w-xs" />
|
||||
|
||||
## Installation
|
||||
|
||||
|
|
@ -26,8 +25,6 @@ npx shadcn-vue@latest add input
|
|||
|
||||
</Steps>
|
||||
|
||||
|
||||
|
||||
</template>
|
||||
</TabPreview>
|
||||
|
||||
|
|
@ -43,6 +40,8 @@ import { Input } from '@/components/ui/input'
|
|||
</template>
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Default
|
||||
|
||||
<ComponentPreview name="InputDemo" class="max-w-xs" />
|
||||
|
|
|
|||
|
|
@ -5,8 +5,7 @@ source: apps/www/src/lib/registry/default/ui/pin-input
|
|||
primitive: https://www.radix-vue.com/components/pin-input.html
|
||||
---
|
||||
|
||||
<ComponentPreview name="PinInputDemo" />
|
||||
|
||||
<ComponentPreview name="PinInputDemo" />
|
||||
|
||||
## Installation
|
||||
|
||||
|
|
@ -16,6 +15,18 @@ npx shadcn-vue@latest add pin-input
|
|||
|
||||
## Usage
|
||||
|
||||
### Controlled
|
||||
|
||||
<ComponentPreview name="PinInputControlled" />
|
||||
|
||||
### Disabled
|
||||
|
||||
<ComponentPreview name="PinInputDisabled" />
|
||||
|
||||
### Separator
|
||||
|
||||
<ComponentPreview name="PinInputSeparatorDemo" />
|
||||
|
||||
### Form
|
||||
|
||||
<ComponentPreview name="PinInputFormDemo" />
|
||||
<ComponentPreview name="PinInputFormDemo" />
|
||||
|
|
|
|||
117
apps/www/src/content/docs/components/resizable.md
Normal file
117
apps/www/src/content/docs/components/resizable.md
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
---
|
||||
title: Resizable
|
||||
description: Accessible resizable panel groups and layouts with keyboard support.
|
||||
source: apps/www/src/lib/registry/default/ui/resizable
|
||||
primitive: https://www.radix-vue.com/components/splitter.html
|
||||
---
|
||||
|
||||
<ComponentPreview name="ResizableDemo" />
|
||||
|
||||
## Installation
|
||||
|
||||
<TabPreview name="CLI">
|
||||
<template #CLI>
|
||||
|
||||
```bash
|
||||
npx shadcn-vue@latest add resizable
|
||||
```
|
||||
</template>
|
||||
|
||||
<template #Manual>
|
||||
|
||||
<Steps>
|
||||
|
||||
### Install the following dependency:
|
||||
|
||||
```bash
|
||||
npm install radix-vue
|
||||
```
|
||||
|
||||
### Copy and paste the following code into your project:
|
||||
|
||||
`index.ts`
|
||||
|
||||
<<< @/lib/registry/default/ui/resizable/index.ts
|
||||
|
||||
`ResizablePanelGroup.vue`
|
||||
|
||||
<<< @/lib/registry/default/ui/resizable/ResizablePanelGroup.vue
|
||||
|
||||
`ResizableHandle.vue`
|
||||
|
||||
<<< @/lib/registry/default/ui/resizable/ResizableHandle.vue
|
||||
|
||||
</Steps>
|
||||
|
||||
</template>
|
||||
</TabPreview>
|
||||
|
||||
## Usage
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ResizableHandle,
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup,
|
||||
} from '@/components/ui/resizable'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ResizablePanelGroup direction="horizontal">
|
||||
<ResizablePanel>One</ResizablePanel>
|
||||
<ResizableHandle />
|
||||
<ResizablePanel>Two</ResizablePanel>
|
||||
</ResizablePanelGroup>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Vertical
|
||||
|
||||
Use the direction prop to set the direction of the resizable panels.
|
||||
|
||||
<ComponentPreview name="ResizableVerticalDemo" />
|
||||
|
||||
```vue:line-numbers {10}
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ResizableHandle,
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup,
|
||||
} from '@/components/ui/resizable'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ResizablePanelGroup direction="horizontal">
|
||||
<ResizablePanel>One</ResizablePanel>
|
||||
<ResizableHandle />
|
||||
<ResizablePanel>Two</ResizablePanel>
|
||||
</ResizablePanelGroup>
|
||||
</template>
|
||||
```
|
||||
|
||||
### Handle
|
||||
|
||||
You can set or hide the handle by using the withHandle prop on the ResizableHandle component.
|
||||
|
||||
<ComponentPreview name="ResizableHandleDemo" />
|
||||
|
||||
```vue:line-numbers {12}
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ResizableHandle,
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup,
|
||||
} from '@/components/ui/resizable'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ResizablePanelGroup direction="horizontal">
|
||||
<ResizablePanel>One</ResizablePanel>
|
||||
<ResizableHandle with-handle />
|
||||
<ResizablePanel>Two</ResizablePanel>
|
||||
</ResizablePanelGroup>
|
||||
</template>
|
||||
```
|
||||
|
|
@ -1,16 +1,14 @@
|
|||
---
|
||||
title: Select
|
||||
description: Displays a list of options for the user to pick from—triggered by a button.
|
||||
source: apps/www/src/lib/registry/default/ui/select
|
||||
source: apps/www/src/lib/registry/default/ui/select
|
||||
primitive: https://www.radix-vue.com/components/select.html
|
||||
---
|
||||
|
||||
|
||||
<ComponentPreview name="SelectDemo" />
|
||||
<ComponentPreview name="SelectDemo" />
|
||||
|
||||
## Installation
|
||||
|
||||
|
||||
```bash
|
||||
npx shadcn-vue@latest add select
|
||||
```
|
||||
|
|
@ -49,6 +47,10 @@ import {
|
|||
|
||||
## Examples
|
||||
|
||||
### Scrollable
|
||||
|
||||
<ComponentPreview name="SelectScrollable" />
|
||||
|
||||
### Form
|
||||
|
||||
<ComponentPreview name="SelectForm" />
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
---
|
||||
title: Skeleton
|
||||
description: Use to show a placeholder while content is loading.
|
||||
description: Use to show a placeholder while content is loading.
|
||||
---
|
||||
|
||||
<ComponentPreview name="SkeletonDemo" />
|
||||
<ComponentPreview name="SkeletonDemo" />
|
||||
|
||||
## Installation
|
||||
|
||||
|
|
@ -21,7 +21,6 @@ npx shadcn-vue@latest add skeleton
|
|||
|
||||
### Copy and paste the following code into your project
|
||||
|
||||
|
||||
<<< @/lib/registry/default/ui/skeleton/Skeleton.vue
|
||||
|
||||
</Steps>
|
||||
|
|
@ -40,3 +39,9 @@ import { Skeleton } from '@/components/ui/skeleton'
|
|||
<Skeleton class="w-[100px] h-5 rounded-full" />
|
||||
</template>
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Card
|
||||
|
||||
<ComponentPreview name="SkeletonCard" />
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
---
|
||||
title: Slider
|
||||
description: An input where the user selects a value from within a given range.
|
||||
source: apps/www/src/lib/registry/default/ui/slider
|
||||
source: apps/www/src/lib/registry/default/ui/slider
|
||||
primitive: https://www.radix-vue.com/components/slider.html
|
||||
---
|
||||
|
||||
<ComponentPreview name="SliderDemo" />
|
||||
<ComponentPreview name="SliderDemo" />
|
||||
|
||||
## Installation
|
||||
|
||||
|
|
@ -25,4 +25,10 @@ import { Slider } from '@/components/ui/slider'
|
|||
:default-value="[33]" :max="100" :step="1"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Form
|
||||
|
||||
<ComponentPreview name="SliderForm" />
|
||||
|
|
|
|||
39
apps/www/src/content/docs/dark-mode.md
Normal file
39
apps/www/src/content/docs/dark-mode.md
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
---
|
||||
title: Dark Mode
|
||||
description: Adding dark mode to your site.
|
||||
---
|
||||
|
||||
<script setup>
|
||||
import { useData } from 'vitepress'
|
||||
const { isDark } = useData()
|
||||
import ViteIcon from '~icons/simple-icons/vite'
|
||||
import NuxtIcon from '~icons/simple-icons/nuxtdotjs'
|
||||
import AstroIcon from '~icons/simple-icons/astro'
|
||||
</script>
|
||||
|
||||
<div class="grid gap-4 mt-8 sm:grid-cols-2 sm:gap-6 not-docs">
|
||||
<LinkedCard href="/docs/dark-mode/vite">
|
||||
<ViteIcon class="w-10 h-10" />
|
||||
<p class="mt-2 font-medium">Vite</p>
|
||||
</LinkedCard>
|
||||
|
||||
<LinkedCard href="/docs/dark-mode/nuxt">
|
||||
<NuxtIcon class="w-10 h-10" />
|
||||
<p class="mt-2 font-medium">Nuxt</p>
|
||||
</LinkedCard>
|
||||
|
||||
<LinkedCard href="/docs/dark-mode/vitepress">
|
||||
<svg width="48" height="48" viewBox="0 0 48 48" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.03628 7.87818C4.75336 5.83955 6.15592 3.95466 8.16899 3.66815L33.6838 0.0367403C35.6969 -0.24977 37.5581 1.1706 37.841 3.20923L42.9637 40.1218C43.2466 42.1604 41.8441 44.0453 39.831 44.3319L14.3162 47.9633C12.3031 48.2498 10.4419 46.8294 10.159 44.7908L5.03628 7.87818Z" />
|
||||
<path d="M6.85877 7.6188C6.71731 6.59948 7.41859 5.65703 8.42512 5.51378L33.9399 1.88237C34.9465 1.73911 35.8771 2.4493 36.0186 3.46861L41.1412 40.3812C41.2827 41.4005 40.5814 42.343 39.5749 42.4862L14.0601 46.1176C13.0535 46.2609 12.1229 45.5507 11.9814 44.5314L6.85877 7.6188Z" class="fill-background"/>
|
||||
<path d="M33.1857 14.9195L25.8505 34.1576C25.6991 34.5547 25.1763 34.63 24.9177 34.2919L12.3343 17.8339C12.0526 17.4655 12.3217 16.9339 12.7806 16.9524L22.9053 17.3607C22.9698 17.3633 23.0344 17.3541 23.0956 17.3337L32.5088 14.1992C32.9431 14.0546 33.3503 14.4878 33.1857 14.9195Z" />
|
||||
<path d="M27.0251 12.5756L19.9352 15.0427C19.8187 15.0832 19.7444 15.1986 19.7546 15.3231L20.3916 23.063C20.4066 23.2453 20.5904 23.3628 20.7588 23.2977L22.7226 22.5392C22.9064 22.4682 23.1021 22.6138 23.0905 22.8128L22.9102 25.8903C22.8982 26.0974 23.1093 26.2436 23.295 26.1567L24.4948 25.5953C24.6808 25.5084 24.892 25.6549 24.8795 25.8624L24.5855 30.6979C24.5671 31.0004 24.9759 31.1067 25.1013 30.8321L25.185 30.6487L29.4298 17.8014C29.5008 17.5863 29.2968 17.3809 29.0847 17.454L27.0519 18.1547C26.8609 18.2205 26.6675 18.0586 26.6954 17.8561L27.3823 12.8739C27.4103 12.6712 27.2163 12.5091 27.0251 12.5756Z" class="stroke-background"/>
|
||||
</svg>
|
||||
<p class="mt-2 font-medium">Vitepress</p>
|
||||
</LinkedCard>
|
||||
|
||||
<LinkedCard href="/docs/dark-mode/astro">
|
||||
<AstroIcon class="w-10 h-10" />
|
||||
<p class="mt-2 font-medium">Astro</p>
|
||||
</LinkedCard>
|
||||
</div>
|
||||
116
apps/www/src/content/docs/dark-mode/astro.md
Normal file
116
apps/www/src/content/docs/dark-mode/astro.md
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
---
|
||||
title: Astro
|
||||
description: Adding dark mode to your astro app.
|
||||
---
|
||||
|
||||
## Dark mode
|
||||
|
||||
<Steps>
|
||||
|
||||
### Create an inline theme script
|
||||
|
||||
```astro title="src/pages/index.astro"
|
||||
---
|
||||
import '../styles/globals.css'
|
||||
---
|
||||
|
||||
<script is:inline>
|
||||
const getThemePreference = () => {
|
||||
if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) {
|
||||
return localStorage.getItem('theme');
|
||||
}
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||
};
|
||||
const isDark = getThemePreference() === 'dark';
|
||||
document.documentElement.classList[isDark ? 'add' : 'remove']('dark');
|
||||
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
const observer = new MutationObserver(() => {
|
||||
const isDark = document.documentElement.classList.contains('dark');
|
||||
localStorage.setItem('theme', isDark ? 'dark' : 'light');
|
||||
});
|
||||
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
|
||||
}
|
||||
</script>
|
||||
|
||||
<html lang="en">
|
||||
<body>
|
||||
<h1>Astro</h1>
|
||||
</body>
|
||||
</html>
|
||||
</script>
|
||||
```
|
||||
|
||||
### Install Dependencies
|
||||
|
||||
```bash
|
||||
npm install @vueuse/core
|
||||
```
|
||||
|
||||
Optional, to include icons for theme button.
|
||||
```bash
|
||||
npm install -D @iconify/vue @iconify-json/radix-icons
|
||||
```
|
||||
|
||||
### Add a mode toggle
|
||||
|
||||
Place a mode toggle on your site to toggle between light and dark mode.
|
||||
|
||||
We're using [`useColorMode`](https://vueuse.org/core/usecolormode/) from [`@vueuse/core`](https://vueuse.org/core/).
|
||||
> Reactive color mode (dark / light / customs) with auto data persistence.
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { useColorMode } from '@vueuse/core'
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'
|
||||
|
||||
const mode = useColorMode()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<Button variant="outline">
|
||||
<Icon icon="radix-icons:moon" class="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
||||
<Icon icon="radix-icons:sun" class="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
||||
<span class="sr-only">Toggle theme</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem @click="mode = 'light'">
|
||||
Light
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem @click="mode = 'dark'">
|
||||
Dark
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem @click="mode = 'auto'">
|
||||
System
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</template>
|
||||
```
|
||||
|
||||
### Display the mode toggle
|
||||
|
||||
Place a mode toggle on your site to toggle between light and dark mode.
|
||||
|
||||
```astro title="src/pages/index.astro"
|
||||
---
|
||||
import '../styles/globals.css'
|
||||
import { ModeToggle } from '@/components/ModeToggle.vue';
|
||||
---
|
||||
|
||||
<!-- Inline script -->
|
||||
|
||||
<html lang="en">
|
||||
<body>
|
||||
<h1>Astro</h1>
|
||||
<ModeToggle client:load />
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
</Steps>
|
||||
74
apps/www/src/content/docs/dark-mode/nuxt.md
Normal file
74
apps/www/src/content/docs/dark-mode/nuxt.md
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
---
|
||||
title: Nuxt
|
||||
description: Adding dark mode to your nuxt app.
|
||||
---
|
||||
|
||||
## Dark mode
|
||||
|
||||
<Steps>
|
||||
|
||||
### Install Dependencies
|
||||
|
||||
```bash
|
||||
npm install -D @nuxtjs/color-mode
|
||||
```
|
||||
|
||||
Then, add `@nuxtjs/color-mode` to the modules section of your `nuxt.config.ts`
|
||||
|
||||
```ts
|
||||
export default defineNuxtConfig({
|
||||
modules: [
|
||||
'@nuxtjs/tailwindcss',
|
||||
'@nuxtjs/color-mode'
|
||||
],
|
||||
colorMode: {
|
||||
classSuffix: ''
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Optional, to include icons for theme button.
|
||||
```bash
|
||||
npm install -D @iconify/vue @iconify-json/radix-icons
|
||||
```
|
||||
|
||||
### Add a mode toggle
|
||||
|
||||
Place a mode toggle on your site to toggle between light and dark mode.
|
||||
|
||||
We're using [`useColorMode`](https://color-mode.nuxtjs.org/#usage) from [`Nuxt Color Mode`](https://color-mode.nuxtjs.org/).
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'
|
||||
|
||||
const colorMode = useColorMode()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<Button variant="outline">
|
||||
<Icon icon="radix-icons:moon" class="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
||||
<Icon icon="radix-icons:sun" class="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
||||
<span class="sr-only">Toggle theme</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem @click="colorMode.preference = 'light'">
|
||||
Light
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem @click="colorMode.preference = 'dark'">
|
||||
Dark
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem @click="colorMode.preference = 'system'">
|
||||
System
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</template>
|
||||
```
|
||||
|
||||
</Steps>
|
||||
62
apps/www/src/content/docs/dark-mode/vite.md
Normal file
62
apps/www/src/content/docs/dark-mode/vite.md
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
---
|
||||
title: Vite
|
||||
description: Adding dark mode to your vite app.
|
||||
---
|
||||
|
||||
## Dark mode
|
||||
|
||||
<Steps>
|
||||
|
||||
### Install Dependencies
|
||||
|
||||
```bash
|
||||
npm install @vueuse/core
|
||||
```
|
||||
|
||||
Optional, to include icons for theme button.
|
||||
```bash
|
||||
npm install -D @iconify/vue @iconify-json/radix-icons
|
||||
```
|
||||
|
||||
### Add a mode toggle
|
||||
|
||||
Place a mode toggle on your site to toggle between light and dark mode.
|
||||
|
||||
We're using [`useColorMode`](https://vueuse.org/core/usecolormode/) from [`@vueuse/core`](https://vueuse.org/core/).
|
||||
> Reactive color mode (dark / light / customs) with auto data persistence.
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { useColorMode } from '@vueuse/core'
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'
|
||||
|
||||
const mode = useColorMode()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<Button variant="outline">
|
||||
<Icon icon="radix-icons:moon" class="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
||||
<Icon icon="radix-icons:sun" class="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
||||
<span class="sr-only">Toggle theme</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem @click="mode = 'light'">
|
||||
Light
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem @click="mode = 'dark'">
|
||||
Dark
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem @click="mode = 'auto'">
|
||||
System
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</template>
|
||||
```
|
||||
|
||||
</Steps>
|
||||
47
apps/www/src/content/docs/dark-mode/vitepress.md
Normal file
47
apps/www/src/content/docs/dark-mode/vitepress.md
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
title: Vitepress
|
||||
description: Adding dark mode to your vitepress app.
|
||||
---
|
||||
|
||||
## Dark mode
|
||||
|
||||
<Steps>
|
||||
|
||||
### Install Dependencies
|
||||
|
||||
```bash
|
||||
npm install @vueuse/core
|
||||
```
|
||||
|
||||
Optional, to include icons for theme button.
|
||||
```bash
|
||||
npm install -D @iconify/vue @iconify-json/radix-icons
|
||||
```
|
||||
|
||||
### Add a mode toggle
|
||||
|
||||
Place a mode toggle on your site to toggle between light and dark mode.
|
||||
|
||||
We're using [`useToggle`](https://vueuse.org/shared/useToggle/) from [`@vueuse/core`](https://vueuse.org/core/).
|
||||
> A boolean switcher with utility functions.
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { useData } from 'vitepress'
|
||||
import { useToggle } from '@vueuse/core'
|
||||
import { Button } from '@/lib/registry/default/ui/button'
|
||||
|
||||
const { frontmatter, isDark } = useData()
|
||||
const toggleDark = useToggle(isDark)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Button variant="outline">
|
||||
<Icon icon="radix-icons:moon" class="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
||||
<Icon icon="radix-icons:sun" class="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
||||
<span class="sr-only">Toggle theme</span>
|
||||
</Button>
|
||||
</template>
|
||||
```
|
||||
|
||||
</Steps>
|
||||
|
|
@ -21,8 +21,6 @@ npm create vite@latest my-vue-app -- --template vue-ts
|
|||
|
||||
Install `tailwindcss` and its peer dependencies, then generate your `tailwind.config.js` and configure `postcss` plugins
|
||||
|
||||
|
||||
|
||||
<TabsMarkdown>
|
||||
<TabMarkdown title="vite.config">
|
||||
|
||||
|
|
@ -59,7 +57,6 @@ Install `tailwindcss` and its peer dependencies, then generate your `tailwind.co
|
|||
|
||||
</TabMarkdown>
|
||||
|
||||
|
||||
<TabMarkdown title="postcss.config.js">
|
||||
|
||||
```bash
|
||||
|
|
@ -80,7 +77,6 @@ Install `tailwindcss` and its peer dependencies, then generate your `tailwind.co
|
|||
</TabMarkdown>
|
||||
</TabsMarkdown>
|
||||
|
||||
|
||||
### Edit tsconfig.json
|
||||
|
||||
Add the code below to the compilerOptions of your tsconfig.json so your app can resolve paths without error
|
||||
|
|
@ -107,11 +103,14 @@ Add the code below to the vite.config.ts so your app can resolve paths without e
|
|||
npm i -D @types/node
|
||||
```
|
||||
|
||||
```typescript {12-16}
|
||||
```typescript {15-19}
|
||||
import path from "path"
|
||||
import vue from "@vitejs/plugin-vue"
|
||||
import { defineConfig } from "vite"
|
||||
|
||||
import tailwind from "tailwindcss"
|
||||
import autoprefixer from "autoprefixer"
|
||||
|
||||
export default defineConfig({
|
||||
css: {
|
||||
postcss: {
|
||||
|
|
@ -148,7 +147,7 @@ Where is your global CSS file? › › src/index.css
|
|||
Do you want to use CSS variables for colors? › no / yes
|
||||
Where is your tailwind.config.js located? › tailwind.config.js
|
||||
Configure the import alias for components: › @/components
|
||||
Configure the import alias for utils: › @/lib/utils
|
||||
Configure the import alias for utils: › @/lib/utils
|
||||
```
|
||||
|
||||
### That's it
|
||||
|
|
|
|||
5
apps/www/src/content/examples/mail.md
Normal file
5
apps/www/src/content/examples/mail.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<script setup>
|
||||
import MailExample from "@/examples/mail/Example.vue"
|
||||
</script>
|
||||
|
||||
<MailExample />
|
||||
|
|
@ -72,7 +72,7 @@ const onSubmit = handleSubmit((values) => {
|
|||
</option>
|
||||
</select>
|
||||
</FormControl>
|
||||
<ChevronDownIcon class="absolute right-3 top-2.5 h-4 w-4 opacity-50" />
|
||||
<ChevronDownIcon class="pointer-events-none absolute right-3 top-2.5 h-4 w-4 opacity-50" />
|
||||
</div>
|
||||
<FormDescription>
|
||||
Set the font you want to use in the dashboard.
|
||||
|
|
|
|||
30
apps/www/src/examples/mail/Example.vue
Normal file
30
apps/www/src/examples/mail/Example.vue
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<script lang="ts" setup>
|
||||
import Mail from './components/Mail.vue'
|
||||
import { accounts, mails } from './data/mails'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="md:hidden">
|
||||
<image
|
||||
src="/examples/mail-dark.png"
|
||||
:width="1280"
|
||||
:height="727"
|
||||
alt="Mail"
|
||||
class="hidden dark:block"
|
||||
/>
|
||||
<image
|
||||
src="/examples/mail-light.png"
|
||||
:width="1280"
|
||||
:height="727"
|
||||
alt="Mail"
|
||||
class="block dark:hidden"
|
||||
/>
|
||||
</div>
|
||||
<div class="hidden flex-col md:flex">
|
||||
<Mail
|
||||
:accounts="accounts"
|
||||
:mails="mails"
|
||||
:nav-collapsed-size="4"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
49
apps/www/src/examples/mail/components/AccountSwitcher.vue
Normal file
49
apps/www/src/examples/mail/components/AccountSwitcher.vue
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/lib/registry/new-york/ui/select'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface AccountSwitcherProps {
|
||||
isCollapsed: boolean
|
||||
accounts: {
|
||||
label: string
|
||||
email: string
|
||||
icon: string
|
||||
}[]
|
||||
}
|
||||
|
||||
const props = defineProps<AccountSwitcherProps>()
|
||||
|
||||
const selectedEmail = ref<string>(props.accounts[0].email)
|
||||
const selectedEmailData = computed(() => props.accounts.find(item => item.email === selectedEmail.value))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Select v-model="selectedEmail">
|
||||
<SelectTrigger
|
||||
aria-label="Select account"
|
||||
:class="cn(
|
||||
'flex items-center gap-2 [&>span]:line-clamp-1 [&>span]:flex [&>span]:w-full [&>span]:items-center [&>span]:gap-1 [&>span]:truncate [&_svg]:h-4 [&_svg]:w-4 [&_svg]:shrink-0',
|
||||
{ 'flex h-9 w-9 shrink-0 items-center justify-center p-0 [&>span]:w-auto [&>svg]:hidden': isCollapsed },
|
||||
)"
|
||||
>
|
||||
<SelectValue placeholder="Select an account">
|
||||
<div class="flex items-center gap-3">
|
||||
<Icon class="size-4" :icon="selectedEmailData!.icon" />
|
||||
<span v-if="!isCollapsed">
|
||||
{{ selectedEmailData!.label }}
|
||||
</span>
|
||||
</div>
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="account of accounts" :key="account.email" :value="account.email">
|
||||
<div class="flex items-center gap-3 [&_svg]:size-4 [&_svg]:shrink-0 [&_svg]:text-foreground">
|
||||
<Icon class="size-4" :icon="account.icon" />
|
||||
{{ account.email }}
|
||||
</div>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</template>
|
||||
223
apps/www/src/examples/mail/components/Mail.vue
Normal file
223
apps/www/src/examples/mail/components/Mail.vue
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
<script lang="ts" setup>
|
||||
import {
|
||||
Search,
|
||||
} from 'lucide-vue-next'
|
||||
|
||||
import { computed, ref } from 'vue'
|
||||
import { refDebounced } from '@vueuse/core'
|
||||
import type { Mail } from '../data/mails'
|
||||
import AccountSwitcher from './AccountSwitcher.vue'
|
||||
import MailList from './MailList.vue'
|
||||
import MailDisplay from './MailDisplay.vue'
|
||||
import Nav, { type LinkProp } from './Nav.vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Separator } from '@/lib/registry/new-york/ui/separator'
|
||||
import { Input } from '@/lib/registry/new-york/ui/input'
|
||||
import {
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from '@/lib/registry/new-york/ui/tabs'
|
||||
import { TooltipProvider } from '@/lib/registry/new-york/ui/tooltip'
|
||||
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@/lib/registry/new-york/ui/resizable'
|
||||
|
||||
interface MailProps {
|
||||
accounts: {
|
||||
label: string
|
||||
email: string
|
||||
icon: string
|
||||
}[]
|
||||
mails: Mail[]
|
||||
defaultLayout?: number[]
|
||||
defaultCollapsed?: boolean
|
||||
navCollapsedSize: number
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<MailProps>(), {
|
||||
defaultCollapsed: false,
|
||||
defaultLayout: () => [265, 440, 655],
|
||||
})
|
||||
|
||||
const isCollapsed = ref(props.defaultCollapsed)
|
||||
const selectedMail = ref<string | undefined>(props.mails[0].id)
|
||||
const searchValue = ref('')
|
||||
const debouncedSearch = refDebounced(searchValue, 250)
|
||||
|
||||
const filteredMailList = computed(() => {
|
||||
let output: Mail[] = []
|
||||
const serachValue = debouncedSearch.value?.trim()
|
||||
if (!serachValue) {
|
||||
output = props.mails
|
||||
}
|
||||
|
||||
else {
|
||||
output = props.mails.filter((item) => {
|
||||
return item.name.includes(debouncedSearch.value)
|
||||
|| item.email.includes(debouncedSearch.value)
|
||||
|| item.name.includes(debouncedSearch.value)
|
||||
|| item.subject.includes(debouncedSearch.value)
|
||||
|| item.text.includes(debouncedSearch.value)
|
||||
})
|
||||
}
|
||||
|
||||
return output
|
||||
})
|
||||
|
||||
const unreadMailList = computed(() => filteredMailList.value.filter(item => !item.read))
|
||||
|
||||
const selectedMailData = computed(() => props.mails.find(item => item.id === selectedMail.value))
|
||||
|
||||
const links: LinkProp[] = [
|
||||
{
|
||||
title: 'Inbox',
|
||||
label: '128',
|
||||
icon: 'lucide:inbox',
|
||||
variant: 'default',
|
||||
},
|
||||
{
|
||||
title: 'Drafts',
|
||||
label: '9',
|
||||
icon: 'lucide:file',
|
||||
variant: 'ghost',
|
||||
},
|
||||
{
|
||||
title: 'Sent',
|
||||
label: '',
|
||||
icon: 'lucide:send',
|
||||
variant: 'ghost',
|
||||
},
|
||||
{
|
||||
title: 'Junk',
|
||||
label: '23',
|
||||
icon: 'lucide:archive',
|
||||
variant: 'ghost',
|
||||
},
|
||||
{
|
||||
title: 'Trash',
|
||||
label: '',
|
||||
icon: 'lucide:trash',
|
||||
variant: 'ghost',
|
||||
},
|
||||
{
|
||||
title: 'Archive',
|
||||
label: '',
|
||||
icon: 'lucide:archive',
|
||||
variant: 'ghost',
|
||||
},
|
||||
]
|
||||
|
||||
const links2: LinkProp[] = [
|
||||
{
|
||||
title: 'Social',
|
||||
label: '972',
|
||||
icon: 'lucide:user-2',
|
||||
variant: 'ghost',
|
||||
},
|
||||
{
|
||||
title: 'Updates',
|
||||
label: '342',
|
||||
icon: 'lucide:alert-circle',
|
||||
variant: 'ghost',
|
||||
},
|
||||
{
|
||||
title: 'Forums',
|
||||
label: '128',
|
||||
icon: 'lucide:message-square',
|
||||
variant: 'ghost',
|
||||
},
|
||||
{
|
||||
title: 'Shopping',
|
||||
label: '8',
|
||||
icon: 'lucide:shopping-cart',
|
||||
variant: 'ghost',
|
||||
},
|
||||
{
|
||||
title: 'Promotions',
|
||||
label: '21',
|
||||
icon: 'lucide:archive',
|
||||
variant: 'ghost',
|
||||
},
|
||||
]
|
||||
|
||||
function onCollapse() {
|
||||
isCollapsed.value = true
|
||||
}
|
||||
|
||||
function onExpand() {
|
||||
isCollapsed.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TooltipProvider :delay-duration="0">
|
||||
<ResizablePanelGroup
|
||||
id="resize-panel-group-1"
|
||||
direction="horizontal"
|
||||
class="h-full max-h-[800px] items-stretch"
|
||||
>
|
||||
<ResizablePanel
|
||||
id="resize-panel-1"
|
||||
:default-size="defaultLayout[0]"
|
||||
:collapsed-size="navCollapsedSize"
|
||||
collapsible
|
||||
:min-size="15"
|
||||
:max-size="20"
|
||||
:class="cn(isCollapsed && 'min-w-[50px] transition-all duration-300 ease-in-out')"
|
||||
@expand="onExpand"
|
||||
@collapse="onCollapse"
|
||||
>
|
||||
<div :class="cn('flex h-[52px] items-center justify-center', isCollapsed ? 'h-[52px]' : 'px-2')">
|
||||
<AccountSwitcher :is-collapsed="isCollapsed" :accounts="accounts" />
|
||||
</div>
|
||||
<Separator />
|
||||
<Nav
|
||||
:is-collapsed="isCollapsed"
|
||||
:links="links"
|
||||
/>
|
||||
<Separator />
|
||||
<Nav
|
||||
:is-collapsed="isCollapsed"
|
||||
:links="links2"
|
||||
/>
|
||||
</ResizablePanel>
|
||||
<ResizableHandle id="resize-handle-1" with-handle />
|
||||
<ResizablePanel id="resize-panel-2" :default-size="defaultLayout[1]" :min-size="30">
|
||||
<Tabs default-value="all">
|
||||
<div class="flex items-center px-4 py-2">
|
||||
<h1 class="text-xl font-bold">
|
||||
Inbox
|
||||
</h1>
|
||||
<TabsList class="ml-auto">
|
||||
<TabsTrigger value="all" class="text-zinc-600 dark:text-zinc-200">
|
||||
All mail
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="unread" class="text-zinc-600 dark:text-zinc-200">
|
||||
Unread
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</div>
|
||||
<Separator />
|
||||
<div class="bg-background/95 p-4 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||
<form>
|
||||
<div class="relative">
|
||||
<Search class="absolute left-2 top-2.5 size-4 text-muted-foreground" />
|
||||
<Input v-model="searchValue" placeholder="Search" class="pl-8" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<TabsContent value="all" class="m-0">
|
||||
<MailList v-model:selected-mail="selectedMail" :items="filteredMailList" />
|
||||
</TabsContent>
|
||||
<TabsContent value="unread" class="m-0">
|
||||
<MailList v-model:selected-mail="selectedMail" :items="unreadMailList" />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</ResizablePanel>
|
||||
<ResizableHandle id="resiz-handle-2" with-handle />
|
||||
<ResizablePanel id="resize-panel-3" :default-size="defaultLayout[2]">
|
||||
<MailDisplay :mail="selectedMailData" />
|
||||
</ResizablePanel>
|
||||
</ResizablePanelGroup>
|
||||
</TooltipProvider>
|
||||
</template>
|
||||
236
apps/www/src/examples/mail/components/MailDisplay.vue
Normal file
236
apps/www/src/examples/mail/components/MailDisplay.vue
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
<script lang="ts" setup>
|
||||
import { Archive, ArchiveX, Clock, Forward, MoreVertical, Reply, ReplyAll, Trash2 } from 'lucide-vue-next'
|
||||
import { computed } from 'vue'
|
||||
import addDays from 'date-fns/addDays'
|
||||
import addHours from 'date-fns/addHours'
|
||||
import format from 'date-fns/format'
|
||||
import nextSaturday from 'date-fns/nextSaturday'
|
||||
import type { Mail } from '../data/mails'
|
||||
import { Calendar } from '@/lib/registry/new-york/ui/calendar'
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/lib/registry/new-york/ui/dropdown-menu'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/lib/registry/new-york/ui/popover'
|
||||
import { Avatar, AvatarFallback } from '@/lib/registry/new-york/ui/avatar'
|
||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||
import { Label } from '@/lib/registry/new-york/ui/label'
|
||||
import { Separator } from '@/lib/registry/new-york/ui/separator'
|
||||
import { Switch } from '@/lib/registry/new-york/ui/switch'
|
||||
import { Textarea } from '@/lib/registry/new-york/ui/textarea'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/lib/registry/new-york/ui/tooltip'
|
||||
|
||||
interface MailDisplayProps {
|
||||
mail: Mail | undefined
|
||||
}
|
||||
|
||||
const props = defineProps<MailDisplayProps>()
|
||||
|
||||
const mailFallbackName = computed(() => {
|
||||
return props.mail?.name
|
||||
.split(' ')
|
||||
.map(chunk => chunk[0])
|
||||
.join('')
|
||||
})
|
||||
|
||||
const today = new Date()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex h-full flex-col">
|
||||
<div class="flex items-center p-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
<Button variant="ghost" size="icon" :disabled="!mail">
|
||||
<Archive class="size-4" />
|
||||
<span class="sr-only">Archive</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Archive</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
<Button variant="ghost" size="icon" :disabled="!mail">
|
||||
<ArchiveX class="size-4" />
|
||||
<span class="sr-only">Move to junk</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Move to junk</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
<Button variant="ghost" size="icon" :disabled="!mail">
|
||||
<Trash2 class="size-4" />
|
||||
<span class="sr-only">Move to trash</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Move to trash</TooltipContent>
|
||||
</Tooltip>
|
||||
<Separator orientation="vertical" class="mx-1 h-6" />
|
||||
<Tooltip>
|
||||
<Popover>
|
||||
<PopoverTrigger as-child>
|
||||
<TooltipTrigger as-child>
|
||||
<Button variant="ghost" size="icon" :disabled="!mail">
|
||||
<Clock class="size-4" />
|
||||
<span class="sr-only">Snooze</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="flex w-[535px] p-0">
|
||||
<div class="flex flex-col gap-2 border-r px-2 py-4">
|
||||
<div class="px-4 text-sm font-medium">
|
||||
Snooze until
|
||||
</div>
|
||||
<div class="grid min-w-[250px] gap-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
class="justify-start font-normal"
|
||||
>
|
||||
Later today
|
||||
<span class="ml-auto text-muted-foreground">
|
||||
{{ format(addHours(today, 4), "E, h:m b") }}
|
||||
</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
class="justify-start font-normal"
|
||||
>
|
||||
Tomorrow
|
||||
<span class="ml-auto text-muted-foreground">
|
||||
{{ format(addDays(today, 1), "E, h:m b") }}
|
||||
</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
class="justify-start font-normal"
|
||||
>
|
||||
This weekend
|
||||
<span class="ml-auto text-muted-foreground">
|
||||
{{ format(nextSaturday(today), "E, h:m b") }}
|
||||
</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
class="justify-start font-normal"
|
||||
>
|
||||
Next week
|
||||
<span class="ml-auto text-muted-foreground">
|
||||
{{ format(addDays(today, 7), "E, h:m b") }}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-2">
|
||||
<Calendar />
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<TooltipContent>Snooze</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div class="ml-auto flex items-center gap-2">
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
<Button variant="ghost" size="icon" :disabled="!mail">
|
||||
<Reply class="size-4" />
|
||||
<span class="sr-only">Reply</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Reply</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
<Button variant="ghost" size="icon" :disabled="!mail">
|
||||
<ReplyAll class="size-4" />
|
||||
<span class="sr-only">Reply all</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Reply all</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
<Button variant="ghost" size="icon" :disabled="!mail">
|
||||
<Forward class="size-4" />
|
||||
<span class="sr-only">Forward</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Forward</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Separator orientation="vertical" class="mx-2 h-6" />
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<Button variant="ghost" size="icon" :disabled="!mail">
|
||||
<MoreVertical class="size-4" />
|
||||
<span class="sr-only">More</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem>Mark as unread</DropdownMenuItem>
|
||||
<DropdownMenuItem>Star thread</DropdownMenuItem>
|
||||
<DropdownMenuItem>Add label</DropdownMenuItem>
|
||||
<DropdownMenuItem>Mute thread</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<Separator />
|
||||
<div v-if="mail" class="flex flex-1 flex-col">
|
||||
<div class="flex items-start p-4">
|
||||
<div class="flex items-start gap-4 text-sm">
|
||||
<Avatar>
|
||||
<AvatarFallback>
|
||||
{{ mailFallbackName }}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div class="grid gap-1">
|
||||
<div class="font-semibold">
|
||||
{{ mail.name }}
|
||||
</div>
|
||||
<div class="line-clamp-1 text-xs">
|
||||
{{ mail.subject }}
|
||||
</div>
|
||||
<div class="line-clamp-1 text-xs">
|
||||
<span class="font-medium">Reply-To:</span> {{ mail.email }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="mail.date" class="ml-auto text-xs text-muted-foreground">
|
||||
{{ format(new Date(mail.date), "PPpp") }}
|
||||
</div>
|
||||
</div>
|
||||
<Separator />
|
||||
<div class="flex-1 whitespace-pre-wrap p-4 text-sm">
|
||||
{{ mail.text }}
|
||||
</div>
|
||||
<Separator class="mt-auto" />
|
||||
<div class="p-4">
|
||||
<form>
|
||||
<div class="grid gap-4">
|
||||
<Textarea
|
||||
class="p-4"
|
||||
:placeholder="`Reply ${mail.name}...`"
|
||||
/>
|
||||
<div class="flex items-center">
|
||||
<Label
|
||||
html-for="mute"
|
||||
class="flex items-center gap-2 text-xs font-normal"
|
||||
>
|
||||
<Switch id="mute" aria-label="Mute thread" /> Mute this
|
||||
thread
|
||||
</Label>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
class="ml-auto"
|
||||
>
|
||||
Send
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="p-8 text-center text-muted-foreground">
|
||||
No message selected
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
93
apps/www/src/examples/mail/components/MailList.vue
Normal file
93
apps/www/src/examples/mail/components/MailList.vue
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
<script lang="ts" setup>
|
||||
import { formatDistanceToNow } from 'date-fns'
|
||||
import type { Mail } from '../data/mails'
|
||||
import { ScrollArea } from '@/lib/registry/new-york/ui/scroll-area'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Badge } from '@/lib/registry/new-york/ui/badge'
|
||||
|
||||
interface MailListProps {
|
||||
items: Mail[]
|
||||
}
|
||||
|
||||
defineProps<MailListProps>()
|
||||
const selectedMail = defineModel<string>('selectedMail', { required: false })
|
||||
|
||||
function getBadgeVariantFromLabel(label: string) {
|
||||
if (['work'].includes(label.toLowerCase()))
|
||||
return 'default'
|
||||
|
||||
if (['personal'].includes(label.toLowerCase()))
|
||||
return 'outline'
|
||||
|
||||
return 'secondary'
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ScrollArea class="h-screen flex">
|
||||
<div class="flex-1 flex flex-col gap-2 p-4 pt-0">
|
||||
<TransitionGroup name="list" appear>
|
||||
<button
|
||||
v-for="item of items"
|
||||
:key="item.id"
|
||||
:class="cn(
|
||||
'flex flex-col items-start gap-2 rounded-lg border p-3 text-left text-sm transition-all hover:bg-accent',
|
||||
selectedMail === item.id && 'bg-muted',
|
||||
)"
|
||||
@click="selectedMail = item.id"
|
||||
>
|
||||
<div class="flex w-full flex-col gap-1">
|
||||
<div class="flex items-center">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="font-semibold">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
<span v-if="!item.read" class="flex h-2 w-2 rounded-full bg-blue-600" />
|
||||
</div>
|
||||
<div
|
||||
:class="cn(
|
||||
'ml-auto text-xs',
|
||||
selectedMail === item.id
|
||||
? 'text-foreground'
|
||||
: 'text-muted-foreground',
|
||||
)"
|
||||
>
|
||||
{{ formatDistanceToNow(new Date(item.date), { addSuffix: true }) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-xs font-medium">
|
||||
{{ item.subject }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="line-clamp-2 text-xs text-muted-foreground">
|
||||
{{ item.text.substring(0, 300) }}
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Badge v-for="label of item.labels" :key="label" :variant="getBadgeVariantFromLabel(label)">
|
||||
{{ label }}
|
||||
</Badge>
|
||||
</div>
|
||||
</button>
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.list-move,
|
||||
.list-enter-active,
|
||||
.list-leave-active {
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
||||
.list-enter-from,
|
||||
.list-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(15px);
|
||||
}
|
||||
|
||||
.list-leave-active {
|
||||
position: absolute;
|
||||
}
|
||||
</style>
|
||||
83
apps/www/src/examples/mail/components/Nav.vue
Normal file
83
apps/www/src/examples/mail/components/Nav.vue
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
<script lang="ts" setup>
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { buttonVariants } from '@/lib/registry/new-york/ui/button'
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from '@/lib/registry/new-york/ui/tooltip'
|
||||
|
||||
export interface LinkProp {
|
||||
title: string
|
||||
label?: string
|
||||
icon: string
|
||||
variant: 'default' | 'ghost'
|
||||
}
|
||||
|
||||
interface NavProps {
|
||||
isCollapsed: boolean
|
||||
links: LinkProp[]
|
||||
}
|
||||
|
||||
defineProps<NavProps>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:data-collapsed="isCollapsed"
|
||||
class="group flex flex-col gap-4 py-2 data-[collapsed=true]:py-2"
|
||||
>
|
||||
<nav class="grid gap-1 px-2 group-[[data-collapsed=true]]:justify-center group-[[data-collapsed=true]]:px-2">
|
||||
<template v-for="(link, index) of links">
|
||||
<Tooltip v-if="isCollapsed" :key="`1-${index}`" :delay-duration="0">
|
||||
<TooltipTrigger as-child>
|
||||
<a
|
||||
href="#"
|
||||
:class="cn(
|
||||
buttonVariants({ variant: link.variant, size: 'icon' }),
|
||||
'h-9 w-9',
|
||||
link.variant === 'default'
|
||||
&& 'dark:bg-muted dark:text-muted-foreground dark:hover:bg-muted dark:hover:text-white',
|
||||
)"
|
||||
>
|
||||
<Icon :icon="link.icon" class="size-4" />
|
||||
<span class="sr-only">{{ link.title }}</span>
|
||||
</a>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right" class="flex items-center gap-4">
|
||||
{{ link.title }}
|
||||
<span v-if="link.label" class="ml-auto text-muted-foreground">
|
||||
{{ link.label }}
|
||||
</span>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<a
|
||||
v-else
|
||||
:key="`2-${index}`"
|
||||
href="#"
|
||||
:class="cn(
|
||||
buttonVariants({ variant: link.variant, size: 'sm' }),
|
||||
link.variant === 'default'
|
||||
&& 'dark:bg-muted dark:text-white dark:hover:bg-muted dark:hover:text-white',
|
||||
'justify-start',
|
||||
)"
|
||||
>
|
||||
<Icon :icon="link.icon" class="mr-2 size-4" />
|
||||
{{ link.title }}
|
||||
<span
|
||||
v-if="link.label"
|
||||
:class="cn(
|
||||
'ml-auto',
|
||||
link.variant === 'default'
|
||||
&& 'text-background dark:text-white',
|
||||
)"
|
||||
>
|
||||
{{ link.label }}
|
||||
</span>
|
||||
</a>
|
||||
</template>
|
||||
</nav>
|
||||
</div>
|
||||
</template>
|
||||
279
apps/www/src/examples/mail/data/mails.ts
Normal file
279
apps/www/src/examples/mail/data/mails.ts
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
export const mails = [
|
||||
{
|
||||
id: '6c84fb90-12c4-11e1-840d-7b25c5ee775a',
|
||||
name: 'William Smith',
|
||||
email: 'williamsmith@example.com',
|
||||
subject: 'Meeting Tomorrow',
|
||||
text: 'Hi, let\'s have a meeting tomorrow to discuss the project. I\'ve been reviewing the project details and have some ideas I\'d like to share. It\'s crucial that we align on our next steps to ensure the project\'s success.\n\nPlease come prepared with any questions or insights you may have. Looking forward to our meeting!\n\nBest regards, William',
|
||||
date: '2023-10-22T09:00:00',
|
||||
read: true,
|
||||
labels: ['meeting', 'work', 'important'],
|
||||
},
|
||||
{
|
||||
id: '110e8400-e29b-11d4-a716-446655440000',
|
||||
name: 'Alice Smith',
|
||||
email: 'alicesmith@example.com',
|
||||
subject: 'Re: Project Update',
|
||||
text: 'Thank you for the project update. It looks great! I\'ve gone through the report, and the progress is impressive. The team has done a fantastic job, and I appreciate the hard work everyone has put in.\n\nI have a few minor suggestions that I\'ll include in the attached document.\n\nLet\'s discuss these during our next meeting. Keep up the excellent work!\n\nBest regards, Alice',
|
||||
date: '2023-10-22T10:30:00',
|
||||
read: true,
|
||||
labels: ['work', 'important'],
|
||||
},
|
||||
{
|
||||
id: '3e7c3f6d-bdf5-46ae-8d90-171300f27ae2',
|
||||
name: 'Bob Johnson',
|
||||
email: 'bobjohnson@example.com',
|
||||
subject: 'Weekend Plans',
|
||||
text: 'Any plans for the weekend? I was thinking of going hiking in the nearby mountains. It\'s been a while since we had some outdoor fun.\n\nIf you\'re interested, let me know, and we can plan the details. It\'ll be a great way to unwind and enjoy nature.\n\nLooking forward to your response!\n\nBest, Bob',
|
||||
date: '2023-04-10T11:45:00',
|
||||
read: true,
|
||||
labels: ['personal'],
|
||||
},
|
||||
{
|
||||
id: '61c35085-72d7-42b4-8d62-738f700d4b92',
|
||||
name: 'Emily Davis',
|
||||
email: 'emilydavis@example.com',
|
||||
subject: 'Re: Question about Budget',
|
||||
text: 'I have a question about the budget for the upcoming project. It seems like there\'s a discrepancy in the allocation of resources.\n\nI\'ve reviewed the budget report and identified a few areas where we might be able to optimize our spending without compromising the project\'s quality.\n\nI\'ve attached a detailed analysis for your reference. Let\'s discuss this further in our next meeting.\n\nThanks, Emily',
|
||||
date: '2023-03-25T13:15:00',
|
||||
read: false,
|
||||
labels: ['work', 'budget'],
|
||||
},
|
||||
{
|
||||
id: '8f7b5db9-d935-4e42-8e05-1f1d0a3dfb97',
|
||||
name: 'Michael Wilson',
|
||||
email: 'michaelwilson@example.com',
|
||||
subject: 'Important Announcement',
|
||||
text: 'I have an important announcement to make during our team meeting. It pertains to a strategic shift in our approach to the upcoming product launch. We\'ve received valuable feedback from our beta testers, and I believe it\'s time to make some adjustments to better meet our customers\' needs.\n\nThis change is crucial to our success, and I look forward to discussing it with the team. Please be prepared to share your insights during the meeting.\n\nRegards, Michael',
|
||||
date: '2023-03-10T15:00:00',
|
||||
read: false,
|
||||
labels: ['meeting', 'work', 'important'],
|
||||
},
|
||||
{
|
||||
id: '1f0f2c02-e299-40de-9b1d-86ef9e42126b',
|
||||
name: 'Sarah Brown',
|
||||
email: 'sarahbrown@example.com',
|
||||
subject: 'Re: Feedback on Proposal',
|
||||
text: 'Thank you for your feedback on the proposal. It looks great! I\'m pleased to hear that you found it promising. The team worked diligently to address all the key points you raised, and I believe we now have a strong foundation for the project.\n\nI\'ve attached the revised proposal for your review.\n\nPlease let me know if you have any further comments or suggestions. Looking forward to your response.\n\nBest regards, Sarah',
|
||||
date: '2023-02-15T16:30:00',
|
||||
read: true,
|
||||
labels: ['work'],
|
||||
},
|
||||
{
|
||||
id: '17c0a96d-4415-42b1-8b4f-764efab57f66',
|
||||
name: 'David Lee',
|
||||
email: 'davidlee@example.com',
|
||||
subject: 'New Project Idea',
|
||||
text: 'I have an exciting new project idea to discuss with you. It involves expanding our services to target a niche market that has shown considerable growth in recent months.\n\nI\'ve prepared a detailed proposal outlining the potential benefits and the strategy for execution.\n\nThis project has the potential to significantly impact our business positively. Let\'s set up a meeting to dive into the details and determine if it aligns with our current goals.\n\nBest regards, David',
|
||||
date: '2023-01-28T17:45:00',
|
||||
read: false,
|
||||
labels: ['meeting', 'work', 'important'],
|
||||
},
|
||||
{
|
||||
id: '2f0130cb-39fc-44c4-bb3c-0a4337edaaab',
|
||||
name: 'Olivia Wilson',
|
||||
email: 'oliviawilson@example.com',
|
||||
subject: 'Vacation Plans',
|
||||
text: 'Let\'s plan our vacation for next month. What do you think? I\'ve been thinking of visiting a tropical paradise, and I\'ve put together some destination options.\n\nI believe it\'s time for us to unwind and recharge. Please take a look at the options and let me know your preferences.\n\nWe can start making arrangements to ensure a smooth and enjoyable trip.\n\nExcited to hear your thoughts! Olivia',
|
||||
date: '2022-12-20T18:30:00',
|
||||
read: true,
|
||||
labels: ['personal'],
|
||||
},
|
||||
{
|
||||
id: 'de305d54-75b4-431b-adb2-eb6b9e546014',
|
||||
name: 'James Martin',
|
||||
email: 'jamesmartin@example.com',
|
||||
subject: 'Re: Conference Registration',
|
||||
text: 'I\'ve completed the registration for the conference next month. The event promises to be a great networking opportunity, and I\'m looking forward to attending the various sessions and connecting with industry experts.\n\nI\'ve also attached the conference schedule for your reference.\n\nIf there are any specific topics or sessions you\'d like me to explore, please let me know. It\'s an exciting event, and I\'ll make the most of it.\n\nBest regards, James',
|
||||
date: '2022-11-30T19:15:00',
|
||||
read: true,
|
||||
labels: ['work', 'conference'],
|
||||
},
|
||||
{
|
||||
id: '7dd90c63-00f6-40f3-bd87-5060a24e8ee7',
|
||||
name: 'Sophia White',
|
||||
email: 'sophiawhite@example.com',
|
||||
subject: 'Team Dinner',
|
||||
text: 'Let\'s have a team dinner next week to celebrate our success. We\'ve achieved some significant milestones, and it\'s time to acknowledge our hard work and dedication.\n\nI\'ve made reservations at a lovely restaurant, and I\'m sure it\'ll be an enjoyable evening.\n\nPlease confirm your availability and any dietary preferences. Looking forward to a fun and memorable dinner with the team!\n\nBest, Sophia',
|
||||
date: '2022-11-05T20:30:00',
|
||||
read: false,
|
||||
labels: ['meeting', 'work'],
|
||||
},
|
||||
{
|
||||
id: '99a88f78-3eb4-4d87-87b7-7b15a49a0a05',
|
||||
name: 'Daniel Johnson',
|
||||
email: 'danieljohnson@example.com',
|
||||
subject: 'Feedback Request',
|
||||
text: 'I\'d like your feedback on the latest project deliverables. We\'ve made significant progress, and I value your input to ensure we\'re on the right track.\n\nI\'ve attached the deliverables for your review, and I\'m particularly interested in any areas where you think we can further enhance the quality or efficiency.\n\nYour feedback is invaluable, and I appreciate your time and expertise. Let\'s work together to make this project a success.\n\nRegards, Daniel',
|
||||
date: '2022-10-22T09:30:00',
|
||||
read: false,
|
||||
labels: ['work'],
|
||||
},
|
||||
{
|
||||
id: 'f47ac10b-58cc-4372-a567-0e02b2c3d479',
|
||||
name: 'Ava Taylor',
|
||||
email: 'avataylor@example.com',
|
||||
subject: 'Re: Meeting Agenda',
|
||||
text: 'Here\'s the agenda for our meeting next week. I\'ve included all the topics we need to cover, as well as time allocations for each.\n\nIf you have any additional items to discuss or any specific points to address, please let me know, and we can integrate them into the agenda.\n\nIt\'s essential that our meeting is productive and addresses all relevant matters.\n\nLooking forward to our meeting! Ava',
|
||||
date: '2022-10-10T10:45:00',
|
||||
read: true,
|
||||
labels: ['meeting', 'work'],
|
||||
},
|
||||
{
|
||||
id: 'c1a0ecb4-2540-49c5-86f8-21e5ce79e4e6',
|
||||
name: 'William Anderson',
|
||||
email: 'williamanderson@example.com',
|
||||
subject: 'Product Launch Update',
|
||||
text: 'The product launch is on track. I\'ll provide an update during our call. We\'ve made substantial progress in the development and marketing of our new product.\n\nI\'m excited to share the latest updates with you during our upcoming call. It\'s crucial that we coordinate our efforts to ensure a successful launch. Please come prepared with any questions or insights you may have.\n\nLet\'s make this product launch a resounding success!\n\nBest regards, William',
|
||||
date: '2022-09-20T12:00:00',
|
||||
read: false,
|
||||
labels: ['meeting', 'work', 'important'],
|
||||
},
|
||||
{
|
||||
id: 'ba54eefd-4097-4949-99f2-2a9ae4d1a836',
|
||||
name: 'Mia Harris',
|
||||
email: 'miaharris@example.com',
|
||||
subject: 'Re: Travel Itinerary',
|
||||
text: 'I\'ve received the travel itinerary. It looks great! Thank you for your prompt assistance in arranging the details. I\'ve reviewed the schedule and the accommodations, and everything seems to be in order. I\'m looking forward to the trip, and I\'m confident it\'ll be a smooth and enjoyable experience.\n\nIf there are any specific activities or attractions you recommend at our destination, please feel free to share your suggestions.\n\nExcited for the trip! Mia',
|
||||
date: '2022-09-10T13:15:00',
|
||||
read: true,
|
||||
labels: ['personal', 'travel'],
|
||||
},
|
||||
{
|
||||
id: 'df09b6ed-28bd-4e0c-85a9-9320ec5179aa',
|
||||
name: 'Ethan Clark',
|
||||
email: 'ethanclark@example.com',
|
||||
subject: 'Team Building Event',
|
||||
text: 'Let\'s plan a team-building event for our department. Team cohesion and morale are vital to our success, and I believe a well-organized team-building event can be incredibly beneficial. I\'ve done some research and have a few ideas for fun and engaging activities.\n\nPlease let me know your thoughts and availability. We want this event to be both enjoyable and productive.\n\nTogether, we\'ll strengthen our team and boost our performance.\n\nRegards, Ethan',
|
||||
date: '2022-08-25T15:30:00',
|
||||
read: false,
|
||||
labels: ['meeting', 'work'],
|
||||
},
|
||||
{
|
||||
id: 'd67c1842-7f8b-4b4b-9be1-1b3b1ab4611d',
|
||||
name: 'Chloe Hall',
|
||||
email: 'chloehall@example.com',
|
||||
subject: 'Re: Budget Approval',
|
||||
text: 'The budget has been approved. We can proceed with the project. I\'m delighted to inform you that our budget proposal has received the green light from the finance department. This is a significant milestone, and it means we can move forward with the project as planned.\n\nI\'ve attached the finalized budget for your reference. Let\'s ensure that we stay on track and deliver the project on time and within budget.\n\nIt\'s an exciting time for us! Chloe',
|
||||
date: '2022-08-10T16:45:00',
|
||||
read: true,
|
||||
labels: ['work', 'budget'],
|
||||
},
|
||||
{
|
||||
id: '6c9a7f94-8329-4d70-95d3-51f68c186ae1',
|
||||
name: 'Samuel Turner',
|
||||
email: 'samuelturner@example.com',
|
||||
subject: 'Weekend Hike',
|
||||
text: 'Who\'s up for a weekend hike in the mountains? I\'ve been craving some outdoor adventure, and a hike in the mountains sounds like the perfect escape. If you\'re up for the challenge, we can explore some scenic trails and enjoy the beauty of nature.\n\nI\'ve done some research and have a few routes in mind.\n\nLet me know if you\'re interested, and we can plan the details.\n\nIt\'s sure to be a memorable experience! Samuel',
|
||||
date: '2022-07-28T17:30:00',
|
||||
read: false,
|
||||
labels: ['personal'],
|
||||
},
|
||||
]
|
||||
|
||||
export type Mail = (typeof mails)[number]
|
||||
|
||||
export const accounts = [
|
||||
{
|
||||
label: 'Alicia Koch',
|
||||
email: 'alicia@example.com',
|
||||
icon: 'ion:logo-vercel',
|
||||
},
|
||||
{
|
||||
label: 'Alicia Koch',
|
||||
email: 'alicia@gmail.com',
|
||||
icon: 'mdi:google',
|
||||
},
|
||||
{
|
||||
label: 'Alicia Koch',
|
||||
email: 'alicia@me.com',
|
||||
icon: 'bx:bxl-gmail',
|
||||
},
|
||||
]
|
||||
|
||||
export type Account = (typeof accounts)[number]
|
||||
|
||||
export const contacts = [
|
||||
{
|
||||
name: 'Emma Johnson',
|
||||
email: 'emma.johnson@example.com',
|
||||
},
|
||||
{
|
||||
name: 'Liam Wilson',
|
||||
email: 'liam.wilson@example.com',
|
||||
},
|
||||
{
|
||||
name: 'Olivia Davis',
|
||||
email: 'olivia.davis@example.com',
|
||||
},
|
||||
{
|
||||
name: 'Noah Martinez',
|
||||
email: 'noah.martinez@example.com',
|
||||
},
|
||||
{
|
||||
name: 'Ava Taylor',
|
||||
email: 'ava.taylor@example.com',
|
||||
},
|
||||
{
|
||||
name: 'Lucas Brown',
|
||||
email: 'lucas.brown@example.com',
|
||||
},
|
||||
{
|
||||
name: 'Sophia Smith',
|
||||
email: 'sophia.smith@example.com',
|
||||
},
|
||||
{
|
||||
name: 'Ethan Wilson',
|
||||
email: 'ethan.wilson@example.com',
|
||||
},
|
||||
{
|
||||
name: 'Isabella Jackson',
|
||||
email: 'isabella.jackson@example.com',
|
||||
},
|
||||
{
|
||||
name: 'Mia Clark',
|
||||
email: 'mia.clark@example.com',
|
||||
},
|
||||
{
|
||||
name: 'Mason Lee',
|
||||
email: 'mason.lee@example.com',
|
||||
},
|
||||
{
|
||||
name: 'Layla Harris',
|
||||
email: 'layla.harris@example.com',
|
||||
},
|
||||
{
|
||||
name: 'William Anderson',
|
||||
email: 'william.anderson@example.com',
|
||||
},
|
||||
{
|
||||
name: 'Ella White',
|
||||
email: 'ella.white@example.com',
|
||||
},
|
||||
{
|
||||
name: 'James Thomas',
|
||||
email: 'james.thomas@example.com',
|
||||
},
|
||||
{
|
||||
name: 'Harper Lewis',
|
||||
email: 'harper.lewis@example.com',
|
||||
},
|
||||
{
|
||||
name: 'Benjamin Moore',
|
||||
email: 'benjamin.moore@example.com',
|
||||
},
|
||||
{
|
||||
name: 'Aria Hall',
|
||||
email: 'aria.hall@example.com',
|
||||
},
|
||||
{
|
||||
name: 'Henry Turner',
|
||||
email: 'henry.turner@example.com',
|
||||
},
|
||||
{
|
||||
name: 'Scarlett Adams',
|
||||
email: 'scarlett.adams@example.com',
|
||||
},
|
||||
]
|
||||
|
||||
export type Contact = (typeof contacts)[number]
|
||||
|
|
@ -17,7 +17,7 @@ import {
|
|||
} from '@tanstack/vue-table'
|
||||
|
||||
import { ref } from 'vue'
|
||||
import { type Task } from '../data/schema'
|
||||
import type { Task } from '../data/schema'
|
||||
import DataTablePagination from './DataTablePagination.vue'
|
||||
import DataTableToolbar from './DataTableToolbar.vue'
|
||||
import { valueUpdater } from '@/lib/utils'
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import type { Column } from '@tanstack/vue-table'
|
||||
import { type Task } from '../data/schema'
|
||||
import type { Task } from '../data/schema'
|
||||
import ArrowDownIcon from '~icons/radix-icons/arrow-down'
|
||||
import ArrowUpIcon from '~icons/radix-icons/arrow-up'
|
||||
import CaretSortIcon from '~icons/radix-icons/caret-sort'
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ const selectedValues = computed(() => new Set(props.column?.getFilterValue() as
|
|||
>
|
||||
<CheckIcon :class="cn('h-4 w-4')" />
|
||||
</div>
|
||||
<option.icon v-if="option.icon" class="mr-2 h-4 w-4 text-muted-foreground" />
|
||||
<component :is="option.icon" v-if="option.icon" class="mr-2 h-4 w-4 text-muted-foreground" />
|
||||
<span>{{ option.label }}</span>
|
||||
<span v-if="facets?.get(option.value)" class="ml-auto flex h-4 w-4 items-center justify-center font-mono text-xs">
|
||||
{{ facets.get(option.value) }}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { type Table } from '@tanstack/vue-table'
|
||||
import { type Task } from '../data/schema'
|
||||
import type { Table } from '@tanstack/vue-table'
|
||||
import type { Task } from '../data/schema'
|
||||
import ChevronLeftIcon from '~icons/radix-icons/chevron-left'
|
||||
import ChevronRightIcon from '~icons/radix-icons/chevron-right'
|
||||
import DoubleArrowLeftIcon from '~icons/radix-icons/double-arrow-left'
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import type { Row } from '@tanstack/vue-table'
|
|||
import { computed } from 'vue'
|
||||
import { labels } from '../data/data'
|
||||
import { taskSchema } from '../data/schema'
|
||||
import { type Task } from '../data/schema'
|
||||
import type { Task } from '../data/schema'
|
||||
import DotsHorizontalIcon from '~icons/radix-icons/dots-horizontal'
|
||||
|
||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { type Table } from '@tanstack/vue-table'
|
||||
import type { Table } from '@tanstack/vue-table'
|
||||
import { computed } from 'vue'
|
||||
import { type Task } from '../data/schema'
|
||||
import type { Task } from '../data/schema'
|
||||
|
||||
import { priorities, statuses } from '../data/data'
|
||||
import DataTableFacetedFilter from './DataTableFacetedFilter.vue'
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import type { Table } from '@tanstack/vue-table'
|
||||
import { computed } from 'vue'
|
||||
import { type Task } from '../data/schema'
|
||||
import type { Task } from '../data/schema'
|
||||
import MixerHorizontalIcon from '~icons/radix-icons/mixer-horizontal'
|
||||
|
||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||
|
|
|
|||
|
|
@ -11,10 +11,13 @@ import { Badge } from '@/lib/registry/new-york/ui/badge'
|
|||
export const columns: ColumnDef<Task>[] = [
|
||||
{
|
||||
id: 'select',
|
||||
header: ({ table }) => h(Checkbox,
|
||||
{ 'checked': table.getIsAllPageRowsSelected(), 'onUpdate:checked': value => table.toggleAllPageRowsSelected(!!value), 'ariaLabel': 'Select all', 'class': 'translate-y-0.5' }),
|
||||
cell: ({ row }) => h(Checkbox,
|
||||
{ 'checked': row.getIsSelected(), 'onUpdate:checked': value => row.toggleSelected(!!value), 'ariaLabel': 'Select row', 'class': 'translate-y-0.5' }),
|
||||
header: ({ table }) => h(Checkbox, {
|
||||
'checked': table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && 'indeterminate'),
|
||||
'onUpdate:checked': value => table.toggleAllPageRowsSelected(!!value),
|
||||
'ariaLabel': 'Select all',
|
||||
'class': 'translate-y-0.5',
|
||||
}),
|
||||
cell: ({ row }) => h(Checkbox, { 'checked': row.getIsSelected(), 'onUpdate:checked': value => row.toggleSelected(!!value), 'ariaLabel': 'Select row', 'class': 'translate-y-0.5' }),
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
},
|
||||
|
|
@ -33,7 +36,7 @@ export const columns: ColumnDef<Task>[] = [
|
|||
const label = labels.find(label => label.value === row.original.label)
|
||||
|
||||
return h('div', { class: 'flex space-x-2' }, [
|
||||
label && h(Badge, { variant: 'outline' }, label.label),
|
||||
label ? h(Badge, { variant: 'outline' }, () => label.label) : null,
|
||||
h('span', { class: 'max-w-[500px] truncate font-medium' }, row.getValue('title')),
|
||||
])
|
||||
},
|
||||
|
|
@ -72,7 +75,7 @@ export const columns: ColumnDef<Task>[] = [
|
|||
|
||||
return h('div', { class: 'flex items-center' }, [
|
||||
priority.icon && h(priority.icon, { class: 'mr-2 h-4 w-4 text-muted-foreground' }),
|
||||
h('span', priority.label),
|
||||
h('span', {}, priority.label),
|
||||
])
|
||||
},
|
||||
filterFn: (row, id, value) => {
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ const columns: ColumnDef<Payment>[] = [
|
|||
{
|
||||
id: 'select',
|
||||
header: ({ table }) => h(Checkbox, {
|
||||
'checked': table.getIsAllPageRowsSelected(),
|
||||
'checked': table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && 'indeterminate'),
|
||||
'onUpdate:checked': value => table.toggleAllPageRowsSelected(!!value),
|
||||
'ariaLabel': 'Select all',
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { watchOnce } from '@vueuse/core'
|
||||
import { Carousel, type CarouselApi, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from '@/lib/registry/default/ui/carousel'
|
||||
import { Card, CardContent } from '@/lib/registry/default/ui/card'
|
||||
|
||||
const emblaMainApi = ref<CarouselApi>()
|
||||
const emblaThumbnailApi = ref<CarouselApi>()
|
||||
const selectedIndex = ref(0)
|
||||
|
||||
function onSelect() {
|
||||
if (!emblaMainApi.value || !emblaThumbnailApi.value)
|
||||
return
|
||||
selectedIndex.value = emblaMainApi.value.selectedScrollSnap()
|
||||
emblaThumbnailApi.value.scrollTo(emblaMainApi.value.selectedScrollSnap())
|
||||
}
|
||||
|
||||
function onThumbClick(index: number) {
|
||||
if (!emblaMainApi.value || !emblaThumbnailApi.value)
|
||||
return
|
||||
emblaMainApi.value.scrollTo(index)
|
||||
}
|
||||
|
||||
watchOnce(emblaMainApi, (emblaMainApi) => {
|
||||
if (!emblaMainApi)
|
||||
return
|
||||
|
||||
onSelect()
|
||||
emblaMainApi.on('select', onSelect)
|
||||
emblaMainApi.on('reInit', onSelect)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full sm:w-auto">
|
||||
<Carousel
|
||||
class="relative w-full max-w-xs"
|
||||
@init-api="(val) => emblaMainApi = val"
|
||||
>
|
||||
<CarouselContent>
|
||||
<CarouselItem v-for="(_, index) in 10" :key="index">
|
||||
<div class="p-1">
|
||||
<Card>
|
||||
<CardContent class="flex aspect-square items-center justify-center p-6">
|
||||
<span class="text-4xl font-semibold">{{ index + 1 }}</span>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</CarouselItem>
|
||||
</CarouselContent>
|
||||
<CarouselPrevious />
|
||||
<CarouselNext />
|
||||
</Carousel>
|
||||
|
||||
<Carousel
|
||||
class="relative w-full max-w-xs"
|
||||
@init-api="(val) => emblaThumbnailApi = val"
|
||||
>
|
||||
<CarouselContent class="flex gap-1 ml-0">
|
||||
<CarouselItem v-for="(_, index) in 10" :key="index" class="pl-0 basis-1/4 cursor-pointer" @click="onThumbClick(index)">
|
||||
<div class="p-1" :class="index === selectedIndex ? '' : 'opacity-50'">
|
||||
<Card>
|
||||
<CardContent class="flex aspect-square items-center justify-center p-6">
|
||||
<span class="text-4xl font-semibold">{{ index + 1 }}</span>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</CarouselItem>
|
||||
</CarouselContent>
|
||||
</Carousel>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
<script lang="ts" setup>
|
||||
import { createReusableTemplate, useMediaQuery } from '@vueuse/core'
|
||||
import { ref } from 'vue'
|
||||
import { Button } from '@/lib/registry/default/ui/button'
|
||||
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '@/lib/registry/default/ui/command'
|
||||
import { Drawer, DrawerContent, DrawerTrigger } from '@/lib/registry/default/ui/drawer'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/lib/registry/default/ui/popover'
|
||||
|
||||
interface Status {
|
||||
value: string
|
||||
label: string
|
||||
}
|
||||
|
||||
const statuses: Status[] = [
|
||||
{
|
||||
value: 'backlog',
|
||||
label: 'Backlog',
|
||||
},
|
||||
{
|
||||
value: 'todo',
|
||||
label: 'Todo',
|
||||
},
|
||||
{
|
||||
value: 'in progress',
|
||||
label: 'In Progress',
|
||||
},
|
||||
{
|
||||
value: 'done',
|
||||
label: 'Done',
|
||||
},
|
||||
{
|
||||
value: 'canceled',
|
||||
label: 'Canceled',
|
||||
},
|
||||
]
|
||||
|
||||
const [UseTemplate, StatusList] = createReusableTemplate()
|
||||
const isDesktop = useMediaQuery('(min-width: 768px)')
|
||||
|
||||
const isOpen = ref(false)
|
||||
const selectedStatus = ref<Status | null>(null)
|
||||
|
||||
function onStatusSelect(status: Status) {
|
||||
selectedStatus.value = status
|
||||
isOpen.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UseTemplate>
|
||||
<Command>
|
||||
<CommandInput placeholder="Filter status..." />
|
||||
<CommandList>
|
||||
<CommandEmpty>No results found.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
<CommandItem
|
||||
v-for="status of statuses"
|
||||
:key="status.value"
|
||||
:value="status.value"
|
||||
@select="onStatusSelect(status)"
|
||||
>
|
||||
{{ status.label }}
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</UseTemplate>
|
||||
|
||||
<Popover v-if="isDesktop" v-model:open="isOpen">
|
||||
<PopoverTrigger as-child>
|
||||
<Button variant="outline" class="w-[150px] justify-start">
|
||||
{{ selectedStatus ? selectedStatus.label : "+ Set status" }}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-[200px] p-0" align="start">
|
||||
<StatusList />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
<Drawer v-else :open="isOpen" @update:open="(newOpenValue) => isOpen = newOpenValue">
|
||||
<DrawerTrigger as-child>
|
||||
<Button variant="outline" class="w-[150px] justify-start">
|
||||
{{ selectedStatus ? selectedStatus.label : "+ Set status" }}
|
||||
</Button>
|
||||
</DrawerTrigger>
|
||||
<DrawerContent>
|
||||
<div class="mt-4 border-t">
|
||||
<StatusList />
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -84,11 +84,11 @@ const columns = [
|
|||
columnHelper.display({
|
||||
id: 'select',
|
||||
header: ({ table }) => h(Checkbox, {
|
||||
'checked': table.getIsAllPageRowsSelected(),
|
||||
'checked': table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && 'indeterminate'),
|
||||
'onUpdate:checked': value => table.toggleAllPageRowsSelected(!!value),
|
||||
'ariaLabel': 'Select all',
|
||||
}),
|
||||
cell: ({ row, column }) => {
|
||||
cell: ({ row }) => {
|
||||
return h(Checkbox, {
|
||||
'checked': row.getIsSelected(),
|
||||
'onUpdate:checked': value => row.toggleSelected(!!value),
|
||||
|
|
@ -165,8 +165,6 @@ const table = useVueTable({
|
|||
},
|
||||
},
|
||||
})
|
||||
|
||||
const getState = table.getState()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ const columns: ColumnDef<Payment>[] = [
|
|||
{
|
||||
id: 'select',
|
||||
header: ({ table }) => h(Checkbox, {
|
||||
'checked': table.getIsAllPageRowsSelected(),
|
||||
'checked': table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && 'indeterminate'),
|
||||
'onUpdate:checked': value => table.toggleAllPageRowsSelected(!!value),
|
||||
'ariaLabel': 'Select all',
|
||||
}),
|
||||
|
|
|
|||
111
apps/www/src/lib/registry/default/example/DrawerDemo.vue
Normal file
111
apps/www/src/lib/registry/default/example/DrawerDemo.vue
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { Minus, Plus } from 'lucide-vue-next'
|
||||
import { VisStackedBar, VisXYContainer } from '@unovis/vue'
|
||||
import { Button } from '@/lib/registry/default/ui/button'
|
||||
import {
|
||||
Drawer,
|
||||
DrawerClose,
|
||||
DrawerContent,
|
||||
DrawerDescription,
|
||||
DrawerFooter,
|
||||
DrawerHeader,
|
||||
DrawerTitle,
|
||||
DrawerTrigger,
|
||||
} from '@/lib/registry/default/ui/drawer'
|
||||
|
||||
const goal = ref(350)
|
||||
|
||||
type Data = typeof data[number]
|
||||
const data = [
|
||||
{ goal: 400 },
|
||||
{ goal: 300 },
|
||||
{ goal: 200 },
|
||||
{ goal: 300 },
|
||||
{ goal: 200 },
|
||||
{ goal: 278 },
|
||||
{ goal: 189 },
|
||||
{ goal: 239 },
|
||||
{ goal: 300 },
|
||||
{ goal: 200 },
|
||||
{ goal: 278 },
|
||||
{ goal: 189 },
|
||||
{ goal: 349 },
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Drawer>
|
||||
<DrawerTrigger as-child>
|
||||
<Button variant="outline">
|
||||
Open Drawer
|
||||
</Button>
|
||||
</DrawerTrigger>
|
||||
<DrawerContent>
|
||||
<div class="mx-auto w-full max-w-sm">
|
||||
<DrawerHeader>
|
||||
<DrawerTitle>Move Goal</DrawerTitle>
|
||||
<DrawerDescription>Set your daily activity goal.</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
<div class="p-4 pb-0">
|
||||
<div class="flex items-center justify-center space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
class="h-8 w-8 shrink-0 rounded-full"
|
||||
:disabled="goal <= 200"
|
||||
@click="goal -= 10"
|
||||
>
|
||||
<Minus class="h-4 w-4" />
|
||||
<span class="sr-only">Decrease</span>
|
||||
</Button>
|
||||
<div class="flex-1 text-center">
|
||||
<div class="text-7xl font-bold tracking-tighter">
|
||||
{{ goal }}
|
||||
</div>
|
||||
<div class="text-[0.70rem] uppercase text-muted-foreground">
|
||||
Calories/day
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
class="h-8 w-8 shrink-0 rounded-full"
|
||||
:disabled="goal >= 400"
|
||||
@click="goal += 10"
|
||||
>
|
||||
<Plus class="h-4 w-4" />
|
||||
<span class="sr-only">Increase</span>
|
||||
</Button>
|
||||
</div>
|
||||
<div class="my-3 px-3 h-[120px]">
|
||||
<VisXYContainer
|
||||
:data="data"
|
||||
class="h-[120px]"
|
||||
:style="{
|
||||
'opacity': 0.9,
|
||||
'--theme-primary': `hsl(var(--foreground))`,
|
||||
}"
|
||||
>
|
||||
<VisStackedBar
|
||||
:x="(d: Data, i :number) => i"
|
||||
:y="(d: Data) => d.goal"
|
||||
color="var(--theme-primary)"
|
||||
:bar-padding="0.1"
|
||||
:rounded-corners="0"
|
||||
/>
|
||||
</VisXYContainer>
|
||||
</div>
|
||||
</div>
|
||||
<DrawerFooter>
|
||||
<Button>Submit</Button>
|
||||
<DrawerClose as-child>
|
||||
<Button variant="outline">
|
||||
Cancel
|
||||
</Button>
|
||||
</DrawerClose>
|
||||
</DrawerFooter>
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
</template>
|
||||
90
apps/www/src/lib/registry/default/example/DrawerDialog.vue
Normal file
90
apps/www/src/lib/registry/default/example/DrawerDialog.vue
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { createReusableTemplate, useMediaQuery } from '@vueuse/core'
|
||||
import { Button } from '@/lib/registry/default/ui/button'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@/lib/registry/default/ui/dialog'
|
||||
import {
|
||||
Drawer,
|
||||
DrawerClose,
|
||||
DrawerContent,
|
||||
DrawerDescription,
|
||||
DrawerFooter,
|
||||
DrawerHeader,
|
||||
DrawerTitle,
|
||||
DrawerTrigger,
|
||||
} from '@/lib/registry/default/ui/drawer'
|
||||
import { Label } from '@/lib/registry/default/ui/label'
|
||||
import { Input } from '@/lib/registry/default/ui/input'
|
||||
|
||||
// Reuse `form` section
|
||||
const [UseTemplate, GridForm] = createReusableTemplate()
|
||||
const isDesktop = useMediaQuery('(min-width: 768px)')
|
||||
|
||||
const isOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UseTemplate>
|
||||
<form class="grid items-start gap-4 px-4">
|
||||
<div class="grid gap-2">
|
||||
<Label html-for="email">Email</Label>
|
||||
<Input id="email" type="email" default-value="shadcn@example.com" />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label html-for="username">Username</Label>
|
||||
<Input id="username" default-value="@shadcn" />
|
||||
</div>
|
||||
<Button type="submit">
|
||||
Save changes
|
||||
</Button>
|
||||
</form>
|
||||
</UseTemplate>
|
||||
|
||||
<Dialog v-if="isDesktop" v-model:open="isOpen">
|
||||
<DialogTrigger as-child>
|
||||
<Button variant="outline">
|
||||
Edit Profile
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent class="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Edit profile</DialogTitle>
|
||||
<DialogDescription>
|
||||
Make changes to your profile here. Click save when you're done.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<GridForm />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Drawer v-else v-model:open="isOpen">
|
||||
<DrawerTrigger as-child>
|
||||
<Button variant="outline">
|
||||
Edit Profile
|
||||
</Button>
|
||||
</DrawerTrigger>
|
||||
<DrawerContent>
|
||||
<DrawerHeader class="text-left">
|
||||
<DrawerTitle>Edit profile</DrawerTitle>
|
||||
<DrawerDescription>
|
||||
Make changes to your profile here. Click save when you're done.
|
||||
</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
<GridForm />
|
||||
<DrawerFooter class="pt-2">
|
||||
<DrawerClose as-child>
|
||||
<Button variant="outline">
|
||||
Cancel
|
||||
</Button>
|
||||
</DrawerClose>
|
||||
</DrawerFooter>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
<script lang="ts" setup>
|
||||
import type { DropdownMenuCheckboxItemProps } from 'radix-vue'
|
||||
import { ref } from 'vue'
|
||||
import { Button } from '@/lib/registry/default/ui/button'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/lib/registry/default/ui/dropdown-menu'
|
||||
|
||||
type Checked = DropdownMenuCheckboxItemProps['checked']
|
||||
|
||||
const showStatusBar = ref<Checked>(true)
|
||||
const showActivityBar = ref<Checked>(false)
|
||||
const showPanel = ref<Checked>(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<Button variant="outline">
|
||||
Open
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent class="w-56">
|
||||
<DropdownMenuLabel>Appearance</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuCheckboxItem
|
||||
v-model:checked="showStatusBar"
|
||||
>
|
||||
Status Bar
|
||||
</DropdownMenuCheckboxItem>
|
||||
<DropdownMenuCheckboxItem
|
||||
v-model:checked="showActivityBar"
|
||||
disabled
|
||||
>
|
||||
Activity Bar
|
||||
</DropdownMenuCheckboxItem>
|
||||
<DropdownMenuCheckboxItem
|
||||
v-model:checked="showPanel"
|
||||
>
|
||||
Panel
|
||||
</DropdownMenuCheckboxItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { Button } from '@/lib/registry/default/ui/button'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/lib/registry/default/ui/dropdown-menu'
|
||||
|
||||
const position = ref('bottom')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<Button variant="outline">
|
||||
Open
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent class="w-56">
|
||||
<DropdownMenuLabel>Panel Position</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuRadioGroup v-model="position">
|
||||
<DropdownMenuRadioItem value="top">
|
||||
Top
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="bottom">
|
||||
Bottom
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="right">
|
||||
Right
|
||||
</DropdownMenuRadioItem>
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</template>
|
||||
|
|
@ -49,5 +49,5 @@ const onSubmit = handleSubmit((values) => {
|
|||
<Button type="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</Form>
|
||||
</form>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -50,5 +50,5 @@ const onSubmit = handleSubmit((values) => {
|
|||
<Button type="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</Form>
|
||||
</form>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import {
|
||||
PinInput,
|
||||
PinInputGroup,
|
||||
PinInputInput,
|
||||
} from '@/lib/registry/default/ui/pin-input'
|
||||
|
||||
const value = ref<string[]>(['1', '2', '3'])
|
||||
const handleComplete = (e: string[]) => alert(e.join(''))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<PinInput
|
||||
id="pin-input"
|
||||
v-model="value"
|
||||
placeholder="○"
|
||||
@complete="handleComplete"
|
||||
>
|
||||
<PinInputGroup>
|
||||
<PinInputInput
|
||||
v-for="(id, index) in 5"
|
||||
:key="id"
|
||||
:index="index"
|
||||
/>
|
||||
</PinInputGroup>
|
||||
</PinInput>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
import { ref } from 'vue'
|
||||
import {
|
||||
PinInput,
|
||||
PinInputGroup,
|
||||
PinInputInput,
|
||||
} from '@/lib/registry/default/ui/pin-input'
|
||||
|
||||
|
|
@ -15,14 +16,15 @@ const handleComplete = (e: string[]) => alert(e.join(''))
|
|||
id="pin-input"
|
||||
v-model="value"
|
||||
placeholder="○"
|
||||
class="flex gap-2 items-center mt-1"
|
||||
@complete="handleComplete"
|
||||
>
|
||||
<PinInputInput
|
||||
v-for="(id, index) in 5"
|
||||
:key="id"
|
||||
:index="index"
|
||||
/>
|
||||
<PinInputGroup>
|
||||
<PinInputInput
|
||||
v-for="(id, index) in 5"
|
||||
:key="id"
|
||||
:index="index"
|
||||
/>
|
||||
</PinInputGroup>
|
||||
</PinInput>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import {
|
||||
PinInput,
|
||||
PinInputGroup,
|
||||
PinInputInput,
|
||||
} from '@/lib/registry/default/ui/pin-input'
|
||||
|
||||
const value = ref<string[]>([])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<PinInput
|
||||
id="pin-input"
|
||||
v-model="value"
|
||||
placeholder="○"
|
||||
disabled
|
||||
>
|
||||
<PinInputGroup>
|
||||
<PinInputInput
|
||||
v-for="(id, index) in 5"
|
||||
:key="id"
|
||||
:index="index"
|
||||
/>
|
||||
</PinInputGroup>
|
||||
</PinInput>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -5,8 +5,9 @@ import { toTypedSchema } from '@vee-validate/zod'
|
|||
import * as z from 'zod'
|
||||
import {
|
||||
PinInput,
|
||||
PinInputGroup,
|
||||
PinInputInput,
|
||||
} from '@/lib/registry/new-york/ui/pin-input'
|
||||
} from '@/lib/registry/default/ui/pin-input'
|
||||
import { Button } from '@/lib/registry/default/ui/button'
|
||||
import {
|
||||
FormControl,
|
||||
|
|
@ -25,7 +26,7 @@ const formSchema = toTypedSchema(z.object({
|
|||
const { handleSubmit, setValues } = useForm({
|
||||
validationSchema: formSchema,
|
||||
initialValues: {
|
||||
pin: [],
|
||||
pin: ['1', '2', '3'],
|
||||
},
|
||||
})
|
||||
|
||||
|
|
@ -59,11 +60,13 @@ const handleComplete = (e: string[]) => console.log(e.join(''))
|
|||
})
|
||||
}"
|
||||
>
|
||||
<PinInputInput
|
||||
v-for="(id, index) in 5"
|
||||
:key="id"
|
||||
:index="index"
|
||||
/>
|
||||
<PinInputGroup>
|
||||
<PinInputInput
|
||||
v-for="(id, index) in 5"
|
||||
:key="id"
|
||||
:index="index"
|
||||
/>
|
||||
</PinInputGroup>
|
||||
</PinInput>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import {
|
||||
PinInput,
|
||||
PinInputGroup,
|
||||
PinInputInput,
|
||||
PinInputSeparator,
|
||||
} from '@/lib/registry/default/ui/pin-input'
|
||||
|
||||
const value = ref<string[]>([])
|
||||
const handleComplete = (e: string[]) => alert(e.join(''))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<PinInput
|
||||
id="pin-input"
|
||||
v-model="value"
|
||||
placeholder="○"
|
||||
@complete="handleComplete"
|
||||
>
|
||||
<PinInputGroup class="gap-1">
|
||||
<template v-for="(id, index) in 5" :key="id">
|
||||
<PinInputInput
|
||||
class="rounded-md border"
|
||||
:index="index"
|
||||
/>
|
||||
<template v-if="index !== 4">
|
||||
<PinInputSeparator />
|
||||
</template>
|
||||
</template>
|
||||
</PinInputGroup>
|
||||
</PinInput>
|
||||
</div>
|
||||
</template>
|
||||
37
apps/www/src/lib/registry/default/example/ResizableDemo.vue
Normal file
37
apps/www/src/lib/registry/default/example/ResizableDemo.vue
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<script setup lang="ts">
|
||||
import {
|
||||
ResizableHandle,
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup,
|
||||
} from '@/lib/registry/default/ui/resizable'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ResizablePanelGroup
|
||||
id="demo-group-1"
|
||||
direction="horizontal"
|
||||
class="max-w-md rounded-lg border"
|
||||
>
|
||||
<ResizablePanel id="demo-panel-1" :default-size="50">
|
||||
<div class="flex h-[200px] items-center justify-center p-6">
|
||||
<span class="font-semibold">One</span>
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
<ResizableHandle id="demo-handle-1" />
|
||||
<ResizablePanel id="demo-panel-2" :default-size="50">
|
||||
<ResizablePanelGroup id="demo-group-2" direction="vertical">
|
||||
<ResizablePanel id="demo-panel-3" :default-size="25">
|
||||
<div class="flex h-full items-center justify-center p-6">
|
||||
<span class="font-semibold">Two</span>
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
<ResizableHandle id="demo-handle-2" />
|
||||
<ResizablePanel id="demo-panel-4" :default-size="75">
|
||||
<div class="flex h-full items-center justify-center p-6">
|
||||
<span class="font-semibold">Three</span>
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
</ResizablePanelGroup>
|
||||
</ResizablePanel>
|
||||
</ResizablePanelGroup>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<script setup lang="ts">
|
||||
import {
|
||||
ResizableHandle,
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup,
|
||||
} from '@/lib/registry/default/ui/resizable'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ResizablePanelGroup
|
||||
id="handle-demo-group-1"
|
||||
direction="horizontal"
|
||||
class="min-h-[200px] max-w-md rounded-lg border"
|
||||
>
|
||||
<ResizablePanel id="handle-demo-panel-1" :default-size="25">
|
||||
<div class="flex h-full items-center justify-center p-6">
|
||||
<span class="font-semibold">Sidebar</span>
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
<ResizableHandle id="handle-demo-handle-1" with-handle />
|
||||
<ResizablePanel id="handle-demo-panel-2" :default-size="75">
|
||||
<div class="flex h-full items-center justify-center p-6">
|
||||
<span class="font-semibold">Content</span>
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
</ResizablePanelGroup>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<script setup lang="ts">
|
||||
import {
|
||||
ResizableHandle,
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup,
|
||||
} from '@/lib/registry/default/ui/resizable'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ResizablePanelGroup
|
||||
id="vertical-demo-group-1"
|
||||
direction="vertical"
|
||||
class="min-h-[200px] max-w-md rounded-lg border"
|
||||
>
|
||||
<ResizablePanel id="vertical-demo-panel-1" :default-size="25">
|
||||
<div class="flex h-full items-center justify-center p-6">
|
||||
<span class="font-semibold">Header</span>
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
<ResizableHandle id="vertical-demo-handle-1" />
|
||||
<ResizablePanel id="vertical-demo-panel-2" :default-size="75">
|
||||
<div class="flex h-full items-center justify-center p-6">
|
||||
<span class="font-semibold">Content</span>
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
</ResizablePanelGroup>
|
||||
</template>
|
||||
117
apps/www/src/lib/registry/default/example/SelectScrollable.vue
Normal file
117
apps/www/src/lib/registry/default/example/SelectScrollable.vue
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
<script lang="ts" setup>
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectLabel,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/lib/registry/default/ui/select'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Select>
|
||||
<SelectTrigger class="w-[280px]">
|
||||
<SelectValue placeholder="Select a timezone" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectLabel>North America</SelectLabel>
|
||||
<SelectItem value="est">
|
||||
Eastern Standard Time (EST)
|
||||
</SelectItem>
|
||||
<SelectItem value="cst">
|
||||
Central Standard Time (CST)
|
||||
</SelectItem>
|
||||
<SelectItem value="mst">
|
||||
Mountain Standard Time (MST)
|
||||
</SelectItem>
|
||||
<SelectItem value="pst">
|
||||
Pacific Standard Time (PST)
|
||||
</SelectItem>
|
||||
<SelectItem value="akst">
|
||||
Alaska Standard Time (AKST)
|
||||
</SelectItem>
|
||||
<SelectItem value="hst">
|
||||
Hawaii Standard Time (HST)
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
<SelectGroup>
|
||||
<SelectLabel>Europe & Africa</SelectLabel>
|
||||
<SelectItem value="gmt">
|
||||
Greenwich Mean Time (GMT)
|
||||
</SelectItem>
|
||||
<SelectItem value="cet">
|
||||
Central European Time (CET)
|
||||
</SelectItem>
|
||||
<SelectItem value="eet">
|
||||
Eastern European Time (EET)
|
||||
</SelectItem>
|
||||
<SelectItem value="west">
|
||||
Western European Summer Time (WEST)
|
||||
</SelectItem>
|
||||
<SelectItem value="cat">
|
||||
Central Africa Time (CAT)
|
||||
</SelectItem>
|
||||
<SelectItem value="eat">
|
||||
East Africa Time (EAT)
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
<SelectGroup>
|
||||
<SelectLabel>Asia</SelectLabel>
|
||||
<SelectItem value="msk">
|
||||
Moscow Time (MSK)
|
||||
</SelectItem>
|
||||
<SelectItem value="ist">
|
||||
India Standard Time (IST)
|
||||
</SelectItem>
|
||||
<SelectItem value="cst_china">
|
||||
China Standard Time (CST)
|
||||
</SelectItem>
|
||||
<SelectItem value="jst">
|
||||
Japan Standard Time (JST)
|
||||
</SelectItem>
|
||||
<SelectItem value="kst">
|
||||
Korea Standard Time (KST)
|
||||
</SelectItem>
|
||||
<SelectItem value="ist_indonesia">
|
||||
Indonesia Central Standard Time (WITA)
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
<SelectGroup>
|
||||
<SelectLabel>Australia & Pacific</SelectLabel>
|
||||
<SelectItem value="awst">
|
||||
Australian Western Standard Time (AWST)
|
||||
</SelectItem>
|
||||
<SelectItem value="acst">
|
||||
Australian Central Standard Time (ACST)
|
||||
</SelectItem>
|
||||
<SelectItem value="aest">
|
||||
Australian Eastern Standard Time (AEST)
|
||||
</SelectItem>
|
||||
<SelectItem value="nzst">
|
||||
New Zealand Standard Time (NZST)
|
||||
</SelectItem>
|
||||
<SelectItem value="fjt">
|
||||
Fiji Time (FJT)
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
<SelectGroup>
|
||||
<SelectLabel>South America</SelectLabel>
|
||||
<SelectItem value="art">
|
||||
Argentina Time (ART)
|
||||
</SelectItem>
|
||||
<SelectItem value="bot">
|
||||
Bolivia Time (BOT)
|
||||
</SelectItem>
|
||||
<SelectItem value="brt">
|
||||
Brasilia Time (BRT)
|
||||
</SelectItem>
|
||||
<SelectItem value="clt">
|
||||
Chile Standard Time (CLT)
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</template>
|
||||
13
apps/www/src/lib/registry/default/example/SkeletonCard.vue
Normal file
13
apps/www/src/lib/registry/default/example/SkeletonCard.vue
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<script lang="ts" setup>
|
||||
import { Skeleton } from '@/lib/registry/default/ui/skeleton'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col space-y-3">
|
||||
<Skeleton class="h-[125px] w-[250px] rounded-xl" />
|
||||
<div class="space-y-2">
|
||||
<Skeleton class="h-4 w-[250px]" />
|
||||
<Skeleton class="h-4 w-[200px]" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
66
apps/www/src/lib/registry/default/example/SliderForm.vue
Normal file
66
apps/www/src/lib/registry/default/example/SliderForm.vue
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
<script setup lang="ts">
|
||||
import { h } from 'vue'
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import * as z from 'zod'
|
||||
|
||||
import { Button } from '@/lib/registry/default/ui/button'
|
||||
import {
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/lib/registry/default/ui/form'
|
||||
import { Slider } from '@/lib/registry/default/ui/slider'
|
||||
import { toast } from '@/lib/registry/default/ui/toast'
|
||||
|
||||
const formSchema = toTypedSchema(z.object({
|
||||
duration: z.array(
|
||||
z.number().min(0).max(60),
|
||||
),
|
||||
}))
|
||||
|
||||
const { handleSubmit } = useForm({
|
||||
validationSchema: formSchema,
|
||||
initialValues: {
|
||||
duration: [30],
|
||||
},
|
||||
})
|
||||
|
||||
const onSubmit = handleSubmit((values) => {
|
||||
toast({
|
||||
title: 'You submitted the following values:',
|
||||
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form class="w-2/3 space-y-6" @submit="onSubmit">
|
||||
<FormField v-slot="{ componentField, value }" name="duration">
|
||||
<FormItem>
|
||||
<FormLabel>Duration</FormLabel>
|
||||
<FormControl>
|
||||
<Slider
|
||||
v-bind="componentField"
|
||||
:default-value="[30]"
|
||||
:max="100"
|
||||
:min="0"
|
||||
:step="5"
|
||||
/>
|
||||
<FormDescription class="flex justify-between">
|
||||
<span>How many minutes are you available?</span>
|
||||
<span>{{ value?.[0] }} min</span>
|
||||
</FormDescription>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<Button type="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</form>
|
||||
</template>
|
||||
|
|
@ -4,7 +4,9 @@ import type { ComboboxContentEmits, ComboboxContentProps } from 'radix-vue'
|
|||
import { ComboboxContent, useForwardPropsEmits } from 'radix-vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<ComboboxContentProps & { class?: HTMLAttributes['class'] }>()
|
||||
const props = withDefaults(defineProps<ComboboxContentProps & { class?: HTMLAttributes['class'] }>(), {
|
||||
dismissable: false,
|
||||
})
|
||||
const emits = defineEmits<ComboboxContentEmits>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
|
|
|
|||
|
|
@ -37,6 +37,13 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|||
)
|
||||
"
|
||||
v-bind="forwarded"
|
||||
@pointer-down-outside="(event) => {
|
||||
const originalEvent = event.detail.originalEvent;
|
||||
const target = originalEvent.target as HTMLElement;
|
||||
if (originalEvent.offsetX > target.clientWidth || originalEvent.offsetY > target.clientHeight) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}"
|
||||
>
|
||||
<slot />
|
||||
|
||||
|
|
|
|||
19
apps/www/src/lib/registry/default/ui/drawer/Drawer.vue
Normal file
19
apps/www/src/lib/registry/default/ui/drawer/Drawer.vue
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<script lang="ts" setup>
|
||||
import type { DrawerRootEmits, DrawerRootProps } from 'vaul-vue'
|
||||
import { DrawerRoot } from 'vaul-vue'
|
||||
import { useForwardPropsEmits } from 'radix-vue'
|
||||
|
||||
const props = withDefaults(defineProps<DrawerRootProps>(), {
|
||||
shouldScaleBackground: true,
|
||||
})
|
||||
|
||||
const emits = defineEmits<DrawerRootEmits>()
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DrawerRoot v-bind="forwarded">
|
||||
<slot />
|
||||
</DrawerRoot>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<script lang="ts" setup>
|
||||
import { DrawerContent, DrawerPortal } from 'vaul-vue'
|
||||
import type { DialogContentEmits, DialogContentProps } from 'radix-vue'
|
||||
import { useForwardPropsEmits } from 'radix-vue'
|
||||
import type { HtmlHTMLAttributes } from 'vue'
|
||||
import DrawerOverlay from './DrawerOverlay.vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<DialogContentProps & { class?: HtmlHTMLAttributes['class'] }>()
|
||||
const emits = defineEmits<DialogContentEmits>()
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DrawerPortal>
|
||||
<DrawerOverlay />
|
||||
<DrawerContent
|
||||
v-bind="forwarded" :class="cn(
|
||||
'fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background',
|
||||
props.class,
|
||||
)"
|
||||
>
|
||||
<div class="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
|
||||
<slot />
|
||||
</DrawerContent>
|
||||
</DrawerPortal>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<script lang="ts" setup>
|
||||
import type { DrawerDescriptionProps } from 'vaul-vue'
|
||||
import { DrawerDescription } from 'vaul-vue'
|
||||
import { type HtmlHTMLAttributes, computed } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<DrawerDescriptionProps & { class?: HtmlHTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DrawerDescription v-bind="delegatedProps" :class="cn('text-sm text-muted-foreground', props.class)">
|
||||
<slot />
|
||||
</DrawerDescription>
|
||||
</template>
|
||||
14
apps/www/src/lib/registry/default/ui/drawer/DrawerFooter.vue
Normal file
14
apps/www/src/lib/registry/default/ui/drawer/DrawerFooter.vue
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<script lang="ts" setup>
|
||||
import type { HtmlHTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HtmlHTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn('mt-auto flex flex-col gap-2 p-4', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
14
apps/www/src/lib/registry/default/ui/drawer/DrawerHeader.vue
Normal file
14
apps/www/src/lib/registry/default/ui/drawer/DrawerHeader.vue
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<script lang="ts" setup>
|
||||
import type { HtmlHTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HtmlHTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn('grid gap-1.5 p-4 text-center sm:text-left', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<script lang="ts" setup>
|
||||
import { DrawerOverlay } from 'vaul-vue'
|
||||
import type { DialogOverlayProps } from 'radix-vue'
|
||||
import { type HtmlHTMLAttributes, computed } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<DialogOverlayProps & { class?: HtmlHTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DrawerOverlay v-bind="delegatedProps" :class="cn('fixed inset-0 z-50 bg-black/80', props.class)" />
|
||||
</template>
|
||||
20
apps/www/src/lib/registry/default/ui/drawer/DrawerTitle.vue
Normal file
20
apps/www/src/lib/registry/default/ui/drawer/DrawerTitle.vue
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<script lang="ts" setup>
|
||||
import type { DrawerTitleProps } from 'vaul-vue'
|
||||
import { DrawerTitle } from 'vaul-vue'
|
||||
import { type HtmlHTMLAttributes, computed } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<DrawerTitleProps & { class?: HtmlHTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DrawerTitle v-bind="delegatedProps" :class="cn('text-lg font-semibold leading-none tracking-tight', props.class)">
|
||||
<slot />
|
||||
</DrawerTitle>
|
||||
</template>
|
||||
8
apps/www/src/lib/registry/default/ui/drawer/index.ts
Normal file
8
apps/www/src/lib/registry/default/ui/drawer/index.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
export { DrawerPortal, DrawerTrigger, DrawerClose } from 'vaul-vue'
|
||||
export { default as Drawer } from './Drawer.vue'
|
||||
export { default as DrawerOverlay } from './DrawerOverlay.vue'
|
||||
export { default as DrawerContent } from './DrawerContent.vue'
|
||||
export { default as DrawerHeader } from './DrawerHeader.vue'
|
||||
export { default as DrawerFooter } from './DrawerFooter.vue'
|
||||
export { default as DrawerTitle } from './DrawerTitle.vue'
|
||||
export { default as DrawerDescription } from './DrawerDescription.vue'
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue'
|
||||
import { Primitive, type PrimitiveProps, useForwardProps } from 'radix-vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<PrimitiveProps & { class?: HTMLAttributes['class'] }>()
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
return delegated
|
||||
})
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Primitive v-bind="forwardedProps" :class="cn('flex items-center', props.class)">
|
||||
<slot />
|
||||
</primitive>
|
||||
</template>
|
||||
|
|
@ -14,5 +14,5 @@ const forwardedProps = useForwardProps(delegatedProps)
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<PinInputInput v-bind="forwardedProps" :class="cn('flex w-10 h-10 text-center rounded-md border border-input bg-background text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50', props.class)" />
|
||||
<PinInputInput v-bind="forwardedProps" :class="cn('relative text-center focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 flex h-10 w-10 items-center justify-center border-y border-r border-input text-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md', props.class)" />
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
<script setup lang="ts">
|
||||
import { Primitive, type PrimitiveProps, useForwardProps } from 'radix-vue'
|
||||
import { Dot } from 'lucide-vue-next'
|
||||
|
||||
const props = defineProps<PrimitiveProps>()
|
||||
const forwardedProps = useForwardProps(props)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Primitive v-bind="forwardedProps">
|
||||
<slot>
|
||||
<Dot />
|
||||
</slot>
|
||||
</primitive>
|
||||
</template>
|
||||
|
|
@ -1,2 +1,4 @@
|
|||
export { default as PinInput } from './PinInput.vue'
|
||||
export { default as PinInputGroup } from './PinInputGroup.vue'
|
||||
export { default as PinInputSeparator } from './PinInputSeparator.vue'
|
||||
export { default as PinInputInput } from './PinInputInput.vue'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue'
|
||||
import { SplitterResizeHandle, type SplitterResizeHandleEmits, type SplitterResizeHandleProps, useForwardPropsEmits } from 'radix-vue'
|
||||
import { GripVertical } from 'lucide-vue-next'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<SplitterResizeHandleProps & { class?: HTMLAttributes['class'], withHandle?: boolean }>()
|
||||
const emits = defineEmits<SplitterResizeHandleEmits>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SplitterResizeHandle v-bind="forwarded" :class="cn('relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 [&[data-orientation=vertical]]:h-px [&[data-orientation=vertical]]:w-full [&[data-orientation=vertical]]:after:left-0 [&[data-orientation=vertical]]:after:h-1 [&[data-orientation=vertical]]:after:w-full [&[data-orientation=vertical]]:after:-translate-y-1/2 [&[data-orientation=vertical]]:after:translate-x-0 [&[data-orientation=vertical]>div]:rotate-90', props.class)">
|
||||
<template v-if="props.withHandle">
|
||||
<div class="z-10 flex h-4 w-3 items-center justify-center rounded-sm border bg-border">
|
||||
<GripVertical class="h-2.5 w-2.5" />
|
||||
</div>
|
||||
</template>
|
||||
</SplitterResizeHandle>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue'
|
||||
import { SplitterGroup, type SplitterGroupEmits, type SplitterGroupProps, useForwardPropsEmits } from 'radix-vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<SplitterGroupProps & { class?: HTMLAttributes['class'] }>()
|
||||
const emits = defineEmits<SplitterGroupEmits>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SplitterGroup v-bind="forwarded" :class="cn('flex h-full w-full data-[panel-group-direction=vertical]:flex-col', props.class)">
|
||||
<slot />
|
||||
</SplitterGroup>
|
||||
</template>
|
||||
3
apps/www/src/lib/registry/default/ui/resizable/index.ts
Normal file
3
apps/www/src/lib/registry/default/ui/resizable/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export { default as ResizablePanelGroup } from './ResizablePanelGroup.vue'
|
||||
export { default as ResizableHandle } from './ResizableHandle.vue'
|
||||
export { SplitterPanel as ResizablePanel } from 'radix-vue'
|
||||
|
|
@ -7,15 +7,16 @@ const props = defineProps<ToasterProps>()
|
|||
<template>
|
||||
<Sonner
|
||||
class="toaster group"
|
||||
:class-names="{
|
||||
toast:
|
||||
'group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg',
|
||||
description: 'group-[.toast]:text-muted-foreground',
|
||||
actionButton:
|
||||
'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground',
|
||||
cancelButton:
|
||||
'group-[.toast]:bg-muted group-[.toast]:text-muted-foreground',
|
||||
}"
|
||||
v-bind="props"
|
||||
:toast-options="{
|
||||
classes: {
|
||||
toast: 'group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg',
|
||||
description: 'group-[.toast]:text-muted-foreground',
|
||||
actionButton:
|
||||
'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground',
|
||||
cancelButton:
|
||||
'group-[.toast]:bg-muted group-[.toast]:text-muted-foreground',
|
||||
},
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ const columns: ColumnDef<Payment>[] = [
|
|||
{
|
||||
id: 'select',
|
||||
header: ({ table }) => h(Checkbox, {
|
||||
'checked': table.getIsAllPageRowsSelected(),
|
||||
'checked': table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && 'indeterminate'),
|
||||
'onUpdate:checked': value => table.toggleAllPageRowsSelected(!!value),
|
||||
'ariaLabel': 'Select all',
|
||||
}),
|
||||
|
|
@ -104,7 +104,7 @@ const columns: ColumnDef<Payment>[] = [
|
|||
return h(Button, {
|
||||
variant: 'ghost',
|
||||
onClick: () => column.toggleSorting(column.getIsSorted() === 'asc'),
|
||||
}, ['Email', h(CaretSortIcon, { class: 'ml-2 h-4 w-4' })])
|
||||
}, () => ['Email', h(CaretSortIcon, { class: 'ml-2 h-4 w-4' })])
|
||||
},
|
||||
cell: ({ row }) => h('div', { class: 'lowercase' }, row.getValue('email')),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { watchOnce } from '@vueuse/core'
|
||||
import { Carousel, type CarouselApi, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from '@/lib/registry/new-york/ui/carousel'
|
||||
import { Card, CardContent } from '@/lib/registry/new-york/ui/card'
|
||||
|
||||
const emblaMainApi = ref<CarouselApi>()
|
||||
const emblaThumbnailApi = ref<CarouselApi>()
|
||||
const selectedIndex = ref(0)
|
||||
|
||||
function onSelect() {
|
||||
if (!emblaMainApi.value || !emblaThumbnailApi.value)
|
||||
return
|
||||
selectedIndex.value = emblaMainApi.value.selectedScrollSnap()
|
||||
emblaThumbnailApi.value.scrollTo(emblaMainApi.value.selectedScrollSnap())
|
||||
}
|
||||
|
||||
function onThumbClick(index: number) {
|
||||
if (!emblaMainApi.value || !emblaThumbnailApi.value)
|
||||
return
|
||||
emblaMainApi.value.scrollTo(index)
|
||||
}
|
||||
|
||||
watchOnce(emblaMainApi, (emblaMainApi) => {
|
||||
if (!emblaMainApi)
|
||||
return
|
||||
|
||||
onSelect()
|
||||
emblaMainApi.on('select', onSelect)
|
||||
emblaMainApi.on('reInit', onSelect)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full sm:w-auto">
|
||||
<Carousel
|
||||
class="relative w-full max-w-xs"
|
||||
@init-api="(val) => emblaMainApi = val"
|
||||
>
|
||||
<CarouselContent>
|
||||
<CarouselItem v-for="(_, index) in 10" :key="index">
|
||||
<div class="p-1">
|
||||
<Card>
|
||||
<CardContent class="flex aspect-square items-center justify-center p-6">
|
||||
<span class="text-4xl font-semibold">{{ index + 1 }}</span>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</CarouselItem>
|
||||
</CarouselContent>
|
||||
<CarouselPrevious />
|
||||
<CarouselNext />
|
||||
</Carousel>
|
||||
|
||||
<Carousel
|
||||
class="relative w-full max-w-xs"
|
||||
@init-api="(val) => emblaThumbnailApi = val"
|
||||
>
|
||||
<CarouselContent class="flex gap-1 ml-0">
|
||||
<CarouselItem v-for="(_, index) in 10" :key="index" class="pl-0 basis-1/4 cursor-pointer" @click="onThumbClick(index)">
|
||||
<div class="p-1" :class="index === selectedIndex ? '' : 'opacity-50'">
|
||||
<Card>
|
||||
<CardContent class="flex aspect-square items-center justify-center p-6">
|
||||
<span class="text-4xl font-semibold">{{ index + 1 }}</span>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</CarouselItem>
|
||||
</CarouselContent>
|
||||
</Carousel>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
<script lang="ts" setup>
|
||||
import { createReusableTemplate, useMediaQuery } from '@vueuse/core'
|
||||
import { ref } from 'vue'
|
||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '@/lib/registry/new-york/ui/command'
|
||||
import { Drawer, DrawerContent, DrawerTrigger } from '@/lib/registry/new-york/ui/drawer'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/lib/registry/new-york/ui/popover'
|
||||
|
||||
interface Status {
|
||||
value: string
|
||||
label: string
|
||||
}
|
||||
|
||||
const statuses: Status[] = [
|
||||
{
|
||||
value: 'backlog',
|
||||
label: 'Backlog',
|
||||
},
|
||||
{
|
||||
value: 'todo',
|
||||
label: 'Todo',
|
||||
},
|
||||
{
|
||||
value: 'in progress',
|
||||
label: 'In Progress',
|
||||
},
|
||||
{
|
||||
value: 'done',
|
||||
label: 'Done',
|
||||
},
|
||||
{
|
||||
value: 'canceled',
|
||||
label: 'Canceled',
|
||||
},
|
||||
]
|
||||
|
||||
const [UseTemplate, StatusList] = createReusableTemplate()
|
||||
const isDesktop = useMediaQuery('(min-width: 768px)')
|
||||
|
||||
const isOpen = ref(false)
|
||||
const selectedStatus = ref<Status | null>(null)
|
||||
|
||||
function onStatusSelect(status: Status) {
|
||||
selectedStatus.value = status
|
||||
isOpen.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UseTemplate>
|
||||
<Command>
|
||||
<CommandInput placeholder="Filter status..." />
|
||||
<CommandList>
|
||||
<CommandEmpty>No results found.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
<CommandItem
|
||||
v-for="status of statuses"
|
||||
:key="status.value"
|
||||
:value="status.value"
|
||||
@select="onStatusSelect(status)"
|
||||
>
|
||||
{{ status.label }}
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</UseTemplate>
|
||||
|
||||
<Popover v-if="isDesktop" v-model:open="isOpen">
|
||||
<PopoverTrigger as-child>
|
||||
<Button variant="outline" class="w-[150px] justify-start">
|
||||
{{ selectedStatus ? selectedStatus.label : "+ Set status" }}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-[200px] p-0" align="start">
|
||||
<StatusList />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
<Drawer v-else v-model:open="isOpen">
|
||||
<DrawerTrigger as-child>
|
||||
<Button variant="outline" class="w-[150px] justify-start">
|
||||
{{ selectedStatus ? selectedStatus.label : "+ Set status" }}
|
||||
</Button>
|
||||
</DrawerTrigger>
|
||||
<DrawerContent>
|
||||
<div class="mt-4 border-t">
|
||||
<StatusList />
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -84,11 +84,11 @@ const columns = [
|
|||
columnHelper.display({
|
||||
id: 'select',
|
||||
header: ({ table }) => h(Checkbox, {
|
||||
'checked': table.getIsAllPageRowsSelected(),
|
||||
'checked': table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && 'indeterminate'),
|
||||
'onUpdate:checked': value => table.toggleAllPageRowsSelected(!!value),
|
||||
'ariaLabel': 'Select all',
|
||||
}),
|
||||
cell: ({ row, column }) => {
|
||||
cell: ({ row }) => {
|
||||
return h(Checkbox, {
|
||||
'checked': row.getIsSelected(),
|
||||
'onUpdate:checked': value => row.toggleSelected(!!value),
|
||||
|
|
@ -165,8 +165,6 @@ const table = useVueTable({
|
|||
},
|
||||
},
|
||||
})
|
||||
|
||||
const getState = table.getState()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ const columns: ColumnDef<Payment>[] = [
|
|||
{
|
||||
id: 'select',
|
||||
header: ({ table }) => h(Checkbox, {
|
||||
'checked': table.getIsAllPageRowsSelected(),
|
||||
'checked': table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && 'indeterminate'),
|
||||
'onUpdate:checked': value => table.toggleAllPageRowsSelected(!!value),
|
||||
'ariaLabel': 'Select all',
|
||||
}),
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user