chore: updates charts using unovis/vue (#77)

* chore: updates charts using unovis/vue

* fix: minor styling
This commit is contained in:
zernonia 2023-09-23 22:18:14 +08:00 committed by GitHub
parent 45eeb25149
commit 05e3f9484e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1561 additions and 749 deletions

View File

@ -4,7 +4,7 @@ import { cn } from '@/lib/utils'
</script> </script>
<template> <template>
<WrapBalancer :class="cn('max-w-[750px] text-lg text-muted-foreground sm:text-xl', $attrs.class ?? '')"> <WrapBalancer :class="cn('max-w-[750px] text-lg text-muted-foreground sm:text-xl', $attrs.class ?? '')" :prefer-native="false">
<slot /> <slot />
</WrapBalancer> </WrapBalancer>
</template> </template>

View File

@ -3,13 +3,24 @@ import { ref } from 'vue'
import { ChevronDown, Minus, Plus, Send } from 'lucide-vue-next' import { ChevronDown, Minus, Plus, Send } from 'lucide-vue-next'
import { addDays, startOfToday } from 'date-fns' import { addDays, startOfToday } from 'date-fns'
import ThemingLayout from './../../layout/ThemingLayout.vue' import ThemingLayout from './../../layout/ThemingLayout.vue'
import {
months, import Container from '@/examples/cards/components/Container.vue'
payments, import CookieSettings from '@/examples/cards/components/CookieSettings.vue'
roles, import CreateAccount from '@/examples/cards/components/CreateAccount.vue'
teamMembers, import DatePicker from '@/examples/cards/components/DatePicker.vue'
years, import GitHubCard from '@/examples/cards/components/GitHubCard.vue'
} from './utils/data' import Notifications from '@/examples/cards/components/Notifications.vue'
import PaymentMethod from '@/examples/cards/components/PaymentMethod.vue'
import ReportAnIssue from '@/examples/cards/components/ReportAnIssue.vue'
import ShareDocument from '@/examples/cards/components/ShareDocument.vue'
import TeamMembers from '@/examples/cards/components/TeamMembers.vue'
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 { import {
Card, Card,
CardContent, CardContent,
@ -18,58 +29,11 @@ import {
CardHeader, CardHeader,
CardTitle, CardTitle,
} from '@/lib/registry/new-york/ui/card' } from '@/lib/registry/new-york/ui/card'
import {
Avatar,
AvatarFallback,
AvatarImage,
} from '@/lib/registry/new-york/ui/avatar'
import { Button } from '@/lib/registry/new-york/ui/button' import { Button } from '@/lib/registry/new-york/ui/button'
import { Textarea } from '@/lib/registry/new-york/ui/textarea'
import { Calendar } from '@/lib/registry/new-york/ui/calendar' import { Calendar } from '@/lib/registry/new-york/ui/calendar'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/lib/registry/new-york/ui/dropdown-menu'
import { Label } from '@/lib/registry/new-york/ui/label'
import { Switch } from '@/lib/registry/new-york/ui/switch'
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/lib/registry/new-york/ui/select'
import { Input } from '@/lib/registry/new-york/ui/input'
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@/lib/registry/new-york/ui/tooltip'
import { Separator } from '@/lib/registry/new-york/ui/separator'
import RadixIconsGithubLogo from '~icons/radix-icons/github-logo'
import RiGoogleLine from '~icons/ri/google-line'
const strictlyNecessarySwitch = ref<boolean>(true)
const functionalCookiesSwitch = ref<boolean>(false)
const performanceCookiesSwitch = ref<boolean>(false)
const selectedArea = ref('Billing')
const selectedSecurity = ref('Medium')
const selectedMonth = ref<string>(months[0])
const selectedYear = ref<string>(years[0])
const selectedPayment = ref(payments[0])
const goal = ref(350) const goal = ref(350)
function switchPayment(payment: any) {
selectedPayment.value = payment
}
const range = ref({ const range = ref({
start: startOfToday(), start: startOfToday(),
end: addDays(startOfToday(), 8), end: addDays(startOfToday(), 8),
@ -82,454 +46,19 @@ const range = ref({
class="items-start justify-center gap-6 rounded-lg md:grids-col-2 grid md:gap-4 lg:grid-cols-10 xl:grid-cols-11 xl:gap-4" class="items-start justify-center gap-6 rounded-lg md:grids-col-2 grid md:gap-4 lg:grid-cols-10 xl:grid-cols-11 xl:gap-4"
> >
<div class="space-y-4 lg:col-span-4 xl:col-span-6 xl:space-y-4"> <div class="space-y-4 lg:col-span-4 xl:col-span-6 xl:space-y-4">
<div class="grid gap-4 sm:grid-cols-2 xl:grid-cols-2"> <CardStats />
<Card>
<CardHeader class="pb-2">
<CardTitle class="text-lg">
Total Revenue
</CardTitle>
</CardHeader>
<CardContent>
<div class="text-2xl font-bold">
$15,231.89
</div>
<p class="text-xs text-muted-foreground">
+20.1% from last month
</p>
<div class="h-24" />
</CardContent>
</Card>
<Card>
<CardHeader class="pb-2">
<CardTitle class="text-lg">
Subscriptions
</CardTitle>
</CardHeader>
<CardContent>
<div class="text-2xl font-bold">
+2,350
</div>
<p class="text-xs text-muted-foreground">
+54.8% from last month
</p>
<div class="h-24" />
</CardContent>
</Card>
</div>
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-1 xl:grid-cols-2"> <div class="grid gap-4 md:grid-cols-2 lg:grid-cols-1 xl:grid-cols-2">
<div class="space-y-4"> <div class="space-y-4">
<Card> <TeamMembers />
<CardHeader> <CookieSettings />
<CardTitle> Team Members </CardTitle> <PaymentMethod />
<CardDescription>
Invite your team members to collaborate.
</CardDescription>
</CardHeader>
<CardContent>
<div
v-for="teamMember in teamMembers"
:key="teamMember.name"
class="flex justify-between items-center"
>
<div class="flex items-center space-x-3 my-4">
<Avatar size="sm">
<AvatarImage :src="teamMember.avatar" />
<AvatarFallback>
{{ teamMember.name.slice(0, 2) }}
</AvatarFallback>
</Avatar>
<div class="flex flex-col">
<p class="text-foreground text-sm font-semibold">
{{ teamMember.name }}
</p>
<p class="text-muted-foreground text-sm">
{{ teamMember.username }}
</p>
</div>
</div>
<DropdownMenu>
<DropdownMenuTrigger>
<Button variant="outline" class="h-9">
{{ teamMember.role }}
<ChevronDown class="w-3 h-3 ml-2" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent class="w-[280px]" align="end">
<DropdownMenuGroup>
<DropdownMenuLabel>
Actions
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem v-for="role in roles" :key="role.name">
<div class="grid space-y-0.5">
<span class="text-foreground font-semibold">
{{ role.name }}
</span>
<span class="text-muted-foreground text-sm">
{{ role.description }}
</span>
</div>
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle> Cookies Settings </CardTitle>
<CardDescription>
Manage your cookies preferences.
</CardDescription>
</CardHeader>
<CardContent>
<div class="grid grid-rows-3 gap-y-5">
<div class="flex justify-between items-center space-x-2">
<Label for="strictly_necessary" class="flex flex-col">
Strictly Necessary
<span
class="text-muted-foreground mt-1 text-xs max-w-[18rem]"
>
These cookies are essential in order to use the website
and use its features.
</span>
</Label>
<Switch
id="strictly_necessary"
v-model:checked="strictlyNecessarySwitch"
/>
</div>
<div class="flex justify-between items-center space-x-2">
<Label for="functional_cookies" class="flex flex-col">
Functional Cookies
<span
class="text-muted-foreground text-xs mt-1 max-w-[18rem]"
>
These cookies enable the website to provide enhanced
functionality and personalization.
</span>
</Label>
<Switch
id="functional_cookies"
v-model:checked="functionalCookiesSwitch"
/>
</div>
<div class="flex justify-between items-center space-x-2">
<Label for="performance_cookies" class="flex flex-col">
Performance Cookies
<span
class="text-muted-foreground text-xs mt-1 max-w-[18rem]"
>
These cookies are used to collect information about how
you use our website.
</span>
</Label>
<Switch
id="performance_cookies"
v-model:checked="performanceCookiesSwitch"
/>
</div>
</div>
</CardContent>
<CardFooter>
<Button variant="outline" class="w-full">
Save Preferences
</Button>
</CardFooter>
</Card>
<Card>
<CardHeader>
<CardTitle class="text-lg">
Payment Method
</CardTitle>
<CardDescription>
Add a new payment method or update your existing payment
method.
</CardDescription>
</CardHeader>
<CardContent>
<div class="grid grid-cols-3 gap-x-4">
<div
v-for="payment in payments"
:key="payment.name"
class="flex flex-col justify-center items-center p-4 rounded-lg cursor-pointer transition-colors ease-in-out duration-200"
:class="[
selectedPayment.name === payment.name
? 'border-2 border-primary'
: 'border border-border hover:bg-accent',
]"
@click="switchPayment(payment)"
>
<component :is="payment.icon" class="w-6 h-6" />
<span class="text-foreground text-sm font-medium mt-1.5">
{{ payment.name }}
</span>
</div>
</div>
<div class="grid gap-2 pt-4">
<Label for="name" class="text-sm"> Name </Label>
<Input id="name" placeholder="Name" />
</div>
<div class="grid gap-2 pt-4">
<Label for="card_number" class="text-sm"> Card number </Label>
<Input id="card_number" placeholder="4242 4242 4242 4242" />
</div>
<div class="grid grid-cols-3 gap-4 pt-4">
<div class="flex flex-col space-y-1.5">
<Label for="expires_month">Month</Label>
<Select v-model="selectedMonth">
<SelectTrigger>
<SelectValue placeholder="Month" />
</SelectTrigger>
<SelectContent side="top">
<SelectGroup>
<SelectItem
v-for="month in months"
:key="month"
:value="month"
>
{{ month }}
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
<div class="flex flex-col space-y-1.5">
<Label for="expires_year"> Year </Label>
<Select v-model="selectedYear">
<SelectTrigger>
<SelectValue placeholder="Year" />
</SelectTrigger>
<SelectContent side="top">
<SelectGroup>
<SelectItem
v-for="year in years"
:key="year"
:value="year"
>
{{ year }}
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
<div class="flex flex-col space-y-1.5">
<Label for="cvc">CVC</Label>
<Input id="cvc" placeholder="123" />
</div>
</div>
</CardContent>
<CardFooter>
<Button class="w-full">
Continue
</Button>
</CardFooter>
</Card>
</div> </div>
<div class="space-y-4"> <div class="space-y-4">
<Card> <CardChat />
<CardHeader class="flex flex-row items-center justify-between"> <CreateAccount />
<div class="flex items-center space-x-3 my-2"> <ReportAnIssue />
<Avatar size="sm">
<AvatarImage
src="https://api.dicebear.com/6.x/lorelei/svg?seed=Bear"
/>
<AvatarFallback>B</AvatarFallback>
</Avatar>
<div class="flex flex-col">
<p class="text-foreground text-sm font-semibold">
Bear Brown
</p>
<p class="text-muted-foreground text-sm">
bear@example.com
</p>
</div>
</div>
<TooltipProvider>
<Tooltip :delay-duration="200">
<TooltipTrigger as-child>
<Button
variant="outline"
class="rounded-full p-2.5 flex items-center justify-center"
>
<Plus class="w-4 h-4" />
</Button>
</TooltipTrigger>
<TooltipContent :side-offset="10">
New message
</TooltipContent>
</Tooltip>
</TooltipProvider>
</CardHeader>
<CardContent>
<div class="space-y-4">
<div
class="flex w-auto max-w-[75%] flex-col gap-2 rounded-lg px-3 py-2 text-sm bg-muted"
>
Hi, how can I help you today?
</div>
<div
class="flex w-auto max-w-[75%] flex-col gap-2 rounded-lg px-3 py-2 text-sm ml-auto bg-primary text-primary-foreground"
>
Hey, I'm having trouble with my account.
</div>
<div
class="flex w-auto max-w-[75%] flex-col gap-2 rounded-lg px-3 py-2 text-sm bg-muted"
>
Sure, I can help you with that. What seems to be the problem?
</div>
<div
class="flex w-auto max-w-[75%] flex-col gap-2 rounded-lg px-3 py-2 text-sm ml-auto bg-primary text-primary-foreground"
>
I can't log in.
</div>
</div>
</CardContent>
<CardFooter>
<div class="flex w-full space-x-2 items-center">
<Input placeholder="Type a message..." class="flex-1" />
<Button class="p-2.5 flex items-center justify-center">
<Send class="w-4 h-4" />
</Button>
</div>
</CardFooter>
</Card>
<Card>
<CardHeader>
<CardTitle>Create an account</CardTitle>
<CardDescription>
Enter your details below to create your account.
</CardDescription>
</CardHeader>
<CardContent>
<div class="grid grid-cols-2 gap-6">
<Button variant="outline">
<RiGoogleLine class="w-4 h-4 mr-2" />
Google
</Button>
<Button variant="outline">
<RadixIconsGithubLogo class="w-4 h-4 mr-2" />
Github
</Button>
</div>
<div class="relative mt-4">
<div class="absolute inset-0 flex items-center">
<span class="w-full border-t border-border" />
</div>
<div class="relative flex justify-center text-xs uppercase">
<span class="bg-card text-muted-foreground px-2"> Or </span>
</div>
</div>
<div class="grid gap-2 pt-4">
<Label for="email">Email</Label>
<Input id="email" placeholder="name@example.com" />
</div>
<div class="grid gap-2 pt-4">
<Label for="password">Password</Label>
<Input id="password" type="password" placeholder="••••••••" />
</div>
</CardContent>
<CardFooter>
<Button class="w-full">
Create account
</Button>
</CardFooter>
</Card>
<Card>
<CardHeader>
<CardTitle> Report an issue </CardTitle>
<CardDescription>
What area are you having problems with?
</CardDescription>
</CardHeader>
<CardContent>
<div class="grid gap-4 sm:grid-cols-2">
<div class="grid gap-2">
<Label for="area">Area</Label>
<Select v-model="selectedArea">
<SelectTrigger>
{{ selectedArea || "Area" }}
</SelectTrigger>
<SelectContent class="w-[139px]">
<SelectGroup>
<SelectItem value="Team">
Team
</SelectItem>
<SelectItem value="Billing">
Billing
</SelectItem>
<SelectItem value="Account">
Account
</SelectItem>
<SelectItem value="Deployment">
Deployment
</SelectItem>
<SelectItem value="Support">
Support
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
<div class="grid gap-2">
<Label for="security">Security Level</Label>
<Select v-model="selectedSecurity">
<SelectTrigger>
<SelectValue placeholder="Medium" />
</SelectTrigger>
<SelectContent class="w-[139px]">
<SelectGroup>
<SelectItem value="Low">
Low
</SelectItem>
<SelectItem value="Medium">
Medium
</SelectItem>
<SelectItem value="High">
High
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
</div>
<div class="grid gap-2 py-4">
<Label for="subject">Subject</Label>
<Input id="subject" placeholder="I need help with..." />
</div>
<div class="grid gap-2">
<Label for="description">Description</Label>
<Textarea
id="description"
placeholder="Describe your issue..."
/>
</div>
</CardContent>
<CardFooter class="flex justify-between items-center">
<Button variant="outline">
Cancel
</Button>
<Button> Submit </Button>
</CardFooter>
</Card>
</div> </div>
</div> </div>
</div> </div>
@ -540,141 +69,17 @@ const range = ref({
</Card> </Card>
<div class="pt-3 sm:pl-2 sm:pt-0 xl:pl-3"> <div class="pt-3 sm:pl-2 sm:pt-0 xl:pl-3">
<Card> <ActivityGoal />
<CardHeader>
<CardTitle class="text-md">
Move Goal
</CardTitle>
<CardDescription>
Set your daily activity goal.
</CardDescription>
</CardHeader>
<CardContent class="pb-2">
<div class="flex items-center justify-center space-x-2">
<Button
variant="outline"
class="w-8 h-8 rounded-full p-0"
:disabled="goal <= 200"
@click="goal -= 10"
>
<Minus class="w-4 h-4" />
</Button>
<div class="flex-1 text-center">
<p
class="text-foreground text-5xl font-bold tracking-tight"
>
{{ goal }}
</p>
<span class="text-muted-foreground text-[11px] uppercase">
Calories/day
</span>
</div>
<Button
variant="outline"
class="w-8 h-8 rounded-full p-0"
:disabled="goal >= 500"
@click="goal += 10"
>
<Plus class="w-4 h-4" />
</Button>
</div>
<div class="my-2" />
</CardContent>
<CardFooter>
<Button class="w-full">
Set Goal
</Button>
</CardFooter>
</Card>
</div> </div>
<div class="pt-3 sm:col-span-2 xl:pt-3"> <div class="pt-3 sm:col-span-2 xl:pt-3">
<Card> <Metric />
<CardHeader>
<CardTitle> Exercise Minutes </CardTitle>
<CardDescription>
Your exercise minutes are ahead of where you normally are.
</CardDescription>
</CardHeader>
<CardContent />
</Card>
</div> </div>
<div class="pt-3 sm:col-span-2 xl:pt-3"> <div class="pt-3 sm:col-span-2 xl:pt-3">
<Card> <DataTable />
<CardHeader>
<CardTitle> Payments </CardTitle>
<CardDescription>
Manage your payment methods and view your billing history.
</CardDescription>
</CardHeader>
<CardContent />
</Card>
</div> </div>
<div class="pt-3 sm:col-span-2 xl:pt-3"> <div class="pt-3 sm:col-span-2 xl:pt-3">
<Card> <ShareDocument />
<CardHeader>
<CardTitle> Share this document </CardTitle>
<CardDescription>
Anyone with this link will be able to view this document.
</CardDescription>
<div class="flex space-x-2 items-center pt-2.5">
<Input
class="flex-1"
placeholder="http://..."
value="http://example.com/link/to/document"
/>
<Button variant="secondary">
Copy Link
</Button>
</div>
</CardHeader>
<CardContent>
<Separator />
<Label class="mt-4"> People with access </Label>
<div
v-for="teamMember in teamMembers"
:key="teamMember.name"
class="flex justify-between items-center"
>
<div class="flex items-center space-x-3 my-4">
<Avatar size="sm">
<AvatarImage :src="teamMember.avatar" />
<AvatarFallback>
{{ teamMember.name.slice(0, 2) }}
</AvatarFallback>
</Avatar>
<div class="flex flex-col">
<p class="text-foreground text-sm font-medium">
{{ teamMember.name }}
</p>
<p class="text-muted-foreground text-sm">
{{ teamMember.username }}
</p>
</div>
</div>
<Select v-model="teamMember.access">
<SelectTrigger class="w-28">
<SelectValue :placeholder="teamMember.access" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="Can edit">
Can edit
</SelectItem>
<SelectItem value="Can view">
Can view
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
</CardContent>
</Card>
</div> </div>
</div> </div>
</div> </div>

View File

@ -15,6 +15,7 @@
"@morev/vue-transitions": "^2.3.6", "@morev/vue-transitions": "^2.3.6",
"@tanstack/vue-table": "^8.9.9", "@tanstack/vue-table": "^8.9.9",
"@unovis/ts": "^1.2.1", "@unovis/ts": "^1.2.1",
"@unovis/vue": "1.3.0-alpha.3",
"@vueuse/core": "^10.4.1", "@vueuse/core": "^10.4.1",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.0.0", "clsx": "^2.0.0",

View File

@ -60,7 +60,7 @@ import { Textarea } from '@/lib/registry/new-york/ui/textarea'
<Select default-value="2"> <Select default-value="2">
<SelectTrigger <SelectTrigger
id="security-level" id="security-level"
class="line-clamp-1 w-[160px] truncate" class="line-clamp-1 w-full truncate"
> >
<SelectValue placeholder="Select level" /> <SelectValue placeholder="Select level" />
</SelectTrigger> </SelectTrigger>

View File

@ -1,100 +1,46 @@
<script setup lang="ts"> <script setup lang="ts">
import { Axis, StackedBar, XYContainer } from '@unovis/ts' import { VisAxis, VisStackedBar, VisXYContainer } from '@unovis/vue'
import { nextTick, onMounted, ref } from 'vue'
type Data = typeof data[number]
const data = [ const data = [
{ { name: 'Jan', total: Math.floor(Math.random() * 5000) + 1000 },
name: 'Jan', { name: 'Feb', total: Math.floor(Math.random() * 5000) + 1000 },
total: Math.floor(Math.random() * 5000) + 1000, { name: 'Mar', total: Math.floor(Math.random() * 5000) + 1000 },
}, { name: 'Apr', total: Math.floor(Math.random() * 5000) + 1000 },
{ { name: 'May', total: Math.floor(Math.random() * 5000) + 1000 },
name: 'Feb', { name: 'Jun', total: Math.floor(Math.random() * 5000) + 1000 },
total: Math.floor(Math.random() * 5000) + 1000, { name: 'Jul', total: Math.floor(Math.random() * 5000) + 1000 },
}, { name: 'Aug', total: Math.floor(Math.random() * 5000) + 1000 },
{ { name: 'Sep', total: Math.floor(Math.random() * 5000) + 1000 },
name: 'Mar', { name: 'Oct', total: Math.floor(Math.random() * 5000) + 1000 },
total: Math.floor(Math.random() * 5000) + 1000, { name: 'Nov', total: Math.floor(Math.random() * 5000) + 1000 },
}, { name: 'Dec', total: Math.floor(Math.random() * 5000) + 1000 },
{
name: 'Apr',
total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: 'May',
total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: 'Jun',
total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: 'Jul',
total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: 'Aug',
total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: 'Sep',
total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: 'Oct',
total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: 'Nov',
total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: 'Dec',
total: Math.floor(Math.random() * 5000) + 1000,
},
] ]
const container = ref<HTMLElement>()
let chart: XYContainer<typeof data[0]>
onMounted(async () => {
if (!container.value)
return
await nextTick()
const bar = new StackedBar<typeof data[0]>({
x: (d, i) => i,
y: d => d.total,
color: '#41b883',
roundedCorners: 4,
barPadding: 0.15,
})
chart = new XYContainer(container.value, {
components: [bar],
height: '350px',
margin: { left: 20, right: 20 },
xAxis: new Axis({
type: 'x',
numTicks: data.length,
tickFormat: (index: number) => data[index].name,
gridLine: false,
tickLine: false,
color: '#888888',
}),
yAxis: new Axis({
type: 'y',
tickFormat: (value: number) => `$${value}`,
gridLine: false,
domainLine: false,
tickLine: false,
color: '#888888',
}),
}, data)
})
</script> </script>
<template> <template>
<div ref="container" /> <VisXYContainer height="350px" :margin="{ left: 20, right: 20 }" :data="data">
<VisStackedBar
:x="(d: Data, i: number) => i"
:y="(d: Data) => d.total"
color="#41b883"
:rounded-corners="4"
:bar-padding="0.15"
/>
<VisAxis
type="x"
:num-ticks="data.length"
:tick-format="(index: number) => data[index]?.name"
:grid-line="false"
:tick-line="false" color="#888888"
/>
<VisAxis
type="y"
:num-ticks="data.length"
:tick-format="(index: number) => data[index]?.name"
:grid-line="false"
:tick-line="false"
:domain-line="false" color="#888888"
/>
</VisXYContainer>
</template> </template>

View File

@ -0,0 +1,209 @@
<script setup lang="ts">
import { Check, Plus, Send } from 'lucide-vue-next'
import { computed, ref } from 'vue'
import {
Card,
CardContent,
CardFooter,
CardHeader,
} from '@/lib/registry/default/ui/card'
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/lib/registry/default/ui/dialog'
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '@/lib/registry/default/ui/command'
import { Avatar, AvatarFallback, AvatarImage } from '@/lib/registry/default/ui/avatar'
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@/lib/registry/default/ui/tooltip'
import { Button } from '@/lib/registry/default/ui/button'
import { Input } from '@/lib/registry/default/ui/input'
import { cn } from '@/lib/utils'
const input = ref('')
const inputLength = computed(() => input.value.trim().length)
const users = ref([
{
name: 'Olivia Martin',
email: 'm@example.com',
avatar: '/avatars/01.png',
},
{
name: 'Isabella Nguyen',
email: 'isabella.nguyen@email.com',
avatar: '/avatars/03.png',
},
{
name: 'Emma Wilson',
email: 'emma@example.com',
avatar: '/avatars/05.png',
},
{
name: 'Jackson Lee',
email: 'lee@example.com',
avatar: '/avatars/02.png',
},
{
name: 'William Kim',
email: 'will@email.com',
avatar: '/avatars/04.png',
},
])
type User = (typeof users.value)[number]
const messages = ref([
{ role: 'agent', content: 'Hi, how can I help you today?' },
{ role: 'user', content: 'Hey, I\'m having trouble with my account.' },
{ role: 'agent', content: 'What seems to be the problem?' },
{ role: 'user', content: 'I can\'t log in.' },
])
const open = ref(false)
const selectedUsers = ref<User[]>([])
</script>
<template>
<Card>
<CardHeader class="flex flex-row items-center justify-between">
<div className="flex items-center space-x-4">
<Avatar>
<AvatarImage src="/avatars/01.png" alt="Image" />
<AvatarFallback>OM</AvatarFallback>
</Avatar>
<div>
<p className="text-sm font-medium leading-none">
Sofia Davis
</p>
<p className="text-sm text-muted-foreground">
m@example.com
</p>
</div>
</div>
<TooltipProvider>
<Tooltip :delay-duration="200">
<TooltipTrigger as-child>
<Button
variant="outline"
class="rounded-full p-2.5 flex items-center justify-center"
@click="open = true"
>
<Plus class="w-4 h-4" />
</Button>
</TooltipTrigger>
<TooltipContent :side-offset="10">
New message
</TooltipContent>
</Tooltip>
</TooltipProvider>
</CardHeader>
<CardContent>
<div class="space-y-4">
<div
v-for="(message, index) in messages"
:key="index"
:class="cn(
'flex w-max max-w-[75%] flex-col gap-2 rounded-lg px-3 py-2 text-sm',
message.role === 'user' ? 'ml-auto bg-primary text-primary-foreground' : 'bg-muted',
)"
>
{{ message.content }}
</div>
</div>
</CardContent>
<CardFooter>
<form
class="flex w-full items-center space-x-2"
@submit.prevent="() => {
if (inputLength === 0) return
messages.push({
role: 'user',
content: input,
})
}"
>
<Input v-model="input" placeholder="Type a message..." class="flex-1" />
<Button class="p-2.5 flex items-center justify-center" :disabled="inputLength === 0">
<Send class="w-4 h-4" />
<span class="sr-only">Send</span>
</Button>
</form>
</CardFooter>
</Card>
<Dialog v-model:open="open">
<DialogContent class="gap-0 p-0 outline-none">
<DialogHeader class="px-4 pb-4 pt-5">
<DialogTitle>New message</DialogTitle>
<DialogDescription>
Invite a user to this thread. This will create a new group
message.
</DialogDescription>
</DialogHeader>
<Command
class="overflow-hidden rounded-t-none border-t"
:filter-function="(list: User[], search) => list.filter(l => l.name.toLowerCase().includes(search.toLowerCase()))"
>
<CommandInput placeholder="Search user..." />
<CommandList>
<CommandEmpty>No users found.</CommandEmpty>
<CommandGroup class="p-2">
<CommandItem
v-for="user in users"
:key="user.email"
:value="user"
class="flex items-center px-2"
@select="() => {
const index = selectedUsers.findIndex(u => u === user)
if (index !== -1) {
selectedUsers.splice(index, 1)
}
else {
selectedUsers.push(user)
}
}"
>
<Avatar>
<AvatarImage :src="user.avatar" alt="Image" />
<AvatarFallback>{{ user.name[0] }}</AvatarFallback>
</Avatar>
<div class="ml-2">
<p class="text-sm font-medium leading-none">
{{ user.name }}
</p>
<p class="text-sm text-muted-foreground">
{{ user.email }}
</p>
</div>
<Check v-if="selectedUsers.includes(user)" class="ml-auto flex h-5 w-5 text-primary" />
</CommandItem>
</CommandGroup>
</CommandList>
</Command>
<DialogFooter class="flex items-center border-t p-4 sm:justify-between">
<div v-if="selectedUsers.length > 0" class="flex -space-x-2 overflow-hidden">
<Avatar
v-for="user in selectedUsers"
:key="user.email"
class="inline-block border-2 border-background"
>
<AvatarImage :src="user.avatar" />
<AvatarFallback>{{ user.name[0] }}</AvatarFallback>
</Avatar>
</div>
<p v-else class="text-sm text-muted-foreground">
Select users to add to this thread.
</p>
<Button
:disabled="selectedUsers.length < 2"
@click="open = false"
>
Continue
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</template>

View File

@ -0,0 +1,101 @@
<script setup lang="ts">
import { VisLine, VisScatter, VisStackedBar, VisXYContainer } from '@unovis/vue'
import { computed } from 'vue'
import { useData } from 'vitepress'
import { Card, CardContent, CardHeader, CardTitle } from '@/lib/registry/default/ui/card'
import { useConfigStore } from '@/stores/config'
import { themes } from '@/lib/registry/themes'
type Data = typeof data[number]
const data = [
{ revenue: 10400, subscription: 240 },
{ revenue: 14405, subscription: 300 },
{ revenue: 9400, subscription: 200 },
{ revenue: 8200, subscription: 278 },
{ revenue: 7000, subscription: 189 },
{ revenue: 9600, subscription: 239 },
{ revenue: 11244, subscription: 278 },
{ revenue: 26475, subscription: 189 },
]
const cfg = useConfigStore()
const { isDark } = useData()
const theme = computed(() => themes.find(theme => theme.name === cfg.config.value.theme))
const lineX = (d: Data, i: number) => i
const lineY = (d: Data) => d.revenue
</script>
<template>
<div class="grid gap-4 sm:grid-cols-2 xl:grid-cols-2">
<Card>
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle class="text-sm font-normal">
Total Revenue
</CardTitle>
</CardHeader>
<CardContent>
<div class="text-2xl font-bold">
$15,231.89
</div>
<p class="text-xs text-muted-foreground">
+20.1% from last month
</p>
<div class="h-[80px]">
<VisXYContainer
height="80px"
:data="data" :margin="{
top: 5,
right: 10,
left: 10,
bottom: 0,
}"
:style="{
'--theme-primary': `hsl(${
theme?.cssVars[isDark ? 'dark' : 'light'].primary
})`,
}"
>
<VisLine :x="lineX" :y="lineY" color="var(--theme-primary)" />
<VisScatter :x="lineX" :y="lineY" :size="6" stroke-color="var(--theme-primary)" :stroke-width="2" color="white" />
</VisXYContainer>
</div>
</CardContent>
</Card>
<Card>
<CardHeader class="pb-2">
<CardTitle class="text-lg">
Subscriptions
</CardTitle>
</CardHeader>
<CardContent>
<div class="text-2xl font-bold">
+2,350
</div>
<p class="text-xs text-muted-foreground">
+54.8% from last month
</p>
<div class="mt-4 h-[80px]">
<VisXYContainer
height="80px" :data="data" :style="{
'--theme-primary': `hsl(${
theme?.cssVars[isDark ? 'dark' : 'light'].primary
})`,
}"
>
<VisStackedBar
:x="lineX"
:y="(d: Data) => d.subscription"
:bar-padding="0.1"
:rounded-corners="0" color="var(--theme-primary)"
/>
</VisXYContainer>
</div>
</CardContent>
</Card>
</div>
</template>

View File

@ -1,6 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { computed, ref } from 'vue'
import { ChevronDown, Minus, Plus, Send } from 'lucide-vue-next'
import { VisStackedBar, VisXYContainer } from '@unovis/vue'
import { useData } from 'vitepress'
import { Button } from '@/lib/registry/default/ui/button' import { Button } from '@/lib/registry/default/ui/button'
import { import {
Card, Card,
CardContent, CardContent,
@ -12,10 +16,13 @@ import {
import { themes } from '@/lib/registry/themes' import { themes } from '@/lib/registry/themes'
import { useConfigStore } from '@/stores/config' import { useConfigStore } from '@/stores/config'
const { theme, radius, setRadius, setTheme } = useConfigStore() const { isDark } = useData()
const cfg = useConfigStore()
const theme = computed(() => themes.find(theme => theme.name === cfg.config.value.theme))
const goal = ref(350) const goal = ref(350)
type Data = typeof data[number]
const data = [ const data = [
{ goal: 400 }, { goal: 400 },
{ goal: 300 }, { goal: 300 },
@ -35,22 +42,22 @@ const data = [
<template> <template>
<Card> <Card>
<CardHeader class-name="pb-4"> <CardHeader class="pb-4">
<CardTitle class-name="text-base"> <CardTitle class="text-base">
Move Goal Move Goal
</CardTitle> </CardTitle>
<CardDescription>Set your daily activity goal.</CardDescription> <CardDescription>Set your daily activity goal.</CardDescription>
</CardHeader> </CardHeader>
<CardContent class-name="pb-2"> <CardContent class="pb-2">
<div class="flex items-center justify-center space-x-2"> <div class="flex items-center justify-center space-x-2">
<Button <Button
variant="outline" variant="outline"
size="icon" size="icon"
class-name="h-8 w-8 shrink-0 rounded-full" class="h-8 w-8 shrink-0 rounded-full"
:disabled="goal <= 200" :disabled="goal <= 200"
@click="goal -= 10" @click="goal -= 10"
> >
<Minus class-name="h-4 w-4" /> <Minus class="h-4 w-4" />
<span class="sr-only">Decrease</span> <span class="sr-only">Decrease</span>
</Button> </Button>
<div class="flex-1 text-center"> <div class="flex-1 text-center">
@ -64,35 +71,37 @@ const data = [
<Button <Button
variant="outline" variant="outline"
size="icon" size="icon"
class-name="h-8 w-8 shrink-0 rounded-full" class="h-8 w-8 shrink-0 rounded-full"
:disabled="goal >= 400" :disabled="goal >= 400"
@click="goal += 10 " @click="goal += 10 "
> >
<Plus class-name="h-4 w-4" /> <Plus class="h-4 w-4" />
<span class="sr-only">Increase</span> <span class="sr-only">Increase</span>
</Button> </Button>
</div> </div>
<div class="my-3 h-[60px]"> <div class="my-3 h-[60px]">
<!-- <ResponsiveContainer width="100%" height="100%"> <VisXYContainer
<BarChart data="{data}"> :data="data"
<Bar height="60px"
data-key="goal" :style="{
style="{" 'opacity': 0.2,
{ '--theme-primary': `hsl(${
fill: "var(--theme-primary)", theme?.cssVars[isDark ? 'dark' : 'light'].primary
opacity: 0.2, })`,
"--theme-primary": `hsl(${ }"
theme?.cssVars[mode="==" "dark" ? "dark" : "light"].primary >
})`, <VisStackedBar
} as React.CSSProperties :x="(d: Data, i :number) => i"
} :y="(d: Data) => d.goal"
/> color="var(--theme-primary)"
</BarChart> :bar-padding="0.1"
</ResponsiveContainer> --> :rounded-corners="0"
/>
</VisXYContainer>
</div> </div>
</CardContent> </CardContent>
<CardFooter> <CardFooter>
<Button class-name="w-full"> <Button class="w-full">
Set Goal Set Goal
</Button> </Button>
</CardFooter> </CardFooter>

View File

@ -0,0 +1,262 @@
<script setup lang="ts">
import type {
ColumnDef,
ColumnFiltersState,
SortingState,
VisibilityState,
} from '@tanstack/vue-table'
import {
FlexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useVueTable,
} from '@tanstack/vue-table'
import { h, ref } from 'vue'
import DropdownAction from '../DataTableDemoColumn.vue'
import RadixIconsCaretSort from '~icons/radix-icons/caret-sort'
import RadixIconsChevronDown from '~icons/radix-icons/chevron-down'
import { Button } from '@/lib/registry/default/ui/button'
import { Checkbox } from '@/lib/registry/default/ui/checkbox'
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuTrigger,
} from '@/lib/registry/default/ui/dropdown-menu'
import { Input } from '@/lib/registry/default/ui/input'
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/lib/registry/default/ui/table'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/lib/registry/default/ui/card'
import { valueUpdater } from '@/lib/utils'
export interface Payment {
id: string
amount: number
status: 'pending' | 'processing' | 'success' | 'failed'
email: string
}
const data: Payment[] = [
{
id: 'm5gr84i9',
amount: 316,
status: 'success',
email: 'ken99@yahoo.com',
},
{
id: '3u1reuv4',
amount: 242,
status: 'success',
email: 'Abe45@gmail.com',
},
{
id: 'derv1ws0',
amount: 837,
status: 'processing',
email: 'Monserrat44@gmail.com',
},
{
id: '5kma53ae',
amount: 874,
status: 'success',
email: 'Silas22@gmail.com',
},
{
id: 'bhqecj4p',
amount: 721,
status: 'failed',
email: 'carmella@hotmail.com',
},
]
const columns: ColumnDef<Payment>[] = [
{
id: 'select',
header: ({ table }) => h(Checkbox, {
'checked': table.getIsAllPageRowsSelected(),
'onUpdate:checked': value => table.toggleAllPageRowsSelected(!!value),
'ariaLabel': 'Select all',
}),
cell: ({ row }) => h(Checkbox, {
'checked': row.getIsSelected(),
'onUpdate:checked': value => row.toggleSelected(!!value),
'ariaLabel': 'Select row',
}),
enableSorting: false,
enableHiding: false,
},
{
accessorKey: 'status',
header: 'Status',
cell: ({ row }) => h('div', { class: 'capitalize' }, row.getValue('status')),
},
{
accessorKey: 'email',
header: ({ column }) => {
return h(Button, {
variant: 'ghost',
onClick: () => column.toggleSorting(column.getIsSorted() === 'asc'),
}, ['Email', h(RadixIconsCaretSort, { class: 'ml-2 h-4 w-4' })])
},
cell: ({ row }) => h('div', { class: 'lowercase' }, row.getValue('email')),
},
{
accessorKey: 'amount',
header: () => h('div', { class: 'text-right' }, 'Amount'),
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
// Format the amount as a dollar amount
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
}).format(amount)
return h('div', { class: 'text-right font-medium' }, formatted)
},
},
{
id: 'actions',
enableHiding: false,
cell: ({ row }) => {
const payment = row.original
return h(DropdownAction, {
payment,
})
},
},
]
const sorting = ref<SortingState>([])
const columnFilters = ref<ColumnFiltersState>([])
const columnVisibility = ref<VisibilityState>({})
const rowSelection = ref({})
const table = useVueTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting),
onColumnFiltersChange: updaterOrValue => valueUpdater(updaterOrValue, columnFilters),
onColumnVisibilityChange: updaterOrValue => valueUpdater(updaterOrValue, columnVisibility),
onRowSelectionChange: updaterOrValue => valueUpdater(updaterOrValue, rowSelection),
state: {
get sorting() { return sorting.value },
get columnFilters() { return columnFilters.value },
get columnVisibility() { return columnVisibility.value },
get rowSelection() { return rowSelection.value },
},
})
</script>
<template>
<Card>
<CardHeader>
<CardTitle>Payments</CardTitle>
<CardDescription>Manage your payments.</CardDescription>
</CardHeader>
<CardContent>
<div class="w-full">
<div className="mb-4 flex items-center gap-4">
<Input
class="max-w-sm"
placeholder="Filter emails..."
:model-value="table.getColumn('email')?.getFilterValue() as string"
@update:model-value=" table.getColumn('email')?.setFilterValue($event)"
/>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<Button variant="outline" class="ml-auto">
Columns <RadixIconsChevronDown class="ml-2 h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuCheckboxItem
v-for="column in table.getAllColumns().filter((column) => column.getCanHide())"
:key="column.id"
class="capitalize"
:checked="column.getIsVisible()"
@update:checked="(value) => {
column.toggleVisibility(!!value)
}"
>
{{ column.id }}
</DropdownMenuCheckboxItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
<div class="rounded-md border">
<Table>
<TableHeader>
<TableRow v-for="headerGroup in table.getHeaderGroups()" :key="headerGroup.id">
<TableHead v-for="header in headerGroup.headers" :key="header.id" class="[&:has([role=checkbox])]:pl-3">
<FlexRender v-if="!header.isPlaceholder" :render="header.column.columnDef.header" :props="header.getContext()" />
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<template v-if="table.getRowModel().rows?.length">
<TableRow
v-for="row in table.getRowModel().rows"
:key="row.id"
:data-state="row.getIsSelected() && 'selected'"
>
<TableCell v-for="cell in row.getVisibleCells()" :key="cell.id" class="[&:has([role=checkbox])]:pl-3">
<FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
</TableCell>
</TableRow>
</template>
<TableRow v-else>
<TableCell
:col-span="columns.length"
class="h-24 text-center"
>
No results.
</TableCell>
</TableRow>
</TableBody>
</Table>
</div>
<div class="flex items-center justify-end space-x-2 py-4">
<div class="flex-1 text-sm text-muted-foreground">
{{ table.getFilteredSelectedRowModel().rows.length }} of
{{ table.getFilteredRowModel().rows.length }} row(s) selected.
</div>
<div class="space-x-2">
<Button
variant="outline"
size="sm"
:disabled="!table.getCanPreviousPage()"
@click="table.previousPage()"
>
Previous
</Button>
<Button
variant="outline"
size="sm"
:disabled="!table.getCanNextPage()"
@click="table.nextPage()"
>
Next
</Button>
</div>
</div>
</div>
</CardContent>
</Card>
</template>

View File

@ -0,0 +1,91 @@
<script setup lang="ts">
import { VisCrosshair, VisLine, VisScatter, VisTooltip, VisXYContainer } from '@unovis/vue'
import { Line } from '@unovis/ts'
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/lib/registry/default/ui/card'
import { useConfigStore } from '@/stores/config'
const { themePrimary } = useConfigStore()
type Data = typeof data[number]
const data = [
{ average: 400, today: 240 },
{ average: 300, today: 139 },
{ average: 200, today: 980 },
{ average: 278, today: 390 },
{ average: 189, today: 480 },
{ average: 239, today: 380 },
{ average: 349, today: 430 },
]
const x = (d: Data, i: number) => i
function template(d: Data) {
return `
<div class="rounded-lg border bg-background p-2 shadow-sm">
<div class="grid grid-cols-2 gap-2">
<div class="flex flex-col">
<span class="text-[0.70rem] uppercase text-muted-foreground">
Average
</span>
<span class="font-bold text-muted-foreground">
${d.average}
</span>
</div>
<div class="flex flex-col">
<span class="text-[0.70rem] uppercase text-muted-foreground">
Today
</span>
<span class="font-bold text-white">
${d.today}
</span>
</div>
</div>
</div>`
}
function computeLineOpacity(val: any, index: number) {
if (index === 0)
return '0.5'
}
</script>
<template>
<Card>
<CardHeader>
<CardTitle>Exercise Minutes</CardTitle>
<CardDescription>
Your excercise minutes are ahead of where you normally are.
</CardDescription>
</CardHeader>
<CardContent class="pb-4">
<div class="h-[200px]">
<VisXYContainer
height="200px"
:data="data"
:margin="{
top: 5,
right: 10,
left: 10,
bottom: 0,
}"
:style="{
'--theme-primary': themePrimary,
'--vis-tooltip-padding': '0px',
'--vis-tooltip-background-color': 'transparent',
'--vis-tooltip-border-color': 'transparent',
}"
>
<VisTooltip />
<VisLine :x="x" :y="[(d: Data) => d.average, (d: Data) => d.today]" :stroke-width="2" color="var(--theme-primary)" :attributes="{ [Line.selectors.linePath]: { opacity: computeLineOpacity } }" />
<VisScatter :x="x" :y="[(d: Data) => d.average, (d: Data) => d.today]" :size="6" :stroke-width="2" stroke-color="var(--theme-primary)" color="white" />
<VisCrosshair :template="template" />
</VisXYContainer>
</div>
</CardContent>
</Card>
</template>

