chore: updates charts using unovis/vue
This commit is contained in:
parent
3e9d8063cc
commit
a725977184
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
209
apps/www/src/lib/registry/default/example/CardChat.vue
Normal file
209
apps/www/src/lib/registry/default/example/CardChat.vue
Normal 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>
|
||||||
101
apps/www/src/lib/registry/default/example/CardStats.vue
Normal file
101
apps/www/src/lib/registry/default/example/CardStats.vue
Normal 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>
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
209
apps/www/src/lib/registry/new-york/example/CardChat.vue
Normal file
209
apps/www/src/lib/registry/new-york/example/CardChat.vue
Normal 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>
|
||||||
101
apps/www/src/lib/registry/new-york/example/CardStats.vue
Normal file
101
apps/www/src/lib/registry/new-york/example/CardStats.vue
Normal 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>
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { ChevronDown, Minus, Plus, Send } from 'lucide-vue-next'
|
||||||
|
import { VisStackedBar, VisXYContainer } from '@unovis/vue'
|
||||||
|
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>
|
||||||
262
apps/www/src/lib/registry/new-york/example/Cards/DataTable.vue
Normal file
262
apps/www/src/lib/registry/new-york/example/Cards/DataTable.vue
Normal 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>
|
||||||
91
apps/www/src/lib/registry/new-york/example/Cards/Metric.vue
Normal file
91
apps/www/src/lib/registry/new-york/example/Cards/Metric.vue
Normal 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>
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user