first init

This commit is contained in:
ISMAIL MASSERAN
2026-06-08 11:37:14 +08:00
commit 94ecbe5887
1058 changed files with 87732 additions and 0 deletions
@@ -0,0 +1,122 @@
<script setup lang="ts">
import { useRouter } from 'vue-router'
import { Box } from '@/components/ui/box'
import { Lucide } from '@/components/ui/lucide'
import {
ComboboxRoot,
ComboboxControl,
ComboboxTrigger,
ComboboxContent,
ComboboxItemGroup,
ComboboxItem,
ComboboxItemText,
} from '@/components/ui/combobox'
import { logout } from '@/modules/auth'
import { useImpersonate } from '@/modules/user/composables/useImpersonate'
import { useAuthStore } from '@/stores/auth'
import { useRoleSwitcher } from '@/composables/useRoleSwitcher'
interface Props {
class?: string
boxClass?: string
}
const props = defineProps<Props>()
const router = useRouter()
const authStore = useAuthStore()
const {
canSwitchRole,
roleCollection,
roleComboValue,
switchingRole,
onRoleValueChange,
onRoleInputValueChange,
} = useRoleSwitcher()
const { impersonating, loading: impersonateLoading, stopImpersonation } = useImpersonate()
const goToProfile = (event: MouseEvent) => {
event.preventDefault()
router.push({ name: 'profile-overview-2' })
}
const handleLogout = async (event: MouseEvent) => {
event.preventDefault()
if (impersonating.value || impersonateLoading.value) {
return
}
try {
await logout()
} catch {
// Still redirect to login if the session is already invalid.
}
authStore.clearSession()
await router.push({ name: 'login' })
}
</script>
<template>
<div v-bind="$attrs" :class="[
'invisible opacity-0 scale-95 transition-all duration-200 delay-0 group-hover/profile:visible group-hover/profile:opacity-100 group-hover/profile:scale-100 group-hover/profile:delay-200',
props.class,
]">
<Box
:class="`text-foreground before:shadow-foreground/5 flex w-64 flex-col gap-2.5 px-6 py-5 before:rounded-2xl before:shadow-xl before:backdrop-blur after:rounded-2xl ${props.boxClass ?? ''}`">
<div class="flex flex-col gap-0.5">
<div class="font-medium">
Selamat Datang, {{ authStore.userName }}
</div>
<div v-if="authStore.userActiveRole" class="text-xs opacity-70">
{{ authStore.userActiveRole }}
</div>
</div>
<template v-if="canSwitchRole">
<div class="bg-foreground/5 h-px"></div>
<div class="flex flex-col gap-1.5">
<div class="text-xs font-medium opacity-70">Tukar Peranan</div>
<ComboboxRoot class="w-full" :collection="roleCollection" :value="roleComboValue"
:disabled="switchingRole || authStore.loading" @value-change="onRoleValueChange"
@input-value-change="onRoleInputValueChange">
<ComboboxControl>
<ComboboxTrigger class="w-full" />
</ComboboxControl>
<ComboboxContent class="w-(--reference-width)">
<ComboboxItemGroup>
<ComboboxItem v-for="item in roleCollection.items" :key="item.value" :item="item">
<ComboboxItemText>{{ item.label }}</ComboboxItemText>
</ComboboxItem>
</ComboboxItemGroup>
</ComboboxContent>
</ComboboxRoot>
</div>
</template>
<div class="bg-foreground/5 h-px"></div>
<div class="flex flex-col gap-0.5">
<a class="hover:bg-foreground/5 -mx-3 flex gap-2.5 rounded-lg px-4 py-1.5" href="" @click="goToProfile">
<Lucide icon="Users" /> Profil
</a>
</div>
<div class="bg-foreground/5 h-px"></div>
<div class="flex flex-col gap-0.5">
<a v-if="impersonating" href="" class="hover:bg-foreground/5 -mx-3 flex gap-2.5 rounded-lg px-4 py-1.5"
:class="{ 'pointer-events-none opacity-50': impersonateLoading }" @click.prevent="stopImpersonation">
<Lucide icon="VenetianMask" /> Keluar Penyamaran
</a>
<a class="hover:bg-foreground/5 -mx-3 flex gap-2.5 rounded-lg px-4 py-1.5"
:class="(impersonating || impersonateLoading) && 'pointer-events-none cursor-not-allowed opacity-40'" href=""
:aria-disabled="impersonating || impersonateLoading" @click="handleLogout">
<Lucide icon="Power" /> Log Keluar
</a>
</div>
</Box>
</div>
</template>
@@ -0,0 +1,66 @@
<script setup lang="ts">
import { Lucide } from '@/components/ui/lucide'
import { useAuthStore } from '@/stores/auth'
interface Props {
class?: string
innerClass?: any
avatarClass?: string
textClass?: string
}
const props = defineProps<Props>()
const authStore = useAuthStore()
</script>
<template>
<div :class="['side-menu__account group/profile transition-[width]', props.class]">
<div :class="['flex cursor-pointer items-center transition', props.innerClass]">
<div :class="[
'relative flex-none overflow-hidden rounded-full border-4',
props.avatarClass ?? 'h-10 w-10 border-background/20 dark:border-foreground/20',
]">
<img v-if="authStore.userImageUrl" class="absolute top-0 h-full w-full object-cover"
:src="authStore.userImageUrl" :alt="authStore.userName" />
<div v-else
class="bg-primary text-primary-foreground absolute inset-0 flex items-center justify-center text-sm font-medium">
{{ authStore.userName }}
</div>
</div>
<div class="ms-3 flex w-full items-center gap-3 overflow-hidden transition-opacity
group-[.side-menu--collapsed]:group-hover:opacity-100
xl:group-[.side-menu--collapsed]:opacity-0">
<div :class="['flex-1 min-w-0', props.textClass]">
<!-- Name + Active Role -->
<div class="flex items-center gap-2">
<div class="truncate font-semibold text-sm">
{{ authStore.userName }}
</div>
<span class="shrink-0 rounded-full bg-green-500 px-2 py-0.5 text-[10px] font-medium text-white">
{{ authStore.userActiveRole }}
</span>
</div>
<!-- Position -->
<div class="mt-0.5 truncate text-xs text-slate-500">
<span class="font-medium text-slate-600">Jawatan:</span>
{{ authStore.userPosition }}
</div>
<!-- Roles Summary -->
<div class="mt-2 flex items-center gap-1 text-[11px] text-slate-500">
<Lucide icon="Shield" class="h-3 w-3" />
<span class="truncate">
{{authStore.roles.map(role => role.name).join(' • ')}}
</span>
</div>
</div>
<Lucide icon="ChevronRight"
class="h-4 w-4 flex-none text-slate-400 transition-transform group-hover:translate-x-1" />
</div>
</div>
<slot />
</div>
</template>
@@ -0,0 +1,2 @@
export { default as AccountDropdown } from './AccountDropdown.vue'
export { default as AccountTrigger } from './AccountTrigger.vue'
+99
View File
@@ -0,0 +1,99 @@
<script lang="ts" setup>
import { Chart, getColor } from '@/components/ui/chart'
import type { Chart as ChartInstance } from 'chart.js'
// Fake visitor data
const chartData = new Array(40).fill(0).map((data, key) => {
if (key % 3 == 0 || key % 5 == 0) {
return Math.ceil(Math.random() * (0 - 20) + 20)
} else {
return Math.ceil(Math.random() * (0 - 7) + 7)
}
})
// Fake visitor bar color
const chartColor = () =>
chartData.map((data) => {
if (data >= 8 && data <= 14) {
return getColor('--color-primary', 0.5)
} else if (data >= 15) {
return getColor('--color-primary', 0.6)
}
return getColor('--color-primary', 0.4)
})
const getRef = (chart: ChartInstance<'bar'>) => {
setInterval(() => {
const datasets = chart.data.datasets[0]
if (datasets) {
// Swap visitor data
let newData = datasets.data[0]
datasets.data.shift()
newData !== undefined && datasets.data.push(newData)
// Swap visitor bar color
if (datasets.backgroundColor && Array.isArray(datasets.backgroundColor)) {
let newColor = datasets.backgroundColor[0]
datasets.backgroundColor.shift()
datasets.backgroundColor.push(newColor)
}
}
chart.update()
}, 1000)
}
</script>
<template>
<div>
<Chart
:config="{
type: 'bar',
data: {
labels: chartData,
datasets: [
{
label: 'Html Template',
barThickness: 6,
data: chartData,
backgroundColor: chartColor(),
borderColor: () => getColor('--color-primary', 0.1),
borderWidth: 0.8,
},
],
},
options: {
maintainAspectRatio: false,
plugins: {
legend: {
display: false,
},
},
scales: {
x: {
ticks: {
display: false,
},
grid: {
display: false,
},
},
y: {
ticks: {
display: false,
},
grid: {
display: false,
},
border: {
display: false,
},
},
},
},
}"
:get-ref="getRef"
/>
</div>
</template>
+85
View File
@@ -0,0 +1,85 @@
<script lang="ts" setup>
import { Chart, getColor } from '@/components/ui/chart'
</script>
<template>
<div>
<Chart
:config="{
type: 'bar',
data: {
labels: [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
],
datasets: [
{
label: 'Html Template',
barThickness: 8,
maxBarThickness: 6,
data: [60, 150, 30, 200, 180, 50, 180, 120, 230, 180, 250, 270],
backgroundColor: () => getColor('--color-primary', 0.3),
borderColor: () => getColor('--color-primary', 0.6),
borderWidth: 0.5,
},
{
label: 'VueJs Template',
barThickness: 8,
maxBarThickness: 6,
data: [50, 135, 40, 180, 190, 60, 150, 90, 250, 170, 240, 250],
backgroundColor: () => getColor('--color-foreground', 0.2),
borderColor: () => getColor('--color-foreground', 0.3),
borderWidth: 0.5,
},
],
},
options: {
maintainAspectRatio: false,
plugins: {
legend: {
display: false,
},
},
scales: {
x: {
ticks: {
font: {
size: 11,
},
color: () => getColor('--color-foreground', 0.5),
},
grid: {
display: false,
},
border: {
display: false,
},
},
y: {
ticks: {
display: false,
},
grid: {
color: () => getColor('--color-foreground', 0.2),
},
border: {
dash: [2, 2],
display: false,
},
},
},
},
}"
/>
</div>
</template>
@@ -0,0 +1,43 @@
<script lang="ts" setup>
import { Chart, getColor } from '@/components/ui/chart'
</script>
<template>
<div>
<Chart
:config="{
type: 'doughnut',
data: {
labels: ['Html', 'Vuejs', 'Laravel'],
datasets: [
{
data: [15, 10, 65],
backgroundColor: [
getColor('--color-pending', 0.4),
getColor('--color-primary', 0.4),
getColor('--color-warning', 0.4),
],
borderWidth: 1,
borderColor: [
getColor('--color-pending', 0.9),
getColor('--color-primary', 0.9),
getColor('--color-warning', 0.9),
],
offset: 20,
borderRadius: 5,
},
],
},
options: {
cutout: '85%',
maintainAspectRatio: false,
plugins: {
legend: {
display: false,
},
},
},
}"
/>
</div>
</template>
@@ -0,0 +1,43 @@
<script lang="ts" setup>
import { Chart, getColor } from '@/components/ui/chart'
</script>
<template>
<div>
<Chart
:config="{
type: 'doughnut',
data: {
labels: ['Yellow', 'Dark'],
datasets: [
{
data: [15, 10, 65],
backgroundColor: [
getColor('--color-pending', 0.4),
getColor('--color-warning', 0.4),
getColor('--color-primary', 0.4),
],
borderWidth: 1,
borderColor: [
getColor('--color-pending', 0.9),
getColor('--color-warning', 0.9),
getColor('--color-primary', 0.9),
],
offset: 20,
borderRadius: 5,
},
],
},
options: {
maintainAspectRatio: false,
plugins: {
legend: {
display: false,
},
},
cutout: '83%',
},
}"
/>
</div>
</template>
+92
View File
@@ -0,0 +1,92 @@
<script lang="ts" setup>
import { Chart, getColor } from '@/components/ui/chart'
</script>
<template>
<div>
<Chart
:config="{
type: 'line',
data: {
labels: [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
],
datasets: [
{
label: '# of Votes',
data: [0, 200, 250, 200, 700, 550, 650, 1050, 950, 1100, 900, 1200],
borderWidth: 1.5,
borderColor: () => getColor('--color-primary', 0.5),
backgroundColor: 'transparent',
pointBorderColor: 'transparent',
tension: 0.3,
},
{
label: '# of Votes',
data: [0, 300, 400, 560, 320, 600, 720, 850, 690, 805, 1200, 1010],
borderWidth: 1.5,
borderDash: [1, 4],
borderColor: () => getColor('--color-foreground'),
backgroundColor: 'transparent',
pointBorderColor: 'transparent',
tension: 0.3,
},
],
},
options: {
maintainAspectRatio: false,
plugins: {
legend: {
display: false,
},
},
scales: {
x: {
ticks: {
font: {
size: 12,
},
color: () => getColor('--color-foreground', 0.5),
},
grid: {
display: false,
},
border: {
display: false,
},
},
y: {
ticks: {
font: {
size: 12,
},
color: () => getColor('--color-foreground', 0.5),
callback: function (value, index, values) {
return '$' + value
},
},
grid: {
color: () => getColor('--color-foreground', 0.2),
},
border: {
dash: [2, 2],
display: false,
},
},
},
},
}"
/>
</div>
</template>
+72
View File
@@ -0,0 +1,72 @@
<script lang="ts" setup>
import { Chart, getColor } from '@/components/ui/chart'
</script>
<template>
<div>
<Chart
:config="{
type: 'line',
data: {
labels: [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
],
datasets: [
{
label: '# of Votes',
data: [0, 200, 250, 200, 500, 450, 850, 1050, 950, 1100, 900, 1200],
borderWidth: 1,
borderColor: () => getColor('--color-primary', 0.8),
backgroundColor: 'transparent',
pointBorderColor: 'transparent',
tension: 0.4,
},
],
},
options: {
maintainAspectRatio: false,
plugins: {
legend: {
display: false,
},
},
scales: {
x: {
ticks: {
display: false,
},
grid: {
display: false,
},
border: {
display: false,
},
},
y: {
ticks: {
display: false,
},
grid: {
display: false,
},
border: {
display: false,
},
},
},
},
}"
/>
</div>
</template>
+82
View File
@@ -0,0 +1,82 @@
<script lang="ts" setup>
import { Chart, getColor } from '@/components/ui/chart'
</script>
<template>
<div>
<Chart
:config="{
type: 'line',
data: {
labels: [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
],
datasets: [
{
label: '# of Votes',
data: [0, 200, 250, 200, 700, 550, 650, 1050, 950, 1100, 900, 1200],
borderWidth: 1,
borderColor: () => getColor('--color-primary', 0.8),
backgroundColor: 'transparent',
pointBorderColor: 'transparent',
tension: 0.4,
},
{
label: '# of Votes',
data: [0, 300, 400, 560, 320, 600, 720, 850, 690, 805, 1200, 1010],
borderDash: [2, 2],
borderWidth: 1,
borderColor: () => getColor('--color-foreground', 0.8),
backgroundColor: 'transparent',
pointBorderColor: 'transparent',
tension: 0.4,
},
],
},
options: {
maintainAspectRatio: false,
plugins: {
legend: {
display: false,
},
},
scales: {
x: {
ticks: {
display: false,
},
grid: {
display: false,
},
border: {
display: false,
},
},
y: {
ticks: {
display: false,
},
grid: {
display: false,
},
border: {
display: false,
},
},
},
},
}"
/>
</div>
</template>
+42
View File
@@ -0,0 +1,42 @@
<script lang="ts" setup>
import { Chart, getColor } from '@/components/ui/chart'
</script>
<template>
<div>
<Chart
:config="{
type: 'pie',
data: {
labels: ['31 - 50 Years old', '>= 50 Years old', '17 - 30 Years old'],
datasets: [
{
data: [15, 10, 65],
backgroundColor: [
getColor('--color-pending', 0.4),
getColor('--color-warning', 0.4),
getColor('--color-primary', 0.4),
],
borderWidth: 1,
borderColor: [
getColor('--color-pending', 0.9),
getColor('--color-warning', 0.9),
getColor('--color-primary', 0.9),
],
offset: 20,
borderRadius: 5,
},
],
},
options: {
maintainAspectRatio: false,
plugins: {
legend: {
display: false,
},
},
},
}"
/>
</div>
</template>
+8
View File
@@ -0,0 +1,8 @@
export { default as Line1 } from './Line1.vue' //report-line-chart
export { default as Line2 } from './Line2.vue' //simple-line-chart-1
export { default as Line3 } from './Line3.vue' //simple-line-chart-3, simple-line-chart-4
export { default as Pie1 } from './Pie1.vue' //report-pie-chart
export { default as Donut1 } from './Donut1.vue' //report-donut-chart
export { default as Donut2 } from './Donut2.vue' //report-donut-chart-1
export { default as Bar1 } from './Bar1.vue' //report-bar-chart
export { default as Bar2 } from './Bar2.vue' //report-bar-chart-1
@@ -0,0 +1,132 @@
<script lang="ts" setup>
import _ from 'lodash'
import { ref } from 'vue'
import fakers from '@/utils/faker'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { Input } from '@/components/ui/input'
import { Box } from '@/components/ui/box'
import {
MenuRoot,
MenuTrigger,
MenuPositioner,
MenuContent,
MenuCheckboxItem,
} from '@/components/ui/menu'
import {
Table,
TableHeader,
TableBody,
TableHead,
TableRow,
TableCell,
} from '@/components/ui/table'
import { NativeSelect, NativeSelectOption } from '@/components/ui/native-select'
import { AvatarRoot, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import {
CarouselRoot,
CarouselPrevTrigger,
CarouselNextTrigger,
CarouselItemGroup,
CarouselItem,
} from '@/components/ui/carousel'
import {
DatePickerRoot,
DatePickerControl,
DatePickerInput,
DatePickerTrigger,
DatePickerPositioner,
DatePickerContent,
DatePickerYearSelect,
DatePickerMonthSelect,
DatePickerView,
DatePickerViewControl,
DatePickerPrevTrigger,
DatePickerNextTrigger,
DatePickerRangeText,
DatePickerTable,
DatePickerTableHead,
DatePickerTableRow,
DatePickerTableHeader,
DatePickerTableBody,
DatePickerTableCell,
DatePickerTableCellTrigger,
DatePickerContext,
} from '@/components/ui/datepicker'
import {
PaginationContext,
PaginationRoot,
PaginationItem,
PaginationPrevTrigger,
PaginationNextTrigger,
PaginationEllipsis,
} from '@/components/ui/pagination'
import { Map } from '@/components/ui/map'
import { Lucide } from '@/components/ui/lucide'
import { Line1, Line2, Pie1, Donut1, Donut2 } from '@/components/chart-presets'
const pc = ref(false)
const electronic = ref(false)
const smartphone = ref(false)
const photography = ref(false)
const sport = ref(false)
</script>
<template>
<!-- BEGIN: Daily Notes -->
<CarouselRoot :default-page="0" :slide-count="4">
<div class="flex h-10 items-center">
<h2 class="me-auto truncate text-lg font-medium">Daily Notes</h2>
<CarouselPrevTrigger as-child>
<Box as-child>
<Button variant="ghost" class="box me-2 px-2">
<Lucide icon="ChevronLeft" />
</Button>
</Box>
</CarouselPrevTrigger>
<CarouselNextTrigger as-child>
<Box as-child>
<Button variant="ghost" class="box px-2">
<Lucide icon="ChevronRight" />
</Button>
</Box>
</CarouselNextTrigger>
</div>
<div class="mt-5">
<Box>
<CarouselItemGroup>
<CarouselItem
as-child
v-for="(faker, fakerKey) in _.take(fakers, 4)"
:key="fakerKey"
:index="fakerKey"
>
<div>
<div class="truncate text-base font-medium">
{{ faker['news'][0]!['title'] }}
</div>
<div class="mt-2 text-xs opacity-50">Posted 20 Hours ago</div>
<div class="mt-2 text-justify opacity-70 line-clamp-3">
{{ faker['news'][0]!['shortContent'] }}
</div>
<div class="mt-5 flex font-medium">
<Button variant="ghost" type="button" size="sm" class="border border-foreground/20">
View Notes
</Button>
<Button
variant="ghost"
class="ms-auto border border-foreground/20"
type="button"
size="sm"
>
Dismiss
</Button>
</div>
</div>
</CarouselItem>
</CarouselItemGroup>
</Box>
</div>
</CarouselRoot>
<!-- END: Daily Notes -->
</template>
+1
View File
@@ -0,0 +1 @@
export { default as DailyNotes } from './DailyNotes.vue'
+9
View File
@@ -0,0 +1,9 @@
<template>
<a
class="pr-3 pl-3.5 py-1 border border-foreground/30 bg-background/20 flex items-center gap-2 text-xs"
target="_blank"
v-bind="$attrs"
>
<slot />
</a>
</template>
+101
View File
@@ -0,0 +1,101 @@
<script lang="ts" setup>
import { twMerge } from "tailwind-merge";
import {
SquareChevronRight,
Clipboard,
Check,
} from "@lucide/vue";
import { ref, useSlots, onUnmounted } from "vue";
const { class: className } = defineProps<{
class?: string;
}>();
const slots = useSlots();
const packageManager = ref("yarn");
const copied = ref(false);
const commandRef = ref<HTMLDivElement | null>(null);
let copyTimeout: ReturnType<typeof setTimeout> | null = null;
onUnmounted(() => {
if (copyTimeout) clearTimeout(copyTimeout);
});
function handleCopy() {
const codeElement = commandRef.value;
if (!codeElement) return;
const text = codeElement.textContent || "";
navigator.clipboard.writeText(text).then(() => {
copied.value = true;
if (copyTimeout) clearTimeout(copyTimeout);
copyTimeout = setTimeout(() => {
copied.value = false;
}, 3000);
});
}
function getSlotText() {
const slotContent = slots.default?.();
if (!slotContent || slotContent.length === 0) return "";
return slotContent
.map((vnode) => (typeof vnode.children === "string" ? vnode.children : ""))
.join("");
}
function getDisplayText() {
const text = getSlotText();
if (packageManager.value === "npm") {
return text.replace("add", "install");
}
return text;
}
</script>
<template>
<div :class="twMerge([
'mt-8 mb-10 border border-foreground/20 bg-background/50',
className,
])
">
<div class="flex items-center py-2 px-5 gap-5">
<SquareChevronRight class="stroke-1 size-4 fill-foreground/10" />
<div class="flex items-center w-full gap-2">
<div @click="packageManager = 'pnpm'" :class="twMerge([
'cursor-pointer px-3.5 py-1 [&.active]:border [&.active]:border-foreground/20 [&.active]:bg-foreground/10',
packageManager == 'pnpm' && 'active',
])
">
pnpm
</div>
<div @click="packageManager = 'npm'" :class="twMerge([
'cursor-pointer px-3.5 py-1 [&.active]:border [&.active]:border-foreground/20 [&.active]:bg-foreground/10',
packageManager == 'npm' && 'active',
])
">
npm
</div>
<div @click="packageManager = 'yarn'" :class="twMerge([
'cursor-pointer px-3.5 py-1 [&.active]:border [&.active]:border-foreground/20 [&.active]:bg-foreground/10',
packageManager == 'yarn' && 'active',
])
">
yarn
</div>
<div @click="packageManager = 'bun'" :class="twMerge([
'cursor-pointer px-3.5 py-1 [&.active]:border [&.active]:border-foreground/20 [&.active]:bg-foreground/10',
packageManager == 'bun' && 'active',
])
">
bun
</div>
<Check v-if="copied" class="ms-auto cursor-pointer size-4 stroke-1 fill-foreground/10" />
<Clipboard v-else @click="handleCopy" class="ms-auto cursor-pointer size-4 stroke-1 fill-foreground/10" />
</div>
</div>
<div ref="commandRef" class="p-5 border-t border-foreground/20">
{{ packageManager }} {{ getDisplayText() }}
</div>
</div>
</template>
+8
View File
@@ -0,0 +1,8 @@
<template>
<div class="hidden xl:block pl-14 col-span-2 text-foreground/50">
<div class="flex flex-col lg:mt-10 pt-10 lg:pt-0 sticky top-40">
<div class="font-medium text-foreground/70 mb-2">On This Page</div>
<slot />
</div>
</div>
</template>
+42
View File
@@ -0,0 +1,42 @@
<script lang="ts" setup>
import * as tabs from "@zag-js/tabs";
import { useMachine, normalizeProps } from "@zag-js/vue";
import { computed } from "vue";
import { cn } from "@mykopkb/core/utils/cn";
const { class: className } = defineProps<{
class?: string;
}>();
const service = useMachine(tabs.machine, {
id: crypto.randomUUID(),
defaultValue: "preview",
});
const api = computed(() => tabs.connect(service, normalizeProps));
</script>
<template>
<div class="mt-10" v-bind="api.getRootProps()">
<div class="flex px-3" v-bind="api.getListProps()">
<button v-bind="api.getTriggerProps({ value: 'preview' })"
class="px-2.5 opacity-70 cursor-pointer data-selected:opacity-100">
Preview
</button>
<button v-bind="api.getTriggerProps({ value: 'code' })"
class="px-2.5 opacity-70 cursor-pointer data-selected:opacity-100">
Code
</button>
</div>
<div v-bind="api.getContentProps({ value: 'preview' })" :class="cn([
'mt-4 backdrop-blur-md border border-foreground/20 bg-background/50 min-h-100 flex flex-col md:flex-row gap-3 items-center justify-center px-5 py-8 sm:p-8 lg:py-20 lg:px-20',
className,
])
">
<slot name="preview" />
</div>
<div v-bind="api.getContentProps({ value: 'code' })" class="-mt-4">
<slot name="code" />
</div>
</div>
</template>
+88
View File
@@ -0,0 +1,88 @@
<script lang="ts" setup>
import { twMerge } from "tailwind-merge";
import { FileScan, Clipboard, ArrowDown, Check } from "@lucide/vue";
import { ref, onMounted, onUnmounted } from "vue";
const { title = "" } = defineProps<{
title?: string;
}>();
const expand = ref(false);
const copied = ref(false);
const codeRef = ref<HTMLDivElement | null>(null);
let copyTimeout: ReturnType<typeof setTimeout> | null = null;
onMounted(() => {
if (codeRef.value) {
if (
codeRef.value.offsetHeight < 300 &&
!(codeRef.value.offsetParent === null)
) {
expand.value = true;
}
}
});
onUnmounted(() => {
if (copyTimeout) clearTimeout(copyTimeout);
});
function handleCopy() {
const codeElement = codeRef.value?.querySelector("code");
if (!codeElement) return;
const text = codeElement.textContent || "";
navigator.clipboard.writeText(text).then(() => {
copied.value = true;
if (copyTimeout) clearTimeout(copyTimeout);
copyTimeout = setTimeout(() => {
copied.value = false;
}, 3000);
});
}
</script>
<template>
<div class="mt-8 mb-10 border border-foreground/20 bg-background/50 relative">
<div class="flex items-center py-2 px-5 gap-5">
<FileScan v-if="title" class="stroke-1 size-4 fill-foreground/10" />
<div class="flex items-center w-full gap-2">
<div>{{ title }}</div>
<div @click="expand = !expand" :class="twMerge([
'ms-auto mr-2 cursor-pointer px-3.5 py-1 relative after:w-px after:h-5 after:inset-y-0 after:my-auto after:bg-foreground/20 after:right-0 after:absolute',
codeRef &&
codeRef.offsetHeight < 300 &&
!(codeRef.offsetParent === null) &&
'opacity-0',
])
">
{{ expand ? "Collapse" : "Expand" }}
</div>
<Check v-if="copied" :class="twMerge([
'cursor-pointer size-4 stroke-1 fill-foreground/10',
!title && 'mt-1',
])
" />
<Clipboard v-else @click="handleCopy" :class="twMerge([
'cursor-pointer size-4 stroke-1 fill-foreground/10',
!title && 'mt-1',
])
" />
</div>
</div>
<div :class="twMerge([
'text-xs px-5 border-t border-foreground/20 overflow-x-auto overflow-y-hidden before:h-3/4 before:absolute before:bg-linear-to-b before:from-transparent before:to-background/80 before:inset-x-0 before:bottom-0',
expand && 'max-h-auto before:hidden',
!expand && 'max-h-100',
!title && '-mt-13 border-0',
])
" ref="codeRef">
<pre><code><slot /></code></pre>
<div v-if="!expand" @click="expand = true"
class="absolute bottom-0 inset-x-0 mx-auto w-26 mb-8 py-1 cursor-pointer border border-foreground/50 bg-foreground/15 flex items-center justify-center gap-2 backdrop-blur">
<ArrowDown class="size-4" /> Expand
</div>
</div>
</div>
</template>
+13
View File
@@ -0,0 +1,13 @@
<script lang="ts" setup>
import { twMerge } from "tailwind-merge";
const { class: className } = defineProps<{
class?: string;
}>();
</script>
<template>
<p :class="twMerge(['mt-2 opacity-70', className])">
<slot />
</p>
</template>
+5
View File
@@ -0,0 +1,5 @@
<template>
<div class="text-xl font-medium">
<slot />
</div>
</template>
+5
View File
@@ -0,0 +1,5 @@
<template>
<div class="mt-2 text-base opacity-70">
<slot />
</div>
</template>
+5
View File
@@ -0,0 +1,5 @@
<template>
<h2 class="text-2xl font-medium">
<slot />
</h2>
</template>
+18
View File
@@ -0,0 +1,18 @@
<script lang="ts" setup>
import { cn } from "@mykopkb/core/utils/cn";
</script>
<template>
<div :class="cn([
'p-7 sm:p-10 lg:px-20 lg:py-14 sm:mx-10 lg:ml-0 xl:mr-0 col-span-10 xl:col-span-8 border-x sm:border-y border-foreground/20 bg-background/50 mb-10 relative',
'before:absolute before:inset-0 before:bg-background/50 before:z-[-1]',
])
">
<div
class="after:bg-accent after:bg-center after:bg-no-repeat after:fixed after:size-200 after:inset-x-0 after:mx-auto after:top-0 after:blur-2xl dark:after:opacity-50">
<div class="z-50 relative">
<slot />
</div>
</div>
</div>
</template>
+10
View File
@@ -0,0 +1,10 @@
export { default as Wrapper } from "./Wrapper.vue";
export { default as Menu } from "./Menu.vue";
export { default as Title } from "./Title.vue";
export { default as Subtitle } from "./Subtitle.vue";
export { default as Preview } from "./Preview.vue";
export { default as SectionTitle } from "./SectionTitle.vue";
export { default as SectionContent } from "./SectionContent.vue";
export { default as InstallPackage } from "./InstallPackage.vue";
export { default as PreviewCode } from "./PreviewCode.vue";
export { default as ApiButton } from "./ApiButton.vue";
@@ -0,0 +1,55 @@
<script setup lang="ts">
import { Box } from '@/components/ui/box'
import { Lucide } from '@/components/ui/lucide'
import { AvatarRoot, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import fakers from '@/utils/faker'
import _ from 'lodash'
interface Props {
class?: string
boxClass?: string
}
const props = defineProps<Props>()
</script>
<template>
<div
v-bind="$attrs"
:class="[
'invisible opacity-0 scale-95 transition-all duration-200 delay-0 group-hover/notifications:visible group-hover/notifications:opacity-100 group-hover/notifications:scale-100 group-hover/notifications:delay-200',
props.class,
]"
>
<Box
:class="`before:shadow-foreground/5 z-50 flex w-96 flex-col gap-2.5 px-6 py-5 before:rounded-2xl before:shadow-xl before:backdrop-blur after:rounded-2xl ${props.boxClass ?? ''}`"
>
<div class="flex place-content-between items-center">
<div class="font-medium">Notifications</div>
<a class="text-primary" href="">View More</a>
</div>
<div class="mt-1 flex flex-col gap-2.5">
<a
v-for="(faker, fakerKey) in _.take(fakers, 5)"
:key="fakerKey"
class="hover:border-foreground/10 hover:bg-foreground/5 -mx-2 flex items-center gap-3.5 rounded-2xl border border-transparent p-2"
href=""
>
<AvatarRoot class="size-11 flex-none rounded-full">
<AvatarFallback>PA</AvatarFallback>
<AvatarImage :src="faker['photos'][2]" alt="avatar" />
</AvatarRoot>
<div class="flex flex-col gap-1">
<div class="flex place-content-between items-center">
<div class="font-medium">{{ faker['users'][0]!['name'] }}</div>
<div class="text-xs opacity-70">{{ faker['times'][0] }}</div>
</div>
<div class="line-clamp-2 opacity-70">
{{ faker['news'][0]!['shortContent'] }}
</div>
</div>
</a>
</div>
</Box>
</div>
</template>
@@ -0,0 +1 @@
export { default as NotificationDropdown } from './NotificationDropdown.vue'
@@ -0,0 +1,151 @@
<script lang="ts" setup>
import _ from 'lodash'
import { ref } from 'vue'
import fakers from '@/utils/faker'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { Input } from '@/components/ui/input'
import { Box } from '@/components/ui/box'
import {
MenuRoot,
MenuTrigger,
MenuPositioner,
MenuContent,
MenuCheckboxItem,
} from '@/components/ui/menu'
import {
Table,
TableHeader,
TableBody,
TableHead,
TableRow,
TableCell,
} from '@/components/ui/table'
import { NativeSelect, NativeSelectOption } from '@/components/ui/native-select'
import { AvatarRoot, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import {
CarouselRoot,
CarouselPrevTrigger,
CarouselNextTrigger,
CarouselItemGroup,
CarouselItem,
} from '@/components/ui/carousel'
import {
DatePickerRoot,
DatePickerControl,
DatePickerInput,
DatePickerTrigger,
DatePickerPositioner,
DatePickerContent,
DatePickerYearSelect,
DatePickerMonthSelect,
DatePickerView,
DatePickerViewControl,
DatePickerPrevTrigger,
DatePickerNextTrigger,
DatePickerRangeText,
DatePickerTable,
DatePickerTableHead,
DatePickerTableRow,
DatePickerTableHeader,
DatePickerTableBody,
DatePickerTableCell,
DatePickerTableCellTrigger,
DatePickerContext,
} from '@/components/ui/datepicker'
import {
PaginationContext,
PaginationRoot,
PaginationItem,
PaginationPrevTrigger,
PaginationNextTrigger,
PaginationEllipsis,
} from '@/components/ui/pagination'
import { Map } from '@/components/ui/map'
import { Lucide } from '@/components/ui/lucide'
import { Line1, Line2, Pie1, Donut1, Donut2 } from '@/components/chart-presets'
const pc = ref(false)
const electronic = ref(false)
const smartphone = ref(false)
const photography = ref(false)
const sport = ref(false)
</script>
<template>
<!-- BEGIN: Official Stores -->
<div class="block h-10 items-center sm:flex">
<h2 class="me-5 truncate text-lg font-medium">Official Store</h2>
<div class="relative mt-3 sm:ms-auto sm:mt-0">
<Lucide
class="[--color:var(--color-foreground)]/50 absolute inset-y-0 start-0 z-10 my-auto ms-3"
icon="MapPin"
/>
<Input class="box pl-10 sm:w-56" type="text" placeholder="Filter by city" />
</div>
</div>
<Box class="mt-12 p-5 sm:mt-5">
<div>250 Official stores in 21 countries, click the marker to see location details.</div>
<Map
class="mt-5 w-full h-[327px]"
:center="[13.388, 52.517]"
:zoom="14"
:markers="{
type: 'FeatureCollection',
features: [
{
type: 'Feature',
properties: { name: 'Cafe Berlin', type: 'cafe' },
geometry: { type: 'Point', coordinates: [13.388, 52.517] },
},
{
type: 'Feature',
properties: { name: 'Restaurant Alex', type: 'restaurant' },
geometry: { type: 'Point', coordinates: [13.39, 52.518] },
},
{
type: 'Feature',
properties: { name: 'Coffee House', type: 'cafe' },
geometry: { type: 'Point', coordinates: [13.385, 52.515] },
},
{
type: 'Feature',
properties: { name: 'Pizza Place', type: 'restaurant' },
geometry: { type: 'Point', coordinates: [13.392, 52.519] },
},
{
type: 'Feature',
properties: { name: 'Burger Joint', type: 'restaurant' },
geometry: { type: 'Point', coordinates: [13.387, 52.516] },
},
{
type: 'Feature',
properties: { name: 'Starbucks', type: 'cafe' },
geometry: { type: 'Point', coordinates: [13.391, 52.52] },
},
{
type: 'Feature',
properties: { name: 'Sushi Bar', type: 'restaurant' },
geometry: { type: 'Point', coordinates: [13.395, 52.522] },
},
{
type: 'Feature',
properties: { name: 'Bakery', type: 'cafe' },
geometry: { type: 'Point', coordinates: [13.383, 52.514] },
},
{
type: 'Feature',
properties: { name: 'Italian Restaurant', type: 'restaurant' },
geometry: { type: 'Point', coordinates: [13.398, 52.525] },
},
{
type: 'Feature',
properties: { name: 'Tea House', type: 'cafe' },
geometry: { type: 'Point', coordinates: [13.38, 52.512] },
},
],
}"
/>
</Box>
<!-- END: Official Stores -->
</template>
@@ -0,0 +1 @@
export { default as OfficialStores } from './OfficialStores.vue'
@@ -0,0 +1,142 @@
<script setup lang="ts">
import { Lucide } from '@/components/ui/lucide'
import { Input } from '@/components/ui/input'
import { DialogRoot, DialogContent } from '@/components/ui/dialog'
import { AvatarRoot, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import fakers from '@/utils/faker'
const { open } = defineProps<{
open?: boolean
}>()
const emit = defineEmits<{
(e: 'update:open', value: boolean): void
}>()
</script>
<template>
<DialogRoot :open="open" @openChange="(details) => emit('update:open', details.open)">
<DialogContent class="sm:max-w-2xl p-0">
<div class="relative border-b border-dashed border-foreground/15">
<Lucide class="absolute inset-y-0 my-auto ml-4 opacity-70 z-50" icon="Search" />
<Input
class="w-full pl-12 bg-transparent border-none pr-19 h-14 focus:ring-offset-transparent focus-visible:ring-transparent shadow-none"
type="text"
placeholder="Quick search..."
/>
<div
class="absolute inset-y-0 right-0 flex items-center h-6 px-2 my-auto mr-4 border rounded-lg border-foreground/30 text-xs opacity-70"
>
esc
</div>
</div>
<div class="flex flex-wrap gap-2 px-5 mt-4">
<a
class="hover:bg-foreground/5 flex items-center gap-x-1.5 rounded-full border border-foreground/15 px-3 py-0.5 text-xs font-medium"
href=""
>
<Lucide class="size-4" icon="Users2" />
Users
</a>
<a
class="hover:bg-foreground/5 flex items-center gap-x-1.5 rounded-full border border-foreground/15 px-3 py-0.5 text-xs font-medium"
href=""
>
<Lucide class="size-4" icon="Building2" />
Departments
</a>
<a
class="hover:bg-foreground/5 flex items-center gap-x-1.5 rounded-full border border-foreground/15 px-3 py-0.5 text-xs font-medium"
href=""
>
<Lucide class="size-4" icon="KanbanSquare" />
Products
</a>
<a
class="hover:bg-foreground/5 flex items-center gap-x-1.5 rounded-full border border-foreground/15 px-3 py-0.5 text-xs font-medium"
href=""
>
<Lucide class="size-4" icon="MailCheck" />
Mails
</a>
</div>
<div class="px-5 py-4 mt-2">
<div class="flex items-center">
<div class="text-xs uppercase opacity-70">Users</div>
<a class="ml-auto text-xs opacity-70" href=""> See All </a>
</div>
<div class="mt-3.5 flex flex-col gap-1">
<template v-for="(faker, fakerKey) in fakers.slice(0, 3)" :key="fakerKey">
<a
class="hover:border-foreground/10 hover:bg-foreground/5 -mx-1 flex items-center gap-2.5 rounded-xl border border-transparent p-1"
href=""
>
<AvatarRoot class="size-8">
<AvatarFallback>{{ faker.users[0]?.name.charAt(0) }}</AvatarFallback>
<AvatarImage :src="faker.photos[2]" />
</AvatarRoot>
<div class="font-medium truncate text-sm">
{{ faker.users[0]?.name }}
</div>
<div class="hidden opacity-70 sm:block text-xs">
{{ faker.jobs[0] }}
</div>
</a>
</template>
</div>
</div>
<div class="px-5 py-4 border-t border-dashed border-foreground/15">
<div class="flex items-center">
<div class="text-xs uppercase opacity-70">Departments</div>
<a class="ml-auto text-xs opacity-70" href=""> See All </a>
</div>
<div class="mt-3.5 flex flex-col gap-1">
<template v-for="(faker, fakerKey) in fakers.slice(0, 3)" :key="fakerKey">
<a
class="hover:border-foreground/10 hover:bg-foreground/5 -mx-1 flex items-center gap-2.5 rounded-xl border border-transparent p-1"
href=""
>
<div
class="flex items-center justify-center overflow-hidden border rounded-lg border-primary/10 bg-primary/10 text-primary size-8"
>
<Lucide v-if="fakerKey % 2 === 0" class="stroke-1" icon="Store" />
<Lucide v-else class="stroke-1" icon="Hotel" />
</div>
<div class="font-medium truncate text-sm">
{{ faker.products[0]?.name }}
</div>
<div class="hidden opacity-70 sm:block text-xs">
{{ faker.products[0]?.category }}
</div>
</a>
</template>
</div>
</div>
<div class="px-5 py-4 border-t border-dashed border-foreground/15 pb-6">
<div class="flex items-center">
<div class="text-xs uppercase opacity-70">Products</div>
<a class="ml-auto text-xs opacity-70" href=""> See All </a>
</div>
<div class="mt-3.5 flex flex-col gap-1">
<template v-for="(faker, fakerKey) in fakers.slice(0, 3)" :key="fakerKey">
<a
class="hover:border-foreground/10 hover:bg-foreground/5 -mx-1 flex items-center gap-2.5 rounded-xl border border-transparent p-1"
href=""
>
<AvatarRoot class="size-8 rounded-lg overflow-hidden">
<AvatarFallback>{{ faker.foods[0]?.name.charAt(0) }}</AvatarFallback>
<AvatarImage :src="faker.images[2]" />
</AvatarRoot>
<div class="font-medium truncate text-sm">
{{ faker.foods[0]?.name }}
</div>
<div class="hidden opacity-70 sm:block text-xs">
{{ faker.products[1]?.category }}
</div>
</a>
</template>
</div>
</div>
</DialogContent>
</DialogRoot>
</template>
@@ -0,0 +1 @@
export { default as QuickSearchDialog } from './QuickSearchDialog.vue'
@@ -0,0 +1,191 @@
<script lang="ts" setup>
import _ from 'lodash'
import { ref } from 'vue'
import fakers from '@/utils/faker'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { Input } from '@/components/ui/input'
import { Box } from '@/components/ui/box'
import {
MenuRoot,
MenuTrigger,
MenuPositioner,
MenuContent,
MenuCheckboxItem,
} from '@/components/ui/menu'
import {
Table,
TableHeader,
TableBody,
TableHead,
TableRow,
TableCell,
} from '@/components/ui/table'
import { NativeSelect, NativeSelectOption } from '@/components/ui/native-select'
import { AvatarRoot, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import {
CarouselRoot,
CarouselPrevTrigger,
CarouselNextTrigger,
CarouselItemGroup,
CarouselItem,
} from '@/components/ui/carousel'
import {
DatePickerRoot,
DatePickerControl,
DatePickerInput,
DatePickerTrigger,
DatePickerPositioner,
DatePickerContent,
DatePickerYearSelect,
DatePickerMonthSelect,
DatePickerView,
DatePickerViewControl,
DatePickerPrevTrigger,
DatePickerNextTrigger,
DatePickerRangeText,
DatePickerTable,
DatePickerTableHead,
DatePickerTableRow,
DatePickerTableHeader,
DatePickerTableBody,
DatePickerTableCell,
DatePickerTableCellTrigger,
DatePickerContext,
} from '@/components/ui/datepicker'
import {
PaginationContext,
PaginationRoot,
PaginationItem,
PaginationPrevTrigger,
PaginationNextTrigger,
PaginationEllipsis,
} from '@/components/ui/pagination'
import { Map } from '@/components/ui/map'
import { Lucide } from '@/components/ui/lucide'
import { Line1, Line2, Pie1, Donut1, Donut2 } from '@/components/chart-presets'
const pc = ref(false)
const electronic = ref(false)
const smartphone = ref(false)
const photography = ref(false)
const sport = ref(false)
</script>
<template>
<!-- BEGIN: Recent Activities -->
<div class="flex h-10 items-center">
<h2 class="me-5 truncate text-lg font-medium">Recent Activities</h2>
<a class="text-primary ms-auto truncate" href=""> Show More </a>
</div>
<div
class="before:bg-foreground/10 relative mt-5 before:absolute before:ms-5 before:mt-5 before:block before:h-[85%] before:w-px"
>
<div class="relative mb-3 flex items-center">
<div
class="before:bg-foreground/10 before:absolute before:ms-5 before:mt-5 before:block before:h-px before:w-20"
>
<AvatarRoot class="size-11">
<AvatarFallback>PA</AvatarFallback>
<AvatarImage :src="fakers[9]!['photos'][0]" alt="avatar" />
</AvatarRoot>
</div>
<Box class="z-10 ms-4 flex-1 px-5 py-3 before:hidden">
<div class="flex items-center">
<div class="font-medium">
{{ fakers[9]!['users'][0]!['name'] }}
</div>
<div class="ms-auto text-xs opacity-70">07:00 PM</div>
</div>
<div class="mt-1 opacity-70">Has joined the team</div>
</Box>
</div>
<div class="relative mb-3 flex items-center">
<div
class="before:bg-foreground/10 before:absolute before:ms-5 before:mt-5 before:block before:h-px before:w-20"
>
<AvatarRoot class="size-11">
<AvatarFallback>PA</AvatarFallback>
<AvatarImage :src="fakers[8]!['photos'][0]" alt="avatar" />
</AvatarRoot>
</div>
<Box class="z-10 ms-4 flex-1 px-5 py-3 before:hidden">
<div class="flex items-center">
<div class="font-medium">
{{ fakers[8]!['users'][0]!['name'] }}
</div>
<div class="ms-auto text-xs opacity-70">07:00 PM</div>
</div>
<div>
<div class="mt-1 opacity-70">Added 3 new photos</div>
<div class="mt-2 flex gap-2">
<AvatarRoot class="size-8 rounded-lg border-none ring-0">
<AvatarFallback>PA</AvatarFallback>
<AvatarImage :src="fakers[6]!['photos'][0]" alt="avatar" />
</AvatarRoot>
<AvatarRoot class="size-8 rounded-lg border-none ring-0">
<AvatarFallback>PA</AvatarFallback>
<AvatarImage :src="fakers[6]!['photos'][1]" alt="avatar" />
</AvatarRoot>
<AvatarRoot class="size-8 rounded-lg border-none ring-0">
<AvatarFallback>PA</AvatarFallback>
<AvatarImage :src="fakers[6]!['photos'][2]" alt="avatar" />
</AvatarRoot>
</div>
</div>
</Box>
</div>
<div class="my-4 text-center text-xs opacity-70">12 November</div>
<div class="relative mb-3 flex items-center">
<div
class="before:bg-foreground/10 before:absolute before:ms-5 before:mt-5 before:block before:h-px before:w-20"
>
<AvatarRoot class="size-11">
<AvatarFallback>PA</AvatarFallback>
<AvatarImage :src="fakers[7]!['photos'][0]" alt="avatar" />
</AvatarRoot>
</div>
<Box class="z-10 ms-4 flex-1 px-5 py-3 before:hidden">
<div class="flex items-center">
<div class="font-medium">
{{ fakers[7]!['users'][0]!['name'] }}
</div>
<div class="ms-auto text-xs opacity-70">07:00 PM</div>
</div>
<div class="mt-1 opacity-70">
Has changed
<a class="text-primary" href="">
{{ fakers[7]!['products'][0]!['name'] }}
</a>
price and description
</div>
</Box>
</div>
<div class="relative mb-3 flex items-center">
<div
class="before:bg-foreground/10 before:absolute before:ms-5 before:mt-5 before:block before:h-px before:w-20"
>
<AvatarRoot class="size-11">
<AvatarFallback>PA</AvatarFallback>
<AvatarImage :src="fakers[6]!['photos'][0]" alt="avatar" />
</AvatarRoot>
</div>
<Box class="z-10 ms-4 flex-1 px-5 py-3 before:hidden">
<div class="flex items-center">
<div class="font-medium">
{{ fakers[6]!['users'][0]!['name'] }}
</div>
<div class="ms-auto text-xs opacity-70">07:00 PM</div>
</div>
<div class="mt-1 opacity-70">
Has changed
<a class="text-primary" href="">
{{ fakers[6]!['products'][0]!['name'] }}
</a>
description
</div>
</Box>
</div>
</div>
<!-- END: Recent Activities -->
</template>
@@ -0,0 +1 @@
export { default as RecentActivities } from './RecentActivities.vue'
+167
View File
@@ -0,0 +1,167 @@
<script lang="ts" setup>
import _ from 'lodash'
import { ref } from 'vue'
import fakers from '@/utils/faker'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { Input } from '@/components/ui/input'
import { Box } from '@/components/ui/box'
import {
MenuRoot,
MenuTrigger,
MenuPositioner,
MenuContent,
MenuCheckboxItem,
} from '@/components/ui/menu'
import {
Table,
TableHeader,
TableBody,
TableHead,
TableRow,
TableCell,
} from '@/components/ui/table'
import { NativeSelect, NativeSelectOption } from '@/components/ui/native-select'
import { AvatarRoot, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import {
CarouselRoot,
CarouselPrevTrigger,
CarouselNextTrigger,
CarouselItemGroup,
CarouselItem,
} from '@/components/ui/carousel'
import {
DatePickerRoot,
DatePickerControl,
DatePickerInput,
DatePickerTrigger,
DatePickerPositioner,
DatePickerContent,
DatePickerYearSelect,
DatePickerMonthSelect,
DatePickerView,
DatePickerViewControl,
DatePickerPrevTrigger,
DatePickerNextTrigger,
DatePickerRangeText,
DatePickerTable,
DatePickerTableHead,
DatePickerTableRow,
DatePickerTableHeader,
DatePickerTableBody,
DatePickerTableCell,
DatePickerTableCellTrigger,
DatePickerContext,
} from '@/components/ui/datepicker'
import {
PaginationContext,
PaginationRoot,
PaginationItem,
PaginationPrevTrigger,
PaginationNextTrigger,
PaginationEllipsis,
} from '@/components/ui/pagination'
import { Map } from '@/components/ui/map'
import { Lucide } from '@/components/ui/lucide'
import { Line1, Line2, Pie1, Donut1, Donut2 } from '@/components/chart-presets'
const pc = ref(false)
const electronic = ref(false)
const smartphone = ref(false)
const photography = ref(false)
const sport = ref(false)
</script>
<template>
<!-- BEGIN: Schedules -->
<div class="flex h-10 items-center">
<h2 class="me-5 truncate text-lg font-medium">Schedules</h2>
<a class="text-primary ms-auto flex items-center truncate" href="">
<Lucide class="me-1" icon="Plus" /> Add New Schedules
</a>
</div>
<div class="mt-5">
<Box>
<div class="flex">
<Lucide class="size-5 opacity-70" icon="ChevronLeft" />
<div class="mx-auto text-base font-medium">April</div>
<Lucide class="size-5 opacity-70" icon="ChevronRight" />
</div>
<div class="mt-5 grid grid-cols-7 gap-4 text-center [&>span]:text-sm [&>span]:shadow-none">
<div class="font-medium">Su</div>
<div class="font-medium">Mo</div>
<div class="font-medium">Tu</div>
<div class="font-medium">We</div>
<div class="font-medium">Th</div>
<div class="font-medium">Fr</div>
<div class="font-medium">Sa</div>
<Badge variant="ghost" class="opacity-70">29</Badge>
<Badge variant="ghost" class="opacity-70">30</Badge>
<Badge variant="ghost" class="opacity-70">31</Badge>
<Badge variant="ghost">1</Badge>
<Badge variant="ghost">2</Badge>
<Badge variant="ghost">3</Badge>
<Badge variant="ghost">4</Badge>
<Badge variant="ghost">5</Badge>
<Badge variant="success" look="outline">6</Badge>
<Badge variant="ghost">7</Badge>
<Badge variant="primary" look="outline">8</Badge>
<Badge variant="ghost">9</Badge>
<Badge variant="ghost">10</Badge>
<Badge variant="ghost">11</Badge>
<Badge variant="ghost">12</Badge>
<Badge variant="ghost">13</Badge>
<Badge variant="ghost">14</Badge>
<Badge variant="ghost">15</Badge>
<Badge variant="ghost">16</Badge>
<Badge variant="ghost">17</Badge>
<Badge variant="ghost">18</Badge>
<Badge variant="ghost">19</Badge>
<Badge variant="ghost">20</Badge>
<Badge variant="ghost">21</Badge>
<Badge variant="ghost">22</Badge>
<Badge variant="pending" look="outline">23</Badge>
<Badge variant="ghost">24</Badge>
<Badge variant="ghost">25</Badge>
<Badge variant="ghost">26</Badge>
<Badge variant="warning" look="outline">27</Badge>
<Badge variant="ghost">28</Badge>
<Badge variant="ghost">29</Badge>
<Badge variant="ghost">30</Badge>
<Badge variant="ghost" class="opacity-70">1</Badge>
<Badge variant="ghost" class="opacity-70">2</Badge>
<Badge variant="ghost" class="opacity-70">3</Badge>
<Badge variant="ghost" class="opacity-70">4</Badge>
<Badge variant="ghost" class="opacity-70">5</Badge>
<Badge variant="ghost" class="opacity-70">6</Badge>
<Badge variant="ghost" class="opacity-70">7</Badge>
<Badge variant="ghost" class="opacity-70">8</Badge>
<Badge variant="ghost" class="opacity-70">9</Badge>
</div>
<div class="mt-5 border-t border-foreground/10 pt-5">
<div class="flex items-center">
<div
class="bg-(--color)/20 border-(--color)/60 me-3 size-2 rounded-full border [--color:var(--color-pending)]"
></div>
<span class="truncate">UI/UX Workshop</span>
<span class="font-medium xl:ms-auto">23th</span>
</div>
<div class="mt-4 flex items-center">
<div
class="bg-(--color)/20 border-(--color)/60 me-3 size-2 rounded-full border [--color:var(--color-primary)]"
></div>
<span class="truncate"> VueJs Frontend Development </span>
<span class="font-medium xl:ms-auto">10th</span>
</div>
<div class="mt-4 flex items-center">
<div
class="bg-(--color)/20 border-(--color)/60 me-3 size-2 rounded-full border [--color:var(--color-warning)]"
></div>
<span class="truncate">Laravel Rest API</span>
<span class="font-medium xl:ms-auto">31th</span>
</div>
</div>
</Box>
</div>
<!-- END: Schedules -->
</template>
+1
View File
@@ -0,0 +1 @@
export { default as Schedules } from './Schedules.vue'
+245
View File
@@ -0,0 +1,245 @@
<script setup lang="ts">
import { ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { Lucide, type Icon } from '@/components/ui/lucide'
import { slideUp, slideDown } from '@/utils/helper'
import { cn } from '@/utils/cn'
import _ from 'lodash'
interface Menu {
icon?: Icon
title?: string
route_name?: string
params?: any
badge?: number
sub_menu?: Menu[]
}
const props = defineProps<{
menu: (string | Menu)[]
}>()
const route = useRoute()
const router = useRouter()
const formattedMenu = ref<(string | Menu)[]>([])
// State to track open menus based on unique index/path
const openMenus = ref<Record<string, boolean>>({})
const isMenuActive = (menu: string | Menu): boolean => {
if (typeof menu === 'string') return false
if (menu.route_name) {
if (menu.route_name === '/') {
return route.path === '/'
}
return route.name === menu.route_name || route.path.includes(menu.route_name)
}
if (menu.sub_menu) {
return menu.sub_menu.some((sub) => isMenuActive(sub))
}
return false
}
const findActiveSubMenus = (subMenu: Menu[]): boolean => {
return subMenu.some((item) => {
if (item.route_name) {
if (item.route_name === '/') {
return route.path === '/'
}
return route.name === item.route_name || route.path.includes(item.route_name)
}
if (item.sub_menu) return findActiveSubMenus(item.sub_menu)
return false
})
}
const initMenu = (menuList: (string | Menu)[], parentKey = 'menu') => {
const newOpenMenus: Record<string, boolean> = {}
const traverse = (list: (string | Menu)[], pk: string) => {
list.forEach((item, index) => {
const key = `${pk}-${index}`
if (typeof item !== 'string' && item.sub_menu) {
if (findActiveSubMenus(item.sub_menu)) {
newOpenMenus[key] = true
}
traverse(item.sub_menu, key)
}
})
}
traverse(menuList, parentKey)
openMenus.value = { ...openMenus.value, ...newOpenMenus }
}
watch(
[() => props.menu, () => route.name],
() => {
formattedMenu.value = props.menu
initMenu(props.menu)
},
{ immediate: true },
)
const toggleMenu = (key: string, event: MouseEvent) => {
event.preventDefault()
const el = (event.currentTarget as HTMLElement).nextElementSibling as HTMLElement
if (el) {
if (openMenus.value[key]) {
slideUp(el, 300)
openMenus.value[key] = false
} else {
slideDown(el, 300)
openMenus.value[key] = true
}
}
}
const linkTo = (menu: Menu, event: MouseEvent) => {
event.preventDefault()
if (menu.route_name) {
router.push({
name: menu.route_name,
params: menu.params,
})
}
}
</script>
<template>
<ul class="scrollable">
<template v-for="(menu, menuKey) in formattedMenu" :key="menuKey">
<!-- BEGIN: First Child -->
<li v-if="typeof menu === 'string'" class="side-menu__group-label">
{{ menu }}
</li>
<li v-else>
<a
href=""
@click="
(event) => {
if (menu.sub_menu) {
toggleMenu(`menu-${menuKey}`, event)
} else {
linkTo(menu, event)
}
}
"
:class="
cn(
'side-menu__link',
isMenuActive(menu) ? 'side-menu__link--active' : '',
)
"
>
<Lucide
v-if="menu.icon"
class="side-menu__link__icon"
:icon="menu.icon"
/>
<div class="side-menu__link__title">{{ menu.title }}</div>
<div v-if="menu.badge" class="side-menu__link__badge">
{{ menu.badge }}
</div>
<Lucide
v-if="menu.sub_menu"
:class="
cn(
'side-menu__link__chevron transition-transform duration-300',
openMenus[`menu-${menuKey}`] ? 'rotate-180' : '',
)
"
icon="ChevronDown"
/>
</a>
<!-- BEGIN: Second Child -->
<ul
v-if="menu.sub_menu"
:class="cn('hidden', { block: openMenus[`menu-${menuKey}`] })"
>
<li v-for="(subMenu, subMenuKey) in menu.sub_menu" :key="subMenuKey">
<a
href=""
@click="
(event) => {
if (subMenu.sub_menu) {
toggleMenu(`menu-${menuKey}-${subMenuKey}`, event)
} else {
linkTo(subMenu, event)
}
}
"
:class="
cn(
'side-menu__link',
isMenuActive(subMenu) ? 'side-menu__link--active' : '',
)
"
>
<Lucide
v-if="subMenu.icon"
class="side-menu__link__icon"
:icon="subMenu.icon"
/>
<div class="side-menu__link__title">
{{ subMenu.title }}
</div>
<div v-if="subMenu.badge" class="side-menu__link__badge">
{{ subMenu.badge }}
</div>
<Lucide
v-if="subMenu.sub_menu"
:class="
cn(
'side-menu__link__chevron transition-transform duration-300',
openMenus[`menu-${menuKey}-${subMenuKey}`] ? 'rotate-180' : '',
)
"
icon="ChevronDown"
/>
</a>
<!-- BEGIN: Third Child -->
<ul
v-if="subMenu.sub_menu"
:class="
cn('hidden', { block: openMenus[`menu-${menuKey}-${subMenuKey}`] })
"
>
<li
v-for="(lastSubMenu, lastSubMenuKey) in subMenu.sub_menu"
:key="lastSubMenuKey"
>
<a
href=""
@click.prevent="linkTo(lastSubMenu, $event)"
:class="
cn(
'side-menu__link',
isMenuActive(lastSubMenu) ? 'side-menu__link--active' : '',
)
"
>
<div class="side-menu__link__icon">
<Lucide v-if="lastSubMenu.icon" :icon="lastSubMenu.icon" />
</div>
<div class="side-menu__link__title">
{{ lastSubMenu.title }}
</div>
<div v-if="lastSubMenu.badge" class="side-menu__link__badge">
{{ lastSubMenu.badge }}
</div>
</a>
</li>
</ul>
<!-- END: Third Child -->
</li>
</ul>
<!-- END: Second Child -->
</li>
<!-- END: First Child -->
</template>
</ul>
</template>
+1
View File
@@ -0,0 +1 @@
export { default as SideMenu } from './SideMenu.vue'
@@ -0,0 +1,131 @@
<script setup lang="ts">
import { SheetRoot, SheetContent } from '@/components/ui/sheet'
import { Lucide } from '@/components/ui/lucide'
import { useColorSchemeStore, type ColorSchemes } from '@/stores/color-scheme'
import { useDarkModeStore } from '@/stores/dark-mode'
import { ref } from 'vue'
const themeSwitcherSheet = ref(false)
const setThemeSwitcherSheet = (value: boolean) => {
themeSwitcherSheet.value = value
}
const setColorSchemeClass = () => {
const el = document.querySelectorAll('html')[0]
el?.setAttribute('data-theme', useColorSchemeStore().colorSchemeValue)
if (useDarkModeStore().darkModeValue) el?.classList.add('dark')
}
const colorSchemeStore = useColorSchemeStore()
const switchColorScheme = (colorScheme: ColorSchemes) => {
useColorSchemeStore().setColorScheme(colorScheme)
setColorSchemeClass()
setThemeSwitcherSheet(false)
}
setColorSchemeClass()
const setDarkModeClass = () => {
const el = document.querySelectorAll('html')[0]
useDarkModeStore().darkModeValue ? el?.classList.add('dark') : el?.classList.remove('dark')
}
const darkModeStore = useDarkModeStore()
const switchDarkMode = (darkMode: boolean) => {
useDarkModeStore().setDarkMode(darkMode)
setDarkModeClass()
setThemeSwitcherSheet(false)
}
setDarkModeClass()
const colorSchemes: Array<ColorSchemes> = ['default', '1', '2', '3', '4', '5']
</script>
<template>
<div>
<SheetRoot :open="themeSwitcherSheet" @openChange="(details) => setThemeSwitcherSheet(details.open)">
<SheetContent>
<a class="absolute inset-y-0 left-0 right-auto my-auto ml-[60px] flex h-8 w-8 items-center justify-center rounded-full border border-white/90 bg-white/5 text-white/90 transition-all hover:rotate-180 hover:scale-105 hover:bg-white/10 focus:outline-none sm:ml-[105px] sm:h-14 sm:w-14"
@click="
(event: MouseEvent) => {
event.preventDefault()
setThemeSwitcherSheet(false)
}
">
<Lucide class="h-3 w-3 stroke-1 sm:h-8 sm:w-8" icon="X" />
</a>
<div class="absolute inset-0 px-7 py-5 overflow-y-scroll">
<div class="flex flex-col">
<div>
<div class="text-base font-medium">Accent Colors</div>
<div class="mt-0.5 opacity-70">Choose your accent color</div>
<div class="mt-5 grid grid-cols-6 gap-x-3 gap-y-4">
<div v-for="colorScheme in colorSchemes" :key="colorScheme">
<a @click="
(event: MouseEvent) => {
event.preventDefault()
switchColorScheme(colorScheme)
}
" :data-theme="colorScheme" :class="[
'h-10 cursor-pointer bg-primary block rounded-(--radius) transition-all duration-100 ring-offset-4 ring-offset-background hover:scale-[105%]',
colorSchemeStore.colorSchemeValue == colorScheme
? 'ring-2 ring-foreground/20'
: 'ring-1 ring-foreground/10',
]"></a>
</div>
</div>
</div>
<div class="border-b border-dashed border-foreground/15 my-5"></div>
<div>
<div class="text-base font-medium">Appearance</div>
<div class="mt-0.5 opacity-70">Choose your appearance</div>
<div class="mt-5 grid grid-cols-2 gap-x-5 gap-y-4">
<div>
<a @click="
(event: MouseEvent) => {
event.preventDefault()
switchDarkMode(false)
}
" :class="[
'h-12 cursor-pointer bg-background block rounded-(--radius) transition-all duration-100 ring-offset-4 ring-offset-background hover:scale-[105%]',
!darkModeStore.darkModeValue
? 'ring-2 ring-foreground/20'
: 'ring-1 ring-foreground/10',
]">
<div class="h-full overflow-hidden rounded-(--radius) bg-slate-200"></div>
</a>
<div class="mt-2.5 text-center text-xs capitalize">Light</div>
</div>
<div>
<a @click="
(event: MouseEvent) => {
event.preventDefault()
switchDarkMode(true)
}
" :class="[
'h-12 cursor-pointer bg-background block rounded-(--radius) transition-all duration-100 ring-offset-4 ring-offset-background hover:scale-[105%]',
darkModeStore.darkModeValue
? 'ring-2 ring-foreground/20'
: 'ring-1 ring-foreground/10',
]">
<div class="h-full overflow-hidden rounded-(--radius) bg-slate-900"></div>
</a>
<div class="mt-2.5 text-center text-xs capitalize">Dark</div>
</div>
</div>
</div>
</div>
</div>
</SheetContent>
</SheetRoot>
<div :class="[
'fixed inset-y-0 right-0 z-50 my-auto flex hover:w-20 transition-all w-14 h-12 cursor-pointer border-(--color)/50 items-center border justify-center rounded-l-full shadow-lg overflow-hidden bg-background/80 [--color:var(--color-primary)]',
'before:bg-(--color)/20 before:absolute before:inset-0',
]" @click="
(event: MouseEvent) => {
event.preventDefault()
setThemeSwitcherSheet(true)
}
">
<Lucide class="animate-spin" icon="Settings" />
</div>
</div>
</template>
@@ -0,0 +1 @@
export { default as ThemeSwitcher } from './ThemeSwitcher.vue'
+166
View File
@@ -0,0 +1,166 @@
<script setup lang="ts">
import { ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { Lucide, type Icon } from '@/components/ui/lucide'
import { slideUp, slideDown } from '@/utils/helper'
interface Menu {
icon?: Icon
title?: string
route_name?: string
params?: any
badge?: number
sub_menu?: Menu[]
}
const props = defineProps<{
menu: (string | Menu)[]
}>()
const route = useRoute()
const router = useRouter()
const formattedMenu = ref<(string | Menu)[]>([])
// State to track open menus based on unique index/path
const openMenus = ref<Record<string, boolean>>({})
watch(
() => props.menu,
() => {
formattedMenu.value = props.menu
},
{ immediate: true },
)
const isMenuActive = (menu: string | Menu): boolean => {
if (typeof menu === 'string') return false
if (menu.route_name && route.name === menu.route_name) return true
if (menu.sub_menu) {
return menu.sub_menu.some((sub) => isMenuActive(sub))
}
return false
}
const linkTo = (menu: Menu, event: MouseEvent) => {
event.preventDefault()
if (menu.route_name) {
router.push({
name: menu.route_name,
params: menu.params,
})
}
}
const toggleMenu = (key: string, event: MouseEvent) => {
event.preventDefault()
// Get the <ul> element that is a sibling of the clicked <a>
const el = (event.currentTarget as HTMLElement).nextElementSibling as HTMLElement
if (el) {
if (openMenus.value[key]) {
slideUp(el, 300)
openMenus.value[key] = false
} else {
slideDown(el, 300)
openMenus.value[key] = true
}
}
}
</script>
<template>
<ul class="top-menu">
<template v-for="(menu, menuKey) in formattedMenu" :key="menuKey">
<li v-if="typeof menu !== 'string'">
<a
href=""
@click="(event) => {
if (menu.sub_menu) {
toggleMenu(`menu-${menuKey}`, event)
} else {
linkTo(menu, event)
}
}"
:class="[
'top-menu__link',
isMenuActive(menu) ? 'top-menu__link--active' : '',
]"
>
<div class="top-menu__link__icon">
<Lucide v-if="menu.icon" :icon="menu.icon" />
</div>
<div class="top-menu__link__title">
{{ menu.title }}
<Lucide
v-if="menu.sub_menu"
:class="[
'top-menu__link__chevron transition-transform duration-300',
openMenus[`menu-${menuKey}`] ? 'rotate-180' : '',
]"
icon="ChevronDown"
/>
</div>
</a>
<!-- BEGIN: Second Child -->
<ul v-if="menu.sub_menu" class="hidden">
<li v-for="(subMenu, subMenuKey) in menu.sub_menu" :key="subMenuKey">
<a
href=""
@click="(event) => {
if (subMenu.sub_menu) {
toggleMenu(`menu-${menuKey}-${subMenuKey}`, event)
} else {
linkTo(subMenu, event)
}
}"
:class="[
'top-menu__link',
isMenuActive(subMenu) ? 'top-menu__link--active' : '',
]"
>
<div class="top-menu__link__icon">
<Lucide v-if="subMenu.icon" :icon="subMenu.icon" />
</div>
<div class="top-menu__link__title">
{{ subMenu.title }}
<Lucide
v-if="subMenu.sub_menu"
:class="[
'top-menu__link__chevron transition-transform duration-300',
openMenus[`menu-${menuKey}-${subMenuKey}`] ? 'rotate-180' : '',
]"
icon="ChevronDown"
/>
</div>
</a>
<!-- BEGIN: Third Child -->
<ul v-if="subMenu.sub_menu" class="hidden">
<li
v-for="(lastSubMenu, lastSubMenuKey) in subMenu.sub_menu"
:key="lastSubMenuKey"
>
<a
href=""
@click.prevent="linkTo(lastSubMenu, $event)"
:class="[
'top-menu__link',
isMenuActive(lastSubMenu) ? 'top-menu__link--active' : '',
]"
>
<div class="top-menu__link__icon">
<Lucide v-if="lastSubMenu.icon" :icon="lastSubMenu.icon" />
</div>
<div class="top-menu__link__title">
{{ lastSubMenu.title }}
</div>
</a>
</li>
</ul>
<!-- END: Third Child -->
</li>
</ul>
<!-- END: Second Child -->
</li>
</template>
</ul>
</template>
+3
View File
@@ -0,0 +1,3 @@
import TopMenu from "./TopMenu.vue";
export { TopMenu };
@@ -0,0 +1,109 @@
<script lang="ts" setup>
import _ from 'lodash'
import { ref } from 'vue'
import fakers from '@/utils/faker'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { Input } from '@/components/ui/input'
import { Box } from '@/components/ui/box'
import {
MenuRoot,
MenuTrigger,
MenuPositioner,
MenuContent,
MenuCheckboxItem,
} from '@/components/ui/menu'
import {
Table,
TableHeader,
TableBody,
TableHead,
TableRow,
TableCell,
} from '@/components/ui/table'
import { NativeSelect, NativeSelectOption } from '@/components/ui/native-select'
import { AvatarRoot, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import {
CarouselRoot,
CarouselPrevTrigger,
CarouselNextTrigger,
CarouselItemGroup,
CarouselItem,
} from '@/components/ui/carousel'
import {
DatePickerRoot,
DatePickerControl,
DatePickerInput,
DatePickerTrigger,
DatePickerPositioner,
DatePickerContent,
DatePickerYearSelect,
DatePickerMonthSelect,
DatePickerView,
DatePickerViewControl,
DatePickerPrevTrigger,
DatePickerNextTrigger,
DatePickerRangeText,
DatePickerTable,
DatePickerTableHead,
DatePickerTableRow,
DatePickerTableHeader,
DatePickerTableBody,
DatePickerTableCell,
DatePickerTableCellTrigger,
DatePickerContext,
} from '@/components/ui/datepicker'
import {
PaginationContext,
PaginationRoot,
PaginationItem,
PaginationPrevTrigger,
PaginationNextTrigger,
PaginationEllipsis,
} from '@/components/ui/pagination'
import { Map } from '@/components/ui/map'
import { Lucide } from '@/components/ui/lucide'
import { Line1, Line2, Pie1, Donut1, Donut2 } from '@/components/chart-presets'
const pc = ref(false)
const electronic = ref(false)
const smartphone = ref(false)
const photography = ref(false)
const sport = ref(false)
</script>
<template>
<!-- BEGIN: Transactions -->
<div class="flex h-10 items-center">
<h2 class="me-5 truncate text-lg font-medium">Transactions</h2>
</div>
<div class="mt-5">
<Box
v-for="(faker, fakerKey) in _.take(fakers, 5)"
class="mb-3 flex items-center px-5 py-3 before:hidden"
>
<AvatarRoot class="size-11">
<AvatarFallback>PA</AvatarFallback>
<AvatarImage :src="faker['photos'][0]" alt="avatar" />
</AvatarRoot>
<div class="me-auto ms-4">
<div class="font-medium">{{ faker['users'][0]!['name'] }}</div>
<div class="mt-1 text-xs opacity-70">
{{ faker['dates'][0] }}
</div>
</div>
<div
:class="{
'text-success': faker['trueFalse'][0],
'text-danger': !faker['trueFalse'][0],
}"
>
{{ faker['trueFalse'][0] ? '+' : '-' }}${{ faker['totals'][0] }}
</div>
</Box>
<Box as-child>
<Button variant="ghost" class="box h-12 w-full border-dotted">View More</Button>
</Box>
</div>
<!-- END: Transactions -->
</template>
+1
View File
@@ -0,0 +1 @@
export { default as Transactions } from './Transactions.vue'
@@ -0,0 +1,28 @@
<script lang="ts" setup>
import { accordionContent } from "@mykopkb/core/styles/accordion.styles";
import { cn } from "@mykopkb/core/utils/cn";
import { inject } from "vue";
import { Slot } from "@/components/ui/slot";
import type { Api, ItemProps } from "@zag-js/accordion";
const {
class: className,
asChild = false,
...props
} = defineProps<{
class?: string;
asChild?: boolean;
}>();
const api = inject<Api>("accordionApi");
const item = inject<ItemProps>("accordionItem");
</script>
<template>
<Slot :class="cn([className, accordionContent])" v-bind="{ ...props, ...$attrs, ...api?.getItemContentProps(item!) }">
<slot v-if="asChild" />
<div v-else>
<slot />
</div>
</Slot>
</template>
@@ -0,0 +1,39 @@
<script lang="ts" setup>
import { accordionItemVariants } from "@mykopkb/core/styles/accordion.styles";
import {
boxVariants,
type BoxVariants,
} from "@mykopkb/core/styles/box.styles";
import { cn } from "@mykopkb/core/utils/cn";
import { inject, provide } from "vue";
import { Slot } from "@/components/ui/slot";
import type { Api, ItemProps } from "@zag-js/accordion";
const {
raised,
class: className,
asChild = false,
...props
} = defineProps<
BoxVariants & ItemProps & { class?: string; asChild?: boolean }
>();
const variant = inject<"default" | "boxed">("accordionVariant", "default");
const api = inject<Api>("accordionApi");
provide("accordionItem", props);
</script>
<template>
<Slot :class="cn([
className,
variant == 'boxed' ? boxVariants({ raised, className }) : '',
accordionItemVariants({ variant, className }),
])
" v-bind="{ ...props, ...$attrs, ...api?.getItemProps(props) }">
<slot v-if="asChild" />
<div v-else>
<slot />
</div>
</Slot>
</template>
@@ -0,0 +1,44 @@
<script lang="ts" setup>
import { accordionRootVariants } from "@mykopkb/core/styles/accordion.styles";
import { cn } from "@mykopkb/core/utils/cn";
import { provide, computed } from "vue";
import * as accordion from "@zag-js/accordion";
import type { Props } from "@zag-js/accordion";
import { useMachine, normalizeProps } from "@zag-js/vue";
import { Slot } from "@/components/ui/slot";
const {
class: className,
variant = "default",
asChild = false,
collapsible = true,
...props
} = defineProps<
Partial<Props> & {
class?: string;
variant?: "default" | "boxed";
asChild?: boolean;
}
>();
const service = useMachine(accordion.machine, {
collapsible,
...props,
id: crypto.randomUUID(),
});
const api = computed(() => accordion.connect(service, normalizeProps));
provide("accordionVariant", variant);
provide("accordionApi", api);
</script>
<template>
<Slot :class="cn([className, accordionRootVariants({ variant, className })])"
v-bind="{ ...props, ...$attrs, ...api.getRootProps() }">
<slot v-if="asChild" />
<div v-else>
<slot />
</div>
</Slot>
</template>
@@ -0,0 +1,35 @@
<script lang="ts" setup>
import { ChevronDownIcon } from "@lucide/vue";
import {
accordionTrigger,
accordionItemIndicator,
} from "@mykopkb/core/styles/accordion.styles";
import { cn } from "@mykopkb/core/utils/cn";
import { inject } from "vue";
import { Slot } from "@/components/ui/slot";
import type { Api, ItemProps } from "@zag-js/accordion";
const {
class: className,
asChild = false,
...props
} = defineProps<{
class?: string;
asChild?: boolean;
}>();
const api = inject<Api>("accordionApi");
const item = inject<ItemProps>("accordionItem");
</script>
<template>
<Slot :class="cn([className, accordionTrigger])" v-bind="{ ...props, ...$attrs, ...api?.getItemTriggerProps(item!) }">
<slot v-if="asChild" />
<button v-else>
<slot />
<div v-bind="api?.getItemIndicatorProps(item!)" :class="cn(accordionItemIndicator)">
<ChevronDownIcon />
</div>
</button>
</Slot>
</template>
+4
View File
@@ -0,0 +1,4 @@
export { default as AccordionRoot } from "./AccordionRoot.vue";
export { default as AccordionItem } from "./AccordionItem.vue";
export { default as AccordionTrigger } from "./AccordionTrigger.vue";
export { default as AccordionContent } from "./AccordionContent.vue";
@@ -0,0 +1,32 @@
<script lang="ts" setup>
import { X } from "@lucide/vue";
import { cn } from "@mykopkb/core/utils/cn";
import { Slot } from "@/components/ui/slot";
import { alertCloseTrigger } from "@mykopkb/core/styles/alert.styles";
import { inject } from "vue";
const {
class: className,
asChild = false,
...props
} = defineProps<{
class?: string;
asChild?: boolean;
}>();
const context = inject<{
present: boolean;
setPresent: (value: boolean) => void;
} | null>("alertPresent", null);
</script>
<template>
<Slot :class="cn([className, alertCloseTrigger])" v-bind="{ ...props, ...$attrs }"
@click="context?.setPresent(false)">
<slot v-if="asChild" />
<div v-else>
<slot v-if="$slots.default" />
<X v-else />
</div>
</Slot>
</template>
@@ -0,0 +1,23 @@
<script lang="ts" setup>
import { cn } from "@mykopkb/core/utils/cn";
import { Slot } from "@/components/ui/slot";
import { alertDescription } from "@mykopkb/core/styles/alert.styles";
const {
class: className,
asChild = false,
...props
} = defineProps<{
class?: string;
asChild?: boolean;
}>();
</script>
<template>
<Slot :class="cn([className, alertDescription])" v-bind="{ ...props, ...$attrs }">
<slot v-if="asChild" />
<div v-else>
<slot />
</div>
</Slot>
</template>
+36
View File
@@ -0,0 +1,36 @@
<script lang="ts" setup>
import { cn } from "@mykopkb/core/utils/cn";
import {
alertRootVariants,
type AlertRootVariants,
} from "@mykopkb/core/styles/alert.styles";
import { Presence } from "@/components/ui/presence";
import { ref, provide } from "vue";
const {
class: className,
look,
variant,
...rest
} = defineProps<AlertRootVariants & { class?: string }>();
const present = ref(true);
const setPresent = (value: boolean) => {
present.value = value;
};
provide("alertPresent", { present, setPresent });
</script>
<template>
<Presence :class="cn(
alertRootVariants({
look,
variant,
}),
className
)
" v-bind="rest" :present="present">
<slot />
</Presence>
</template>
+23
View File
@@ -0,0 +1,23 @@
<script lang="ts" setup>
import { cn } from "@mykopkb/core/utils/cn";
import { Slot } from "@/components/ui/slot";
import { alertTitle } from "@mykopkb/core/styles/alert.styles";
const {
class: className,
asChild = false,
...props
} = defineProps<{
class?: string;
asChild?: boolean;
}>();
</script>
<template>
<Slot :class="cn([className, alertTitle])" v-bind="{ ...props, ...$attrs }">
<slot v-if="asChild" />
<div v-else>
<slot />
</div>
</Slot>
</template>
+4
View File
@@ -0,0 +1,4 @@
export { default as AlertRoot } from "./AlertRoot.vue";
export { default as AlertTitle } from "./AlertTitle.vue";
export { default as AlertDescription } from "./AlertDescription.vue";
export { default as AlertCloseTrigger } from "./AlertCloseTrigger.vue";
@@ -0,0 +1,27 @@
<script lang="ts" setup>
import type { Api } from "@zag-js/avatar";
import { cn } from "@mykopkb/core/utils/cn";
import { avatarFallback } from "@mykopkb/core/styles/avatar.styles";
import { inject } from "vue";
import { Slot } from "@/components/ui/slot";
const {
class: className,
asChild = false,
...props
} = defineProps<{
asChild?: boolean;
class?: string;
}>();
const api = inject<Api>("avatarApi");
</script>
<template>
<Slot :class="cn(avatarFallback, className)" v-bind="{ ...props, ...$attrs, ...api?.getFallbackProps() }">
<slot v-if="asChild" />
<span v-else>
<slot />
</span>
</Slot>
</template>
@@ -0,0 +1,16 @@
<script lang="ts" setup>
import { cn } from "@mykopkb/core/utils/cn";
import { avatarImage } from "@mykopkb/core/styles/avatar.styles";
import { inject } from "vue";
import type { Api } from "@zag-js/avatar";
const { class: className, ...props } = defineProps<{
class?: string;
}>();
const api = inject<Api>("avatarApi");
</script>
<template>
<img :class="cn(avatarImage, className)" v-bind="{ ...props, ...$attrs, ...api?.getImageProps() }" />
</template>
@@ -0,0 +1,50 @@
<script lang="ts" setup>
import * as avatar from "@zag-js/avatar";
import { provide, computed } from "vue";
import { useMachine, normalizeProps } from "@zag-js/vue";
import type { Props } from "@zag-js/avatar";
import { cn } from "@mykopkb/core/utils/cn";
import {
avatarRootVariants,
type AvatarRootVariants,
} from "@mykopkb/core/styles/avatar.styles";
import { Slot } from "@/components/ui/slot";
const {
class: className,
bordered,
asChild = false,
...props
} = defineProps<
AvatarRootVariants &
Partial<Props> & {
class?: string;
asChild?: boolean;
}
>();
const service = useMachine(avatar.machine, {
...props,
id: crypto.randomUUID(),
});
const api = computed(() => avatar.connect(service, normalizeProps));
provide("avatarApi", api);
</script>
<template>
<Slot :class="cn(
avatarRootVariants({
bordered,
className,
}),
className
)
" v-bind="{ ...props, ...$attrs, ...api.getRootProps() }">
<slot v-if="asChild" />
<div v-else>
<slot />
</div>
</Slot>
</template>
+3
View File
@@ -0,0 +1,3 @@
export { default as AvatarRoot } from "./AvatarRoot.vue";
export { default as AvatarFallback } from "./AvatarFallback.vue";
export { default as AvatarImage } from "./AvatarImage.vue";
+39
View File
@@ -0,0 +1,39 @@
<script lang="ts" setup>
import { cn } from "@mykopkb/core/utils/cn";
import {
TooltipRoot,
TooltipTrigger,
TooltipPositioner,
TooltipContent,
} from "@/components/ui/tooltip";
import {
badgeVariants,
type BadgeVariants,
} from "@mykopkb/core/styles/badge.styles";
const {
class: className,
look,
variant,
content,
...props
} = defineProps<
BadgeVariants & {
class?: string;
content?: string;
}
>();
</script>
<template>
<TooltipRoot :disabled="!content">
<TooltipTrigger as-child>
<span :class="cn(badgeVariants({ look, variant, className }))" v-bind="{ ...props, ...$attrs }">
<slot />
</span>
</TooltipTrigger>
<TooltipPositioner>
<TooltipContent>{{ content }}</TooltipContent>
</TooltipPositioner>
</TooltipRoot>
</template>
+1
View File
@@ -0,0 +1 @@
export { default as Badge } from "./Badge.vue";
+29
View File
@@ -0,0 +1,29 @@
<script lang="ts" setup>
import { cn } from "@mykopkb/core/utils/cn";
import {
boxVariants,
type BoxVariants,
} from "@mykopkb/core/styles/box.styles";
import { Slot } from "@/components/ui/slot";
const {
class: className,
asChild = false,
raised,
...props
} = defineProps<
BoxVariants & {
class?: string;
asChild?: boolean;
}
>();
</script>
<template>
<Slot :class="cn(boxVariants({ raised, className }), className)" v-bind="{ ...props, ...$attrs }">
<slot v-if="asChild" />
<div v-else>
<slot />
</div>
</Slot>
</template>
+1
View File
@@ -0,0 +1 @@
export { default as Box } from "./Box.vue";
@@ -0,0 +1,61 @@
<script lang="ts" setup>
import { cn } from "@mykopkb/core/utils/cn";
import { ChevronRight, Ellipsis } from "@lucide/vue";
import {
MenuRoot,
MenuTrigger,
MenuPositioner,
MenuContent,
MenuItem,
} from "@/components/ui/menu";
import { BreadcrumbItem, BreadcrumbLink, BreadcrumbList } from ".";
const { class: className, ...props } = defineProps<{
class?: string;
items: string[];
}>();
</script>
<template>
<nav aria-label="breadcrumb" data-slot="breadcrumb" v-bind="{ ...props, ...$attrs }" :class="cn(className)">
<BreadcrumbList>
<template v-if="items.length <= 3">
<template v-for="(item, key) in items">
<BreadcrumbItem>
<BreadcrumbLink>{{ item }}</BreadcrumbLink>
</BreadcrumbItem>
<ChevronRight v-if="key < items.length - 1" />
</template>
</template>
<template v-else>
<BreadcrumbItem>
<BreadcrumbLink>{{ items[0] }}</BreadcrumbLink>
</BreadcrumbItem>
<ChevronRight />
<BreadcrumbItem>
<MenuRoot>
<MenuTrigger asChild>
<BreadcrumbLink>
<Ellipsis />
</BreadcrumbLink>
</MenuTrigger>
<MenuPositioner>
<MenuContent>
<MenuItem v-for="(item, itemKey) in items.slice(1, -2)" :value="item" :key="itemKey">
{{ item }}
</MenuItem>
</MenuContent>
</MenuPositioner>
</MenuRoot>
</BreadcrumbItem>
<ChevronRight />
<template v-for="(item, index) in items.slice(-2)">
<BreadcrumbItem>
<BreadcrumbLink>{{ item }}</BreadcrumbLink>
</BreadcrumbItem>
<ChevronRight v-if="index < 1" />
</template>
</template>
</BreadcrumbList>
</nav>
</template>
@@ -0,0 +1,14 @@
<script lang="ts" setup>
import { cn } from "@mykopkb/core/utils/cn";
import { breadcrumbItem } from "@mykopkb/core/styles/breadcrumb.styles";
const { class: className, ...props } = defineProps<{
class?: string;
}>();
</script>
<template>
<li :class="cn(className, breadcrumbItem)" v-bind="{ ...props, ...$attrs }">
<slot />
</li>
</template>
@@ -0,0 +1,14 @@
<script lang="ts" setup>
import { cn } from "@mykopkb/core/utils/cn";
import { breadcrumbLink } from "@mykopkb/core/styles/breadcrumb.styles";
const { class: className, ...props } = defineProps<{
class?: string;
}>();
</script>
<template>
<a :class="cn(className, breadcrumbLink)" v-bind="{ ...props, ...$attrs }">
<slot />
</a>
</template>
@@ -0,0 +1,14 @@
<script lang="ts" setup>
import { cn } from "@mykopkb/core/utils/cn";
import { breadcrumbList } from "@mykopkb/core/styles/breadcrumb.styles";
const { class: className, ...props } = defineProps<{
class?: string;
}>();
</script>
<template>
<ol :class="cn(className, breadcrumbList)" v-bind="{ ...props, ...$attrs }">
<slot />
</ol>
</template>
+4
View File
@@ -0,0 +1,4 @@
export { default as Breadcrumb } from "./Breadcrumb.vue";
export { default as BreadcrumbItem } from "./BreadcrumbItem.vue";
export { default as BreadcrumbLink } from "./BreadcrumbLink.vue";
export { default as BreadcrumbList } from "./BreadcrumbList.vue";
+31
View File
@@ -0,0 +1,31 @@
<script lang="ts" setup>
import { cn } from "@mykopkb/core/utils/cn";
import {
buttonVariants,
type ButtonVariants,
} from "@mykopkb/core/styles/button.styles";
import { Slot } from "@/components/ui/slot";
const {
class: className,
look,
variant,
size,
asChild = false,
...props
} = defineProps<
ButtonVariants & {
class?: string;
asChild?: boolean;
}
>();
</script>
<template>
<Slot v-bind="{ ...props, ...$attrs }" :class="cn(buttonVariants({ look, variant, size, className }), className)">
<slot v-if="asChild" />
<button v-else>
<slot />
</button>
</Slot>
</template>
+1
View File
@@ -0,0 +1 @@
export { default as Button } from "./Button.vue";
@@ -0,0 +1,27 @@
<script lang="ts" setup>
import { Slot } from "@/components/ui/slot";
import { cn } from "@mykopkb/core/utils/cn";
import { carouselControl } from "@mykopkb/core/styles/carousel.styles";
import type { Api } from "@zag-js/carousel";
import { inject } from "vue";
const {
class: className,
asChild = false,
...props
} = defineProps<{
class?: string;
asChild?: boolean;
}>();
const api = inject<Api>("carouselApi");
</script>
<template>
<Slot :class="cn(carouselControl, className)" v-bind="{ ...props, ...$attrs, ...api?.getControlProps() }">
<slot v-if="asChild" />
<div v-else>
<slot />
</div>
</Slot>
</template>
@@ -0,0 +1,25 @@
<script lang="ts" setup>
import { cn } from "@mykopkb/core/utils/cn";
import { carouselIndicator } from "@mykopkb/core/styles/carousel.styles";
import type { Api, IndicatorProps } from "@zag-js/carousel";
import { inject } from "vue";
const {
class: className,
asChild = false,
index,
...props
} = defineProps<
{
class?: string;
asChild?: boolean;
} & IndicatorProps
>();
const api = inject<Api>("carouselApi");
</script>
<template>
<button :class="cn(carouselIndicator, className)"
v-bind="{ ...props, ...$attrs, ...api?.getIndicatorProps({ index }) }" />
</template>
@@ -0,0 +1,28 @@
<script lang="ts" setup>
import { Slot } from "@/components/ui/slot";
import { cn } from "@mykopkb/core/utils/cn";
import { carouselIndicatorGroup } from "@mykopkb/core/styles/carousel.styles";
import type { Api } from "@zag-js/carousel";
import { inject } from "vue";
const {
class: className,
asChild = false,
...props
} = defineProps<{
class?: string;
asChild?: boolean;
}>();
const api = inject<Api>("carouselApi");
</script>
<template>
<Slot :class="cn(carouselIndicatorGroup, className)"
v-bind="{ ...props, ...$attrs, ...api?.getIndicatorGroupProps() }">
<slot v-if="asChild" />
<div v-else>
<slot />
</div>
</Slot>
</template>
@@ -0,0 +1,31 @@
<script lang="ts" setup>
import { Slot } from "@/components/ui/slot";
import { cn } from "@mykopkb/core/utils/cn";
import { Box } from "@/components/ui/box";
import { carouselItem } from "@mykopkb/core/styles/carousel.styles";
import type { Api, ItemProps } from "@zag-js/carousel";
import { inject } from "vue";
const {
class: className,
asChild = false,
index,
...props
} = defineProps<
{
class?: string;
asChild?: boolean;
} & ItemProps
>();
const api = inject<Api>("carouselApi");
</script>
<template>
<Slot v-bind="{ ...props, ...$attrs, ...api?.getItemProps({ index }) }">
<slot v-if="asChild" />
<Box v-else :class="cn(carouselItem, className)">
<slot />
</Box>
</Slot>
</template>
@@ -0,0 +1,27 @@
<script lang="ts" setup>
import { Slot } from "@/components/ui/slot";
import { cn } from "@mykopkb/core/utils/cn";
import { carouselItemGroup } from "@mykopkb/core/styles/carousel.styles";
import type { Api } from "@zag-js/carousel";
import { inject } from "vue";
const {
class: className,
asChild = false,
...props
} = defineProps<{
class?: string;
asChild?: boolean;
}>();
const api = inject<Api>("carouselApi");
</script>
<template>
<Slot :class="cn(carouselItemGroup, className)" v-bind="{ ...props, ...$attrs, ...api?.getItemGroupProps() }">
<slot v-if="asChild" />
<div v-else>
<slot />
</div>
</Slot>
</template>
@@ -0,0 +1,30 @@
<script lang="ts" setup>
import { Slot } from "@/components/ui/slot";
import { cn } from "@mykopkb/core/utils/cn";
import { Button } from "@/components/ui/button";
import { ArrowRight } from "@lucide/vue";
import { carouselNextTrigger } from "@mykopkb/core/styles/carousel.styles";
import type { Api } from "@zag-js/carousel";
import { inject } from "vue";
const {
class: className,
asChild = false,
...props
} = defineProps<{
class?: string;
asChild?: boolean;
}>();
const api = inject<Api>("carouselApi");
</script>
<template>
<Slot v-bind="{ ...props, ...$attrs, ...api?.getNextTriggerProps() }">
<slot v-if="asChild" />
<Button variant="ghost" v-else :class="cn(carouselNextTrigger, className)">
<slot v-if="$slots.default" />
<ArrowRight v-else />
</Button>
</Slot>
</template>
@@ -0,0 +1,30 @@
<script lang="ts" setup>
import { Slot } from "@/components/ui/slot";
import { cn } from "@mykopkb/core/utils/cn";
import { Button } from "@/components/ui/button";
import { ArrowLeft } from "@lucide/vue";
import { carouselPrevTrigger } from "@mykopkb/core/styles/carousel.styles";
import type { Api } from "@zag-js/carousel";
import { inject } from "vue";
const {
class: className,
asChild = false,
...props
} = defineProps<{
class?: string;
asChild?: boolean;
}>();
const api = inject<Api>("carouselApi");
</script>
<template>
<Slot v-bind="{ ...props, ...$attrs, ...api?.getPrevTriggerProps() }">
<slot v-if="asChild" />
<Button variant="ghost" v-else :class="cn(carouselPrevTrigger, className)">
<slot v-if="$slots.default" />
<ArrowLeft v-else />
</Button>
</Slot>
</template>
@@ -0,0 +1,41 @@
<script lang="ts" setup>
import * as carousel from "@zag-js/carousel";
import { useMachine, normalizeProps } from "@zag-js/vue";
import type { Props } from "@zag-js/carousel";
import { Slot } from "@/components/ui/slot";
import { cn } from "@mykopkb/core/utils/cn";
import { carouselRoot } from "@mykopkb/core/styles/carousel.styles";
import { computed, provide } from "vue";
const {
class: className,
defaultPage,
slideCount,
spacing = "2rem",
allowMouseDrag = true,
asChild = false,
...props
} = defineProps<Partial<Props> & { asChild?: boolean; class?: string }>();
const service = useMachine(carousel.machine, {
defaultPage,
slideCount,
spacing,
allowMouseDrag,
...props,
id: crypto.randomUUID(),
});
const api = computed(() => carousel.connect(service, normalizeProps));
provide("carouselApi", api);
</script>
<template>
<Slot :class="cn(carouselRoot, className)" v-bind="{ ...props, ...$attrs, ...api.getRootProps() }">
<slot v-if="asChild" />
<div v-else>
<slot />
</div>
</Slot>
</template>
+8
View File
@@ -0,0 +1,8 @@
export { default as CarouselRoot } from "./CarouselRoot.vue";
export { default as CarouselControl } from "./CarouselControl.vue";
export { default as CarouselPrevTrigger } from "./CarouselPrevTrigger.vue";
export { default as CarouselNextTrigger } from "./CarouselNextTrigger.vue";
export { default as CarouselIndicatorGroup } from "./CarouselIndicatorGroup.vue";
export { default as CarouselIndicator } from "./CarouselIndicator.vue";
export { default as CarouselItemGroup } from "./CarouselItemGroup.vue";
export { default as CarouselItem } from "./CarouselItem.vue";
+36
View File
@@ -0,0 +1,36 @@
<script lang="ts" setup generic="TType extends ChartType">
import ChartJs from "chart.js/auto";
import { ref, onMounted } from "vue";
import { cn } from "@mykopkb/core/utils/cn";
import { chart } from "@mykopkb/core/styles/chart.styles";
import type { ChartType, ChartConfiguration } from "chart.js";
const {
class: className,
config,
getRef,
...props
} = defineProps<{
class?: string;
config: ChartConfiguration<TType>;
getRef?: (chart: ChartJs<TType>) => void;
}>();
const chartRef = ref<
| (HTMLCanvasElement & {
instance?: ChartJs<TType>;
})
| null
>(null);
onMounted(() => {
if (chartRef.value && !chartRef.value.instance) {
chartRef.value.instance = new ChartJs(chartRef.value, config);
getRef?.(chartRef.value.instance);
}
});
</script>
<template>
<canvas :class="cn(chart, className)" ref="chartRef" v-bind="props" />
</template>
+2
View File
@@ -0,0 +1,2 @@
export { default as Chart } from "./Chart.vue";
export { getColor } from "./utils.ts";
+11
View File
@@ -0,0 +1,11 @@
export function getColor(name: string, opacity = 1) {
const color = getComputedStyle(document.documentElement)
.getPropertyValue(name)
.trim();
if (opacity < 1) {
return `color-mix(in oklch, ${color} ${opacity * 100}%, transparent ${
100 - opacity * 100
}%)`;
}
return color;
}
@@ -0,0 +1,31 @@
<script lang="ts" setup>
import { Slot } from "@/components/ui/slot";
import { CheckIcon } from "@lucide/vue";
import { cn } from "@mykopkb/core/utils/cn";
import { checkboxControl } from "@mykopkb/core/styles/checkbox.styles";
import { CheckboxIndicator } from ".";
import type { Api } from "@zag-js/checkbox";
import { inject } from "vue";
const {
class: className,
asChild = false,
...props
} = defineProps<{
asChild?: boolean;
class?: string;
}>();
const api = inject<Api>("checkboxApi");
</script>
<template>
<Slot :class="cn(checkboxControl, className)" v-bind="{ ...props, ...$attrs, ...api?.getControlProps() }">
<slot v-if="asChild" />
<div v-else>
<CheckboxIndicator>
<CheckIcon />
</CheckboxIndicator>
</div>
</Slot>
</template>
@@ -0,0 +1,16 @@
<script lang="ts" setup>
import type { Api } from "@zag-js/checkbox";
import { cn } from "@mykopkb/core/utils/cn";
import { checkboxHiddenInput } from "@mykopkb/core/styles/checkbox.styles";
import { inject } from "vue";
const { class: className, ...props } = defineProps<{
class?: string;
}>();
const api = inject<Api>("checkboxApi");
</script>
<template>
<input :class="cn(checkboxHiddenInput, className)" v-bind="{ ...props, ...$attrs, ...api?.getHiddenInputProps() }" />
</template>
@@ -0,0 +1,27 @@
<script lang="ts" setup>
import { Slot } from "@/components/ui/slot";
import type { Api } from "@zag-js/checkbox";
import { cn } from "@mykopkb/core/utils/cn";
import { checkboxIndicator } from "@mykopkb/core/styles/checkbox.styles";
import { inject } from "vue";
const {
class: className,
asChild = false,
...props
} = defineProps<{
asChild?: boolean;
class?: string;
}>();
const api = inject<Api>("checkboxApi");
</script>
<template>
<Slot :class="cn(checkboxIndicator, className)" v-bind="{ ...props, ...$attrs, ...api?.getIndicatorProps() }">
<slot v-if="asChild" />
<div v-else>
<slot />
</div>
</Slot>
</template>
@@ -0,0 +1,28 @@
<script lang="ts" setup>
import { Slot } from "@/components/ui/slot";
import { cn } from "@mykopkb/core/utils/cn";
import type { Api } from "@zag-js/checkbox";
import { checkboxLabel } from "@mykopkb/core/styles/checkbox.styles";
import { label } from "@mykopkb/core/styles/label.styles";
import { inject } from "vue";
const {
class: className,
asChild = false,
...props
} = defineProps<{
asChild?: boolean;
class?: string;
}>();
const api = inject<Api>("checkboxApi");
</script>
<template>
<Slot :class="cn([label, checkboxLabel, className])" v-bind="{ ...props, ...$attrs, ...api?.getLabelProps() }">
<slot v-if="asChild" />
<span v-else>
<slot />
</span>
</Slot>
</template>
@@ -0,0 +1,35 @@
<script lang="ts" setup>
import { cn } from "@mykopkb/core/utils/cn";
import { checkboxRoot } from "@mykopkb/core/styles/checkbox.styles";
import * as checkbox from "@zag-js/checkbox";
import { useMachine, normalizeProps } from "@zag-js/vue";
import type { Props } from "@zag-js/checkbox";
import { CheckboxHiddenInput } from ".";
import { computed, provide } from "vue";
const {
class: className,
checked = undefined,
...props
} = defineProps<Partial<Props> & { class?: string }>();
const service = useMachine(
checkbox.machine,
computed(() => ({
...props,
checked,
id: crypto.randomUUID(),
}))
);
const api = computed(() => checkbox.connect(service, normalizeProps));
provide("checkboxApi", api);
</script>
<template>
<label :class="cn(checkboxRoot, className)" v-bind="{ ...props, ...$attrs, ...api.getRootProps() }">
<slot />
<CheckboxHiddenInput />
</label>
</template>
+5
View File
@@ -0,0 +1,5 @@
export { default as CheckboxRoot } from "./CheckboxRoot.vue";
export { default as CheckboxLabel } from "./CheckboxLabel.vue";
export { default as CheckboxControl } from "./CheckboxControl.vue";
export { default as CheckboxIndicator } from "./CheckboxIndicator.vue";
export { default as CheckboxHiddenInput } from "./CheckboxHiddenInput.vue";
@@ -0,0 +1,27 @@
<script lang="ts" setup>
import { Slot } from "@/components/ui/slot";
import { cn } from "@mykopkb/core/utils/cn";
import { comboboxClearTrigger } from "@mykopkb/core/styles/combobox.styles";
import type { Api } from "@zag-js/combobox";
import { inject } from "vue";
const {
class: className,
asChild = false,
...props
} = defineProps<{
class?: string;
asChild?: boolean;
}>();
const api = inject<Api>("comboboxApi");
</script>
<template>
<Slot v-bind="{ ...props, ...$attrs, ...api?.getClearTriggerProps() }">
<slot v-if="asChild" />
<span :class="cn(comboboxClearTrigger, className)" v-else>
<slot />
</span>
</Slot>
</template>
@@ -0,0 +1,35 @@
<script lang="ts" setup>
import { cn } from "@mykopkb/core/utils/cn";
import { comboboxContent } from "@mykopkb/core/styles/combobox.styles";
import { Box } from "@/components/ui/box";
import { ComboboxPositioner } from ".";
import type { Api } from "@zag-js/combobox";
import { inject } from "vue";
import { Slot } from "@/components/ui/slot";
const {
class: className,
asChild = false,
...props
} = defineProps<{
class?: string;
asChild?: boolean;
}>();
const api = inject<Api>("comboboxApi");
</script>
<template>
<Teleport to="body">
<ComboboxPositioner>
<Slot v-bind="{ ...props, ...$attrs, ...api?.getContentProps() }">
<slot v-if="asChild" />
<Box v-else raised="single" :class="cn(comboboxContent, className)">
<div>
<slot />
</div>
</Box>
</Slot>
</ComboboxPositioner>
</Teleport>
</template>
@@ -0,0 +1,27 @@
<script lang="ts" setup>
import type { Api } from "@zag-js/combobox";
import { inject } from "vue";
import { Slot } from "@/components/ui/slot";
import { cn } from "@mykopkb/core/utils/cn";
import { comboboxControl } from "@mykopkb/core/styles/combobox.styles";
const {
class: className,
asChild = false,
...props
} = defineProps<{
class?: string;
asChild?: boolean;
}>();
const api = inject<Api>("comboboxApi");
</script>
<template>
<Slot :class="cn(comboboxControl, className)" v-bind="{ ...props, ...$attrs, ...api?.getLabelProps() }">
<slot v-if="asChild" />
<div v-else>
<slot />
</div>
</Slot>
</template>
@@ -0,0 +1,17 @@
<script lang="ts" setup>
import { cn } from "@mykopkb/core/utils/cn";
import { comboboxInput } from "@mykopkb/core/styles/combobox.styles";
import { Input } from "@/components/ui/input";
import type { Api } from "@zag-js/combobox";
import { inject } from "vue";
const { class: className, ...props } = defineProps<{
class?: string;
}>();
const api = inject<Api>("comboboxApi");
</script>
<template>
<Input :class="cn(comboboxInput, className)" v-bind="{ ...props, ...$attrs, ...api?.getInputProps() }" />
</template>
@@ -0,0 +1,29 @@
<script lang="ts" setup>
import { Slot } from "@/components/ui/slot";
import { cn } from "@mykopkb/core/utils/cn";
import { comboboxItem } from "@mykopkb/core/styles/combobox.styles";
import type { ItemProps } from "@zag-js/combobox";
import type { Api } from "@zag-js/combobox";
import { provide, inject } from "vue";
import { ComboboxItemIndicator } from ".";
const {
class: className,
asChild = false,
...props
} = defineProps<ItemProps & { class?: string; asChild?: boolean }>();
const api = inject<Api>("comboboxApi");
provide("comboboxItem", props);
</script>
<template>
<Slot :class="cn(comboboxItem, className)" v-bind="{ ...props, ...$attrs, ...api?.getItemProps(props) }">
<slot v-if="asChild" />
<div v-else>
<slot />
<ComboboxItemIndicator />
</div>
</Slot>
</template>
@@ -0,0 +1,31 @@
<script lang="ts" setup>
import { Slot } from "@/components/ui/slot";
import { cn } from "@mykopkb/core/utils/cn";
import { comboboxItemGroup } from "@mykopkb/core/styles/combobox.styles";
import type { Api } from "@zag-js/combobox";
import { provide, inject } from "vue";
const {
class: className,
asChild = false,
...props
} = defineProps<{
class?: string;
asChild?: boolean;
}>();
const api = inject<Api>("comboboxApi");
const itemGroupId = { id: crypto.randomUUID() };
provide("comboboxItemGroupId", itemGroupId);
</script>
<template>
<Slot :class="cn(comboboxItemGroup, className)"
v-bind="{ ...props, ...$attrs, ...api?.getItemGroupProps(itemGroupId) }">
<slot v-if="asChild" />
<div v-else>
<slot />
</div>
</Slot>
</template>
@@ -0,0 +1,32 @@
<script lang="ts" setup>
import { Slot } from "@/components/ui/slot";
import { cn } from "@mykopkb/core/utils/cn";
import { comboboxItemGroupLabel } from "@mykopkb/core/styles/combobox.styles";
import type { Api, ItemGroupProps } from "@zag-js/combobox";
import { inject } from "vue";
const {
class: className,
asChild = false,
...props
} = defineProps<{
class?: string;
asChild?: boolean;
}>();
const api = inject<Api>("comboboxApi");
const itemGroupId = inject<ItemGroupProps>("comboboxItemGroupId");
</script>
<template>
<Slot :class="cn(comboboxItemGroupLabel, className)" v-bind="{
...props, ...$attrs, ...api?.getItemGroupLabelProps({
htmlFor: itemGroupId?.id!,
})
}">
<slot v-if="asChild" />
<label v-else>
<slot />
</label>
</Slot>
</template>
@@ -0,0 +1,31 @@
<script lang="ts" setup>
import { Slot } from "@/components/ui/slot";
import { cn } from "@mykopkb/core/utils/cn";
import { comboboxItemIndicator } from "@mykopkb/core/styles/combobox.styles";
import { Check } from "@lucide/vue";
import type { Api, ItemProps } from "@zag-js/combobox";
import { inject } from "vue";
const {
class: className,
asChild = false,
...props
} = defineProps<{
class?: string;
asChild?: boolean;
}>();
const api = inject<Api>("comboboxApi");
const item = inject<ItemProps>("comboboxItem");
</script>
<template>
<Slot :class="cn(comboboxItemIndicator, className)"
v-bind="{ ...props, ...$attrs, ...api?.getItemIndicatorProps(item!) }">
<slot v-if="asChild" />
<div v-else>
<slot v-if="$slots.default" />
<Check v-else class="size-3.5" />
</div>
</Slot>
</template>
@@ -0,0 +1,28 @@
<script lang="ts" setup>
import { Slot } from "@/components/ui/slot";
import { cn } from "@mykopkb/core/utils/cn";
import { comboboxItemText } from "@mykopkb/core/styles/combobox.styles";
import type { Api, ItemProps } from "@zag-js/combobox";
import { inject } from "vue";
const {
class: className,
asChild = false,
...props
} = defineProps<{
class?: string;
asChild?: boolean;
}>();
const api = inject<Api>("comboboxApi");
const item = inject<ItemProps>("comboboxItem");
</script>
<template>
<Slot :class="cn(comboboxItemText, className)" v-bind="{ ...props, ...$attrs, ...api?.getItemTextProps(item!) }">
<slot v-if="asChild" />
<div v-else>
<slot />
</div>
</Slot>
</template>
@@ -0,0 +1,28 @@
<script lang="ts" setup>
import { Slot } from "@/components/ui/slot";
import { cn } from "@mykopkb/core/utils/cn";
import { comboboxLabel } from "@mykopkb/core/styles/combobox.styles";
import { Label } from "@/components/ui/label";
import type { Api } from "@zag-js/combobox";
import { inject } from "vue";
const {
class: className,
asChild = false,
...props
} = defineProps<{
class?: string;
asChild?: boolean;
}>();
const api = inject<Api>("comboboxApi");
</script>
<template>
<Slot v-bind="{ ...props, ...$attrs, ...api?.getLabelProps() }">
<slot v-if="asChild" />
<Label v-else :class="cn(comboboxLabel, className)">
<slot />
</Label>
</Slot>
</template>
@@ -0,0 +1,27 @@
<script lang="ts" setup>
import { cn } from "@mykopkb/core/utils/cn";
import { comboboxPositioner } from "@mykopkb/core/styles/combobox.styles";
import type { Api } from "@zag-js/combobox";
import { inject } from "vue";
import { Slot } from "@/components/ui/slot";
const {
class: className,
asChild = false,
...props
} = defineProps<{
class?: string;
asChild?: boolean;
}>();
const api = inject<Api>("comboboxApi");
</script>
<template>
<Slot :class="cn(comboboxPositioner, className)" v-bind="{ ...props, ...$attrs, ...api?.getPositionerProps() }">
<slot v-if="asChild" />
<div v-else>
<slot />
</div>
</Slot>
</template>
@@ -0,0 +1,47 @@
<script lang="ts" setup>
import { cn } from "@mykopkb/core/utils/cn";
import { comboboxRoot } from "@mykopkb/core/styles/combobox.styles";
import { provide, computed } from "vue";
import * as combobox from "@zag-js/combobox";
import { useMachine, normalizeProps } from "@zag-js/vue";
import type { Props } from "@zag-js/combobox";
import { Slot } from "@/components/ui/slot";
const {
class: className,
multiple = false,
selectionBehavior = "clear",
value,
asChild = false,
onOpenChange,
onInputValueChange,
onValueChange,
open = undefined,
...props
} = defineProps<Partial<Props> & { asChild?: boolean; class?: string }>();
const service = useMachine(combobox.machine, {
multiple,
selectionBehavior,
onOpenChange,
onInputValueChange,
onValueChange,
open,
...props,
id: crypto.randomUUID(),
});
const api = computed(() => combobox.connect(service, normalizeProps));
provide("comboboxApi", api);
</script>
<template>
<Slot :class="cn(comboboxRoot, className)" :data-multiple="multiple"
v-bind="{ ...props, ...$attrs, ...api.getRootProps() }">
<slot v-if="asChild" />
<div v-else>
<slot />
</div>
</Slot>
</template>
@@ -0,0 +1,32 @@
<script lang="ts" setup>
import { cn } from "@mykopkb/core/utils/cn";
import { Button } from "@/components/ui/button";
import { ChevronsUpDownIcon } from "@lucide/vue";
import { comboboxTrigger } from "@mykopkb/core/styles/combobox.styles";
import { ComboboxClearTrigger } from ".";
import type { Api } from "@zag-js/combobox";
import { inject } from "vue";
import { Slot } from "@/components/ui/slot";
const {
class: className,
asChild = false,
...props
} = defineProps<{
class?: string;
asChild?: boolean;
}>();
const api = inject<Api>("comboboxApi");
</script>
<template>
<Slot v-bind="{ ...props, ...$attrs, ...api?.getTriggerProps() }">
<Button variant="ghost" v-if="!asChild" :class="cn(comboboxTrigger, className)">
<div>{{ api?.valueAsString || "Select Options..." }}</div>
<ComboboxClearTrigger>Clear</ComboboxClearTrigger>
<ChevronsUpDownIcon />
</Button>
<slot v-else />
</Slot>
</template>
+13
View File
@@ -0,0 +1,13 @@
export { default as ComboboxRoot } from "./ComboboxRoot.vue";
export { default as ComboboxLabel } from "./ComboboxLabel.vue";
export { default as ComboboxControl } from "./ComboboxControl.vue";
export { default as ComboboxInput } from "./ComboboxInput.vue";
export { default as ComboboxTrigger } from "./ComboboxTrigger.vue";
export { default as ComboboxClearTrigger } from "./ComboboxClearTrigger.vue";
export { default as ComboboxPositioner } from "./ComboboxPositioner.vue";
export { default as ComboboxContent } from "./ComboboxContent.vue";
export { default as ComboboxItemGroup } from "./ComboboxItemGroup.vue";
export { default as ComboboxItemGroupLabel } from "./ComboboxItemGroupLabel.vue";
export { default as ComboboxItem } from "./ComboboxItem.vue";
export { default as ComboboxItemText } from "./ComboboxItemText.vue";
export { default as ComboboxItemIndicator } from "./ComboboxItemIndicator.vue";

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