View File

@ -0,0 +1,209 @@
<script setup lang="ts">
import { Check, Plus, Send } from 'lucide-vue-next'
import { computed, ref } from 'vue'
import {
Card,
CardContent,
CardFooter,
CardHeader,
} from '@/lib/registry/new-york/ui/card'
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/lib/registry/new-york/ui/dialog'
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '@/lib/registry/new-york/ui/command'
import { Avatar, AvatarFallback, AvatarImage } from '@/lib/registry/new-york/ui/avatar'
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@/lib/registry/new-york/ui/tooltip'
import { Button } from '@/lib/registry/new-york/ui/button'
import { Input } from '@/lib/registry/new-york/ui/input'
import { cn } from '@/lib/utils'
const input = ref('')
const inputLength = computed(() => input.value.trim().length)
const users = ref([
{
name: 'Olivia Martin',
email: 'm@example.com',
avatar: '/avatars/01.png',
},
{
name: 'Isabella Nguyen',
email: 'isabella.nguyen@email.com',
avatar: '/avatars/03.png',
},
{
name: 'Emma Wilson',
email: 'emma@example.com',
avatar: '/avatars/05.png',
},
{
name: 'Jackson Lee',
email: 'lee@example.com',
avatar: '/avatars/02.png',
},
{
name: 'William Kim',
email: 'will@email.com',
avatar: '/avatars/04.png',
},
])
type User = (typeof users.value)[number]
const messages = ref([
{ role: 'agent', content: 'Hi, how can I help you today?' },
{ role: 'user', content: 'Hey, I\'m having trouble with my account.' },
{ role: 'agent', content: 'What seems to be the problem?' },
{ role: 'user', content: 'I can\'t log in.' },
])
const open = ref(false)
const selectedUsers = ref<User[]>([])
</script>
<template>
<Card>
<CardHeader class="flex flex-row items-center justify-between">
<div class="flex items-center space-x-4">
<Avatar>
<AvatarImage src="/avatars/01.png" alt="Image" />
<AvatarFallback>OM</AvatarFallback>
</Avatar>
<div>
<p class="text-sm font-medium leading-none">
Sofia Davis
</p>
<p class="text-sm text-muted-foreground">
m@example.com
</p>
</div>
</div>
<TooltipProvider>
<Tooltip :delay-duration="200">
<TooltipTrigger as-child>
<Button
variant="outline"
class="rounded-full p-2.5 flex items-center justify-center"
@click="open = true"
>
<Plus class="w-4 h-4" />
</Button>
</TooltipTrigger>
<TooltipContent :side-offset="10">
New message
</TooltipContent>
</Tooltip>
</TooltipProvider>
</CardHeader>
<CardContent>
<div class="space-y-4">
<div
v-for="(message, index) in messages"
:key="index"
:class="cn(
'flex w-max max-w-[75%] flex-col gap-2 rounded-lg px-3 py-2 text-sm',
message.role === 'user' ? 'ml-auto bg-primary text-primary-foreground' : 'bg-muted',
)"
>
{{ message.content }}
</div>
</div>
</CardContent>
<CardFooter>
<form
class="flex w-full items-center space-x-2"
@submit.prevent="() => {
if (inputLength === 0) return
messages.push({
role: 'user',
content: input,
})
}"
>
<Input v-model="input" placeholder="Type a message..." class="flex-1" />
<Button class="p-2.5 flex items-center justify-center" :disabled="inputLength === 0">
<Send class="w-4 h-4" />
<span class="sr-only">Send</span>
</Button>
</form>
</CardFooter>
</Card>
<Dialog v-model:open="open">
<DialogContent class="gap-0 p-0 outline-none">
<DialogHeader class="px-4 pb-4 pt-5">
<DialogTitle>New message</DialogTitle>
<DialogDescription>
Invite a user to this thread. This will create a new group
message.
</DialogDescription>
</DialogHeader>
<Command
class="overflow-hidden rounded-t-none border-t"
:filter-function="(list: User[], search) => list.filter(l => l.name.toLowerCase().includes(search.toLowerCase()))"
>
<CommandInput placeholder="Search user..." />
<CommandList>
<CommandEmpty>No users found.</CommandEmpty>
<CommandGroup class="p-2">
<CommandItem
v-for="user in users"
:key="user.email"
:value="user"
class="flex items-center px-2"
@select="() => {
const index = selectedUsers.findIndex(u => u === user)
if (index !== -1) {
selectedUsers.splice(index, 1)
}
else {
selectedUsers.push(user)
}
}"
>
<Avatar>
<AvatarImage :src="user.avatar" alt="Image" />
<AvatarFallback>{{ user.name[0] }}</AvatarFallback>
</Avatar>
<div class="ml-2">
<p class="text-sm font-medium leading-none">
{{ user.name }}
</p>
<p class="text-sm text-muted-foreground">
{{ user.email }}
</p>
</div>
<Check v-if="selectedUsers.includes(user)" class="ml-auto flex h-5 w-5 text-primary" />
</CommandItem>
</CommandGroup>
</CommandList>
</Command>
<DialogFooter class="flex items-center border-t p-4 sm:justify-between">
<div v-if="selectedUsers.length > 0" class="flex -space-x-2 overflow-hidden">
<Avatar
v-for="user in selectedUsers"
:key="user.email"
class="inline-block border-2 border-background"
>
<AvatarImage :src="user.avatar" />
<AvatarFallback>{{ user.name[0] }}</AvatarFallback>
</Avatar>
</div>
<p v-else class="text-sm text-muted-foreground">
Select users to add to this thread.
</p>
<Button
:disabled="selectedUsers.length < 2"
@click="open = false"
>
Continue
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</template>

