DONE: add bos signature on letter, remove unused files or templates files, update logo to mykopkb, remove theme switcher use gradient colour, add external system module on frontend
This commit is contained in:
@@ -3,17 +3,22 @@
|
||||
namespace Modules\ActivityLog\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Http\Request;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
use App\Services\ActivityLogger;
|
||||
|
||||
class ActivityLogController extends Controller
|
||||
{
|
||||
use AuthorizesRequests;
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$this->authorize('viewAny', Activity::class);
|
||||
|
||||
// ActivityLogger::logView(new Activity, 'Viewed activity logs');
|
||||
|
||||
$perPage = $request->get('per_page', 10);
|
||||
@@ -27,8 +32,10 @@ class ActivityLogController extends Controller
|
||||
$q->where('description', 'ILIKE', "%{$search}%")
|
||||
->orWhere('subject_type', 'ILIKE', "%{$search}%")
|
||||
->orWhere('event', 'ILIKE', "%{$search}%")
|
||||
->orWhere('causer.name', 'ILIKE', "%{$search}%")
|
||||
->orWhere('causer.email', 'ILIKE', "%{$search}%");
|
||||
->orWhereHas('causer', function ($causerQuery) use ($search) {
|
||||
$causerQuery->where('name', 'ILIKE', "%{$search}%")
|
||||
->orWhere('email', 'ILIKE', "%{$search}%");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
namespace Modules\ActivityLog\Providers;
|
||||
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Modules\ActivityLog\Policies\ActivityLogPolicy;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
use Nwidart\Modules\Traits\PathNamespace;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
@@ -21,6 +24,8 @@ class ActivityLogServiceProvider extends ServiceProvider
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
Gate::policy(Activity::class, ActivityLogPolicy::class);
|
||||
|
||||
$this->registerCommands();
|
||||
$this->registerCommandSchedules();
|
||||
$this->registerTranslations();
|
||||
|
||||
@@ -66,7 +66,11 @@
|
||||
@section('letter-signature')
|
||||
<div class="signature-header">Dengan hormatnya,</div>
|
||||
<div class="signature-company">KOPERASI PERMODALAN KELANTAN BERHAD</div>
|
||||
<div class="signature-space"></div>
|
||||
<div class="signature-space">
|
||||
@if (! empty($signatureUrl))
|
||||
<img src="{{ $signatureUrl }}" alt="" class="signature-space-img">
|
||||
@endif
|
||||
</div>
|
||||
<div class="signatory-name">MUHAMMAD NAFIS BIN ZAINUDIN</div>
|
||||
<div class="signatory-title">Pengurus Besar</div>
|
||||
<div class="signatory-title">b/p Setiausaha</div>
|
||||
|
||||
@@ -4,5 +4,5 @@ return [
|
||||
'name' => 'User',
|
||||
'public_profile_token_ttl_days' => (int) env('PUBLIC_PROFILE_TOKEN_TTL_DAYS', 90),
|
||||
'frontend_url' => rtrim(env('FRONTEND_URL', env('APP_URL', 'http://localhost')), '/'),
|
||||
'digital_card_logo_path' => env('DIGITAL_CARD_LOGO_PATH', public_path('images/logo/logo-kopkb.svg')),
|
||||
'digital_card_logo_path' => env('DIGITAL_CARD_LOGO_PATH', public_path('images/logo/logo.svg')),
|
||||
];
|
||||
|
||||
@@ -56,11 +56,14 @@ class LetterService
|
||||
public function prepareViewData(array $overrides = []): array
|
||||
{
|
||||
$logoPath = $overrides['logoPath'] ?? config('mail.logo_path');
|
||||
$signaturePath = $overrides['signaturePath'] ?? config('letter.signature_path');
|
||||
|
||||
return array_merge([
|
||||
'letterLayout' => LetterLayout::resolve(),
|
||||
'logoPath' => $logoPath,
|
||||
'logoUrl' => $this->resolveLogoDataUri($logoPath),
|
||||
'logoUrl' => $this->resolveImageDataUri($logoPath),
|
||||
'signaturePath' => $signaturePath,
|
||||
'signatureUrl' => $this->resolveImageDataUri($signaturePath),
|
||||
'organizationAddress' => $overrides['organizationAddress'] ?? null,
|
||||
'organizationContact' => $overrides['organizationContact'] ?? null,
|
||||
], $overrides);
|
||||
@@ -97,14 +100,14 @@ class LetterService
|
||||
return $browsershot;
|
||||
}
|
||||
|
||||
protected function resolveLogoDataUri(?string $logoPath): ?string
|
||||
protected function resolveImageDataUri(?string $path): ?string
|
||||
{
|
||||
if (empty($logoPath) || ! file_exists($logoPath)) {
|
||||
if (empty($path) || ! file_exists($path)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$mime = mime_content_type($logoPath) ?: 'image/svg+xml';
|
||||
$mime = mime_content_type($path) ?: 'image/svg+xml';
|
||||
|
||||
return 'data:'.$mime.';base64,'.base64_encode(file_get_contents($logoPath));
|
||||
return 'data:'.$mime.';base64,'.base64_encode(file_get_contents($path));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,4 +82,12 @@ return [
|
||||
'black' => '#000000',
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Assets
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
'signature_path' => resource_path('assets/signatures/signature.svg'),
|
||||
|
||||
];
|
||||
|
||||
+1
-1
@@ -124,6 +124,6 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'logo_path' => public_path('images/logo/logo-kopkb-letterhead.svg'),
|
||||
'logo_path' => public_path('images/logo/logo.svg'),
|
||||
|
||||
];
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 275 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 202 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 7.8 KiB |
@@ -239,6 +239,12 @@
|
||||
height: var(--letter-signature-space-height);
|
||||
}
|
||||
|
||||
.signature-space-img {
|
||||
height: var(--letter-signature-space-height);
|
||||
width: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.signatory-name {
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
|
||||
@@ -27,19 +27,19 @@
|
||||
</p>
|
||||
<p style="line-height: 1;">
|
||||
2. Sukacita dimaklumkan bahawa, permohonan tuan untuk menjadi anggota Koperasi
|
||||
Permodalan Kelantan Berhad(KoPKB) telah <strong>{{ membership_applications.board_result }}</strong> dalam {{ User manual fills before generate }}
|
||||
Permodalan Kelantan Berhad(KoPKB) telah <strong></strong> dalam
|
||||
</p>
|
||||
<p style="line-height: 1;">
|
||||
3. Pihak Koperasi akan membuat potongan gaji melalui majikan tuan sebagaiman ketetapan berikut:-
|
||||
<ul style="line-height: 1;">
|
||||
<li>Saham</li>
|
||||
<ul style="line-height: 1;">
|
||||
<li>Potongan sebanyak RM 84.00 {{(6 months auto calculate)}}</li>
|
||||
<li>Potongan sebanyak RM {{ membership_application_applicants.stock_monthly_contribution }} sehingga Syer Maksima</li>
|
||||
<li>Potongan sebanyak RM 84.00 </li>
|
||||
<li>Potongan sebanyak RM sehingga Syer Maksima</li>
|
||||
</ul>
|
||||
<li>Yuran</li>
|
||||
<ul style="line-height: 1;">
|
||||
<li>Potongan sebanyak RM {{ membership_application_applicants.fee_monthly_contribution }} setiap bulan</li>
|
||||
<li>Potongan sebanyak RM setiap bulan</li>
|
||||
</ul>
|
||||
<li>Fi Masuk</li>
|
||||
<ul style="line-height: 1;">
|
||||
@@ -65,7 +65,11 @@
|
||||
@section('letter-signature')
|
||||
<div class="signature-header">Dengan hormatnya,</div>
|
||||
<div class="signature-company">KOPERASI PERMODALAN KELANTAN BERHAD</div>
|
||||
<div class="signature-space"></div>
|
||||
<div class="signature-space">
|
||||
@if (! empty($signatureUrl))
|
||||
<img src="{{ $signatureUrl }}" alt="" class="signature-space-img">
|
||||
@endif
|
||||
</div>
|
||||
<div class="signatory-name">MUHAMMAD NAFIS BIN ZAINUDDIN</div>
|
||||
<div class="signatory-title">Pengurus Besar</div>
|
||||
<div class="signatory-title">b/p Setiausaha</div>
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+648
-417
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 275 KiB After Width: | Height: | Size: 202 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 33 KiB |
@@ -3,6 +3,7 @@ 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 { applyColorSchemeClass, applyDarkModeClass } from '@/utils/applyAppearance'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const themeSwitcherSheet = ref(false)
|
||||
@@ -10,30 +11,19 @@ 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()
|
||||
applyColorSchemeClass()
|
||||
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()
|
||||
applyDarkModeClass()
|
||||
setThemeSwitcherSheet(false)
|
||||
}
|
||||
setDarkModeClass()
|
||||
|
||||
const colorSchemes: Array<ColorSchemes> = ['default', '1', '2', '3', '4', '5']
|
||||
|
||||
|
||||
@@ -100,6 +100,10 @@ const props = withDefaults(defineProps<{
|
||||
currentSort?: SortConfig[];
|
||||
exportable?: boolean;
|
||||
exportFileName?: string;
|
||||
/** When true, table body scrolls inside a max-height box instead of growing the page. */
|
||||
scrollable?: boolean;
|
||||
/** Max height of the scrollable table area (CSS value). */
|
||||
maxHeight?: string;
|
||||
}>(), {
|
||||
items: () => [],
|
||||
headers: () => [],
|
||||
@@ -110,7 +114,9 @@ const props = withDefaults(defineProps<{
|
||||
showPagination: false,
|
||||
currentSort: () => [],
|
||||
exportable: false,
|
||||
exportFileName: 'table-data'
|
||||
exportFileName: 'table-data',
|
||||
scrollable: true,
|
||||
maxHeight: 'min(70dvh, 640px)',
|
||||
});
|
||||
|
||||
// Define model props
|
||||
@@ -461,6 +467,14 @@ const slots = useSlots();
|
||||
|
||||
const showTableToolbar = computed(() => props.exportable || !!slots.toolbar);
|
||||
|
||||
const scrollContainerStyle = computed(() => {
|
||||
if (!props.scrollable) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return { maxHeight: props.maxHeight };
|
||||
});
|
||||
|
||||
const recordSummary = computed(() => {
|
||||
if (!props.pagination) return null;
|
||||
|
||||
@@ -733,12 +747,20 @@ watch(sortBy, (newSort) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative px-4 pt-3" :class="loading && 'pointer-events-none opacity-50'">
|
||||
<Table variant="boxed" class="custom-data-table border-separate border-spacing-y-2.5">
|
||||
<div
|
||||
class="relative px-4 pt-3"
|
||||
:class="[
|
||||
loading && 'pointer-events-none opacity-50',
|
||||
scrollable && 'data-table-scroll-area overflow-auto',
|
||||
]"
|
||||
:style="scrollContainerStyle"
|
||||
>
|
||||
<Table variant="boxed" :class="cn('custom-data-table border-separate border-spacing-y-2.5', !scrollable && 'overflow-hidden')">
|
||||
<TableHeader>
|
||||
<TableRow class="border-0 hover:bg-transparent">
|
||||
<TableHead v-for="header in sortedHeaders" :key="header.key" :class="cn(
|
||||
'group/head relative bg-primary text-primary-foreground font-semibold border-y border-primary/20 first:rounded-tl-xl first:border-s last:rounded-tr-xl last:border-e',
|
||||
scrollable && 'sticky top-0 z-10',
|
||||
headerAlignClass(header.align),
|
||||
)" :style="{ width: `${header.width}px`, minWidth: `${header.width}px` }">
|
||||
<div v-if="header.sortable" class="flex items-center gap-2 cursor-pointer select-none" role="button"
|
||||
@@ -864,8 +886,8 @@ watch(sortBy, (newSort) => {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.custom-data-table {
|
||||
overflow: hidden;
|
||||
.data-table-scroll-area > :deep(div) {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.sortable-indicator {
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { Box } from "@/components/ui/box";
|
||||
</script>
|
||||
<template>
|
||||
<Box class="w-full xl:w-1/2">
|
||||
<div class="text-xl font-medium capitalize border-b border-foreground/15 pb-5">{{ $route.name }}</div>
|
||||
<div class="mt-16 flex flex-col gap-10">
|
||||
<RouterView />
|
||||
</div>
|
||||
</Box>
|
||||
</template>
|
||||
@@ -1,639 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
AlertRoot,
|
||||
AlertTitle,
|
||||
AlertDescription,
|
||||
AlertCloseTrigger,
|
||||
} from "@/components/ui/alert";
|
||||
import {
|
||||
Preview,
|
||||
SectionTitle,
|
||||
SectionContent,
|
||||
InstallPackage,
|
||||
PreviewCode,
|
||||
} from "@/components/docs";
|
||||
import { Compass } from "@lucide/vue";
|
||||
import { Box } from "@/components/ui/box";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="preview" class="-mt-20">
|
||||
<Preview>
|
||||
<template #preview>
|
||||
<AlertRoot variant="primary">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertDescription>
|
||||
This is an alert with icon, title and description.
|
||||
</AlertDescription>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<AlertRoot variant="primary">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertDescription>
|
||||
This is an alert with icon, title and description.
|
||||
</AlertDescription>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
</div>
|
||||
<div id="installation">
|
||||
<SectionTitle>Installation</SectionTitle>
|
||||
<SectionContent>Install the following dependencies:</SectionContent>
|
||||
<InstallPackage>add @zag-js/vue @zag-js/alert</InstallPackage>
|
||||
<SectionContent>
|
||||
Copy and paste the following code into your project.
|
||||
</SectionContent>
|
||||
<PreviewCode title="components/ui/alert/AlertCloseTrigger.vue">
|
||||
{{`
|
||||
<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>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/alert/AlertDescription.vue">
|
||||
{{ `
|
||||
<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>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/alert/AlertRoot.vue">
|
||||
{{`
|
||||
<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>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/alert/AlertTitle.vue">
|
||||
{{ `
|
||||
<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>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/alert/index.ts">
|
||||
{{ `
|
||||
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";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<SectionContent>
|
||||
Update the import paths to match your project setup.
|
||||
</SectionContent>
|
||||
</div>
|
||||
<div id="usage">
|
||||
<SectionTitle>Usage</SectionTitle>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
import {
|
||||
AlertRoot,
|
||||
AlertTitle,
|
||||
AlertDescription,
|
||||
AlertCloseTrigger,
|
||||
} from "@/components/ui/alert";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<AlertRoot>
|
||||
<Compass />
|
||||
<AlertTitle>
|
||||
Success! Your changes have been saved
|
||||
</AlertTitle>
|
||||
<AlertDescription>
|
||||
This is an alert with icon, title and description.
|
||||
</AlertDescription>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</div>
|
||||
<div id="variants">
|
||||
<SectionTitle>Variants</SectionTitle>
|
||||
<SectionContent>A collection of components you can use.</SectionContent>
|
||||
<Preview class="flex-col!">
|
||||
<template #preview>
|
||||
<AlertRoot variant="primary">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertDescription>
|
||||
This is an alert with icon, title and description.
|
||||
</AlertDescription>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
<AlertRoot variant="secondary">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertDescription>
|
||||
This is an alert with icon, title and description.
|
||||
</AlertDescription>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
<AlertRoot variant="success">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertDescription>
|
||||
This is an alert with icon, title and description.
|
||||
</AlertDescription>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
<AlertRoot variant="danger">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertDescription>
|
||||
This is an alert with icon, title and description.
|
||||
</AlertDescription>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
<AlertRoot variant="pending">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertDescription>
|
||||
This is an alert with icon, title and description.
|
||||
</AlertDescription>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
<AlertRoot variant="warning">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertDescription>
|
||||
This is an alert with icon, title and description.
|
||||
</AlertDescription>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<AlertRoot variant="primary">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertDescription>
|
||||
This is an alert with icon, title and description.
|
||||
</AlertDescription>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
<AlertRoot variant="secondary">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertDescription>
|
||||
This is an alert with icon, title and description.
|
||||
</AlertDescription>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
<AlertRoot variant="success">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertDescription>
|
||||
This is an alert with icon, title and description.
|
||||
</AlertDescription>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
<AlertRoot variant="danger">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertDescription>
|
||||
This is an alert with icon, title and description.
|
||||
</AlertDescription>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
<AlertRoot variant="pending">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertDescription>
|
||||
This is an alert with icon, title and description.
|
||||
</AlertDescription>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
<AlertRoot variant="warning">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertDescription>
|
||||
This is an alert with icon, title and description.
|
||||
</AlertDescription>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
<Preview class="flex-col!">
|
||||
<template #preview>
|
||||
<AlertRoot look="filled" variant="primary">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertDescription>
|
||||
This is an alert with icon, title and description.
|
||||
</AlertDescription>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
<AlertRoot look="filled" variant="secondary">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertDescription>
|
||||
This is an alert with icon, title and description.
|
||||
</AlertDescription>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
<AlertRoot look="filled" variant="success">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertDescription>
|
||||
This is an alert with icon, title and description.
|
||||
</AlertDescription>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
<AlertRoot look="filled" variant="danger">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertDescription>
|
||||
This is an alert with icon, title and description.
|
||||
</AlertDescription>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
<AlertRoot look="filled" variant="pending">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertDescription>
|
||||
This is an alert with icon, title and description.
|
||||
</AlertDescription>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
<AlertRoot look="filled" variant="warning">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertDescription>
|
||||
This is an alert with icon, title and description.
|
||||
</AlertDescription>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<AlertRoot look="filled" variant="primary">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertDescription>
|
||||
This is an alert with icon, title and description.
|
||||
</AlertDescription>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
<AlertRoot look="filled" variant="secondary">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertDescription>
|
||||
This is an alert with icon, title and description.
|
||||
</AlertDescription>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
<AlertRoot look="filled" variant="success">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertDescription>
|
||||
This is an alert with icon, title and description.
|
||||
</AlertDescription>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
<AlertRoot look="filled" variant="danger">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertDescription>
|
||||
This is an alert with icon, title and description.
|
||||
</AlertDescription>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
<AlertRoot look="filled" variant="pending">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertDescription>
|
||||
This is an alert with icon, title and description.
|
||||
</AlertDescription>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
<AlertRoot look="filled" variant="warning">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertDescription>
|
||||
This is an alert with icon, title and description.
|
||||
</AlertDescription>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
<Preview class="flex-col!">
|
||||
<template #preview>
|
||||
<AlertRoot variant="primary">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
<AlertRoot variant="secondary">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
<AlertRoot variant="success">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
<AlertRoot variant="danger">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
<AlertRoot variant="pending">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
<AlertRoot variant="warning">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<AlertRoot variant="primary">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
<AlertRoot variant="secondary">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
<AlertRoot variant="success">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
<AlertRoot variant="danger">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
<AlertRoot variant="pending">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
<AlertRoot variant="warning">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
<Preview class="flex-col!">
|
||||
<template #preview>
|
||||
<AlertRoot look="filled" variant="primary">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
</AlertRoot>
|
||||
<AlertRoot look="filled" variant="secondary">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
</AlertRoot>
|
||||
<AlertRoot look="filled" variant="success">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
</AlertRoot>
|
||||
<AlertRoot look="filled" variant="danger">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
</AlertRoot>
|
||||
<AlertRoot look="filled" variant="pending">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
</AlertRoot>
|
||||
<AlertRoot look="filled" variant="warning">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
</AlertRoot>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<AlertRoot look="filled" variant="primary">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
</AlertRoot>
|
||||
<AlertRoot look="filled" variant="secondary">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
</AlertRoot>
|
||||
<AlertRoot look="filled" variant="success">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
</AlertRoot>
|
||||
<AlertRoot look="filled" variant="danger">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
</AlertRoot>
|
||||
<AlertRoot look="filled" variant="pending">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
</AlertRoot>
|
||||
<AlertRoot look="filled" variant="warning">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
</AlertRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
<Preview class="flex-col!">
|
||||
<template #preview>
|
||||
<Box class="p-0">
|
||||
<AlertRoot variant="ghost">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertDescription>
|
||||
This is an alert with icon, title and description.
|
||||
</AlertDescription>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
</Box>
|
||||
<Box class="p-0" raised="single">
|
||||
<AlertRoot variant="ghost">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertDescription>
|
||||
This is an alert with icon, title and description.
|
||||
</AlertDescription>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
</Box>
|
||||
<Box class="p-0" raised="double">
|
||||
<AlertRoot variant="ghost">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertDescription>
|
||||
This is an alert with icon, title and description.
|
||||
</AlertDescription>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
</Box>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<Box class="p-0">
|
||||
<AlertRoot variant="ghost">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertDescription>
|
||||
This is an alert with icon, title and description.
|
||||
</AlertDescription>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
</Box>
|
||||
<Box class="p-0" raised="single">
|
||||
<AlertRoot variant="ghost">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertDescription>
|
||||
This is an alert with icon, title and description.
|
||||
</AlertDescription>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
</Box>
|
||||
<Box class="p-0" raised="double">
|
||||
<AlertRoot variant="ghost">
|
||||
<Compass />
|
||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
||||
<AlertDescription>
|
||||
This is an alert with icon, title and description.
|
||||
</AlertDescription>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
</Box>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,199 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { CircleGauge } from "@lucide/vue";
|
||||
import { Box } from "@/components/ui/box";
|
||||
import {
|
||||
Preview,
|
||||
SectionTitle,
|
||||
SectionContent,
|
||||
PreviewCode,
|
||||
} from "@/components/docs";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="preview" class="-mt-20">
|
||||
<Preview>
|
||||
<template #preview>
|
||||
<Box class="w-70">
|
||||
<CircleGauge class="size-7 stroke-1 fill-foreground/10" />
|
||||
<div class="mt-6 text-2xl font-medium leading-8">
|
||||
$724,091.47
|
||||
</div>
|
||||
<div class="mt-1.5 text-xs uppercase opacity-70">
|
||||
Item Sales
|
||||
</div>
|
||||
</Box>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<Box class="w-70">
|
||||
<CircleGauge class="size-7 stroke-1 fill-foreground/10" />
|
||||
<div class="mt-6 text-2xl font-medium leading-8">
|
||||
$724,091.47
|
||||
</div>
|
||||
<div class="mt-1.5 text-xs uppercase opacity-70">
|
||||
Item Sales
|
||||
</div>
|
||||
</Box>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
</div>
|
||||
<div id="installation">
|
||||
<SectionTitle>Installation</SectionTitle>
|
||||
<SectionContent>
|
||||
Copy and paste the following code into your project.
|
||||
</SectionContent>
|
||||
<PreviewCode title="components/ui/box/box.vue">
|
||||
{{ `
|
||||
<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>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/box/index.ts">
|
||||
{{ `
|
||||
export { default as Box } from "./box.vue";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<SectionContent>
|
||||
Update the import paths to match your project setup.
|
||||
</SectionContent>
|
||||
</div>
|
||||
<div id="usage">
|
||||
<SectionTitle>Usage</SectionTitle>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
import { Box } from "@/components/ui/box";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<Box class="w-70">
|
||||
<CircleGauge class="size-7 stroke-1 fill-foreground/10" />
|
||||
<div class="mt-6 text-2xl font-medium leading-8">
|
||||
$724,091.47
|
||||
</div>
|
||||
<div class="mt-1.5 text-xs uppercase opacity-70">
|
||||
Item Sales
|
||||
</div>
|
||||
</Box>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</div>
|
||||
<div id="variants">
|
||||
<SectionTitle>Variants</SectionTitle>
|
||||
<SectionContent>A collection of components you can use.</SectionContent>
|
||||
<Preview>
|
||||
<template #preview>
|
||||
<Box class="w-70">
|
||||
<CircleGauge class="size-7 stroke-1 fill-foreground/10" />
|
||||
<div class="mt-6 text-2xl font-medium leading-8">
|
||||
$724,091.47
|
||||
</div>
|
||||
<div class="mt-1.5 text-xs uppercase opacity-70">
|
||||
Item Sales
|
||||
</div>
|
||||
</Box>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<Box class="w-70">
|
||||
<CircleGauge class="size-7 stroke-1 fill-foreground/10" />
|
||||
<div class="mt-6 text-2xl font-medium leading-8">
|
||||
$724,091.47
|
||||
</div>
|
||||
<div class="mt-1.5 text-xs uppercase opacity-70">
|
||||
Item Sales
|
||||
</div>
|
||||
</Box>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
<Preview>
|
||||
<template #preview>
|
||||
<Box raised="single" class="w-70">
|
||||
<CircleGauge class="size-7 stroke-1 fill-foreground/10" />
|
||||
<div class="mt-6 text-2xl font-medium leading-8">
|
||||
$724,091.47
|
||||
</div>
|
||||
<div class="mt-1.5 text-xs uppercase opacity-70">
|
||||
Item Sales
|
||||
</div>
|
||||
</Box>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<Box raised="single" class="w-70">
|
||||
<CircleGauge class="size-7 stroke-1 fill-foreground/10" />
|
||||
<div class="mt-6 text-2xl font-medium leading-8">
|
||||
$724,091.47
|
||||
</div>
|
||||
<div class="mt-1.5 text-xs uppercase opacity-70">
|
||||
Item Sales
|
||||
</div>
|
||||
</Box>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
<Preview>
|
||||
<template #preview>
|
||||
<Box raised="double" class="w-70">
|
||||
<CircleGauge class="size-7 stroke-1 fill-foreground/10" />
|
||||
<div class="mt-6 text-2xl font-medium leading-8">
|
||||
$724,091.47
|
||||
</div>
|
||||
<div class="mt-1.5 text-xs uppercase opacity-70">
|
||||
Item Sales
|
||||
</div>
|
||||
</Box>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<Box raised="double" class="w-70">
|
||||
<CircleGauge class="size-7 stroke-1 fill-foreground/10" />
|
||||
<div class="mt-6 text-2xl font-medium leading-8">
|
||||
$724,091.47
|
||||
</div>
|
||||
<div class="mt-1.5 text-xs uppercase opacity-70">
|
||||
Item Sales
|
||||
</div>
|
||||
</Box>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,181 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { Breadcrumb } from "@/components/ui/breadcrumb";
|
||||
import {
|
||||
Preview,
|
||||
SectionTitle,
|
||||
SectionContent,
|
||||
PreviewCode,
|
||||
} from "@/components/docs";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="preview" class="-mt-20">
|
||||
<Preview>
|
||||
<template #preview>
|
||||
<Breadcrumb :items="['Dashboard', 'Users', 'Admins', 'Settings', 'Edit Profile']" />
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<Breadcrumb :items="['Dashboard', 'Users', 'Admins', 'Settings', 'Edit Profile']" />
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
</div>
|
||||
<div id="installation">
|
||||
<SectionTitle>Installation</SectionTitle>
|
||||
<SectionContent>
|
||||
Copy and paste the following code into your project.
|
||||
</SectionContent>
|
||||
<PreviewCode title="components/ui/breadcrumb/Breadcrumb.vue">
|
||||
{{ `
|
||||
<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>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/breadcrumb/BreadcrumbItem.vue">
|
||||
{{ `
|
||||
<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>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/breadcrumb/BreadcrumbLink.vue">
|
||||
{{ `
|
||||
<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>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/breadcrumb/BreadcrumbList.vue">
|
||||
{{ `
|
||||
<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>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/breadcrumb/index.ts">
|
||||
{{ `
|
||||
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";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<SectionContent>
|
||||
Update the import paths to match your project setup.
|
||||
</SectionContent>
|
||||
</div>
|
||||
<div id="usage">
|
||||
<SectionTitle>Usage</SectionTitle>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
import { Breadcrumb } from "@/components/ui/breadcrumb";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<Breadcrumb :items="[
|
||||
'Dashboard',
|
||||
'Users',
|
||||
'Admins',
|
||||
'Settings',
|
||||
'Edit Profile',
|
||||
]" />
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,235 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { Chart, getColor } from "@/components/ui/chart";
|
||||
import {
|
||||
Preview,
|
||||
SectionTitle,
|
||||
SectionContent,
|
||||
InstallPackage,
|
||||
PreviewCode,
|
||||
} from "@/components/docs";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="preview" class="-mt-20">
|
||||
<Preview>
|
||||
<template #preview>
|
||||
<Chart class="max-w-100" :config="{
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: [
|
||||
'Jan',
|
||||
'Feb',
|
||||
'Mar',
|
||||
'Apr',
|
||||
'May',
|
||||
'Jun',
|
||||
'Jul',
|
||||
'Aug',
|
||||
'Sep',
|
||||
'Oct',
|
||||
'Nov',
|
||||
'Dec',
|
||||
],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Html Template',
|
||||
maxBarThickness: 12,
|
||||
data: [
|
||||
60, 150, 30, 200, 180, 50, 180, 120, 230, 180, 250, 270,
|
||||
],
|
||||
backgroundColor: () => getColor('--color-foreground', 0.3),
|
||||
borderColor: () => getColor('--color-foreground'),
|
||||
borderWidth: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
options: {
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
display: false,
|
||||
},
|
||||
y: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
}" />
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{`
|
||||
<Chart class="max-w-100" :config="{
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: [
|
||||
'Jan',
|
||||
'Feb',
|
||||
'Mar',
|
||||
'Apr',
|
||||
'May',
|
||||
'Jun',
|
||||
'Jul',
|
||||
'Aug',
|
||||
'Sep',
|
||||
'Oct',
|
||||
'Nov',
|
||||
'Dec',
|
||||
],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Html Template',
|
||||
maxBarThickness: 12,
|
||||
data: [
|
||||
60, 150, 30, 200, 180, 50, 180, 120, 230, 180, 250, 270,
|
||||
],
|
||||
backgroundColor: () => getColor('--color-foreground', 0.3),
|
||||
borderColor: () => getColor('--color-foreground'),
|
||||
borderWidth: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
options: {
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
display: false,
|
||||
},
|
||||
y: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
}" />
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
</div>
|
||||
<div id="installation">
|
||||
<SectionTitle>Installation</SectionTitle>
|
||||
<SectionContent>Install the following dependencies:</SectionContent>
|
||||
<InstallPackage>add chart.js</InstallPackage>
|
||||
<SectionContent>
|
||||
Copy and paste the following code into your project.
|
||||
</SectionContent>
|
||||
<PreviewCode title="components/ui/chart/Chart.vue">
|
||||
{{`
|
||||
<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>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/chart/index.ts">
|
||||
{{ `
|
||||
export { default as Chart } from "./Chart.vue";
|
||||
export { getColor } from "./utils";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<SectionContent>
|
||||
Update the import paths to match your project setup.
|
||||
</SectionContent>
|
||||
</div>
|
||||
<div id="usage">
|
||||
<SectionTitle>Usage</SectionTitle>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
import { Chart, getColor } from "@/components/ui/chart";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode>
|
||||
{{`
|
||||
<Chart class="max-w-100" :config="{
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: [
|
||||
'Jan',
|
||||
'Feb',
|
||||
'Mar',
|
||||
'Apr',
|
||||
'May',
|
||||
'Jun',
|
||||
'Jul',
|
||||
'Aug',
|
||||
'Sep',
|
||||
'Oct',
|
||||
'Nov',
|
||||
'Dec',
|
||||
],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Html Template',
|
||||
maxBarThickness: 12,
|
||||
data: [
|
||||
60, 150, 30, 200, 180, 50, 180, 120, 230, 180, 250, 270,
|
||||
],
|
||||
backgroundColor: () => getColor('--color-foreground', 0.3),
|
||||
borderColor: () => getColor('--color-foreground'),
|
||||
borderWidth: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
options: {
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
display: false,
|
||||
},
|
||||
y: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
}" />
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</div>
|
||||
</template>
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,560 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
DialogRoot,
|
||||
DialogTrigger,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogCloseTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { SquareX, Save, ExternalLink } from "@lucide/vue";
|
||||
import {
|
||||
Preview,
|
||||
SectionTitle,
|
||||
SectionContent,
|
||||
InstallPackage,
|
||||
PreviewCode,
|
||||
} from "@/components/docs";
|
||||
import { ref } from "vue";
|
||||
const dialog = ref(false);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="preview" class="-mt-20">
|
||||
<Preview>
|
||||
<template #preview>
|
||||
<DialogRoot>
|
||||
<DialogTrigger>Open Dialog</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogTitle>Dialog Title</DialogTitle>
|
||||
<DialogDescription>
|
||||
Make changes to your profile here. Click save when you're done.
|
||||
</DialogDescription>
|
||||
<div class="grid gap-4 mt-2">
|
||||
<div class="grid gap-2.5">
|
||||
<Label for="name-1">Name</Label>
|
||||
<Input id="name-1" name="name" defaultValue="Pedro Duarte" />
|
||||
</div>
|
||||
<div class="grid gap-2.5">
|
||||
<Label for="username-1">Username</Label>
|
||||
<Input id="username-1" name="username" defaultValue="@peduarte" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2 justify-end mt-7">
|
||||
<DialogCloseTrigger>
|
||||
<SquareX />
|
||||
Close
|
||||
</DialogCloseTrigger>
|
||||
<Button variant="primary">
|
||||
<Save />
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
<DialogCloseTrigger />
|
||||
</DialogContent>
|
||||
</DialogRoot>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<DialogRoot>
|
||||
<DialogTrigger>Open Dialog</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogTitle>Dialog Title</DialogTitle>
|
||||
<DialogDescription>
|
||||
Make changes to your profile here. Click save when you're done.
|
||||
</DialogDescription>
|
||||
<div class="grid gap-4 mt-2">
|
||||
<div class="grid gap-2.5">
|
||||
<Label for="name-1">Name</Label>
|
||||
<Input id="name-1" name="name" defaultValue="Pedro Duarte" />
|
||||
</div>
|
||||
<div class="grid gap-2.5">
|
||||
<Label for="username-1">Username</Label>
|
||||
<Input id="username-1" name="username" defaultValue="@peduarte" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2 justify-end mt-7">
|
||||
<DialogCloseTrigger>
|
||||
<SquareX />
|
||||
Close
|
||||
</DialogCloseTrigger>
|
||||
<Button variant="primary">
|
||||
<Save />
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
<DialogCloseTrigger />
|
||||
</DialogContent>
|
||||
</DialogRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
</div>
|
||||
<div id="installation">
|
||||
<SectionTitle>Installation</SectionTitle>
|
||||
<SectionContent>Install the following dependencies:</SectionContent>
|
||||
<InstallPackage>add @zag-js/vue @zag-js/dialog</InstallPackage>
|
||||
<SectionContent>
|
||||
Copy and paste the following code into your project.
|
||||
</SectionContent>
|
||||
<PreviewCode title="components/ui/dialog/DialogRoot.vue">
|
||||
{{`
|
||||
<script lang="ts" setup>
|
||||
import { provide, computed } from "vue";
|
||||
import * as dialog from "@zag-js/dialog";
|
||||
import type { Props } from "@zag-js/dialog";
|
||||
import { useMachine, normalizeProps } from "@zag-js/vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
open = undefined,
|
||||
closeOnInteractOutside = undefined,
|
||||
...props
|
||||
} = defineProps<
|
||||
Partial<Props> & {
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}
|
||||
>();
|
||||
|
||||
const service = useMachine(dialog.machine, {
|
||||
...props,
|
||||
get open() {
|
||||
return open;
|
||||
},
|
||||
closeOnInteractOutside,
|
||||
id: crypto.randomUUID(),
|
||||
});
|
||||
|
||||
const api = computed(() => dialog.connect(service, normalizeProps));
|
||||
|
||||
provide("dialogApi", api);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<slot />
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/dialog/DialogTrigger.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { dialogTrigger } from "@mykopkb/core/styles/dialog.styles";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import type { Api } from "@zag-js/dialog";
|
||||
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>("dialogApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot v-bind="{ ...props, ...$attrs, ...api?.getTriggerProps() }">
|
||||
<Button variant="secondary" look="outline" v-if="!asChild" :class="cn(dialogTrigger, className)">
|
||||
<slot />
|
||||
</Button>
|
||||
<slot v-else />
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/dialog/DialogBackdrop.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { dialogBackdrop } from "@mykopkb/core/styles/dialog.styles";
|
||||
import type { Api } from "@zag-js/dialog";
|
||||
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>("dialogApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(dialogBackdrop, className)" v-bind="{ ...props, ...$attrs, ...api?.getBackdropProps() }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/dialog/DialogPositioner.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { dialogPositioner } from "@mykopkb/core/styles/dialog.styles";
|
||||
import type { Api } from "@zag-js/dialog";
|
||||
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>("dialogApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(dialogPositioner, className)" v-bind="{ ...props, ...$attrs, ...api?.getPositionerProps() }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/dialog/DialogContent.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { dialogContent } from "@mykopkb/core/styles/dialog.styles";
|
||||
import { Box } from "@/components/ui/box";
|
||||
import { DialogBackdrop, DialogPositioner } from "@/components/ui/dialog";
|
||||
import type { Api } from "@zag-js/dialog";
|
||||
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>("dialogApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<DialogBackdrop />
|
||||
<DialogPositioner>
|
||||
<Slot v-bind="{ ...props, ...$attrs, ...api?.getContentProps() }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<Box raised="double" :class="cn(dialogContent, className)" v-bind="{ ...props }">
|
||||
<div>
|
||||
<slot />
|
||||
</div>
|
||||
</Box>
|
||||
</div>
|
||||
</Slot>
|
||||
</DialogPositioner>
|
||||
</Teleport>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/dialog/DialogTitle.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { dialogTitle } from "@mykopkb/core/styles/dialog.styles";
|
||||
import type { Api } from "@zag-js/dialog";
|
||||
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>("dialogApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(dialogTitle, className)" v-bind="{ ...props, ...$attrs, ...api?.getTitleProps() }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/dialog/DialogDescription.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { dialogDescription } from "@mykopkb/core/styles/dialog.styles";
|
||||
import type { Api } from "@zag-js/dialog";
|
||||
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>("dialogApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(dialogDescription, className)" v-bind="{ ...props, ...$attrs, ...api?.getDescriptionProps() }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/dialog/DialogCloseTrigger.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { dialogCloseTrigger } from "@mykopkb/core/styles/dialog.styles";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import type { Api } from "@zag-js/dialog";
|
||||
import {
|
||||
buttonVariants,
|
||||
type ButtonVariants,
|
||||
} from "@mykopkb/core/styles/button.styles";
|
||||
import { X } from "@lucide/vue";
|
||||
import { inject } from "vue";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
look = "outline",
|
||||
variant = "secondary",
|
||||
size,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<
|
||||
ButtonVariants & {
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}
|
||||
>();
|
||||
|
||||
const api = inject<Api>("dialogApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot v-bind="{ ...props, ...$attrs, ...api?.getCloseTriggerProps() }">
|
||||
<Button variant="ghost" v-if="!$slots.default" :class="cn(dialogCloseTrigger, className)"
|
||||
v-bind="{ ...props }">
|
||||
<X class="size-4" />
|
||||
</Button>
|
||||
<template v-else>
|
||||
<slot v-if="asChild" />
|
||||
<Button v-else :class="
|
||||
cn(buttonVariants({ look, variant, size, className }), className)
|
||||
">
|
||||
<slot />
|
||||
</Button>
|
||||
</template>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/dialog/index.ts">
|
||||
{{ `
|
||||
export { default as DialogRoot } from "./DialogRoot.vue";
|
||||
export { default as DialogTrigger } from "./DialogTrigger.vue";
|
||||
export { default as DialogBackdrop } from "./DialogBackdrop.vue";
|
||||
export { default as DialogPositioner } from "./DialogPositioner.vue";
|
||||
export { default as DialogContent } from "./DialogContent.vue";
|
||||
export { default as DialogTitle } from "./DialogTitle.vue";
|
||||
export { default as DialogDescription } from "./DialogDescription.vue";
|
||||
export { default as DialogCloseTrigger } from "./DialogCloseTrigger.vue";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<SectionContent>
|
||||
Update the import paths to match your project setup.
|
||||
</SectionContent>
|
||||
</div>
|
||||
<div id="usage">
|
||||
<SectionTitle>Usage</SectionTitle>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
import {
|
||||
DialogRoot,
|
||||
DialogTrigger,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogCloseTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<DialogRoot>
|
||||
<DialogTrigger>Open Dialog</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogTitle>Dialog Title</DialogTitle>
|
||||
<DialogDescription>
|
||||
Make changes to your profile here. Click save when you're done.
|
||||
</DialogDescription>
|
||||
<div class="grid gap-4 mt-2">
|
||||
<div class="grid gap-2.5">
|
||||
<Label for="name-1">Name</Label>
|
||||
<Input id="name-1" name="name" defaultValue="Pedro Duarte" />
|
||||
</div>
|
||||
<div class="grid gap-2.5">
|
||||
<Label for="username-1">Username</Label>
|
||||
<Input id="username-1" name="username" defaultValue="@peduarte" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2 justify-end mt-7">
|
||||
<DialogCloseTrigger>
|
||||
<SquareX />
|
||||
Close
|
||||
</DialogCloseTrigger>
|
||||
<Button variant="primary">
|
||||
<Save />
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
<DialogCloseTrigger />
|
||||
</DialogContent>
|
||||
</DialogRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</div>
|
||||
<div id="variants">
|
||||
<SectionTitle>Variants</SectionTitle>
|
||||
<SectionContent>A collection of components you can use.</SectionContent>
|
||||
<Preview>
|
||||
<template #preview>
|
||||
<DialogRoot>
|
||||
<DialogTrigger>Custom Close</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogTitle>Share Link</DialogTitle>
|
||||
<DialogDescription>
|
||||
Anyone who has this link will be able to view this.
|
||||
</DialogDescription>
|
||||
<div class="grid gap-4 mt-2">
|
||||
<Input id="name-1" name="name" defaultValue="https://midone-ui.com/docs/installation" />
|
||||
</div>
|
||||
<div class="flex gap-2 mt-5">
|
||||
<DialogCloseTrigger>
|
||||
<ExternalLink />
|
||||
Share Link
|
||||
</DialogCloseTrigger>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</DialogRoot>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<DialogRoot>
|
||||
<DialogTrigger>Custom Close</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogTitle>Share Link</DialogTitle>
|
||||
<DialogDescription>
|
||||
Anyone who has this link will be able to view this.
|
||||
</DialogDescription>
|
||||
<div class="grid gap-4 mt-2">
|
||||
<Input id="name-1" name="name" defaultValue="https://midone-ui.com/docs/installation" />
|
||||
</div>
|
||||
<div class="flex gap-2 mt-5">
|
||||
<DialogCloseTrigger>
|
||||
<ExternalLink />
|
||||
Share Link
|
||||
</DialogCloseTrigger>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</DialogRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
<Preview>
|
||||
<template #preview>
|
||||
<Button look="outline" variant="secondary" @click.prevent="dialog = true">
|
||||
Programmatic Trigger
|
||||
</Button>
|
||||
<DialogRoot :open="dialog" @openChange="(details) => (dialog = details.open)">
|
||||
<DialogContent>
|
||||
<DialogTitle>Share Link</DialogTitle>
|
||||
<DialogDescription>
|
||||
Anyone who has this link will be able to view this.
|
||||
</DialogDescription>
|
||||
<div class="grid gap-4 mt-2">
|
||||
<Input id="name-1" name="name" defaultValue="https://midone-ui.com/docs/installation" />
|
||||
</div>
|
||||
<div class="flex gap-2 mt-5">
|
||||
<DialogCloseTrigger>
|
||||
<ExternalLink />
|
||||
Share Link
|
||||
</DialogCloseTrigger>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</DialogRoot>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{`
|
||||
const dialog = ref(false);
|
||||
|
||||
<Button look="outline" variant="secondary" @click.prevent="dialog = true">
|
||||
Programmatic Trigger
|
||||
</Button>
|
||||
<DialogRoot :open="dialog" @openChange="(details) => (dialog = details.open)">
|
||||
<DialogContent>
|
||||
<DialogTitle>Share Link</DialogTitle>
|
||||
<DialogDescription>
|
||||
Anyone who has this link will be able to view this.
|
||||
</DialogDescription>
|
||||
<div class="grid gap-4 mt-2">
|
||||
<Input id="name-1" name="name" defaultValue="https://midone-ui.com/docs/installation" />
|
||||
</div>
|
||||
<div class="flex gap-2 mt-5">
|
||||
<DialogCloseTrigger>
|
||||
<ExternalLink />
|
||||
Share Link
|
||||
</DialogCloseTrigger>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</DialogRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,484 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
CheckboxRoot,
|
||||
CheckboxLabel,
|
||||
CheckboxControl,
|
||||
} from "@/components/ui/checkbox";
|
||||
import {
|
||||
Field,
|
||||
FieldDescription,
|
||||
FieldGroup,
|
||||
FieldLabel,
|
||||
FieldLegend,
|
||||
FieldSeparator,
|
||||
FieldSet,
|
||||
FieldTitle,
|
||||
FieldContent,
|
||||
} from "@/components/ui/field";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
SelectRoot,
|
||||
SelectControl,
|
||||
SelectTrigger,
|
||||
SelectValueText,
|
||||
SelectContent,
|
||||
SelectItemGroup,
|
||||
SelectItemGroupLabel,
|
||||
SelectItem,
|
||||
SelectItemText,
|
||||
} from "@/components/ui/select";
|
||||
import * as select from "@zag-js/select";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import {
|
||||
RadioGroupRoot,
|
||||
RadioGroupItem,
|
||||
RadioGroupItemControl,
|
||||
} from "@/components/ui/radio-group";
|
||||
import {
|
||||
Preview,
|
||||
SectionTitle,
|
||||
SectionContent,
|
||||
PreviewCode,
|
||||
} from "@/components/docs";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="preview" class="-mt-20">
|
||||
<Preview>
|
||||
<template #preview>
|
||||
<div class="w-full max-w-md">
|
||||
<form>
|
||||
<FieldGroup>
|
||||
<FieldSet>
|
||||
<FieldLegend>Payment Method</FieldLegend>
|
||||
<FieldDescription>
|
||||
All transactions are secure and encrypted
|
||||
</FieldDescription>
|
||||
<FieldGroup>
|
||||
<Field>
|
||||
<FieldLabel for="card-name">Name on Card</FieldLabel>
|
||||
<Input id="card-name" placeholder="Evil Rabbit" required />
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel for="card-number">Card Number</FieldLabel>
|
||||
<Input id="card-number" placeholder="1234 5678 9012 3456" required />
|
||||
<FieldDescription>Enter your 16-digit card number</FieldDescription>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</FieldSet>
|
||||
<FieldSeparator />
|
||||
<FieldSet>
|
||||
<FieldLegend>Billing Address</FieldLegend>
|
||||
<FieldDescription>
|
||||
The billing address associated with your payment method
|
||||
</FieldDescription>
|
||||
<CheckboxRoot>
|
||||
<CheckboxControl />
|
||||
<CheckboxLabel class="font-normal">
|
||||
Same as shipping address
|
||||
</CheckboxLabel>
|
||||
</CheckboxRoot>
|
||||
</FieldSet>
|
||||
<Field orientation="horizontal">
|
||||
<Button look="outline" type="submit">Submit</Button>
|
||||
<Button type="button">Cancel</Button>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<div class="w-full max-w-md">
|
||||
<form>
|
||||
<FieldGroup>
|
||||
<FieldSet>
|
||||
<FieldLegend>Payment Method</FieldLegend>
|
||||
<FieldDescription>
|
||||
All transactions are secure and encrypted
|
||||
</FieldDescription>
|
||||
<FieldGroup>
|
||||
<Field>
|
||||
<FieldLabel for="card-name">Name on Card</FieldLabel>
|
||||
<Input id="card-name" placeholder="Evil Rabbit" required />
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel for="card-number">Card Number</FieldLabel>
|
||||
<Input id="card-number" placeholder="1234 5678 9012 3456" required />
|
||||
<FieldDescription>Enter your 16-digit card number</FieldDescription>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</FieldSet>
|
||||
<FieldSeparator />
|
||||
<FieldSet>
|
||||
<FieldLegend>Billing Address</FieldLegend>
|
||||
<FieldDescription>
|
||||
The billing address associated with your payment method
|
||||
</FieldDescription>
|
||||
<CheckboxRoot>
|
||||
<CheckboxControl />
|
||||
<CheckboxLabel class="font-normal">
|
||||
Same as shipping address
|
||||
</CheckboxLabel>
|
||||
</CheckboxRoot>
|
||||
</FieldSet>
|
||||
<Field orientation="horizontal">
|
||||
<Button look="outline" type="submit">Submit</Button>
|
||||
<Button type="button">Cancel</Button>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</form>
|
||||
</div>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
</div>
|
||||
<div id="installation">
|
||||
<SectionTitle>Installation</SectionTitle>
|
||||
<SectionContent>
|
||||
Copy and paste the following code into your project.
|
||||
</SectionContent>
|
||||
<PreviewCode title="components/ui/field/Field.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import {
|
||||
fieldVariants,
|
||||
type FieldVariants,
|
||||
} from "@mykopkb/core/styles/field.styles";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
orientation = "vertical",
|
||||
...props
|
||||
} = defineProps<
|
||||
FieldVariants & {
|
||||
class?: string;
|
||||
}
|
||||
>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div role="group" data-part="field" :data-orientation="orientation"
|
||||
:class="cn(fieldVariants({ orientation }), className)" v-bind="{ ...props }">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/field/FieldContent.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { fieldContent } from "@mykopkb/core/styles/field.styles";
|
||||
|
||||
const { class: className, ...props } = defineProps<{
|
||||
class?: string;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div data-part="field-content" :class="cn(fieldContent, className)" v-bind="{ ...props }">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/field/FieldDescription.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { fieldDescription } from "@mykopkb/core/styles/field.styles";
|
||||
|
||||
const { class: className, ...props } = defineProps<{
|
||||
class?: string;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<p data-part="field-description" :class="cn(fieldDescription, className)" v-bind="{ ...props }">
|
||||
<slot />
|
||||
</p>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/field/FieldLabel.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { fieldLabel } from "@mykopkb/core/styles/field.styles";
|
||||
|
||||
const { class: className, ...props } = defineProps<{
|
||||
class?: string;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Label data-part="field-label" :class="cn(fieldLabel, className)" v-bind="{ ...props }">
|
||||
<slot />
|
||||
</Label>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/field/FieldSeparator.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { fieldSeparator } from "@mykopkb/core/styles/field.styles";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
|
||||
const { class: className, ...props } = defineProps<{
|
||||
class?: string;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div data-part="field-separator" :data-content="!!$slots.default" :class="cn(fieldSeparator, className)"
|
||||
v-bind="{ ...props }">
|
||||
<Separator class="absolute inset-0 top-1/2" />
|
||||
<span v-if="$slots.default" data-part="field-separator-content">
|
||||
<slot />
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/field/index.ts">
|
||||
{{ `
|
||||
export { default as Field } from "./Field.vue";
|
||||
export { default as FieldContent } from "./FieldContent.vue";
|
||||
export { default as FieldDescription } from "./FieldDescription.vue";
|
||||
export { default as FieldError } from "./FieldError.vue";
|
||||
export { default as FieldGroup } from "./FieldGroup.vue";
|
||||
export { default as FieldLabel } from "./FieldLabel.vue";
|
||||
export { default as FieldLegend } from "./FieldLegend.vue";
|
||||
export { default as FieldSeparator } from "./FieldSeparator.vue";
|
||||
export { default as FieldSet } from "./FieldSet.vue";
|
||||
export { default as FieldTitle } from "./FieldTitle.vue";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<SectionContent>
|
||||
Update the import paths to match your project setup.
|
||||
</SectionContent>
|
||||
</div>
|
||||
<div id="usage">
|
||||
<SectionTitle>Usage</SectionTitle>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
import {
|
||||
Field,
|
||||
FieldContent,
|
||||
FieldDescription,
|
||||
FieldError,
|
||||
FieldGroup,
|
||||
FieldLabel,
|
||||
FieldLegend,
|
||||
FieldSeparator,
|
||||
FieldSet,
|
||||
FieldTitle,
|
||||
} from "@/components/ui/field";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<Field>
|
||||
<FieldLabel for="email">Email</FieldLabel>
|
||||
<Input id="email" placeholder="Enter your email" />
|
||||
<FieldDescription>We'll never share your email.</FieldDescription>
|
||||
</Field>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</div>
|
||||
<div id="variants">
|
||||
<SectionTitle>Variants</SectionTitle>
|
||||
<SectionContent>A collection of components you can use.</SectionContent>
|
||||
<Preview>
|
||||
<template #preview>
|
||||
<form>
|
||||
<FieldGroup>
|
||||
<FieldSet>
|
||||
<FieldLegend>Payment Method</FieldLegend>
|
||||
<FieldDescription>
|
||||
All transactions are secure and encrypted
|
||||
</FieldDescription>
|
||||
<FieldGroup>
|
||||
<Field>
|
||||
<FieldLabel for="card-name">Name on Card</FieldLabel>
|
||||
<Input id="card-name" placeholder="Evil Rabbit" required />
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel for="card-number">Card Number</FieldLabel>
|
||||
<Input id="card-number" placeholder="1234 5678 9012 3456" required />
|
||||
<FieldDescription>Enter your 16-digit card number</FieldDescription>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</FieldSet>
|
||||
<FieldSeparator />
|
||||
<FieldSet>
|
||||
<FieldLegend>Billing Address</FieldLegend>
|
||||
<FieldDescription>
|
||||
The billing address associated with your payment method
|
||||
</FieldDescription>
|
||||
<CheckboxRoot>
|
||||
<CheckboxControl />
|
||||
<CheckboxLabel class="font-normal">
|
||||
Same as shipping address
|
||||
</CheckboxLabel>
|
||||
</CheckboxRoot>
|
||||
</FieldSet>
|
||||
<FieldSet>
|
||||
<FieldGroup>
|
||||
<Field>
|
||||
<FieldLabel for="comments">Comments</FieldLabel>
|
||||
<Textarea id="comments" placeholder="Add any additional comments" class="resize-none" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</FieldSet>
|
||||
<Field orientation="horizontal">
|
||||
<Button look="outline" type="submit">Submit</Button>
|
||||
<Button type="button">Cancel</Button>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</form>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<form>
|
||||
<FieldGroup>
|
||||
<FieldSet>
|
||||
<FieldLegend>Payment Method</FieldLegend>
|
||||
<FieldDescription>
|
||||
All transactions are secure and encrypted
|
||||
</FieldDescription>
|
||||
<FieldGroup>
|
||||
<Field>
|
||||
<FieldLabel for="card-name">Name on Card</FieldLabel>
|
||||
<Input id="card-name" placeholder="Evil Rabbit" required />
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel for="card-number">Card Number</FieldLabel>
|
||||
<Input id="card-number" placeholder="1234 5678 9012 3456" required />
|
||||
<FieldDescription>Enter your 16-digit card number</FieldDescription>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</FieldSet>
|
||||
<FieldSeparator />
|
||||
<FieldSet>
|
||||
<FieldLegend>Billing Address</FieldLegend>
|
||||
<FieldDescription>
|
||||
The billing address associated with your payment method
|
||||
</FieldDescription>
|
||||
<CheckboxRoot>
|
||||
<CheckboxControl />
|
||||
<CheckboxLabel class="font-normal">
|
||||
Same as shipping address
|
||||
</CheckboxLabel>
|
||||
</CheckboxRoot>
|
||||
</FieldSet>
|
||||
<FieldSet>
|
||||
<FieldGroup>
|
||||
<Field>
|
||||
<FieldLabel for="comments">Comments</FieldLabel>
|
||||
<Textarea id="comments" placeholder="Add any additional comments" class="resize-none" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</FieldSet>
|
||||
<Field orientation="horizontal">
|
||||
<Button look="outline" type="submit">Submit</Button>
|
||||
<Button type="button">Cancel</Button>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</form>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
<Preview>
|
||||
<template #preview>
|
||||
<FieldGroup class="w-full max-w-xs">
|
||||
<FieldSet>
|
||||
<FieldLegend variant="label">Compute Environment</FieldLegend>
|
||||
<FieldDescription>
|
||||
Select the compute environment for your cluster.
|
||||
</FieldDescription>
|
||||
<RadioGroupRoot defaultValue="React">
|
||||
<FieldLabel>
|
||||
<Field orientation="horizontal">
|
||||
<FieldContent>
|
||||
<FieldTitle>Kubernetes</FieldTitle>
|
||||
<FieldDescription>
|
||||
Run GPU workloads on a K8s cluster.
|
||||
</FieldDescription>
|
||||
</FieldContent>
|
||||
<RadioGroupItem value="React">
|
||||
<RadioGroupItemControl />
|
||||
</RadioGroupItem>
|
||||
</Field>
|
||||
</FieldLabel>
|
||||
<FieldLabel>
|
||||
<Field orientation="horizontal">
|
||||
<FieldContent>
|
||||
<FieldTitle>Virtual Machine</FieldTitle>
|
||||
<FieldDescription>
|
||||
Access a cluster to run GPU workloads.
|
||||
</FieldDescription>
|
||||
</FieldContent>
|
||||
<RadioGroupItem value="Solid">
|
||||
<RadioGroupItemControl />
|
||||
</RadioGroupItem>
|
||||
</Field>
|
||||
</FieldLabel>
|
||||
</RadioGroupRoot>
|
||||
</FieldSet>
|
||||
</FieldGroup>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<FieldGroup class="w-full max-w-xs">
|
||||
<FieldSet>
|
||||
<FieldLegend variant="label">Compute Environment</FieldLegend>
|
||||
<FieldDescription>
|
||||
Select the compute environment for your cluster.
|
||||
</FieldDescription>
|
||||
<RadioGroupRoot defaultValue="React">
|
||||
<FieldLabel>
|
||||
<Field orientation="horizontal">
|
||||
<FieldContent>
|
||||
<FieldTitle>Kubernetes</FieldTitle>
|
||||
<FieldDescription>
|
||||
Run GPU workloads on a K8s cluster.
|
||||
</FieldDescription>
|
||||
</FieldContent>
|
||||
<RadioGroupItem value="React">
|
||||
<RadioGroupItemControl />
|
||||
</RadioGroupItem>
|
||||
</Field>
|
||||
</FieldLabel>
|
||||
<FieldLabel>
|
||||
<Field orientation="horizontal">
|
||||
<FieldContent>
|
||||
<FieldTitle>Virtual Machine</FieldTitle>
|
||||
<FieldDescription>
|
||||
Access a cluster to run GPU workloads.
|
||||
</FieldDescription>
|
||||
</FieldContent>
|
||||
<RadioGroupItem value="Solid">
|
||||
<RadioGroupItemControl />
|
||||
</RadioGroupItem>
|
||||
</Field>
|
||||
</FieldLabel>
|
||||
</RadioGroupRoot>
|
||||
</FieldSet>
|
||||
</FieldGroup>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,831 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
MenuRoot,
|
||||
MenuTrigger,
|
||||
MenuPositioner,
|
||||
MenuContent,
|
||||
MenuItem,
|
||||
MenuCheckboxItem,
|
||||
MenuSeparator,
|
||||
MenuTriggerItem,
|
||||
MenuRadioItemGroup,
|
||||
MenuItemGroupLabel,
|
||||
MenuRadioItem,
|
||||
} from "@/components/ui/menu";
|
||||
import {
|
||||
Preview,
|
||||
SectionTitle,
|
||||
SectionContent,
|
||||
InstallPackage,
|
||||
PreviewCode,
|
||||
} from "@/components/docs";
|
||||
import { ref } from "vue";
|
||||
const react = ref(false);
|
||||
const solid = ref(false);
|
||||
const vue = ref(false);
|
||||
const svelte = ref(false);
|
||||
const value = ref("react");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="preview" class="-mt-20">
|
||||
<Preview>
|
||||
<template #preview>
|
||||
<MenuRoot class="w-56">
|
||||
<MenuTrigger>Open menu</MenuTrigger>
|
||||
<MenuPositioner>
|
||||
<MenuContent>
|
||||
<MenuItem value="react">React</MenuItem>
|
||||
<MenuItem value="solid">Solid</MenuItem>
|
||||
<MenuItem value="vue">Vue</MenuItem>
|
||||
<MenuItem value="svelte">Svelte</MenuItem>
|
||||
</MenuContent>
|
||||
</MenuPositioner>
|
||||
</MenuRoot>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<MenuRoot class="w-56">
|
||||
<MenuTrigger>Open menu</MenuTrigger>
|
||||
<MenuPositioner>
|
||||
<MenuContent>
|
||||
<MenuItem value="react">React</MenuItem>
|
||||
<MenuItem value="solid">Solid</MenuItem>
|
||||
<MenuItem value="vue">Vue</MenuItem>
|
||||
<MenuItem value="svelte">Svelte</MenuItem>
|
||||
</MenuContent>
|
||||
</MenuPositioner>
|
||||
</MenuRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
</div>
|
||||
<div id="installation">
|
||||
<SectionTitle>Installation</SectionTitle>
|
||||
<SectionContent>Install the following dependencies:</SectionContent>
|
||||
<InstallPackage>add @zag-js/vue @zag-js/menu</InstallPackage>
|
||||
<SectionContent>
|
||||
Copy and paste the following code into your project.
|
||||
</SectionContent>
|
||||
<PreviewCode title="components/ui/menu/MenuRoot.vue">
|
||||
{{`
|
||||
<script lang="ts" setup>
|
||||
import * as menu from "@zag-js/menu";
|
||||
import type { Props } from "@zag-js/menu";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { normalizeProps, useMachine } from "@zag-js/vue";
|
||||
import { computed, provide } from "vue";
|
||||
import { menuRoot } from "@mykopkb/core/styles/menu.styles";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
closeOnSelect = false,
|
||||
open = undefined,
|
||||
...props
|
||||
} = defineProps<Partial<Props> & { class?: string; asChild?: boolean }>();
|
||||
|
||||
const service = useMachine(menu.machine, {
|
||||
...props,
|
||||
open,
|
||||
closeOnSelect,
|
||||
id: crypto.randomUUID(),
|
||||
});
|
||||
const api = computed(() => menu.connect(service, normalizeProps));
|
||||
|
||||
provide("menuApi", api);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(menuRoot, className)" v-bind="{ ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/menu/MenuTrigger.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { type Api } from "@zag-js/menu";
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { inject } from "vue";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import { menuTrigger } from "@mykopkb/core/styles/menu.styles";
|
||||
import { MenuIndicator } from ".";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("menuApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot v-bind="{ ...api?.getTriggerProps(), ...props, ...$attrs }">
|
||||
<Button variant="ghost" v-if="!asChild" :class="cn(menuTrigger, className)">
|
||||
<slot />
|
||||
<MenuIndicator />
|
||||
</Button>
|
||||
<slot v-else />
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/menu/MenuIndicator.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { ChevronDown } from "@lucide/vue";
|
||||
import { menuIndicator } from "@mykopkb/core/styles/menu.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import type { Api } from "@zag-js/menu";
|
||||
import { inject } from "vue";
|
||||
|
||||
const { class: className, ...props } = defineProps<{
|
||||
class?: string;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("menuApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(menuIndicator, className)" v-bind="{ ...api?.getIndicatorProps(), ...props, ...$attrs }">
|
||||
<slot v-if="$slots.default" />
|
||||
<ChevronDown v-else />
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/menu/MenuPositioner.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { type Api } from "@zag-js/menu";
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { inject } from "vue";
|
||||
import { menuPositioner } from "@mykopkb/core/styles/menu.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("menuApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<Slot :class="cn(menuPositioner, className)" v-bind="{ ...props, ...$attrs, ...api?.getPositionerProps() }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</Teleport>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/menu/MenuContent.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { type Api } from "@zag-js/menu";
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { inject } from "vue";
|
||||
import { Box } from "@/components/ui/box";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import { menuContent } from "@mykopkb/core/styles/menu.styles";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("menuApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(menuContent, className)" v-bind="{ ...api?.getContentProps(), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<Box v-else raised="single" :class="cn(menuContent, className)">
|
||||
<div>
|
||||
<slot />
|
||||
</div>
|
||||
</Box>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/menu/MenuItem.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import { type Api, type ItemProps } from "@zag-js/menu";
|
||||
import { inject } from "vue";
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { menuItem } from "@mykopkb/core/styles/menu.styles";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
shortcut,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<
|
||||
ItemProps & {
|
||||
class?: string;
|
||||
shortcut?: string;
|
||||
asChild?: boolean;
|
||||
}
|
||||
>();
|
||||
|
||||
const api = inject<Api>("menuApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(menuItem, className)" v-bind="{ ...api?.getItemProps(props), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<div>
|
||||
<slot />
|
||||
</div>
|
||||
<div>\{\{ shortcut \}\}</div>
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/menu/MenuTriggerItem.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { type Api } from "@zag-js/menu";
|
||||
import { inject } from "vue";
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { ChevronRight } from "@lucide/vue";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import { menuItem } from "@mykopkb/core/styles/menu.styles";
|
||||
|
||||
const { class: className, ...props } = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("menuApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(menuItem, className)" v-bind="{ ...api?.getTriggerItemProps(api), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<div>
|
||||
<slot />
|
||||
</div>
|
||||
<ChevronRight data-part="nested-menu-chevron" />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/menu/MenuCheckboxItem.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import type { Api, OptionItemProps } from "@zag-js/menu";
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { Check } from "@lucide/vue";
|
||||
import { menuItem } from "@mykopkb/core/styles/menu.styles";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
shortcut,
|
||||
class: className,
|
||||
type = "checkbox",
|
||||
...props
|
||||
} = defineProps<
|
||||
Omit<OptionItemProps, "type"> & {
|
||||
class?: string;
|
||||
shortcut?: string;
|
||||
type?: OptionItemProps["type"];
|
||||
}
|
||||
>();
|
||||
|
||||
const api = inject<Api>("menuApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn(menuItem, className)" v-bind="{
|
||||
...props,
|
||||
...$attrs,
|
||||
...api?.getOptionItemProps({
|
||||
...props,
|
||||
type,
|
||||
}),
|
||||
}">
|
||||
<div>
|
||||
<span data-part="item-indicator" v-bind="{ ...api?.getItemIndicatorProps(props) }">
|
||||
<Check />
|
||||
</span>
|
||||
<slot />
|
||||
</div>
|
||||
<div>\{\{ shortcut \}\}</div>
|
||||
</div>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/menu/MenuRadioItemGroup.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { menuRadioItemGroup } from "@mykopkb/core/styles/menu.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(menuRadioItemGroup, className)" v-bind="{
|
||||
...props,
|
||||
...$attrs,
|
||||
}">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/menu/MenuItemGroupLabel.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { menuItemGroupLabel } from "@mykopkb/core/styles/menu.styles";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(menuItemGroupLabel, className)" v-bind="{ ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<label v-else>
|
||||
<slot />
|
||||
</label>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/menu/MenuRadioItem.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import type { Api, OptionItemProps } from "@zag-js/menu";
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { Dot } from "@lucide/vue";
|
||||
import { menuItem } from "@mykopkb/core/styles/menu.styles";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
shortcut,
|
||||
class: className,
|
||||
asChild = false,
|
||||
type = "radio",
|
||||
...props
|
||||
} = defineProps<
|
||||
Omit<OptionItemProps, "type"> & {
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
shortcut?: string;
|
||||
type?: OptionItemProps["type"];
|
||||
}
|
||||
>();
|
||||
|
||||
const api = inject<Api>("menuApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn(menuItem, className)" v-bind="{
|
||||
...props,
|
||||
...$attrs,
|
||||
...api?.getOptionItemProps({
|
||||
...props,
|
||||
type,
|
||||
}),
|
||||
}">
|
||||
<div>
|
||||
<span data-part="item-indicator" v-bind="{ ...api?.getItemIndicatorProps(props) }">
|
||||
<Dot />
|
||||
</span>
|
||||
<slot />
|
||||
</div>
|
||||
<div>\{\{ shortcut \}\}</div>
|
||||
</div>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/menu/MenuSeparator.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import type { Api } from "@zag-js/menu";
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { menuSeparator } from "@mykopkb/core/styles/menu.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("menuApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(menuSeparator, className)" v-bind="{
|
||||
...props,
|
||||
...$attrs,
|
||||
...api?.getSeparatorProps(),
|
||||
}">
|
||||
<slot v-if="asChild" />
|
||||
<hr v-else>
|
||||
<slot />
|
||||
</hr>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/menu/index.ts">
|
||||
{{ `
|
||||
export { default as MenuRoot } from "./MenuRoot.vue";
|
||||
export { default as MenuTrigger } from "./MenuTrigger.vue";
|
||||
export { default as MenuIndicator } from "./MenuIndicator.vue";
|
||||
export { default as MenuPositioner } from "./MenuPositioner.vue";
|
||||
export { default as MenuContent } from "./MenuContent.vue";
|
||||
export { default as MenuItem } from "./MenuItem.vue";
|
||||
export { default as MenuTriggerItem } from "./MenuTriggerItem.vue";
|
||||
export { default as MenuCheckboxItem } from "./MenuCheckboxItem.vue";
|
||||
export { default as MenuRadioItemGroup } from "./MenuRadioItemGroup.vue";
|
||||
export { default as MenuItemGroupLabel } from "./MenuItemGroupLabel.vue";
|
||||
export { default as MenuRadioItem } from "./MenuRadioItem.vue";
|
||||
export { default as MenuSeparator } from "./MenuSeparator.vue";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<SectionContent>
|
||||
Update the import paths to match your project setup.
|
||||
</SectionContent>
|
||||
</div>
|
||||
<div id="usage">
|
||||
<SectionTitle>Usage</SectionTitle>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
import {
|
||||
MenuRoot,
|
||||
MenuTrigger,
|
||||
MenuPositioner,
|
||||
MenuContent,
|
||||
MenuItem,
|
||||
MenuCheckboxItem,
|
||||
MenuSeparator,
|
||||
MenuTriggerItem,
|
||||
MenuRadioItemGroup,
|
||||
MenuItemGroupLabel,
|
||||
MenuRadioItem,
|
||||
} from "@/components/ui/menu";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<MenuRoot class="w-56">
|
||||
<MenuTrigger>Open menu</MenuTrigger>
|
||||
<MenuPositioner>
|
||||
<MenuContent>
|
||||
<MenuItem value="react">React</MenuItem>
|
||||
<MenuItem value="solid">Solid</MenuItem>
|
||||
<MenuItem value="vue">Vue</MenuItem>
|
||||
<MenuItem value="svelte">Svelte</MenuItem>
|
||||
</MenuContent>
|
||||
</MenuPositioner>
|
||||
</MenuRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</div>
|
||||
<div id="variants">
|
||||
<SectionTitle>Variants</SectionTitle>
|
||||
<SectionContent>A collection of components you can use.</SectionContent>
|
||||
<Preview>
|
||||
<template #preview>
|
||||
<MenuRoot class="w-56">
|
||||
<MenuTrigger>Open menu</MenuTrigger>
|
||||
<MenuPositioner>
|
||||
<MenuContent>
|
||||
<MenuItem value="react">React</MenuItem>
|
||||
<MenuItem value="solid">Solid</MenuItem>
|
||||
<MenuItem value="vue">Vue</MenuItem>
|
||||
<MenuItem value="svelte">Svelte</MenuItem>
|
||||
<MenuSeparator />
|
||||
<MenuItem value="react">React</MenuItem>
|
||||
<MenuItem value="solid">Solid</MenuItem>
|
||||
<MenuItem value="vue">Vue</MenuItem>
|
||||
<MenuItem value="svelte">Svelte</MenuItem>
|
||||
</MenuContent>
|
||||
</MenuPositioner>
|
||||
</MenuRoot>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<MenuRoot class="w-56">
|
||||
<MenuTrigger>Open menu</MenuTrigger>
|
||||
<MenuPositioner>
|
||||
<MenuContent>
|
||||
<MenuItem value="react">React</MenuItem>
|
||||
<MenuItem value="solid">Solid</MenuItem>
|
||||
<MenuItem value="vue">Vue</MenuItem>
|
||||
<MenuItem value="svelte">Svelte</MenuItem>
|
||||
<MenuSeparator />
|
||||
<MenuItem value="react">React</MenuItem>
|
||||
<MenuItem value="solid">Solid</MenuItem>
|
||||
<MenuItem value="vue">Vue</MenuItem>
|
||||
<MenuItem value="svelte">Svelte</MenuItem>
|
||||
</MenuContent>
|
||||
</MenuPositioner>
|
||||
</MenuRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
<Preview>
|
||||
<template #preview>
|
||||
<MenuRoot class="w-56">
|
||||
<MenuTrigger>Open menu</MenuTrigger>
|
||||
<MenuPositioner>
|
||||
<MenuContent>
|
||||
<MenuItem shortcut="⇧⌘P" value="react">
|
||||
React
|
||||
</MenuItem>
|
||||
<MenuItem shortcut="⌘B" value="solid">
|
||||
Solid
|
||||
</MenuItem>
|
||||
<MenuItem shortcut="⌘S" value="vue">
|
||||
Vue
|
||||
</MenuItem>
|
||||
<MenuItem shortcut="⌘K" value="svelte">
|
||||
Svelte
|
||||
</MenuItem>
|
||||
<MenuSeparator />
|
||||
<MenuItem value="react">React</MenuItem>
|
||||
<MenuItem value="solid">Solid</MenuItem>
|
||||
<MenuItem value="vue">Vue</MenuItem>
|
||||
<MenuItem shortcut="⇧⌘Q" value="svelte">
|
||||
Svelte
|
||||
</MenuItem>
|
||||
</MenuContent>
|
||||
</MenuPositioner>
|
||||
</MenuRoot>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<MenuRoot class="w-56">
|
||||
<MenuTrigger>Open menu</MenuTrigger>
|
||||
<MenuPositioner>
|
||||
<MenuContent>
|
||||
<MenuItem shortcut="⇧⌘P" value="react">
|
||||
React
|
||||
</MenuItem>
|
||||
<MenuItem shortcut="⌘B" value="solid">
|
||||
Solid
|
||||
</MenuItem>
|
||||
<MenuItem shortcut="⌘S" value="vue">
|
||||
Vue
|
||||
</MenuItem>
|
||||
<MenuItem shortcut="⌘K" value="svelte">
|
||||
Svelte
|
||||
</MenuItem>
|
||||
<MenuSeparator />
|
||||
<MenuItem value="react">React</MenuItem>
|
||||
<MenuItem value="solid">Solid</MenuItem>
|
||||
<MenuItem value="vue">Vue</MenuItem>
|
||||
<MenuItem shortcut="⇧⌘Q" value="svelte">
|
||||
Svelte
|
||||
</MenuItem>
|
||||
</MenuContent>
|
||||
</MenuPositioner>
|
||||
</MenuRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
<Preview>
|
||||
<template #preview>
|
||||
<MenuRoot class="w-56">
|
||||
<MenuTrigger>Open menu</MenuTrigger>
|
||||
<MenuPositioner>
|
||||
<MenuContent>
|
||||
<MenuItem shortcut="⇧⌘P" value="react">
|
||||
React
|
||||
</MenuItem>
|
||||
<MenuItem shortcut="⌘B" value="solid">
|
||||
Solid
|
||||
</MenuItem>
|
||||
<MenuItem shortcut="⌘S" value="vue">
|
||||
Vue
|
||||
</MenuItem>
|
||||
<MenuItem shortcut="⌘K" value="svelte">
|
||||
Svelte
|
||||
</MenuItem>
|
||||
<MenuRoot :positioning="{ placement: 'right-start', gutter: 12 }">
|
||||
<MenuTriggerItem>Frameworks</MenuTriggerItem>
|
||||
<MenuPositioner>
|
||||
<MenuContent>
|
||||
<MenuItem value="react">React</MenuItem>
|
||||
<MenuItem value="solid">Solid</MenuItem>
|
||||
<MenuItem value="vue">Vue</MenuItem>
|
||||
<MenuItem value="svelte">Svelte</MenuItem>
|
||||
</MenuContent>
|
||||
</MenuPositioner>
|
||||
</MenuRoot>
|
||||
<MenuSeparator />
|
||||
<MenuItem disabled value="react">
|
||||
React
|
||||
</MenuItem>
|
||||
<MenuItem value="solid">Solid</MenuItem>
|
||||
<MenuItem value="vue">Vue</MenuItem>
|
||||
<MenuItem shortcut="⇧⌘Q" value="svelte">
|
||||
Svelte
|
||||
</MenuItem>
|
||||
</MenuContent>
|
||||
</MenuPositioner>
|
||||
</MenuRoot>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<MenuRoot class="w-56">
|
||||
<MenuTrigger>Open menu</MenuTrigger>
|
||||
<MenuPositioner>
|
||||
<MenuContent>
|
||||
<MenuItem shortcut="⇧⌘P" value="react">
|
||||
React
|
||||
</MenuItem>
|
||||
<MenuItem shortcut="⌘B" value="solid">
|
||||
Solid
|
||||
</MenuItem>
|
||||
<MenuItem shortcut="⌘S" value="vue">
|
||||
Vue
|
||||
</MenuItem>
|
||||
<MenuItem shortcut="⌘K" value="svelte">
|
||||
Svelte
|
||||
</MenuItem>
|
||||
<MenuRoot :positioning="{ placement: 'right-start', gutter: 12 }">
|
||||
<MenuTriggerItem>Frameworks</MenuTriggerItem>
|
||||
<MenuPositioner>
|
||||
<MenuContent>
|
||||
<MenuItem value="react">React</MenuItem>
|
||||
<MenuItem value="solid">Solid</MenuItem>
|
||||
<MenuItem value="vue">Vue</MenuItem>
|
||||
<MenuItem value="svelte">Svelte</MenuItem>
|
||||
</MenuContent>
|
||||
</MenuPositioner>
|
||||
</MenuRoot>
|
||||
<MenuSeparator />
|
||||
<MenuItem disabled value="react">
|
||||
React
|
||||
</MenuItem>
|
||||
<MenuItem value="solid">Solid</MenuItem>
|
||||
<MenuItem value="vue">Vue</MenuItem>
|
||||
<MenuItem shortcut="⇧⌘Q" value="svelte">
|
||||
Svelte
|
||||
</MenuItem>
|
||||
</MenuContent>
|
||||
</MenuPositioner>
|
||||
</MenuRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
<Preview>
|
||||
<template #preview>
|
||||
<MenuRoot class="w-56">
|
||||
<MenuTrigger>Open menu</MenuTrigger>
|
||||
<MenuPositioner>
|
||||
<MenuContent>
|
||||
<MenuCheckboxItem :checked="react" :onCheckedChange="(checked) => (react = checked)" value="checked">
|
||||
React
|
||||
</MenuCheckboxItem>
|
||||
<MenuCheckboxItem :checked="solid" :onCheckedChange="(checked) => (solid = checked)" value="checked">
|
||||
Solid
|
||||
</MenuCheckboxItem>
|
||||
<MenuCheckboxItem :checked="vue" :onCheckedChange="(checked) => (vue = checked)" value="checked">
|
||||
Vue
|
||||
</MenuCheckboxItem>
|
||||
<MenuCheckboxItem :checked="svelte" :onCheckedChange="(checked) => (svelte = checked)" value="checked">
|
||||
Svelte
|
||||
</MenuCheckboxItem>
|
||||
</MenuContent>
|
||||
</MenuPositioner>
|
||||
</MenuRoot>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{`
|
||||
<MenuRoot class="w-56">
|
||||
<MenuTrigger>Open menu</MenuTrigger>
|
||||
<MenuPositioner>
|
||||
<MenuContent>
|
||||
<MenuCheckboxItem :checked="react" :onCheckedChange="(checked) => (react = checked)" value="checked">
|
||||
React
|
||||
</MenuCheckboxItem>
|
||||
<MenuCheckboxItem :checked="solid" :onCheckedChange="(checked) => (solid = checked)" value="checked">
|
||||
Solid
|
||||
</MenuCheckboxItem>
|
||||
<MenuCheckboxItem :checked="vue" :onCheckedChange="(checked) => (vue = checked)" value="checked">
|
||||
Vue
|
||||
</MenuCheckboxItem>
|
||||
<MenuCheckboxItem :checked="svelte" :onCheckedChange="(checked) => (svelte = checked)" value="checked">
|
||||
Svelte
|
||||
</MenuCheckboxItem>
|
||||
</MenuContent>
|
||||
</MenuPositioner>
|
||||
</MenuRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
<Preview>
|
||||
<template #preview>
|
||||
<MenuRoot class="w-56">
|
||||
<MenuTrigger>Open menu</MenuTrigger>
|
||||
<MenuPositioner>
|
||||
<MenuContent>
|
||||
<MenuRadioItemGroup>
|
||||
<MenuItemGroupLabel>Frameworks</MenuItemGroupLabel>
|
||||
<MenuRadioItem v-for="framework in ['React', 'Solid', 'Vue', 'Svelte']" :key="framework"
|
||||
:value="framework" :checked="framework == value" :onCheckedChange="(checked) => (checked ? (value = framework) : '')
|
||||
">
|
||||
{{ framework }}
|
||||
</MenuRadioItem>
|
||||
</MenuRadioItemGroup>
|
||||
</MenuContent>
|
||||
</MenuPositioner>
|
||||
</MenuRoot>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{`
|
||||
<MenuRoot class="w-56">
|
||||
<MenuTrigger>Open menu</MenuTrigger>
|
||||
<MenuPositioner>
|
||||
<MenuContent>
|
||||
<MenuRadioItemGroup>
|
||||
<MenuItemGroupLabel>Frameworks</MenuItemGroupLabel>
|
||||
<MenuRadioItem v-for="framework in ['React', 'Solid', 'Vue', 'Svelte']" :key="framework"
|
||||
:value="framework" :checked="framework == value" :onCheckedChange="
|
||||
(checked) => (checked ? (value = framework) : '')
|
||||
">
|
||||
\{\{ framework \}\}
|
||||
</MenuRadioItem>
|
||||
</MenuRadioItemGroup>
|
||||
</MenuContent>
|
||||
</MenuPositioner>
|
||||
</MenuRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,298 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
PaginationContext,
|
||||
PaginationRoot,
|
||||
PaginationItem,
|
||||
PaginationPrevTrigger,
|
||||
PaginationNextTrigger,
|
||||
PaginationEllipsis,
|
||||
} from "@/components/ui/pagination";
|
||||
import {
|
||||
Preview,
|
||||
SectionTitle,
|
||||
SectionContent,
|
||||
InstallPackage,
|
||||
PreviewCode,
|
||||
} from "@/components/docs";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="preview" class="-mt-20">
|
||||
<Preview>
|
||||
<template #preview>
|
||||
<PaginationRoot :count="5000" :pageSize="10" :siblingCount="2">
|
||||
<PaginationPrevTrigger>Previous</PaginationPrevTrigger>
|
||||
<PaginationContext v-slot="{ pagination }">
|
||||
<template v-for="(page, index) in pagination?.pages" :key="index">
|
||||
<PaginationItem v-if="page.type === 'page'" v-bind="{ ...page }">
|
||||
{{ page.value }}
|
||||
</PaginationItem>
|
||||
<PaginationEllipsis v-else :index="index" />
|
||||
</template>
|
||||
</PaginationContext>
|
||||
<PaginationNextTrigger>Next</PaginationNextTrigger>
|
||||
</PaginationRoot>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<PaginationRoot :count="5000" :pageSize="10" :siblingCount="2">
|
||||
<PaginationPrevTrigger>Previous</PaginationPrevTrigger>
|
||||
<PaginationContext v-slot="{ pagination }">
|
||||
<template v-for="(page, index) in pagination?.pages" :key="index">
|
||||
<PaginationItem v-if="page.type === 'page'" v-bind="{ ...page }">
|
||||
\{\{ page.value \}\}
|
||||
</PaginationItem>
|
||||
<PaginationEllipsis v-else :index="index" />
|
||||
</template>
|
||||
</PaginationContext>
|
||||
<PaginationNextTrigger>Next</PaginationNextTrigger>
|
||||
</PaginationRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
</div>
|
||||
<div id="installation">
|
||||
<SectionTitle>Installation</SectionTitle>
|
||||
<SectionContent>Install the following dependencies:</SectionContent>
|
||||
<InstallPackage>add @zag-js/vue @zag-js/pagination</InstallPackage>
|
||||
<SectionContent>
|
||||
Copy and paste the following code into your project.
|
||||
</SectionContent>
|
||||
<PreviewCode title="components/ui/pagination/PaginationContext.vue">
|
||||
{{ `
|
||||
<script setup lang="ts">
|
||||
import type { Api } from "@zag-js/pagination";
|
||||
import { inject } from "vue";
|
||||
|
||||
const api = inject<Api>("paginationApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<slot :pagination="api"></slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/pagination/PaginationEllipsis.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { paginationEllipsis } from "@mykopkb/core/styles/pagination.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import type { Api, EllipsisProps } from "@zag-js/pagination";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<
|
||||
EllipsisProps & {
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}
|
||||
>();
|
||||
|
||||
const api = inject<Api>("paginationApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(paginationEllipsis, className)"
|
||||
v-bind="{ ...api?.getEllipsisProps(props), ...props, ...$attrs }">
|
||||
<div v-if="!$slots.default">…</div>
|
||||
<template v-else>
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/pagination/PaginationItem.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { paginationItem } from "@mykopkb/core/styles/pagination.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import type { Api, ItemProps } from "@zag-js/pagination";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<
|
||||
ItemProps & {
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}
|
||||
>();
|
||||
|
||||
const api = inject<Api>("paginationApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(paginationItem, className)" v-bind="{ ...api?.getItemProps(props), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/pagination/PaginationNextTrigger.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { paginationNextTrigger } from "@mykopkb/core/styles/pagination.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import type { Api } from "@zag-js/pagination";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("paginationApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(paginationNextTrigger, className)"
|
||||
v-bind="{ ...api?.getNextTriggerProps(), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/pagination/PaginationPrevTrigger.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { paginationPrevTrigger } from "@mykopkb/core/styles/pagination.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import type { Api } from "@zag-js/pagination";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("paginationApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(paginationPrevTrigger, className)"
|
||||
v-bind="{ ...api?.getPrevTriggerProps(), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/pagination/PaginationRoot.vue">
|
||||
{{`
|
||||
<script lang="ts" setup>
|
||||
import * as pagination from "@zag-js/pagination";
|
||||
import type { Props } from "@zag-js/pagination";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { normalizeProps, useMachine } from "@zag-js/vue";
|
||||
import { computed, provide } from "vue";
|
||||
import { paginationRoot } from "@mykopkb/core/styles/pagination.styles";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
count,
|
||||
pageSize,
|
||||
siblingCount,
|
||||
...props
|
||||
} = defineProps<Partial<Props> & { class?: string; asChild?: boolean }>();
|
||||
|
||||
const service = useMachine(pagination.machine, {
|
||||
...props,
|
||||
count,
|
||||
pageSize,
|
||||
siblingCount,
|
||||
id: crypto.randomUUID(),
|
||||
});
|
||||
const api = computed(() => pagination.connect(service, normalizeProps));
|
||||
|
||||
provide("paginationApi", api);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(paginationRoot, className)" v-bind="{ ...props, ...$attrs, ...api.getRootProps() }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/pagination/index.ts">
|
||||
{{ `
|
||||
export { default as PaginationContext } from "./PaginationContext.vue";
|
||||
export { default as PaginationEllipsis } from "./PaginationEllipsis.vue";
|
||||
export { default as PaginationItem } from "./PaginationItem.vue";
|
||||
export { default as PaginationNextTrigger } from "./PaginationNextTrigger.vue";
|
||||
export { default as PaginationPrevTrigger } from "./PaginationPrevTrigger.vue";
|
||||
export { default as PaginationRoot } from "./PaginationRoot.vue";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<SectionContent>
|
||||
Update the import paths to match your project setup.
|
||||
</SectionContent>
|
||||
</div>
|
||||
<div id="usage">
|
||||
<SectionTitle>Usage</SectionTitle>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
import {
|
||||
PaginationContext,
|
||||
PaginationRoot,
|
||||
PaginationItem,
|
||||
PaginationPrevTrigger,
|
||||
PaginationNextTrigger,
|
||||
PaginationEllipsis,
|
||||
} from "@/components/ui/pagination";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<PaginationRoot :count="5000" :pageSize="10" :siblingCount="2">
|
||||
<PaginationPrevTrigger>Previous</PaginationPrevTrigger>
|
||||
<PaginationContext v-slot="{ pagination }">
|
||||
<template v-for="(page, index) in pagination?.pages" :key="index">
|
||||
<PaginationItem v-if="page.type === 'page'" v-bind="{ ...page }">
|
||||
\{\{ page.value \}\}
|
||||
</PaginationItem>
|
||||
<PaginationEllipsis v-else :index="index" />
|
||||
</template>
|
||||
</PaginationContext>
|
||||
<PaginationNextTrigger>Next</PaginationNextTrigger>
|
||||
</PaginationRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,447 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
PopoverRoot,
|
||||
PopoverTrigger,
|
||||
PopoverPositioner,
|
||||
PopoverContent,
|
||||
PopoverTitle,
|
||||
PopoverDescription,
|
||||
} from "@/components/ui/popover";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
Preview,
|
||||
SectionTitle,
|
||||
SectionContent,
|
||||
InstallPackage,
|
||||
PreviewCode,
|
||||
} from "@/components/docs";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="preview" class="-mt-20">
|
||||
<Preview>
|
||||
<template #preview>
|
||||
<PopoverRoot>
|
||||
<PopoverTrigger class="w-56">Open Popover</PopoverTrigger>
|
||||
<PopoverPositioner>
|
||||
<PopoverContent class="w-100">
|
||||
<PopoverTitle>Dimensions</PopoverTitle>
|
||||
<PopoverDescription>
|
||||
Set the dimensions for the layer.
|
||||
</PopoverDescription>
|
||||
<div class="grid gap-3 mt-4 mb-2">
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<Label for="width">Width</Label>
|
||||
<Input id="width" defaultValue="100%" class="col-span-2" />
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<Label for="maxWidth">Max. width</Label>
|
||||
<Input id="maxWidth" defaultValue="300px" class="col-span-2" />
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<Label for="height">Height</Label>
|
||||
<Input id="height" defaultValue="25px" class="col-span-2" />
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<Label for="maxHeight">Max. height</Label>
|
||||
<Input id="maxHeight" defaultValue="none" class="col-span-2" />
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</PopoverPositioner>
|
||||
</PopoverRoot>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<PopoverRoot>
|
||||
<PopoverTrigger class="w-56">Open Popover</PopoverTrigger>
|
||||
<PopoverPositioner>
|
||||
<PopoverContent class="w-100">
|
||||
<PopoverTitle>Dimensions</PopoverTitle>
|
||||
<PopoverDescription>
|
||||
Set the dimensions for the layer.
|
||||
</PopoverDescription>
|
||||
<div class="grid gap-3 mt-4 mb-2">
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<Label for="width">Width</Label>
|
||||
<Input id="width" defaultValue="100%" class="col-span-2" />
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<Label for="maxWidth">Max. width</Label>
|
||||
<Input id="maxWidth" defaultValue="300px" class="col-span-2" />
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<Label for="height">Height</Label>
|
||||
<Input id="height" defaultValue="25px" class="col-span-2" />
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<Label for="maxHeight">Max. height</Label>
|
||||
<Input id="maxHeight" defaultValue="none" class="col-span-2" />
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</PopoverPositioner>
|
||||
</PopoverRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
</div>
|
||||
<div id="installation">
|
||||
<SectionTitle>Installation</SectionTitle>
|
||||
<SectionContent>Install the following dependencies:</SectionContent>
|
||||
<InstallPackage>add @zag-js/vue @zag-js/popover</InstallPackage>
|
||||
<SectionContent>
|
||||
Copy and paste the following code into your project.
|
||||
</SectionContent>
|
||||
<PreviewCode title="components/ui/popover/PopoverArrow.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { type Api } from "@zag-js/popover";
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { inject } from "vue";
|
||||
import { popoverArrow } from "@mykopkb/core/styles/popover.styles";
|
||||
|
||||
const { class: className, ...props } = defineProps<{
|
||||
class?: string;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("popoverApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn(popoverArrow, className)" v-bind="{ ...api?.getArrowProps(), ...props, ...$attrs }">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/popover/PopoverArrowTip.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { type Api } from "@zag-js/popover";
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { inject } from "vue";
|
||||
import { popoverArrowTip } from "@mykopkb/core/styles/popover.styles";
|
||||
|
||||
const { class: className, ...props } = defineProps<{
|
||||
class?: string;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("popoverApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn(popoverArrowTip, className)" v-bind="{ ...api?.getArrowTipProps(), ...props, ...$attrs }">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/popover/PopoverContent.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { type Api } from "@zag-js/popover";
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { inject } from "vue";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import { popoverContent } from "@mykopkb/core/styles/popover.styles";
|
||||
import { PopoverArrow, PopoverArrowTip } from ".";
|
||||
import { Box } from "@/components/ui/box";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("popoverApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(popoverContent, className)" v-bind="{ ...api?.getContentProps(), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<Box v-else raised="single" :class="cn(popoverContent, className)">
|
||||
<div>
|
||||
<slot />
|
||||
</div>
|
||||
<PopoverArrow>
|
||||
<PopoverArrowTip />
|
||||
</PopoverArrow>
|
||||
</Box>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/popover/PopoverDescription.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { type Api } from "@zag-js/popover";
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { inject } from "vue";
|
||||
import { popoverDescription } from "@mykopkb/core/styles/popover.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("popoverApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(popoverDescription, className)"
|
||||
v-bind="{ ...api?.getDescriptionProps(), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/popover/PopoverIndicator.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { type Api } from "@zag-js/popover";
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { inject } from "vue";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import { popoverIndicator } from "@mykopkb/core/styles/popover.styles";
|
||||
import { ChevronDown } from "@lucide/vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("popoverApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(popoverIndicator, className)" v-bind="{ ...api?.getIndicatorProps(), ...props, ...$attrs }">
|
||||
<slot v-if="$slots.default" />
|
||||
<ChevronDown v-else />
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/popover/PopoverPositioner.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { type Api } from "@zag-js/popover";
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { inject } from "vue";
|
||||
import { popoverPositioner } from "@mykopkb/core/styles/popover.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("popoverApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<Slot :class="cn(popoverPositioner, className)"
|
||||
v-bind="{ ...api?.getPositionerProps(), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</Teleport>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/popover/PopoverRoot.vue">
|
||||
{{`
|
||||
<script lang="ts" setup>
|
||||
import * as popover from "@zag-js/popover";
|
||||
import type { Props } from "@zag-js/popover";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { normalizeProps, useMachine } from "@zag-js/vue";
|
||||
import { computed, provide } from "vue";
|
||||
import { popoverRoot } from "@mykopkb/core/styles/popover.styles";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
open = undefined,
|
||||
closeOnInteractOutside = undefined,
|
||||
...props
|
||||
} = defineProps<Partial<Props> & { class?: string; asChild?: boolean }>();
|
||||
|
||||
const service = useMachine(popover.machine, {
|
||||
...props,
|
||||
open,
|
||||
closeOnInteractOutside,
|
||||
id: crypto.randomUUID(),
|
||||
});
|
||||
const api = computed(() => popover.connect(service, normalizeProps));
|
||||
|
||||
provide("popoverApi", api);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(popoverRoot, className)" v-bind="{ ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/popover/PopoverTitle.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { type Api } from "@zag-js/popover";
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { inject } from "vue";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import { popoverTitle } from "@mykopkb/core/styles/popover.styles";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("popoverApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(popoverTitle, className)" v-bind="{ ...api?.getTitleProps(), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/popover/PopoverTrigger.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { type Api } from "@zag-js/popover";
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { inject } from "vue";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import { popoverTrigger } from "@mykopkb/core/styles/popover.styles";
|
||||
import { PopoverIndicator } from ".";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("popoverApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot v-bind="{ ...api?.getTriggerProps(), ...props, ...$attrs }">
|
||||
<Button variant="ghost" v-if="!asChild" :class="cn(popoverTrigger, className)">
|
||||
<slot />
|
||||
<PopoverIndicator />
|
||||
</Button>
|
||||
<slot v-else />
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/popover/index.ts">
|
||||
{{ `
|
||||
export { default as PopoverArrow } from "./PopoverArrow.vue";
|
||||
export { default as PopoverArrowTip } from "./PopoverArrowTip.vue";
|
||||
export { default as PopoverContent } from "./PopoverContent.vue";
|
||||
export { default as PopoverDescription } from "./PopoverDescription.vue";
|
||||
export { default as PopoverIndicator } from "./PopoverIndicator.vue";
|
||||
export { default as PopoverPositioner } from "./PopoverPositioner.vue";
|
||||
export { default as PopoverRoot } from "./PopoverRoot.vue";
|
||||
export { default as PopoverTitle } from "./PopoverTitle.vue";
|
||||
export { default as PopoverTrigger } from "./PopoverTrigger.vue";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<SectionContent>
|
||||
Update the import paths to match your project setup.
|
||||
</SectionContent>
|
||||
</div>
|
||||
<div id="usage">
|
||||
<SectionTitle>Usage</SectionTitle>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
import {
|
||||
PopoverRoot,
|
||||
PopoverTrigger,
|
||||
PopoverPositioner,
|
||||
PopoverContent,
|
||||
PopoverTitle,
|
||||
PopoverDescription,
|
||||
} from "@/components/ui/popover";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<PopoverRoot>
|
||||
<PopoverTrigger class="w-56"> Open Popover </PopoverTrigger>
|
||||
<PopoverPositioner>
|
||||
<PopoverContent class="w-100">
|
||||
<PopoverTitle>Dimensions</PopoverTitle>
|
||||
<PopoverDescription>
|
||||
Set the dimensions for the layer.
|
||||
</PopoverDescription>
|
||||
<div class="grid gap-3 mt-4 mb-2">
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<Label for="width">Width</Label>
|
||||
<Input id="width" defaultValue="100%" class="col-span-2" />
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<Label for="maxWidth">Max. width</Label>
|
||||
<Input id="maxWidth" defaultValue="300px" class="col-span-2" />
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<Label for="height">Height</Label>
|
||||
<Input id="height" defaultValue="25px" class="col-span-2" />
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<Label for="maxHeight">Max. height</Label>
|
||||
<Input id="maxHeight" defaultValue="none" class="col-span-2" />
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</PopoverPositioner>
|
||||
</PopoverRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,255 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
ProgressRoot,
|
||||
ProgressLabel,
|
||||
ProgressValueText,
|
||||
ProgressCircle,
|
||||
ProgressCircleTrack,
|
||||
ProgressCircleRange,
|
||||
} from "@/components/ui/progress-circular";
|
||||
import {
|
||||
Preview,
|
||||
SectionTitle,
|
||||
SectionContent,
|
||||
InstallPackage,
|
||||
PreviewCode,
|
||||
} from "@/components/docs";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="preview" class="-mt-20">
|
||||
<Preview>
|
||||
<template #preview>
|
||||
<ProgressRoot :defaultValue="42">
|
||||
<ProgressLabel>Progress Circular</ProgressLabel>
|
||||
<ProgressCircle class="max-w-48">
|
||||
<ProgressCircleTrack />
|
||||
<ProgressCircleRange />
|
||||
</ProgressCircle>
|
||||
<ProgressValueText />
|
||||
</ProgressRoot>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<ProgressRoot :defaultValue="42">
|
||||
<ProgressLabel>Progress Circular</ProgressLabel>
|
||||
<ProgressCircle class="max-w-48">
|
||||
<ProgressCircleTrack />
|
||||
<ProgressCircleRange />
|
||||
</ProgressCircle>
|
||||
<ProgressValueText />
|
||||
</ProgressRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
</div>
|
||||
<div id="installation">
|
||||
<SectionTitle>Installation</SectionTitle>
|
||||
<SectionContent>Install the following dependencies:</SectionContent>
|
||||
<InstallPackage>add @zag-js/vue @zag-js/progress</InstallPackage>
|
||||
<SectionContent>
|
||||
Copy and paste the following code into your project.
|
||||
</SectionContent>
|
||||
<PreviewCode title="components/ui/progress-circular/ProgressCircle.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { progressCircle } from "@mykopkb/core/styles/progress-circular.styles";
|
||||
import type { Api } from "@zag-js/progress";
|
||||
import { inject } from "vue";
|
||||
|
||||
const { class: className, ...props } = defineProps<{
|
||||
class?: string;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("progressApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg :class="cn(progressCircle, className)" v-bind="{ ...api?.getCircleProps(), ...props, ...$attrs }">
|
||||
<slot />
|
||||
</svg>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/progress-circular/ProgressCircleRange.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { progressCircleRange } from "@mykopkb/core/styles/progress-circular.styles";
|
||||
import type { Api } from "@zag-js/progress";
|
||||
import { inject } from "vue";
|
||||
|
||||
const { class: className, ...props } = defineProps<{
|
||||
class?: string;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("progressApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<circle :class="cn(progressCircleRange, className)"
|
||||
v-bind="{ ...api?.getCircleRangeProps(), ...props, ...$attrs }" />
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/progress-circular/ProgressCircleTrack.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { progressCircleTrack } from "@mykopkb/core/styles/progress-circular.styles";
|
||||
import type { Api } from "@zag-js/progress";
|
||||
import { inject } from "vue";
|
||||
|
||||
const { class: className, ...props } = defineProps<{
|
||||
class?: string;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("progressApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<circle :class="cn(progressCircleTrack, className)"
|
||||
v-bind="{ ...api?.getCircleTrackProps(), ...props, ...$attrs }" />
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/progress-circular/ProgressLabel.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { progressLabel } from "@mykopkb/core/styles/progress-circular.styles";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import type { Api } from "@zag-js/progress";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("progressApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot v-bind="{ ...api?.getLabelProps(), ...props, ...$attrs }">
|
||||
<Label v-if="!asChild" :class="cn(progressLabel, className)">
|
||||
<slot />
|
||||
</Label>
|
||||
<slot v-else />
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/progress-circular/ProgressRoot.vue">
|
||||
{{`
|
||||
<script lang="ts" setup>
|
||||
import * as progress from "@zag-js/progress";
|
||||
import type { Props } from "@zag-js/progress";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { normalizeProps, useMachine } from "@zag-js/vue";
|
||||
import { computed, provide } from "vue";
|
||||
import { progressRoot } from "@mykopkb/core/styles/progress-circular.styles";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<Partial<Props> & { class?: string; asChild?: boolean }>();
|
||||
|
||||
const service = useMachine(progress.machine, {
|
||||
...props,
|
||||
id: crypto.randomUUID(),
|
||||
});
|
||||
const api = computed(() => progress.connect(service, normalizeProps));
|
||||
|
||||
provide("progressApi", api);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(progressRoot, className)" v-bind="{ ...props, ...$attrs, ...api.getRootProps() }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/progress-circular/ProgressValueText.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { progressValueText } from "@mykopkb/core/styles/progress-circular.styles";
|
||||
import type { Api } from "@zag-js/progress";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("progressApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn(progressValueText, className)" v-bind="{ ...api?.getValueTextProps(), ...props, ...$attrs }">
|
||||
\{\{ api?.valueAsString \}\}
|
||||
</div>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/progress-circular/index.ts">
|
||||
{{ `
|
||||
export { default as ProgressCircle } from "./ProgressCircle.vue";
|
||||
export { default as ProgressCircleRange } from "./ProgressCircleRange.vue";
|
||||
export { default as ProgressCircleTrack } from "./ProgressCircleTrack.vue";
|
||||
export { default as ProgressLabel } from "./ProgressLabel.vue";
|
||||
export { default as ProgressRoot } from "./ProgressRoot.vue";
|
||||
export { default as ProgressValueText } from "./ProgressValueText.vue";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<SectionContent>
|
||||
Update the import paths to match your project setup.
|
||||
</SectionContent>
|
||||
</div>
|
||||
<div id="usage">
|
||||
<SectionTitle>Usage</SectionTitle>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
import {
|
||||
ProgressRoot,
|
||||
ProgressLabel,
|
||||
ProgressValueText,
|
||||
ProgressCircle,
|
||||
ProgressCircleTrack,
|
||||
ProgressCircleRange,
|
||||
} from "@/components/ui/progress-circular";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<ProgressRoot :defaultValue="42">
|
||||
<ProgressLabel>Progress Circular</ProgressLabel>
|
||||
<ProgressCircle class="max-w-48">
|
||||
<ProgressCircleTrack />
|
||||
<ProgressCircleRange />
|
||||
</ProgressCircle>
|
||||
<ProgressValueText />
|
||||
</ProgressRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,227 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
ProgressRoot,
|
||||
ProgressLabel,
|
||||
ProgressValueText,
|
||||
ProgressTrack,
|
||||
ProgressRange,
|
||||
} from "@/components/ui/progress-linear";
|
||||
import {
|
||||
Preview,
|
||||
SectionTitle,
|
||||
SectionContent,
|
||||
InstallPackage,
|
||||
PreviewCode,
|
||||
} from "@/components/docs";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="preview" class="-mt-20">
|
||||
<Preview>
|
||||
<template #preview>
|
||||
<ProgressRoot :defaultValue="42">
|
||||
<ProgressLabel>Progress Linear</ProgressLabel>
|
||||
<ProgressTrack class="max-w-72">
|
||||
<ProgressRange />
|
||||
</ProgressTrack>
|
||||
<ProgressValueText />
|
||||
</ProgressRoot>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<ProgressRoot :defaultValue="42">
|
||||
<ProgressLabel>Progress Linear</ProgressLabel>
|
||||
<ProgressTrack class="max-w-72">
|
||||
<ProgressRange />
|
||||
</ProgressTrack>
|
||||
<ProgressValueText />
|
||||
</ProgressRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
</div>
|
||||
<div id="installation">
|
||||
<SectionTitle>Installation</SectionTitle>
|
||||
<SectionContent>Install the following dependencies:</SectionContent>
|
||||
<InstallPackage>add @zag-js/vue @zag-js/progress</InstallPackage>
|
||||
<SectionContent>
|
||||
Copy and paste the following code into your project.
|
||||
</SectionContent>
|
||||
<PreviewCode title="components/ui/progress-linear/ProgressLabel.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { progressLabel } from "@mykopkb/core/styles/progress-linear.styles";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import type { Api } from "@zag-js/progress";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("progressApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot v-bind="{ ...api?.getLabelProps(), ...props, ...$attrs }">
|
||||
<Label v-if="!asChild" :class="cn(progressLabel, className)">
|
||||
<slot />
|
||||
</Label>
|
||||
<slot v-else />
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/progress-linear/ProgressRange.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { progressRange } from "@mykopkb/core/styles/progress-linear.styles";
|
||||
import type { Api } from "@zag-js/progress";
|
||||
import { inject } from "vue";
|
||||
|
||||
const { class: className, ...props } = defineProps<{
|
||||
class?: string;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("progressApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn(progressRange, className)" v-bind="{ ...api?.getRangeProps(), ...props, ...$attrs }" />
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/progress-linear/ProgressRoot.vue">
|
||||
{{`
|
||||
<script lang="ts" setup>
|
||||
import * as progress from "@zag-js/progress";
|
||||
import type { Props } from "@zag-js/progress";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { normalizeProps, useMachine } from "@zag-js/vue";
|
||||
import { computed, provide } from "vue";
|
||||
import { progressRoot } from "@mykopkb/core/styles/progress-linear.styles";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<Partial<Props> & { class?: string; asChild?: boolean }>();
|
||||
|
||||
const service = useMachine(progress.machine, {
|
||||
...props,
|
||||
id: crypto.randomUUID(),
|
||||
});
|
||||
const api = computed(() => progress.connect(service, normalizeProps));
|
||||
|
||||
provide("progressApi", api);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(progressRoot, className)" v-bind="{ ...props, ...$attrs, ...api.getRootProps() }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/progress-linear/ProgressTrack.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { progressTrack } from "@mykopkb/core/styles/progress-linear.styles";
|
||||
import type { Api } from "@zag-js/progress";
|
||||
import { inject } from "vue";
|
||||
|
||||
const { class: className, ...props } = defineProps<{
|
||||
class?: string;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("progressApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn(progressTrack, className)" v-bind="{ ...api?.getTrackProps(), ...props, ...$attrs }">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/progress-linear/ProgressValueText.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { progressValueText } from "@mykopkb/core/styles/progress-linear.styles";
|
||||
import type { Api } from "@zag-js/progress";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("progressApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn(progressValueText, className)" v-bind="{ ...api?.getValueTextProps(), ...props, ...$attrs }">
|
||||
\{\{ api?.valueAsString \}\}
|
||||
</div>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/progress-linear/index.ts">
|
||||
{{ `
|
||||
export { default as ProgressLabel } from "./ProgressLabel.vue";
|
||||
export { default as ProgressRange } from "./ProgressRange.vue";
|
||||
export { default as ProgressRoot } from "./ProgressRoot.vue";
|
||||
export { default as ProgressTrack } from "./ProgressTrack.vue";
|
||||
export { default as ProgressValueText } from "./ProgressValueText.vue";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<SectionContent>
|
||||
Update the import paths to match your project setup.
|
||||
</SectionContent>
|
||||
</div>
|
||||
<div id="usage">
|
||||
<SectionTitle>Usage</SectionTitle>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
import {
|
||||
ProgressRoot,
|
||||
ProgressLabel,
|
||||
ProgressValueText,
|
||||
ProgressTrack,
|
||||
ProgressRange,
|
||||
} from "@/components/ui/progress-linear";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<ProgressRoot :defaultValue="42">
|
||||
<ProgressLabel>Progress Linear</ProgressLabel>
|
||||
<ProgressTrack class="max-w-72">
|
||||
<ProgressRange />
|
||||
</ProgressTrack>
|
||||
<ProgressValueText />
|
||||
</ProgressRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,325 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
RadioGroupRoot,
|
||||
RadioGroupLabel,
|
||||
RadioGroupItem,
|
||||
RadioGroupItemText,
|
||||
RadioGroupItemControl,
|
||||
} from "@/components/ui/radio-group";
|
||||
import {
|
||||
Preview,
|
||||
SectionTitle,
|
||||
SectionContent,
|
||||
InstallPackage,
|
||||
PreviewCode,
|
||||
} from "@/components/docs";
|
||||
const frameworks = ["React", "Solid", "Vue", "Svelte"];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="preview" class="-mt-20">
|
||||
<Preview>
|
||||
<template #preview>
|
||||
<RadioGroupRoot defaultValue="React">
|
||||
<RadioGroupLabel>Framework</RadioGroupLabel>
|
||||
<RadioGroupItem v-for="framework in frameworks" :key="framework" :value="framework">
|
||||
<RadioGroupItemControl />
|
||||
<RadioGroupItemText>{{ framework }}</RadioGroupItemText>
|
||||
</RadioGroupItem>
|
||||
</RadioGroupRoot>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
const frameworks = ["React", "Solid", "Vue", "Svelte"];
|
||||
|
||||
<RadioGroupRoot defaultValue="React">
|
||||
<RadioGroupLabel>Framework</RadioGroupLabel>
|
||||
<RadioGroupItem v-for="framework in frameworks" :key="framework" :value="framework">
|
||||
<RadioGroupItemControl />
|
||||
<RadioGroupItemText>\{\{ framework \}\}</RadioGroupItemText>
|
||||
</RadioGroupItem>
|
||||
</RadioGroupRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
</div>
|
||||
<div id="installation">
|
||||
<SectionTitle>Installation</SectionTitle>
|
||||
<SectionContent>Install the following dependencies:</SectionContent>
|
||||
<InstallPackage>add @zag-js/vue @zag-js/radio-group</InstallPackage>
|
||||
<SectionContent>
|
||||
Copy and paste the following code into your project.
|
||||
</SectionContent>
|
||||
<PreviewCode title="components/ui/radio-group/RadioGroupRoot.vue">
|
||||
{{`
|
||||
<script lang="ts" setup>
|
||||
import * as radioGroup from "@zag-js/radio-group";
|
||||
import type { Props } from "@zag-js/radio-group";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { normalizeProps, useMachine } from "@zag-js/vue";
|
||||
import { computed, provide } from "vue";
|
||||
import { radioGroupRoot } from "@mykopkb/core/styles/radio-group.styles";
|
||||
import { Dot } from "@lucide/vue";
|
||||
import { RadioGroupIndicator } from ".";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<Partial<Props> & { class?: string; asChild?: boolean }>();
|
||||
|
||||
const service = useMachine(radioGroup.machine, {
|
||||
...props,
|
||||
id: crypto.randomUUID(),
|
||||
});
|
||||
const api = computed(() => radioGroup.connect(service, normalizeProps));
|
||||
|
||||
provide("radioGroupApi", api);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(radioGroupRoot, className)" v-bind="{ ...props, ...$attrs, ...api.getRootProps() }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
<RadioGroupIndicator>
|
||||
<Dot />
|
||||
</RadioGroupIndicator>
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/radio-group/RadioGroupLabel.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { radioGroupLabel } from "@mykopkb/core/styles/radio-group.styles";
|
||||
import { label } from "@mykopkb/core/styles/label.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import type { Api } from "@zag-js/radio-group";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("radioGroupApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn([label, radioGroupLabel, className])"
|
||||
v-bind="{ ...api?.getLabelProps(), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<span v-else>
|
||||
<slot />
|
||||
</span>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/radio-group/RadioGroupIndicator.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { radioGroupIndicator } from "@mykopkb/core/styles/radio-group.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import type { Api } from "@zag-js/radio-group";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("radioGroupApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(radioGroupIndicator, className)" v-bind="{ ...api?.getIndicatorProps(), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/radio-group/RadioGroupItem.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { radioGroupItem } from "@mykopkb/core/styles/radio-group.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import { RadioGroupItemHiddenInput } from ".";
|
||||
import type { Api, ItemProps } from "@zag-js/radio-group";
|
||||
import { provide, inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<
|
||||
ItemProps & {
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}
|
||||
>();
|
||||
|
||||
const api = inject<Api>("radioGroupApi");
|
||||
|
||||
provide("radioGroupItem", props);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(radioGroupItem, className)" v-bind="{ ...api?.getItemProps(props), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<label v-else>
|
||||
<slot />
|
||||
<RadioGroupItemHiddenInput />
|
||||
</label>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/radio-group/RadioGroupItemText.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { radioGroupItemText } from "@mykopkb/core/styles/radio-group.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import type { Api, ItemProps } from "@zag-js/radio-group";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("radioGroupApi");
|
||||
const itemProps = inject<ItemProps>("radioGroupItem");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(radioGroupItemText, className)"
|
||||
v-bind="{ ...api?.getItemTextProps(itemProps!), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<span v-else>
|
||||
<slot />
|
||||
</span>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/radio-group/RadioGroupItemControl.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { radioGroupItemControl } from "@mykopkb/core/styles/radio-group.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import type { Api, ItemProps } from "@zag-js/radio-group";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("radioGroupApi");
|
||||
const itemProps = inject<ItemProps>("radioGroupItem");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(radioGroupItemControl, className)"
|
||||
v-bind="{ ...api?.getItemControlProps(itemProps!), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/radio-group/RadioGroupItemHiddenInput.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { radioGroupItemHiddenInput } from "@mykopkb/core/styles/radio-group.styles";
|
||||
import type { Api, ItemProps } from "@zag-js/radio-group";
|
||||
import { inject } from "vue";
|
||||
|
||||
const { class: className, ...props } = defineProps<{
|
||||
class?: string;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("radioGroupApi");
|
||||
const itemProps = inject<ItemProps>("radioGroupItem");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input :class="cn(radioGroupItemHiddenInput, className)"
|
||||
v-bind="{ ...api?.getItemHiddenInputProps(itemProps!), ...props, ...$attrs }" />
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/radio-group/index.ts">
|
||||
{{ `
|
||||
export { default as RadioGroupRoot } from "./RadioGroupRoot.vue";
|
||||
export { default as RadioGroupLabel } from "./RadioGroupLabel.vue";
|
||||
export { default as RadioGroupIndicator } from "./RadioGroupIndicator.vue";
|
||||
export { default as RadioGroupItem } from "./RadioGroupItem.vue";
|
||||
export { default as RadioGroupItemText } from "./RadioGroupItemText.vue";
|
||||
export { default as RadioGroupItemControl } from "./RadioGroupItemControl.vue";
|
||||
export { default as RadioGroupItemHiddenInput } from "./RadioGroupItemHiddenInput.vue";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<SectionContent>
|
||||
Update the import paths to match your project setup.
|
||||
</SectionContent>
|
||||
</div>
|
||||
<div id="usage">
|
||||
<SectionTitle>Usage</SectionTitle>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
import {
|
||||
RadioGroupRoot,
|
||||
RadioGroupLabel,
|
||||
RadioGroupItem,
|
||||
RadioGroupItemText,
|
||||
RadioGroupItemControl,
|
||||
} from "@/components/ui/radio-group";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<RadioGroupRoot defaultValue="React">
|
||||
<RadioGroupLabel>Framework</RadioGroupLabel>
|
||||
<RadioGroupItem v-for="framework in frameworks" :key="framework" :value="framework">
|
||||
<RadioGroupItemControl />
|
||||
<RadioGroupItemText>\{\{ framework \}\}</RadioGroupItemText>
|
||||
</RadioGroupItem>
|
||||
</RadioGroupRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,259 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
Preview,
|
||||
SectionTitle,
|
||||
SectionContent,
|
||||
InstallPackage,
|
||||
PreviewCode,
|
||||
} from "@/components/docs";
|
||||
import {
|
||||
ScrollAreaRoot,
|
||||
ScrollAreaViewport,
|
||||
ScrollAreaContent,
|
||||
ScrollAreaScrollbar,
|
||||
ScrollAreaThumb,
|
||||
ScrollAreaCorner,
|
||||
} from "@/components/ui/scroll-area";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="preview" class="-mt-20">
|
||||
<Preview>
|
||||
<template #preview>
|
||||
<ScrollAreaRoot class="h-72 w-70">
|
||||
<ScrollAreaViewport>
|
||||
<ScrollAreaContent>
|
||||
<div class="text-base font-medium mb-4">Scroll Area Example</div>
|
||||
<div v-for="i in 20" :key="i" class="mb-4 last:mb-0 opacity-80">
|
||||
This is line number {{ i }} of the scrollable content. It helps
|
||||
demonstrate how the custom scrollbar works within the Midone UI
|
||||
system.
|
||||
</div>
|
||||
</ScrollAreaContent>
|
||||
</ScrollAreaViewport>
|
||||
<ScrollAreaScrollbar>
|
||||
<ScrollAreaThumb />
|
||||
</ScrollAreaScrollbar>
|
||||
<ScrollAreaCorner />
|
||||
</ScrollAreaRoot>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ScrollAreaRoot,
|
||||
ScrollAreaViewport,
|
||||
ScrollAreaContent,
|
||||
ScrollAreaScrollbar,
|
||||
ScrollAreaThumb,
|
||||
ScrollAreaCorner,
|
||||
} from "@/components/ui/scroll-area";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ScrollAreaRoot class="h-72 w-70">
|
||||
<ScrollAreaViewport>
|
||||
<ScrollAreaContent>
|
||||
<div class="text-base font-medium mb-4">Scroll Area Example</div>
|
||||
<div v-for="i in 20" :key="i" class="mb-4 last:mb-0 opacity-80">
|
||||
This is line number \{\{ i \}\} of the scrollable content. It helps
|
||||
demonstrate how the custom scrollbar works within the Midone UI
|
||||
system.
|
||||
</div>
|
||||
</ScrollAreaContent>
|
||||
</ScrollAreaViewport>
|
||||
<ScrollAreaScrollbar>
|
||||
<ScrollAreaThumb />
|
||||
</ScrollAreaScrollbar>
|
||||
<ScrollAreaCorner />
|
||||
</ScrollAreaRoot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
</div>
|
||||
|
||||
<div id="installation">
|
||||
<SectionTitle>Installation</SectionTitle>
|
||||
<SectionContent>Install the following dependencies:</SectionContent>
|
||||
<InstallPackage>add @zag-js/vue @zag-js/scroll-area</InstallPackage>
|
||||
<SectionContent>
|
||||
Copy and paste the following code into your project.
|
||||
</SectionContent>
|
||||
<PreviewCode title="components/ui/scroll-area/ScrollAreaRoot.vue">
|
||||
{{`
|
||||
<script lang="ts" setup>
|
||||
import * as scrollArea from "@zag-js/scroll-area";
|
||||
import type { Props } from "@zag-js/scroll-area";
|
||||
import { useMachine, normalizeProps } from "@zag-js/vue";
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { computed, provide } from "vue";
|
||||
import { scrollAreaRoot } from "@mykopkb/core/styles/scroll-area.styles";
|
||||
|
||||
const { class: className, ...props } = defineProps<
|
||||
Partial<Props> & {
|
||||
class?: string;
|
||||
}
|
||||
>();
|
||||
|
||||
const service = useMachine(scrollArea.machine, {
|
||||
...props,
|
||||
id: crypto.randomUUID(),
|
||||
});
|
||||
|
||||
const api = computed(() => scrollArea.connect(service, normalizeProps));
|
||||
|
||||
provide("scrollAreaApi", api);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-bind="{ ...api.getRootProps() }" :class="cn(scrollAreaRoot, className)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/scroll-area/ScrollAreaViewport.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import type { Api } from "@zag-js/scroll-area";
|
||||
import { inject } from "vue";
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { scrollAreaViewport } from "@mykopkb/core/styles/scroll-area.styles";
|
||||
|
||||
const { class: className, ...props } = defineProps<{ class?: string }>();
|
||||
|
||||
const api = inject<Api<any>>("scrollAreaApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-bind="{ ...api?.getViewportProps(), ...props }" :class="cn(scrollAreaViewport, className)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/scroll-area/ScrollAreaContent.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import type { Api } from "@zag-js/scroll-area";
|
||||
import { inject } from "vue";
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { scrollAreaContent } from "@mykopkb/core/styles/scroll-area.styles";
|
||||
|
||||
const { class: className, ...props } = defineProps<{ class?: string }>();
|
||||
|
||||
const api = inject<Api<any>>("scrollAreaApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-bind="{ ...api?.getContentProps(), ...props }" :class="cn(scrollAreaContent, className)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/scroll-area/ScrollAreaScrollbar.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import type { Api, ScrollbarProps } from "@zag-js/scroll-area";
|
||||
import { inject } from "vue";
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { scrollAreaScrollbar } from "@mykopkb/core/styles/scroll-area.styles";
|
||||
|
||||
const { class: className, ...props } = defineProps<
|
||||
ScrollbarProps & { class?: string }
|
||||
>();
|
||||
|
||||
const api = inject<Api<any>>("scrollAreaApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-bind="{ ...api?.getScrollbarProps(), ...props }" :class="cn(scrollAreaScrollbar, className)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/scroll-area/ScrollAreaThumb.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import type { Api, ScrollbarProps } from "@zag-js/scroll-area";
|
||||
import { inject } from "vue";
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { scrollAreaThumb } from "@mykopkb/core/styles/scroll-area.styles";
|
||||
|
||||
const { class: className, ...props } = defineProps<
|
||||
ScrollbarProps & { class?: string }
|
||||
>();
|
||||
|
||||
const api = inject<Api<any>>("scrollAreaApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-bind="{ ...api?.getThumbProps(props), ...props }" :class="cn(scrollAreaThumb, className)" />
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/scroll-area/ScrollAreaCorner.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import type { Api } from "@zag-js/scroll-area";
|
||||
import { inject } from "vue";
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { scrollAreaCorner } from "@mykopkb/core/styles/scroll-area.styles";
|
||||
|
||||
const { class: className, ...props } = defineProps<{ class?: string }>();
|
||||
|
||||
const api = inject<Api<any>>("scrollAreaApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-bind="{ ...api?.getCornerProps(), ...props }" :class="cn(scrollAreaCorner, className)" />
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/scroll-area/index.ts">
|
||||
{{ `
|
||||
export { default as ScrollAreaRoot } from "./ScrollAreaRoot.vue";
|
||||
export { default as ScrollAreaViewport } from "./ScrollAreaViewport.vue";
|
||||
export { default as ScrollAreaContent } from "./ScrollAreaContent.vue";
|
||||
export { default as ScrollAreaScrollbar } from "./ScrollAreaScrollbar.vue";
|
||||
export { default as ScrollAreaThumb } from "./ScrollAreaThumb.vue";
|
||||
export { default as ScrollAreaCorner } from "./ScrollAreaCorner.vue";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</div>
|
||||
<div id="usage">
|
||||
<SectionTitle>Usage</SectionTitle>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
import {
|
||||
ScrollAreaRoot,
|
||||
ScrollAreaViewport,
|
||||
ScrollAreaContent,
|
||||
ScrollAreaScrollbar,
|
||||
ScrollAreaThumb,
|
||||
ScrollAreaCorner,
|
||||
} from "@/components/ui/scroll-area";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<ScrollAreaRoot class="h-72 w-70">
|
||||
<ScrollAreaViewport>
|
||||
<ScrollAreaContent>
|
||||
<!-- Scrollable content here -->
|
||||
</ScrollAreaContent>
|
||||
</ScrollAreaViewport>
|
||||
<ScrollAreaScrollbar>
|
||||
<ScrollAreaThumb />
|
||||
</ScrollAreaScrollbar>
|
||||
<ScrollAreaCorner />
|
||||
</ScrollAreaRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,886 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
SelectRoot,
|
||||
SelectLabel,
|
||||
SelectControl,
|
||||
SelectTrigger,
|
||||
SelectValueText,
|
||||
SelectContent,
|
||||
SelectItemGroup,
|
||||
SelectItemGroupLabel,
|
||||
SelectItem,
|
||||
SelectItemText,
|
||||
} from "@/components/ui/select";
|
||||
import * as select from "@zag-js/select";
|
||||
import {
|
||||
Preview,
|
||||
SectionTitle,
|
||||
SectionContent,
|
||||
InstallPackage,
|
||||
PreviewCode,
|
||||
} from "@/components/docs";
|
||||
const comboboxData = [
|
||||
{ label: "React", code: "react" },
|
||||
{ label: "Solid", code: "solid" },
|
||||
{ label: "Vue", code: "vue" },
|
||||
{ label: "Svelte", code: "svelte" },
|
||||
];
|
||||
|
||||
const timezoneData = [
|
||||
{
|
||||
label: "North America",
|
||||
items: [
|
||||
{ value: "est", label: "Eastern Standard Time (EST)" },
|
||||
{ value: "cst", label: "Central Standard Time (CST)" },
|
||||
{ value: "mst", label: "Mountain Standard Time (MST)" },
|
||||
{ value: "pst", label: "Pacific Standard Time (PST)" },
|
||||
{ value: "akst", label: "Alaska Standard Time (AKST)" },
|
||||
{ value: "hst", label: "Hawaii Standard Time (HST)" },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Europe & Africa",
|
||||
items: [
|
||||
{ value: "gmt", label: "Greenwich Mean Time (GMT)" },
|
||||
{ value: "cet", label: "Central European Time (CET)" },
|
||||
{ value: "eet", label: "Eastern European Time (EET)" },
|
||||
{ value: "west", label: "Western European Summer Time (WEST)" },
|
||||
{ value: "cat", label: "Central Africa Time (CAT)" },
|
||||
{ value: "eat", label: "East Africa Time (EAT)" },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Asia",
|
||||
items: [
|
||||
{ value: "msk", label: "Moscow Time (MSK)" },
|
||||
{ value: "ist", label: "India Standard Time (IST)" },
|
||||
{ value: "cst_china", label: "China Standard Time (CST)" },
|
||||
{ value: "jst", label: "Japan Standard Time (JST)" },
|
||||
{ value: "kst", label: "Korea Standard Time (KST)" },
|
||||
{
|
||||
value: "ist_indonesia",
|
||||
label: "Indonesia Central Standard Time (WITA)",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Australia & Pacific",
|
||||
items: [
|
||||
{ value: "awst", label: "Australian Western Standard Time (AWST)" },
|
||||
{ value: "acst", label: "Australian Central Standard Time (ACST)" },
|
||||
{ value: "aest", label: "Australian Eastern Standard Time (AEST)" },
|
||||
{ value: "nzst", label: "New Zealand Standard Time (NZST)" },
|
||||
{ value: "fjt", label: "Fiji Time (FJT)" },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "South America",
|
||||
items: [
|
||||
{ value: "art", label: "Argentina Time (ART)" },
|
||||
{ value: "bot", label: "Bolivia Time (BOT)" },
|
||||
{ value: "brt", label: "Brasilia Time (BRT)" },
|
||||
{ value: "clt", label: "Chile Standard Time (CLT)" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const collection = select.collection({
|
||||
items: comboboxData,
|
||||
itemToValue: (item) => item.label,
|
||||
});
|
||||
|
||||
const collectionTimezone = select.collection({
|
||||
items: timezoneData.flatMap((region) =>
|
||||
region.items.map((item) => ({
|
||||
region: region.label,
|
||||
value: item.value,
|
||||
label: item.label,
|
||||
}))
|
||||
),
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="preview" class="-mt-20">
|
||||
<Preview>
|
||||
<template #preview>
|
||||
<SelectRoot class="w-56" :collection="collection">
|
||||
<SelectLabel>Single</SelectLabel>
|
||||
<SelectControl>
|
||||
<SelectTrigger>
|
||||
<SelectValueText placeholder="Select a Framework" />
|
||||
</SelectTrigger>
|
||||
</SelectControl>
|
||||
<SelectContent>
|
||||
<SelectItemGroup>
|
||||
<SelectItemGroupLabel> Frameworks </SelectItemGroupLabel>
|
||||
<SelectItem v-for="item in collection.items" :key="item.code" :item="item">
|
||||
<SelectItemText>{{ item.label }}</SelectItemText>
|
||||
</SelectItem>
|
||||
</SelectItemGroup>
|
||||
</SelectContent>
|
||||
</SelectRoot>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{`
|
||||
const comboboxData = [
|
||||
{ label: "React", code: "react" },
|
||||
{ label: "Solid", code: "solid" },
|
||||
{ label: "Vue", code: "vue" },
|
||||
{ label: "Svelte", code: "svelte" },
|
||||
];
|
||||
|
||||
const timezoneData = [
|
||||
{
|
||||
label: "North America",
|
||||
items: [
|
||||
{ value: "est", label: "Eastern Standard Time (EST)" },
|
||||
{ value: "cst", label: "Central Standard Time (CST)" },
|
||||
{ value: "mst", label: "Mountain Standard Time (MST)" },
|
||||
{ value: "pst", label: "Pacific Standard Time (PST)" },
|
||||
{ value: "akst", label: "Alaska Standard Time (AKST)" },
|
||||
{ value: "hst", label: "Hawaii Standard Time (HST)" },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Europe & Africa",
|
||||
items: [
|
||||
{ value: "gmt", label: "Greenwich Mean Time (GMT)" },
|
||||
{ value: "cet", label: "Central European Time (CET)" },
|
||||
{ value: "eet", label: "Eastern European Time (EET)" },
|
||||
{ value: "west", label: "Western European Summer Time (WEST)" },
|
||||
{ value: "cat", label: "Central Africa Time (CAT)" },
|
||||
{ value: "eat", label: "East Africa Time (EAT)" },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Asia",
|
||||
items: [
|
||||
{ value: "msk", label: "Moscow Time (MSK)" },
|
||||
{ value: "ist", label: "India Standard Time (IST)" },
|
||||
{ value: "cst_china", label: "China Standard Time (CST)" },
|
||||
{ value: "jst", label: "Japan Standard Time (JST)" },
|
||||
{ value: "kst", label: "Korea Standard Time (KST)" },
|
||||
{
|
||||
value: "ist_indonesia",
|
||||
label: "Indonesia Central Standard Time (WITA)",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Australia & Pacific",
|
||||
items: [
|
||||
{ value: "awst", label: "Australian Western Standard Time (AWST)" },
|
||||
{ value: "acst", label: "Australian Central Standard Time (ACST)" },
|
||||
{ value: "aest", label: "Australian Eastern Standard Time (AEST)" },
|
||||
{ value: "nzst", label: "New Zealand Standard Time (NZST)" },
|
||||
{ value: "fjt", label: "Fiji Time (FJT)" },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "South America",
|
||||
items: [
|
||||
{ value: "art", label: "Argentina Time (ART)" },
|
||||
{ value: "bot", label: "Bolivia Time (BOT)" },
|
||||
{ value: "brt", label: "Brasilia Time (BRT)" },
|
||||
{ value: "clt", label: "Chile Standard Time (CLT)" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const collection = select.collection({
|
||||
items: comboboxData,
|
||||
itemToValue: (item) => item.label,
|
||||
});
|
||||
|
||||
const collectionTimezone = select.collection({
|
||||
items: timezoneData.flatMap((region) =>
|
||||
region.items.map((item) => ({
|
||||
region: region.label,
|
||||
value: item.value,
|
||||
label: item.label,
|
||||
}))
|
||||
),
|
||||
});
|
||||
|
||||
<SelectRoot class="w-56" :collection="collection">
|
||||
<SelectLabel>Single</SelectLabel>
|
||||
<SelectControl>
|
||||
<SelectTrigger>
|
||||
<SelectValueText placeholder="Select a Framework" />
|
||||
</SelectTrigger>
|
||||
</SelectControl>
|
||||
<SelectContent>
|
||||
<SelectItemGroup>
|
||||
<SelectItemGroupLabel> Frameworks </SelectItemGroupLabel>
|
||||
<SelectItem v-for="item in collection.items" :key="item.code" :item="item">
|
||||
<SelectItemText>\{\{ item.label \}\}</SelectItemText>
|
||||
</SelectItem>
|
||||
</SelectItemGroup>
|
||||
</SelectContent>
|
||||
</SelectRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
</div>
|
||||
<div id="installation">
|
||||
<SectionTitle>Installation</SectionTitle>
|
||||
<SectionContent>Install the following dependencies:</SectionContent>
|
||||
<InstallPackage>add @zag-js/vue @zag-js/select</InstallPackage>
|
||||
<SectionContent>
|
||||
Copy and paste the following code into your project.
|
||||
</SectionContent>
|
||||
<PreviewCode title="components/ui/select/SelectClearTrigger.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { selectClearTrigger } from "@mykopkb/core/styles/select.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import type { Api } from "@zag-js/select";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("selectApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot v-bind="{ ...api?.getClearTriggerProps(), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<span v-else :class="cn(selectClearTrigger, className)">
|
||||
<slot />
|
||||
</span>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/select/SelectContent.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { selectContent } from "@mykopkb/core/styles/select.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import { Box } from "@/components/ui/box";
|
||||
import { SelectPositioner } from ".";
|
||||
import type { Api } from "@zag-js/select";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("selectApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<SelectPositioner>
|
||||
<Slot v-bind="{ ...api?.getContentProps(), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<Box v-else raised="single" :class="cn(selectContent, className)">
|
||||
<div>
|
||||
<slot />
|
||||
</div>
|
||||
</Box>
|
||||
</Slot>
|
||||
</SelectPositioner>
|
||||
</Teleport>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/select/SelectControl.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { selectControl } from "@mykopkb/core/styles/select.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import type { Api } from "@zag-js/select";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("selectApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(selectControl, className)" v-bind="{ ...api?.getControlProps(), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/select/SelectHiddenSelect.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { selectHiddenSelect } from "@mykopkb/core/styles/select.styles";
|
||||
import type { Api } from "@zag-js/select";
|
||||
import { inject } from "vue";
|
||||
|
||||
const { class: className, ...props } = defineProps<{
|
||||
class?: string;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("selectApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<select :class="cn(selectHiddenSelect, className)"
|
||||
v-bind="{ ...api?.getHiddenSelectProps(), ...props, ...$attrs }" />
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/select/SelectIndicator.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { selectIndicator } from "@mykopkb/core/styles/select.styles";
|
||||
import { ChevronDownIcon } from "@lucide/vue";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import type { Api } from "@zag-js/select";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("selectApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(selectIndicator, className)" v-bind="{ ...api?.getIndicatorProps(), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot v-if="$slots.default" />
|
||||
<ChevronDownIcon v-else class="size-3.5" />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/select/SelectItem.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { selectItem } from "@mykopkb/core/styles/select.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import { SelectItemIndicator } from ".";
|
||||
import type { Api, ItemProps } from "@zag-js/select";
|
||||
import { provide, inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<
|
||||
ItemProps & {
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}
|
||||
>();
|
||||
|
||||
const api = inject<Api>("selectApi");
|
||||
|
||||
provide("selectItem", props);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(selectItem, className)" v-bind="{ ...api?.getItemProps(props), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
<SelectItemIndicator />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/select/SelectItemGroup.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { selectItemGroup } from "@mykopkb/core/styles/select.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import type { Api } from "@zag-js/select";
|
||||
import { provide, inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("selectApi");
|
||||
const itemGroupId = { id: crypto.randomUUID() };
|
||||
|
||||
provide("selectItemGroup", props);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(selectItemGroup, className)"
|
||||
v-bind="{ ...api?.getItemGroupProps(itemGroupId), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/select/SelectItemGroupLabel.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { selectItemGroupLabel } from "@mykopkb/core/styles/select.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import type { Api, ItemGroupProps } from "@zag-js/select";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("selectApi");
|
||||
const itemGroupId = inject<ItemGroupProps>("selectItemGroup");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(selectItemGroupLabel, className)" v-bind="{ ...api?.getItemGroupLabelProps({
|
||||
htmlFor: itemGroupId?.id!,
|
||||
}), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<label v-else>
|
||||
<slot />
|
||||
</label>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/select/SelectItemIndicator.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { selectItemIndicator } from "@mykopkb/core/styles/select.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import type { Api, ItemProps } from "@zag-js/select";
|
||||
import { Check } from "@lucide/vue";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("selectApi");
|
||||
const item = inject<ItemProps>("selectItem");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(selectItemIndicator, className)"
|
||||
v-bind="{ ...api?.getItemIndicatorProps(item!), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot v-if="$slots.default" />
|
||||
<Check v-else class="size-3.5" />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/select/SelectItemText.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { selectItemText } from "@mykopkb/core/styles/select.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import type { Api, ItemProps } from "@zag-js/select";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("selectApi");
|
||||
const item = inject<ItemProps>("selectItem");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(selectItemText, className)" v-bind="{ ...api?.getItemTextProps(item!), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/select/SelectLabel.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { selectLabel } from "@mykopkb/core/styles/select.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import type { Api } from "@zag-js/select";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("selectApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot v-bind="{ ...api?.getLabelProps(), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<Label v-else :class="cn(selectLabel, className)">
|
||||
<slot />
|
||||
</Label>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/select/SelectPositioner.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { selectPositioner } from "@mykopkb/core/styles/select.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import type { Api } from "@zag-js/select";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("selectApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(selectPositioner, className)" v-bind="{ ...api?.getPositionerProps(), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/select/SelectRoot.vue">
|
||||
{{`
|
||||
<script lang="ts" setup>
|
||||
import * as select from "@zag-js/select";
|
||||
import type { Props } from "@zag-js/select";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { normalizeProps, useMachine } from "@zag-js/vue";
|
||||
import { computed, provide } from "vue";
|
||||
import { selectRoot } from "@mykopkb/core/styles/select.styles";
|
||||
import { SelectHiddenSelect } from ".";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
multiple = undefined,
|
||||
open = undefined,
|
||||
closeOnSelect = undefined,
|
||||
...props
|
||||
} = defineProps<Partial<Props> & { class?: string; asChild?: boolean }>();
|
||||
|
||||
const service = useMachine(select.machine, {
|
||||
...props,
|
||||
multiple,
|
||||
open,
|
||||
closeOnSelect,
|
||||
id: crypto.randomUUID(),
|
||||
});
|
||||
const api = computed(() => select.connect(service, normalizeProps));
|
||||
|
||||
provide("selectApi", api);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(selectRoot, className)" :data-multiple="multiple"
|
||||
v-bind="{ ...props, ...$attrs, ...api.getRootProps() }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
<SelectHiddenSelect />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/select/SelectTrigger.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { selectTrigger } from "@mykopkb/core/styles/select.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { SelectClearTrigger, SelectIndicator } from ".";
|
||||
import type { Api } from "@zag-js/select";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("selectApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot v-bind="{ ...api?.getTriggerProps(), ...props, ...$attrs }">
|
||||
<Button variant="ghost" v-if="!asChild" :class="cn(selectTrigger, className)">
|
||||
<slot />
|
||||
<SelectClearTrigger>Clear</SelectClearTrigger>
|
||||
<SelectIndicator />
|
||||
</Button>
|
||||
<slot v-else />
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/select/SelectValueText.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { selectValueText } from "@mykopkb/core/styles/select.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import type { Api } from "@zag-js/select";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
placeholder?: string;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("selectApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(selectValueText, className)" v-bind="{ ...api?.getValueTextProps(), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>\{\{ api?.valueAsString || props.placeholder \}\}</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/select/index.ts">
|
||||
{{ `
|
||||
export { default as SelectClearTrigger } from "./SelectClearTrigger.vue";
|
||||
export { default as SelectContent } from "./SelectContent.vue";
|
||||
export { default as SelectControl } from "./SelectControl.vue";
|
||||
export { default as SelectHiddenSelect } from "./SelectHiddenSelect.vue";
|
||||
export { default as SelectIndicator } from "./SelectIndicator.vue";
|
||||
export { default as SelectItem } from "./SelectItem.vue";
|
||||
export { default as SelectItemGroup } from "./SelectItemGroup.vue";
|
||||
export { default as SelectItemGroupLabel } from "./SelectItemGroupLabel.vue";
|
||||
export { default as SelectItemIndicator } from "./SelectItemIndicator.vue";
|
||||
export { default as SelectItemText } from "./SelectItemText.vue";
|
||||
export { default as SelectLabel } from "./SelectLabel.vue";
|
||||
export { default as SelectPositioner } from "./SelectPositioner.vue";
|
||||
export { default as SelectRoot } from "./SelectRoot.vue";
|
||||
export { default as SelectTrigger } from "./SelectTrigger.vue";
|
||||
export { default as SelectValueText } from "./SelectValueText.vue";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<SectionContent>
|
||||
Update the import paths to match your project setup.
|
||||
</SectionContent>
|
||||
</div>
|
||||
<div id="usage">
|
||||
<SectionTitle>Usage</SectionTitle>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
import {
|
||||
SelectRoot,
|
||||
SelectLabel,
|
||||
SelectControl,
|
||||
SelectTrigger,
|
||||
SelectValueText,
|
||||
SelectContent,
|
||||
SelectItemGroup,
|
||||
SelectItemGroupLabel,
|
||||
SelectItem,
|
||||
SelectItemText,
|
||||
} from "@/components/ui/select";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<SelectRoot class="w-56" :collection="collection">
|
||||
<SelectLabel>Single</SelectLabel>
|
||||
<SelectControl>
|
||||
<SelectTrigger>
|
||||
<SelectValueText placeholder="Select a Framework" />
|
||||
</SelectTrigger>
|
||||
</SelectControl>
|
||||
<SelectContent>
|
||||
<SelectItemGroup>
|
||||
<SelectItemGroupLabel> Frameworks </SelectItemGroupLabel>
|
||||
<SelectItem v-for="item in collection.items" :key="item.code" :item="item">
|
||||
<SelectItemText>\{\{ item.label \}\}</SelectItemText>
|
||||
</SelectItem>
|
||||
</SelectItemGroup>
|
||||
</SelectContent>
|
||||
</SelectRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</div>
|
||||
<div id="variants">
|
||||
<SectionTitle>Variants</SectionTitle>
|
||||
<SectionContent>A collection of components you can use.</SectionContent>
|
||||
<Preview>
|
||||
<template #preview>
|
||||
<SelectRoot class="w-56" :collection="collection" multiple>
|
||||
<SelectLabel>Multiple</SelectLabel>
|
||||
<SelectControl>
|
||||
<SelectTrigger>
|
||||
<SelectValueText placeholder="Select a Framework" />
|
||||
</SelectTrigger>
|
||||
</SelectControl>
|
||||
<SelectContent>
|
||||
<SelectItemGroup>
|
||||
<SelectItemGroupLabel> Frameworks </SelectItemGroupLabel>
|
||||
<SelectItem v-for="item in collection.items" :key="item.code" :item="item">
|
||||
<SelectItemText>{{ item.label }}</SelectItemText>
|
||||
</SelectItem>
|
||||
</SelectItemGroup>
|
||||
</SelectContent>
|
||||
</SelectRoot>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<SelectRoot class="w-56" :collection="collection" multiple>
|
||||
<SelectLabel>Multiple</SelectLabel>
|
||||
<SelectControl>
|
||||
<SelectTrigger>
|
||||
<SelectValueText placeholder="Select a Framework" />
|
||||
</SelectTrigger>
|
||||
</SelectControl>
|
||||
<SelectContent>
|
||||
<SelectItemGroup>
|
||||
<SelectItemGroupLabel> Frameworks </SelectItemGroupLabel>
|
||||
<SelectItem v-for="item in collection.items" :key="item.code" :item="item">
|
||||
<SelectItemText>\{\{ item.label \}\}</SelectItemText>
|
||||
</SelectItem>
|
||||
</SelectItemGroup>
|
||||
</SelectContent>
|
||||
</SelectRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
<Preview>
|
||||
<template #preview>
|
||||
<SelectRoot class="w-56" :collection="collectionTimezone" multiple>
|
||||
<SelectLabel>Scrollable</SelectLabel>
|
||||
<SelectControl>
|
||||
<SelectTrigger>
|
||||
<SelectValueText placeholder="Select a Timezone" />
|
||||
</SelectTrigger>
|
||||
</SelectControl>
|
||||
<SelectContent>
|
||||
<SelectItemGroup v-for="item in timezoneData" :key="item.label">
|
||||
<SelectItemGroupLabel>{{ item.label }}</SelectItemGroupLabel>
|
||||
<SelectItem v-for="timezoneItem in item.items" :key="timezoneItem.value" :item="timezoneItem.value">
|
||||
<SelectItemText>{{ timezoneItem.label }}</SelectItemText>
|
||||
</SelectItem>
|
||||
</SelectItemGroup>
|
||||
</SelectContent>
|
||||
</SelectRoot>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<SelectRoot class="w-56" :collection="collectionTimezone" multiple>
|
||||
<SelectLabel>Scrollable</SelectLabel>
|
||||
<SelectControl>
|
||||
<SelectTrigger>
|
||||
<SelectValueText placeholder="Select a Timezone" />
|
||||
</SelectTrigger>
|
||||
</SelectControl>
|
||||
<SelectContent>
|
||||
<SelectItemGroup v-for="item in timezoneData" :key="item.label">
|
||||
<SelectItemGroupLabel>\{\{ item.label \}\}</SelectItemGroupLabel>
|
||||
<SelectItem v-for="timezoneItem in item.items" :key="timezoneItem.value" :item="timezoneItem.value">
|
||||
<SelectItemText>\{\{ timezoneItem.label \}\}</SelectItemText>
|
||||
</SelectItem>
|
||||
</SelectItemGroup>
|
||||
</SelectContent>
|
||||
</SelectRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,508 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
SheetRoot,
|
||||
SheetTrigger,
|
||||
SheetContent,
|
||||
SheetTitle,
|
||||
SheetDescription,
|
||||
SheetCloseTrigger,
|
||||
} from "@/components/ui/sheet";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { SquareX, Save, ExternalLink } from "@lucide/vue";
|
||||
import {
|
||||
Preview,
|
||||
SectionTitle,
|
||||
SectionContent,
|
||||
InstallPackage,
|
||||
PreviewCode,
|
||||
} from "@/components/docs";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="preview" class="-mt-20">
|
||||
<Preview>
|
||||
<template #preview>
|
||||
<SheetRoot>
|
||||
<SheetTrigger>Open Sheet</SheetTrigger>
|
||||
<SheetContent>
|
||||
<SheetTitle>Sheet Title</SheetTitle>
|
||||
<SheetDescription>
|
||||
Make changes to your profile here. Click save when you're done.
|
||||
</SheetDescription>
|
||||
<div class="grid gap-4 mt-2">
|
||||
<div class="grid gap-2.5">
|
||||
<Label for="name-1">Name</Label>
|
||||
<Input id="name-1" name="name" defaultValue="Pedro Duarte" />
|
||||
</div>
|
||||
<div class="grid gap-2.5">
|
||||
<Label for="username-1">Username</Label>
|
||||
<Input id="username-1" name="username" defaultValue="@peduarte" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2 justify-end mt-7">
|
||||
<SheetCloseTrigger>
|
||||
<SquareX />
|
||||
Close
|
||||
</SheetCloseTrigger>
|
||||
<Button variant="primary">
|
||||
<Save />
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
<SheetCloseTrigger />
|
||||
</SheetContent>
|
||||
</SheetRoot>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<SheetRoot>
|
||||
<SheetTrigger>Open Sheet</SheetTrigger>
|
||||
<SheetContent>
|
||||
<SheetTitle>Sheet Title</SheetTitle>
|
||||
<SheetDescription>
|
||||
Make changes to your profile here. Click save when you're done.
|
||||
</SheetDescription>
|
||||
<div class="grid gap-4 mt-2">
|
||||
<div class="grid gap-2.5">
|
||||
<Label for="name-1">Name</Label>
|
||||
<Input id="name-1" name="name" defaultValue="Pedro Duarte" />
|
||||
</div>
|
||||
<div class="grid gap-2.5">
|
||||
<Label for="username-1">Username</Label>
|
||||
<Input id="username-1" name="username" defaultValue="@peduarte" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2 justify-end mt-7">
|
||||
<SheetCloseTrigger>
|
||||
<SquareX />
|
||||
Close
|
||||
</SheetCloseTrigger>
|
||||
<Button variant="primary">
|
||||
<Save />
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
<SheetCloseTrigger />
|
||||
</SheetContent>
|
||||
</SheetRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
</div>
|
||||
<div id="installation">
|
||||
<SectionTitle>Installation</SectionTitle>
|
||||
<SectionContent>Install the following dependencies:</SectionContent>
|
||||
<InstallPackage>add @zag-js/vue @zag-js/dialog</InstallPackage>
|
||||
<SectionContent>
|
||||
Copy and paste the following code into your project.
|
||||
</SectionContent>
|
||||
<PreviewCode title="components/ui/sheet/SheetRoot.vue">
|
||||
{{`
|
||||
<script lang="ts" setup>
|
||||
import { provide, computed } from "vue";
|
||||
import * as dialog from "@zag-js/dialog";
|
||||
import type { Props } from "@zag-js/dialog";
|
||||
import { useMachine, normalizeProps } from "@zag-js/vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
open = undefined,
|
||||
closeOnInteractOutside = undefined,
|
||||
...props
|
||||
} = defineProps<
|
||||
Partial<Props> & {
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}
|
||||
>();
|
||||
|
||||
const service = useMachine(dialog.machine, {
|
||||
...props,
|
||||
get open() {
|
||||
return open;
|
||||
},
|
||||
closeOnInteractOutside,
|
||||
id: crypto.randomUUID(),
|
||||
});
|
||||
|
||||
const api = computed(() => dialog.connect(service, normalizeProps));
|
||||
|
||||
provide("sheetApi", api);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<slot />
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/sheet/SheetTrigger.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { sheetTrigger } from "@mykopkb/core/styles/sheet.styles";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import type { Api } from "@zag-js/dialog";
|
||||
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>("sheetApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot v-bind="{ ...props, ...$attrs, ...api?.getTriggerProps() }">
|
||||
<Button variant="secondary" look="outline" v-if="!asChild" :class="cn(sheetTrigger, className)">
|
||||
<slot />
|
||||
</Button>
|
||||
<slot v-else />
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/sheet/SheetBackdrop.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { sheetBackdrop } from "@mykopkb/core/styles/sheet.styles";
|
||||
import type { Api } from "@zag-js/dialog";
|
||||
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>("sheetApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(sheetBackdrop, className)" v-bind="{ ...props, ...$attrs, ...api?.getBackdropProps() }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/sheet/SheetPositioner.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { sheetPositioner } from "@mykopkb/core/styles/sheet.styles";
|
||||
import type { Api } from "@zag-js/dialog";
|
||||
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>("sheetApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(sheetPositioner, className)" v-bind="{ ...props, ...$attrs, ...api?.getPositionerProps() }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/sheet/SheetContent.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { sheetContent } from "@mykopkb/core/styles/sheet.styles";
|
||||
import { Box } from "@/components/ui/box";
|
||||
import { SheetBackdrop, SheetPositioner } from "@/components/ui/sheet";
|
||||
import type { Api } from "@zag-js/dialog";
|
||||
import { inject } from "vue";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
side = "right",
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
side?: "top" | "right" | "bottom" | "left";
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("sheetApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<SheetBackdrop />
|
||||
<SheetPositioner>
|
||||
<Slot v-bind="{ ...props, ...$attrs, ...api?.getContentProps() }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<Box raised="double" :data-side="side" :class="cn(sheetContent, className)" v-bind="{ ...props }">
|
||||
<div>
|
||||
<slot />
|
||||
</div>
|
||||
</Box>
|
||||
</div>
|
||||
</Slot>
|
||||
</SheetPositioner>
|
||||
</Teleport>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/sheet/SheetTitle.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { sheetTitle } from "@mykopkb/core/styles/sheet.styles";
|
||||
import type { Api } from "@zag-js/dialog";
|
||||
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>("sheetApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(sheetTitle, className)" v-bind="{ ...props, ...$attrs, ...api?.getTitleProps() }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/sheet/SheetDescription.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { sheetDescription } from "@mykopkb/core/styles/sheet.styles";
|
||||
import type { Api } from "@zag-js/dialog";
|
||||
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>("sheetApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(sheetDescription, className)" v-bind="{ ...props, ...$attrs, ...api?.getDescriptionProps() }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/sheet/SheetCloseTrigger.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { sheetCloseTrigger } from "@mykopkb/core/styles/sheet.styles";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import type { Api } from "@zag-js/dialog";
|
||||
import {
|
||||
buttonVariants,
|
||||
type ButtonVariants,
|
||||
} from "@mykopkb/core/styles/button.styles";
|
||||
import { X } from "@lucide/vue";
|
||||
import { inject } from "vue";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
look = "outline",
|
||||
variant = "secondary",
|
||||
size,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<
|
||||
ButtonVariants & {
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}
|
||||
>();
|
||||
|
||||
const api = inject<Api>("sheetApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot v-bind="{ ...props, ...$attrs, ...api?.getCloseTriggerProps() }">
|
||||
<Button variant="ghost" v-if="!$slots.default" :class="cn(sheetCloseTrigger, className)"
|
||||
v-bind="{ ...props }">
|
||||
<X class="size-4" />
|
||||
</Button>
|
||||
<template v-else>
|
||||
<slot v-if="asChild" />
|
||||
<Button v-else :class="
|
||||
cn(buttonVariants({ look, variant, size, className }), className)
|
||||
">
|
||||
<slot />
|
||||
</Button>
|
||||
</template>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/sheet/index.ts">
|
||||
{{ `
|
||||
export { default as SheetRoot } from "./SheetRoot.vue";
|
||||
export { default as SheetTrigger } from "./SheetTrigger.vue";
|
||||
export { default as SheetBackdrop } from "./SheetBackdrop.vue";
|
||||
export { default as SheetPositioner } from "./SheetPositioner.vue";
|
||||
export { default as SheetContent } from "./SheetContent.vue";
|
||||
export { default as SheetTitle } from "./SheetTitle.vue";
|
||||
export { default as SheetDescription } from "./SheetDescription.vue";
|
||||
export { default as SheetCloseTrigger } from "./SheetCloseTrigger.vue";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<SectionContent>
|
||||
Update the import paths to match your project setup.
|
||||
</SectionContent>
|
||||
</div>
|
||||
<div id="usage">
|
||||
<SectionTitle>Usage</SectionTitle>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
import {
|
||||
SheetRoot,
|
||||
SheetTrigger,
|
||||
SheetContent,
|
||||
SheetTitle,
|
||||
SheetDescription,
|
||||
SheetCloseTrigger,
|
||||
} from "@/components/ui/sheet";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<SheetRoot>
|
||||
<SheetTrigger>Open Sheet</SheetTrigger>
|
||||
<SheetContent>
|
||||
<SheetTitle>Sheet Title</SheetTitle>
|
||||
<SheetDescription>
|
||||
Make changes to your profile here. Click save when you're done.
|
||||
</SheetDescription>
|
||||
<div class="grid gap-4 mt-2">
|
||||
<div class="grid gap-2.5">
|
||||
<Label for="name-1">Name</Label>
|
||||
<Input id="name-1" name="name" defaultValue="Pedro Duarte" />
|
||||
</div>
|
||||
<div class="grid gap-2.5">
|
||||
<Label for="username-1">Username</Label>
|
||||
<Input id="username-1" name="username" defaultValue="@peduarte" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2 justify-end mt-7">
|
||||
<SheetCloseTrigger>
|
||||
<SquareX />
|
||||
Close
|
||||
</SheetCloseTrigger>
|
||||
<Button variant="primary">
|
||||
<Save />
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
<SheetCloseTrigger />
|
||||
</SheetContent>
|
||||
</SheetRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</div>
|
||||
<div id="variants">
|
||||
<SectionTitle>Variants</SectionTitle>
|
||||
<SectionContent>A collection of components you can use.</SectionContent>
|
||||
<Preview>
|
||||
<template #preview>
|
||||
<SheetRoot>
|
||||
<SheetTrigger>Custom Close</SheetTrigger>
|
||||
<SheetContent>
|
||||
<SheetTitle>Share Link</SheetTitle>
|
||||
<SheetDescription>
|
||||
Anyone who has this link will be able to view this.
|
||||
</SheetDescription>
|
||||
<div class="grid gap-4 mt-2">
|
||||
<Input id="name-1" name="name" defaultValue="https://midone-ui.com/docs/installation" />
|
||||
</div>
|
||||
<div class="flex gap-2 mt-5">
|
||||
<SheetCloseTrigger>
|
||||
<ExternalLink />
|
||||
Share Link
|
||||
</SheetCloseTrigger>
|
||||
</div>
|
||||
</SheetContent>
|
||||
</SheetRoot>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<SheetRoot>
|
||||
<SheetTrigger>Custom Close</SheetTrigger>
|
||||
<SheetContent>
|
||||
<SheetTitle>Share Link</SheetTitle>
|
||||
<SheetDescription>
|
||||
Anyone who has this link will be able to view this.
|
||||
</SheetDescription>
|
||||
<div class="grid gap-4 mt-2">
|
||||
<Input id="name-1" name="name" defaultValue="https://midone-ui.com/docs/installation" />
|
||||
</div>
|
||||
<div class="flex gap-2 mt-5">
|
||||
<SheetCloseTrigger>
|
||||
<ExternalLink />
|
||||
Share Link
|
||||
</SheetCloseTrigger>
|
||||
</div>
|
||||
</SheetContent>
|
||||
</SheetRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,493 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
SliderRoot,
|
||||
SliderLabel,
|
||||
SliderValueText,
|
||||
SliderControl,
|
||||
SliderTrack,
|
||||
SliderRange,
|
||||
SliderThumb,
|
||||
SliderHiddenInput,
|
||||
SliderMarkerGroup,
|
||||
SliderMarker,
|
||||
} from "@/components/ui/slider";
|
||||
import {
|
||||
Preview,
|
||||
SectionTitle,
|
||||
SectionContent,
|
||||
InstallPackage,
|
||||
PreviewCode,
|
||||
} from "@/components/docs";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="preview" class="-mt-20">
|
||||
<Preview>
|
||||
<template #preview>
|
||||
<SliderRoot class="w-72" :defaultValue="[20]">
|
||||
<SliderLabel>Max Items</SliderLabel>
|
||||
<SliderControl>
|
||||
<SliderTrack>
|
||||
<SliderRange />
|
||||
</SliderTrack>
|
||||
<SliderThumb :index="0">
|
||||
<SliderHiddenInput />
|
||||
</SliderThumb>
|
||||
</SliderControl>
|
||||
<div class="flex items-center text-xs gap-1 font-medium justify-center opacity-70">
|
||||
<SliderValueText /> Items
|
||||
</div>
|
||||
</SliderRoot>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<SliderRoot class="w-72" :defaultValue="[20]">
|
||||
<SliderLabel>Max Items</SliderLabel>
|
||||
<SliderControl>
|
||||
<SliderTrack>
|
||||
<SliderRange />
|
||||
</SliderTrack>
|
||||
<SliderThumb :index="0">
|
||||
<SliderHiddenInput />
|
||||
</SliderThumb>
|
||||
</SliderControl>
|
||||
<div class="flex items-center text-xs gap-1 font-medium justify-center opacity-70">
|
||||
<SliderValueText /> Items
|
||||
</div>
|
||||
</SliderRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
</div>
|
||||
<div id="installation">
|
||||
<SectionTitle>Installation</SectionTitle>
|
||||
<SectionContent>Install the following dependencies:</SectionContent>
|
||||
<InstallPackage>add @zag-js/vue @zag-js/slider</InstallPackage>
|
||||
<SectionContent>
|
||||
Copy and paste the following code into your project.
|
||||
</SectionContent>
|
||||
<PreviewCode title="components/ui/slider/SliderRoot.vue">
|
||||
{{`
|
||||
<script lang="ts" setup>
|
||||
import * as slider from "@zag-js/slider";
|
||||
import type { Props } from "@zag-js/slider";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { normalizeProps, useMachine } from "@zag-js/vue";
|
||||
import { computed, provide } from "vue";
|
||||
import { sliderRoot } from "@mykopkb/core/styles/slider.styles";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<Partial<Props> & { class?: string; asChild?: boolean }>();
|
||||
|
||||
const service = useMachine(slider.machine, {
|
||||
...props,
|
||||
id: crypto.randomUUID(),
|
||||
});
|
||||
const api = computed(() => slider.connect(service, normalizeProps));
|
||||
|
||||
provide("sliderApi", api);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(sliderRoot, className)" v-bind="{ ...props, ...$attrs, ...api.getRootProps() }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/slider/SliderLabel.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { sliderLabel } from "@mykopkb/core/styles/slider.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import type { Api } from "@zag-js/slider";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("sliderApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot v-bind="{ ...api?.getLabelProps(), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<Label v-else :class="cn(sliderLabel, className)">
|
||||
<slot />
|
||||
</Label>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/slider/SliderValueText.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { sliderValueText } from "@mykopkb/core/styles/slider.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import type { Api } from "@zag-js/slider";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("sliderApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(sliderValueText, className)" v-bind="{ ...api?.getValueTextProps(), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<output v-else>\{\{ api?.value?.[0] \}\}</output>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/slider/SliderControl.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { sliderControl } from "@mykopkb/core/styles/slider.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import type { Api } from "@zag-js/slider";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("sliderApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(sliderControl, className)" v-bind="{ ...api?.getControlProps(), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/slider/SliderTrack.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { sliderTrack } from "@mykopkb/core/styles/slider.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import type { Api } from "@zag-js/slider";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("sliderApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(sliderTrack, className)" v-bind="{ ...api?.getTrackProps(), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/slider/SliderRange.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { sliderRange } from "@mykopkb/core/styles/slider.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import type { Api } from "@zag-js/slider";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("sliderApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(sliderRange, className)" v-bind="{ ...api?.getRangeProps(), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/slider/SliderThumb.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { sliderThumb } from "@mykopkb/core/styles/slider.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import type { Api, ThumbProps } from "@zag-js/slider";
|
||||
import { provide, inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<
|
||||
ThumbProps & {
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}
|
||||
>();
|
||||
|
||||
const api = inject<Api>("sliderApi");
|
||||
|
||||
provide("sliderThumb", props);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(sliderThumb, className)" v-bind="{ ...api?.getThumbProps(props), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/slider/SliderHiddenInput.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { sliderHiddenInput } from "@mykopkb/core/styles/slider.styles";
|
||||
import type { Api, ThumbProps } from "@zag-js/slider";
|
||||
import { inject } from "vue";
|
||||
|
||||
const { class: className, ...props } = defineProps<{
|
||||
class?: string;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("sliderApi");
|
||||
const thumbProps = inject<ThumbProps>("sliderThumb");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input :class="cn(sliderHiddenInput, className)"
|
||||
v-bind="{ ...api?.getHiddenInputProps(thumbProps!), ...props, ...$attrs }" />
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/slider/SliderMarkerGroup.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { sliderMarkerGroup } from "@mykopkb/core/styles/slider.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import type { Api } from "@zag-js/slider";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("sliderApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(sliderMarkerGroup, className)" v-bind="{ ...api?.getMarkerGroupProps(), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/slider/SliderMarker.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { sliderMarker } from "@mykopkb/core/styles/slider.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import type { Api, MarkerProps } from "@zag-js/slider";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<
|
||||
MarkerProps & {
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}
|
||||
>();
|
||||
|
||||
const api = inject<Api>("sliderApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(sliderMarker, className)" v-bind="{ ...api?.getMarkerProps(props), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/slider/index.ts">
|
||||
{{ `
|
||||
export { default as SliderRoot } from "./SliderRoot.vue";
|
||||
export { default as SliderLabel } from "./SliderLabel.vue";
|
||||
export { default as SliderValueText } from "./SliderValueText.vue";
|
||||
export { default as SliderControl } from "./SliderControl.vue";
|
||||
export { default as SliderTrack } from "./SliderTrack.vue";
|
||||
export { default as SliderRange } from "./SliderRange.vue";
|
||||
export { default as SliderThumb } from "./SliderThumb.vue";
|
||||
export { default as SliderHiddenInput } from "./SliderHiddenInput.vue";
|
||||
export { default as SliderMarkerGroup } from "./SliderMarkerGroup.vue";
|
||||
export { default as SliderMarker } from "./SliderMarker.vue";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<SectionContent>
|
||||
Update the import paths to match your project setup.
|
||||
</SectionContent>
|
||||
</div>
|
||||
<div id="usage">
|
||||
<SectionTitle>Usage</SectionTitle>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
import {
|
||||
SliderRoot,
|
||||
SliderLabel,
|
||||
SliderValueText,
|
||||
SliderControl,
|
||||
SliderTrack,
|
||||
SliderRange,
|
||||
SliderThumb,
|
||||
SliderHiddenInput,
|
||||
SliderMarkerGroup,
|
||||
SliderMarker,
|
||||
} from "@/components/ui/slider";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<SliderRoot class="w-72" :defaultValue="[20]">
|
||||
<SliderLabel>Max Items</SliderLabel>
|
||||
<SliderControl>
|
||||
<SliderTrack>
|
||||
<SliderRange />
|
||||
</SliderTrack>
|
||||
<SliderThumb :index="0">
|
||||
<SliderHiddenInput />
|
||||
</SliderThumb>
|
||||
</SliderControl>
|
||||
<div class="flex items-center text-xs gap-1 font-medium justify-center opacity-70">
|
||||
<SliderValueText /> Items
|
||||
</div>
|
||||
</SliderRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</div>
|
||||
<div id="variants">
|
||||
<SectionTitle>Variants</SectionTitle>
|
||||
<SectionContent>A collection of components you can use.</SectionContent>
|
||||
<Preview>
|
||||
<template #preview>
|
||||
<SliderRoot class="w-72" :value="[20, 80]">
|
||||
<SliderLabel>Price Range</SliderLabel>
|
||||
<SliderControl>
|
||||
<SliderTrack>
|
||||
<SliderRange />
|
||||
</SliderTrack>
|
||||
<SliderThumb :index="0">
|
||||
<SliderHiddenInput />
|
||||
</SliderThumb>
|
||||
<SliderThumb :index="1">
|
||||
<SliderHiddenInput />
|
||||
</SliderThumb>
|
||||
</SliderControl>
|
||||
<SliderMarkerGroup>
|
||||
<SliderMarker :value="0">$0</SliderMarker>
|
||||
<SliderMarker :value="25">$25</SliderMarker>
|
||||
<SliderMarker :value="50">$50</SliderMarker>
|
||||
<SliderMarker :value="75">$75</SliderMarker>
|
||||
<SliderMarker :value="100">$100</SliderMarker>
|
||||
</SliderMarkerGroup>
|
||||
</SliderRoot>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<SliderRoot class="w-72" :value="[20, 80]">
|
||||
<SliderLabel>Price Range</SliderLabel>
|
||||
<SliderControl>
|
||||
<SliderTrack>
|
||||
<SliderRange />
|
||||
</SliderTrack>
|
||||
<SliderThumb :index="0">
|
||||
<SliderHiddenInput />
|
||||
</SliderThumb>
|
||||
<SliderThumb :index="1">
|
||||
<SliderHiddenInput />
|
||||
</SliderThumb>
|
||||
</SliderControl>
|
||||
<SliderMarkerGroup>
|
||||
<SliderMarker :value="0">$0</SliderMarker>
|
||||
<SliderMarker :value="25">$25</SliderMarker>
|
||||
<SliderMarker :value="50">$50</SliderMarker>
|
||||
<SliderMarker :value="75">$75</SliderMarker>
|
||||
<SliderMarker :value="100">$100</SliderMarker>
|
||||
</SliderMarkerGroup>
|
||||
</SliderRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,196 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
Preview,
|
||||
SectionTitle,
|
||||
SectionContent,
|
||||
PreviewCode,
|
||||
} from "@/components/docs";
|
||||
import { Box } from "@/components/ui/box";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "@lucide/vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="preview" class="-mt-20">
|
||||
<Preview>
|
||||
<template #preview>
|
||||
<div class="justify-center items-center flex gap-2">
|
||||
<Box as-child>
|
||||
<Button variant="ghost" class="me-2 px-2">
|
||||
<ChevronLeft class="size-5" />
|
||||
</Button>
|
||||
</Box>
|
||||
<Box as-child>
|
||||
<Button variant="ghost" class="px-2">
|
||||
<ChevronRight class="size-5" />
|
||||
</Button>
|
||||
</Box>
|
||||
</div>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<div class="justify-center items-center flex gap-2">
|
||||
<Box as-child>
|
||||
<Button variant="ghost" class="me-2 px-2">
|
||||
<ChevronLeft class="size-5" />
|
||||
</Button>
|
||||
</Box>
|
||||
<Box as-child>
|
||||
<Button variant="ghost" class="px-2">
|
||||
<ChevronRight class="size-5" />
|
||||
</Button>
|
||||
</Box>
|
||||
</div>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
</div>
|
||||
|
||||
<div id="installation">
|
||||
<SectionTitle>Installation</SectionTitle>
|
||||
<SectionContent>
|
||||
By using <code>Slot</code> at the root, you ensure that props are correctly merged whether
|
||||
you're using it as a direct wrapper or an <code>asChild</code> bridge.
|
||||
</SectionContent>
|
||||
<SectionContent>
|
||||
Copy and paste the following code into your project.
|
||||
</SectionContent>
|
||||
|
||||
<PreviewCode title="components/ui/slot/index.ts">
|
||||
{{ `import {
|
||||
defineComponent,
|
||||
h,
|
||||
cloneVNode,
|
||||
Fragment,
|
||||
isVNode,
|
||||
type VNode,
|
||||
type PropType,
|
||||
} from "vue";
|
||||
import { calculateSlot, flattenItems, type AnyProps } from "./slot";
|
||||
|
||||
export const Slot = defineComponent({
|
||||
name: "Slot",
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
children: {
|
||||
type: [Object, Array] as PropType<any>,
|
||||
},
|
||||
},
|
||||
setup(props, { attrs, slots }) {
|
||||
return () => {
|
||||
const raw = props.children ?? slots.default?.();
|
||||
const isValidVNode = (item: any): item is VNode => isVNode(item) && typeof item.type !== "symbol";
|
||||
|
||||
const items = flattenItems<VNode>(
|
||||
raw as any,
|
||||
(item) => isVNode(item) && item.type === Fragment,
|
||||
(item) => (isVNode(item) && Array.isArray(item.children) ? (item.children as VNode[]) : [])
|
||||
).filter(isValidVNode);
|
||||
|
||||
const result = calculateSlot<VNode>({
|
||||
props: attrs as AnyProps,
|
||||
items,
|
||||
isValid: isValidVNode,
|
||||
getProps: (item) => (item.props as AnyProps) || {},
|
||||
getChildren: (item) => item.children,
|
||||
});
|
||||
|
||||
if (result.type === "wrapper") {
|
||||
return h("div", result.props, result.children as any);
|
||||
}
|
||||
|
||||
const target = result.target;
|
||||
return cloneVNode(target, result.props, false);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export { Slot as Root };` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/slot/slot.ts">
|
||||
{{ `export type AnyProps = Record<string, any>;
|
||||
|
||||
export interface SlotParams<T> {
|
||||
props: AnyProps;
|
||||
items: T[];
|
||||
isValid: (item: T) => boolean;
|
||||
getProps: (item: T) => AnyProps;
|
||||
getChildren: (item: T) => any;
|
||||
}
|
||||
|
||||
export type SlotResult<T> =
|
||||
| { type: "slotted"; target: T; props: AnyProps; children: any }
|
||||
| { type: "wrapper"; target: "div"; props: AnyProps; children: T[] };
|
||||
|
||||
export function mergeProps(slotProps: AnyProps, childProps: AnyProps): AnyProps {
|
||||
const result: AnyProps = { ...childProps };
|
||||
for (const key in slotProps) {
|
||||
const slotValue = slotProps[key];
|
||||
const isHandler = /^on[A-Z]/.test(key);
|
||||
if (isHandler) {
|
||||
const childValue = childProps[key];
|
||||
if (typeof slotValue === "function" && typeof childValue === "function") {
|
||||
result[key] = (...args: any[]) => {
|
||||
childValue(...args);
|
||||
slotValue(...args);
|
||||
};
|
||||
} else if (slotValue) {
|
||||
result[key] = slotValue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (key === "class" || key === "className") {
|
||||
const slotClasses = (slotValue || "").split(/\\s+/);
|
||||
const childClasses = (childProps.class || childProps.className || "").split(/\\s+/);
|
||||
const combined = Array.from(new Set([...slotClasses, ...childClasses])).filter(Boolean).join(" ");
|
||||
result[key] = combined;
|
||||
continue;
|
||||
}
|
||||
if (key === "style") {
|
||||
result[key] = { ...slotValue, ...childProps.style };
|
||||
continue;
|
||||
}
|
||||
if (childProps[key] === undefined) {
|
||||
result[key] = slotValue;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function flattenItems<T>(items: T | T[], isFragment: (item: T) => boolean, getChildren: (item: T) =>
|
||||
T
|
||||
| T[]): T[] {
|
||||
const result: T[] = [];
|
||||
const list = Array.isArray(items) ? items : [items];
|
||||
list.forEach((item) => {
|
||||
if (item === null || item === undefined) return;
|
||||
if (isFragment(item)) {
|
||||
const children = getChildren(item);
|
||||
result.push(...flattenItems(children, isFragment, getChildren));
|
||||
} else {
|
||||
result.push(item);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export function calculateSlot<T>(params: SlotParams<T>): SlotResult<T> {
|
||||
const { props, items, isValid, getProps, getChildren } = params;
|
||||
if (items.length === 1) {
|
||||
const primary = items[0];
|
||||
if (isValid(primary)) {
|
||||
return {
|
||||
type: "slotted",
|
||||
target: primary,
|
||||
props: mergeProps(props, getProps(primary)),
|
||||
children: getChildren(primary),
|
||||
};
|
||||
}
|
||||
}
|
||||
return { type: "wrapper", target: "div", props: props, children: items };
|
||||
}` }}
|
||||
</PreviewCode>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,230 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { SwitchRoot, SwitchControl, SwitchLabel } from "@/components/ui/switch";
|
||||
import {
|
||||
Preview,
|
||||
SectionTitle,
|
||||
SectionContent,
|
||||
InstallPackage,
|
||||
PreviewCode,
|
||||
} from "@/components/docs";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="preview" class="-mt-20">
|
||||
<Preview>
|
||||
<template #preview>
|
||||
<SwitchRoot>
|
||||
<SwitchControl />
|
||||
<SwitchLabel>Airplane Mode</SwitchLabel>
|
||||
</SwitchRoot>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<SwitchRoot>
|
||||
<SwitchControl />
|
||||
<SwitchLabel>Airplane Mode</SwitchLabel>
|
||||
</SwitchRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
</div>
|
||||
<div id="installation">
|
||||
<SectionTitle>Installation</SectionTitle>
|
||||
<SectionContent>Install the following dependencies:</SectionContent>
|
||||
<InstallPackage>add @zag-js/vue @zag-js/switch</InstallPackage>
|
||||
<SectionContent>
|
||||
Copy and paste the following code into your project.
|
||||
</SectionContent>
|
||||
<PreviewCode title="components/ui/switch/SwitchRoot.vue">
|
||||
{{`
|
||||
<script lang="ts" setup>
|
||||
import * as zagSwitch from "@zag-js/switch";
|
||||
import type { Props } from "@zag-js/switch";
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { switchRoot } from "@mykopkb/core/styles/switch.styles";
|
||||
import { SwitchHiddenInput } from ".";
|
||||
import { normalizeProps, useMachine } from "@zag-js/vue";
|
||||
import { computed, provide } from "vue";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
checked = undefined,
|
||||
...props
|
||||
} = defineProps<Partial<Props> & { class?: string; asChild?: boolean }>();
|
||||
|
||||
const service = useMachine(zagSwitch.machine, {
|
||||
...props,
|
||||
checked,
|
||||
id: crypto.randomUUID(),
|
||||
});
|
||||
const api = computed(() => zagSwitch.connect(service, normalizeProps));
|
||||
|
||||
provide("switchApi", api);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(switchRoot, className)" v-bind="{ ...props, ...$attrs, ...api?.getRootProps() }">
|
||||
<slot v-if="asChild" />
|
||||
<label v-else>
|
||||
<slot />
|
||||
<SwitchHiddenInput />
|
||||
</label>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/switch/SwitchControl.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { switchControl } from "@mykopkb/core/styles/switch.styles";
|
||||
import { SwitchThumb } from ".";
|
||||
import type { Api } from "@zag-js/switch";
|
||||
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>("switchApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(switchControl, className)" v-bind="{ ...props, ...$attrs, ...api?.getControlProps() }">
|
||||
<slot v-if="asChild" />
|
||||
<span v-else>
|
||||
<SwitchThumb />
|
||||
</span>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/switch/SwitchThumb.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { switchThumb } from "@mykopkb/core/styles/switch.styles";
|
||||
import type { Api } from "@zag-js/switch";
|
||||
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>("switchApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(switchThumb, className)" v-bind="{ ...props, ...$attrs, ...api?.getThumbProps() }">
|
||||
<slot v-if="asChild" />
|
||||
<span v-else>
|
||||
<slot />
|
||||
</span>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/switch/SwitchLabel.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { switchLabel } from "@mykopkb/core/styles/switch.styles";
|
||||
import { label } from "@mykopkb/core/styles/label.styles";
|
||||
import type { Api } from "@zag-js/switch";
|
||||
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>("switchApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn([label, switchLabel, className])" v-bind="{ ...props, ...$attrs, ...api?.getLabelProps() }">
|
||||
<slot v-if="asChild" />
|
||||
<span v-else>
|
||||
<slot />
|
||||
</span>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/switch/SwitchHiddenInput.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { switchHiddenInput } from "@mykopkb/core/styles/switch.styles";
|
||||
import type { Api } from "@zag-js/switch";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("switchApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input :class="cn(switchHiddenInput, className)"
|
||||
v-bind="{ ...props, ...$attrs, ...api?.getHiddenInputProps() }" />
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/switch/index.ts">
|
||||
{{ `
|
||||
export { default as SwitchRoot } from "./SwitchRoot.vue";
|
||||
export { default as SwitchControl } from "./SwitchControl.vue";
|
||||
export { default as SwitchThumb } from "./SwitchThumb.vue";
|
||||
export { default as SwitchLabel } from "./SwitchLabel.vue";
|
||||
export { default as SwitchHiddenInput } from "./SwitchHiddenInput.vue";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<SectionContent>
|
||||
Update the import paths to match your project setup.
|
||||
</SectionContent>
|
||||
</div>
|
||||
<div id="usage">
|
||||
<SectionTitle>Usage</SectionTitle>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
import { SwitchRoot, SwitchControl, SwitchLabel } from "@/components/ui/switch";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<SwitchRoot>
|
||||
<SwitchControl />
|
||||
<SwitchLabel>Airplane Mode</SwitchLabel>
|
||||
</SwitchRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,361 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { Box } from "@/components/ui/box";
|
||||
import {
|
||||
TabsRoot,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
TabsContent,
|
||||
} from "@/components/ui/tabs";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { SquareX, Save, ExternalLink } from "@lucide/vue";
|
||||
import {
|
||||
Preview,
|
||||
SectionTitle,
|
||||
SectionContent,
|
||||
InstallPackage,
|
||||
PreviewCode,
|
||||
} from "@/components/docs";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="preview" class="-mt-20">
|
||||
<Preview>
|
||||
<template #preview>
|
||||
<TabsRoot defaultValue="update-profile">
|
||||
<TabsList>
|
||||
<TabsTrigger value="update-profile"> Update Profile </TabsTrigger>
|
||||
<TabsTrigger value="share-profile"> Share Profile </TabsTrigger>
|
||||
</TabsList>
|
||||
<Box raised="single" class="w-90">
|
||||
<TabsContent value="update-profile">
|
||||
<div class="grid gap-4 mt-2">
|
||||
<div class="grid gap-2.5">
|
||||
<Label for="name-1">Name</Label>
|
||||
<Input id="name-1" name="name" defaultValue="Pedro Duarte" />
|
||||
</div>
|
||||
<div class="grid gap-2.5">
|
||||
<Label for="username-1">Username</Label>
|
||||
<Input id="username-1" name="username" defaultValue="@peduarte" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2 justify-end mt-7">
|
||||
<Button>
|
||||
<SquareX />
|
||||
Close
|
||||
</Button>
|
||||
<Button variant="primary">
|
||||
<Save />
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="share-profile">
|
||||
<div class="grid gap-4 mt-2">
|
||||
<Input id="name-1" name="name" defaultValue="https://midone-ui.com/docs/installation" />
|
||||
</div>
|
||||
<div class="flex gap-2 mt-5">
|
||||
<Button>
|
||||
<ExternalLink />
|
||||
Share Link
|
||||
</Button>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Box>
|
||||
</TabsRoot>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<TabsRoot defaultValue="update-profile">
|
||||
<TabsList>
|
||||
<TabsTrigger value="update-profile"> Update Profile </TabsTrigger>
|
||||
<TabsTrigger value="share-profile"> Share Profile </TabsTrigger>
|
||||
</TabsList>
|
||||
<Box raised="single" class="w-90">
|
||||
<TabsContent value="update-profile">
|
||||
<div class="grid gap-4 mt-2">
|
||||
<div class="grid gap-2.5">
|
||||
<Label for="name-1">Name</Label>
|
||||
<Input id="name-1" name="name" defaultValue="Pedro Duarte" />
|
||||
</div>
|
||||
<div class="grid gap-2.5">
|
||||
<Label for="username-1">Username</Label>
|
||||
<Input id="username-1" name="username" defaultValue="@peduarte" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2 justify-end mt-7">
|
||||
<Button>
|
||||
<SquareX />
|
||||
Close
|
||||
</Button>
|
||||
<Button variant="primary">
|
||||
<Save />
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="share-profile">
|
||||
<div class="grid gap-4 mt-2">
|
||||
<Input id="name-1" name="name" defaultValue="https://midone-ui.com/docs/installation" />
|
||||
</div>
|
||||
<div class="flex gap-2 mt-5">
|
||||
<Button>
|
||||
<ExternalLink />
|
||||
Share Link
|
||||
</Button>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Box>
|
||||
</TabsRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
</div>
|
||||
<div id="installation">
|
||||
<SectionTitle>Installation</SectionTitle>
|
||||
<SectionContent>Install the following dependencies:</SectionContent>
|
||||
<InstallPackage>add @zag-js/react @zag-js/tabs</InstallPackage>
|
||||
<SectionContent>
|
||||
Copy and paste the following code into your project.
|
||||
</SectionContent>
|
||||
<PreviewCode title="components/ui/tabs/TabsContent.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { tabsContent } from "@mykopkb/core/styles/tabs.styles";
|
||||
import type { Api, ContentProps } from "@zag-js/tabs";
|
||||
import { inject } from "vue";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<
|
||||
ContentProps & {
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}
|
||||
>();
|
||||
|
||||
const api = inject<Api>("tabsApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(tabsContent, className)" v-bind="{ ...props, ...$attrs, ...api?.getContentProps(props) }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/tabs/TabsIndicator.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { tabsIndicator } from "@mykopkb/core/styles/tabs.styles";
|
||||
import type { Api } from "@zag-js/tabs";
|
||||
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>("tabsApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(tabsIndicator, className)" v-bind="{ ...props, ...$attrs, ...api?.getIndicatorProps() }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/tabs/TabsList.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { tabsList } from "@mykopkb/core/styles/tabs.styles";
|
||||
import type { Api } from "@zag-js/tabs";
|
||||
import { inject } from "vue";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import { TabsIndicator } from ".";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("tabsApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(tabsList, className)" v-bind="{ ...props, ...$attrs, ...api?.getListProps() }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
<TabsIndicator />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/tabs/TabsRoot.vue">
|
||||
{{`
|
||||
<script lang="ts" setup>
|
||||
import * as tabs from "@zag-js/tabs";
|
||||
import type { Props } from "@zag-js/tabs";
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { tabsRoot } from "@mykopkb/core/styles/tabs.styles";
|
||||
import { normalizeProps, useMachine } from "@zag-js/vue";
|
||||
import { computed, provide } from "vue";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<Partial<Props> & { class?: string; asChild?: boolean }>();
|
||||
|
||||
const service = useMachine(tabs.machine, {
|
||||
...props,
|
||||
id: crypto.randomUUID(),
|
||||
});
|
||||
const api = computed(() => tabs.connect(service, normalizeProps));
|
||||
|
||||
provide("tabsApi", api);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(tabsRoot, className)" v-bind="{ ...props, ...$attrs, ...api?.getRootProps() }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/tabs/TabsTrigger.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { tabsTrigger } from "@mykopkb/core/styles/tabs.styles";
|
||||
import type { Api, TriggerProps } from "@zag-js/tabs";
|
||||
import { inject } from "vue";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<
|
||||
TriggerProps & {
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}
|
||||
>();
|
||||
|
||||
const api = inject<Api>("tabsApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(tabsTrigger, className)" v-bind="{ ...props, ...$attrs, ...api?.getTriggerProps(props) }">
|
||||
<slot v-if="asChild" />
|
||||
<button v-else>
|
||||
<slot />
|
||||
</button>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/tabs/index.ts">
|
||||
{{ `
|
||||
export { default as TabsContent } from "./TabsContent.vue";
|
||||
export { default as TabsIndicator } from "./TabsIndicator.vue";
|
||||
export { default as TabsList } from "./TabsList.vue";
|
||||
export { default as TabsRoot } from "./TabsRoot.vue";
|
||||
export { default as TabsTrigger } from "./TabsTrigger.vue";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<SectionContent>
|
||||
Update the import paths to match your project setup.
|
||||
</SectionContent>
|
||||
</div>
|
||||
<div id="usage">
|
||||
<SectionTitle>Usage</SectionTitle>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
import {
|
||||
TabsRoot,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
TabsContent,
|
||||
} from "@/components/ui/tabs";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<TabsRoot defaultValue="update-profile">
|
||||
<TabsList>
|
||||
<TabsTrigger value="update-profile"> Update Profile </TabsTrigger>
|
||||
<TabsTrigger value="share-profile"> Share Profile </TabsTrigger>
|
||||
</TabsList>
|
||||
<Box raised="single" class="w-90">
|
||||
<TabsContent value="update-profile">
|
||||
<div class="grid gap-4 mt-2">
|
||||
<div class="grid gap-2.5">
|
||||
<Label for="name-1">Name</Label>
|
||||
<Input id="name-1" name="name" defaultValue="Pedro Duarte" />
|
||||
</div>
|
||||
<div class="grid gap-2.5">
|
||||
<Label for="username-1">Username</Label>
|
||||
<Input id="username-1" name="username" defaultValue="@peduarte" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2 justify-end mt-7">
|
||||
<Button>
|
||||
<SquareX />
|
||||
Close
|
||||
</Button>
|
||||
<Button variant="primary">
|
||||
<Save />
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="share-profile">
|
||||
<div class="grid gap-4 mt-2">
|
||||
<Input id="name-1" name="name" defaultValue="https://midone-ui.com/docs/installation" />
|
||||
</div>
|
||||
<div class="flex gap-2 mt-5">
|
||||
<Button>
|
||||
<ExternalLink />
|
||||
Share Link
|
||||
</Button>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Box>
|
||||
</TabsRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,69 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import {
|
||||
Preview,
|
||||
SectionTitle,
|
||||
SectionContent,
|
||||
PreviewCode,
|
||||
} from "@/components/docs";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="preview" class="-mt-20">
|
||||
<Preview>
|
||||
<template #preview>
|
||||
<Textarea class="w-86" placeholder="Type your message here." />
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<Textarea class="w-86" placeholder="Type your message here." />
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
</div>
|
||||
<div id="installation">
|
||||
<SectionTitle>Installation</SectionTitle>
|
||||
<SectionContent>
|
||||
Copy and paste the following code into your project.
|
||||
</SectionContent>
|
||||
<PreviewCode title="components/ui/textarea/Textarea.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { textarea } from "@mykopkb/core/styles/textarea.styles";
|
||||
|
||||
const { class: className, ...props } = defineProps<{
|
||||
class?: string;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<textarea :class="cn(textarea, className)" v-bind="{ ...props }" />
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/textarea/index.ts">
|
||||
{{ `
|
||||
export { default as Textarea } from "./Textarea.vue";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<SectionContent>
|
||||
Update the import paths to match your project setup.
|
||||
</SectionContent>
|
||||
</div>
|
||||
<div id="usage">
|
||||
<SectionTitle>Usage</SectionTitle>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<Textarea class="w-86" placeholder="Type your message here." />
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,371 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
toaster,
|
||||
ToasterContainer,
|
||||
ToastRoot,
|
||||
ToastTitle,
|
||||
ToastDescription,
|
||||
ToastCloseTrigger,
|
||||
} from "@/components/ui/toast";
|
||||
import {
|
||||
Preview,
|
||||
SectionTitle,
|
||||
SectionContent,
|
||||
InstallPackage,
|
||||
PreviewCode,
|
||||
} from "@/components/docs";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="preview" class="-mt-20">
|
||||
<Preview>
|
||||
<template #preview>
|
||||
<Button @click="
|
||||
() =>
|
||||
toaster.create({
|
||||
title: 'Event has been created',
|
||||
description: 'Sunday, December 03, 2023 at 9:00 AM',
|
||||
type: 'info',
|
||||
})
|
||||
">
|
||||
Show Toast
|
||||
</Button>
|
||||
<ToasterContainer :toaster="toaster" v-slot="{ toast }">
|
||||
<ToastRoot :key="toast.id">
|
||||
<ToastTitle>{{ toast.title }}</ToastTitle>
|
||||
<ToastDescription>
|
||||
{{ toast.description }}
|
||||
</ToastDescription>
|
||||
<ToastCloseTrigger />
|
||||
</ToastRoot>
|
||||
</ToasterContainer>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{`
|
||||
<Button @click="
|
||||
() =>
|
||||
toaster.create({
|
||||
title: 'Event has been created',
|
||||
description: 'Sunday, December 03, 2023 at 9:00 AM',
|
||||
type: 'info',
|
||||
})
|
||||
">
|
||||
Show Toast
|
||||
</Button>
|
||||
<ToasterContainer :toaster="toaster" v-slot="{ toast }">
|
||||
<ToastRoot :key="toast.id">
|
||||
<ToastTitle>\{\{ toast.title \}\}</ToastTitle>
|
||||
<ToastDescription>
|
||||
\{\{ toast.description \}\}
|
||||
</ToastDescription>
|
||||
<ToastCloseTrigger />
|
||||
</ToastRoot>
|
||||
</ToasterContainer>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
</div>
|
||||
<div id="installation">
|
||||
<SectionTitle>Installation</SectionTitle>
|
||||
<SectionContent>Install the following dependencies:</SectionContent>
|
||||
<InstallPackage>add @zag-js/vue @zag-js/toast</InstallPackage>
|
||||
<SectionContent>
|
||||
Copy and paste the following code into your project.
|
||||
</SectionContent>
|
||||
<PreviewCode title="components/ui/toast/toaster.ts">
|
||||
{{ `
|
||||
import * as toast from "@zag-js/toast";
|
||||
|
||||
const toaster = toast.createStore({
|
||||
placement: "bottom-end",
|
||||
overlap: true,
|
||||
gap: 24,
|
||||
});
|
||||
|
||||
export default toaster;
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/toast/ToastRoot.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { toastRoot } from "@mykopkb/core/styles/toast.styles";
|
||||
import {
|
||||
boxVariants,
|
||||
type BoxVariants,
|
||||
} from "@mykopkb/core/styles/box.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import type { Api } from "@zag-js/toast";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
raised = "single",
|
||||
...props
|
||||
} = defineProps<
|
||||
BoxVariants & {
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}
|
||||
>();
|
||||
|
||||
const api = inject<Api>("toastApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn([boxVariants({ raised, className }), toastRoot, className])"
|
||||
v-bind="{ ...api?.getRootProps(), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<span v-bind="{ ...api?.getGhostBeforeProps() }" />
|
||||
<div data-scope="toast" data-part="progressbar" />
|
||||
<slot />
|
||||
<span v-bind="{ ...api?.getGhostAfterProps() }" />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/toast/ToastTitle.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { toastTitle } from "@mykopkb/core/styles/toast.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import type { Api } from "@zag-js/toast";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("toastApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(toastTitle, className)" v-bind="{ ...api?.getTitleProps(), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/toast/ToastDescription.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { toastDescription } from "@mykopkb/core/styles/toast.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import type { Api } from "@zag-js/toast";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("toastApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(toastDescription, className)" v-bind="{ ...api?.getDescriptionProps(), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/toast/ToastCloseTrigger.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { toastCloseTrigger } from "@mykopkb/core/styles/toast.styles";
|
||||
import {
|
||||
buttonVariants,
|
||||
type ButtonVariants,
|
||||
} from "@mykopkb/core/styles/button.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { X } from "@lucide/vue";
|
||||
import type { Api } from "@zag-js/toast";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
look = "outline",
|
||||
variant = "secondary",
|
||||
size,
|
||||
...props
|
||||
} = defineProps<
|
||||
ButtonVariants & {
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}
|
||||
>();
|
||||
|
||||
const api = inject<Api>("toastApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot v-bind="{ ...api?.getCloseTriggerProps(), ...props, ...$attrs }">
|
||||
<Button variant="ghost" v-if="!$slots.default" :class="cn(toastCloseTrigger, className)"
|
||||
v-bind="{ ...props }">
|
||||
<X class="size-4" />
|
||||
</Button>
|
||||
<template v-else>
|
||||
<slot v-if="asChild" />
|
||||
<Button v-else :class="
|
||||
cn(buttonVariants({ look, variant, size, className }), className)
|
||||
">
|
||||
<slot />
|
||||
</Button>
|
||||
</template>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/toast/ToastItem.vue">
|
||||
{{`
|
||||
<script lang="ts" setup>
|
||||
import * as toast from "@zag-js/toast";
|
||||
import { provide, computed } from "vue";
|
||||
import { useMachine, normalizeProps } from "@zag-js/vue";
|
||||
|
||||
const { toastGroup, serviceGroup, index } = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
toastGroup: toast.Options;
|
||||
serviceGroup: toast.GroupService;
|
||||
index: number;
|
||||
}>();
|
||||
|
||||
const composedProps = computed(() => ({
|
||||
...toastGroup,
|
||||
index,
|
||||
parent: serviceGroup,
|
||||
}));
|
||||
const service = useMachine(toast.machine, composedProps);
|
||||
const api = toast.connect(service, normalizeProps);
|
||||
|
||||
provide("toastApi", api);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<slot :toast="{
|
||||
...api,
|
||||
id: toastGroup.id,
|
||||
}" />
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/toast/ToasterContainer.vue">
|
||||
{{`
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import * as toast from "@zag-js/toast";
|
||||
import { toasterContainer } from "@mykopkb/core/styles/toast.styles";
|
||||
import type { Store } from "@zag-js/toast";
|
||||
import { normalizeProps, useMachine } from "@zag-js/vue";
|
||||
import { computed } from "vue";
|
||||
import { ToastItem } from ".";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
toaster,
|
||||
...props
|
||||
} = defineProps<{ class?: string; asChild?: boolean; toaster: Store }>();
|
||||
|
||||
const serviceGroup = useMachine(toast.group.machine, {
|
||||
id: crypto.randomUUID(),
|
||||
store: toaster,
|
||||
});
|
||||
const apiGroup = computed(() =>
|
||||
toast.group.connect(serviceGroup, normalizeProps)
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<div :class="cn(toasterContainer, className)" v-bind="{ ...apiGroup?.getGroupProps(), ...props, ...$attrs }">
|
||||
<ToastItem v-for="(toastGroup, index) in apiGroup.getToasts()" :key="toastGroup.id" :index="index"
|
||||
:toastGroup="toastGroup" :serviceGroup="serviceGroup" v-slot="{ toast }">
|
||||
<slot :toast="toast" />
|
||||
</ToastItem>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/toast/index.ts">
|
||||
{{ `
|
||||
export { default as toaster } from "./toaster";
|
||||
export { default as ToastRoot } from "./ToastRoot.vue";
|
||||
export { default as ToastTitle } from "./ToastTitle.vue";
|
||||
export { default as ToastDescription } from "./ToastDescription.vue";
|
||||
export { default as ToastCloseTrigger } from "./ToastCloseTrigger.vue";
|
||||
export { default as ToastItem } from "./ToastItem.vue";
|
||||
export { default as ToasterContainer } from "./ToasterContainer.vue";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<SectionContent>
|
||||
Update the import paths to match your project setup.
|
||||
</SectionContent>
|
||||
</div>
|
||||
<div id="usage">
|
||||
<SectionTitle>Usage</SectionTitle>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
import {
|
||||
toaster,
|
||||
ToasterContainer,
|
||||
ToastRoot,
|
||||
ToastTitle,
|
||||
ToastDescription,
|
||||
ToastCloseTrigger,
|
||||
} from "@/components/ui/toast";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode>
|
||||
{{`
|
||||
<Button @click="
|
||||
() =>
|
||||
toaster.create({
|
||||
title: 'Event has been created',
|
||||
description: 'Sunday, December 03, 2023 at 9:00 AM',
|
||||
type: 'info',
|
||||
})
|
||||
">
|
||||
Show Toast
|
||||
</Button>
|
||||
<ToasterContainer :toaster="toaster" v-slot="{ toast }">
|
||||
<ToastRoot :key="toast.id">
|
||||
<ToastTitle>\{\{ toast.title \}\}</ToastTitle>
|
||||
<ToastDescription>
|
||||
\{\{ toast.description \}\}
|
||||
</ToastDescription>
|
||||
<ToastCloseTrigger />
|
||||
</ToastRoot>
|
||||
</ToasterContainer>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,287 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
TooltipRoot,
|
||||
TooltipTrigger,
|
||||
TooltipPositioner,
|
||||
TooltipContent,
|
||||
} from "@/components/ui/tooltip";
|
||||
import {
|
||||
Preview,
|
||||
SectionTitle,
|
||||
SectionContent,
|
||||
InstallPackage,
|
||||
PreviewCode,
|
||||
} from "@/components/docs";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="preview" class="-mt-20">
|
||||
<Preview>
|
||||
<template #preview>
|
||||
<TooltipRoot>
|
||||
<TooltipTrigger>Hover Me</TooltipTrigger>
|
||||
<TooltipPositioner>
|
||||
<TooltipContent>I am a tooltip!</TooltipContent>
|
||||
</TooltipPositioner>
|
||||
</TooltipRoot>
|
||||
</template>
|
||||
<template #code>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<TooltipRoot>
|
||||
<TooltipTrigger>Hover Me</TooltipTrigger>
|
||||
<TooltipPositioner>
|
||||
<TooltipContent>I am a tooltip!</TooltipContent>
|
||||
</TooltipPositioner>
|
||||
</TooltipRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</template>
|
||||
</Preview>
|
||||
</div>
|
||||
<div id="installation">
|
||||
<SectionTitle>Installation</SectionTitle>
|
||||
<SectionContent>Install the following dependencies:</SectionContent>
|
||||
<InstallPackage>add @zag-js/vue @zag-js/tooltip</InstallPackage>
|
||||
<SectionContent>
|
||||
Copy and paste the following code into your project.
|
||||
</SectionContent>
|
||||
<PreviewCode title="components/ui/tooltip/TooltipRoot.vue">
|
||||
{{`
|
||||
<script lang="ts" setup>
|
||||
import * as tooltip from "@zag-js/tooltip";
|
||||
import type { Props } from "@zag-js/tooltip";
|
||||
import { normalizeProps, useMachine } from "@zag-js/vue";
|
||||
import { computed, provide } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
open = undefined,
|
||||
disabled = false,
|
||||
...props
|
||||
} = defineProps<Partial<Props> & { class?: string; asChild?: boolean }>();
|
||||
|
||||
const service = useMachine(tooltip.machine, {
|
||||
...props,
|
||||
positioning: {
|
||||
placement: "top",
|
||||
offset: { mainAxis: 10 },
|
||||
},
|
||||
closeDelay: 0,
|
||||
openDelay: 0,
|
||||
open,
|
||||
disabled,
|
||||
id: crypto.randomUUID(),
|
||||
});
|
||||
const api = computed(() => tooltip.connect(service, normalizeProps));
|
||||
|
||||
provide("tooltipApi", api);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<slot />
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/tooltip/TooltipTrigger.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { tooltipTrigger } from "@mykopkb/core/styles/tooltip.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import type { Api } from "@zag-js/tooltip";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("tooltipApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot v-bind="{ ...api?.getTriggerProps(), ...props, ...$attrs }">
|
||||
<Button variant="secondary" look="outline" v-if="!asChild" :class="cn(tooltipTrigger, className)">
|
||||
<slot />
|
||||
</Button>
|
||||
<slot v-else />
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/tooltip/TooltipPositioner.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { tooltipPositioner } from "@mykopkb/core/styles/tooltip.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import type { Api } from "@zag-js/tooltip";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("tooltipApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<Slot :class="cn(tooltipPositioner, className)"
|
||||
v-bind="{ ...api?.getPositionerProps(), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</Teleport>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/tooltip/TooltipContent.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { tooltipContent } from "@mykopkb/core/styles/tooltip.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import { TooltipArrow, TooltipArrowTip } from ".";
|
||||
import type { Api } from "@zag-js/tooltip";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("tooltipApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(tooltipContent, className)" v-bind="{ ...api?.getContentProps(), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
<TooltipArrow>
|
||||
<TooltipArrowTip />
|
||||
</TooltipArrow>
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/tooltip/TooltipArrow.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { tooltipArrow } from "@mykopkb/core/styles/tooltip.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import type { Api } from "@zag-js/tooltip";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("tooltipApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(tooltipArrow, className)" v-bind="{ ...api?.getArrowProps(), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/tooltip/TooltipArrowTip.vue">
|
||||
{{ `
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@mykopkb/core/utils/cn";
|
||||
import { tooltipArrowTip } from "@mykopkb/core/styles/tooltip.styles";
|
||||
import { Slot } from "@/components/ui/slot";
|
||||
import type { Api } from "@zag-js/tooltip";
|
||||
import { inject } from "vue";
|
||||
|
||||
const {
|
||||
class: className,
|
||||
asChild = false,
|
||||
...props
|
||||
} = defineProps<{
|
||||
class?: string;
|
||||
asChild?: boolean;
|
||||
}>();
|
||||
|
||||
const api = inject<Api>("tooltipApi");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot :class="cn(tooltipArrowTip, className)" v-bind="{ ...api?.getArrowTipProps(), ...props, ...$attrs }">
|
||||
<slot v-if="asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</Slot>
|
||||
</template>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode title="components/ui/tooltip/index.ts">
|
||||
{{ `
|
||||
export { default as TooltipRoot } from "./TooltipRoot.vue";
|
||||
export { default as TooltipTrigger } from "./TooltipTrigger.vue";
|
||||
export { default as TooltipPositioner } from "./TooltipPositioner.vue";
|
||||
export { default as TooltipContent } from "./TooltipContent.vue";
|
||||
export { default as TooltipArrow } from "./TooltipArrow.vue";
|
||||
export { default as TooltipArrowTip } from "./TooltipArrowTip.vue";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<SectionContent>
|
||||
Update the import paths to match your project setup.
|
||||
</SectionContent>
|
||||
</div>
|
||||
<div id="usage">
|
||||
<SectionTitle>Usage</SectionTitle>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
import {
|
||||
TooltipRoot,
|
||||
TooltipTrigger,
|
||||
TooltipPositioner,
|
||||
TooltipContent,
|
||||
} from "@/components/ui/tooltip";
|
||||
` }}
|
||||
</PreviewCode>
|
||||
<PreviewCode>
|
||||
{{ `
|
||||
<TooltipRoot>
|
||||
<TooltipTrigger>Hover Me</TooltipTrigger>
|
||||
<TooltipPositioner>
|
||||
<TooltipContent>I am a tooltip!</TooltipContent>
|
||||
</TooltipPositioner>
|
||||
</TooltipRoot>
|
||||
` }}
|
||||
</PreviewCode>
|
||||
</div>
|
||||
</template>
|
||||
@@ -2,6 +2,7 @@ import "./index.css";
|
||||
|
||||
import { createApp } from 'vue'
|
||||
import { pinia } from '@/pinia'
|
||||
import { applyAppearance } from '@/utils/applyAppearance'
|
||||
|
||||
import App from './App.vue'
|
||||
import router from './router/index.ts'
|
||||
@@ -9,6 +10,7 @@ import router from './router/index.ts'
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(pinia)
|
||||
applyAppearance()
|
||||
app.use(router)
|
||||
|
||||
app.mount('#app')
|
||||
|
||||
+5
-190
@@ -4,207 +4,22 @@ import { roleMenu } from '@/modules/role'
|
||||
import { membershipApplicationMenu } from '@/modules/membership-application'
|
||||
import { activityMenu } from '@/modules/activity'
|
||||
import { dashboardMenu } from '@/modules/dashboard/menu'
|
||||
import { externalSystemMenu } from '@/modules/external-system/menu'
|
||||
import { activityLogMenu } from '@/modules/activity-log/menu'
|
||||
|
||||
export type { Menu }
|
||||
|
||||
const mainMenu: (string | Menu)[] = [
|
||||
'Dashboard',
|
||||
...dashboardMenu,
|
||||
'Umum',
|
||||
...dashboardMenu,
|
||||
...externalSystemMenu,
|
||||
...activityMenu,
|
||||
'Teknologi Maklumat',
|
||||
...roleMenu,
|
||||
...activityLogMenu,
|
||||
'Pentadbiran',
|
||||
...membershipApplicationMenu,
|
||||
...userMenu,
|
||||
'E-Commerce',
|
||||
{
|
||||
icon: 'SquareKanban',
|
||||
title: 'E-Commerce',
|
||||
badge: 2,
|
||||
sub_menu: [
|
||||
{
|
||||
icon: 'CircleGauge',
|
||||
route_name: 'categories',
|
||||
title: 'Categories',
|
||||
},
|
||||
{
|
||||
icon: 'CircleGauge',
|
||||
route_name: 'add-product',
|
||||
title: 'Add Product',
|
||||
},
|
||||
{
|
||||
icon: 'CircleGauge',
|
||||
title: 'Products',
|
||||
sub_menu: [
|
||||
{
|
||||
icon: 'CircleGauge',
|
||||
route_name: 'product-list',
|
||||
title: 'Product List',
|
||||
},
|
||||
{
|
||||
icon: 'CircleGauge',
|
||||
route_name: 'product-grid',
|
||||
title: 'Product Grid',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: 'CircleGauge',
|
||||
title: 'Transactions',
|
||||
sub_menu: [
|
||||
{
|
||||
icon: 'CircleGauge',
|
||||
route_name: 'transaction-list',
|
||||
title: 'Transaction List',
|
||||
},
|
||||
{
|
||||
icon: 'CircleGauge',
|
||||
route_name: 'transaction-detail',
|
||||
title: 'Transaction Detail',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: 'CircleGauge',
|
||||
title: 'Sellers',
|
||||
sub_menu: [
|
||||
{
|
||||
icon: 'CircleGauge',
|
||||
route_name: 'seller-list',
|
||||
title: 'Seller List',
|
||||
},
|
||||
{
|
||||
icon: 'CircleGauge',
|
||||
route_name: 'seller-detail',
|
||||
title: 'Seller Detail',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: 'CircleGauge',
|
||||
route_name: 'reviews',
|
||||
title: 'Reviews',
|
||||
},
|
||||
],
|
||||
},
|
||||
'APPS',
|
||||
{
|
||||
icon: 'CircleGauge',
|
||||
route_name: 'file-manager',
|
||||
title: 'File Manager',
|
||||
badge: 5,
|
||||
},
|
||||
{
|
||||
icon: 'CircleGauge',
|
||||
route_name: 'point-of-sale',
|
||||
title: 'Point of Sale',
|
||||
},
|
||||
'UI COMPONENTS',
|
||||
{
|
||||
icon: 'CircleGauge',
|
||||
title: 'Forms',
|
||||
sub_menu: [
|
||||
{
|
||||
icon: 'CircleGauge',
|
||||
route_name: 'combobox',
|
||||
title: 'Combobox',
|
||||
},
|
||||
{
|
||||
icon: 'CircleGauge',
|
||||
route_name: 'datepicker',
|
||||
title: 'Datepicker',
|
||||
},
|
||||
{
|
||||
icon: 'CircleGauge',
|
||||
route_name: 'input',
|
||||
title: 'Input',
|
||||
},
|
||||
{
|
||||
icon: 'CircleGauge',
|
||||
route_name: 'radio-group',
|
||||
title: 'Radio Group',
|
||||
},
|
||||
{
|
||||
icon: 'CircleGauge',
|
||||
route_name: 'select',
|
||||
title: 'Select',
|
||||
},
|
||||
{
|
||||
icon: 'CircleGauge',
|
||||
route_name: 'slider',
|
||||
title: 'Slider',
|
||||
},
|
||||
{
|
||||
icon: 'CircleGauge',
|
||||
route_name: 'switch',
|
||||
title: 'Switch',
|
||||
},
|
||||
{
|
||||
icon: 'CircleGauge',
|
||||
route_name: 'textarea',
|
||||
title: 'Textarea',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: 'CircleGauge',
|
||||
title: 'Feedback',
|
||||
sub_menu: [
|
||||
{
|
||||
icon: 'CircleGauge',
|
||||
route_name: 'progress-circular',
|
||||
title: 'Progress Circular',
|
||||
},
|
||||
{
|
||||
icon: 'CircleGauge',
|
||||
route_name: 'progress-linear',
|
||||
title: 'Progress Linear',
|
||||
},
|
||||
{
|
||||
icon: 'CircleGauge',
|
||||
route_name: 'toast',
|
||||
title: 'Toast',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: 'CircleGauge',
|
||||
title: 'Overlay',
|
||||
sub_menu: [
|
||||
{
|
||||
icon: 'CircleGauge',
|
||||
route_name: 'dialog',
|
||||
title: 'Dialog',
|
||||
},
|
||||
{
|
||||
icon: 'CircleGauge',
|
||||
route_name: 'popover',
|
||||
title: 'Popover',
|
||||
},
|
||||
{
|
||||
icon: 'CircleGauge',
|
||||
route_name: 'sheet',
|
||||
title: 'Sheet',
|
||||
},
|
||||
{
|
||||
icon: 'CircleGauge',
|
||||
route_name: 'tooltip',
|
||||
title: 'Tooltip',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: 'CircleGauge',
|
||||
title: 'Visuals',
|
||||
sub_menu: [
|
||||
{
|
||||
icon: 'CircleGauge',
|
||||
route_name: 'chart',
|
||||
title: 'Chart',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
export default mainMenu
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import debounce from 'lodash/debounce'
|
||||
import { useApiPagination } from '@/composables/useApiPagination'
|
||||
import { getApiErrorMessage } from '@/core/utils/getApiErrorMessage'
|
||||
import { listActivityLogs } from '../services/activity-log.service'
|
||||
import type { ActivityLogItem } from '../types/activity-log.types'
|
||||
|
||||
export function useActivityLogList() {
|
||||
const activityLogs = ref<ActivityLogItem[]>([])
|
||||
const loading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
const search = ref('')
|
||||
const page = ref(1)
|
||||
const itemsPerPage = ref(10)
|
||||
|
||||
const { pagination, applyPagination } = useApiPagination({ per_page: 10 })
|
||||
|
||||
async function fetchActivityLogs(requestPage = page.value) {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
const data = await listActivityLogs({
|
||||
page: requestPage,
|
||||
per_page: itemsPerPage.value,
|
||||
search: search.value.trim() || undefined,
|
||||
})
|
||||
|
||||
activityLogs.value = data.data
|
||||
applyPagination(data.pagination)
|
||||
page.value = data.pagination.current_page
|
||||
} catch (err) {
|
||||
error.value = getApiErrorMessage(err, 'Gagal memuatkan log aktiviti.')
|
||||
activityLogs.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const debouncedSearch = debounce(() => {
|
||||
fetchActivityLogs(1)
|
||||
}, 400)
|
||||
|
||||
watch(search, () => {
|
||||
debouncedSearch()
|
||||
})
|
||||
|
||||
watch(page, (nextPage, previousPage) => {
|
||||
if (nextPage !== previousPage) {
|
||||
fetchActivityLogs(nextPage)
|
||||
}
|
||||
})
|
||||
|
||||
watch(itemsPerPage, (nextValue, previousValue) => {
|
||||
if (nextValue !== previousValue) {
|
||||
fetchActivityLogs(1)
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
fetchActivityLogs(1)
|
||||
})
|
||||
|
||||
return {
|
||||
activityLogs,
|
||||
loading,
|
||||
error,
|
||||
search,
|
||||
page,
|
||||
itemsPerPage,
|
||||
pagination,
|
||||
fetchActivityLogs,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export { activityLogLayoutRoutes } from './routes'
|
||||
export { activityLogMenu } from './menu'
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { Menu } from '@/core/types/menu'
|
||||
|
||||
export const activityLogMenu: Menu[] = [
|
||||
{
|
||||
icon: 'ScrollText',
|
||||
route_name: 'list-activity-logs',
|
||||
title: 'Log Aktiviti',
|
||||
permission: 'lihat log aktiviti',
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,105 @@
|
||||
<script lang="ts" setup>
|
||||
import dayjs from 'dayjs'
|
||||
import { Search } from '@lucide/vue'
|
||||
import { AlertRoot, AlertTitle, AlertDescription } from '@/components/ui/alert'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import DataTable from '@/components/ui/usage/DataTable.vue'
|
||||
import type { TableHeader } from '@/components/ui/usage/DataTable.vue'
|
||||
import { useActivityLogList } from '../composables/useActivityLogList'
|
||||
import { formatModelType } from '../utils/activity-log.utils'
|
||||
|
||||
const {
|
||||
activityLogs,
|
||||
loading,
|
||||
error,
|
||||
search,
|
||||
page,
|
||||
itemsPerPage,
|
||||
pagination,
|
||||
} = useActivityLogList()
|
||||
|
||||
function formatDateTime(value: string | null | undefined): string {
|
||||
if (!value) return '-'
|
||||
return dayjs(value).format('DD MMM YYYY, HH:mm')
|
||||
}
|
||||
|
||||
function formatCauserName(item: { causer?: { name?: string } | null }): string {
|
||||
return item.causer?.name ?? '-'
|
||||
}
|
||||
|
||||
const headers: TableHeader[] = [
|
||||
{ title: 'Bil.', key: '#', sortable: false },
|
||||
{
|
||||
title: 'Tarikh',
|
||||
key: 'created_at',
|
||||
sortable: false,
|
||||
exportValue: (item) => formatDateTime(item.created_at),
|
||||
},
|
||||
{
|
||||
title: 'Pengguna',
|
||||
key: 'causer.name',
|
||||
sortable: false,
|
||||
exportValue: (item) => formatCauserName(item),
|
||||
},
|
||||
{
|
||||
title: 'Emel',
|
||||
key: 'causer.email',
|
||||
sortable: false,
|
||||
exportValue: (item) => item.causer?.email ?? '-',
|
||||
},
|
||||
{ title: 'Keterangan', key: 'description', sortable: false },
|
||||
{
|
||||
title: 'Subjek',
|
||||
key: 'subject_type',
|
||||
sortable: false,
|
||||
exportValue: (item) => formatModelType(item.subject_type),
|
||||
},
|
||||
{ title: 'Peristiwa', key: 'event', sortable: false },
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full space-y-6">
|
||||
<div>
|
||||
<h2 class="text-lg font-medium">Log Aktiviti</h2>
|
||||
<p class="mt-1 text-sm opacity-70">Semak rekod aktiviti pengguna dalam sistem.</p>
|
||||
</div>
|
||||
|
||||
<AlertRoot v-if="error" variant="danger">
|
||||
<AlertTitle>Error</AlertTitle>
|
||||
<AlertDescription>{{ error }}</AlertDescription>
|
||||
</AlertRoot>
|
||||
|
||||
<DataTable :headers="headers" :items="activityLogs" :loading="loading" :pagination="pagination" show-pagination
|
||||
exportable export-file-name="activity-logs" v-model:page="page" v-model:items-per-page="itemsPerPage">
|
||||
<template #toolbar>
|
||||
<div class="relative w-full max-w-md flex-1">
|
||||
<Search class="pointer-events-none absolute top-1/2 left-3 z-10 size-4 -translate-y-1/2 text-foreground/50"
|
||||
aria-hidden="true" />
|
||||
<Input v-model="search" type="search" placeholder="Cari keterangan, subjek, peristiwa, pengguna..."
|
||||
class="w-full pl-9" aria-label="Cari log aktiviti" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #item.created_at="{ item }">
|
||||
{{ formatDateTime(item.created_at) }}
|
||||
</template>
|
||||
|
||||
<template #item.causer.name="{ item }">
|
||||
{{ formatCauserName(item) }}
|
||||
</template>
|
||||
|
||||
<template #item.causer.email="{ item }">
|
||||
<span class="lowercase">{{ item.causer?.email ?? '-' }}</span>
|
||||
</template>
|
||||
|
||||
<template #item.subject_type="{ item }">
|
||||
{{ formatModelType(item.subject_type) }}
|
||||
</template>
|
||||
|
||||
<template #item.event="{ item }">
|
||||
{{ item.event ?? '-' }}
|
||||
</template>
|
||||
</DataTable>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,14 @@
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
|
||||
export const activityLogLayoutRoutes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: 'activity-logs',
|
||||
name: 'list-activity-logs',
|
||||
component: () => import('./pages/ActivityLogList.vue'),
|
||||
meta: {
|
||||
title: 'Log Aktiviti',
|
||||
module: 'activity-log',
|
||||
permission: 'lihat log aktiviti',
|
||||
},
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,17 @@
|
||||
import { api } from '@/core/services/api'
|
||||
import type { PaginatedApiResponse } from '@/core/types/api'
|
||||
import type { ActivityLogItem, ListActivityLogsParams } from '../types/activity-log.types'
|
||||
|
||||
export async function listActivityLogs(
|
||||
params: ListActivityLogsParams,
|
||||
): Promise<PaginatedApiResponse<ActivityLogItem>> {
|
||||
const { data } = await api.get<PaginatedApiResponse<ActivityLogItem>>('/v1/activitylogs', {
|
||||
params,
|
||||
})
|
||||
|
||||
if (!data.success) {
|
||||
throw new Error(data.message ?? 'Failed to load activity logs')
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
export type ActivityLogCauser = {
|
||||
id: string
|
||||
name: string
|
||||
email: string
|
||||
}
|
||||
|
||||
export type ActivityLogItem = {
|
||||
id: number
|
||||
log_name: string | null
|
||||
description: string
|
||||
subject_type: string | null
|
||||
subject_id: string | null
|
||||
event: string | null
|
||||
causer_type: string | null
|
||||
causer_id: string | null
|
||||
properties: Record<string, unknown> | null
|
||||
created_at: string
|
||||
updated_at: string
|
||||
causer: ActivityLogCauser | null
|
||||
}
|
||||
|
||||
export type ListActivityLogsParams = {
|
||||
page?: number
|
||||
per_page?: number
|
||||
search?: string
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export function formatModelType(value: string | null | undefined): string {
|
||||
if (!value) return '-'
|
||||
|
||||
const segments = value.split('\\')
|
||||
return segments[segments.length - 1] || value
|
||||
}
|
||||
@@ -11,14 +11,14 @@ const sections = [
|
||||
id: 'pengenalan',
|
||||
title: '1. Pengenalan',
|
||||
placeholder: `
|
||||
Terma dan Syarat ini mengawal penggunaan platform, laman web, dan perkhidmatan yang disediakan oleh Kementerian Pertahanan Malaysia (MINDEF). Dengan mengakses atau menggunakan perkhidmatan kami, anda dianggap telah membaca, memahami, dan bersetuju untuk terikat dengan terma ini. Jika anda tidak bersetuju dengan mana-mana bahagian terma ini, sila hentikan penggunaan perkhidmatan kami.
|
||||
Terma dan Syarat ini mengawal penggunaan platform, laman web, dan perkhidmatan yang disediakan oleh Koperasi Permodalan Kelantan Berhad (KOPKB). Dengan mengakses atau menggunakan perkhidmatan kami, anda dianggap telah membaca, memahami, dan bersetuju untuk terikat dengan terma ini. Jika anda tidak bersetuju dengan mana-mana bahagian terma ini, sila hentikan penggunaan perkhidmatan kami.
|
||||
`,
|
||||
},
|
||||
{
|
||||
id: 'akaun',
|
||||
title: '2. Akaun & Kelayakan',
|
||||
placeholder: `
|
||||
Anda mesti berumur sekurang-kurangnya 18 tahun dan merupakan tentera. Anda bertanggungjawab memastikan maklumat akaun sentiasa tepat dan terkini. Anda juga bertanggungjawab menjaga kerahsiaan kata laluan serta segala aktiviti yang berlaku di bawah akaun anda.
|
||||
Anda mesti berumur sekurang-kurangnya 18 tahun dan merupakan ahli KOPKB. Anda bertanggungjawab memastikan maklumat akaun sentiasa tepat dan terkini. Anda juga bertanggungjawab menjaga kerahsiaan kata laluan serta segala aktiviti yang berlaku di bawah akaun anda.
|
||||
`,
|
||||
},
|
||||
{
|
||||
@@ -39,7 +39,7 @@ Sesetengah perkhidmatan mungkin tertakluk kepada bayaran yang dinyatakan semasa
|
||||
id: 'kandungan',
|
||||
title: '5. Kandungan & Hak Milik',
|
||||
placeholder: `
|
||||
Semua kandungan, reka bentuk, logo, teks, grafik, dan bahan lain yang terdapat pada platform ini adalah hak milik Kementerian Pertahanan Malaysia (MINDEF) atau pemberi lesennya. Anda diberikan lesen terhad untuk menggunakan kandungan tersebut bagi tujuan penggunaan peribadi dan bukan komersial sahaja. Sebarang penyalinan, pengubahsuaian, atau pengedaran tanpa kebenaran bertulis adalah dilarang.
|
||||
Semua kandungan, reka bentuk, logo, teks, grafik, dan bahan lain yang terdapat pada platform ini adalah hak milik Koperasi Permodalan Kelantan Berhad (KOPKB) atau pemberi lesennya. Anda diberikan lesen terhad untuk menggunakan kandungan tersebut bagi tujuan penggunaan peribadi dan bukan komersial sahaja. Sebarang penyalinan, pengubahsuaian, atau pengedaran tanpa kebenaran bertulis adalah dilarang.
|
||||
`,
|
||||
},
|
||||
{
|
||||
@@ -69,8 +69,8 @@ Kami boleh mengemas kini atau meminda Terma dan Syarat ini dari semasa ke semasa
|
||||
placeholder: `
|
||||
Sekiranya anda mempunyai sebarang pertanyaan berkaitan Terma dan Syarat ini, sila hubungi:
|
||||
|
||||
Nama: KAPT MOHAMMAD EFANDY BIN JAFFARI
|
||||
E-mel: effandy.jaffari@army.mil.my
|
||||
Nama: Koperasi Permodalan Kelantan Berhad (KOPKB)
|
||||
E-mel: admin@koppkb.com
|
||||
`,
|
||||
},
|
||||
] as const;
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useExternalSystemList } from './useExternalSystemList'
|
||||
import {
|
||||
getExternalSystemStatus,
|
||||
isExternalSystemAccessible,
|
||||
} from '../utils/external-system.utils'
|
||||
|
||||
export function useExternalSystemDetail() {
|
||||
const route = useRoute()
|
||||
const { getSystemById } = useExternalSystemList()
|
||||
|
||||
const loading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
const systemId = computed(() => String(route.params.id ?? ''))
|
||||
|
||||
const system = computed(() => getSystemById(systemId.value) ?? null)
|
||||
|
||||
const status = computed(() => (system.value ? getExternalSystemStatus(system.value) : null))
|
||||
|
||||
const isAccessible = computed(() =>
|
||||
system.value ? isExternalSystemAccessible(system.value) : false,
|
||||
)
|
||||
|
||||
watch(
|
||||
systemId,
|
||||
() => {
|
||||
error.value = system.value ? null : 'Sistem luaran tidak dijumpai.'
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
return {
|
||||
system,
|
||||
loading,
|
||||
error,
|
||||
status,
|
||||
isAccessible,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { computed, ref } from 'vue'
|
||||
import { dummyExternalSystems } from '../data/dummy-external-systems'
|
||||
import {
|
||||
externalSystemStatusLabel,
|
||||
getExternalSystemStatus,
|
||||
} from '../utils/external-system.utils'
|
||||
import type { ExternalSystem } from '../types/external-system.types'
|
||||
|
||||
export function useExternalSystemList() {
|
||||
const search = ref('')
|
||||
const loading = ref(false)
|
||||
|
||||
const systems = computed(() => {
|
||||
const query = search.value.trim().toLowerCase()
|
||||
if (!query) {
|
||||
return dummyExternalSystems
|
||||
}
|
||||
|
||||
return dummyExternalSystems.filter((system) => {
|
||||
const haystack = [
|
||||
system.name,
|
||||
system.code,
|
||||
system.description,
|
||||
externalSystemStatusLabel(getExternalSystemStatus(system)),
|
||||
]
|
||||
.join(' ')
|
||||
.toLowerCase()
|
||||
|
||||
return haystack.includes(query)
|
||||
})
|
||||
})
|
||||
|
||||
const availableCount = computed(
|
||||
() => systems.value.filter((system) => getExternalSystemStatus(system) === 'available').length,
|
||||
)
|
||||
|
||||
function getSystemById(id: string): ExternalSystem | undefined {
|
||||
return dummyExternalSystems.find((system) => system.id === id)
|
||||
}
|
||||
|
||||
return {
|
||||
systems,
|
||||
search,
|
||||
loading,
|
||||
availableCount,
|
||||
getSystemById,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import type { ExternalSystem } from '../types/external-system.types'
|
||||
|
||||
export const dummyExternalSystems: ExternalSystem[] = [
|
||||
{
|
||||
id: 'ext-001',
|
||||
code: 'KOPKB-PORTAL',
|
||||
name: 'Portal Ahli KOPKB',
|
||||
description:
|
||||
'Sistem utama keahlian Koperasi Permodalan Kelantan Berhad untuk semakan dividen, penyata dan maklumat ahli.',
|
||||
url: 'https://anggota.koppkb.com',
|
||||
icon: 'Users',
|
||||
is_active: true,
|
||||
starts_at: '2026-01-01T00:00:00+08:00',
|
||||
ends_at: null,
|
||||
opens_in_new_tab: true,
|
||||
contact_email: 'sokongan@koppkb.com',
|
||||
notes: 'Log masuk menggunakan e-mel berdaftar ahli KOPKB.',
|
||||
created_at: '2026-01-15T09:00:00+08:00',
|
||||
updated_at: '2026-06-01T14:30:00+08:00',
|
||||
},
|
||||
{
|
||||
id: 'ext-002',
|
||||
code: 'AGM-VOTE',
|
||||
name: 'Sistem Pengundian AGM',
|
||||
description:
|
||||
'Platform pengundian dalam talian untuk Mesyuarat Agung Tahunan. Hanya tersedia semasa tempoh pengundian.',
|
||||
url: 'https://e-vote.erahn.com.my/login',
|
||||
icon: 'Vote',
|
||||
is_active: true,
|
||||
starts_at: '2026-05-01T08:00:00+08:00',
|
||||
ends_at: null,
|
||||
opens_in_new_tab: true,
|
||||
contact_email: 'agm@koppkb.com',
|
||||
notes: 'Sila lengkapkan profil sebelum mengundi.',
|
||||
created_at: '2026-05-20T10:00:00+08:00',
|
||||
updated_at: '2026-06-28T11:15:00+08:00',
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,2 @@
|
||||
export { externalSystemLayoutRoutes } from './routes'
|
||||
export { externalSystemMenu } from './menu'
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { Menu } from '@/core/types/menu'
|
||||
|
||||
export const externalSystemMenu: Menu[] = [
|
||||
{
|
||||
icon: 'ExternalLink',
|
||||
route_name: 'list-external-systems',
|
||||
title: 'Sistem Luaran',
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,115 @@
|
||||
<script lang="ts" setup>
|
||||
import { useRouter } from 'vue-router'
|
||||
import { AlertRoot, AlertTitle, AlertDescription } from '@/components/ui/alert'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Box } from '@/components/ui/box'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Lucide } from '@/components/ui/lucide'
|
||||
import { useExternalSystemDetail } from '../composables/useExternalSystemDetail'
|
||||
import {
|
||||
externalSystemStatusLabel,
|
||||
externalSystemStatusVariant,
|
||||
formatExternalSystemDateTime,
|
||||
openExternalSystem,
|
||||
} from '../utils/external-system.utils'
|
||||
|
||||
const router = useRouter()
|
||||
const { system, error, status, isAccessible } = useExternalSystemDetail()
|
||||
|
||||
function goBack() {
|
||||
router.push({ name: 'list-external-systems' })
|
||||
}
|
||||
|
||||
function handleOpen() {
|
||||
if (!system.value) return
|
||||
openExternalSystem(system.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="mb-5 flex flex-wrap items-center gap-3">
|
||||
<Button variant="ghost" look="outline" @click="goBack">
|
||||
<Lucide icon="ArrowLeft" class="size-4" />
|
||||
Kembali
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<AlertRoot v-if="error" class="mb-6" variant="danger">
|
||||
<AlertTitle>Ralat</AlertTitle>
|
||||
<AlertDescription>{{ error }}</AlertDescription>
|
||||
</AlertRoot>
|
||||
|
||||
<template v-else-if="system && status">
|
||||
<Box class="p-5 sm:p-6">
|
||||
<div class="flex flex-wrap items-start justify-between gap-4">
|
||||
<div class="flex min-w-0 items-start gap-4">
|
||||
<div
|
||||
class="flex size-12 shrink-0 items-center justify-center rounded-2xl bg-primary/10 text-primary"
|
||||
>
|
||||
<Lucide :icon="system.icon" class="size-6" />
|
||||
</div>
|
||||
<div class="min-w-0">
|
||||
<div class="text-sm opacity-70">Sistem Luaran</div>
|
||||
<h2 class="text-xl font-semibold">{{ system.name }}</h2>
|
||||
<div class="mt-1 text-sm font-medium text-primary/80">{{ system.code }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Badge look="outline" :variant="externalSystemStatusVariant(status)">
|
||||
{{ externalSystemStatusLabel(status) }}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<p class="mt-5 max-w-3xl text-sm leading-relaxed opacity-80">
|
||||
{{ system.description }}
|
||||
</p>
|
||||
|
||||
<div class="mt-6 grid gap-3 text-sm sm:grid-cols-2 lg:grid-cols-4">
|
||||
<div>
|
||||
<span class="opacity-70">URL:</span>
|
||||
<div class="mt-0.5 break-all font-medium">{{ system.url }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<span class="opacity-70">Tarikh Mula:</span>
|
||||
<div class="mt-0.5 font-medium">{{ formatExternalSystemDateTime(system.starts_at) }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<span class="opacity-70">Tarikh Tamat:</span>
|
||||
<div class="mt-0.5 font-medium">{{ formatExternalSystemDateTime(system.ends_at) }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<span class="opacity-70">E-mel Sokongan:</span>
|
||||
<div class="mt-0.5 font-medium">{{ system.contact_email ?? '-' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="system.notes" class="mt-6 border-t border-foreground/10 pt-5">
|
||||
<div class="text-sm font-medium opacity-70">Nota</div>
|
||||
<p class="mt-2 text-sm leading-relaxed opacity-80">{{ system.notes }}</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex flex-wrap gap-3 border-t border-foreground/10 pt-5">
|
||||
<Button
|
||||
look="outline"
|
||||
variant="primary"
|
||||
:disabled="!isAccessible"
|
||||
@click="handleOpen"
|
||||
>
|
||||
Buka Sistem
|
||||
<Lucide icon="ExternalLink" class="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<AlertRoot v-if="!isAccessible" class="mt-4" look="outline" variant="pending">
|
||||
<Lucide class="mr-2 size-4 shrink-0" icon="Clock" />
|
||||
<AlertTitle>Sistem Tidak Tersedia</AlertTitle>
|
||||
<AlertDescription>
|
||||
Pautan ini hanya boleh dibuka semasa tempoh acara yang ditetapkan dan sistem berstatus
|
||||
aktif.
|
||||
</AlertDescription>
|
||||
</AlertRoot>
|
||||
</Box>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,104 @@
|
||||
<script lang="ts" setup>
|
||||
import { useRouter } from 'vue-router'
|
||||
import { AlertRoot, AlertTitle, AlertDescription } from '@/components/ui/alert'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Box } from '@/components/ui/box'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Lucide } from '@/components/ui/lucide'
|
||||
import { useExternalSystemList } from '../composables/useExternalSystemList'
|
||||
import {
|
||||
externalSystemStatusLabel,
|
||||
externalSystemStatusVariant,
|
||||
formatExternalSystemDateTime,
|
||||
getExternalSystemStatus,
|
||||
openExternalSystem,
|
||||
} from '../utils/external-system.utils'
|
||||
import type { ExternalSystem } from '../types/external-system.types'
|
||||
|
||||
const router = useRouter()
|
||||
const { systems, search, loading, availableCount } = useExternalSystemList()
|
||||
|
||||
function goToDetail(system: ExternalSystem) {
|
||||
router.push({ name: 'view-external-system', params: { id: system.id } })
|
||||
}
|
||||
|
||||
function handleOpen(system: ExternalSystem) {
|
||||
openExternalSystem(system)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="flex flex-wrap items-start justify-between gap-4">
|
||||
<div>
|
||||
<h2 class="text-lg font-medium">Sistem Luaran</h2>
|
||||
</div>
|
||||
<Badge look="outline" variant="primary">
|
||||
{{ availableCount }} tersedia
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div class="mt-5 grid grid-cols-12 gap-x-6 gap-y-8">
|
||||
<div class="col-span-12 mt-2 flex flex-wrap items-center sm:flex-nowrap">
|
||||
<div class="w-full sm:w-auto">
|
||||
<div class="relative w-56">
|
||||
<Input v-model="search" class="w-56 pr-10" type="search" placeholder="Cari sistem..." :disabled="loading" />
|
||||
<Lucide class="absolute inset-y-0 right-0 my-auto mr-3 h-4 w-4" icon="Search" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="systems.length">
|
||||
<Box v-for="system in systems" :key="system.id"
|
||||
class="col-span-12 flex h-full flex-col p-5 md:col-span-6 xl:col-span-4">
|
||||
<div class="mb-4 flex items-start justify-between gap-3">
|
||||
<div class="flex size-10 shrink-0 items-center justify-center rounded-xl bg-primary/10 text-primary">
|
||||
<Lucide :icon="system.icon" class="size-5" />
|
||||
</div>
|
||||
<Badge look="outline" :variant="externalSystemStatusVariant(getExternalSystemStatus(system))">
|
||||
{{ externalSystemStatusLabel(getExternalSystemStatus(system)) }}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div class="text-base font-medium">{{ system.name }}</div>
|
||||
<div class="mt-1 text-xs font-medium uppercase tracking-wide text-primary/80">
|
||||
{{ system.code }}
|
||||
</div>
|
||||
<p class="mt-2 flex-1 text-sm leading-relaxed opacity-70">
|
||||
{{ system.description }}
|
||||
</p>
|
||||
|
||||
<div class="mt-4 space-y-1 text-xs opacity-70">
|
||||
<div>
|
||||
<span class="font-medium">Mula:</span>
|
||||
{{ formatExternalSystemDateTime(system.starts_at) }}
|
||||
</div>
|
||||
<div>
|
||||
<span class="font-medium">Tamat:</span>
|
||||
{{ formatExternalSystemDateTime(system.ends_at) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-5 flex flex-col gap-2 sm:flex-row">
|
||||
<Button class="w-full sm:flex-1" look="outline" variant="primary"
|
||||
:disabled="getExternalSystemStatus(system) !== 'available'" @click="handleOpen(system)">
|
||||
Buka Sistem
|
||||
<Lucide icon="ExternalLink" class="size-4" />
|
||||
</Button>
|
||||
<Button class="w-full sm:flex-1" variant="ghost" @click="goToDetail(system)">
|
||||
Butiran
|
||||
<Lucide icon="ArrowRight" class="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</Box>
|
||||
</template>
|
||||
|
||||
<Box v-else class="col-span-12 p-8 text-center">
|
||||
<Lucide icon="SearchX" class="mx-auto size-8 opacity-40" />
|
||||
<div class="mt-3 text-base font-medium">Tiada sistem dijumpai</div>
|
||||
<p class="mt-1 text-sm opacity-70">Cuba istilah carian yang berbeza.</p>
|
||||
</Box>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,22 @@
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
|
||||
export const externalSystemLayoutRoutes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: 'external-systems',
|
||||
name: 'list-external-systems',
|
||||
component: () => import('./pages/ExternalSystemList.vue'),
|
||||
meta: {
|
||||
title: 'Senarai Sistem Luaran',
|
||||
module: 'external-system',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'external-systems/:id',
|
||||
name: 'view-external-system',
|
||||
component: () => import('./pages/ExternalSystemDetail.vue'),
|
||||
meta: {
|
||||
title: 'Butiran Sistem Luaran',
|
||||
module: 'external-system',
|
||||
},
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { Icon } from '@/components/ui/lucide'
|
||||
|
||||
export type ExternalSystemStatus = 'available' | 'upcoming' | 'ended' | 'inactive'
|
||||
|
||||
export type ExternalSystem = {
|
||||
id: string
|
||||
code: string
|
||||
name: string
|
||||
description: string
|
||||
url: string
|
||||
icon: Icon
|
||||
is_active: boolean
|
||||
starts_at: string | null
|
||||
ends_at: string | null
|
||||
opens_in_new_tab: boolean
|
||||
contact_email: string | null
|
||||
notes: string | null
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import dayjs from 'dayjs'
|
||||
import type { BadgeVariants } from '@/components/ui/styles/badge.styles'
|
||||
import type { ExternalSystem, ExternalSystemStatus } from '../types/external-system.types'
|
||||
|
||||
export function getExternalSystemStatus(
|
||||
system: ExternalSystem,
|
||||
now = dayjs(),
|
||||
): ExternalSystemStatus {
|
||||
if (!system.is_active) {
|
||||
return 'inactive'
|
||||
}
|
||||
|
||||
const startsAt = system.starts_at ? dayjs(system.starts_at) : null
|
||||
const endsAt = system.ends_at ? dayjs(system.ends_at) : null
|
||||
|
||||
if (startsAt?.isAfter(now)) {
|
||||
return 'upcoming'
|
||||
}
|
||||
|
||||
if (endsAt?.isBefore(now)) {
|
||||
return 'ended'
|
||||
}
|
||||
|
||||
return 'available'
|
||||
}
|
||||
|
||||
export function isExternalSystemAccessible(system: ExternalSystem, now = dayjs()): boolean {
|
||||
return getExternalSystemStatus(system, now) === 'available'
|
||||
}
|
||||
|
||||
export function externalSystemStatusLabel(status: ExternalSystemStatus): string {
|
||||
switch (status) {
|
||||
case 'available':
|
||||
return 'Tersedia'
|
||||
case 'upcoming':
|
||||
return 'Akan Datang'
|
||||
case 'ended':
|
||||
return 'Tamat'
|
||||
case 'inactive':
|
||||
return 'Tidak Aktif'
|
||||
}
|
||||
}
|
||||
|
||||
export function externalSystemStatusVariant(
|
||||
status: ExternalSystemStatus,
|
||||
): NonNullable<BadgeVariants['variant']> {
|
||||
switch (status) {
|
||||
case 'available':
|
||||
return 'success'
|
||||
case 'upcoming':
|
||||
return 'pending'
|
||||
case 'ended':
|
||||
return 'ghost'
|
||||
case 'inactive':
|
||||
return 'danger'
|
||||
}
|
||||
}
|
||||
|
||||
export function formatExternalSystemDateTime(value: string | null): string {
|
||||
if (!value) return '-'
|
||||
return dayjs(value).format('DD MMM YYYY, HH:mm')
|
||||
}
|
||||
|
||||
export function openExternalSystem(system: ExternalSystem) {
|
||||
if (!isExternalSystemAccessible(system)) {
|
||||
return
|
||||
}
|
||||
|
||||
window.open(
|
||||
system.url,
|
||||
system.opens_in_new_tab ? '_blank' : '_self',
|
||||
'noopener,noreferrer',
|
||||
)
|
||||
}
|
||||
@@ -223,7 +223,7 @@ onMounted(async () => {
|
||||
<div class="truncate text-base font-medium">
|
||||
{{ displayValue(companyName) }}
|
||||
</div>
|
||||
<div class="text-xs opacity-70">Syarikat</div>
|
||||
<div class="text-xs opacity-70">Unit</div>
|
||||
</div>
|
||||
<div class="col-span-2 text-center sm:col-span-1 lg:text-left">
|
||||
<div class="truncate text-base font-medium">
|
||||
|
||||
@@ -113,7 +113,7 @@ onMounted(() => {
|
||||
<div class="flex items-center gap-3 rounded-lg border border-foreground/10 p-4">
|
||||
<Lucide class="size-5 text-primary" icon="Briefcase" />
|
||||
<div class="min-w-0">
|
||||
<div class="text-xs uppercase tracking-wide text-slate-500">Syarikat</div>
|
||||
<div class="text-xs uppercase tracking-wide text-slate-500">Unit</div>
|
||||
<div class="truncate text-base font-medium text-slate-900">
|
||||
{{ displayValue(member.company_name) }}
|
||||
</div>
|
||||
|
||||
+4
-244
@@ -1,6 +1,5 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import Layout from '@/themes'
|
||||
import DocsLayout from '@/docs/DocsLayout.vue'
|
||||
import { hasAnyActiveRolePermission } from '@/core/utils/activeRolePermission'
|
||||
import { getRouteRequiredPermissions } from '@/core/utils/routePermission'
|
||||
import { authPublicRoutes, resolvePostAuthRoute } from '@/modules/auth'
|
||||
@@ -12,6 +11,8 @@ import { userLayoutRoutes } from '@/modules/user'
|
||||
import { roleLayoutRoutes } from '@/modules/role'
|
||||
import { activityLayoutRoutes } from '@/modules/activity'
|
||||
import { dashboardLayoutRoutes } from '@/modules/dashboard'
|
||||
import { externalSystemLayoutRoutes } from '@/modules/external-system'
|
||||
import { activityLogLayoutRoutes } from '@/modules/activity-log'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
@@ -22,259 +23,18 @@ const router = createRouter({
|
||||
component: Layout,
|
||||
children: [
|
||||
...dashboardLayoutRoutes,
|
||||
{
|
||||
path: 'categories',
|
||||
name: 'categories',
|
||||
component: () => import('../views/Categories.vue'),
|
||||
},
|
||||
{
|
||||
path: 'add-product',
|
||||
name: 'add-product',
|
||||
component: () => import('../views/AddProduct.vue'),
|
||||
},
|
||||
{
|
||||
path: 'product-list',
|
||||
name: 'product-list',
|
||||
component: () => import('../views/ProductList.vue'),
|
||||
},
|
||||
{
|
||||
path: 'product-grid',
|
||||
name: 'product-grid',
|
||||
component: () => import('../views/ProductGrid.vue'),
|
||||
},
|
||||
{
|
||||
path: 'transaction-list',
|
||||
name: 'transaction-list',
|
||||
component: () => import('../views/TransactionList.vue'),
|
||||
},
|
||||
{
|
||||
path: 'transaction-detail',
|
||||
name: 'transaction-detail',
|
||||
component: () => import('../views/TransactionDetail.vue'),
|
||||
},
|
||||
{
|
||||
path: 'seller-list',
|
||||
name: 'seller-list',
|
||||
component: () => import('../views/SellerList.vue'),
|
||||
},
|
||||
{
|
||||
path: 'seller-detail',
|
||||
name: 'seller-detail',
|
||||
component: () => import('../views/SellerDetail.vue'),
|
||||
},
|
||||
{
|
||||
path: 'reviews',
|
||||
name: 'reviews',
|
||||
component: () => import('../views/Reviews.vue'),
|
||||
},
|
||||
{
|
||||
path: 'file-manager',
|
||||
name: 'file-manager',
|
||||
component: () => import('../views/FileManager.vue'),
|
||||
},
|
||||
{
|
||||
path: 'point-of-sale',
|
||||
name: 'point-of-sale',
|
||||
component: () => import('../views/PointOfSale.vue'),
|
||||
},
|
||||
{
|
||||
path: 'crud-form',
|
||||
name: 'crud-form',
|
||||
component: () => import('../views/CrudForm.vue'),
|
||||
},
|
||||
{
|
||||
path: 'blog-layout-1',
|
||||
name: 'blog-layout-1',
|
||||
component: () => import('../views/BlogLayout1.vue'),
|
||||
},
|
||||
{
|
||||
path: 'blog-layout-2',
|
||||
name: 'blog-layout-2',
|
||||
component: () => import('../views/BlogLayout2.vue'),
|
||||
},
|
||||
{
|
||||
path: 'blog-layout-3',
|
||||
name: 'blog-layout-3',
|
||||
component: () => import('../views/BlogLayout3.vue'),
|
||||
},
|
||||
{
|
||||
path: 'pricing-layout-1',
|
||||
name: 'pricing-layout-1',
|
||||
component: () => import('../views/PricingLayout1.vue'),
|
||||
},
|
||||
{
|
||||
path: 'pricing-layout-2',
|
||||
name: 'pricing-layout-2',
|
||||
component: () => import('../views/PricingLayout2.vue'),
|
||||
},
|
||||
{
|
||||
path: 'invoice-layout-1',
|
||||
name: 'invoice-layout-1',
|
||||
component: () => import('../views/InvoiceLayout1.vue'),
|
||||
},
|
||||
{
|
||||
path: 'invoice-layout-2',
|
||||
name: 'invoice-layout-2',
|
||||
component: () => import('../views/InvoiceLayout2.vue'),
|
||||
},
|
||||
{
|
||||
path: 'faq-layout-1',
|
||||
name: 'faq-layout-1',
|
||||
component: () => import('../views/FaqLayout1.vue'),
|
||||
},
|
||||
{
|
||||
path: 'faq-layout-2',
|
||||
name: 'faq-layout-2',
|
||||
component: () => import('../views/FaqLayout2.vue'),
|
||||
},
|
||||
{
|
||||
path: 'faq-layout-3',
|
||||
name: 'faq-layout-3',
|
||||
component: () => import('../views/FaqLayout3.vue'),
|
||||
},
|
||||
...profileLayoutRoutes,
|
||||
...userLayoutRoutes,
|
||||
...roleLayoutRoutes,
|
||||
...activityLogLayoutRoutes,
|
||||
...activityLayoutRoutes,
|
||||
...externalSystemLayoutRoutes,
|
||||
...membershipApplicationLayoutRoutes,
|
||||
// Docs Layout
|
||||
{
|
||||
path: '/',
|
||||
component: DocsLayout,
|
||||
children: [
|
||||
// Documentation
|
||||
{
|
||||
path: "box",
|
||||
name: "box",
|
||||
component: () => import("../docs/pages/box.vue"),
|
||||
},
|
||||
{
|
||||
path: "breadcrumb",
|
||||
name: "breadcrumb",
|
||||
component: () => import("../docs/pages/breadcrumb.vue"),
|
||||
},
|
||||
{
|
||||
path: "chart",
|
||||
name: "chart",
|
||||
component: () => import("../docs/pages/chart.vue"),
|
||||
},
|
||||
{
|
||||
path: "combobox",
|
||||
name: "combobox",
|
||||
component: () => import("../docs/pages/combobox.vue"),
|
||||
},
|
||||
{
|
||||
path: "datepicker",
|
||||
name: "datepicker",
|
||||
component: () => import("../docs/pages/datepicker.vue"),
|
||||
},
|
||||
{
|
||||
path: "dialog",
|
||||
name: "dialog",
|
||||
component: () => import("../docs/pages/dialog.vue"),
|
||||
},
|
||||
{
|
||||
path: "field",
|
||||
name: "field",
|
||||
component: () => import("../docs/pages/field.vue"),
|
||||
},
|
||||
{
|
||||
path: "menu",
|
||||
name: "menu",
|
||||
component: () => import("../docs/pages/menu.vue"),
|
||||
},
|
||||
{
|
||||
path: "pagination",
|
||||
name: "pagination",
|
||||
component: () => import("../docs/pages/pagination.vue"),
|
||||
},
|
||||
{
|
||||
path: "popover",
|
||||
name: "popover",
|
||||
component: () => import("../docs/pages/popover.vue"),
|
||||
},
|
||||
{
|
||||
path: "progress-circular",
|
||||
name: "progress-circular",
|
||||
component: () => import("../docs/pages/progress-circular.vue"),
|
||||
},
|
||||
{
|
||||
path: "progress-linear",
|
||||
name: "progress-linear",
|
||||
component: () => import("../docs/pages/progress-linear.vue"),
|
||||
},
|
||||
{
|
||||
path: "radio-group",
|
||||
name: "radio-group",
|
||||
component: () => import("../docs/pages/radio-group.vue"),
|
||||
},
|
||||
{
|
||||
path: "scroll-area",
|
||||
name: "scroll-area",
|
||||
component: () => import("../docs/pages/scroll-area.vue"),
|
||||
},
|
||||
{
|
||||
path: "select",
|
||||
name: "select",
|
||||
component: () => import("../docs/pages/select.vue"),
|
||||
},
|
||||
{
|
||||
path: "sheet",
|
||||
name: "sheet",
|
||||
component: () => import("../docs/pages/sheet.vue"),
|
||||
},
|
||||
{
|
||||
path: "slider",
|
||||
name: "slider",
|
||||
component: () => import("../docs/pages/slider.vue"),
|
||||
},
|
||||
{
|
||||
path: "slot",
|
||||
name: "slot",
|
||||
component: () => import("../docs/pages/slot.vue"),
|
||||
},
|
||||
{
|
||||
path: "switch",
|
||||
name: "switch",
|
||||
component: () => import("../docs/pages/switch.vue"),
|
||||
},
|
||||
{
|
||||
path: "tabs",
|
||||
name: "tabs",
|
||||
component: () => import("../docs/pages/tabs.vue"),
|
||||
},
|
||||
{
|
||||
path: "textarea",
|
||||
name: "textarea",
|
||||
component: () => import("../docs/pages/textarea.vue"),
|
||||
},
|
||||
{
|
||||
path: "toast",
|
||||
name: "toast",
|
||||
component: () => import("../docs/pages/toast.vue"),
|
||||
},
|
||||
{
|
||||
path: "tooltip",
|
||||
name: "tooltip",
|
||||
component: () => import("../docs/pages/tooltip.vue"),
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
...authPublicRoutes,
|
||||
...membershipApplicationPublicRoutes,
|
||||
...profilePublicRoutes,
|
||||
{
|
||||
path: '/error-page',
|
||||
name: 'error-page',
|
||||
component: () => import('../views/ErrorPage.vue'),
|
||||
},
|
||||
{
|
||||
path: '/forbidden',
|
||||
name: 'forbidden',
|
||||
component: () => import('../views/ForbiddenPage.vue'),
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import { useBreadcrumb } from '@/composables/useBreadcrumb'
|
||||
import { useNotifications } from '@/modules/notification'
|
||||
import '@/assets/css/themes/enigma/side-menu.css'
|
||||
import logo from '@/assets/images/logonew-mykopkb.svg'
|
||||
import logo from '@/assets/images/logo.svg'
|
||||
import { useSideMenu } from '@/composables/useSideMenu'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { Lucide } from '@/components/ui/lucide'
|
||||
@@ -129,16 +129,18 @@ onUnmounted(() => {
|
||||
<!-- Logo -->
|
||||
<div
|
||||
class="relative z-10 mt-3 hidden h-[90px] w-[320px] flex-none items-center overflow-hidden pl-8 pr-14 duration-100 xl:flex group-[.side-menu--collapsed]:group-hover:xl:w-[320px] group-[.side-menu--collapsed]:xl:w-[165px]">
|
||||
<a class="relative flex items-center transition-[margin] duration-100 xl:ml-1.5 group-[.side-menu--collapsed]:group-hover:xl:ml-1.5 group-[.side-menu--collapsed]:xl:ml-8"
|
||||
<a class="relative flex items-center pr-9 transition-[margin] duration-100 xl:ml-1.5 group-[.side-menu--collapsed]:group-hover:xl:ml-1.5 group-[.side-menu--collapsed]:xl:ml-8"
|
||||
href="">
|
||||
<img class="size-12" :src="logo" />
|
||||
<span class="inline-flex shrink-0 items-center rounded-md bg-white px-2 py-1">
|
||||
<img class="h-auto max-h-5 w-auto max-w-18 object-contain object-left" :src="logo" alt="" />
|
||||
</span>
|
||||
<div
|
||||
class="dark:text-foreground text-background ml-3.5 text-nowrap transition-opacity group-[.side-menu--collapsed]:group-hover:xl:opacity-100 group-[.side-menu--collapsed]:xl:opacity-0">
|
||||
<span class="text-base font-medium">{{ appName }} {{ appVersion }}</span>
|
||||
</div>
|
||||
</a>
|
||||
<button type="button" @click="toggleCompactMenu" aria-label="Toggle compact menu"
|
||||
class="toggle-compact-menu text-background dark:text-foreground border-background/20 bg-background/10 dark:bg-foreground/2 dark:border-foreground/11 relative ml-auto hidden items-center justify-center rounded-md border py-0.5 pl-0.5 pr-1 opacity-70 transition-[opacity,transform] hover:opacity-100 group-[.side-menu--collapsed]:xl:rotate-180 group-[.side-menu--collapsed]:group-hover:xl:opacity-100 group-[.side-menu--collapsed]:xl:opacity-0 2xl:flex">
|
||||
class="toggle-compact-menu text-background dark:text-foreground border-background/20 bg-background/10 dark:bg-foreground/2 dark:border-foreground/11 absolute right-14 top-1/2 z-10 hidden -translate-y-1/2 items-center justify-center rounded-md border py-0.5 pl-0.5 pr-1 opacity-70 transition-[opacity,transform] hover:opacity-100 group-[.side-menu--collapsed]:xl:rotate-180 group-[.side-menu--collapsed]:group-hover:xl:opacity-100 group-[.side-menu--collapsed]:xl:opacity-0 2xl:flex">
|
||||
<Lucide icon="ChevronLeft" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
+11
-12
@@ -1,6 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { useThemeStore, getTheme, themes, type Themes } from '@/stores/theme'
|
||||
import { ThemeSwitcher } from '@/components/theme-switcher'
|
||||
import { Lucide, type Icon as LucideIcon } from '@/components/ui/lucide'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { onMounted, computed } from 'vue'
|
||||
@@ -56,19 +55,19 @@ const sideTabs: SideTab[] = [
|
||||
icon: 'ci:facebook',
|
||||
offsetRem: 7,
|
||||
onClick: () => {
|
||||
window.open('https://www.facebook.com/p/Rejimen-Askar-Jurutera-DiRaja-100086287161777/', '_blank')
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'feedback-page',
|
||||
label: 'Helpdesk',
|
||||
iconType: 'lucide',
|
||||
icon: 'MessageCircle',
|
||||
offsetRem: 10.5,
|
||||
onClick: () => {
|
||||
window.open('https://docs.google.com/forms/d/e/1FAIpQLSd_0p-kKmsHq9z4fZ3e6X13V6O64s1-CpJw4_vR768Gg0_sA/viewform?usp=header', '_blank')
|
||||
window.open('https://www.facebook.com/KoperasiPermodalanKelantanBerhad/', '_blank')
|
||||
},
|
||||
},
|
||||
// {
|
||||
// id: 'feedback-page',
|
||||
// label: 'Helpdesk',
|
||||
// iconType: 'lucide',
|
||||
// icon: 'MessageCircle',
|
||||
// offsetRem: 10.5,
|
||||
// onClick: () => {
|
||||
// window.open('https://docs.google.com/forms/d/e/1FAIpQLSd_0p-kKmsHq9z4fZ3e6X13V6O64s1-CpJw4_vR768Gg0_sA/viewform?usp=header', '_blank')
|
||||
// },
|
||||
// },
|
||||
]
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { useColorSchemeStore } from '@/stores/color-scheme'
|
||||
import { useDarkModeStore } from '@/stores/dark-mode'
|
||||
|
||||
export function applyColorSchemeClass() {
|
||||
const el = document.documentElement
|
||||
el.setAttribute('data-theme', useColorSchemeStore().colorSchemeValue)
|
||||
if (useDarkModeStore().darkModeValue) {
|
||||
el.classList.add('dark')
|
||||
}
|
||||
}
|
||||
|
||||
export function applyDarkModeClass() {
|
||||
const el = document.documentElement
|
||||
if (useDarkModeStore().darkModeValue) {
|
||||
el.classList.add('dark')
|
||||
} else {
|
||||
el.classList.remove('dark')
|
||||
}
|
||||
}
|
||||
|
||||
export function applyAppearance() {
|
||||
applyColorSchemeClass()
|
||||
applyDarkModeClass()
|
||||
}
|
||||
@@ -1,913 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import _ from 'lodash'
|
||||
import fakers from '@/utils/faker'
|
||||
import { AlertRoot, AlertTitle, AlertCloseTrigger } from '@/components/ui/alert'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Box } from '@/components/ui/box'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Field, FieldDescription } from '@/components/ui/field'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Lucide } from '@/components/ui/lucide'
|
||||
import {
|
||||
RadioGroupRoot,
|
||||
RadioGroupItem,
|
||||
RadioGroupItemControl,
|
||||
RadioGroupItemText,
|
||||
} from '@/components/ui/radio-group'
|
||||
import { SwitchRoot, SwitchControl, SwitchLabel } from '@/components/ui/switch'
|
||||
import {
|
||||
Table,
|
||||
TableHeader,
|
||||
TableBody,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableCell,
|
||||
} from '@/components/ui/table'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import {
|
||||
TooltipRoot,
|
||||
TooltipTrigger,
|
||||
TooltipPositioner,
|
||||
TooltipContent,
|
||||
} from '@/components/ui/tooltip'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center">
|
||||
<h2 class="mr-auto text-lg font-medium">Add Product</h2>
|
||||
</div>
|
||||
<div class="mt-5 grid grid-cols-11 gap-x-6 pb-20">
|
||||
<!-- BEGIN: Notification -->
|
||||
<AlertRoot look="outline" class="col-span-11 mb-8" variant="primary">
|
||||
<Lucide class="mr-2 size-4" icon="Info" />
|
||||
<AlertTitle>
|
||||
Starting May 10, 2024, there will be changes to the Terms & Conditions regarding the
|
||||
number of products that may be added by the Seller.
|
||||
<a class="ml-1 font-medium underline decoration-dotted underline-offset-3"
|
||||
href="https://themeforest.net/item/midone-jquery-tailwindcss-html-admin-template/26366820" target="blank">
|
||||
Learn More
|
||||
</a>
|
||||
</AlertTitle>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
<!-- END: Notification -->
|
||||
<div class="col-span-11 2xl:col-span-9">
|
||||
<!-- BEGIN: Upload Product -->
|
||||
<Box>
|
||||
<div class="rounded-lg border border-foreground/15 p-5">
|
||||
<div class="flex items-center border-b border-foreground/15 pb-5 text-base font-medium">
|
||||
<Lucide class="mr-2 size-4" icon="ChevronDown" /> Upload Product
|
||||
</div>
|
||||
<div class="mt-5 flex flex-col gap-5">
|
||||
<div class="mb-5 flex items-center">
|
||||
<span>
|
||||
<Lucide class="text-warning h-5 w-5" icon="Lightbulb" />
|
||||
</span>
|
||||
<div class="ml-2">
|
||||
<span class="mr-1 opacity-70">
|
||||
Avoid selling counterfeit products / violating Intellectual Property Rights, so
|
||||
that your products are not deleted.
|
||||
</span>
|
||||
<a class="text-primary font-medium"
|
||||
href="https://themeforest.net/item/midone-jquery-tailwindcss-html-admin-template/26366820"
|
||||
target="blank">
|
||||
Learn More
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-start xl:flex-row">
|
||||
<div class="w-full xl:mr-10 xl:w-64">
|
||||
<div class="text-left">
|
||||
<div class="flex items-center">
|
||||
<div class="font-medium">Product Photos</div>
|
||||
<Badge look="outline" variant="secondary" class="ml-3">Required</Badge>
|
||||
</div>
|
||||
<div class="mt-3 text-xs leading-relaxed opacity-70">
|
||||
<div>
|
||||
The image format is .jpg .jpeg .png and a minimum size of 300 x 300 pixels
|
||||
(For optimal images use a minimum size of 700 x 700 pixels).
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
Select product photos or drag and drop up to 5 photos at once here. Include
|
||||
min. 3 attractive photos to make the product more attractive to buyers.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 w-full flex-1 xl:mt-0">
|
||||
<div class="rounded-xl border-2 border-dashed border-foreground/15 pt-4">
|
||||
<div class="grid grid-cols-10 gap-5 pl-4 pr-5">
|
||||
<div v-for="(faker, fakerKey) in _.take(fakers, 5)" :key="fakerKey"
|
||||
class="image-fit relative col-span-5 h-28 cursor-pointer md:col-span-2">
|
||||
<img class="rounded-xl" :src="faker['images'][0]"
|
||||
alt="Midone - Tailwind Admin Dashboard Template" />
|
||||
<TooltipRoot>
|
||||
<TooltipTrigger as-child>
|
||||
<div
|
||||
class="bg-(--color)/80 border-(--color) absolute right-0 top-0 -mr-2 -mt-2 flex size-5 items-center justify-center rounded-full text-white [--color:var(--color-danger)]">
|
||||
<Lucide class="size-4" icon="X" />
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipPositioner>
|
||||
<TooltipContent>Remove this image?</TooltipContent>
|
||||
</TooltipPositioner>
|
||||
</TooltipRoot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative mt-5 flex cursor-pointer items-center justify-center px-4 pb-4">
|
||||
<Lucide class="mr-2 size-4" icon="Image" />
|
||||
<span class="text-primary mr-1"> Upload a file </span>
|
||||
or drag and drop
|
||||
<Input class="absolute left-0 top-0 h-full w-full opacity-0" id="horizontal-form-1" type="file" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
<!-- END: Upload Product -->
|
||||
<!-- BEGIN: Product Information -->
|
||||
<Box class="mt-8">
|
||||
<div class="rounded-lg border border-foreground/15 p-5">
|
||||
<div class="flex items-center border-b border-foreground/15 pb-5 text-base font-medium">
|
||||
<Lucide class="mr-2 size-4" icon="ChevronDown" /> Product Information
|
||||
</div>
|
||||
<div class="mt-5 flex flex-col gap-5">
|
||||
<div class="flex flex-col items-start xl:flex-row">
|
||||
<div class="w-full xl:mr-10 xl:w-64">
|
||||
<div class="text-left">
|
||||
<div class="flex items-center">
|
||||
<div class="font-medium">Product Name</div>
|
||||
<Badge look="outline" variant="secondary" class="ml-3">Required</Badge>
|
||||
</div>
|
||||
<div class="mt-3 text-xs leading-relaxed opacity-70">
|
||||
Include min. 40 characters to make it more attractive and easy for buyers to
|
||||
find, consisting of product type, brand, and information such as color,
|
||||
material, or type.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 w-full flex-1 xl:mt-0">
|
||||
<Field>
|
||||
<Input type="text" placeholder="Product name" />
|
||||
<FieldDescription class="text-right">Maximum character 0/70</FieldDescription>
|
||||
</Field>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-start xl:flex-row">
|
||||
<div class="w-full xl:mr-10 xl:w-64">
|
||||
<div class="text-left">
|
||||
<div class="flex items-center">
|
||||
<div class="font-medium">Category</div>
|
||||
<Badge look="outline" variant="secondary" class="ml-3">Required</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 w-full flex-1 xl:mt-0">
|
||||
<NativeSelect>
|
||||
<NativeSelectOption v-for="(faker, index) in fakers.slice(0, 9)" :key="index"
|
||||
:value="faker['categories'][0]!['name']">
|
||||
{{ faker['categories'][0]!['name'] }}
|
||||
</NativeSelectOption>
|
||||
</NativeSelect>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-start xl:flex-row">
|
||||
<div class="w-full xl:mr-10 xl:w-64">
|
||||
<div class="text-left">
|
||||
<div class="flex items-center">
|
||||
<div class="font-medium">Subcategory</div>
|
||||
</div>
|
||||
<div class="mt-3 text-xs leading-relaxed opacity-70">
|
||||
You can add a new subcategory or choose from the existing subcategory list.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 w-full flex-1 xl:mt-0">
|
||||
<NativeSelect>
|
||||
<NativeSelectOption v-for="(faker, index) in fakers.slice(0, 2)" :key="index"
|
||||
:value="faker['categories'][0]!['name']">
|
||||
{{ faker['categories'][0]!['name'] }}
|
||||
</NativeSelectOption>
|
||||
</NativeSelect>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
<!-- END: Product Information -->
|
||||
<!-- BEGIN: Product Detail -->
|
||||
<Box class="mt-8">
|
||||
<div class="rounded-lg border border-foreground/15 p-5">
|
||||
<div class="flex items-center border-b border-foreground/15 pb-5 text-base font-medium">
|
||||
<Lucide class="mr-2 size-4" icon="ChevronDown" /> Product Detail
|
||||
</div>
|
||||
<div class="mt-5 flex flex-col gap-5">
|
||||
<div class="flex flex-col items-start xl:flex-row">
|
||||
<div class="w-full xl:mr-10 xl:w-64">
|
||||
<div class="text-left">
|
||||
<div class="flex items-center">
|
||||
<div class="font-medium">Condition</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 w-full flex-1 xl:mt-0">
|
||||
<RadioGroupRoot class="w-40" defaultValue="new">
|
||||
<RadioGroupItem value="new">
|
||||
<RadioGroupItemControl />
|
||||
<RadioGroupItemText>New</RadioGroupItemText>
|
||||
</RadioGroupItem>
|
||||
<RadioGroupItem value="used">
|
||||
<RadioGroupItemControl />
|
||||
<RadioGroupItemText>Used</RadioGroupItemText>
|
||||
</RadioGroupItem>
|
||||
</RadioGroupRoot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-start xl:flex-row">
|
||||
<div class="w-full xl:mr-10 xl:w-64">
|
||||
<div class="text-left">
|
||||
<div class="flex items-center">
|
||||
<div class="font-medium">Product Description</div>
|
||||
</div>
|
||||
<div class="mt-3 text-xs leading-relaxed opacity-70">
|
||||
<div>
|
||||
Make sure the product description provides a detailed explanation of your
|
||||
product so that it is easy to understand and find your product.
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
It is recommended not to enter info on mobile numbers, e-mails, etc. into the
|
||||
product description to protect your personal data.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 w-full flex-1 xl:mt-0">
|
||||
<Field>
|
||||
<Textarea placeholder="Content of the editor." class="min-h-[200px]" />
|
||||
<FieldDescription class="text-right">Maximum character 0/2000</FieldDescription>
|
||||
</Field>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-start xl:flex-row">
|
||||
<div class="w-full xl:mr-10 xl:w-64">
|
||||
<div class="text-left">
|
||||
<div class="flex items-center">
|
||||
<div class="font-medium">Product Video</div>
|
||||
</div>
|
||||
<div class="mt-3 text-xs leading-relaxed opacity-70">
|
||||
Add a video so that buyers are more interested in your product.
|
||||
<a class="text-primary font-medium"
|
||||
href="https://themeforest.net/item/midone-jquery-tailwindcss-html-admin-template/26366820"
|
||||
target="blank">
|
||||
Learn more.
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 w-full flex-1 xl:mt-0">
|
||||
<Button look="outline" class="w-40">
|
||||
<Lucide class="mr-2 size-4" icon="Plus" /> Add Video URL
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
<!-- END: Product Detail -->
|
||||
<!-- BEGIN: Product Variant -->
|
||||
<Box class="mt-8">
|
||||
<div class="rounded-lg border border-foreground/15 p-5">
|
||||
<div class="flex items-center border-b border-foreground/15 pb-5 text-base font-medium">
|
||||
<Lucide class="mr-2 size-4" icon="ChevronDown" /> Product Variant
|
||||
</div>
|
||||
<div class="mt-5 flex flex-col gap-5">
|
||||
<div class="flex flex-col items-start xl:flex-row">
|
||||
<div class="w-full">
|
||||
<div class="text-left">
|
||||
<div class="flex items-center">
|
||||
<div class="font-medium">Product Variant</div>
|
||||
</div>
|
||||
<div class="mt-3 text-xs leading-relaxed opacity-70">
|
||||
Add variants such as color, size, or more. Choose a maximum of 2 variant types.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 w-full flex-1 xl:mt-0">
|
||||
<div class="xl:text-right">
|
||||
<Button look="outline" class="w-44" variant="primary">
|
||||
<Lucide class="mr-2 size-4" icon="Plus" /> Add Variant
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
<!-- END: Product Variant -->
|
||||
<!-- BEGIN: Product Variant (Details) -->
|
||||
<Box class="mt-8">
|
||||
<div class="rounded-lg border border-foreground/15 p-5">
|
||||
<div class="flex items-center border-b border-foreground/15 pb-5 text-base font-medium">
|
||||
<Lucide class="mr-2 size-4" icon="ChevronDown" /> Product Variant (Details)
|
||||
</div>
|
||||
<div class="mt-5 flex flex-col gap-5">
|
||||
<!-- Variant 1 -->
|
||||
<div class="flex flex-col items-start xl:flex-row">
|
||||
<div class="w-full xl:mr-10 xl:w-64">
|
||||
<div class="text-left">
|
||||
<div class="flex items-center">
|
||||
<div class="font-medium">Variant 1</div>
|
||||
</div>
|
||||
<div class="mt-3 text-xs leading-relaxed opacity-70">
|
||||
Add the types of variants and options, you can add up to 5 options.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 w-full flex-1 xl:mt-0">
|
||||
<div
|
||||
class="bg-(--color)/[.03] border-(--color)/5 relative rounded-lg border py-10 pl-5 pr-5 [--color:var(--color-foreground)] xl:pr-10">
|
||||
<a class="absolute right-0 top-0 mr-4 mt-4 opacity-70" href="">
|
||||
<Lucide class="size-5" icon="X" />
|
||||
</a>
|
||||
<div class="flex flex-col gap-5">
|
||||
<div class="grid gap-5 sm:grid-cols-12">
|
||||
<div class="col-span-2">
|
||||
<Label class="block sm:mt-3 sm:text-right">Name</Label>
|
||||
</div>
|
||||
<div class="col-span-10 grid gap-5">
|
||||
<div class="grid gap-5 sm:grid-cols-12">
|
||||
<div class="col-span-10 flex">
|
||||
<Input class="rounded-e-none border-e-0" type="text" placeholder="Size" />
|
||||
<div
|
||||
class="bg-(--color)/[.03] border-(--color)/[.08] text-(--color)/70 flex w-16 items-center justify-center rounded-r-lg border [--color:var(--color-foreground)]">
|
||||
6/14
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid gap-5 sm:grid-cols-12">
|
||||
<div class="col-span-2">
|
||||
<Label class="block sm:mt-3 sm:text-right">Options</Label>
|
||||
</div>
|
||||
<div class="col-span-10 grid gap-5">
|
||||
<div v-for="(option, optIndex) in ['Small', 'Medium', 'Large']" :key="optIndex"
|
||||
class="grid gap-5 sm:grid-cols-12">
|
||||
<div class="col-span-10 flex">
|
||||
<Input class="rounded-e-none border-e-0" type="text" :placeholder="option" />
|
||||
<div
|
||||
class="bg-(--color)/[.03] border-(--color)/[.08] text-(--color)/70 flex w-16 items-center justify-center rounded-r-lg border [--color:var(--color-foreground)]">
|
||||
6/14
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-2 flex w-16 items-center gap-3 opacity-70">
|
||||
<a href="">
|
||||
<Lucide class="size-4" icon="Move" />
|
||||
</a>
|
||||
<a href="">
|
||||
<Lucide class="size-4" icon="Trash" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid gap-5 sm:grid-cols-12">
|
||||
<div class="col-span-2"></div>
|
||||
<div class="col-span-10 grid gap-5">
|
||||
<div class="grid gap-5 sm:grid-cols-12">
|
||||
<div class="col-span-10 flex">
|
||||
<Button look="outline" class="w-full border-dashed">
|
||||
<Lucide class="mr-2 size-4" icon="Plus" /> Add New Option
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Variant 2 -->
|
||||
<div class="flex flex-col items-start xl:flex-row">
|
||||
<div class="w-full xl:mr-10 xl:w-64">
|
||||
<div class="text-left">
|
||||
<div class="flex items-center">
|
||||
<div class="font-medium">Variant 2</div>
|
||||
</div>
|
||||
<div class="mt-3 text-xs leading-relaxed opacity-70">
|
||||
Add the types of variants and options, you can add up to 5 options.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 w-full flex-1 xl:mt-0">
|
||||
<div
|
||||
class="bg-(--color)/[.03] border-(--color)/5 relative rounded-lg border py-10 pl-5 pr-5 [--color:var(--color-foreground)] xl:pr-10">
|
||||
<a class="absolute right-0 top-0 mr-4 mt-4 opacity-70" href="">
|
||||
<Lucide class="size-5" icon="X" />
|
||||
</a>
|
||||
<div class="flex flex-col gap-5">
|
||||
<div class="grid gap-5 sm:grid-cols-12">
|
||||
<div class="col-span-2">
|
||||
<Label class="block sm:mt-3 sm:text-right">Name</Label>
|
||||
</div>
|
||||
<div class="col-span-10 grid gap-5">
|
||||
<div class="grid gap-5 sm:grid-cols-12">
|
||||
<div class="col-span-10 flex">
|
||||
<Input class="rounded-e-none border-e-0" type="text" placeholder="Color" />
|
||||
<div
|
||||
class="bg-(--color)/[.03] border-(--color)/[.08] text-(--color)/70 flex w-16 items-center justify-center rounded-r-lg border [--color:var(--color-foreground)]">
|
||||
6/14
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid gap-5 sm:grid-cols-12">
|
||||
<div class="col-span-2">
|
||||
<Label class="block sm:mt-3 sm:text-right">Options</Label>
|
||||
</div>
|
||||
<div class="col-span-10 grid gap-5">
|
||||
<div v-for="(color, colorIndex) in ['Black', 'White', 'Gray']" :key="colorIndex"
|
||||
class="grid gap-5 sm:grid-cols-12">
|
||||
<div class="col-span-10 flex">
|
||||
<Input class="rounded-e-none border-e-0" type="text" :placeholder="color" />
|
||||
<div
|
||||
class="bg-(--color)/[.03] border-(--color)/[.08] text-(--color)/70 flex w-16 items-center justify-center rounded-r-lg border [--color:var(--color-foreground)]">
|
||||
6/14
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-2 flex w-16 items-center gap-3 opacity-70">
|
||||
<a href="">
|
||||
<Lucide class="size-4" icon="Move" />
|
||||
</a>
|
||||
<a href="">
|
||||
<Lucide class="size-4" icon="Trash" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid gap-5 sm:grid-cols-12">
|
||||
<div class="col-span-2"></div>
|
||||
<div class="col-span-10 grid gap-5">
|
||||
<div class="grid gap-5 sm:grid-cols-12">
|
||||
<div class="col-span-10 flex">
|
||||
<Button look="outline" class="w-full border-dashed">
|
||||
<Lucide class="mr-2 size-4" icon="Plus" /> Add New Option
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="xl:ml-64 xl:pl-10">
|
||||
<Button look="outline" class="w-full">
|
||||
<Lucide class="size-4" icon="Plus" /> Add New Variant
|
||||
</Button>
|
||||
</div>
|
||||
<!-- Variant Information -->
|
||||
<div class="flex flex-col items-start xl:flex-row">
|
||||
<div class="w-full xl:mr-10 xl:w-64">
|
||||
<div class="text-left">
|
||||
<div class="flex items-center">
|
||||
<div class="font-medium">Variant Information</div>
|
||||
</div>
|
||||
<div class="mt-3 text-xs leading-relaxed opacity-70">
|
||||
Apply price and stock on all variants or based on certain variant codes.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 w-full flex-1 xl:mt-0">
|
||||
<div class="grid grid-cols-4 gap-5">
|
||||
<div>
|
||||
<div class="flex">
|
||||
<div
|
||||
class="bg-(--color)/[.03] border-(--color)/[.08] text-(--color)/70 flex w-16 items-center justify-center rounded-s-lg border [--color:var(--color-foreground)]">
|
||||
$
|
||||
</div>
|
||||
<Input class="rounded-s-none border-s-0" type="text" placeholder="Price" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Input type="text" placeholder="Stock" />
|
||||
</div>
|
||||
<div>
|
||||
<Input type="text" placeholder="Variant Code" />
|
||||
</div>
|
||||
<div>
|
||||
<Button look="outline" class="w-full" type="button" variant="primary">Apply To All</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Variant List -->
|
||||
<div class="flex flex-col items-start xl:flex-row">
|
||||
<div class="w-full xl:mr-10 xl:w-64">
|
||||
<div class="text-left">
|
||||
<div class="flex items-center">
|
||||
<div class="font-medium">Variant List</div>
|
||||
</div>
|
||||
<div class="mt-3 text-xs leading-relaxed opacity-70">
|
||||
Set the price and stock for each variant.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 w-full flex-1 xl:mt-0">
|
||||
<Table class="border border-foreground/[.08]">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead class="bg-foreground/5">Size</TableHead>
|
||||
<TableHead class="bg-foreground/5">
|
||||
<div class="flex items-center">
|
||||
Color
|
||||
<Lucide class="ml-2 size-4" icon="HelpCircle" />
|
||||
</div>
|
||||
</TableHead>
|
||||
<TableHead class="bg-foreground/5">Price</TableHead>
|
||||
<TableHead class="bg-foreground/5">
|
||||
<div class="flex items-center">
|
||||
<div :class="[
|
||||
'relative -mt-0.5 mr-2 size-4',
|
||||
'after:bg-primary after:absolute after:size-4 after:rounded-full after:border-4 after:border-white/60 after:box-border',
|
||||
'before:bg-primary/20 before:absolute before:size-4 before:rounded-full lg:before:animate-ping',
|
||||
]"></div>
|
||||
Stock
|
||||
<Lucide class="ml-2 size-4" icon="HelpCircle" />
|
||||
</div>
|
||||
</TableHead>
|
||||
<TableHead class="bg-foreground/5">Variant Code</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<template v-for="(size, sizeIndex) in ['Small', 'Medium', 'Large']" :key="sizeIndex">
|
||||
<TableRow v-for="(color, colorIndex) in ['Black', 'White', 'Gray']"
|
||||
:key="`${sizeIndex}-${colorIndex}`">
|
||||
<TableCell v-if="colorIndex === 0" class="border-r border-foreground/15" :rowspan="3">{{ size }}
|
||||
</TableCell>
|
||||
<TableCell>{{ color }}</TableCell>
|
||||
<TableCell>
|
||||
<div class="flex">
|
||||
<div
|
||||
class="bg-(--color)/[.03] border-(--color)/[.08] text-(--color)/70 flex w-16 items-center justify-center rounded-l-lg border [--color:var(--color-foreground)]">
|
||||
$
|
||||
</div>
|
||||
<Input class="rounded-s-none border-s-0" type="text" placeholder="Price" />
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Input class="min-w-[6rem]" type="text" placeholder="Stock" />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Input class="min-w-[6rem]" type="text" placeholder="Variant Code" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</template>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Wholesale -->
|
||||
<div class="flex flex-col items-start xl:flex-row">
|
||||
<div class="w-full xl:mr-10 xl:w-64">
|
||||
<div class="text-left">
|
||||
<div class="flex items-center">
|
||||
<div class="font-medium">Wholesale</div>
|
||||
</div>
|
||||
<div class="mt-3 text-xs leading-relaxed opacity-70">
|
||||
Add wholesale price for certain quantity purchases.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 w-full flex-1 xl:mt-0">
|
||||
<Table class="border border-foreground/[.08]">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead></TableHead>
|
||||
<TableHead></TableHead>
|
||||
<TableHead>Min.</TableHead>
|
||||
<TableHead>Max.</TableHead>
|
||||
<TableHead>Unit Price</TableHead>
|
||||
<TableHead></TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow v-for="(row, rowIndex) in 3" :key="rowIndex">
|
||||
<TableCell>{{ rowIndex + 1 }}.</TableCell>
|
||||
<TableCell class="whitespace-nowrap">Wholesale Price {{ rowIndex + 1 }}</TableCell>
|
||||
<TableCell>
|
||||
<Input type="text" placeholder="Min Qty" />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Input type="text" placeholder="Max Qty" />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div class="flex">
|
||||
<div
|
||||
class="bg-(--color)/[.03] border-(--color)/[.08] text-(--color)/70 flex w-16 items-center justify-center rounded-l-lg border [--color:var(--color-foreground)]">
|
||||
$
|
||||
</div>
|
||||
<Input class="rounded-s-none border-s-0" type="text" placeholder="Price" />
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<a href="">
|
||||
<Lucide class="size-4 opacity-70" icon="Trash" />
|
||||
</a>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
<Button look="outline" class="mt-4 w-full">
|
||||
<Lucide class="mr-2 size-4" icon="Plus" /> Add New Wholesale Price
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
<!-- END: Product Variant (Details) -->
|
||||
<!-- BEGIN: Product Management -->
|
||||
<Box class="mt-8">
|
||||
<div class="rounded-lg border border-foreground/15 p-5">
|
||||
<div class="flex items-center border-b border-foreground/15 pb-5 text-base font-medium">
|
||||
<Lucide class="mr-2 size-4" icon="ChevronDown" /> Product Management
|
||||
</div>
|
||||
<div class="mt-5 flex flex-col gap-5">
|
||||
<div class="flex flex-col items-start xl:flex-row">
|
||||
<div class="w-full xl:mr-10 xl:w-64">
|
||||
<div class="text-left">
|
||||
<div class="flex items-center">
|
||||
<div class="font-medium">Product Status</div>
|
||||
</div>
|
||||
<div class="mt-3 text-xs leading-relaxed opacity-70">
|
||||
If the status is active, your product can be searched for by potential buyers.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 w-full flex-1 xl:mt-0">
|
||||
<SwitchRoot>
|
||||
<SwitchControl />
|
||||
<SwitchLabel class="text-xs font-normal leading-relaxed opacity-70">Active</SwitchLabel>
|
||||
</SwitchRoot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-start xl:flex-row">
|
||||
<div class="w-full xl:mr-10 xl:w-64">
|
||||
<div class="text-left">
|
||||
<div class="flex items-center">
|
||||
<div class="font-medium">Product Stock</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 w-full flex-1 xl:mt-0">
|
||||
<Input type="text" placeholder="Input Product Stock" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-start xl:flex-row">
|
||||
<div class="w-full xl:mr-10 xl:w-64">
|
||||
<div class="text-left">
|
||||
<div class="flex items-center">
|
||||
<div class="font-medium">SKU (Stock Keeping Unit)</div>
|
||||
</div>
|
||||
<div class="mt-3 text-xs leading-relaxed opacity-70">
|
||||
Use a unique SKU code if you want to mark your product.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 w-full flex-1 xl:mt-0">
|
||||
<Input type="text" placeholder="Input SKU" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
<!-- END: Product Management -->
|
||||
<!-- BEGIN: Weight & Shipping -->
|
||||
<Box class="mt-8">
|
||||
<div class="rounded-lg border border-foreground/15 p-5">
|
||||
<div class="flex items-center border-b border-foreground/15 pb-5 text-base font-medium">
|
||||
<Lucide class="mr-2 size-4" icon="ChevronDown" /> Weight & Shipping
|
||||
</div>
|
||||
<div class="mt-5 flex flex-col gap-5">
|
||||
<div class="flex flex-col items-start xl:flex-row">
|
||||
<div class="w-full xl:mr-10 xl:w-64">
|
||||
<div class="text-left">
|
||||
<div class="flex items-center">
|
||||
<div class="font-medium">Product Weight</div>
|
||||
</div>
|
||||
<div class="mt-3 text-xs leading-relaxed opacity-70">
|
||||
Enter the weight by weighing the product after it is
|
||||
<a class="text-primary font-medium" href="">packaged</a>.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 w-full flex-1 xl:mt-0">
|
||||
<div class="grid grid-cols-4 gap-5">
|
||||
<div>
|
||||
<NativeSelect>
|
||||
<NativeSelectOption value="Gram (g)">Gram (g)</NativeSelectOption>
|
||||
<NativeSelectOption value="Kilogram (kg)">Kilogram (kg)</NativeSelectOption>
|
||||
</NativeSelect>
|
||||
</div>
|
||||
<div>
|
||||
<Input class="mt-2 sm:mt-0" id="product-weight" type="text" placeholder="Stock" />
|
||||
</div>
|
||||
</div>
|
||||
<AlertRoot look="outline" class="mt-5" variant="warning">
|
||||
<Lucide class="mr-2 size-4" icon="AlertTriangle" />
|
||||
<AlertTitle>
|
||||
Pay close attention to the weight of the product so that there is no difference
|
||||
in data with the shipping courier.
|
||||
<a class="ml-1 font-medium underline decoration-dotted underline-offset-3"
|
||||
href="https://themeforest.net/item/midone-jquery-tailwindcss-html-admin-template/26366820"
|
||||
target="blank">
|
||||
Learn More
|
||||
</a>
|
||||
</AlertTitle>
|
||||
<AlertCloseTrigger />
|
||||
</AlertRoot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-start xl:flex-row">
|
||||
<div class="w-full xl:mr-10 xl:w-64">
|
||||
<div class="text-left">
|
||||
<div class="flex items-center">
|
||||
<div class="font-medium">Product Size</div>
|
||||
</div>
|
||||
<div class="mt-3 text-xs leading-relaxed opacity-70">
|
||||
Enter the product size after packing to calculate the volume weight.
|
||||
<a class="text-primary font-medium" href="">Learn Volume Weight</a>.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 w-full flex-1 xl:mt-0">
|
||||
<div class="grid grid-cols-3 gap-5">
|
||||
<div v-for="(dim, dimIndex) in ['Width', 'Height', 'Length']" :key="dimIndex">
|
||||
<div class="flex">
|
||||
<Input class="rounded-e-none border-e-0" type="text" :placeholder="dim" />
|
||||
<div
|
||||
class="bg-(--color)/[.03] border-(--color)/[.08] text-(--color)/70 flex w-16 items-center justify-center rounded-e-lg border [--color:var(--color-foreground)]">
|
||||
cm
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-start xl:flex-row">
|
||||
<div class="w-full xl:mr-10 xl:w-64">
|
||||
<div class="text-left">
|
||||
<div class="flex items-center">
|
||||
<div class="font-medium">Shipping Insurance</div>
|
||||
</div>
|
||||
<div class="mt-3 text-xs leading-relaxed opacity-70">
|
||||
Refund product & postage for the seller and buyer in case of damage / loss
|
||||
during shipping.
|
||||
<a class="text-primary font-medium" href="">Learn More</a>.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 w-full flex-1 xl:mt-0">
|
||||
<RadioGroupRoot class="w-[30rem]" defaultValue="required">
|
||||
<RadioGroupItem value="required">
|
||||
<RadioGroupItemControl />
|
||||
<RadioGroupItemText>
|
||||
<div class="font-medium">Required</div>
|
||||
<div class="mt-1 text-xs leading-relaxed opacity-70">
|
||||
You require the buyer to activate shipping insurance
|
||||
</div>
|
||||
</RadioGroupItemText>
|
||||
</RadioGroupItem>
|
||||
<RadioGroupItem value="optional">
|
||||
<RadioGroupItemControl />
|
||||
<RadioGroupItemText>
|
||||
<div class="font-medium">Optional</div>
|
||||
<div class="mt-1 text-xs leading-relaxed opacity-70">
|
||||
You give the buyer the option to activate shipping insurance
|
||||
</div>
|
||||
</RadioGroupItemText>
|
||||
</RadioGroupItem>
|
||||
</RadioGroupRoot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-start xl:flex-row">
|
||||
<div class="w-full xl:mr-10 xl:w-64">
|
||||
<div class="text-left">
|
||||
<div class="flex items-center">
|
||||
<div class="font-medium">Shipping Service</div>
|
||||
</div>
|
||||
<div class="mt-3 text-xs leading-relaxed opacity-70">
|
||||
Configure shipping services according to your product type.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 w-full flex-1 xl:mt-0">
|
||||
<RadioGroupRoot class="w-72" defaultValue="standard">
|
||||
<RadioGroupItem value="standard">
|
||||
<RadioGroupItemControl />
|
||||
<RadioGroupItemText>Standard</RadioGroupItemText>
|
||||
</RadioGroupItem>
|
||||
<RadioGroupItem value="custom">
|
||||
<RadioGroupItemControl />
|
||||
<RadioGroupItemText>Custom</RadioGroupItemText>
|
||||
</RadioGroupItem>
|
||||
</RadioGroupRoot>
|
||||
<div class="mt-3 text-xs leading-relaxed opacity-70">
|
||||
The delivery service for this product will be the same as in the
|
||||
<a class="text-primary" href="">Shipping Settings</a>.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-start xl:flex-row">
|
||||
<div class="w-full xl:mr-10 xl:w-64">
|
||||
<div class="text-left">
|
||||
<div class="flex items-center">
|
||||
<div class="font-medium">Pre-order</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 w-full flex-1 xl:mt-0">
|
||||
<SwitchRoot>
|
||||
<SwitchControl />
|
||||
<SwitchLabel class="text-xs font-normal leading-relaxed opacity-70">
|
||||
Activate PreOrder if you need a longer shipping process.
|
||||
<a class="text-primary" href="">Learn more</a>.
|
||||
</SwitchLabel>
|
||||
</SwitchRoot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
<!-- END: Weight & Shipping -->
|
||||
<div class="mt-8 flex flex-col justify-end gap-4 md:flex-row">
|
||||
<Box class="bg-none" as-child>
|
||||
<Button variant="ghost" class="box w-full py-3 md:w-64" type="button"> Cancel </Button>
|
||||
</Box>
|
||||
<Box class="bg-none" as-child>
|
||||
<Button variant="ghost" class="box w-full py-3 md:w-64" type="button">
|
||||
Save & Add New Product
|
||||
</Button>
|
||||
</Box>
|
||||
<Button class="box w-full py-3 md:w-64" type="button" variant="primary"> Save </Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-2 hidden 2xl:block">
|
||||
<div class="sticky top-0 pt-5">
|
||||
<ul
|
||||
class="before:bg-foreground/10 relative before:absolute before:left-0 before:z-[-1] before:h-full before:w-[2px] before:content-['']">
|
||||
<li
|
||||
class="[&.active]:border-primary [&.active]:text-primary active mb-4 border-l-2 border-transparent py-1 pl-5 opacity-70 [&.active]:font-medium [&.active]:opacity-100">
|
||||
<a href="">Upload Product</a>
|
||||
</li>
|
||||
<li
|
||||
class="[&.active]:border-primary [&.active]:text-primary mb-4 border-l-2 border-transparent py-1 pl-5 opacity-70 [&.active]:font-medium [&.active]:opacity-100">
|
||||
<a href="">Product Information</a>
|
||||
</li>
|
||||
<li
|
||||
class="[&.active]:border-primary [&.active]:text-primary mb-4 border-l-2 border-transparent py-1 pl-5 opacity-70 [&.active]:font-medium [&.active]:opacity-100">
|
||||
<a href="">Product Detail</a>
|
||||
</li>
|
||||
<li
|
||||
class="[&.active]:border-primary [&.active]:text-primary mb-4 border-l-2 border-transparent py-1 pl-5 opacity-70 [&.active]:font-medium [&.active]:opacity-100">
|
||||
<a href="">Product Variant</a>
|
||||
</li>
|
||||
<li
|
||||
class="[&.active]:border-primary [&.active]:text-primary mb-4 border-l-2 border-transparent py-1 pl-5 opacity-70 [&.active]:font-medium [&.active]:opacity-100">
|
||||
<a href="">Product Variant (Details)</a>
|
||||
</li>
|
||||
<li
|
||||
class="[&.active]:border-primary [&.active]:text-primary mb-4 border-l-2 border-transparent py-1 pl-5 opacity-70 [&.active]:font-medium [&.active]:opacity-100">
|
||||
<a href="">Product Management</a>
|
||||
</li>
|
||||
<li
|
||||
class="[&.active]:border-primary [&.active]:text-primary mb-4 border-l-2 border-transparent py-1 pl-5 opacity-70 [&.active]:font-medium [&.active]:opacity-100">
|
||||
<a href="">Weight & Shipping</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div
|
||||
class="bg-(--color)/10 border-(--color)/20 box text-(--color) box relative mt-10 border p-5 [--color:var(--color-warning)] rounded-(--radius)">
|
||||
<Lucide class="text-warning/80 absolute right-0 top-0 mr-3 mt-5 size-12 stroke-[.8]" icon="Lightbulb" />
|
||||
<h2 class="text-lg font-medium">Tips</h2>
|
||||
<div class="mt-5 font-medium">Price</div>
|
||||
<div class="mt-2 text-xs leading-relaxed opacity-70">
|
||||
<div>
|
||||
The image format is .jpg .jpeg .png and a minimum size of 300 x 300 pixels (For
|
||||
optimal images use a minimum size of 700 x 700 pixels).
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
Select product photos or drag and drop up to 5 photos at once here. Include min. 3
|
||||
attractive photos to make the product more attractive to buyers.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,207 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import _ from 'lodash'
|
||||
import fakers from '@/utils/faker'
|
||||
import { AvatarRoot, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||
import { Box } from '@/components/ui/box'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Lucide } from '@/components/ui/lucide'
|
||||
import { MenuRoot, MenuTrigger, MenuPositioner, MenuContent, MenuItem } from '@/components/ui/menu'
|
||||
|
||||
import {
|
||||
PaginationContext,
|
||||
PaginationRoot,
|
||||
PaginationItem,
|
||||
PaginationPrevTrigger,
|
||||
PaginationNextTrigger,
|
||||
PaginationEllipsis,
|
||||
} from '@/components/ui/pagination'
|
||||
import {
|
||||
TooltipRoot,
|
||||
TooltipTrigger,
|
||||
TooltipPositioner,
|
||||
TooltipContent,
|
||||
} from '@/components/ui/tooltip'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col items-center sm:flex-row">
|
||||
<h2 class="mr-auto text-lg font-medium">Blog Layout</h2>
|
||||
<div class="mt-4 flex w-full sm:mt-0 sm:w-auto">
|
||||
<Button look="outline" class="mr-2 shadow-sm" variant="primary"> Add New Post </Button>
|
||||
<MenuRoot class="w-auto ml-auto sm:ml-0">
|
||||
<MenuTrigger as-child>
|
||||
<Box class="px-2" as-child>
|
||||
<Button variant="ghost" class="box flex items-center">
|
||||
<span class="flex h-5 w-5 items-center justify-center">
|
||||
<Lucide class="h-4 w-4" icon="Plus" />
|
||||
</span>
|
||||
</Button>
|
||||
</Box>
|
||||
</MenuTrigger>
|
||||
<MenuPositioner>
|
||||
<MenuContent class="w-40">
|
||||
<MenuItem value="share">
|
||||
<Lucide class="mr-2 size-4" icon="Share" /> Share Post
|
||||
</MenuItem>
|
||||
<MenuItem value="download">
|
||||
<Lucide class="mr-2 size-4" icon="Download" /> Download Post
|
||||
</MenuItem>
|
||||
</MenuContent>
|
||||
</MenuPositioner>
|
||||
</MenuRoot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 grid grid-cols-12 gap-x-6 gap-y-8">
|
||||
<!-- BEGIN: Blog Layout -->
|
||||
<template v-for="(faker, fakerKey) in _.take(fakers, 9)" :key="fakerKey">
|
||||
<Box class="col-span-12 p-0 md:col-span-6 xl:col-span-4">
|
||||
<div class="flex items-center border-b border-foreground/15 px-5 py-4">
|
||||
<AvatarRoot class="size-10 rounded-full">
|
||||
<AvatarFallback>PA</AvatarFallback>
|
||||
<AvatarImage :src="faker.photos[0]" alt="avatar" />
|
||||
</AvatarRoot>
|
||||
<div class="ml-3 mr-auto">
|
||||
<a class="font-medium" href="">
|
||||
{{ faker.users[0]?.name }}
|
||||
</a>
|
||||
<div class="mt-0.5 flex truncate text-xs opacity-70">
|
||||
<a class="text-primary inline-block truncate" href="">
|
||||
{{ faker.products[0]?.category }}
|
||||
</a>
|
||||
<span class="mx-1">•</span> {{ faker.formattedTimes[0] }}
|
||||
</div>
|
||||
</div>
|
||||
<MenuRoot class="w-auto ml-3">
|
||||
<MenuTrigger as-child>
|
||||
<a href="#">
|
||||
<Lucide class="size-5 opacity-70" icon="MoreVertical" />
|
||||
</a>
|
||||
</MenuTrigger>
|
||||
<MenuPositioner>
|
||||
<MenuContent class="w-40">
|
||||
<MenuItem value="edit">
|
||||
<Lucide class="mr-2 size-4" icon="Edit" /> Edit Post
|
||||
</MenuItem>
|
||||
<MenuItem value="delete">
|
||||
<Lucide class="mr-2 size-4" icon="Trash" /> Delete Post
|
||||
</MenuItem>
|
||||
</MenuContent>
|
||||
</MenuPositioner>
|
||||
</MenuRoot>
|
||||
</div>
|
||||
<div class="p-5">
|
||||
<div class="image-fit h-40 2xl:h-56">
|
||||
<img class="rounded-md" :src="faker.images[0]" alt="Midone - Tailwind Admin Dashboard Template" />
|
||||
</div>
|
||||
<a class="mt-5 block text-base font-medium" href="">
|
||||
{{ faker.news[0]?.title }}
|
||||
</a>
|
||||
<div class="mt-2 text-foreground/70">
|
||||
{{ faker.news[0]?.shortContent }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center border-t border-foreground/15 px-5 py-3">
|
||||
<TooltipRoot>
|
||||
<TooltipTrigger as-child>
|
||||
<a class="bg-foreground/5 mr-2 flex size-8 items-center justify-center rounded-full border border-foreground/15"
|
||||
href="">
|
||||
<Lucide class="size-3" icon="Bookmark" />
|
||||
</a>
|
||||
</TooltipTrigger>
|
||||
<TooltipPositioner>
|
||||
<TooltipContent>Bookmark</TooltipContent>
|
||||
</TooltipPositioner>
|
||||
</TooltipRoot>
|
||||
<div class="mr-2 flex">
|
||||
<AvatarRoot class="bg-background size-8 rounded-full" :title="`Uploaded at ${faker.dates[0]}`">
|
||||
<AvatarFallback>PA</AvatarFallback>
|
||||
<AvatarImage :src="faker.images[0]" alt="avatar" />
|
||||
</AvatarRoot>
|
||||
<AvatarRoot class="bg-background -ml-4 size-8 rounded-full" :title="`Uploaded at ${faker.dates[1]}`">
|
||||
<AvatarFallback>PA</AvatarFallback>
|
||||
<AvatarImage :src="faker.images[1]" alt="avatar" />
|
||||
</AvatarRoot>
|
||||
<AvatarRoot class="bg-background -ml-4 size-8 rounded-full" :title="`Uploaded at ${faker.dates[2]}`">
|
||||
<AvatarFallback>PA</AvatarFallback>
|
||||
<AvatarImage :src="faker.images[2]" alt="avatar" />
|
||||
</AvatarRoot>
|
||||
</div>
|
||||
<TooltipRoot>
|
||||
<TooltipTrigger as-child>
|
||||
<a class="bg-primary/10 border-primary/20 text-primary ml-auto flex size-8 items-center justify-center rounded-full border"
|
||||
href="">
|
||||
<Lucide class="size-3" icon="Share" />
|
||||
</a>
|
||||
</TooltipTrigger>
|
||||
<TooltipPositioner>
|
||||
<TooltipContent>Share</TooltipContent>
|
||||
</TooltipPositioner>
|
||||
</TooltipRoot>
|
||||
<TooltipRoot>
|
||||
<TooltipTrigger as-child>
|
||||
<a class="bg-primary ml-2 flex size-8 items-center justify-center rounded-full text-white" href="">
|
||||
<Lucide class="size-3" icon="Share" />
|
||||
</a>
|
||||
</TooltipTrigger>
|
||||
<TooltipPositioner>
|
||||
<TooltipContent>Download PDF</TooltipContent>
|
||||
</TooltipPositioner>
|
||||
</TooltipRoot>
|
||||
</div>
|
||||
<div class="border-t border-foreground/15 px-5 pb-5 pt-3">
|
||||
<div class="flex w-full text-xs opacity-70 sm:text-sm">
|
||||
<div class="mr-2">
|
||||
Comments:
|
||||
<span class="font-medium">{{ faker.totals[0] }}</span>
|
||||
</div>
|
||||
<div class="mr-2">
|
||||
Views: <span class="font-medium">{{ faker.totals[1] }}k</span>
|
||||
</div>
|
||||
<div class="ml-auto">
|
||||
Likes: <span class="font-medium">{{ faker.totals[2] }}k</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 flex w-full items-center">
|
||||
<AvatarRoot class="mr-3 size-10 rounded-full">
|
||||
<AvatarFallback>PA</AvatarFallback>
|
||||
<AvatarImage :src="faker.photos[0]" alt="avatar" />
|
||||
</AvatarRoot>
|
||||
<div class="relative flex-1">
|
||||
<Input class="rounded-full" type="text" placeholder="Post a comment..." />
|
||||
<Lucide class="absolute inset-y-0 right-0 my-auto mr-3 size-4 opacity-70" icon="Smile" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
</template>
|
||||
<!-- END: Blog Layout -->
|
||||
|
||||
<!-- BEGIN: Pagination -->
|
||||
<div class="col-span-12 flex flex-wrap items-center sm:flex-row sm:flex-nowrap">
|
||||
<PaginationRoot :count="5000" :pageSize="10" :siblingCount="2" class="w-full sm:mr-auto sm:w-auto">
|
||||
<PaginationPrevTrigger>
|
||||
<Lucide class="size-4" icon="ChevronLeft" />
|
||||
</PaginationPrevTrigger>
|
||||
<PaginationContext v-slot="{ pagination }">
|
||||
<template v-for="(page, index) in pagination?.pages" :key="index">
|
||||
<PaginationItem v-if="page.type === 'page'" v-bind="{ ...page }">
|
||||
{{ page.value }}
|
||||
</PaginationItem>
|
||||
<PaginationEllipsis v-else :index="index" />
|
||||
</template>
|
||||
</PaginationContext>
|
||||
<PaginationNextTrigger>
|
||||
<Lucide class="size-4" icon="ChevronRight" />
|
||||
</PaginationNextTrigger>
|
||||
</PaginationRoot>
|
||||
<NativeSelect class="box mt-3 w-20 sm:mt-0">
|
||||
<NativeSelectOption>10</NativeSelectOption>
|
||||
<NativeSelectOption>25</NativeSelectOption>
|
||||
<NativeSelectOption>35</NativeSelectOption>
|
||||
<NativeSelectOption>50</NativeSelectOption>
|
||||
</NativeSelect>
|
||||
</div>
|
||||
<!-- END: Pagination -->
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,207 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import _ from 'lodash'
|
||||
import fakers from '@/utils/faker'
|
||||
import { AvatarRoot, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||
import { Box } from '@/components/ui/box'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Lucide } from '@/components/ui/lucide'
|
||||
import { MenuRoot, MenuTrigger, MenuPositioner, MenuContent, MenuItem } from '@/components/ui/menu'
|
||||
|
||||
import {
|
||||
PaginationContext,
|
||||
PaginationRoot,
|
||||
PaginationItem,
|
||||
PaginationPrevTrigger,
|
||||
PaginationNextTrigger,
|
||||
PaginationEllipsis,
|
||||
} from '@/components/ui/pagination'
|
||||
import {
|
||||
TooltipRoot,
|
||||
TooltipTrigger,
|
||||
TooltipPositioner,
|
||||
TooltipContent,
|
||||
} from '@/components/ui/tooltip'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col items-center sm:flex-row">
|
||||
<h2 class="mr-auto text-lg font-medium">Blog Layout</h2>
|
||||
<div class="mt-4 flex w-full sm:mt-0 sm:w-auto">
|
||||
<Button look="outline" class="mr-2 shadow-sm" variant="primary"> Add New Post </Button>
|
||||
<MenuRoot class="w-auto ml-auto sm:ml-0">
|
||||
<MenuTrigger as-child>
|
||||
<Box class="px-2" as-child>
|
||||
<Button variant="ghost" class="box flex items-center">
|
||||
<span class="flex h-5 w-5 items-center justify-center">
|
||||
<Lucide class="h-4 w-4" icon="Plus" />
|
||||
</span>
|
||||
</Button>
|
||||
</Box>
|
||||
</MenuTrigger>
|
||||
<MenuPositioner>
|
||||
<MenuContent class="w-40">
|
||||
<MenuItem value="share">
|
||||
<Lucide class="mr-2 size-4" icon="Share" /> Share Post
|
||||
</MenuItem>
|
||||
<MenuItem value="download">
|
||||
<Lucide class="mr-2 size-4" icon="Download" /> Download Post
|
||||
</MenuItem>
|
||||
</MenuContent>
|
||||
</MenuPositioner>
|
||||
</MenuRoot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 grid grid-cols-12 gap-6">
|
||||
<!-- BEGIN: Blog Layout -->
|
||||
<template v-for="(faker, fakerKey) in _.take(fakers, 6)" :key="fakerKey">
|
||||
<Box class="col-span-12 p-0 md:col-span-6">
|
||||
<div
|
||||
class="image-fit h-[320px] before:absolute before:left-0 before:top-0 before:z-10 before:block before:h-full before:w-full before:rounded-t-xl before:bg-gradient-to-t before:from-black/90 before:to-black/10">
|
||||
<img class="rounded-t-xl" :src="faker.images[0]" alt="Midone - Tailwind Admin Dashboard Template" />
|
||||
<div class="absolute z-10 flex w-full items-center px-5 pt-6">
|
||||
<AvatarRoot class="size-10 rounded-full border-2 border-white">
|
||||
<AvatarFallback>PA</AvatarFallback>
|
||||
<AvatarImage :src="faker.photos[0]" alt="avatar" />
|
||||
</AvatarRoot>
|
||||
<div class="ml-3 mr-auto text-white">
|
||||
<a class="font-medium" href="">
|
||||
{{ faker.users[0]?.name }}
|
||||
</a>
|
||||
<div class="mt-0.5 text-xs">
|
||||
{{ faker.formattedTimes[0] }}
|
||||
</div>
|
||||
</div>
|
||||
<MenuRoot class="w-auto ml-3">
|
||||
<MenuTrigger as-child>
|
||||
<a class="flex size-8 items-center justify-center rounded-full bg-white/20" href="#">
|
||||
<Lucide class="size-4 text-white" icon="MoreVertical" />
|
||||
</a>
|
||||
</MenuTrigger>
|
||||
<MenuPositioner>
|
||||
<MenuContent class="w-40">
|
||||
<MenuItem value="edit">
|
||||
<Lucide class="mr-2 size-4" icon="Edit" /> Edit Post
|
||||
</MenuItem>
|
||||
<MenuItem value="delete">
|
||||
<Lucide class="mr-2 size-4" icon="Trash" /> Delete Post
|
||||
</MenuItem>
|
||||
</MenuContent>
|
||||
</MenuPositioner>
|
||||
</MenuRoot>
|
||||
</div>
|
||||
<div class="absolute bottom-0 z-10 px-5 pb-6 text-white">
|
||||
<span class="rounded-lg bg-white/20 px-2 py-1">
|
||||
{{ faker.products[0]?.category }}
|
||||
</span>
|
||||
<a class="mt-3 block text-xl font-medium" href="">
|
||||
{{ faker.news[0]?.title }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-5 opacity-70">
|
||||
{{ faker.news[0]?.shortContent }}
|
||||
</div>
|
||||
<div class="flex items-center border-t border-foreground/15 px-5 py-3">
|
||||
<TooltipRoot>
|
||||
<TooltipTrigger as-child>
|
||||
<a class="bg-foreground/5 mr-2 flex size-8 items-center justify-center rounded-full border border-foreground/15"
|
||||
href="">
|
||||
<Lucide class="size-3" icon="Bookmark" />
|
||||
</a>
|
||||
</TooltipTrigger>
|
||||
<TooltipPositioner>
|
||||
<TooltipContent>Bookmark</TooltipContent>
|
||||
</TooltipPositioner>
|
||||
</TooltipRoot>
|
||||
<div class="mr-2 flex">
|
||||
<AvatarRoot class="bg-background size-8 rounded-full" :title="`Uploaded at ${faker.dates[0]}`">
|
||||
<AvatarFallback>PA</AvatarFallback>
|
||||
<AvatarImage :src="faker.images[0]" alt="avatar" />
|
||||
</AvatarRoot>
|
||||
<AvatarRoot class="bg-background -ml-4 size-8 rounded-full" :title="`Uploaded at ${faker.dates[1]}`">
|
||||
<AvatarFallback>PA</AvatarFallback>
|
||||
<AvatarImage :src="faker.images[1]" alt="avatar" />
|
||||
</AvatarRoot>
|
||||
<AvatarRoot class="bg-background -ml-4 size-8 rounded-full" :title="`Uploaded at ${faker.dates[2]}`">
|
||||
<AvatarFallback>PA</AvatarFallback>
|
||||
<AvatarImage :src="faker.images[2]" alt="avatar" />
|
||||
</AvatarRoot>
|
||||
</div>
|
||||
<TooltipRoot>
|
||||
<TooltipTrigger as-child>
|
||||
<a class="bg-primary/10 border-primary/20 text-primary ml-auto flex size-8 items-center justify-center rounded-full border"
|
||||
href="">
|
||||
<Lucide class="size-3" icon="Share" />
|
||||
</a>
|
||||
</TooltipTrigger>
|
||||
<TooltipPositioner>
|
||||
<TooltipContent>Share</TooltipContent>
|
||||
</TooltipPositioner>
|
||||
</TooltipRoot>
|
||||
<TooltipRoot>
|
||||
<TooltipTrigger as-child>
|
||||
<a class="bg-primary ml-2 flex size-8 items-center justify-center rounded-full text-white" href="">
|
||||
<Lucide class="size-3" icon="Share" />
|
||||
</a>
|
||||
</TooltipTrigger>
|
||||
<TooltipPositioner>
|
||||
<TooltipContent>Download PDF</TooltipContent>
|
||||
</TooltipPositioner>
|
||||
</TooltipRoot>
|
||||
</div>
|
||||
<div class="border-t border-foreground/15 px-5 pb-5 pt-3">
|
||||
<div class="flex w-full text-xs opacity-70 sm:text-sm">
|
||||
<div class="mr-2">
|
||||
Comments:
|
||||
<span class="font-medium">{{ faker.totals[0] }}</span>
|
||||
</div>
|
||||
<div class="mr-2">
|
||||
Views: <span class="font-medium">{{ faker.totals[1] }}k</span>
|
||||
</div>
|
||||
<div class="ml-auto">
|
||||
Likes: <span class="font-medium">{{ faker.totals[2] }}k</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 flex w-full items-center">
|
||||
<AvatarRoot class="mr-3 size-10 rounded-full">
|
||||
<AvatarFallback>PA</AvatarFallback>
|
||||
<AvatarImage :src="faker.photos[0]" alt="avatar" />
|
||||
</AvatarRoot>
|
||||
<div class="relative flex-1">
|
||||
<Input class="rounded-full" type="text" placeholder="Post a comment..." />
|
||||
<Lucide class="absolute inset-y-0 right-0 my-auto mr-3 size-4 opacity-70" icon="Smile" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
</template>
|
||||
<!-- END: Blog Layout -->
|
||||
<!-- BEGIN: Pagination -->
|
||||
<div class="col-span-12 flex flex-wrap items-center sm:flex-row sm:flex-nowrap">
|
||||
<PaginationRoot :count="5000" :pageSize="10" :siblingCount="2" class="w-full sm:mr-auto sm:w-auto">
|
||||
<PaginationPrevTrigger>
|
||||
<Lucide class="size-4" icon="ChevronLeft" />
|
||||
</PaginationPrevTrigger>
|
||||
<PaginationContext v-slot="{ pagination }">
|
||||
<template v-for="(page, index) in pagination?.pages" :key="index">
|
||||
<PaginationItem v-if="page.type === 'page'" v-bind="{ ...page }">
|
||||
{{ page.value }}
|
||||
</PaginationItem>
|
||||
<PaginationEllipsis v-else :index="index" />
|
||||
</template>
|
||||
</PaginationContext>
|
||||
<PaginationNextTrigger>
|
||||
<Lucide class="size-4" icon="ChevronRight" />
|
||||
</PaginationNextTrigger>
|
||||
</PaginationRoot>
|
||||
<NativeSelect class="box mt-3 w-20 sm:mt-0">
|
||||
<NativeSelectOption>10</NativeSelectOption>
|
||||
<NativeSelectOption>25</NativeSelectOption>
|
||||
<NativeSelectOption>35</NativeSelectOption>
|
||||
<NativeSelectOption>50</NativeSelectOption>
|
||||
</NativeSelect>
|
||||
</div>
|
||||
<!-- END: Pagination -->
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,236 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import fakers from '@/utils/faker'
|
||||
import { AvatarRoot, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||
import { Box } from '@/components/ui/box'
|
||||
import { Lucide } from '@/components/ui/lucide'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import {
|
||||
TooltipRoot,
|
||||
TooltipTrigger,
|
||||
TooltipPositioner,
|
||||
TooltipContent,
|
||||
} from '@/components/ui/tooltip'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Box class="p-5 xl:w-3/5">
|
||||
<!-- BEGIN: Blog Layout -->
|
||||
<h2 class="text-xl font-medium sm:text-2xl">
|
||||
{{ fakers[0]?.news[0]?.title }}
|
||||
</h2>
|
||||
<div class="mt-2 text-xs opacity-70 sm:text-sm">
|
||||
{{ fakers[0]?.dates[0] }} <span class="mx-1">•</span>
|
||||
<a class="text-primary" href="">
|
||||
{{ fakers[0]?.products[0]?.category }}
|
||||
</a>
|
||||
<span class="mx-1">•</span> 7 Min read
|
||||
</div>
|
||||
<div class="mt-6">
|
||||
<div class="image-fit h-[250px]">
|
||||
<img
|
||||
class="rounded-xl"
|
||||
:src="fakers[0]?.images[0]"
|
||||
alt="Midone - Tailwind Admin Dashboard Template"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative flex items-center gap-3 pb-6 pt-16 sm:pt-6">
|
||||
<TooltipRoot>
|
||||
<TooltipTrigger as-child>
|
||||
<a
|
||||
class="bg-foreground/5 flex size-8 flex-none items-center justify-center rounded-full border border-foreground/15 sm:size-10"
|
||||
href=""
|
||||
>
|
||||
<Lucide class="size-3" icon="Bookmark" />
|
||||
</a>
|
||||
</TooltipTrigger>
|
||||
<TooltipPositioner>
|
||||
<TooltipContent>Bookmark</TooltipContent>
|
||||
</TooltipPositioner>
|
||||
</TooltipRoot>
|
||||
<div class="flex">
|
||||
<AvatarRoot
|
||||
class="bg-background size-8 sm:size-10 rounded-full"
|
||||
:title="`Uploaded at ${fakers[0]?.dates[0]}`"
|
||||
>
|
||||
<AvatarFallback>PA</AvatarFallback>
|
||||
<AvatarImage :src="fakers[0]?.photos[0]" alt="avatar" />
|
||||
</AvatarRoot>
|
||||
<AvatarRoot
|
||||
class="bg-background -ml-4 size-8 sm:size-10 rounded-full"
|
||||
:title="`Uploaded at ${fakers[0]?.dates[1]}`"
|
||||
>
|
||||
<AvatarFallback>PA</AvatarFallback>
|
||||
<AvatarImage :src="fakers[0]?.photos[1]" alt="avatar" />
|
||||
</AvatarRoot>
|
||||
<AvatarRoot
|
||||
class="bg-background -ml-4 size-8 sm:size-10 rounded-full"
|
||||
:title="`Uploaded at ${fakers[0]?.dates[2]}`"
|
||||
>
|
||||
<AvatarFallback>PA</AvatarFallback>
|
||||
<AvatarImage :src="fakers[0]?.photos[2]" alt="avatar" />
|
||||
</AvatarRoot>
|
||||
</div>
|
||||
<div
|
||||
class="absolute -mt-12 flex w-full gap-3 text-xs opacity-70 sm:relative sm:mt-0 sm:text-sm"
|
||||
>
|
||||
<div class="mr-1">
|
||||
Comments:
|
||||
<span class="font-medium">{{ fakers[0]?.totals[0] }}</span>
|
||||
</div>
|
||||
<div class="mr-1">
|
||||
Views:
|
||||
<span class="font-medium">{{ fakers[0]?.totals[1] }}k</span>
|
||||
</div>
|
||||
<div class="ml-auto">
|
||||
Likes:
|
||||
<span class="font-medium">{{ fakers[0]?.totals[2] }}k</span>
|
||||
</div>
|
||||
</div>
|
||||
<TooltipRoot>
|
||||
<TooltipTrigger as-child>
|
||||
<a
|
||||
class="bg-primary/10 border-primary/20 text-primary ml-auto flex size-8 flex-none items-center justify-center rounded-full border sm:size-10"
|
||||
href=""
|
||||
>
|
||||
<Lucide class="size-3" icon="Share" />
|
||||
</a>
|
||||
</TooltipTrigger>
|
||||
<TooltipPositioner>
|
||||
<TooltipContent>Share</TooltipContent>
|
||||
</TooltipPositioner>
|
||||
</TooltipRoot>
|
||||
<TooltipRoot>
|
||||
<TooltipTrigger as-child>
|
||||
<a
|
||||
class="bg-primary -ml-1 flex size-8 flex-none items-center justify-center rounded-full text-white sm:size-10"
|
||||
href=""
|
||||
>
|
||||
<Lucide class="size-3" icon="Download" />
|
||||
</a>
|
||||
</TooltipTrigger>
|
||||
<TooltipPositioner>
|
||||
<TooltipContent>Download PDF</TooltipContent>
|
||||
</TooltipPositioner>
|
||||
</TooltipRoot>
|
||||
</div>
|
||||
<div class="text-justify indent-8">
|
||||
<p class="mb-5">{{ fakers[1]?.news[0]?.content }}</p>
|
||||
<p class="mb-5">{{ fakers[2]?.news[0]?.content }}</p>
|
||||
<p>{{ fakers[3]?.news[0]?.content }}</p>
|
||||
</div>
|
||||
<div
|
||||
class="mt-5 flex flex-col items-center border-t border-foreground/10 pt-5 text-xs sm:flex-row sm:text-sm"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<AvatarRoot class="size-12 rounded-full">
|
||||
<AvatarFallback>PA</AvatarFallback>
|
||||
<AvatarImage :src="fakers[0]?.photos[0]" alt="avatar" />
|
||||
</AvatarRoot>
|
||||
<div class="ml-3.5 mr-auto">
|
||||
<a class="font-medium" href="">
|
||||
{{ fakers[0]?.users[0]?.name }}
|
||||
</a>
|
||||
<div class="mt-0.5 text-xs opacity-70">Senior Frontend Engineer</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 flex items-center gap-2 sm:ml-auto sm:mt-0">
|
||||
<div class="mr-1">Share this post:</div>
|
||||
<TooltipRoot>
|
||||
<TooltipTrigger as-child>
|
||||
<a
|
||||
class="flex size-8 items-center justify-center rounded-full border border-foreground/15 sm:size-10"
|
||||
href=""
|
||||
>
|
||||
<Lucide class="size-3 fill-current opacity-70" icon="Facebook" />
|
||||
</a>
|
||||
</TooltipTrigger>
|
||||
<TooltipPositioner>
|
||||
<TooltipContent>Facebook</TooltipContent>
|
||||
</TooltipPositioner>
|
||||
</TooltipRoot>
|
||||
<TooltipRoot>
|
||||
<TooltipTrigger as-child>
|
||||
<a
|
||||
class="flex size-8 items-center justify-center rounded-full border border-foreground/15 sm:size-10"
|
||||
href=""
|
||||
>
|
||||
<Lucide class="size-3 fill-current opacity-70" icon="Twitter" />
|
||||
</a>
|
||||
</TooltipTrigger>
|
||||
<TooltipPositioner>
|
||||
<TooltipContent>Twitter</TooltipContent>
|
||||
</TooltipPositioner>
|
||||
</TooltipRoot>
|
||||
<TooltipRoot>
|
||||
<TooltipTrigger as-child>
|
||||
<a
|
||||
class="flex size-8 items-center justify-center rounded-full border border-foreground/15 sm:size-10"
|
||||
href=""
|
||||
>
|
||||
<Lucide class="size-3 fill-current opacity-70" icon="Linkedin" />
|
||||
</a>
|
||||
</TooltipTrigger>
|
||||
<TooltipPositioner>
|
||||
<TooltipContent>Linked In</TooltipContent>
|
||||
</TooltipPositioner>
|
||||
</TooltipRoot>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END: Blog Layout -->
|
||||
<!-- BEGIN: Comments -->
|
||||
<div class="mt-5 border-t border-foreground/10 pt-5">
|
||||
<div class="text-base font-medium">2 Responses</div>
|
||||
<div class="h-18 relative mt-5">
|
||||
<Lucide
|
||||
class="absolute inset-y-0 left-0 my-auto ml-6 size-5 opacity-50 z-10"
|
||||
icon="MessageCircle"
|
||||
/>
|
||||
<Textarea class="min-h-24 pl-16 pt-6" placeholder="Post a comment..."></Textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 pb-8">
|
||||
<div class="pt-5">
|
||||
<div class="flex">
|
||||
<AvatarRoot class="size-10 sm:size-12 rounded-full">
|
||||
<AvatarFallback>PA</AvatarFallback>
|
||||
<AvatarImage :src="fakers[0]?.photos[1]" alt="avatar" />
|
||||
</AvatarRoot>
|
||||
<div class="ml-3.5 flex-1">
|
||||
<div class="flex items-center">
|
||||
<a class="font-medium" href="">
|
||||
{{ fakers[0]?.users[1]?.name }}
|
||||
</a>
|
||||
<a class="ml-auto text-xs opacity-70" href=""> Reply </a>
|
||||
</div>
|
||||
<div class="mt-0.5 text-xs opacity-70">
|
||||
{{ fakers[0]?.formattedTimes[1] }}
|
||||
</div>
|
||||
<div class="mt-2">{{ fakers[0]?.news[1]?.shortContent }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 border-t border-foreground/10 pt-5">
|
||||
<div class="flex">
|
||||
<AvatarRoot class="size-10 sm:size-12 rounded-full">
|
||||
<AvatarFallback>PA</AvatarFallback>
|
||||
<AvatarImage :src="fakers[0]?.photos[2]" alt="avatar" />
|
||||
</AvatarRoot>
|
||||
<div class="ml-3.5 flex-1">
|
||||
<div class="flex items-center">
|
||||
<a class="font-medium" href="">
|
||||
{{ fakers[0]?.users[2]?.name }}
|
||||
</a>
|
||||
<a class="ml-auto text-xs opacity-70" href=""> Reply </a>
|
||||
</div>
|
||||
<div class="mt-0.5 text-xs opacity-70">
|
||||
{{ fakers[0]?.formattedTimes[2] }}
|
||||
</div>
|
||||
<div class="mt-2">{{ fakers[0]?.news[2]?.shortContent }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END: Comments -->
|
||||
</Box>
|
||||
</template>
|
||||
@@ -1,180 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import fakers from '@/utils/faker'
|
||||
import { AvatarRoot, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||
import { Box } from '@/components/ui/box'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { DialogRoot, DialogContent, DialogCloseTrigger } from '@/components/ui/dialog'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Lucide } from '@/components/ui/lucide'
|
||||
import { MenuRoot, MenuTrigger, MenuPositioner, MenuContent, MenuItem } from '@/components/ui/menu'
|
||||
|
||||
import {
|
||||
PaginationContext,
|
||||
PaginationRoot,
|
||||
PaginationItem,
|
||||
PaginationPrevTrigger,
|
||||
PaginationNextTrigger,
|
||||
PaginationEllipsis,
|
||||
} from '@/components/ui/pagination'
|
||||
import {
|
||||
Table,
|
||||
TableHeader,
|
||||
TableBody,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableCell,
|
||||
} from '@/components/ui/table'
|
||||
|
||||
const deleteConfirmationOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h2 class="text-lg font-medium">Categories</h2>
|
||||
<div class="mt-5 grid grid-cols-12 gap-6">
|
||||
<div class="col-span-12 mt-2 flex flex-wrap items-center sm:flex-nowrap">
|
||||
<Button class="mr-2" look="outline" variant="primary"> Add New Category </Button>
|
||||
<MenuRoot class="w-auto">
|
||||
<MenuTrigger as-child>
|
||||
<Box class="px-2" as-child>
|
||||
<Button variant="ghost" class="box flex items-center">
|
||||
<span class="flex h-5 w-5 items-center justify-center">
|
||||
<Lucide class="h-4 w-4" icon="Plus" />
|
||||
</span>
|
||||
</Button>
|
||||
</Box>
|
||||
</MenuTrigger>
|
||||
<MenuPositioner>
|
||||
<MenuContent class="w-40">
|
||||
<MenuItem value="print">
|
||||
<Lucide class="mr-2 h-4 w-4" icon="Printer" /> Print
|
||||
</MenuItem>
|
||||
<MenuItem value="excel">
|
||||
<Lucide class="mr-2 h-4 w-4" icon="FileText" /> Export to Excel
|
||||
</MenuItem>
|
||||
<MenuItem value="pdf">
|
||||
<Lucide class="mr-2 h-4 w-4" icon="FileText" /> Export to PDF
|
||||
</MenuItem>
|
||||
</MenuContent>
|
||||
</MenuPositioner>
|
||||
</MenuRoot>
|
||||
<div class="mx-auto hidden opacity-70 md:block">Showing 1 to 10 of 150 entries</div>
|
||||
<div class="mt-3 w-full sm:ml-auto sm:mt-0 sm:w-auto md:ml-0">
|
||||
<div class="relative w-56">
|
||||
<Input class="w-56 pr-10" type="text" placeholder="Search..." />
|
||||
<Lucide class="absolute inset-y-0 right-0 my-auto mr-3 h-4 w-4" icon="Search" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- BEGIN: Data List -->
|
||||
<div class="col-span-12 overflow-auto lg:overflow-visible">
|
||||
<Table class="-mt-2" variant="boxed">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead> IMAGES </TableHead>
|
||||
<TableHead> CATEGORY NAME </TableHead>
|
||||
<TableHead> SLUG </TableHead>
|
||||
<TableHead class="text-center"> STATUS </TableHead>
|
||||
<TableHead class="text-center"> ACTIONS </TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow v-for="(faker, index) in fakers.slice(0, 9)" :key="index">
|
||||
<TableCell>
|
||||
<div class="flex">
|
||||
<AvatarRoot class="size-11 bg-background rounded-full">
|
||||
<AvatarFallback>IM</AvatarFallback>
|
||||
<AvatarImage :src="faker['images'][0]" :alt="'Uploaded at ' + faker['dates'][0]" />
|
||||
</AvatarRoot>
|
||||
<AvatarRoot class="size-11 bg-background rounded-full -ms-5">
|
||||
<AvatarFallback>IM</AvatarFallback>
|
||||
<AvatarImage :src="faker['images'][1]" :alt="'Uploaded at ' + faker['dates'][1]" />
|
||||
</AvatarRoot>
|
||||
<AvatarRoot class="size-11 bg-background rounded-full -ms-5">
|
||||
<AvatarFallback>IM</AvatarFallback>
|
||||
<AvatarImage :src="faker['images'][2]" :alt="'Uploaded at ' + faker['dates'][2]" />
|
||||
</AvatarRoot>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<a class="whitespace-nowrap font-medium" href="">
|
||||
{{ faker['categories'][0]!['name'] }}
|
||||
</a>
|
||||
<div class="mt-0.5 whitespace-nowrap text-xs opacity-70">
|
||||
Tags: {{ faker['categories'][0]!['tags'] }}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<a class="mr-3 flex items-center opacity-70" href="#">
|
||||
<Lucide class="mr-2 h-4 w-4" icon="ExternalLink" />
|
||||
/categories/{{ faker['categories'][0]!['slug'] }}
|
||||
</a>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div class="flex items-center justify-center"
|
||||
:class="faker['trueFalse'][0] ? 'text-success' : 'text-danger'">
|
||||
<Lucide class="mr-2 h-4 w-4" icon="CheckSquare" />
|
||||
{{ faker['trueFalse'][0] ? 'Active' : 'Inactive' }}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div class="flex items-center justify-center">
|
||||
<a class="mr-3 flex items-center" href="">
|
||||
<Lucide class="mr-1 h-4 w-4" icon="CheckSquare" />
|
||||
Edit
|
||||
</a>
|
||||
<a class="text-danger flex items-center" href="#" @click.prevent="deleteConfirmationOpen = true">
|
||||
<Lucide class="mr-1 h-4 w-4" icon="Trash" />
|
||||
Delete
|
||||
</a>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
<!-- END: Data List -->
|
||||
<!-- BEGIN: Pagination -->
|
||||
<div class="col-span-12 flex flex-wrap items-center sm:flex-row sm:flex-nowrap">
|
||||
<PaginationRoot :count="150" :pageSize="10" :siblingCount="1" class="w-full sm:mr-auto sm:w-auto">
|
||||
<PaginationPrevTrigger>Previous</PaginationPrevTrigger>
|
||||
<PaginationContext v-slot="{ pagination }">
|
||||
<template v-for="(page, index) in pagination?.pages" :key="index">
|
||||
<PaginationItem v-if="page.type === 'page'" v-bind="{ ...page }">
|
||||
{{ page.value }}
|
||||
</PaginationItem>
|
||||
<PaginationEllipsis v-else :index="index" />
|
||||
</template>
|
||||
</PaginationContext>
|
||||
<PaginationNextTrigger>Next</PaginationNextTrigger>
|
||||
</PaginationRoot>
|
||||
<NativeSelect class="mt-3 w-20 sm:mt-0">
|
||||
<NativeSelectOption value="10">10</NativeSelectOption>
|
||||
<NativeSelectOption value="25">25</NativeSelectOption>
|
||||
<NativeSelectOption value="35">35</NativeSelectOption>
|
||||
<NativeSelectOption value="50">50</NativeSelectOption>
|
||||
</NativeSelect>
|
||||
</div>
|
||||
<!-- END: Pagination -->
|
||||
</div>
|
||||
<!-- BEGIN: Delete Confirmation Modal -->
|
||||
<DialogRoot :open="deleteConfirmationOpen" @openChange="(details) => (deleteConfirmationOpen = details.open)">
|
||||
<DialogContent>
|
||||
<div class="p-5 text-center">
|
||||
<Lucide class="text-danger mx-auto mt-3 size-16 stroke-1" icon="CircleX" />
|
||||
<div class="mt-5 text-2xl font-medium">Are you sure?</div>
|
||||
<div class="mt-2 opacity-70">
|
||||
Do you really want to delete these records? <br />
|
||||
This process cannot be undone.
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-5 pb-8 text-center">
|
||||
<DialogCloseTrigger class="mr-2 w-24"> Cancel </DialogCloseTrigger>
|
||||
<Button class="w-24" type="button" variant="danger" look="outline"> Delete </Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</DialogRoot>
|
||||
<!-- END: Delete Confirmation Modal -->
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,141 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import * as combobox from '@zag-js/combobox'
|
||||
import { Box } from '@/components/ui/box'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
ComboboxRoot,
|
||||
ComboboxControl,
|
||||
ComboboxTrigger,
|
||||
ComboboxContent,
|
||||
ComboboxInput,
|
||||
ComboboxItemGroup,
|
||||
ComboboxItemGroupLabel,
|
||||
ComboboxItem,
|
||||
ComboboxItemText,
|
||||
} from '@/components/ui/combobox'
|
||||
import { Field, FieldLabel } from '@/components/ui/field'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { SwitchRoot, SwitchControl } from '@/components/ui/switch'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
|
||||
const categoryData = [
|
||||
{ label: 'Sport & Outdoor', code: 'sport' },
|
||||
{ label: 'PC & Laptop', code: 'pc' },
|
||||
{ label: 'Smartphone & Tablet', code: 'smartphone' },
|
||||
{ label: 'Photography', code: 'photography' },
|
||||
]
|
||||
|
||||
const categoryValue = ref(['Sport & Outdoor', 'Smartphone & Tablet'])
|
||||
const categoryOptions = ref(categoryData)
|
||||
|
||||
const categoryCollection = computed(() =>
|
||||
combobox.collection({
|
||||
items: categoryOptions.value,
|
||||
itemToValue: (item) => item.label,
|
||||
}),
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center">
|
||||
<h2 class="mr-auto text-lg font-medium">Form Layout</h2>
|
||||
</div>
|
||||
<div class="mt-5 grid grid-cols-12 gap-6">
|
||||
<div class="col-span-12 lg:col-span-6">
|
||||
<!-- BEGIN: Form Layout -->
|
||||
<Box>
|
||||
<Field>
|
||||
<FieldLabel for="crud-form-1">Product Name</FieldLabel>
|
||||
<Input class="w-full" id="crud-form-1" type="text" placeholder="Product name" />
|
||||
</Field>
|
||||
<Field class="mt-3">
|
||||
<FieldLabel>Category</FieldLabel>
|
||||
<ComboboxRoot :collection="categoryCollection" :value="categoryValue"
|
||||
@value-change="(details) => (categoryValue = details.value)" @input-value-change="
|
||||
({ inputValue }) => {
|
||||
const filtered = categoryData.filter((item) =>
|
||||
item.label.toLowerCase().includes(inputValue.toLowerCase()),
|
||||
)
|
||||
categoryOptions = filtered.length > 0 ? filtered : categoryData
|
||||
}
|
||||
" class="w-full" multiple>
|
||||
<ComboboxControl>
|
||||
<ComboboxTrigger />
|
||||
</ComboboxControl>
|
||||
<ComboboxContent>
|
||||
<ComboboxInput placeholder="Search category..." />
|
||||
<ComboboxItemGroup>
|
||||
<ComboboxItemGroupLabel>Categories</ComboboxItemGroupLabel>
|
||||
<ComboboxItem v-for="item in categoryCollection.items" :key="item.code" :item="item">
|
||||
<ComboboxItemText>{{ item.label }}</ComboboxItemText>
|
||||
</ComboboxItem>
|
||||
</ComboboxItemGroup>
|
||||
</ComboboxContent>
|
||||
</ComboboxRoot>
|
||||
</Field>
|
||||
<Field class="mt-3">
|
||||
<FieldLabel for="crud-form-3">Quantity</FieldLabel>
|
||||
<div class="flex">
|
||||
<Input class="rounded-e-none border-e-0" type="text" placeholder="Quantity" />
|
||||
<div
|
||||
class="bg-(--color)/3 border-(--color)/8 text-(--color)/70 flex w-16 items-center justify-center rounded-e-lg border [--color:var(--color-foreground)]">
|
||||
pcs
|
||||
</div>
|
||||
</div>
|
||||
</Field>
|
||||
<Field class="mt-3">
|
||||
<FieldLabel for="crud-form-4">Weight</FieldLabel>
|
||||
<div class="flex">
|
||||
<Input class="rounded-e-none border-e-0" type="text" placeholder="Weight" />
|
||||
<div
|
||||
class="bg-(--color)/3 border-(--color)/8 text-(--color)/70 flex w-16 items-center justify-center rounded-e-lg border [--color:var(--color-foreground)]">
|
||||
grams
|
||||
</div>
|
||||
</div>
|
||||
</Field>
|
||||
<Field class="mt-3">
|
||||
<FieldLabel>Price</FieldLabel>
|
||||
<div class="grid-cols-3 gap-2 sm:grid">
|
||||
<div class="flex">
|
||||
<div
|
||||
class="bg-(--color)/3 border-(--color)/8 text-(--color)/70 flex items-center justify-center rounded-s-lg border px-3 [--color:var(--color-foreground)]">
|
||||
Unit
|
||||
</div>
|
||||
<Input class="rounded-s-none border-s-0" type="text" placeholder="Unit" />
|
||||
</div>
|
||||
<div class="mt-2 flex sm:mt-0">
|
||||
<div
|
||||
class="bg-(--color)/3 border-(--color)/8 text-(--color)/70 flex items-center justify-center rounded-s-lg border px-3 [--color:var(--color-foreground)]">
|
||||
Wholesale
|
||||
</div>
|
||||
<Input class="rounded-s-none border-s-0" type="text" placeholder="Wholesale" />
|
||||
</div>
|
||||
<div class="mt-2 flex sm:mt-0">
|
||||
<div
|
||||
class="bg-(--color)/3 border-(--color)/8 text-(--color)/70 flex items-center justify-center rounded-s-lg border px-3 [--color:var(--color-foreground)]">
|
||||
Bulk
|
||||
</div>
|
||||
<Input class="rounded-s-none border-s-0" type="text" placeholder="Bulk" />
|
||||
</div>
|
||||
</div>
|
||||
</Field>
|
||||
<Field class="mt-3">
|
||||
<FieldLabel>Active Status</FieldLabel>
|
||||
<SwitchRoot>
|
||||
<SwitchControl />
|
||||
</SwitchRoot>
|
||||
</Field>
|
||||
<Field class="mt-3">
|
||||
<FieldLabel for="crud-form-5">Description</FieldLabel>
|
||||
<Textarea id="crud-form-5" placeholder="Product description">Content of the editor.</Textarea>
|
||||
</Field>
|
||||
<div class="mt-5 text-right">
|
||||
<Button class="mr-1 w-24" type="button" look="outline"> Cancel </Button>
|
||||
<Button class="w-24" type="button" variant="primary"> Save </Button>
|
||||
</div>
|
||||
</Box>
|
||||
<!-- END: Form Layout -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,33 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import errorIllustrationUrl from '@/assets/images/error-illustration.svg'
|
||||
import { Button } from '@/components/ui/button'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="[
|
||||
'relative py-2',
|
||||
'before:bg-primary dark:before:bg-foreground/[.01] before:fixed before:inset-0 before:bg-noise',
|
||||
'after:bg-accent after:bg-contain after:fixed after:inset-0 after:blur-xl dark:after:opacity-20',
|
||||
]">
|
||||
<div class="container relative z-10 mx-auto">
|
||||
<!-- BEGIN: Error Page -->
|
||||
<div class="flex h-screen flex-col items-center justify-center text-center lg:flex-row lg:text-left">
|
||||
<div class="lg:mr-20">
|
||||
<img class="h-48 w-[450px] lg:h-auto" :src="errorIllustrationUrl"
|
||||
alt="Midone - Tailwind Admin Dashboard Template" />
|
||||
</div>
|
||||
<div class="mt-10 text-white lg:mt-0">
|
||||
<div class="text-9xl [text-shadow:_7px_7px_--alpha(var(--color-white)_/_20%)]">404</div>
|
||||
<div class="mt-8 text-xl font-medium lg:text-2xl">Oops. This page has gone missing.</div>
|
||||
<div class="mt-3 text-base opacity-70">
|
||||
You may have mistyped the address or the page may have moved.
|
||||
</div>
|
||||
<Button class="box mt-10 border border-white bg-transparent px-7 py-6 text-white" variant="ghost">
|
||||
Back to Home
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END: Error Page -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,271 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { Box } from '@/components/ui/box'
|
||||
import {
|
||||
AccordionRoot,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
AccordionContent,
|
||||
} from '@/components/ui/accordion'
|
||||
import { Lucide } from '@/components/ui/lucide'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center">
|
||||
<h2 class="mr-auto text-lg font-medium">FAQ Layout</h2>
|
||||
</div>
|
||||
<div class="mt-5 grid grid-cols-12 gap-x-6 gap-y-8">
|
||||
<!-- BEGIN: FAQ Menu -->
|
||||
<Box
|
||||
class="active [&.active]:before:bg-(--color)/5 [&.active]:before:border-(--color)/20 [&.active]:after:bg-(--color)/5 [&.active]:after:border-(--color)/20 col-span-12 py-10 [--color:var(--color-primary)] lg:col-span-4"
|
||||
href=""
|
||||
as="a"
|
||||
>
|
||||
<Lucide class="text-primary mx-auto block size-12 stroke-1" icon="Shield" />
|
||||
<div class="mt-3 text-center text-base font-medium">Single Application</div>
|
||||
<div class="mx-auto mt-2 w-3/4 text-center opacity-70">
|
||||
Lorem Ipsum is simply dummy text of the printing
|
||||
</div>
|
||||
</Box>
|
||||
<Box
|
||||
class="[&.active]:before:bg-(--color)/5 [&.active]:before:border-(--color)/20 [&.active]:after:bg-(--color)/5 [&.active]:after:border-(--color)/20 col-span-12 py-10 [--color:var(--color-primary)] lg:col-span-4"
|
||||
href=""
|
||||
as="a"
|
||||
>
|
||||
<Lucide class="text-primary mx-auto block size-12 stroke-1" icon="Send" />
|
||||
<div class="mt-3 text-center text-base font-medium">Multi Application</div>
|
||||
<div class="mx-auto mt-2 w-3/4 text-center opacity-70">
|
||||
Lorem Ipsum is simply dummy text of the printing
|
||||
</div>
|
||||
</Box>
|
||||
<Box
|
||||
class="[&.active]:before:bg-(--color)/5 [&.active]:before:border-(--color)/20 [&.active]:after:bg-(--color)/5 [&.active]:after:border-(--color)/20 col-span-12 py-10 [--color:var(--color-primary)] lg:col-span-4"
|
||||
href=""
|
||||
as="a"
|
||||
>
|
||||
<Lucide class="text-primary mx-auto block size-12 stroke-1" icon="TrendingUp" />
|
||||
<div class="mt-3 text-center text-base font-medium">Custom License</div>
|
||||
<div class="mx-auto mt-2 w-3/4 text-center opacity-70">
|
||||
Lorem Ipsum is simply dummy text of the printing
|
||||
</div>
|
||||
</Box>
|
||||
<!-- END: FAQ Menu -->
|
||||
<!-- BEGIN: FAQ Content -->
|
||||
<div class="col-span-12 lg:col-span-6">
|
||||
<Box class="p-0">
|
||||
<div class="flex items-center border-b border-foreground/15 p-5">
|
||||
<h2 class="mr-auto text-base font-medium">Working with Dropplets</h2>
|
||||
</div>
|
||||
<div class="p-5">
|
||||
<AccordionRoot class="border-none" :default-value="['faq-1-1']">
|
||||
<AccordionItem value="faq-1-1">
|
||||
<AccordionTrigger>
|
||||
OpenSSL Essentials: Working with SSL Certificates, Private Keys
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="faq-1-2">
|
||||
<AccordionTrigger>
|
||||
Understanding IP Addresses, Subnets, and CIDR Notation
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="faq-1-3">
|
||||
<AccordionTrigger>How To Troubleshoot Common HTTP Error Codes</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="faq-1-4">
|
||||
<AccordionTrigger>An Introduction to Securing your Linux VPS</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</AccordionRoot>
|
||||
</div>
|
||||
</Box>
|
||||
<Box class="mt-8 p-0">
|
||||
<div class="flex items-center border-b border-foreground/15 p-5">
|
||||
<h2 class="mr-auto text-base font-medium">Working with Dropplets</h2>
|
||||
</div>
|
||||
<div class="p-5">
|
||||
<AccordionRoot class="border-none">
|
||||
<AccordionItem value="faq-2-1">
|
||||
<AccordionTrigger>
|
||||
OpenSSL Essentials: Working with SSL Certificates, Private Keys
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="faq-2-2">
|
||||
<AccordionTrigger>
|
||||
Understanding IP Addresses, Subnets, and CIDR Notation
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="faq-2-3">
|
||||
<AccordionTrigger>How To Troubleshoot Common HTTP Error Codes</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="faq-2-4">
|
||||
<AccordionTrigger>An Introduction to Securing your Linux VPS</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</AccordionRoot>
|
||||
</div>
|
||||
</Box>
|
||||
</div>
|
||||
<div class="col-span-12 lg:col-span-6">
|
||||
<Box class="p-0">
|
||||
<div class="flex items-center border-b border-foreground/15 p-5">
|
||||
<h2 class="mr-auto text-base font-medium">Working with Dropplets</h2>
|
||||
</div>
|
||||
<div class="p-5">
|
||||
<AccordionRoot class="border-none">
|
||||
<AccordionItem value="faq-3-1">
|
||||
<AccordionTrigger>
|
||||
OpenSSL Essentials: Working with SSL Certificates, Private Keys
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="faq-3-2">
|
||||
<AccordionTrigger>
|
||||
Understanding IP Addresses, Subnets, and CIDR Notation
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="faq-3-3">
|
||||
<AccordionTrigger>How To Troubleshoot Common HTTP Error Codes</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="faq-3-4">
|
||||
<AccordionTrigger>An Introduction to Securing your Linux VPS</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</AccordionRoot>
|
||||
</div>
|
||||
</Box>
|
||||
<Box class="mt-8 p-0">
|
||||
<div class="flex items-center border-b border-foreground/15 p-5">
|
||||
<h2 class="mr-auto text-base font-medium">Working with Dropplets</h2>
|
||||
</div>
|
||||
<div class="p-5">
|
||||
<AccordionRoot class="border-none">
|
||||
<AccordionItem value="faq-4-1">
|
||||
<AccordionTrigger>
|
||||
OpenSSL Essentials: Working with SSL Certificates, Private Keys
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="faq-4-2">
|
||||
<AccordionTrigger>
|
||||
Understanding IP Addresses, Subnets, and CIDR Notation
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="faq-4-3">
|
||||
<AccordionTrigger>How To Troubleshoot Common HTTP Error Codes</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="faq-4-4">
|
||||
<AccordionTrigger>An Introduction to Securing your Linux VPS</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</AccordionRoot>
|
||||
</div>
|
||||
</Box>
|
||||
</div>
|
||||
<!-- END: FAQ Content -->
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,232 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { Box } from '@/components/ui/box'
|
||||
import {
|
||||
AccordionRoot,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
AccordionContent,
|
||||
} from '@/components/ui/accordion'
|
||||
import { Lucide } from '@/components/ui/lucide'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center">
|
||||
<h2 class="mr-auto text-lg font-medium">FAQ Layout</h2>
|
||||
</div>
|
||||
<div class="grid grid-cols-12 gap-6">
|
||||
<!-- BEGIN: FAQ Menu -->
|
||||
<div class="col-span-12 lg:col-span-4 xl:col-span-3">
|
||||
<Box class="mt-5 p-0">
|
||||
<div class="flex flex-col gap-5 p-5">
|
||||
<a
|
||||
class="[&.active]:text-primary active flex items-center [&.active]:font-medium"
|
||||
href=""
|
||||
>
|
||||
<Lucide class="mr-2 size-4" icon="Activity" /> About Our Products
|
||||
</a>
|
||||
<a class="[&.active]:text-primary flex items-center [&.active]:font-medium" href="">
|
||||
<Lucide class="mr-2 size-4" icon="Box" /> Related License
|
||||
</a>
|
||||
<a class="[&.active]:text-primary flex items-center [&.active]:font-medium" href="">
|
||||
<Lucide class="mr-2 size-4" icon="Lock" /> Single Application License
|
||||
</a>
|
||||
<a class="[&.active]:text-primary flex items-center [&.active]:font-medium" href="">
|
||||
<Lucide class="mr-2 size-4" icon="Settings" /> Multi Application License
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex flex-col gap-5 border-t border-foreground/15 p-5">
|
||||
<a class="[&.active]:text-primary flex items-center [&.active]:font-medium" href="">
|
||||
<Lucide class="mr-2 size-4" icon="Activity" /> Term of Use
|
||||
</a>
|
||||
<a class="[&.active]:text-primary flex items-center [&.active]:font-medium" href="">
|
||||
<Lucide class="mr-2 size-4" icon="Box" /> Author Fees
|
||||
</a>
|
||||
<a class="[&.active]:text-primary flex items-center [&.active]:font-medium" href="">
|
||||
<Lucide class="mr-2 size-4" icon="Lock" /> Product Review
|
||||
</a>
|
||||
<a class="[&.active]:text-primary flex items-center [&.active]:font-medium" href="">
|
||||
<Lucide class="mr-2 size-4" icon="Settings" /> Privacy Policy
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex flex-col gap-5 border-t border-foreground/15 p-5">
|
||||
<a class="[&.active]:text-primary flex items-center [&.active]:font-medium" href="">
|
||||
<Lucide class="mr-2 size-4" icon="Activity" /> About Our Products
|
||||
</a>
|
||||
<a class="[&.active]:text-primary flex items-center [&.active]:font-medium" href="">
|
||||
<Lucide class="mr-2 size-4" icon="Box" /> Related License
|
||||
</a>
|
||||
<a class="[&.active]:text-primary flex items-center [&.active]:font-medium" href="">
|
||||
<Lucide class="mr-2 size-4" icon="Lock" /> Single Application License
|
||||
</a>
|
||||
<a class="[&.active]:text-primary flex items-center [&.active]:font-medium" href="">
|
||||
<Lucide class="mr-2 size-4" icon="Settings" /> Multi Application License
|
||||
</a>
|
||||
</div>
|
||||
</Box>
|
||||
</div>
|
||||
<!-- END: FAQ Menu -->
|
||||
<!-- BEGIN: FAQ Content -->
|
||||
<div class="col-span-12 lg:col-span-8 xl:col-span-9">
|
||||
<Box class="p-0 lg:mt-5">
|
||||
<div class="flex items-center border-b border-foreground/15 p-5">
|
||||
<h2 class="mr-auto text-base font-medium">About Our Products</h2>
|
||||
</div>
|
||||
<div class="p-5">
|
||||
<AccordionRoot class="border-none" :default-value="['faq-1-1']">
|
||||
<AccordionItem value="faq-1-1">
|
||||
<AccordionTrigger>
|
||||
OpenSSL Essentials: Working with SSL Certificates, Private Keys
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="faq-1-2">
|
||||
<AccordionTrigger>
|
||||
Understanding IP Addresses, Subnets, and CIDR Notation
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="faq-1-3">
|
||||
<AccordionTrigger>How To Troubleshoot Common HTTP Error Codes</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="faq-1-4">
|
||||
<AccordionTrigger>An Introduction to Securing your Linux VPS</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</AccordionRoot>
|
||||
</div>
|
||||
</Box>
|
||||
<Box class="mt-8 p-0">
|
||||
<div class="flex items-center border-b border-foreground/15 p-5">
|
||||
<h2 class="mr-auto text-base font-medium">Related License</h2>
|
||||
</div>
|
||||
<div class="p-5">
|
||||
<AccordionRoot class="border-none">
|
||||
<AccordionItem value="faq-2-1">
|
||||
<AccordionTrigger>
|
||||
OpenSSL Essentials: Working with SSL Certificates, Private Keys
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="faq-2-2">
|
||||
<AccordionTrigger>
|
||||
Understanding IP Addresses, Subnets, and CIDR Notation
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="faq-2-3">
|
||||
<AccordionTrigger>How To Troubleshoot Common HTTP Error Codes</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="faq-2-4">
|
||||
<AccordionTrigger>An Introduction to Securing your Linux VPS</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</AccordionRoot>
|
||||
</div>
|
||||
</Box>
|
||||
<Box class="mt-8 p-0">
|
||||
<div class="flex items-center border-b border-foreground/15 p-5">
|
||||
<h2 class="mr-auto text-base font-medium">Single Application License</h2>
|
||||
</div>
|
||||
<div class="p-5">
|
||||
<AccordionRoot class="border-none">
|
||||
<AccordionItem value="faq-3-1">
|
||||
<AccordionTrigger>
|
||||
OpenSSL Essentials: Working with SSL Certificates, Private Keys
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="faq-3-2">
|
||||
<AccordionTrigger>
|
||||
Understanding IP Addresses, Subnets, and CIDR Notation
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="faq-3-3">
|
||||
<AccordionTrigger>How To Troubleshoot Common HTTP Error Codes</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="faq-3-4">
|
||||
<AccordionTrigger>An Introduction to Securing your Linux VPS</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</AccordionRoot>
|
||||
</div>
|
||||
</Box>
|
||||
</div>
|
||||
<!-- END: FAQ Content -->
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,271 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { Box } from '@/components/ui/box'
|
||||
import {
|
||||
AccordionRoot,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
AccordionContent,
|
||||
} from '@/components/ui/accordion'
|
||||
import { Lucide } from '@/components/ui/lucide'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center">
|
||||
<h2 class="mr-auto text-lg font-medium">FAQ Layout</h2>
|
||||
</div>
|
||||
<div class="grid grid-cols-12 gap-6">
|
||||
<!-- BEGIN: FAQ Menu -->
|
||||
<div class="col-span-12 lg:col-span-4 xl:col-span-3">
|
||||
<Box class="mt-5 p-0">
|
||||
<div class="flex flex-col gap-1 px-4 pb-3 pt-5">
|
||||
<a
|
||||
class="[&.active]:bg-primary/5 active [&.active]:border-primary/10 [&.active]:text-primary flex items-center rounded-lg border border-transparent px-4 py-2 [&.active]:font-medium"
|
||||
href=""
|
||||
>
|
||||
<Lucide class="mr-2 size-4" icon="Activity" />
|
||||
<div class="flex-1 truncate">About Our Products</div>
|
||||
</a>
|
||||
<a
|
||||
class="[&.active]:bg-primary/5 [&.active]:border-primary/10 [&.active]:text-primary flex items-center rounded-lg border border-transparent px-4 py-2 [&.active]:font-medium"
|
||||
href=""
|
||||
>
|
||||
<Lucide class="mr-2 size-4" icon="Box" />
|
||||
<div class="flex-1 truncate">Related License</div>
|
||||
</a>
|
||||
<a
|
||||
class="[&.active]:bg-primary/5 [&.active]:border-primary/10 [&.active]:text-primary flex items-center rounded-lg border border-transparent px-4 py-2 [&.active]:font-medium"
|
||||
href=""
|
||||
>
|
||||
<Lucide class="mr-2 size-4" icon="Lock" />
|
||||
<div class="flex-1 truncate">Single Application License</div>
|
||||
</a>
|
||||
<a
|
||||
class="[&.active]:bg-primary/5 [&.active]:border-primary/10 [&.active]:text-primary flex items-center rounded-lg border border-transparent px-4 py-2 [&.active]:font-medium"
|
||||
href=""
|
||||
>
|
||||
<Lucide class="mr-2 size-4" icon="Settings" />
|
||||
<div class="flex-1 truncate">Multi Application License</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1 border-t border-foreground/15 px-4 py-3">
|
||||
<a class="flex items-center px-4 py-2" href="">
|
||||
<Lucide class="mr-2 size-4" icon="Activity" />
|
||||
<div class="flex-1 truncate">Term of Use</div>
|
||||
</a>
|
||||
<a
|
||||
class="[&.active]:bg-primary/5 [&.active]:border-primary/10 [&.active]:text-primary flex items-center rounded-lg border border-transparent px-4 py-2 [&.active]:font-medium"
|
||||
href=""
|
||||
>
|
||||
<Lucide class="mr-2 size-4" icon="Box" />
|
||||
<div class="flex-1 truncate">Author Fees</div>
|
||||
</a>
|
||||
<a
|
||||
class="[&.active]:bg-primary/5 [&.active]:border-primary/10 [&.active]:text-primary flex items-center rounded-lg border border-transparent px-4 py-2 [&.active]:font-medium"
|
||||
href=""
|
||||
>
|
||||
<Lucide class="mr-2 size-4" icon="Lock" />
|
||||
<div class="flex-1 truncate">Product Review</div>
|
||||
</a>
|
||||
<a
|
||||
class="[&.active]:bg-primary/5 [&.active]:border-primary/10 [&.active]:text-primary flex items-center rounded-lg border border-transparent px-4 py-2 [&.active]:font-medium"
|
||||
href=""
|
||||
>
|
||||
<Lucide class="mr-2 size-4" icon="Settings" />
|
||||
<div class="flex-1 truncate">Privacy Policy</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1 border-t border-foreground/15 px-4 pb-5 pt-3">
|
||||
<a class="flex items-center px-4 py-2" href="">
|
||||
<Lucide class="mr-2 size-4" icon="Activity" />
|
||||
<div class="flex-1 truncate">About Our Products</div>
|
||||
</a>
|
||||
<a
|
||||
class="[&.active]:bg-primary/5 [&.active]:border-primary/10 [&.active]:text-primary flex items-center rounded-lg border border-transparent px-4 py-2 [&.active]:font-medium"
|
||||
href=""
|
||||
>
|
||||
<Lucide class="mr-2 size-4" icon="Box" />
|
||||
<div class="flex-1 truncate">Related License</div>
|
||||
</a>
|
||||
<a
|
||||
class="[&.active]:bg-primary/5 [&.active]:border-primary/10 [&.active]:text-primary flex items-center rounded-lg border border-transparent px-4 py-2 [&.active]:font-medium"
|
||||
href=""
|
||||
>
|
||||
<Lucide class="mr-2 size-4" icon="Lock" />
|
||||
<div class="flex-1 truncate">Single Application License</div>
|
||||
</a>
|
||||
<a
|
||||
class="[&.active]:bg-primary/5 [&.active]:border-primary/10 [&.active]:text-primary flex items-center rounded-lg border border-transparent px-4 py-2 [&.active]:font-medium"
|
||||
href=""
|
||||
>
|
||||
<Lucide class="mr-2 size-4" icon="Settings" />
|
||||
<div class="flex-1 truncate">Multi Application License</div>
|
||||
</a>
|
||||
</div>
|
||||
</Box>
|
||||
</div>
|
||||
<!-- END: FAQ Menu -->
|
||||
<!-- BEGIN: FAQ Content -->
|
||||
<div class="col-span-12 lg:col-span-8 xl:col-span-9">
|
||||
<Box class="p-0 lg:mt-5">
|
||||
<div class="flex items-center border-b border-foreground/15 p-5">
|
||||
<h2 class="mr-auto text-base font-medium">About Our Products</h2>
|
||||
</div>
|
||||
<div class="p-5">
|
||||
<AccordionRoot :default-value="['faq-1-1']">
|
||||
<AccordionItem value="faq-1-1">
|
||||
<AccordionTrigger>
|
||||
OpenSSL Essentials: Working with SSL Certificates, Private Keys
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="faq-1-2">
|
||||
<AccordionTrigger>
|
||||
Understanding IP Addresses, Subnets, and CIDR Notation
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="faq-1-3">
|
||||
<AccordionTrigger>How To Troubleshoot Common HTTP Error Codes</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="faq-1-4">
|
||||
<AccordionTrigger>An Introduction to Securing your Linux VPS</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</AccordionRoot>
|
||||
</div>
|
||||
</Box>
|
||||
<Box class="mt-5 p-0">
|
||||
<div class="flex items-center border-b border-foreground/15 p-5">
|
||||
<h2 class="mr-auto text-base font-medium">Single Application License</h2>
|
||||
</div>
|
||||
<div class="p-5">
|
||||
<AccordionRoot>
|
||||
<AccordionItem value="faq-2-1">
|
||||
<AccordionTrigger>
|
||||
OpenSSL Essentials: Working with SSL Certificates, Private Keys
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="faq-2-2">
|
||||
<AccordionTrigger>
|
||||
Understanding IP Addresses, Subnets, and CIDR Notation
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="faq-2-3">
|
||||
<AccordionTrigger>How To Troubleshoot Common HTTP Error Codes</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="faq-2-4">
|
||||
<AccordionTrigger>An Introduction to Securing your Linux VPS</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</AccordionRoot>
|
||||
</div>
|
||||
</Box>
|
||||
<Box class="mt-5 p-0">
|
||||
<div class="flex items-center border-b border-foreground/15 p-5">
|
||||
<h2 class="mr-auto text-base font-medium">Multi Application License</h2>
|
||||
</div>
|
||||
<div class="p-5">
|
||||
<AccordionRoot>
|
||||
<AccordionItem value="faq-3-1">
|
||||
<AccordionTrigger>
|
||||
OpenSSL Essentials: Working with SSL Certificates, Private Keys
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="faq-3-2">
|
||||
<AccordionTrigger>
|
||||
Understanding IP Addresses, Subnets, and CIDR Notation
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="faq-3-3">
|
||||
<AccordionTrigger>How To Troubleshoot Common HTTP Error Codes</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="faq-3-4">
|
||||
<AccordionTrigger>An Introduction to Securing your Linux VPS</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever since the 1500s, when an
|
||||
unknown printer took a galley of type and scrambled it to make a type specimen book.
|
||||
It has survived not only five centuries, but also the leap into electronic
|
||||
typesetting, remaining essentially unchanged.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</AccordionRoot>
|
||||
</div>
|
||||
</Box>
|
||||
</div>
|
||||
<!-- END: FAQ Content -->
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,235 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import fakers from '@/utils/faker'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Box } from '@/components/ui/box'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { CheckboxRoot, CheckboxControl } from '@/components/ui/checkbox'
|
||||
import { Field, FieldLabel } from '@/components/ui/field'
|
||||
import { FileIcon } from '@/components/ui/file-icon'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Lucide } from '@/components/ui/lucide'
|
||||
import { MenuRoot, MenuTrigger, MenuPositioner, MenuContent, MenuItem } from '@/components/ui/menu'
|
||||
|
||||
import {
|
||||
PaginationContext,
|
||||
PaginationRoot,
|
||||
PaginationItem,
|
||||
PaginationPrevTrigger,
|
||||
PaginationNextTrigger,
|
||||
PaginationEllipsis,
|
||||
} from '@/components/ui/pagination'
|
||||
import {
|
||||
PopoverRoot,
|
||||
PopoverTrigger,
|
||||
PopoverPositioner,
|
||||
PopoverContent,
|
||||
} from '@/components/ui/popover'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="grid grid-cols-10 gap-6">
|
||||
<div class="col-span-10 lg:col-span-2">
|
||||
<h2 class="mr-auto mt-2 text-lg font-medium">File Manager</h2>
|
||||
<!-- BEGIN: File Manager Menu -->
|
||||
<Box class="mt-6">
|
||||
<div class="flex flex-col gap-2">
|
||||
<a class="[&.active]:bg-foreground/5 [&.active]:border-foreground/10 active flex items-center rounded-md border border-transparent px-3 py-2"
|
||||
href="">
|
||||
<Lucide class="mr-2 size-4" icon="Image" /> Images
|
||||
<Badge variant="warning" look="outline" class="ms-auto">412</Badge>
|
||||
</a>
|
||||
<a class="[&.active]:bg-foreground/5 [&.active]:border-foreground/10 flex items-center rounded-md border border-transparent px-3 py-2"
|
||||
href="">
|
||||
<Lucide class="mr-2 size-4" icon="Video" /> Videos
|
||||
</a>
|
||||
<a class="[&.active]:bg-foreground/5 [&.active]:border-foreground/10 flex items-center rounded-md border border-transparent px-3 py-2"
|
||||
href="">
|
||||
<Lucide class="mr-2 size-4" icon="File" /> Documents
|
||||
<Badge variant="warning" look="outline" class="ms-auto">34</Badge>
|
||||
</a>
|
||||
<a class="[&.active]:bg-foreground/5 [&.active]:border-foreground/10 flex items-center rounded-md border border-transparent px-3 py-2"
|
||||
href="">
|
||||
<Lucide class="mr-2 size-4" icon="Users" /> Shared
|
||||
</a>
|
||||
<a class="[&.active]:bg-foreground/5 [&.active]:border-foreground/10 flex items-center rounded-md border border-transparent px-3 py-2"
|
||||
href="">
|
||||
<Lucide class="mr-2 size-4" icon="Trash" /> Trash
|
||||
</a>
|
||||
</div>
|
||||
<div class="mt-4 border-t border-foreground/15 pt-4">
|
||||
<a class="flex items-center truncate px-3 py-2" href="">
|
||||
<div
|
||||
class="bg-(--color)/20 border-(--color)/60 me-3 mr-3 h-2 w-2 rounded-full border [--color:var(--color-pending)]">
|
||||
</div>
|
||||
Custom Work
|
||||
</a>
|
||||
<a class="mt-2 flex items-center truncate rounded-md px-3 py-2" href="">
|
||||
<div
|
||||
class="bg-(--color)/20 border-(--color)/60 me-3 mr-3 h-2 w-2 rounded-full border [--color:var(--color-success)]">
|
||||
</div>
|
||||
Important Meetings
|
||||
</a>
|
||||
<a class="mt-2 flex items-center truncate rounded-md px-3 py-2" href="">
|
||||
<div
|
||||
class="bg-(--color)/20 border-(--color)/60 me-3 mr-3 h-2 w-2 rounded-full border [--color:var(--color-warning)]">
|
||||
</div>
|
||||
Work
|
||||
</a>
|
||||
<a class="mt-2 flex items-center truncate rounded-md px-3 py-2" href="">
|
||||
<div
|
||||
class="bg-(--color)/20 border-(--color)/60 me-3 mr-3 h-2 w-2 rounded-full border [--color:var(--color-pending)]">
|
||||
</div>
|
||||
Design
|
||||
</a>
|
||||
<a class="mt-2 flex items-center truncate rounded-md px-3 py-2" href="">
|
||||
<div
|
||||
class="bg-(--color)/20 border-(--color)/60 me-3 mr-3 h-2 w-2 rounded-full border [--color:var(--color-danger)]">
|
||||
</div>
|
||||
Next Week
|
||||
</a>
|
||||
<a class="mt-2 flex items-center truncate rounded-md px-3 py-2" href="">
|
||||
<Lucide class="mr-2 size-4" icon="Plus" /> Add New Label
|
||||
</a>
|
||||
</div>
|
||||
</Box>
|
||||
<!-- END: File Manager Menu -->
|
||||
</div>
|
||||
<div class="col-span-10 lg:col-span-8">
|
||||
<!-- BEGIN: File Manager Filter -->
|
||||
<div class="flex flex-col-reverse items-center sm:flex-row">
|
||||
<div class="relative mr-auto mt-3 w-full sm:mt-0 sm:w-auto">
|
||||
<Lucide class="absolute inset-y-0 left-0 z-10 my-auto ml-3 size-4 opacity-70" icon="Search" />
|
||||
<Input class="box w-full px-10 sm:w-64" type="text" placeholder="Search files" />
|
||||
<PopoverRoot>
|
||||
<PopoverTrigger as-child>
|
||||
<a class="absolute inset-y-0 right-0 mr-3 flex items-center" href="#">
|
||||
<Lucide class="size-4 cursor-pointer opacity-70" icon="ChevronDown" />
|
||||
</a>
|
||||
</PopoverTrigger>
|
||||
<PopoverPositioner>
|
||||
<PopoverContent class="ml-2 w-[30rem]">
|
||||
<div class="grid grid-cols-12 gap-4 gap-y-3 p-3">
|
||||
<Field class="col-span-6">
|
||||
<FieldLabel class="text-xs" for="input-filter-1">File Name</FieldLabel>
|
||||
<Input id="input-filter-1" type="text" placeholder="Type the file name" />
|
||||
</Field>
|
||||
<Field class="col-span-6">
|
||||
<FieldLabel class="text-xs" for="input-filter-2">Shared With</FieldLabel>
|
||||
<Input id="input-filter-2" type="text" placeholder="example@gmail.com" />
|
||||
</Field>
|
||||
<Field class="col-span-6">
|
||||
<FieldLabel class="text-xs" for="input-filter-3">Created At</FieldLabel>
|
||||
<Input id="input-filter-3" type="text" placeholder="Important Meeting" />
|
||||
</Field>
|
||||
<Field class="col-span-6">
|
||||
<FieldLabel class="text-xs" for="input-filter-4">Size</FieldLabel>
|
||||
<NativeSelect id="input-filter-4">
|
||||
<NativeSelectOption>10</NativeSelectOption>
|
||||
<NativeSelectOption>25</NativeSelectOption>
|
||||
<NativeSelectOption>35</NativeSelectOption>
|
||||
<NativeSelectOption>50</NativeSelectOption>
|
||||
</NativeSelect>
|
||||
</Field>
|
||||
<div class="col-span-12 mt-3 flex items-center">
|
||||
<Button class="ml-auto w-32" look="outline" variant="secondary">
|
||||
Create Filter
|
||||
</Button>
|
||||
<Button class="ml-2 w-32" variant="primary"> Search </Button>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</PopoverPositioner>
|
||||
</PopoverRoot>
|
||||
</div>
|
||||
<div class="flex w-full sm:w-auto">
|
||||
<Button class="box mr-2" look="outline" variant="primary"> Upload New Files </Button>
|
||||
<MenuRoot class="w-auto">
|
||||
<MenuTrigger as-child>
|
||||
<Box as-child>
|
||||
<Button variant="ghost" class="box flex items-center px-2">
|
||||
<span class="flex size-5 items-center justify-center">
|
||||
<Lucide class="size-4" icon="Plus" />
|
||||
</span>
|
||||
</Button>
|
||||
</Box>
|
||||
</MenuTrigger>
|
||||
<MenuPositioner>
|
||||
<MenuContent class="w-40">
|
||||
<MenuItem value="share">
|
||||
<Lucide class="mr-2 size-4" icon="File" /> Share Files
|
||||
</MenuItem>
|
||||
<MenuItem value="settings">
|
||||
<Lucide class="mr-2 size-4" icon="Settings" /> Settings
|
||||
</MenuItem>
|
||||
</MenuContent>
|
||||
</MenuPositioner>
|
||||
</MenuRoot>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END: File Manager Filter -->
|
||||
<!-- BEGIN: Directory & Files -->
|
||||
<div class="mt-5 grid grid-cols-2 gap-3 gap-y-8 sm:gap-x-5 md:grid-cols-4 lg:grid-cols-5">
|
||||
<div v-for="(faker, fakerKey) in fakers" :key="fakerKey">
|
||||
<Box class="relative rounded-md px-3 pb-5 pt-8 sm:px-5">
|
||||
<div class="absolute left-0 top-0 ml-3 mt-3">
|
||||
<CheckboxRoot :checked="faker.trueFalse[0]">
|
||||
<CheckboxControl />
|
||||
</CheckboxRoot>
|
||||
</div>
|
||||
<FileIcon v-if="faker.files[0]!.type === 'Empty Folder'" class="mx-auto w-3/5" variant="empty-directory" />
|
||||
<FileIcon v-else-if="faker.files[0]!.type === 'Folder'" class="mx-auto w-3/5" variant="directory" />
|
||||
<FileIcon v-else-if="faker.files[0]!.type === 'Image'" class="mx-auto w-3/5" :src="faker.files[0]!.fileName"
|
||||
variant="image" />
|
||||
<FileIcon v-else class="mx-auto w-3/5" :type="faker.files[0]!.type" variant="file" />
|
||||
<a class="mt-4 block truncate text-center font-medium" href="">
|
||||
{{ faker.files[0]!.fileName.split('/').pop() }}
|
||||
</a>
|
||||
<div class="mt-0.5 text-center text-xs opacity-70">
|
||||
{{ faker.files[0]!.size }}
|
||||
</div>
|
||||
<MenuRoot class="absolute right-0 top-0 ml-auto mr-2 mt-3 w-auto">
|
||||
<MenuTrigger as-child>
|
||||
<a class="block size-4" href="#">
|
||||
<Lucide class="size-4 opacity-70" icon="MoreVertical" />
|
||||
</a>
|
||||
</MenuTrigger>
|
||||
<MenuPositioner>
|
||||
<MenuContent class="w-40">
|
||||
<MenuItem value="share">
|
||||
<Lucide class="mr-2 size-4" icon="Users" /> Share File
|
||||
</MenuItem>
|
||||
<MenuItem value="delete">
|
||||
<Lucide class="mr-2 size-4" icon="Trash" /> Delete
|
||||
</MenuItem>
|
||||
</MenuContent>
|
||||
</MenuPositioner>
|
||||
</MenuRoot>
|
||||
</Box>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END: Directory & Files -->
|
||||
<!-- BEGIN: Pagination -->
|
||||
<div class="mt-6 flex flex-wrap items-center sm:flex-row sm:flex-nowrap">
|
||||
<PaginationRoot :count="5000" :pageSize="10" :siblingCount="2" class="w-full sm:mr-auto sm:w-auto">
|
||||
<PaginationPrevTrigger>Previous</PaginationPrevTrigger>
|
||||
<PaginationContext v-slot="{ pagination }">
|
||||
<template v-for="(page, index) in pagination?.pages" :key="index">
|
||||
<PaginationItem v-if="page.type === 'page'" v-bind="{ ...page }">
|
||||
{{ page.value }}
|
||||
</PaginationItem>
|
||||
<PaginationEllipsis v-else :index="index" />
|
||||
</template>
|
||||
</PaginationContext>
|
||||
<PaginationNextTrigger>Next</PaginationNextTrigger>
|
||||
</PaginationRoot>
|
||||
<NativeSelect class="box mt-3 w-20 sm:mt-0">
|
||||
<NativeSelectOption>10</NativeSelectOption>
|
||||
<NativeSelectOption>25</NativeSelectOption>
|
||||
<NativeSelectOption>35</NativeSelectOption>
|
||||
<NativeSelectOption>50</NativeSelectOption>
|
||||
</NativeSelect>
|
||||
</div>
|
||||
<!-- END: Pagination -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,44 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import errorIllustrationUrl from '@/assets/images/error-illustration.svg'
|
||||
import { RouterLink } from 'vue-router'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { resolvePostAuthRoute } from '@/modules/auth'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const homeRoute = resolvePostAuthRoute(authStore.user)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="[
|
||||
'relative py-2',
|
||||
'before:bg-primary dark:before:bg-foreground/[.01] before:fixed before:inset-0 before:bg-noise',
|
||||
'after:bg-accent after:bg-contain after:fixed after:inset-0 after:blur-xl dark:after:opacity-20',
|
||||
]">
|
||||
<div class="container relative z-10 mx-auto">
|
||||
<div class="flex h-screen flex-col items-center justify-center text-center lg:flex-row lg:text-left">
|
||||
<div class="lg:mr-20">
|
||||
<img
|
||||
class="h-48 w-[450px] lg:h-auto"
|
||||
:src="errorIllustrationUrl"
|
||||
alt="Access denied"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-10 text-white lg:mt-0">
|
||||
<div class="text-9xl [text-shadow:_7px_7px_--alpha(var(--color-white)_/_20%)]">403</div>
|
||||
<div class="mt-8 text-xl font-medium lg:text-2xl">Access denied.</div>
|
||||
<div class="mt-3 text-base opacity-70">
|
||||
You do not have permission to access this page.
|
||||
</div>
|
||||
<Button
|
||||
as-child
|
||||
class="box mt-10 border border-white bg-transparent px-7 py-6 text-white"
|
||||
variant="ghost"
|
||||
>
|
||||
<RouterLink :to="homeRoute">Back to Home</RouterLink>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,137 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Box } from '@/components/ui/box'
|
||||
import { MenuRoot, MenuTrigger, MenuPositioner, MenuContent, MenuItem } from '@/components/ui/menu'
|
||||
import {
|
||||
Table,
|
||||
TableHeader,
|
||||
TableBody,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableCell,
|
||||
} from '@/components/ui/table'
|
||||
import { Lucide } from '@/components/ui/lucide'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mt-8 flex flex-col items-center sm:flex-row">
|
||||
<h2 class="mr-auto text-lg font-medium">Invoice Layout</h2>
|
||||
<div class="mt-4 flex w-full sm:mt-0 sm:w-auto">
|
||||
<Button look="outline" class="mr-2 shadow-sm" variant="primary"> Print </Button>
|
||||
<MenuRoot class="w-auto ml-auto sm:ml-0">
|
||||
<MenuTrigger as-child>
|
||||
<Box class="px-2" as-child>
|
||||
<Button variant="ghost" class="box flex items-center">
|
||||
<span class="flex h-5 w-5 items-center justify-center">
|
||||
<Lucide class="h-4 w-4" icon="Plus" />
|
||||
</span>
|
||||
</Button>
|
||||
</Box>
|
||||
</MenuTrigger>
|
||||
<MenuPositioner>
|
||||
<MenuContent class="w-40">
|
||||
<MenuItem value="word">
|
||||
<Lucide class="mr-2 h-4 w-4" icon="File" /> Export Word
|
||||
</MenuItem>
|
||||
<MenuItem value="pdf">
|
||||
<Lucide class="mr-2 h-4 w-4" icon="File" /> Export PDF
|
||||
</MenuItem>
|
||||
</MenuContent>
|
||||
</MenuPositioner>
|
||||
</MenuRoot>
|
||||
</div>
|
||||
</div>
|
||||
<!-- BEGIN: Invoice -->
|
||||
<Box class="mt-5">
|
||||
<div
|
||||
class="bg-primary/[.01] border-primary/20 relative overflow-hidden rounded-xl border text-center sm:text-left before:bg-noise before:absolute before:inset-0 before:opacity-30 after:bg-accent after:absolute after:inset-0 after:opacity-30 after:blur-2xl"
|
||||
>
|
||||
<div class="px-5 py-10 sm:px-20 sm:py-20">
|
||||
<div class="text-primary text-2xl font-semibold">INVOICE</div>
|
||||
<div class="mt-2">
|
||||
Receipt <span class="font-medium">#1923195</span>
|
||||
</div>
|
||||
<div class="mt-1">Jan 02, 2021</div>
|
||||
</div>
|
||||
<div class="flex flex-col px-5 pb-10 pt-10 sm:px-20 sm:pb-20 lg:flex-row">
|
||||
<div>
|
||||
<div class="text-base opacity-70">Client Details</div>
|
||||
<div class="text-primary mt-2 text-lg font-medium">Arnold Schwarzenegger</div>
|
||||
<div class="mt-1">arnodlschwarzenegger@gmail.com</div>
|
||||
<div class="mt-1">260 W. Storm Street New York, NY 10025.</div>
|
||||
</div>
|
||||
<div class="mt-10 lg:ml-auto lg:mt-0 lg:text-right">
|
||||
<div class="text-base opacity-70">Payment to</div>
|
||||
<div class="text-primary mt-2 text-lg font-medium">Left4code</div>
|
||||
<div class="mt-1">left4code@gmail.com</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-5 py-10 sm:px-16 sm:py-20">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead class="whitespace-nowrap border-none pb-6">DESCRIPTION</TableHead>
|
||||
<TableHead class="whitespace-nowrap border-none pb-6 text-right">QTY</TableHead>
|
||||
<TableHead class="whitespace-nowrap border-none pb-6 text-right">PRICE</TableHead>
|
||||
<TableHead class="whitespace-nowrap border-none pb-6 text-right">SUBTOTAL</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<div class="whitespace-nowrap font-medium">
|
||||
Midone - Tailwind Admin Dashboard Template
|
||||
</div>
|
||||
<div class="mt-0.5 whitespace-nowrap text-sm opacity-70">Regular License</div>
|
||||
</TableCell>
|
||||
<TableCell class="text-right">2</TableCell>
|
||||
<TableCell class="text-right">$25</TableCell>
|
||||
<TableCell class="text-right font-medium">$50</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<div class="whitespace-nowrap font-medium">Vuejs Admin Template</div>
|
||||
<div class="mt-0.5 whitespace-nowrap text-sm opacity-70">Regular License</div>
|
||||
</TableCell>
|
||||
<TableCell class="text-right">1</TableCell>
|
||||
<TableCell class="text-right">$25</TableCell>
|
||||
<TableCell class="text-right font-medium">$25</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<div class="whitespace-nowrap font-medium">React Admin Template</div>
|
||||
<div class="mt-0.5 whitespace-nowrap text-sm opacity-70">Regular License</div>
|
||||
</TableCell>
|
||||
<TableCell class="text-right">1</TableCell>
|
||||
<TableCell class="text-right">$25</TableCell>
|
||||
<TableCell class="text-right font-medium">$25</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<div class="whitespace-nowrap font-medium">Laravel Admin Template</div>
|
||||
<div class="mt-0.5 whitespace-nowrap text-sm opacity-70">Regular License</div>
|
||||
</TableCell>
|
||||
<TableCell class="text-right">3</TableCell>
|
||||
<TableCell class="text-right">$25</TableCell>
|
||||
<TableCell class="text-right font-medium">$75</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
<div class="flex flex-col-reverse px-5 pb-10 sm:flex-row sm:px-20 sm:pb-20">
|
||||
<div class="mt-10 text-center sm:mt-0 sm:text-left">
|
||||
<div class="text-base opacity-70">Bank Transfer</div>
|
||||
<div class="text-primary mt-2 text-lg font-medium">Elon Musk</div>
|
||||
<div class="mt-1 opacity-70">Bank Account : 098347234832</div>
|
||||
<div class="mt-1 opacity-70">Code : LFT133243</div>
|
||||
</div>
|
||||
<div class="text-center sm:ml-auto sm:text-right">
|
||||
<div class="text-base opacity-70">Total Amount</div>
|
||||
<div class="text-primary mt-2 text-xl font-medium">$20.600.00</div>
|
||||
<div class="mt-1 opacity-70">Taxes included</div>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
<!-- END: Invoice -->
|
||||
</template>
|
||||
@@ -1,142 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Box } from '@/components/ui/box'
|
||||
import { MenuRoot, MenuTrigger, MenuPositioner, MenuContent, MenuItem } from '@/components/ui/menu'
|
||||
import {
|
||||
Table,
|
||||
TableHeader,
|
||||
TableBody,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableCell,
|
||||
} from '@/components/ui/table'
|
||||
import { Lucide } from '@/components/ui/lucide'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mt-8 flex flex-col items-center sm:flex-row">
|
||||
<h2 class="mr-auto text-lg font-medium">Invoice Layout</h2>
|
||||
<div class="mt-4 flex w-full sm:mt-0 sm:w-auto">
|
||||
<Button look="outline" class="mr-2 shadow-sm" variant="primary"> Print </Button>
|
||||
<MenuRoot class="w-auto ml-auto sm:ml-0">
|
||||
<MenuTrigger as-child>
|
||||
<Box class="px-2" as-child>
|
||||
<Button variant="ghost" class="box flex items-center">
|
||||
<span class="flex h-5 w-5 items-center justify-center">
|
||||
<Lucide class="h-4 w-4" icon="Plus" />
|
||||
</span>
|
||||
</Button>
|
||||
</Box>
|
||||
</MenuTrigger>
|
||||
<MenuPositioner>
|
||||
<MenuContent class="w-40">
|
||||
<MenuItem value="word">
|
||||
<Lucide class="mr-2 h-4 w-4" icon="File" /> Export Word
|
||||
</MenuItem>
|
||||
<MenuItem value="pdf">
|
||||
<Lucide class="mr-2 h-4 w-4" icon="File" /> Export PDF
|
||||
</MenuItem>
|
||||
</MenuContent>
|
||||
</MenuPositioner>
|
||||
</MenuRoot>
|
||||
</div>
|
||||
</div>
|
||||
<!-- BEGIN: Invoice -->
|
||||
<Box class="mt-5">
|
||||
<div
|
||||
class="bg-primary/[.01] border-primary/20 relative overflow-hidden rounded-xl border text-center sm:text-left before:bg-noise before:absolute before:inset-0 before:opacity-30 after:bg-accent after:absolute after:inset-0 after:opacity-30 after:blur-2xl"
|
||||
>
|
||||
<div
|
||||
class="flex flex-col px-5 pt-10 text-center sm:px-20 sm:pt-20 sm:text-left lg:flex-row lg:pb-20"
|
||||
>
|
||||
<div class="text-primary text-3xl font-semibold">INVOICE</div>
|
||||
<div class="mt-20 lg:ml-auto lg:mt-0 lg:text-right">
|
||||
<div class="text-primary text-xl font-medium">Left4code</div>
|
||||
<div class="mt-1">left4code@gmail.com</div>
|
||||
<div class="mt-1">8023 Amerige Street Harriman, NY 10926.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-col px-5 pb-10 pt-10 text-center sm:px-20 sm:pb-20 sm:text-left lg:flex-row"
|
||||
>
|
||||
<div>
|
||||
<div class="text-base opacity-70">Client Details</div>
|
||||
<div class="text-primary mt-2 text-lg font-medium">Arnold Schwarzenegger</div>
|
||||
<div class="mt-1">arnodlschwarzenegger@gmail.com</div>
|
||||
<div class="mt-1">260 W. Storm Street New York, NY 10025.</div>
|
||||
</div>
|
||||
<div class="mt-10 lg:ml-auto lg:mt-0 lg:text-right">
|
||||
<div class="text-base opacity-70">Receipt</div>
|
||||
<div class="text-primary mt-2 text-lg font-medium">#1923195</div>
|
||||
<div class="mt-1">Jan 02, 2021</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-5 py-10 sm:px-16 sm:py-20">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead class="whitespace-nowrap border-none pb-6">DESCRIPTION</TableHead>
|
||||
<TableHead class="whitespace-nowrap border-none pb-6 text-right">QTY</TableHead>
|
||||
<TableHead class="whitespace-nowrap border-none pb-6 text-right">PRICE</TableHead>
|
||||
<TableHead class="whitespace-nowrap border-none pb-6 text-right">SUBTOTAL</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<div class="whitespace-nowrap font-medium">
|
||||
Midone - Tailwind Admin Dashboard Template
|
||||
</div>
|
||||
<div class="mt-0.5 whitespace-nowrap text-sm opacity-70">Regular License</div>
|
||||
</TableCell>
|
||||
<TableCell class="text-right">2</TableCell>
|
||||
<TableCell class="text-right">$25</TableCell>
|
||||
<TableCell class="text-right font-medium">$50</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<div class="whitespace-nowrap font-medium">Vuejs Admin Template</div>
|
||||
<div class="mt-0.5 whitespace-nowrap text-sm opacity-70">Regular License</div>
|
||||
</TableCell>
|
||||
<TableCell class="text-right">1</TableCell>
|
||||
<TableCell class="text-right">$25</TableCell>
|
||||
<TableCell class="text-right font-medium">$25</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<div class="whitespace-nowrap font-medium">React Admin Template</div>
|
||||
<div class="mt-0.5 whitespace-nowrap text-sm opacity-70">Regular License</div>
|
||||
</TableCell>
|
||||
<TableCell class="text-right">1</TableCell>
|
||||
<TableCell class="text-right">$25</TableCell>
|
||||
<TableCell class="text-right font-medium">$25</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<div class="whitespace-nowrap font-medium">Laravel Admin Template</div>
|
||||
<div class="mt-0.5 whitespace-nowrap text-sm opacity-70">Regular License</div>
|
||||
</TableCell>
|
||||
<TableCell class="text-right">3</TableCell>
|
||||
<TableCell class="text-right">$25</TableCell>
|
||||
<TableCell class="text-right font-medium">$75</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
<div class="flex flex-col-reverse px-5 pb-10 sm:flex-row sm:px-20 sm:pb-20">
|
||||
<div class="mt-10 text-center sm:mt-0 sm:text-left">
|
||||
<div class="text-base opacity-70">Bank Transfer</div>
|
||||
<div class="text-primary mt-2 text-lg font-medium">Elon Musk</div>
|
||||
<div class="mt-1 opacity-70">Bank Account : 098347234832</div>
|
||||
<div class="mt-1 opacity-70">Code : LFT133243</div>
|
||||
</div>
|
||||
<div class="text-center sm:ml-auto sm:text-right">
|
||||
<div class="text-base opacity-70">Total Amount</div>
|
||||
<div class="text-primary mt-2 text-xl font-medium">$20.600.00</div>
|
||||
<div class="mt-1 opacity-70">Taxes included</div>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
<!-- END: Invoice -->
|
||||
</template>
|
||||
@@ -1,282 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import fakers from '@/utils/faker'
|
||||
import { Box } from '@/components/ui/box'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
DialogRoot,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogCloseTrigger,
|
||||
} from '@/components/ui/dialog'
|
||||
import { Field, FieldLabel } from '@/components/ui/field'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Lucide } from '@/components/ui/lucide'
|
||||
import { MenuRoot, MenuTrigger, MenuPositioner, MenuContent, MenuItem } from '@/components/ui/menu'
|
||||
|
||||
import { TabsRoot, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
|
||||
const newOrderDialogOpen = ref(false)
|
||||
const addItemDialogOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="flex flex-col items-center sm:flex-row">
|
||||
<h2 class="mr-auto text-lg font-medium">Point of Sale</h2>
|
||||
<div class="mt-4 flex w-full sm:mt-0 sm:w-auto">
|
||||
<Button class="box mr-2" href="#" as="a" variant="primary" look="outline"
|
||||
@click.prevent="newOrderDialogOpen = true">
|
||||
New Order
|
||||
</Button>
|
||||
<MenuRoot class="ml-auto sm:ml-0">
|
||||
<MenuTrigger as-child>
|
||||
<Box class="px-2" as-child>
|
||||
<Button variant="ghost" class="box flex items-center">
|
||||
<span class="flex size-5 items-center justify-center">
|
||||
<Lucide class="size-4" icon="ChevronDown" />
|
||||
</span>
|
||||
</Button>
|
||||
</Box>
|
||||
</MenuTrigger>
|
||||
<MenuPositioner>
|
||||
<MenuContent class="w-auto">
|
||||
<MenuItem value="0">
|
||||
<Lucide class="mr-2 size-4" icon="Activity" />
|
||||
<span class="truncate"> INV-0206020 - {{ fakers[3]!['users'][0]!['name'] }} </span>
|
||||
</MenuItem>
|
||||
<MenuItem value="1">
|
||||
<Lucide class="mr-2 size-4" icon="Activity" />
|
||||
<span class="truncate"> INV-0206022 - {{ fakers[4]!['users'][0]!['name'] }} </span>
|
||||
</MenuItem>
|
||||
<MenuItem value="2">
|
||||
<Lucide class="mr-2 size-4" icon="Activity" />
|
||||
<span class="truncate"> INV-0206021 - {{ fakers[5]!['users'][0]!['name'] }} </span>
|
||||
</MenuItem>
|
||||
</MenuContent>
|
||||
</MenuPositioner>
|
||||
</MenuRoot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 grid grid-cols-12 gap-5">
|
||||
<!-- BEGIN: Item List -->
|
||||
<div class="col-span-12 lg:col-span-8">
|
||||
<div class="lg:flex">
|
||||
<div class="relative">
|
||||
<Input class="box w-full px-4 py-3 pr-10 lg:w-64" type="text" placeholder="Search item..." />
|
||||
<Lucide class="absolute inset-y-0 right-0 my-auto mr-3 size-4 opacity-70" icon="Search" />
|
||||
</div>
|
||||
<NativeSelect class="box ml-auto mt-3 w-full lg:mt-0 lg:w-auto">
|
||||
<NativeSelectOption>Sort By</NativeSelectOption>
|
||||
<NativeSelectOption>A to Z</NativeSelectOption>
|
||||
<NativeSelectOption>Z to A</NativeSelectOption>
|
||||
<NativeSelectOption>Lowest Price</NativeSelectOption>
|
||||
<NativeSelectOption>Highest Price</NativeSelectOption>
|
||||
</NativeSelect>
|
||||
</div>
|
||||
<div class="mt-5 grid grid-cols-12 gap-x-5 gap-y-4">
|
||||
<Box raised="single"
|
||||
class="group col-span-12 cursor-pointer sm:col-span-4 2xl:col-span-3 [&.active]:border-primary/25 [&.active]:bg-primary/10 [&.active]:text-primary">
|
||||
<div class="text-base font-medium">Soup</div>
|
||||
<div class="mt-1 text-xs opacity-70">5 Items</div>
|
||||
</Box>
|
||||
<Box raised="single"
|
||||
class="group col-span-12 cursor-pointer sm:col-span-4 2xl:col-span-3 active [&.active]:border-primary/25 [&.active]:bg-primary/10 [&.active]:text-primary">
|
||||
<div class="text-base font-medium">Pancake & Toast</div>
|
||||
<div class="mt-1 text-xs opacity-70">8 Items</div>
|
||||
</Box>
|
||||
<Box raised="single"
|
||||
class="group col-span-12 cursor-pointer sm:col-span-4 2xl:col-span-3 [&.active]:border-primary/25 [&.active]:bg-primary/10 [&.active]:text-primary">
|
||||
<div class="text-base font-medium">Pasta</div>
|
||||
<div class="mt-1 text-xs opacity-70">4 Items</div>
|
||||
</Box>
|
||||
<Box raised="single"
|
||||
class="group col-span-12 cursor-pointer sm:col-span-4 2xl:col-span-3 [&.active]:border-primary/25 [&.active]:bg-primary/10 [&.active]:text-primary">
|
||||
<div class="text-base font-medium">Waffle</div>
|
||||
<div class="mt-1 text-xs opacity-70">3 Items</div>
|
||||
</Box>
|
||||
<Box raised="single"
|
||||
class="group col-span-12 cursor-pointer sm:col-span-4 2xl:col-span-3 [&.active]:border-primary/25 [&.active]:bg-primary/10 [&.active]:text-primary">
|
||||
<div class="text-base font-medium">Snacks</div>
|
||||
<div class="mt-1 text-xs opacity-70">8 Items</div>
|
||||
</Box>
|
||||
<Box raised="single"
|
||||
class="group col-span-12 cursor-pointer sm:col-span-4 2xl:col-span-3 [&.active]:border-primary/25 [&.active]:bg-primary/10 [&.active]:text-primary">
|
||||
<div class="text-base font-medium">Deserts</div>
|
||||
<div class="mt-1 text-xs opacity-70">8 Items</div>
|
||||
</Box>
|
||||
<Box raised="single"
|
||||
class="group col-span-12 cursor-pointer sm:col-span-4 2xl:col-span-3 [&.active]:border-primary/25 [&.active]:bg-primary/10 [&.active]:text-primary">
|
||||
<div class="text-base font-medium">Beverage</div>
|
||||
<div class="mt-1 text-xs opacity-70">9 Items</div>
|
||||
</Box>
|
||||
</div>
|
||||
<div class="mt-10 grid grid-cols-12 gap-x-5 gap-y-8 border-t border-foreground/15 pt-5">
|
||||
<a v-for="(faker, index) in fakers.slice(0, 8)" :key="index"
|
||||
class="col-span-12 block sm:col-span-4 2xl:col-span-3" href="#" @click.prevent="addItemDialogOpen = true">
|
||||
<Box class="relative p-3">
|
||||
<div class="relative block flex-none before:block before:w-full before:pt-[100%]">
|
||||
<div class="image-fit absolute left-0 top-0 h-full w-full">
|
||||
<img class="rounded-lg" :src="faker['foods'][0]!['image']"
|
||||
alt="Midone - Tailwind Admin Dashboard Template" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 block truncate text-center font-medium">
|
||||
{{ faker['foods'][0]!['name'] }}
|
||||
</div>
|
||||
</Box>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END: Item List -->
|
||||
<!-- BEGIN: Ticket -->
|
||||
<TabsRoot defaultValue="ticket" class="col-span-12 lg:col-span-4">
|
||||
<Box class="p-4">
|
||||
<TabsList class="m-0 w-full">
|
||||
<TabsTrigger class="w-1/2" value="ticket">Ticket</TabsTrigger>
|
||||
<TabsTrigger class="w-1/2" value="details">Details</TabsTrigger>
|
||||
</TabsList>
|
||||
</Box>
|
||||
<TabsContent value="ticket" class="mt-2 space-y-5">
|
||||
<Box class="p-2">
|
||||
<a v-for="(faker, index) in fakers.slice(0, 5)" :key="index"
|
||||
class="hover:bg-foreground/5 flex cursor-pointer items-center rounded-lg px-4 py-3 transition duration-300 ease-in-out"
|
||||
href="#" @click.prevent="addItemDialogOpen = true">
|
||||
<div class="mr-1 max-w-[50%] truncate">
|
||||
{{ faker['foods'][0]!['name'] }}
|
||||
</div>
|
||||
<div class="opacity-70">x 1</div>
|
||||
<Lucide class="ml-2 size-4 opacity-70" icon="Edit" />
|
||||
<div class="ml-auto font-medium">${{ faker['totals'][0] }}</div>
|
||||
</a>
|
||||
</Box>
|
||||
<Box>
|
||||
<div class="flex gap-2">
|
||||
<Input class="bg-foreground/[.03]" type="text" placeholder="Use coupon code..." />
|
||||
<Button variant="primary" look="outline"> Apply </Button>
|
||||
</div>
|
||||
</Box>
|
||||
<Box>
|
||||
<div class="flex">
|
||||
<div class="mr-auto">Subtotal</div>
|
||||
<div class="font-medium">$250</div>
|
||||
</div>
|
||||
<div class="mt-4 flex">
|
||||
<div class="mr-auto">Discount</div>
|
||||
<div class="text-danger font-medium">-$20</div>
|
||||
</div>
|
||||
<div class="mt-4 flex">
|
||||
<div class="mr-auto">Tax</div>
|
||||
<div class="font-medium">15%</div>
|
||||
</div>
|
||||
<div class="mt-4 flex border-t border-foreground/15 pt-4">
|
||||
<div class="mr-auto text-base font-medium">Total Charge</div>
|
||||
<div class="text-base font-medium">$220</div>
|
||||
</div>
|
||||
</Box>
|
||||
<div>
|
||||
<Button class="box ml-auto w-full" variant="primary"> Charge </Button>
|
||||
<Box as-child>
|
||||
<Button class="box mt-3 w-full" variant="ghost"> Clear Items </Button>
|
||||
</Box>
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="details" class="mt-2">
|
||||
<Box>
|
||||
<div class="flex items-center border-b border-foreground/15 pb-5">
|
||||
<div>
|
||||
<div class="opacity-70">Time</div>
|
||||
<div class="mt-1">02/06/20 02:10 PM</div>
|
||||
</div>
|
||||
<Lucide class="ml-auto size-4 opacity-70" icon="Clock" />
|
||||
</div>
|
||||
<div class="flex items-center border-b border-foreground/15 py-5">
|
||||
<div>
|
||||
<div class="opacity-70">Customer</div>
|
||||
<div class="mt-1">{{ fakers[0]!['users'][0]!['name'] }}</div>
|
||||
</div>
|
||||
<Lucide class="ml-auto size-4 opacity-70" icon="User" />
|
||||
</div>
|
||||
<div class="flex items-center border-b border-foreground/15 py-5">
|
||||
<div>
|
||||
<div class="opacity-70">People</div>
|
||||
<div class="mt-1">3</div>
|
||||
</div>
|
||||
<Lucide class="ml-auto size-4 opacity-70" icon="Users" />
|
||||
</div>
|
||||
<div class="flex items-center pt-5">
|
||||
<div>
|
||||
<div class="opacity-70">Table</div>
|
||||
<div class="mt-1">21</div>
|
||||
</div>
|
||||
<Lucide class="ml-auto size-4 opacity-70" icon="Mic" />
|
||||
</div>
|
||||
</Box>
|
||||
</TabsContent>
|
||||
</TabsRoot>
|
||||
<!-- END: Ticket -->
|
||||
</div>
|
||||
<!-- BEGIN: New Order Modal -->
|
||||
<DialogRoot :open="newOrderDialogOpen" @openChange="(details) => (newOrderDialogOpen = details.open)">
|
||||
<DialogContent>
|
||||
<DialogTitle>New Order</DialogTitle>
|
||||
<DialogDescription>
|
||||
Enter the details of the new product to add it to your inventory. Ensure that all required
|
||||
fields are filled correctly.
|
||||
</DialogDescription>
|
||||
<div class="grid grid-cols-12 gap-4 gap-y-3">
|
||||
<Field class="col-span-12">
|
||||
<FieldLabel for="pos-form-1">Name</FieldLabel>
|
||||
<Input id="pos-form-1" type="text" placeholder="Customer name" />
|
||||
</Field>
|
||||
<Field class="col-span-12">
|
||||
<FieldLabel for="pos-form-2">Table</FieldLabel>
|
||||
<Input id="pos-form-2" type="text" placeholder="Customer table" />
|
||||
</Field>
|
||||
<Field class="col-span-12">
|
||||
<FieldLabel for="pos-form-3">Number of People</FieldLabel>
|
||||
<Input id="pos-form-3" type="text" placeholder="People" />
|
||||
</Field>
|
||||
</div>
|
||||
<div class="flex gap-2 justify-end mt-7">
|
||||
<DialogCloseTrigger> Cancel </DialogCloseTrigger>
|
||||
<Button type="button" variant="primary"> Create Ticket </Button>
|
||||
</div>
|
||||
<DialogCloseTrigger />
|
||||
</DialogContent>
|
||||
</DialogRoot>
|
||||
<!-- END: New Order Modal -->
|
||||
<!-- BEGIN: Add Item Modal -->
|
||||
<DialogRoot :open="addItemDialogOpen" @openChange="(details) => (addItemDialogOpen = details.open)">
|
||||
<DialogContent>
|
||||
<DialogTitle>{{ fakers[0]!['foods'][0]!['name'] }}</DialogTitle>
|
||||
<DialogDescription>
|
||||
Fill in the details to add a new product to your inventory. Adjust the initial stock
|
||||
quantity as needed to ensure accurate tracking.
|
||||
</DialogDescription>
|
||||
<div class="grid grid-cols-12 gap-4 gap-y-3">
|
||||
<Field class="col-span-12">
|
||||
<FieldLabel for="pos-form-4">Quantity</FieldLabel>
|
||||
<div class="flex flex-1 gap-1">
|
||||
<Button class="w-12" type="button" look="outline"> - </Button>
|
||||
<Input class="w-24 text-center" id="pos-form-4" type="text" value="2" placeholder="Item quantity" />
|
||||
<Button class="w-12" type="button" look="outline"> + </Button>
|
||||
</div>
|
||||
</Field>
|
||||
<Field class="col-span-12">
|
||||
<FieldLabel for="pos-form-5">Notes</FieldLabel>
|
||||
<Textarea id="pos-form-5" placeholder="Item notes" />
|
||||
</Field>
|
||||
</div>
|
||||
<div class="flex gap-2 justify-end mt-7">
|
||||
<DialogCloseTrigger> Cancel </DialogCloseTrigger>
|
||||
<Button type="button" variant="primary"> Add Item </Button>
|
||||
</div>
|
||||
<DialogCloseTrigger />
|
||||
</DialogContent>
|
||||
</DialogRoot>
|
||||
<!-- END: Add Item Modal -->
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,131 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Box } from '@/components/ui/box'
|
||||
import { Lucide } from '@/components/ui/lucide'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center">
|
||||
<h2 class="mr-auto text-lg font-medium">Pricing Layout</h2>
|
||||
</div>
|
||||
<!-- BEGIN: Pricing Layout -->
|
||||
<Box class="mt-5 flex flex-col lg:flex-row">
|
||||
<div class="flex-1 px-5 py-16">
|
||||
<Lucide class="text-primary mx-auto block size-12 stroke-1" icon="CreditCard" />
|
||||
<div class="mt-10 text-center text-xl font-medium">Basic Plan</div>
|
||||
<div class="mt-5 text-center opacity-70">
|
||||
1 Domain <span class="mx-1">•</span> 10 Users <span class="mx-1">•</span> 20 Copies
|
||||
</div>
|
||||
<div class="mx-auto mt-2 px-10 text-center opacity-70">
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry.
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<div class="relative mx-auto mt-8 text-5xl font-medium">
|
||||
<span class="absolute left-0 top-0 -ml-4 text-2xl">$</span> 35
|
||||
</div>
|
||||
</div>
|
||||
<Button look="outline" class="px-10 mx-auto mt-10 block rounded-full" variant="primary">
|
||||
Purchase Now
|
||||
</Button>
|
||||
</div>
|
||||
<div
|
||||
class="flex-1 border-b border-t px-5 py-16 lg:border-b-0 lg:border-l lg:border-r lg:border-t-0 border-foreground/15"
|
||||
>
|
||||
<Lucide class="text-primary mx-auto block size-12 stroke-1" icon="Briefcase" />
|
||||
<div class="mt-10 text-center text-xl font-medium">Business</div>
|
||||
<div class="mt-5 text-center opacity-70">
|
||||
1 Domain <span class="mx-1">•</span> 10 Users <span class="mx-1">•</span> 20 Copies
|
||||
</div>
|
||||
<div class="mx-auto mt-2 px-10 text-center opacity-70">
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry.
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<div class="relative mx-auto mt-8 text-5xl font-medium">
|
||||
<span class="absolute left-0 top-0 -ml-4 text-2xl">$</span> 60
|
||||
</div>
|
||||
</div>
|
||||
<Button look="outline" class="px-10 mx-auto mt-10 block rounded-full" variant="primary">
|
||||
Purchase Now
|
||||
</Button>
|
||||
</div>
|
||||
<div class="flex-1 px-5 py-16">
|
||||
<Lucide class="text-primary mx-auto block size-12 stroke-1" icon="ShoppingBag" />
|
||||
<div class="mt-10 text-center text-xl font-medium">Enterprise</div>
|
||||
<div class="mt-5 text-center opacity-70">
|
||||
1 Domain <span class="mx-1">•</span> 10 Users <span class="mx-1">•</span> 20 Copies
|
||||
</div>
|
||||
<div class="mx-auto mt-2 px-10 text-center opacity-70">
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry.
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<div class="relative mx-auto mt-8 text-5xl font-medium">
|
||||
<span class="absolute left-0 top-0 -ml-4 text-2xl">$</span> 120
|
||||
</div>
|
||||
</div>
|
||||
<Button look="outline" class="px-10 mx-auto mt-10 block rounded-full" variant="primary">
|
||||
Purchase Now
|
||||
</Button>
|
||||
</div>
|
||||
</Box>
|
||||
<!-- END: Pricing Layout -->
|
||||
<!-- BEGIN: Pricing Layout -->
|
||||
<Box class="mt-8 flex flex-col lg:flex-row">
|
||||
<div class="flex-1 px-5 py-16">
|
||||
<Lucide class="text-primary mx-auto block size-12 stroke-1" icon="Activity" />
|
||||
<div class="mt-10 text-center text-xl font-medium">Basic Plan</div>
|
||||
<div class="mt-5 text-center opacity-70">
|
||||
1 Domain <span class="mx-1">•</span> 10 Users <span class="mx-1">•</span> 20 Copies
|
||||
</div>
|
||||
<div class="mx-auto mt-2 px-10 text-center opacity-70">
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry.
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<div class="relative mx-auto mt-8 text-5xl font-medium">
|
||||
<span class="absolute left-0 top-0 -ml-4 text-2xl">$</span> 35
|
||||
</div>
|
||||
</div>
|
||||
<Button look="outline" class="px-10 mx-auto mt-10 block rounded-full" variant="primary">
|
||||
Purchase Now
|
||||
</Button>
|
||||
</div>
|
||||
<div
|
||||
class="flex-1 border-b border-t px-5 py-16 lg:border-b-0 lg:border-l lg:border-r lg:border-t-0 border-foreground/15"
|
||||
>
|
||||
<Lucide class="text-primary mx-auto block size-12 stroke-1" icon="Box" />
|
||||
<div class="mt-10 text-center text-xl font-medium">Business</div>
|
||||
<div class="mt-5 text-center opacity-70">
|
||||
1 Domain <span class="mx-1">•</span> 10 Users <span class="mx-1">•</span> 20 Copies
|
||||
</div>
|
||||
<div class="mx-auto mt-2 px-10 text-center opacity-70">
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry.
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<div class="relative mx-auto mt-8 text-5xl font-medium">
|
||||
<span class="absolute left-0 top-0 -ml-4 text-2xl">$</span> 60
|
||||
</div>
|
||||
</div>
|
||||
<Button look="outline" class="px-10 mx-auto mt-10 block rounded-full" variant="primary">
|
||||
Purchase Now
|
||||
</Button>
|
||||
</div>
|
||||
<div class="flex-1 px-5 py-16">
|
||||
<Lucide class="text-primary mx-auto block size-12 stroke-1" icon="Send" />
|
||||
<div class="mt-10 text-center text-xl font-medium">Enterprise</div>
|
||||
<div class="mt-5 text-center opacity-70">
|
||||
1 Domain <span class="mx-1">•</span> 10 Users <span class="mx-1">•</span> 20 Copies
|
||||
</div>
|
||||
<div class="mx-auto mt-2 px-10 text-center opacity-70">
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry.
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<div class="relative mx-auto mt-8 text-5xl font-medium">
|
||||
<span class="absolute left-0 top-0 -ml-4 text-2xl">$</span> 120
|
||||
</div>
|
||||
</div>
|
||||
<Button look="outline" class="px-10 mx-auto mt-10 block rounded-full" variant="primary">
|
||||
Purchase Now
|
||||
</Button>
|
||||
</div>
|
||||
</Box>
|
||||
<!-- END: Pricing Layout -->
|
||||
</template>
|
||||
@@ -1,279 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Box } from '@/components/ui/box'
|
||||
import { TabsRoot, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs'
|
||||
import { Lucide } from '@/components/ui/lucide'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h2 class="mr-auto text-center text-2xl font-medium">Best Price & Services</h2>
|
||||
<div class="mt-6">
|
||||
<TabsRoot defaultValue="monthly">
|
||||
<!-- BEGIN: Pricing Tab -->
|
||||
<div class="flex justify-center">
|
||||
<Box raised="single" class="mx-auto w-96 p-3">
|
||||
<TabsList class="mb-0 w-full">
|
||||
<TabsTrigger class="w-1/2" value="monthly">Monthly Fees</TabsTrigger>
|
||||
<TabsTrigger class="w-1/2" value="annual">Annual Fees</TabsTrigger>
|
||||
</TabsList>
|
||||
</Box>
|
||||
</div>
|
||||
<!-- END: Pricing Tab -->
|
||||
<!-- BEGIN: Pricing Content -->
|
||||
<TabsContent value="monthly" class="mt-8">
|
||||
<div class="flex flex-col gap-5 lg:flex-row">
|
||||
<div
|
||||
class="flex flex-1 flex-col justify-center pb-10 text-center sm:px-10 lg:px-5 lg:pb-0"
|
||||
>
|
||||
<div class="text-lg font-medium">Monthly Product Pricing</div>
|
||||
<div class="mt-3 indent-10 opacity-70 lg:text-justify">
|
||||
<p>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever.
|
||||
</p>
|
||||
<p class="mt-2">
|
||||
When an unknown printer took a galley of type and scrambled it to make a type
|
||||
specimen book. It has survived not only five centuries, but also the leap into
|
||||
electronic typesetting, remaining essentially unchanged.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Box class="flex-1 py-16">
|
||||
<Lucide class="text-primary mx-auto block size-12 stroke-1" icon="Briefcase" />
|
||||
<div class="mt-10 text-center text-xl font-medium">Business</div>
|
||||
<div class="mt-5 text-center opacity-70">
|
||||
1 Domain <span class="mx-1">•</span> 10 Users <span class="mx-1">•</span> 20 Copies
|
||||
</div>
|
||||
<div class="mx-auto mt-2 px-10 text-center opacity-70">
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry.
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<div class="relative mx-auto mt-8 text-5xl font-medium">
|
||||
<span class="absolute left-0 top-0 -ml-4 text-2xl">$</span>
|
||||
60
|
||||
</div>
|
||||
</div>
|
||||
<Button look="outline" class="px-10 mx-auto mt-10 block rounded-full" variant="primary">
|
||||
Purchase Now
|
||||
</Button>
|
||||
</Box>
|
||||
<Box class="flex-1 py-16">
|
||||
<Lucide class="text-primary mx-auto block size-12 stroke-1" icon="ShoppingBag" />
|
||||
<div class="mt-10 text-center text-xl font-medium">Enterprise</div>
|
||||
<div class="mt-5 text-center opacity-70">
|
||||
1 Domain <span class="mx-1">•</span> 10 Users <span class="mx-1">•</span> 20 Copies
|
||||
</div>
|
||||
<div class="mx-auto mt-2 px-10 text-center opacity-70">
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry.
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<div class="relative mx-auto mt-8 text-5xl font-medium">
|
||||
<span class="absolute left-0 top-0 -ml-4 text-2xl">$</span>
|
||||
120
|
||||
</div>
|
||||
</div>
|
||||
<Button look="outline" class="px-10 mx-auto mt-10 block rounded-full" variant="primary">
|
||||
Purchase Now
|
||||
</Button>
|
||||
</Box>
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="annual" class="mt-8">
|
||||
<div class="flex flex-col lg:flex-row">
|
||||
<div
|
||||
class="flex flex-1 flex-col justify-center pb-10 text-center sm:px-10 lg:px-5 lg:pb-0"
|
||||
>
|
||||
<div class="text-lg font-medium">Annual Product Pricing</div>
|
||||
<div class="mt-3 opacity-70 lg:text-justify">
|
||||
<p>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever.
|
||||
</p>
|
||||
<p class="mt-2">
|
||||
When an unknown printer took a galley of type and scrambled it to make a type
|
||||
specimen book. It has survived not only five centuries, but also the leap into
|
||||
electronic typesetting, remaining essentially unchanged.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Box class="mb-5 flex-1 py-16 lg:mb-0 lg:ml-5">
|
||||
<Lucide class="text-primary mx-auto block size-12 stroke-1" icon="Briefcase" />
|
||||
<div class="mt-10 text-center text-xl font-medium">Business</div>
|
||||
<div class="mt-5 text-center opacity-70">
|
||||
1 Domain <span class="mx-1">•</span> 10 Users <span class="mx-1">•</span> 20 Copies
|
||||
</div>
|
||||
<div class="mx-auto mt-2 px-10 text-center opacity-70">
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry.
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<div class="relative mx-auto mt-8 text-5xl font-medium">
|
||||
<span class="absolute left-0 top-0 -ml-4 text-2xl">$</span>
|
||||
40
|
||||
</div>
|
||||
</div>
|
||||
<Button look="outline" class="px-10 mx-auto mt-10 block rounded-full" variant="primary">
|
||||
Purchase Now
|
||||
</Button>
|
||||
</Box>
|
||||
<Box class="flex-1 py-16 lg:ml-5">
|
||||
<Lucide class="text-primary mx-auto block size-12 stroke-1" icon="ShoppingBag" />
|
||||
<div class="mt-10 text-center text-xl font-medium">Enterprise</div>
|
||||
<div class="mt-5 text-center opacity-70">
|
||||
1 Domain <span class="mx-1">•</span> 10 Users <span class="mx-1">•</span> 20 Copies
|
||||
</div>
|
||||
<div class="mx-auto mt-2 px-10 text-center opacity-70">
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry.
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<div class="relative mx-auto mt-8 text-5xl font-medium">
|
||||
<span class="absolute left-0 top-0 -ml-4 text-2xl">$</span>
|
||||
90
|
||||
</div>
|
||||
</div>
|
||||
<Button look="outline" class="px-10 mx-auto mt-10 block rounded-full" variant="primary">
|
||||
Purchase Now
|
||||
</Button>
|
||||
</Box>
|
||||
</div>
|
||||
</TabsContent>
|
||||
<!-- END: Pricing Content -->
|
||||
</TabsRoot>
|
||||
</div>
|
||||
<Box class="mt-20 px-8 py-12">
|
||||
<h2 class="mr-auto text-center text-2xl font-medium">Best Price & Services</h2>
|
||||
<TabsRoot defaultValue="monthly2">
|
||||
<!-- BEGIN: Pricing Tab -->
|
||||
<div class="mt-6 flex justify-center">
|
||||
<div class="mx-auto w-96 rounded-2xl border border-foreground/15 p-1">
|
||||
<TabsList class="mb-0 w-full">
|
||||
<TabsTrigger class="w-1/2" value="monthly2">Monthly Fees</TabsTrigger>
|
||||
<TabsTrigger class="w-1/2" value="annual2">Annual Fees</TabsTrigger>
|
||||
</TabsList>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END: Pricing Tab -->
|
||||
<!-- BEGIN: Pricing Content -->
|
||||
<TabsContent value="monthly2" class="mt-10">
|
||||
<div class="flex flex-col lg:flex-row">
|
||||
<div
|
||||
class="flex flex-1 flex-col justify-center pb-10 text-center sm:px-10 lg:px-5 lg:pb-0"
|
||||
>
|
||||
<div class="text-lg font-medium">Monthly Product Pricing</div>
|
||||
<div class="mt-3 indent-10 opacity-70 lg:text-justify">
|
||||
<p>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever.
|
||||
</p>
|
||||
<p class="mt-2">
|
||||
When an unknown printer took a galley of type and scrambled it to make a type
|
||||
specimen book. It has survived not only five centuries, but also the leap into
|
||||
electronic typesetting, remaining essentially unchanged.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex-1 border-b border-t py-16 lg:ml-8 lg:border-b-0 lg:border-l lg:border-r lg:border-t-0 border-foreground/15"
|
||||
>
|
||||
<Lucide class="text-primary mx-auto block size-12 stroke-1" icon="Briefcase" />
|
||||
<div class="mt-10 text-center text-xl font-medium">Business</div>
|
||||
<div class="mt-5 text-center opacity-70">
|
||||
1 Domain <span class="mx-1">•</span> 10 Users <span class="mx-1">•</span> 20 Copies
|
||||
</div>
|
||||
<div class="mx-auto mt-2 px-10 text-center opacity-70">
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry.
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<div class="relative mx-auto mt-8 text-5xl font-medium">
|
||||
<span class="absolute left-0 top-0 -ml-4 text-2xl">$</span>
|
||||
60
|
||||
</div>
|
||||
</div>
|
||||
<Button look="outline" class="px-10 mx-auto mt-10 block rounded-full" variant="primary">
|
||||
Purchase Now
|
||||
</Button>
|
||||
</div>
|
||||
<div class="flex-1 py-16">
|
||||
<Lucide class="text-primary mx-auto block size-12 stroke-1" icon="ShoppingBag" />
|
||||
<div class="mt-10 text-center text-xl font-medium">Enterprise</div>
|
||||
<div class="mt-5 text-center opacity-70">
|
||||
1 Domain <span class="mx-1">•</span> 10 Users <span class="mx-1">•</span> 20 Copies
|
||||
</div>
|
||||
<div class="mx-auto mt-2 px-10 text-center opacity-70">
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry.
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<div class="relative mx-auto mt-8 text-5xl font-medium">
|
||||
<span class="absolute left-0 top-0 -ml-4 text-2xl">$</span>
|
||||
120
|
||||
</div>
|
||||
</div>
|
||||
<Button look="outline" class="px-10 mx-auto mt-10 block rounded-full" variant="primary">
|
||||
Purchase Now
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="annual2" class="mt-10">
|
||||
<div class="flex flex-col lg:flex-row">
|
||||
<div
|
||||
class="flex flex-1 flex-col justify-center pb-10 text-center sm:px-10 lg:px-5 lg:pb-0"
|
||||
>
|
||||
<div class="text-lg font-medium">Monthly Product Pricing</div>
|
||||
<div class="mt-3 indent-10 opacity-70 lg:text-justify">
|
||||
<p>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||
Ipsum has been the industry's standard dummy text ever.
|
||||
</p>
|
||||
<p class="mt-2">
|
||||
When an unknown printer took a galley of type and scrambled it to make a type
|
||||
specimen book. It has survived not only five centuries, but also the leap into
|
||||
electronic typesetting, remaining essentially unchanged.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex-1 border-b border-t py-16 lg:ml-8 lg:border-b-0 lg:border-l lg:border-r lg:border-t-0 border-foreground/15"
|
||||
>
|
||||
<Lucide class="text-primary mx-auto block size-12 stroke-1" icon="Briefcase" />
|
||||
<div class="mt-10 text-center text-xl font-medium">Business</div>
|
||||
<div class="mt-5 text-center opacity-70">
|
||||
1 Domain <span class="mx-1">•</span> 10 Users <span class="mx-1">•</span> 20 Copies
|
||||
</div>
|
||||
<div class="mx-auto mt-2 px-10 text-center opacity-70">
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry.
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<div class="relative mx-auto mt-8 text-5xl font-medium">
|
||||
<span class="absolute left-0 top-0 -ml-4 text-2xl">$</span>
|
||||
120
|
||||
</div>
|
||||
</div>
|
||||
<Button look="outline" class="px-10 mx-auto mt-10 block rounded-full" variant="primary">
|
||||
Purchase Now
|
||||
</Button>
|
||||
</div>
|
||||
<div class="flex-1 py-16">
|
||||
<Lucide class="text-primary mx-auto block size-12 stroke-1" icon="ShoppingBag" />
|
||||
<div class="mt-10 text-center text-xl font-medium">Enterprise</div>
|
||||
<div class="mt-5 text-center opacity-70">
|
||||
1 Domain <span class="mx-1">•</span> 10 Users <span class="mx-1">•</span> 20 Copies
|
||||
</div>
|
||||
<div class="mx-auto mt-2 px-10 text-center opacity-70">
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry.
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<div class="relative mx-auto mt-8 text-5xl font-medium">
|
||||
<span class="absolute left-0 top-0 -ml-4 text-2xl">$</span>
|
||||
210
|
||||
</div>
|
||||
</div>
|
||||
<Button look="outline" class="px-10 mx-auto mt-10 block rounded-full" variant="primary">
|
||||
Purchase Now
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
<!-- END: Pricing Content -->
|
||||
</TabsRoot>
|
||||
</Box>
|
||||
</template>
|
||||
@@ -1,152 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import fakers from '@/utils/faker'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Box } from '@/components/ui/box'
|
||||
import { MenuRoot, MenuTrigger, MenuPositioner, MenuContent, MenuItem } from '@/components/ui/menu'
|
||||
import {
|
||||
PaginationContext,
|
||||
PaginationRoot,
|
||||
PaginationItem,
|
||||
PaginationPrevTrigger,
|
||||
PaginationNextTrigger,
|
||||
PaginationEllipsis,
|
||||
} from '@/components/ui/pagination'
|
||||
import { DialogRoot, DialogContent, DialogCloseTrigger } from '@/components/ui/dialog'
|
||||
import { Lucide } from '@/components/ui/lucide'
|
||||
|
||||
const deleteConfirmationOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h2 class="text-lg font-medium">Product List</h2>
|
||||
<div class="mt-5 grid grid-cols-12 gap-x-6 gap-y-8">
|
||||
<div class="col-span-12 mt-2 flex flex-wrap items-center sm:flex-nowrap">
|
||||
<Button class="mr-2" look="outline" variant="primary"> Add New Product </Button>
|
||||
<MenuRoot class="w-auto">
|
||||
<MenuTrigger as-child>
|
||||
<Box class="px-2" as-child>
|
||||
<Button variant="ghost" class="box flex items-center">
|
||||
<span class="flex h-5 w-5 items-center justify-center">
|
||||
<Lucide class="h-4 w-4" icon="Plus" />
|
||||
</span>
|
||||
</Button>
|
||||
</Box>
|
||||
</MenuTrigger>
|
||||
<MenuPositioner>
|
||||
<MenuContent class="w-40">
|
||||
<MenuItem value="print">
|
||||
<Lucide class="mr-2 h-4 w-4" icon="Printer" /> Print
|
||||
</MenuItem>
|
||||
<MenuItem value="excel">
|
||||
<Lucide class="mr-2 h-4 w-4" icon="FileText" /> Export to Excel
|
||||
</MenuItem>
|
||||
<MenuItem value="pdf">
|
||||
<Lucide class="mr-2 h-4 w-4" icon="FileText" /> Export to PDF
|
||||
</MenuItem>
|
||||
</MenuContent>
|
||||
</MenuPositioner>
|
||||
</MenuRoot>
|
||||
<div class="mx-auto hidden opacity-70 md:block">Showing 1 to 10 of 150 entries</div>
|
||||
<div class="mt-3 w-full sm:ml-auto sm:mt-0 sm:w-auto md:ml-0">
|
||||
<div class="relative w-56">
|
||||
<Input class="w-56 pr-10" type="text" placeholder="Search..." />
|
||||
<Lucide class="absolute inset-y-0 right-0 my-auto mr-3 h-4 w-4" icon="Search" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- BEGIN: Data List -->
|
||||
<div v-for="(faker, index) in fakers.slice(0, 12)" :key="index"
|
||||
class="col-span-12 md:col-span-6 lg:col-span-4 xl:col-span-3">
|
||||
<Box class="p-0">
|
||||
<div class="p-5">
|
||||
<div
|
||||
class="image-fit h-40 overflow-hidden rounded-lg before:absolute before:left-0 before:top-0 before:z-10 before:block before:h-full before:w-full before:bg-gradient-to-t before:from-black before:to-black/10 2xl:h-56">
|
||||
<img class="rounded-lg" :src="faker['images'][0]" alt="Midone - Tailwind Admin Dashboard Template" />
|
||||
<Badge v-if="faker['trueFalse'][0]" class="absolute top-0 z-10 m-5" look="outline" variant="pending">
|
||||
Featured
|
||||
</Badge>
|
||||
<div class="absolute bottom-0 z-10 px-5 pb-6 text-white">
|
||||
<a class="block text-base font-medium" href="">
|
||||
{{ faker['products'][0]!['name'] }}
|
||||
</a>
|
||||
<span class="mt-3 text-xs opacity-70">
|
||||
{{ faker['products'][0]!['category'] }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 opacity-70">
|
||||
<div class="flex items-center">
|
||||
<Lucide class="mr-2 h-4 w-4" icon="Link" /> Price: ${{ faker['totals'][0] }}
|
||||
</div>
|
||||
<div class="mt-2 flex items-center">
|
||||
<Lucide class="mr-2 h-4 w-4" icon="Layers" /> Remaining Stock:
|
||||
{{ faker['stocks'][0] }}
|
||||
</div>
|
||||
<div class="mt-2 flex items-center">
|
||||
<Lucide class="mr-2 h-4 w-4" icon="CheckSquare" />
|
||||
Status:
|
||||
{{ faker['trueFalse'][0] ? 'Active' : 'Inactive' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-center border-t border-foreground/[0.08] p-5 lg:justify-end">
|
||||
<a class="text-primary mr-auto flex items-center" href="#">
|
||||
<Lucide class="mr-1 h-4 w-4" icon="Eye" /> Preview
|
||||
</a>
|
||||
<a class="mr-3 flex items-center" href="#">
|
||||
<Lucide class="mr-1 h-4 w-4" icon="CheckSquare" /> Edit
|
||||
</a>
|
||||
<a class="text-danger flex items-center" href="#" @click.prevent="deleteConfirmationOpen = true">
|
||||
<Lucide class="mr-1 h-4 w-4" icon="Trash" /> Delete
|
||||
</a>
|
||||
</div>
|
||||
</Box>
|
||||
</div>
|
||||
<!-- END: Data List -->
|
||||
<!-- BEGIN: Pagination -->
|
||||
<div class="col-span-12 flex flex-wrap items-center sm:flex-row sm:flex-nowrap">
|
||||
<PaginationRoot :count="150" :pageSize="10" :siblingCount="1" class="w-full sm:mr-auto sm:w-auto">
|
||||
<PaginationPrevTrigger>Previous</PaginationPrevTrigger>
|
||||
<PaginationContext v-slot="{ pagination }">
|
||||
<template v-for="(page, index) in pagination?.pages" :key="index">
|
||||
<PaginationItem v-if="page.type === 'page'" v-bind="{ ...page }">
|
||||
{{ page.value }}
|
||||
</PaginationItem>
|
||||
<PaginationEllipsis v-else :index="index" />
|
||||
</template>
|
||||
</PaginationContext>
|
||||
<PaginationNextTrigger>Next</PaginationNextTrigger>
|
||||
</PaginationRoot>
|
||||
<NativeSelect class="mt-3 w-20 sm:mt-0">
|
||||
<NativeSelectOption value="10">10</NativeSelectOption>
|
||||
<NativeSelectOption value="25">25</NativeSelectOption>
|
||||
<NativeSelectOption value="35">35</NativeSelectOption>
|
||||
<NativeSelectOption value="50">50</NativeSelectOption>
|
||||
</NativeSelect>
|
||||
</div>
|
||||
<!-- END: Pagination -->
|
||||
</div>
|
||||
<!-- BEGIN: Delete Confirmation Modal -->
|
||||
<DialogRoot :open="deleteConfirmationOpen" @openChange="(details) => (deleteConfirmationOpen = details.open)">
|
||||
<DialogContent>
|
||||
<div class="p-5 text-center">
|
||||
<Lucide class="text-danger mx-auto mt-3 size-16 stroke-1" icon="CircleX" />
|
||||
<div class="mt-5 text-2xl font-medium">Are you sure?</div>
|
||||
<div class="mt-2 opacity-70">
|
||||
Do you really want to delete these records? <br />
|
||||
This process cannot be undone.
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-5 pb-8 text-center">
|
||||
<DialogCloseTrigger class="mr-2 w-24"> Cancel </DialogCloseTrigger>
|
||||
<Button class="w-24" type="button" variant="danger" look="outline"> Delete </Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</DialogRoot>
|
||||
<!-- END: Delete Confirmation Modal -->
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,178 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import fakers from '@/utils/faker'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Box } from '@/components/ui/box'
|
||||
import { MenuRoot, MenuTrigger, MenuPositioner, MenuContent, MenuItem } from '@/components/ui/menu'
|
||||
import {
|
||||
Table,
|
||||
TableHeader,
|
||||
TableBody,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableCell,
|
||||
} from '@/components/ui/table'
|
||||
import { AvatarRoot, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||
import {
|
||||
PaginationContext,
|
||||
PaginationRoot,
|
||||
PaginationItem,
|
||||
PaginationPrevTrigger,
|
||||
PaginationNextTrigger,
|
||||
PaginationEllipsis,
|
||||
} from '@/components/ui/pagination'
|
||||
import { DialogRoot, DialogContent, DialogCloseTrigger } from '@/components/ui/dialog'
|
||||
import { Lucide } from '@/components/ui/lucide'
|
||||
|
||||
const deleteConfirmationOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h2 class="text-lg font-medium">Product List</h2>
|
||||
<div class="mt-5 grid grid-cols-12 gap-6">
|
||||
<div class="col-span-12 mt-2 flex flex-wrap items-center sm:flex-nowrap">
|
||||
<Button class="mr-2" look="outline" variant="primary"> Add New Product </Button>
|
||||
<MenuRoot class="w-auto">
|
||||
<MenuTrigger as-child>
|
||||
<Box class="px-2" as-child>
|
||||
<Button variant="ghost" class="box flex items-center">
|
||||
<span class="flex h-5 w-5 items-center justify-center">
|
||||
<Lucide class="h-4 w-4" icon="Plus" />
|
||||
</span>
|
||||
</Button>
|
||||
</Box>
|
||||
</MenuTrigger>
|
||||
<MenuPositioner>
|
||||
<MenuContent class="w-40">
|
||||
<MenuItem value="print">
|
||||
<Lucide class="mr-2 h-4 w-4" icon="Printer" /> Print
|
||||
</MenuItem>
|
||||
<MenuItem value="excel">
|
||||
<Lucide class="mr-2 h-4 w-4" icon="FileText" /> Export to Excel
|
||||
</MenuItem>
|
||||
<MenuItem value="pdf">
|
||||
<Lucide class="mr-2 h-4 w-4" icon="FileText" /> Export to PDF
|
||||
</MenuItem>
|
||||
</MenuContent>
|
||||
</MenuPositioner>
|
||||
</MenuRoot>
|
||||
<div class="mx-auto hidden opacity-70 md:block">Showing 1 to 10 of 150 entries</div>
|
||||
<div class="mt-3 w-full sm:ml-auto sm:mt-0 sm:w-auto md:ml-0">
|
||||
<div class="relative w-56">
|
||||
<Input class="w-56 pr-10" type="text" placeholder="Search..." />
|
||||
<Lucide class="absolute inset-y-0 right-0 my-auto mr-3 h-4 w-4" icon="Search" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- BEGIN: Data List -->
|
||||
<div class="col-span-12 overflow-auto lg:overflow-visible">
|
||||
<Table class="-mt-2" variant="boxed">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead> IMAGES </TableHead>
|
||||
<TableHead> PRODUCT NAME </TableHead>
|
||||
<TableHead class="text-center"> STOCK </TableHead>
|
||||
<TableHead class="text-center"> PRICE </TableHead>
|
||||
<TableHead class="text-center"> STATUS </TableHead>
|
||||
<TableHead class="text-center"> ACTIONS </TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow v-for="(faker, index) in fakers.slice(0, 9)" :key="index">
|
||||
<TableCell>
|
||||
<div class="flex">
|
||||
<AvatarRoot class="size-11 rounded-full bg-background">
|
||||
<AvatarFallback>IM</AvatarFallback>
|
||||
<AvatarImage :src="faker['images'][0]" :alt="'Uploaded at ' + faker['dates'][0]" />
|
||||
</AvatarRoot>
|
||||
<AvatarRoot class="size-11 rounded-full bg-background -ms-5">
|
||||
<AvatarFallback>IM</AvatarFallback>
|
||||
<AvatarImage :src="faker['images'][1]" :alt="'Uploaded at ' + faker['dates'][1]" />
|
||||
</AvatarRoot>
|
||||
<AvatarRoot class="size-11 rounded-full bg-background -ms-5">
|
||||
<AvatarFallback>IM</AvatarFallback>
|
||||
<AvatarImage :src="faker['images'][2]" :alt="'Uploaded at ' + faker['dates'][2]" />
|
||||
</AvatarRoot>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<a class="whitespace-nowrap font-medium" href="">
|
||||
{{ faker['categories'][0]!['name'] }}
|
||||
</a>
|
||||
<div class="mt-0.5 whitespace-nowrap text-xs opacity-70">
|
||||
{{ faker['products'][0]!['category'] }}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell class="text-center">
|
||||
{{ faker['stocks'][0] }}
|
||||
</TableCell>
|
||||
<TableCell class="text-center"> ${{ faker['totals'][0] }} </TableCell>
|
||||
<TableCell>
|
||||
<div class="flex items-center justify-center"
|
||||
:class="faker['trueFalse'][0] ? 'text-success' : 'text-danger'">
|
||||
<Lucide class="mr-2 h-4 w-4" icon="CheckSquare" />
|
||||
{{ faker['trueFalse'][0] ? 'Active' : 'Inactive' }}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div class="flex items-center justify-center">
|
||||
<a class="mr-3 flex items-center" href="">
|
||||
<Lucide class="mr-1 h-4 w-4" icon="CheckSquare" />
|
||||
Edit
|
||||
</a>
|
||||
<a class="text-danger flex items-center" href="#" @click.prevent="deleteConfirmationOpen = true">
|
||||
<Lucide class="mr-1 h-4 w-4" icon="Trash" />
|
||||
Delete
|
||||
</a>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
<!-- END: Data List -->
|
||||
<!-- BEGIN: Pagination -->
|
||||
<div class="col-span-12 flex flex-wrap items-center sm:flex-row sm:flex-nowrap">
|
||||
<PaginationRoot :count="150" :pageSize="10" :siblingCount="1" class="w-full sm:mr-auto sm:w-auto">
|
||||
<PaginationPrevTrigger>Previous</PaginationPrevTrigger>
|
||||
<PaginationContext v-slot="{ pagination }">
|
||||
<template v-for="(page, index) in pagination?.pages" :key="index">
|
||||
<PaginationItem v-if="page.type === 'page'" v-bind="{ ...page }">
|
||||
{{ page.value }}
|
||||
</PaginationItem>
|
||||
<PaginationEllipsis v-else :index="index" />
|
||||
</template>
|
||||
</PaginationContext>
|
||||
<PaginationNextTrigger>Next</PaginationNextTrigger>
|
||||
</PaginationRoot>
|
||||
<NativeSelect class="mt-3 w-20 sm:mt-0">
|
||||
<NativeSelectOption value="10">10</NativeSelectOption>
|
||||
<NativeSelectOption value="25">25</NativeSelectOption>
|
||||
<NativeSelectOption value="35">35</NativeSelectOption>
|
||||
<NativeSelectOption value="50">50</NativeSelectOption>
|
||||
</NativeSelect>
|
||||
</div>
|
||||
<!-- END: Pagination -->
|
||||
</div>
|
||||
<!-- BEGIN: Delete Confirmation Modal -->
|
||||
<DialogRoot :open="deleteConfirmationOpen" @openChange="(details) => (deleteConfirmationOpen = details.open)">
|
||||
<DialogContent>
|
||||
<div class="p-5 text-center">
|
||||
<Lucide class="text-danger mx-auto mt-3 size-16 stroke-1" icon="CircleX" />
|
||||
<div class="mt-5 text-2xl font-medium">Are you sure?</div>
|
||||
<div class="mt-2 opacity-70">
|
||||
Do you really want to delete these records? <br />
|
||||
This process cannot be undone.
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-5 pb-8 text-center">
|
||||
<DialogCloseTrigger class="mr-2 w-24"> Cancel </DialogCloseTrigger>
|
||||
<Button class="w-24" type="button" variant="danger" look="outline"> Delete </Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</DialogRoot>
|
||||
<!-- END: Delete Confirmation Modal -->
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,192 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import fakers from '@/utils/faker'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Box } from '@/components/ui/box'
|
||||
import { MenuRoot, MenuTrigger, MenuPositioner, MenuContent, MenuItem } from '@/components/ui/menu'
|
||||
import {
|
||||
Table,
|
||||
TableHeader,
|
||||
TableBody,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableCell,
|
||||
} from '@/components/ui/table'
|
||||
|
||||
import { CheckboxRoot, CheckboxControl } from '@/components/ui/checkbox'
|
||||
import {
|
||||
PaginationContext,
|
||||
PaginationRoot,
|
||||
PaginationItem,
|
||||
PaginationPrevTrigger,
|
||||
PaginationNextTrigger,
|
||||
PaginationEllipsis,
|
||||
} from '@/components/ui/pagination'
|
||||
import {
|
||||
TooltipRoot,
|
||||
TooltipTrigger,
|
||||
TooltipPositioner,
|
||||
TooltipContent,
|
||||
} from '@/components/ui/tooltip'
|
||||
import { Lucide } from '@/components/ui/lucide'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h2 class="text-lg font-medium">Reviews</h2>
|
||||
<div class="mt-5 grid grid-cols-12 gap-6">
|
||||
<div class="col-span-12 mt-2 flex flex-wrap items-center sm:flex-nowrap">
|
||||
<div class="flex w-full sm:w-auto">
|
||||
<div class="relative w-56">
|
||||
<Input class="w-56 pr-10" type="text" placeholder="Search..." />
|
||||
<Lucide class="absolute inset-y-0 right-0 my-auto mr-3 h-4 w-4" icon="Search" />
|
||||
</div>
|
||||
<NativeSelect class="ml-2">
|
||||
<NativeSelectOption value="Status">Status</NativeSelectOption>
|
||||
<NativeSelectOption value="Waiting Payment">Waiting Payment</NativeSelectOption>
|
||||
<NativeSelectOption value="Confirmed">Confirmed</NativeSelectOption>
|
||||
<NativeSelectOption value="Packing">Packing</NativeSelectOption>
|
||||
<NativeSelectOption value="Delivered">Delivered</NativeSelectOption>
|
||||
<NativeSelectOption value="Completed">Completed</NativeSelectOption>
|
||||
</NativeSelect>
|
||||
</div>
|
||||
<div class="mx-auto hidden opacity-70 md:block">Showing 1 to 10 of 150 entries</div>
|
||||
<div class="mt-3 flex w-full items-center xl:mt-0 xl:w-auto">
|
||||
<Button look="outline" class="mr-2">
|
||||
<Lucide class="size-4" icon="FileText" />
|
||||
Export to Excel
|
||||
</Button>
|
||||
<Button look="outline" class="mr-2">
|
||||
<Lucide class="size-4" icon="FileText" />
|
||||
Export to PDF
|
||||
</Button>
|
||||
<MenuRoot class="w-auto">
|
||||
<MenuTrigger as-child>
|
||||
<Box class="px-2" as-child>
|
||||
<Button variant="ghost" class="box flex items-center">
|
||||
<span class="flex h-5 w-5 items-center justify-center">
|
||||
<Lucide class="h-4 w-4" icon="Plus" />
|
||||
</span>
|
||||
</Button>
|
||||
</Box>
|
||||
</MenuTrigger>
|
||||
<MenuPositioner>
|
||||
<MenuContent class="w-40">
|
||||
<MenuItem value="print">
|
||||
<Lucide class="mr-2 h-4 w-4" icon="Printer" /> Print
|
||||
</MenuItem>
|
||||
<MenuItem value="excel">
|
||||
<Lucide class="mr-2 h-4 w-4" icon="FileText" /> Export to Excel
|
||||
</MenuItem>
|
||||
<MenuItem value="pdf">
|
||||
<Lucide class="mr-2 h-4 w-4" icon="FileText" /> Export to PDF
|
||||
</MenuItem>
|
||||
</MenuContent>
|
||||
</MenuPositioner>
|
||||
</MenuRoot>
|
||||
</div>
|
||||
</div>
|
||||
<!-- BEGIN: Data List -->
|
||||
<div class="col-span-12 overflow-auto lg:overflow-visible">
|
||||
<Table class="-mt-2" variant="boxed">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>
|
||||
<CheckboxRoot>
|
||||
<CheckboxControl />
|
||||
</CheckboxRoot>
|
||||
</TableHead>
|
||||
<TableHead> PRODUCT </TableHead>
|
||||
<TableHead> NAME </TableHead>
|
||||
<TableHead> RATING </TableHead>
|
||||
<TableHead> POSTED </TableHead>
|
||||
<TableHead class="text-center"> STATUS </TableHead>
|
||||
<TableHead class="text-center"> ACTIONS </TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow v-for="(faker, index) in fakers.slice(0, 9)" :key="index">
|
||||
<TableCell>
|
||||
<CheckboxRoot>
|
||||
<CheckboxControl />
|
||||
</CheckboxRoot>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div class="flex items-center">
|
||||
<div class="image-fit h-10 w-10">
|
||||
<TooltipRoot>
|
||||
<TooltipTrigger as-child>
|
||||
<img class="rounded-lg shadow-md" :src="faker['images'][0]"
|
||||
alt="Midone - Tailwind Admin Dashboard Template" />
|
||||
</TooltipTrigger>
|
||||
<TooltipPositioner>
|
||||
<TooltipContent>{{ 'Uploaded at ' + faker['dates'][0] }}</TooltipContent>
|
||||
</TooltipPositioner>
|
||||
</TooltipRoot>
|
||||
</div>
|
||||
<a class="ml-4 whitespace-nowrap font-medium" href="">
|
||||
{{ faker['products'][0]!['name'] }}
|
||||
</a>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<a class="flex items-center underline decoration-dotted" href="#">
|
||||
{{ faker['users'][0]!['name'] }}
|
||||
</a>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div class="flex items-center">
|
||||
<div class="flex items-center">
|
||||
<Lucide v-for="n in 4" :key="'filled-' + n" class="fill-pending/30 text-pending mr-1 h-4 w-4"
|
||||
icon="Star" />
|
||||
<Lucide class="fill-foreground/20 text-foreground/70 mr-1 h-4 w-4" icon="Star" />
|
||||
</div>
|
||||
<div class="ml-1 text-xs opacity-70">(4.5+)</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{{ faker['formattedTimes'][0] }}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div class="flex items-center justify-center"
|
||||
:class="faker['trueFalse'][0] ? 'text-success' : 'text-danger'">
|
||||
<Lucide class="mr-2 h-4 w-4" icon="CheckSquare" />
|
||||
{{ faker['trueFalse'][0] ? 'Active' : 'Inactive' }}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div class="flex items-center justify-center">
|
||||
<a class="text-primary mr-5 flex items-center whitespace-nowrap" href="#">
|
||||
<Lucide class="mr-1 h-4 w-4" icon="CheckSquare" />
|
||||
View Details
|
||||
</a>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
<!-- END: Data List -->
|
||||
<!-- BEGIN: Pagination -->
|
||||
<div class="col-span-12 flex flex-wrap items-center sm:flex-row sm:flex-nowrap">
|
||||
<PaginationRoot :count="150" :pageSize="10" :siblingCount="1" class="w-full sm:mr-auto sm:w-auto">
|
||||
<PaginationPrevTrigger>Previous</PaginationPrevTrigger>
|
||||
<PaginationContext v-slot="{ pagination }">
|
||||
<template v-for="(page, index) in pagination?.pages" :key="index">
|
||||
<PaginationItem v-if="page.type === 'page'" v-bind="{ ...page }">
|
||||
{{ page.value }}
|
||||
</PaginationItem>
|
||||
<PaginationEllipsis v-else :index="index" />
|
||||
</template>
|
||||
</PaginationContext>
|
||||
<PaginationNextTrigger>Next</PaginationNextTrigger>
|
||||
</PaginationRoot>
|
||||
<NativeSelect class="mt-3 w-20 sm:mt-0">
|
||||
<NativeSelectOption value="10">10</NativeSelectOption>
|
||||
<NativeSelectOption value="25">25</NativeSelectOption>
|
||||
<NativeSelectOption value="35">35</NativeSelectOption>
|
||||
<NativeSelectOption value="50">50</NativeSelectOption>
|
||||
</NativeSelect>
|
||||
</div>
|
||||
<!-- END: Pagination -->
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,261 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import fakers from '@/utils/faker'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Box } from '@/components/ui/box'
|
||||
import { MenuRoot, MenuTrigger, MenuPositioner, MenuContent, MenuItem } from '@/components/ui/menu'
|
||||
|
||||
import {
|
||||
PaginationContext,
|
||||
PaginationRoot,
|
||||
PaginationItem,
|
||||
PaginationPrevTrigger,
|
||||
PaginationNextTrigger,
|
||||
PaginationEllipsis,
|
||||
} from '@/components/ui/pagination'
|
||||
import { DialogRoot, DialogContent, DialogCloseTrigger } from '@/components/ui/dialog'
|
||||
import { Lucide } from '@/components/ui/lucide'
|
||||
|
||||
const deleteConfirmationOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="flex flex-col items-center sm:flex-row">
|
||||
<h2 class="mr-auto text-lg font-medium">Seller Details</h2>
|
||||
<div class="mt-4 flex w-full sm:mt-0 sm:w-auto">
|
||||
<Button class="mr-2" look="outline" variant="primary"> Print </Button>
|
||||
<MenuRoot class="ml-auto w-auto sm:ml-0">
|
||||
<MenuTrigger as-child>
|
||||
<Box class="px-2" as-child>
|
||||
<Button variant="ghost" class="box flex items-center">
|
||||
<span class="flex h-5 w-5 items-center justify-center">
|
||||
<Lucide class="size-4" icon="Plus" />
|
||||
</span>
|
||||
</Button>
|
||||
</Box>
|
||||
</MenuTrigger>
|
||||
<MenuPositioner>
|
||||
<MenuContent class="w-40">
|
||||
<MenuItem value="print">
|
||||
<Lucide class="mr-2 size-4" icon="Printer" /> Print
|
||||
</MenuItem>
|
||||
<MenuItem value="word">
|
||||
<Lucide class="mr-2 size-4" icon="FileText" /> Export to Word
|
||||
</MenuItem>
|
||||
<MenuItem value="pdf">
|
||||
<Lucide class="mr-2 size-4" icon="FileText" /> Export to PDF
|
||||
</MenuItem>
|
||||
</MenuContent>
|
||||
</MenuPositioner>
|
||||
</MenuRoot>
|
||||
</div>
|
||||
</div>
|
||||
<!-- BEGIN: Seller Details -->
|
||||
<div class="mt-5 grid grid-cols-11 gap-5">
|
||||
<div class="col-span-12 lg:col-span-4 2xl:col-span-3">
|
||||
<Box>
|
||||
<div class="mb-5 flex items-center border-b border-foreground/[.08] pb-5">
|
||||
<div class="truncate text-base font-medium">User Details</div>
|
||||
<a class="text-primary ml-auto flex items-center" href="">
|
||||
<Lucide class="mr-2 h-4 w-4" icon="Edit" /> More Details
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<Lucide class="mr-2 size-4 opacity-70" icon="Clipboard" />
|
||||
Unique ID:
|
||||
<a class="ml-1 underline decoration-dotted" href=""> SLR-20220217-2053411933 </a>
|
||||
</div>
|
||||
<div class="mt-3 flex items-center">
|
||||
<Lucide class="mr-2 size-4 opacity-70" icon="User" />
|
||||
Name:
|
||||
<a class="ml-1 underline decoration-dotted" href="">
|
||||
{{ fakers[0]!['users'][0]!['name'] }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="mt-3 flex items-center">
|
||||
<Lucide class="mr-2 size-4 opacity-70" icon="Calendar" />
|
||||
Phone Number: +71828273732
|
||||
</div>
|
||||
<div class="mt-3 flex items-center">
|
||||
<Lucide class="mr-2 size-4 opacity-70" icon="MapPin" />
|
||||
Address: 260 W. Storm Street New York, NY 10025.
|
||||
</div>
|
||||
<div class="mt-5 flex items-center border-t border-foreground/[.08] pt-5 font-medium">
|
||||
<Button look="outline" class="w-full px-2 py-1" type="button"> Message User </Button>
|
||||
</div>
|
||||
</Box>
|
||||
<Box class="mt-8">
|
||||
<div class="mb-5 flex items-center border-b border-foreground/[.08] pb-5">
|
||||
<div class="truncate text-base font-medium">Store Details</div>
|
||||
<a class="text-primary ml-auto flex items-center" href="">
|
||||
<Lucide class="mr-2 h-4 w-4" icon="Edit" /> More Details
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<Lucide class="mr-2 size-4 opacity-70" icon="Clipboard" />
|
||||
Unique ID:
|
||||
<a class="ml-1 underline decoration-dotted" href=""> STR-2053411933-20220217 </a>
|
||||
</div>
|
||||
<div class="mt-3 flex items-center">
|
||||
<Lucide class="mr-2 size-4 opacity-70" icon="ShoppingBag" />
|
||||
Name:
|
||||
<a class="ml-1 underline decoration-dotted" href=""> Themeforest </a>
|
||||
</div>
|
||||
<div class="mt-3 flex items-center">
|
||||
<Lucide class="mr-2 size-4 opacity-70" icon="Calendar" />
|
||||
Phone Number: +71828273732
|
||||
</div>
|
||||
<div class="mt-3 flex items-center">
|
||||
<Lucide class="mr-2 size-4 opacity-70" icon="MapPin" />
|
||||
Address: 260 W. Storm Street New York, NY 10025.
|
||||
</div>
|
||||
<div class="mt-3 flex items-center">
|
||||
<Lucide class="mr-2 size-4 opacity-70" icon="Calendar" />
|
||||
Status:
|
||||
<Badge class="ml-2" variant="success"> Active </Badge>
|
||||
</div>
|
||||
<div class="mt-5 flex items-center border-t border-foreground/[.08] pt-5 font-medium">
|
||||
<Button look="outline" class="w-full px-2 py-1" type="button"> Change Status </Button>
|
||||
</div>
|
||||
</Box>
|
||||
<Box class="mt-8">
|
||||
<div class="mb-5 flex items-center border-b border-foreground/[.08] pb-5">
|
||||
<div class="truncate text-base font-medium">Transaction Reports</div>
|
||||
<a class="text-primary ml-auto flex items-center" href="">
|
||||
<Lucide class="mr-2 h-4 w-4" icon="Edit" /> More Details
|
||||
</a>
|
||||
</div>
|
||||
<div class="mt-3 flex items-center">
|
||||
<Lucide class="mr-2 size-4 opacity-70" icon="Clipboard" />
|
||||
Avg. Daily Transactions:
|
||||
<div class="ml-auto">$1,500.00</div>
|
||||
</div>
|
||||
<div class="mt-3 flex items-center">
|
||||
<Lucide class="mr-2 size-4 opacity-70" icon="Clipboard" />
|
||||
Avg. Monthly Transactions:
|
||||
<div class="ml-auto">$42,500.00</div>
|
||||
</div>
|
||||
<div class="mt-3 flex items-center">
|
||||
<Lucide class="mr-2 size-4 opacity-70" icon="Clipboard" />
|
||||
Avg. Annually Transactions:
|
||||
<div class="ml-auto">$1,012,500.00</div>
|
||||
</div>
|
||||
<div class="mt-3 flex items-center">
|
||||
<Lucide class="mr-2 size-4 opacity-70" icon="Star" />
|
||||
Average Rating:
|
||||
<div class="ml-auto">4.9+</div>
|
||||
</div>
|
||||
<div class="mt-3 flex items-center">
|
||||
<Lucide class="mr-2 size-4 opacity-70" icon="Album" />
|
||||
Total Products:
|
||||
<div class="ml-auto">7,120</div>
|
||||
</div>
|
||||
<div class="mt-3 flex items-center">
|
||||
<Lucide class="mr-2 size-4 opacity-70" icon="Archive" />
|
||||
Total Transactions:
|
||||
<div class="ml-auto">1.512.001</div>
|
||||
</div>
|
||||
<div class="mt-3 flex items-center">
|
||||
<Lucide class="mr-2 size-4 opacity-70" icon="Monitor" />
|
||||
Active Disputes:
|
||||
<div class="ml-auto">1</div>
|
||||
</div>
|
||||
</Box>
|
||||
</div>
|
||||
<div class="col-span-12 lg:col-span-7 2xl:col-span-8">
|
||||
<div class="grid grid-cols-12 gap-x-5 gap-y-8">
|
||||
<div v-for="(faker, index) in fakers.slice(0, 9)" :key="index"
|
||||
class="col-span-12 sm:col-span-6 2xl:col-span-4">
|
||||
<Box class="p-0">
|
||||
<div class="p-5">
|
||||
<div
|
||||
class="image-fit h-40 overflow-hidden rounded-xl before:absolute before:left-0 before:top-0 before:z-10 before:block before:h-full before:w-full before:bg-gradient-to-t before:from-black before:to-black/10 2xl:h-56">
|
||||
<img class="rounded-xl" :src="faker['images'][0]" alt="Midone - Tailwind Admin Dashboard Template" />
|
||||
<Badge v-if="faker['trueFalse'][0]" class="absolute top-0 z-10 m-5 text-white" look="filled"
|
||||
variant="pending">
|
||||
Featured
|
||||
</Badge>
|
||||
<div class="absolute bottom-0 z-10 px-5 pb-6 text-white">
|
||||
<a class="block text-base font-medium" href="">
|
||||
{{ faker['products'][0]!['name'] }}
|
||||
</a>
|
||||
<span class="mt-3 text-xs text-white/90">
|
||||
{{ faker['products'][0]!['category'] }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 opacity-70">
|
||||
<div class="flex items-center">
|
||||
<Lucide class="mr-2 h-4 w-4" icon="Link" /> Price: ${{ faker['totals'][0] }}
|
||||
</div>
|
||||
<div class="mt-2 flex items-center">
|
||||
<Lucide class="mr-2 h-4 w-4" icon="Layers" /> Remaining Stock:
|
||||
{{ faker['stocks'][0] }}
|
||||
</div>
|
||||
<div class="mt-2 flex items-center">
|
||||
<Lucide class="mr-2 h-4 w-4" icon="CheckSquare" />
|
||||
Status: {{ faker['trueFalse'][0] ? 'Active' : 'Inactive' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-center border-t border-foreground/[.08] p-5 lg:justify-end">
|
||||
<a class="text-primary mr-auto flex items-center" href="#">
|
||||
<Lucide class="mr-1 h-4 w-4" icon="Eye" /> Preview
|
||||
</a>
|
||||
<a class="mr-3 flex items-center" href="#">
|
||||
<Lucide class="mr-1 h-4 w-4" icon="CheckSquare" /> Edit
|
||||
</a>
|
||||
<a class="text-danger flex items-center" href="#" @click.prevent="deleteConfirmationOpen = true">
|
||||
<Lucide class="mr-1 h-4 w-4" icon="Trash" /> Delete
|
||||
</a>
|
||||
</div>
|
||||
</Box>
|
||||
</div>
|
||||
</div>
|
||||
<!-- BEGIN: Pagination -->
|
||||
<div class="col-span-11 mt-6 flex flex-wrap items-center sm:flex-row sm:flex-nowrap">
|
||||
<PaginationRoot :count="150" :pageSize="10" :siblingCount="1" class="w-full sm:mr-auto sm:w-auto">
|
||||
<PaginationPrevTrigger>Previous</PaginationPrevTrigger>
|
||||
<PaginationContext v-slot="{ pagination }">
|
||||
<template v-for="(page, index) in pagination?.pages" :key="index">
|
||||
<PaginationItem v-if="page.type === 'page'" v-bind="{ ...page }">
|
||||
{{ page.value }}
|
||||
</PaginationItem>
|
||||
<PaginationEllipsis v-else :index="index" />
|
||||
</template>
|
||||
</PaginationContext>
|
||||
<PaginationNextTrigger>Next</PaginationNextTrigger>
|
||||
</PaginationRoot>
|
||||
<NativeSelect class="mt-3 w-20 sm:mt-0">
|
||||
<NativeSelectOption value="10">10</NativeSelectOption>
|
||||
<NativeSelectOption value="25">25</NativeSelectOption>
|
||||
<NativeSelectOption value="35">35</NativeSelectOption>
|
||||
<NativeSelectOption value="50">50</NativeSelectOption>
|
||||
</NativeSelect>
|
||||
</div>
|
||||
<!-- END: Pagination -->
|
||||
</div>
|
||||
</div>
|
||||
<!-- END: Seller Details -->
|
||||
<!-- BEGIN: Delete Confirmation Modal -->
|
||||
<DialogRoot :open="deleteConfirmationOpen" @openChange="(details) => (deleteConfirmationOpen = details.open)">
|
||||
<DialogContent>
|
||||
<div class="p-5 text-center">
|
||||
<Lucide class="text-danger mx-auto mt-3 size-16 stroke-1" icon="CircleX" />
|
||||
<div class="mt-5 text-2xl font-medium">Are you sure?</div>
|
||||
<div class="mt-2 opacity-70">
|
||||
Do you really want to delete these records? <br />
|
||||
This process cannot be undone.
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-5 pb-8 text-center">
|
||||
<DialogCloseTrigger class="mr-2 w-24"> Cancel </DialogCloseTrigger>
|
||||
<Button class="w-24" type="button" variant="danger" look="outline"> Delete </Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</DialogRoot>
|
||||
<!-- END: Delete Confirmation Modal -->
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,207 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import fakers from '@/utils/faker'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Box } from '@/components/ui/box'
|
||||
import { MenuRoot, MenuTrigger, MenuPositioner, MenuContent, MenuItem } from '@/components/ui/menu'
|
||||
import {
|
||||
Table,
|
||||
TableHeader,
|
||||
TableBody,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableCell,
|
||||
} from '@/components/ui/table'
|
||||
|
||||
import { CheckboxRoot, CheckboxControl } from '@/components/ui/checkbox'
|
||||
import {
|
||||
PaginationContext,
|
||||
PaginationRoot,
|
||||
PaginationItem,
|
||||
PaginationPrevTrigger,
|
||||
PaginationNextTrigger,
|
||||
PaginationEllipsis,
|
||||
} from '@/components/ui/pagination'
|
||||
import { DialogRoot, DialogContent, DialogCloseTrigger } from '@/components/ui/dialog'
|
||||
import {
|
||||
TooltipRoot,
|
||||
TooltipTrigger,
|
||||
TooltipPositioner,
|
||||
TooltipContent,
|
||||
} from '@/components/ui/tooltip'
|
||||
import { Lucide } from '@/components/ui/lucide'
|
||||
|
||||
const deleteConfirmationOpen = ref(false)
|
||||
|
||||
const stores = ['Themeforest', 'Codecanyon', 'Graphicriver']
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h2 class="text-lg font-medium">Seller List</h2>
|
||||
<div class="mt-5 grid grid-cols-12 gap-6">
|
||||
<div class="col-span-12 mt-2 flex flex-wrap items-center sm:flex-nowrap">
|
||||
<div class="flex w-full sm:w-auto">
|
||||
<Button class="mr-2" look="outline" variant="primary"> Add New Seller </Button>
|
||||
<MenuRoot class="w-auto">
|
||||
<MenuTrigger as-child>
|
||||
<Box class="px-2" as-child>
|
||||
<Button variant="ghost" class="box flex items-center">
|
||||
<span class="flex h-5 w-5 items-center justify-center">
|
||||
<Lucide class="h-4 w-4" icon="Plus" />
|
||||
</span>
|
||||
</Button>
|
||||
</Box>
|
||||
</MenuTrigger>
|
||||
<MenuPositioner>
|
||||
<MenuContent class="w-40">
|
||||
<MenuItem value="print">
|
||||
<Lucide class="mr-2 h-4 w-4" icon="Printer" /> Print
|
||||
</MenuItem>
|
||||
<MenuItem value="excel">
|
||||
<Lucide class="mr-2 h-4 w-4" icon="FileText" /> Export to Excel
|
||||
</MenuItem>
|
||||
<MenuItem value="pdf">
|
||||
<Lucide class="mr-2 h-4 w-4" icon="FileText" /> Export to PDF
|
||||
</MenuItem>
|
||||
</MenuContent>
|
||||
</MenuPositioner>
|
||||
</MenuRoot>
|
||||
</div>
|
||||
<div class="mx-auto hidden opacity-70 md:block">Showing 1 to 10 of 150 entries</div>
|
||||
<div class="mt-3 flex w-full items-center xl:mt-0 xl:w-auto">
|
||||
<div class="relative w-56">
|
||||
<Input class="w-56 pr-10" type="text" placeholder="Search..." />
|
||||
<Lucide class="absolute inset-y-0 right-0 my-auto mr-3 h-4 w-4" icon="Search" />
|
||||
</div>
|
||||
<NativeSelect class="ml-2 w-56 xl:w-24">
|
||||
<NativeSelectOption value="Status">Status</NativeSelectOption>
|
||||
<NativeSelectOption value="Active">Active</NativeSelectOption>
|
||||
<NativeSelectOption value="Inactive">Inactive</NativeSelectOption>
|
||||
</NativeSelect>
|
||||
</div>
|
||||
</div>
|
||||
<!-- BEGIN: Data List -->
|
||||
<div class="col-span-12 overflow-auto lg:overflow-visible">
|
||||
<Table class="-mt-2" variant="boxed">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>
|
||||
<CheckboxRoot>
|
||||
<CheckboxControl />
|
||||
</CheckboxRoot>
|
||||
</TableHead>
|
||||
<TableHead> SELLER </TableHead>
|
||||
<TableHead> STORE </TableHead>
|
||||
<TableHead> GENDER </TableHead>
|
||||
<TableHead class="text-center"> STATUS </TableHead>
|
||||
<TableHead class="text-right"> TOTAL PRODUCTS </TableHead>
|
||||
<TableHead class="text-center"> ACTIONS </TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow v-for="(faker, index) in fakers.slice(0, 9)" :key="index">
|
||||
<TableCell>
|
||||
<CheckboxRoot>
|
||||
<CheckboxControl />
|
||||
</CheckboxRoot>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div class="flex items-center">
|
||||
<div class="image-fit h-9 w-9">
|
||||
<TooltipRoot>
|
||||
<TooltipTrigger as-child>
|
||||
<img class="rounded-lg shadow-md" :src="faker['images'][0]"
|
||||
alt="Midone - Tailwind Admin Dashboard Template" />
|
||||
</TooltipTrigger>
|
||||
<TooltipPositioner>
|
||||
<TooltipContent>{{ 'Uploaded at ' + faker['dates'][0] }}</TooltipContent>
|
||||
</TooltipPositioner>
|
||||
</TooltipRoot>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<a class="whitespace-nowrap font-medium" href="">
|
||||
{{ faker['users'][0]!['name'] }}
|
||||
</a>
|
||||
<div class="mt-0.5 whitespace-nowrap text-xs opacity-70">
|
||||
{{ faker['users'][0]!['email'] }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<a class="flex items-center underline decoration-dotted" href="#">
|
||||
{{ stores[index % 3] }}
|
||||
</a>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div class="capitalize">{{ faker['users'][0]!['gender'] }}</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div class="flex items-center justify-center"
|
||||
:class="faker['trueFalse'][0] ? 'text-success' : 'text-danger'">
|
||||
<Lucide class="mr-2 h-4 w-4" icon="CheckSquare" />
|
||||
{{ faker['trueFalse'][0] ? 'Active' : 'Inactive' }}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell class="text-right"> {{ faker['totals'][0] }} Items </TableCell>
|
||||
<TableCell>
|
||||
<div class="flex items-center justify-center">
|
||||
<a class="mr-3 flex items-center" href="">
|
||||
<Lucide class="mr-1 h-4 w-4" icon="CheckSquare" />
|
||||
Edit
|
||||
</a>
|
||||
<a class="text-danger flex items-center" href="#" @click.prevent="deleteConfirmationOpen = true">
|
||||
<Lucide class="mr-1 h-4 w-4" icon="Trash" /> Delete
|
||||
</a>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
<!-- END: Data List -->
|
||||
<!-- BEGIN: Pagination -->
|
||||
<div class="col-span-12 flex flex-wrap items-center sm:flex-row sm:flex-nowrap">
|
||||
<PaginationRoot :count="150" :pageSize="10" :siblingCount="1" class="w-full sm:mr-auto sm:w-auto">
|
||||
<PaginationPrevTrigger>Previous</PaginationPrevTrigger>
|
||||
<PaginationContext v-slot="{ pagination }">
|
||||
<template v-for="(page, index) in pagination?.pages" :key="index">
|
||||
<PaginationItem v-if="page.type === 'page'" v-bind="{ ...page }">
|
||||
{{ page.value }}
|
||||
</PaginationItem>
|
||||
<PaginationEllipsis v-else :index="index" />
|
||||
</template>
|
||||
</PaginationContext>
|
||||
<PaginationNextTrigger>Next</PaginationNextTrigger>
|
||||
</PaginationRoot>
|
||||
<NativeSelect class="mt-3 w-20 sm:mt-0">
|
||||
<NativeSelectOption value="10">10</NativeSelectOption>
|
||||
<NativeSelectOption value="25">25</NativeSelectOption>
|
||||
<NativeSelectOption value="35">35</NativeSelectOption>
|
||||
<NativeSelectOption value="50">50</NativeSelectOption>
|
||||
</NativeSelect>
|
||||
</div>
|
||||
<!-- END: Pagination -->
|
||||
</div>
|
||||
<!-- BEGIN: Delete Confirmation Modal -->
|
||||
<DialogRoot :open="deleteConfirmationOpen" @openChange="(details) => (deleteConfirmationOpen = details.open)">
|
||||
<DialogContent>
|
||||
<div class="p-5 text-center">
|
||||
<Lucide class="text-danger mx-auto mt-3 size-16 stroke-1" icon="CircleX" />
|
||||
<div class="mt-5 text-2xl font-medium">Are you sure?</div>
|
||||
<div class="mt-2 opacity-70">
|
||||
Do you really want to delete these records? <br />
|
||||
This process cannot be undone.
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-5 pb-8 text-center">
|
||||
<DialogCloseTrigger class="mr-2 w-24"> Cancel </DialogCloseTrigger>
|
||||
<Button class="w-24" type="button" variant="danger" look="outline"> Delete </Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</DialogRoot>
|
||||
<!-- END: Delete Confirmation Modal -->
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,209 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import fakers from '@/utils/faker'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Box } from '@/components/ui/box'
|
||||
import { MenuRoot, MenuTrigger, MenuPositioner, MenuContent, MenuItem } from '@/components/ui/menu'
|
||||
import {
|
||||
Table,
|
||||
TableHeader,
|
||||
TableBody,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableCell,
|
||||
} from '@/components/ui/table'
|
||||
import {
|
||||
TooltipRoot,
|
||||
TooltipTrigger,
|
||||
TooltipPositioner,
|
||||
TooltipContent,
|
||||
} from '@/components/ui/tooltip'
|
||||
import { Lucide } from '@/components/ui/lucide'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col items-center sm:flex-row">
|
||||
<h2 class="mr-auto text-lg font-medium">Transaction Details</h2>
|
||||
<div class="mt-4 flex w-full sm:mt-0 sm:w-auto">
|
||||
<Button class="mr-2" look="outline" variant="primary"> Print </Button>
|
||||
<MenuRoot class="ml-auto w-auto sm:ml-0">
|
||||
<MenuTrigger as-child>
|
||||
<Box class="px-2" as-child>
|
||||
<Button variant="ghost" class="box flex items-center">
|
||||
<span class="flex h-5 w-5 items-center justify-center">
|
||||
<Lucide class="size-4" icon="Plus" />
|
||||
</span>
|
||||
</Button>
|
||||
</Box>
|
||||
</MenuTrigger>
|
||||
<MenuPositioner>
|
||||
<MenuContent class="w-40">
|
||||
<MenuItem value="print"> <Lucide class="mr-2 size-4" icon="Printer" /> Print </MenuItem>
|
||||
<MenuItem value="excel">
|
||||
<Lucide class="mr-2 size-4" icon="FileText" /> Export to Excel
|
||||
</MenuItem>
|
||||
<MenuItem value="pdf">
|
||||
<Lucide class="mr-2 size-4" icon="FileText" /> Export to PDF
|
||||
</MenuItem>
|
||||
</MenuContent>
|
||||
</MenuPositioner>
|
||||
</MenuRoot>
|
||||
</div>
|
||||
</div>
|
||||
<!-- BEGIN: Transaction Details -->
|
||||
<div class="mt-5 grid grid-cols-11 gap-5">
|
||||
<div class="col-span-12 lg:col-span-4 2xl:col-span-3">
|
||||
<Box>
|
||||
<div class="mb-5 flex items-center border-b border-foreground/15 pb-5">
|
||||
<div class="truncate text-base font-medium">Transaction Details</div>
|
||||
<a class="text-primary ml-auto flex items-center" href="">
|
||||
<Lucide class="mr-2 size-4" icon="Edit" /> Change Status
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<Lucide class="mr-2 size-4 opacity-70" icon="Clipboard" />
|
||||
Invoice:
|
||||
<a class="ml-1 underline decoration-dotted" href=""> INV/20220217/MPL/2053411933 </a>
|
||||
</div>
|
||||
<div class="mt-3 flex items-center">
|
||||
<Lucide class="mr-2 size-4 opacity-70" icon="Calendar" />
|
||||
Purchase Date: 24 March 2022
|
||||
</div>
|
||||
<div class="mt-3 flex items-center">
|
||||
<Lucide class="mr-2 size-4 opacity-70" icon="Clock" />
|
||||
Transaction Status:
|
||||
<Badge class="ml-2" variant="success" look="outline"> Completed </Badge>
|
||||
</div>
|
||||
</Box>
|
||||
<Box class="mt-8">
|
||||
<div class="mb-5 flex items-center border-b border-foreground/15 pb-5">
|
||||
<div class="truncate text-base font-medium">Buyer Details</div>
|
||||
<a class="text-primary ml-auto flex items-center" href="">
|
||||
<Lucide class="mr-2 size-4" icon="Edit" /> View Details
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<Lucide class="mr-2 size-4 opacity-70" icon="Clipboard" />
|
||||
Name:
|
||||
<a class="ml-1 underline decoration-dotted" href="">
|
||||
{{ fakers[0]!['users'][0]!['name'] }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="mt-3 flex items-center">
|
||||
<Lucide class="mr-2 size-4 opacity-70" icon="Calendar" />
|
||||
Phone Number: +71828273732
|
||||
</div>
|
||||
<div class="mt-3 flex items-center">
|
||||
<Lucide class="mr-2 size-4 opacity-70" icon="MapPin" />
|
||||
Address: 260 W. Storm Street New York, NY 10025.
|
||||
</div>
|
||||
</Box>
|
||||
<Box class="mt-8">
|
||||
<div class="mb-5 flex items-center border-b border-foreground/15 pb-5">
|
||||
<div class="truncate text-base font-medium">Payment Details</div>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<Lucide class="mr-2 size-4 opacity-70" icon="Clipboard" />
|
||||
Payment Method:
|
||||
<div class="ml-auto">Direct bank transfer</div>
|
||||
</div>
|
||||
<div class="mt-3 flex items-center">
|
||||
<Lucide class="mr-2 size-4 opacity-70" icon="CreditCard" />
|
||||
Total Price (2 items):
|
||||
<div class="ml-auto">$12,500.00</div>
|
||||
</div>
|
||||
<div class="mt-3 flex items-center">
|
||||
<Lucide class="mr-2 size-4 opacity-70" icon="CreditCard" />
|
||||
Total Shipping Cost (800 gr):
|
||||
<div class="ml-auto">$1,500.00</div>
|
||||
</div>
|
||||
<div class="mt-3 flex items-center">
|
||||
<Lucide class="mr-2 size-4 opacity-70" icon="CreditCard" />
|
||||
Shipping Insurance:
|
||||
<div class="ml-auto">$600.00</div>
|
||||
</div>
|
||||
<div class="mt-5 flex items-center border-t border-foreground/15 pt-5 font-medium">
|
||||
<Lucide class="mr-2 size-4 opacity-70" icon="CreditCard" />
|
||||
Grand Total:
|
||||
<div class="ml-auto">$15,000.00</div>
|
||||
</div>
|
||||
</Box>
|
||||
<Box class="mt-8">
|
||||
<div class="mb-5 flex items-center border-b border-foreground/15 pb-5">
|
||||
<div class="truncate text-base font-medium">Shipping Information</div>
|
||||
<a class="text-primary ml-auto flex items-center" href="">
|
||||
<Lucide class="mr-2 size-4" icon="MapPin" /> Tracking Info
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<Lucide class="mr-2 size-4 opacity-70" icon="Clipboard" />
|
||||
Courier: Left4code Express
|
||||
</div>
|
||||
<div class="mt-3 flex items-center">
|
||||
<Lucide class="mr-2 size-4 opacity-70" icon="Calendar" />
|
||||
Tracking Number: 003005580322
|
||||
<Lucide class="ml-2 size-4 opacity-70" icon="Copy" />
|
||||
</div>
|
||||
<div class="mt-3 flex items-center">
|
||||
<Lucide class="mr-2 size-4 opacity-70" icon="MapPin" />
|
||||
Address: 260 W. Storm Street New York, NY 10025.
|
||||
</div>
|
||||
</Box>
|
||||
</div>
|
||||
<div class="col-span-12 lg:col-span-7 2xl:col-span-8">
|
||||
<Box>
|
||||
<div class="mb-5 flex items-center border-b border-foreground/15 pb-5">
|
||||
<div class="truncate text-base font-medium">Order Details</div>
|
||||
<a class="text-primary ml-auto flex items-center" href="">
|
||||
<Lucide class="mr-2 size-4" icon="Plus" /> Add Notes
|
||||
</a>
|
||||
</div>
|
||||
<Table class="-mt-3">
|
||||
<TableHeader>
|
||||
<TableRow class="[&_th]:border-transparent [&_th]:py-5">
|
||||
<TableHead> Product Name </TableHead>
|
||||
<TableHead class="text-right"> Unit Price </TableHead>
|
||||
<TableHead class="text-right"> QTY </TableHead>
|
||||
<TableHead class="text-right"> Total </TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow
|
||||
v-for="(faker, index) in fakers.slice(0, 8)"
|
||||
:key="index"
|
||||
class="[&_td]:border-transparent [&_td]:py-3"
|
||||
>
|
||||
<TableCell>
|
||||
<div class="flex items-center">
|
||||
<div
|
||||
class="border-(--color)/5 border-3 ring-(--color)/25 relative block size-11 flex-none overflow-hidden rounded-xl ring-1 [--color:var(--color-primary)]"
|
||||
>
|
||||
<TooltipRoot>
|
||||
<TooltipTrigger as-child>
|
||||
<img
|
||||
class="absolute top-0 size-full overflow-hidden object-cover"
|
||||
:src="faker['images'][0]"
|
||||
alt="Midone - Tailwind Admin Dashboard Template"
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipPositioner>
|
||||
<TooltipContent>{{ 'Uploaded at ' + faker['dates'][2] }}</TooltipContent>
|
||||
</TooltipPositioner>
|
||||
</TooltipRoot>
|
||||
</div>
|
||||
<a class="ml-4 whitespace-nowrap font-medium" href="">
|
||||
{{ faker['products'][0]!['name'] }}
|
||||
</a>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell class="text-right"> ${{ faker['totals'][0] + ',000.00' }} </TableCell>
|
||||
<TableCell class="text-right">2</TableCell>
|
||||
<TableCell class="text-right"> ${{ faker['totals'][0]! * 2 + ',000.00' }} </TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Box>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END: Transaction Details -->
|
||||
</template>
|
||||
@@ -1,207 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import fakers from '@/utils/faker'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Box } from '@/components/ui/box'
|
||||
import { MenuRoot, MenuTrigger, MenuPositioner, MenuContent, MenuItem } from '@/components/ui/menu'
|
||||
import {
|
||||
Table,
|
||||
TableHeader,
|
||||
TableBody,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableCell,
|
||||
} from '@/components/ui/table'
|
||||
|
||||
import { CheckboxRoot, CheckboxControl } from '@/components/ui/checkbox'
|
||||
import {
|
||||
PaginationContext,
|
||||
PaginationRoot,
|
||||
PaginationItem,
|
||||
PaginationPrevTrigger,
|
||||
PaginationNextTrigger,
|
||||
PaginationEllipsis,
|
||||
} from '@/components/ui/pagination'
|
||||
import { DialogRoot, DialogContent, DialogCloseTrigger } from '@/components/ui/dialog'
|
||||
import { Lucide } from '@/components/ui/lucide'
|
||||
|
||||
const deleteConfirmationOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h2 class="text-lg font-medium">Transaction List</h2>
|
||||
<div class="mt-5 grid grid-cols-12 gap-6">
|
||||
<div class="col-span-12 mt-2 flex flex-wrap items-center sm:flex-nowrap">
|
||||
<div class="flex w-full sm:w-auto">
|
||||
<div class="relative w-56">
|
||||
<Input class="w-56 pr-10" type="text" placeholder="Search..." />
|
||||
<Lucide class="absolute inset-y-0 right-0 my-auto mr-3 h-4 w-4" icon="Search" />
|
||||
</div>
|
||||
<NativeSelect class="ml-2">
|
||||
<NativeSelectOption value="Status">Status</NativeSelectOption>
|
||||
<NativeSelectOption value="Waiting Payment">Waiting Payment</NativeSelectOption>
|
||||
<NativeSelectOption value="Confirmed">Confirmed</NativeSelectOption>
|
||||
<NativeSelectOption value="Packing">Packing</NativeSelectOption>
|
||||
<NativeSelectOption value="Delivered">Delivered</NativeSelectOption>
|
||||
<NativeSelectOption value="Completed">Completed</NativeSelectOption>
|
||||
</NativeSelect>
|
||||
</div>
|
||||
<div class="mx-auto hidden opacity-70 md:block">Showing 1 to 10 of 150 entries</div>
|
||||
<div class="mt-3 flex w-full items-center xl:mt-0 xl:w-auto">
|
||||
<Box as-child>
|
||||
<Button variant="ghost" class="mr-2">
|
||||
<Lucide class="size-4" icon="FileText" />
|
||||
Export to Excel
|
||||
</Button>
|
||||
</Box>
|
||||
<Box as-child>
|
||||
<Button variant="ghost" class="mr-2">
|
||||
<Lucide class="size-4" icon="FileText" />
|
||||
Export to PDF
|
||||
</Button>
|
||||
</Box>
|
||||
<MenuRoot class="w-auto">
|
||||
<MenuTrigger as-child>
|
||||
<Box class="px-2" as-child>
|
||||
<Button variant="ghost" class="box flex items-center">
|
||||
<span class="flex h-5 w-5 items-center justify-center">
|
||||
<Lucide class="h-4 w-4" icon="Plus" />
|
||||
</span>
|
||||
</Button>
|
||||
</Box>
|
||||
</MenuTrigger>
|
||||
<MenuPositioner>
|
||||
<MenuContent class="w-40">
|
||||
<MenuItem value="print">
|
||||
<Lucide class="mr-2 h-4 w-4" icon="Printer" /> Print
|
||||
</MenuItem>
|
||||
<MenuItem value="excel">
|
||||
<Lucide class="mr-2 h-4 w-4" icon="FileText" /> Export to Excel
|
||||
</MenuItem>
|
||||
<MenuItem value="pdf">
|
||||
<Lucide class="mr-2 h-4 w-4" icon="FileText" /> Export to PDF
|
||||
</MenuItem>
|
||||
</MenuContent>
|
||||
</MenuPositioner>
|
||||
</MenuRoot>
|
||||
</div>
|
||||
</div>
|
||||
<!-- BEGIN: Data List -->
|
||||
<div class="col-span-12 overflow-auto lg:overflow-visible">
|
||||
<Table class="-mt-2" variant="boxed">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>
|
||||
<CheckboxRoot>
|
||||
<CheckboxControl />
|
||||
</CheckboxRoot>
|
||||
</TableHead>
|
||||
<TableHead> INVOICE </TableHead>
|
||||
<TableHead> BUYER NAME </TableHead>
|
||||
<TableHead class="text-center"> STATUS </TableHead>
|
||||
<TableHead> PAYMENT </TableHead>
|
||||
<TableHead class="text-right"> TOTAL TRANSACTION </TableHead>
|
||||
<TableHead class="text-center"> ACTIONS </TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow v-for="(faker, index) in fakers.slice(0, 9)" :key="index">
|
||||
<TableCell>
|
||||
<CheckboxRoot>
|
||||
<CheckboxControl />
|
||||
</CheckboxRoot>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<a class="whitespace-nowrap underline decoration-dotted" href="">
|
||||
{{ '#INV-' + faker['totals'][0] + '807556' }}
|
||||
</a>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<a class="whitespace-nowrap font-medium" href="">
|
||||
{{ faker['users'][0]!['name'] }}
|
||||
</a>
|
||||
<div class="mt-0.5 whitespace-nowrap text-xs opacity-70">
|
||||
{{ faker['trueFalse'][0] ? 'Ohio, Ohio' : 'California, LA' }}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div class="flex items-center justify-center"
|
||||
:class="faker['trueFalse'][0] ? 'text-success' : 'text-danger'">
|
||||
<Lucide class="mr-2 h-4 w-4" icon="CheckSquare" />
|
||||
{{ faker['trueFalse'][0] ? 'Active' : 'Inactive' }}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<template v-if="faker['trueFalse'][0]">
|
||||
<div class="whitespace-nowrap">Direct bank transfer</div>
|
||||
<div class="mt-0.5 whitespace-nowrap text-xs opacity-70">25 March, 12:55</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="whitespace-nowrap">Checking payments</div>
|
||||
<div class="mt-0.5 whitespace-nowrap text-xs opacity-70">30 March, 11:00</div>
|
||||
</template>
|
||||
</TableCell>
|
||||
<TableCell class="text-right"> ${{ faker['totals'][0] + ',000,00' }} </TableCell>
|
||||
<TableCell>
|
||||
<div class="flex items-center justify-center">
|
||||
<a class="text-primary mr-5 flex items-center whitespace-nowrap" href="#">
|
||||
<Lucide class="mr-1 h-4 w-4" icon="CheckSquare" />
|
||||
View Details
|
||||
</a>
|
||||
<a class="text-primary flex items-center whitespace-nowrap" href="#"
|
||||
@click.prevent="deleteConfirmationOpen = true">
|
||||
<Lucide class="mr-1 h-4 w-4" icon="ArrowLeftRight" />
|
||||
Change Status
|
||||
</a>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
<!-- END: Data List -->
|
||||
<!-- BEGIN: Pagination -->
|
||||
<div class="col-span-12 flex flex-wrap items-center sm:flex-row sm:flex-nowrap">
|
||||
<PaginationRoot :count="150" :pageSize="10" :siblingCount="1" class="w-full sm:mr-auto sm:w-auto">
|
||||
<PaginationPrevTrigger>Previous</PaginationPrevTrigger>
|
||||
<PaginationContext v-slot="{ pagination }">
|
||||
<template v-for="(page, index) in pagination?.pages" :key="index">
|
||||
<PaginationItem v-if="page.type === 'page'" v-bind="{ ...page }">
|
||||
{{ page.value }}
|
||||
</PaginationItem>
|
||||
<PaginationEllipsis v-else :index="index" />
|
||||
</template>
|
||||
</PaginationContext>
|
||||
<PaginationNextTrigger>Next</PaginationNextTrigger>
|
||||
</PaginationRoot>
|
||||
<NativeSelect class="mt-3 w-20 sm:mt-0">
|
||||
<NativeSelectOption value="10">10</NativeSelectOption>
|
||||
<NativeSelectOption value="25">25</NativeSelectOption>
|
||||
<NativeSelectOption value="35">35</NativeSelectOption>
|
||||
<NativeSelectOption value="50">50</NativeSelectOption>
|
||||
</NativeSelect>
|
||||
</div>
|
||||
<!-- END: Pagination -->
|
||||
</div>
|
||||
<!-- BEGIN: Delete Confirmation Modal -->
|
||||
<DialogRoot :open="deleteConfirmationOpen" @openChange="(details) => (deleteConfirmationOpen = details.open)">
|
||||
<DialogContent>
|
||||
<div class="p-5 text-center">
|
||||
<Lucide class="text-danger mx-auto mt-3 size-16 stroke-1" icon="CircleX" />
|
||||
<div class="mt-5 text-2xl font-medium">Are you sure?</div>
|
||||
<div class="mt-2 opacity-70">
|
||||
Do you really want to delete these records? <br />
|
||||
This process cannot be undone.
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-5 pb-8 text-center">
|
||||
<DialogCloseTrigger class="mr-2 w-24"> Cancel </DialogCloseTrigger>
|
||||
<Button class="w-24" type="button" variant="danger" look="outline"> Delete </Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</DialogRoot>
|
||||
<!-- END: Delete Confirmation Modal -->
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user