View File

@ -0,0 +1,101 @@
<script setup lang="ts">
import { VisLine, VisScatter, VisStackedBar, VisXYContainer } from '@unovis/vue'
import { computed } from 'vue'
import { useData } from 'vitepress'
import { Card, CardContent, CardHeader, CardTitle } from '@/lib/registry/new-york/ui/card'
import { useConfigStore } from '@/stores/config'
import { themes } from '@/lib/registry/themes'
type Data = typeof data[number]
const data = [
{ revenue: 10400, subscription: 240 },
{ revenue: 14405, subscription: 300 },
{ revenue: 9400, subscription: 200 },
{ revenue: 8200, subscription: 278 },
{ revenue: 7000, subscription: 189 },
{ revenue: 9600, subscription: 239 },
{ revenue: 11244, subscription: 278 },
{ revenue: 26475, subscription: 189 },
]
const cfg = useConfigStore()
const { isDark } = useData()
const theme = computed(() => themes.find(theme => theme.name === cfg.config.value.theme))
const lineX = (d: Data, i: number) => i
const lineY = (d: Data) => d.revenue
</script>
<template>
<div class="grid gap-4 sm:grid-cols-2 xl:grid-cols-2">
<Card>
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle class="text-sm font-normal">
Total Revenue
</CardTitle>
</CardHeader>
<CardContent>
<div class="text-2xl font-bold">
$15,231.89
</div>
<p class="text-xs text-muted-foreground">
+20.1% from last month
</p>
<div class="h-[80px]">
<VisXYContainer
height="80px"
:data="data" :margin="{
top: 5,
right: 10,
left: 10,
bottom: 0,
}"
:style="{
'--theme-primary': `hsl(${
theme?.cssVars[isDark ? 'dark' : 'light'].primary
})`,
}"
>
<VisLine :x="lineX" :y="lineY" color="var(--theme-primary)" />
<VisScatter :x="lineX" :y="lineY" :size="6" stroke-color="var(--theme-primary)" :stroke-width="2" color="white" />
</VisXYContainer>
</div>
</CardContent>
</Card>
<Card>
<CardHeader class="pb-2">
<CardTitle class="text-lg">
Subscriptions
</CardTitle>
</CardHeader>
<CardContent>
<div class="text-2xl font-bold">
+2,350
</div>
<p class="text-xs text-muted-foreground">
+54.8% from last month
</p>
<div class="mt-4 h-[80px]">
<VisXYContainer
height="80px" :data="data" :style="{
'--theme-primary': `hsl(${
theme?.cssVars[isDark ? 'dark' : 'light'].primary
})`,
}"
>
<VisStackedBar
:x="lineX"
:y="(d: Data) => d.subscription"
:bar-padding="0.1"
:rounded-corners="0" color="var(--theme-primary)"
/>
</VisXYContainer>
</div>
</CardContent>
</Card>
</div>
</template>

View File

@ -0,0 +1,104 @@
<script setup lang="ts">
import { ref } from 'vue'
import { VisStackedBar, VisXYContainer } from '@unovis/vue'
import Minus from '~icons/radix-icons/minus'
import Plus from '~icons/radix-icons/plus'
import { Button } from '@/lib/registry/new-york/ui/button'
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from '@/lib/registry/new-york/ui/card'
import { useConfigStore } from '@/stores/config'
const { themePrimary } = useConfigStore()
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>
<Card>
<CardHeader class="pb-4">
<CardTitle class="text-base">
Move Goal
</CardTitle>
<CardDescription>Set your daily activity goal.</CardDescription>
</CardHeader>
<CardContent class="pb-2">
<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-5xl 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 h-[60px]">
<VisXYContainer
:data="data"
height="60px"
:style="{
'opacity': 0.2,
'--theme-primary': themePrimary,
}"
>
<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>
</CardContent>
<CardFooter>
<Button class="w-full">
Set Goal
</Button>
</CardFooter>
</Card>
</template>

View File

@ -0,0 +1,262 @@
<script setup lang="ts">
import type {
ColumnDef,
ColumnFiltersState,
SortingState,
VisibilityState,
} from '@tanstack/vue-table'
import {
FlexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useVueTable,
} from '@tanstack/vue-table'
import { h, ref } from 'vue'
import DropdownAction from '../DataTableDemoColumn.vue'
import RadixIconsCaretSort from '~icons/radix-icons/caret-sort'
import RadixIconsChevronDown from '~icons/radix-icons/chevron-down'
import { Button } from '@/lib/registry/new-york/ui/button'
import { Checkbox } from '@/lib/registry/new-york/ui/checkbox'
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuTrigger,
} from '@/lib/registry/new-york/ui/dropdown-menu'
import { Input } from '@/lib/registry/new-york/ui/input'
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/lib/registry/new-york/ui/table'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/lib/registry/new-york/ui/card'
import { valueUpdater } from '@/lib/utils'
export interface Payment {
id: string
amount: number
status: 'pending' | 'processing' | 'success' | 'failed'
email: string
}
const data: Payment[] = [
{
id: 'm5gr84i9',
amount: 316,
status: 'success',
email: 'ken99@yahoo.com',
},
{
id: '3u1reuv4',
amount: 242,
status: 'success',
email: 'Abe45@gmail.com',
},
{
id: 'derv1ws0',
amount: 837,
status: 'processing',
email: 'Monserrat44@gmail.com',
},
{
id: '5kma53ae',
amount: 874,
status: 'success',
email: 'Silas22@gmail.com',
},
{
id: 'bhqecj4p',
amount: 721,
status: 'failed',
email: 'carmella@hotmail.com',
},
]
const columns: ColumnDef<Payment>[] = [
{
id: 'select',
header: ({ table }) => h(Checkbox, {
'checked': table.getIsAllPageRowsSelected(),
'onUpdate:checked': value => table.toggleAllPageRowsSelected(!!value),
'ariaLabel': 'Select all',
}),
cell: ({ row }) => h(Checkbox, {
'checked': row.getIsSelected(),
'onUpdate:checked': value => row.toggleSelected(!!value),
'ariaLabel': 'Select row',
}),
enableSorting: false,
enableHiding: false,
},
{
accessorKey: 'status',
header: 'Status',
cell: ({ row }) => h('div', { class: 'capitalize' }, row.getValue('status')),
},
{
accessorKey: 'email',
header: ({ column }) => {
return h(Button, {
variant: 'ghost',
onClick: () => column.toggleSorting(column.getIsSorted() === 'asc'),
}, ['Email', h(RadixIconsCaretSort, { class: 'ml-2 h-4 w-4' })])
},
cell: ({ row }) => h('div', { class: 'lowercase' }, row.getValue('email')),
},
{
accessorKey: 'amount',
header: () => h('div', { class: 'text-right' }, 'Amount'),
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
// Format the amount as a dollar amount
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
}).format(amount)
return h('div', { class: 'text-right font-medium' }, formatted)
},
},
{
id: 'actions',
enableHiding: false,
cell: ({ row }) => {
const payment = row.original
return h(DropdownAction, {
payment,
})
},
},
]
const sorting = ref<SortingState>([])
const columnFilters = ref<ColumnFiltersState>([])
const columnVisibility = ref<VisibilityState>({})
const rowSelection = ref({})
const table = useVueTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting),
onColumnFiltersChange: updaterOrValue => valueUpdater(updaterOrValue, columnFilters),
onColumnVisibilityChange: updaterOrValue => valueUpdater(updaterOrValue, columnVisibility),
onRowSelectionChange: updaterOrValue => valueUpdater(updaterOrValue, rowSelection),
state: {
get sorting() { return sorting.value },
get columnFilters() { return columnFilters.value },
get columnVisibility() { return columnVisibility.value },
get rowSelection() { return rowSelection.value },
},
})
</script>
<template>
<Card>
<CardHeader>
<CardTitle>Payments</CardTitle>
<CardDescription>Manage your payments.</CardDescription>
</CardHeader>
<CardContent>
<div class="w-full">
<div className="mb-4 flex items-center gap-4">
<Input
class="max-w-sm"
placeholder="Filter emails..."
:model-value="table.getColumn('email')?.getFilterValue() as string"
@update:model-value=" table.getColumn('email')?.setFilterValue($event)"
/>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<Button variant="outline" class="ml-auto">
Columns <RadixIconsChevronDown class="ml-2 h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuCheckboxItem
v-for="column in table.getAllColumns().filter((column) => column.getCanHide())"
:key="column.id"
class="capitalize"
:checked="column.getIsVisible()"
@update:checked="(value) => {
column.toggleVisibility(!!value)
}"
>
{{ column.id }}
</DropdownMenuCheckboxItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
<div class="rounded-md border">
<Table>
<TableHeader>
<TableRow v-for="headerGroup in table.getHeaderGroups()" :key="headerGroup.id">
<TableHead v-for="header in headerGroup.headers" :key="header.id" class="[&:has([role=checkbox])]:pl-3">
<FlexRender v-if="!header.isPlaceholder" :render="header.column.columnDef.header" :props="header.getContext()" />
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<template v-if="table.getRowModel().rows?.length">
<TableRow
v-for="row in table.getRowModel().rows"
:key="row.id"
:data-state="row.getIsSelected() && 'selected'"
>
<TableCell v-for="cell in row.getVisibleCells()" :key="cell.id" class="[&:has([role=checkbox])]:pl-3">
<FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
</TableCell>
</TableRow>
</template>
<TableRow v-else>
<TableCell
:col-span="columns.length"
class="h-24 text-center"
>
No results.
</TableCell>
</TableRow>
</TableBody>
</Table>
</div>
<div class="flex items-center justify-end space-x-2 py-4">
<div class="flex-1 text-sm text-muted-foreground">
{{ table.getFilteredSelectedRowModel().rows.length }} of
{{ table.getFilteredRowModel().rows.length }} row(s) selected.
</div>
<div class="space-x-2">
<Button
variant="outline"
size="sm"
:disabled="!table.getCanPreviousPage()"
@click="table.previousPage()"
>
Previous
</Button>
<Button
variant="outline"
size="sm"
:disabled="!table.getCanNextPage()"
@click="table.nextPage()"
>
Next
</Button>
</div>
</div>
</div>
</CardContent>
</Card>
</template>

View File

@ -0,0 +1,91 @@
<script setup lang="ts">
import { VisCrosshair, VisLine, VisScatter, VisTooltip, VisXYContainer } from '@unovis/vue'
import { Line } from '@unovis/ts'
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/lib/registry/new-york/ui/card'
import { useConfigStore } from '@/stores/config'
const { themePrimary } = useConfigStore()
type Data = typeof data[number]
const data = [
{ average: 400, today: 240 },
{ average: 300, today: 139 },
{ average: 200, today: 980 },
{ average: 278, today: 390 },
{ average: 189, today: 480 },
{ average: 239, today: 380 },
{ average: 349, today: 430 },
]
const x = (d: Data, i: number) => i
function template(d: Data) {
return `
<div class="rounded-lg border bg-background p-2 shadow-sm">
<div class="grid grid-cols-2 gap-2">
<div class="flex flex-col">
<span class="text-[0.70rem] uppercase text-muted-foreground">
Average
</span>
<span class="font-bold text-muted-foreground">
${d.average}
</span>
</div>
<div class="flex flex-col">
<span class="text-[0.70rem] uppercase text-muted-foreground">
Today
</span>
<span class="font-bold text-white">
${d.today}
</span>
</div>
</div>
</div>`
}
function computeLineOpacity(val: any, index: number) {
if (index === 0)
return '0.5'
}
</script>
<template>
<Card>
<CardHeader>
<CardTitle>Exercise Minutes</CardTitle>
<CardDescription>
Your excercise minutes are ahead of where you normally are.
</CardDescription>
</CardHeader>
<CardContent class="pb-4">
<div class="h-[200px]">
<VisXYContainer
height="200px"
:data="data"
:margin="{
top: 5,
right: 10,
left: 10,
bottom: 0,
}"
:style="{
'--theme-primary': themePrimary,
'--vis-tooltip-padding': '0px',
'--vis-tooltip-background-color': 'transparent',
'--vis-tooltip-border-color': 'transparent',
}"
>
<VisTooltip />
<VisLine :x="x" :y="[(d: Data) => d.average, (d: Data) => d.today]" :stroke-width="2" color="var(--theme-primary)" :attributes="{ [Line.selectors.linePath]: { opacity: computeLineOpacity } }" />
<VisScatter :x="x" :y="[(d: Data) => d.average, (d: Data) => d.today]" :size="6" :stroke-width="2" stroke-color="var(--theme-primary)" color="white" />
<VisCrosshair :template="template" />
</VisXYContainer>
</div>
</CardContent>
</Card>
</template>

View File

@ -245,7 +245,6 @@ const table = useVueTable({
:disabled="!table.getCanNextPage()" :disabled="!table.getCanNextPage()"
@click="table.nextPage()" @click="table.nextPage()"
> >
>
Next Next
</Button> </Button>
</div> </div>

View File

@ -24,7 +24,7 @@ const emitsAsProps = useEmitAsProps(emits)
v-bind="{ ...props, ...emitsAsProps, ...$attrs }" v-bind="{ ...props, ...emitsAsProps, ...$attrs }"
:class=" :class="
cn( cn(
'relative z-50 min-w-[10rem] overflow-hidden rounded-md bg-background border border-border text-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', 'relative z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
position === 'popper' position === 'popper'
&& 'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1', && 'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
props.class, props.class,

View File

@ -17,7 +17,7 @@ const props = withDefaults(
v-bind="props" v-bind="props"
:class="[ :class="[
cn( cn(
'flex h-9 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50', 'flex h-9 w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
props.class, props.class,
), ),
props.invalid props.invalid

View File

@ -1,6 +1,7 @@
import { computed } from 'vue' import { computed } from 'vue'
import { useSessionStorage } from '@vueuse/core' import { useSessionStorage } from '@vueuse/core'
import type { Theme } from './../lib/registry/themes' import { useData } from 'vitepress'
import { type Theme, themes } from './../lib/registry/themes'
import { styles } from '@/lib/registry/styles' import { styles } from '@/lib/registry/styles'
interface Config { interface Config {
@ -12,6 +13,7 @@ interface Config {
export const RADII = [0, 0.25, 0.5, 0.75, 1] export const RADII = [0, 0.25, 0.5, 0.75, 1]
export function useConfigStore() { export function useConfigStore() {
const { isDark } = useData()
const config = useSessionStorage<Config>('config', { const config = useSessionStorage<Config>('config', {
theme: 'zinc', theme: 'zinc',
radius: 0.5, radius: 0.5,
@ -32,5 +34,12 @@ export function useConfigStore() {
config.value.radius = newRadius config.value.radius = newRadius
} }
return { config, theme, setTheme, radius, setRadius, themeClass, style } const themePrimary = computed(() => {
const t = themes.find(t => t.name === theme.value)
return `hsl(${
t?.cssVars[isDark ? 'dark' : 'light'].primary
})`
})
return { config, theme, setTheme, radius, setRadius, themeClass, style, themePrimary }
} }

View File

@ -59,6 +59,9 @@ importers:
'@unovis/ts': '@unovis/ts':
specifier: ^1.2.1 specifier: ^1.2.1
version: 1.2.1 version: 1.2.1
'@unovis/vue':
specifier: 1.3.0-alpha.3
version: 1.3.0-alpha.3(@unovis/ts@1.2.1)(vue@3.3.4)
'@vueuse/core': '@vueuse/core':
specifier: ^10.4.1 specifier: ^10.4.1
version: 10.4.1(vue@3.3.4) version: 10.4.1(vue@3.3.4)
@ -2519,6 +2522,16 @@ packages:
tslib: 2.6.2 tslib: 2.6.2
dev: false dev: false
/@unovis/vue@1.3.0-alpha.3(@unovis/ts@1.2.1)(vue@3.3.4):
resolution: {integrity: sha512-plJiEpeoDfVzeSfaC0GeHeJ3CDD0uJxzojFQWyGABDpVmOC50D+xkoIi7f3Zb5F8jOwt0KyThUKVzUUsE+cCCg==}
peerDependencies:
'@unovis/ts': 1.3.0-alpha.3
vue: ^3
dependencies:
'@unovis/ts': 1.2.1
vue: 3.3.4
dev: false
/@vitejs/plugin-vue-jsx@3.0.2(vite@4.4.9)(vue@3.3.4): /@vitejs/plugin-vue-jsx@3.0.2(vite@4.4.9)(vue@3.3.4):
resolution: {integrity: sha512-obF26P2Z4Ogy3cPp07B4VaW6rpiu0ue4OT2Y15UxT5BZZ76haUY9guOsZV3uWh/I6xc+VeiW+ZVabRE82FyzWw==} resolution: {integrity: sha512-obF26P2Z4Ogy3cPp07B4VaW6rpiu0ue4OT2Y15UxT5BZZ76haUY9guOsZV3uWh/I6xc+VeiW+ZVabRE82FyzWw==}
engines: {node: ^14.18.0 || >=16.0.0} engines: {node: ^14.18.0 || >=16.0.0}