DONE: layout letter with letterhead and footer, notification for newly...

This commit is contained in:
ISMAIL MASSERAN
2026-06-28 02:16:49 +00:00
parent df0d0e743b
commit 3dc6ec41b4
145 changed files with 15028 additions and 2556 deletions
-357
View File
@@ -126,361 +126,4 @@ import { Box } from "@/components/ui/box";
</template>
</Preview>
</div>
<div id="installation">
<SectionTitle>Installation</SectionTitle>
<SectionContent>Install the following dependencies:</SectionContent>
<InstallPackage>add @zag-js/vue @zag-js/accordion</InstallPackage>
<SectionContent>
Copy and paste the following code into your project.
</SectionContent>
<PreviewCode title="components/ui/accordion/AccordionContent.vue">
{{ `
<script lang="ts" setup>
import { accordionContent } from "@mykopkb/core/styles/accordion.styles";
import { cn } from "@mykopkb/core/utils/cn";
import { inject } from "vue";
import { Slot } from "@/components/ui/slot";
import type { Api, ItemProps } from "@zag-js/accordion";
const {
class: className,
asChild = false,
...props
} = defineProps<{
class?: string;
asChild?: boolean;
}>();
const api = inject<Api>("accordionApi");
const item = inject<ItemProps>("accordionItem");
</script>
<template>
<Slot :class="cn([className, accordionContent])"
v-bind="{ ...props, ...$attrs, ...api?.getItemContentProps(item!) }">
<slot v-if="asChild" />
<div v-else>
<slot />
</div>
</Slot>
</template>
` }}
</PreviewCode>
<PreviewCode title="components/ui/accordion/AccordionItem.vue">
{{ `
<script lang="ts" setup>
import { accordionItemVariants } from "@mykopkb/core/styles/accordion.styles";
import {
boxVariants,
type BoxVariants,
} from "@mykopkb/core/styles/box.styles";
import { cn } from "@mykopkb/core/utils/cn";
import { inject, provide } from "vue";
import { Slot } from "@/components/ui/slot";
import type { Api, ItemProps } from "@zag-js/accordion";
const {
raised,
class: className,
asChild = false,
...props
} = defineProps<
BoxVariants & ItemProps & { class?: string; asChild?: boolean }
>();
const variant = inject<"default" | "boxed">("accordionVariant", "default");
const api = inject<Api>("accordionApi");
provide("accordionItem", props);
</script>
<template>
<Slot :class="
cn([
className,
variant == 'boxed' ? boxVariants({ raised, className }) : '',
accordionItemVariants({ variant, className }),
])
" v-bind="{ ...props, ...$attrs, ...api?.getItemProps(props) }">
<slot v-if="asChild" />
<div v-else>
<slot />
</div>
</Slot>
</template>
` }}
</PreviewCode>
<PreviewCode title="components/ui/accordion/AccordionRoot.vue">
{{`
<script lang="ts" setup>
import { accordionRootVariants } from "@mykopkb/core/styles/accordion.styles";
import { cn } from "@mykopkb/core/utils/cn";
import { provide, computed } from "vue";
import * as accordion from "@zag-js/accordion";
import type { Props } from "@zag-js/accordion";
import { useMachine, normalizeProps } from "@zag-js/vue";
import { Slot } from "@/components/ui/slot";
const {
class: className,
variant = "default",
asChild = false,
collapsible = true,
...props
} = defineProps<
Partial<Props> & {
class?: string;
variant?: "default" | "boxed";
asChild?: boolean;
}
>();
const service = useMachine(accordion.machine, {
collapsible,
...props,
id: crypto.randomUUID(),
});
const api = computed(() => accordion.connect(service, normalizeProps));
provide("accordionVariant", variant);
provide("accordionApi", api);
</script>
<template>
<Slot :class="cn([className, accordionRootVariants({ variant, className })])"
v-bind="{ ...props, ...$attrs, ...api.getRootProps() }">
<slot v-if="asChild" />
<div v-else>
<slot />
</div>
</Slot>
</template>
` }}
</PreviewCode>
<PreviewCode title="components/ui/accordion/AccordionTrigger.vue">
{{ `
<script lang="ts" setup>
import { ChevronDownIcon } from "@lucide/vue";
import {
accordionTrigger,
accordionItemIndicator,
} from "@mykopkb/core/styles/accordion.styles";
import { cn } from "@mykopkb/core/utils/cn";
import { inject } from "vue";
import { Slot } from "@/components/ui/slot";
import type { Api, ItemProps } from "@zag-js/accordion";
const {
class: className,
asChild = false,
...props
} = defineProps<{
class?: string;
asChild?: boolean;
}>();
const api = inject<Api>("accordionApi");
const item = inject<ItemProps>("accordionItem");
</script>
<template>
<Slot :class="cn([className, accordionTrigger])"
v-bind="{ ...props, ...$attrs, ...api?.getItemTriggerProps(item!) }">
<slot v-if="asChild" />
<button v-else>
<slot />
<div v-bind="api?.getItemIndicatorProps(item!)" :class="cn(accordionItemIndicator)">
<ChevronDownIcon />
</div>
</button>
</Slot>
</template>
` }}
</PreviewCode>
<PreviewCode title="components/ui/accordion/index.ts">
{{ `
export { default as AccordionRoot } from "./AccordionRoot.vue";
export { default as AccordionItem } from "./AccordionItem.vue";
export { default as AccordionTrigger } from "./AccordionTrigger.vue";
export { default as AccordionContent } from "./AccordionContent.vue";
` }}
</PreviewCode>
<SectionContent>
Update the import paths to match your project setup.
</SectionContent>
</div>
<div id="usage">
<SectionTitle>Usage</SectionTitle>
<PreviewCode>
{{ `
import {
AccordionRoot,
AccordionItem,
AccordionTrigger,
AccordionContent,
} from "@/components/ui/accordion";
import { Box } from "@/components/ui/box";
` }}
</PreviewCode>
<PreviewCode>
{{ `
<Box raised="single" class="w-full">
<AccordionRoot class="w-full" :default-value="['product-information']">
<AccordionItem value="product-information">
<AccordionTrigger>Product Information</AccordionTrigger>
<AccordionContent>
<p class="mb-2">
Our flagship product combines cutting-edge technology with
sleek design. Built with premium materials, it offers
unparalleled performance and reliability.
</p>
<p>
Key features include advanced processing capabilities, and an
intuitive user interface designed for both beginners and
experts.
</p>
</AccordionContent>
</AccordionItem>
<AccordionItem value="shipping-details">
<AccordionTrigger>Shipping Details</AccordionTrigger>
<AccordionContent>
<p class="mb-2">
We offer worldwide shipping through trusted courier partners.
Standard delivery takes 3-5 business days, while express
shipping ensures delivery within 1-2 business days.
</p>
<p>
All orders are carefully packaged and fully insured. Track
your shipment in real-time through our dedicated tracking
portal.
</p>
</AccordionContent>
</AccordionItem>
<AccordionItem value="return-policy">
<AccordionTrigger>Return Policy</AccordionTrigger>
<AccordionContent>
<p class="mb-2">
We stand behind our products with a comprehensive 30-day
return policy. If you're not completely satisfied, simply
return the item in its original condition.
</p>
<p>
Our hassle-free return process includes free return shipping
and full refunds processed within 48 hours of receiving the
returned item.
</p>
</AccordionContent>
</AccordionItem>
</AccordionRoot>
</Box>
` }}
</PreviewCode>
</div>
<div id="variants">
<SectionTitle>Variants</SectionTitle>
<SectionContent>A collection of components you can use.</SectionContent>
<Preview>
<template #preview>
<AccordionRoot class="w-full" variant="boxed" :default-value="['product-information']">
<AccordionItem raised="single" value="product-information">
<AccordionTrigger>Product Information</AccordionTrigger>
<AccordionContent>
<p class="mb-2">
Our flagship product combines cutting-edge technology with sleek
design. Built with premium materials, it offers unparalleled
performance and reliability.
</p>
<p>
Key features include advanced processing capabilities, and an
intuitive user interface designed for both beginners and
experts.
</p>
</AccordionContent>
</AccordionItem>
<AccordionItem raised="single" value="shipping-details">
<AccordionTrigger>Shipping Details</AccordionTrigger>
<AccordionContent>
<p class="mb-2">
We offer worldwide shipping through trusted courier partners.
Standard delivery takes 3-5 business days, while express
shipping ensures delivery within 1-2 business days.
</p>
<p>
All orders are carefully packaged and fully insured. Track your
shipment in real-time through our dedicated tracking portal.
</p>
</AccordionContent>
</AccordionItem>
<AccordionItem raised="single" value="return-policy">
<AccordionTrigger>Return Policy</AccordionTrigger>
<AccordionContent>
<p class="mb-2">
We stand behind our products with a comprehensive 30-day return
policy. If you're not completely satisfied, simply return the
item in its original condition.
</p>
<p>
Our hassle-free return process includes free return shipping and
full refunds processed within 48 hours of receiving the returned
item.
</p>
</AccordionContent>
</AccordionItem>
</AccordionRoot>
</template>
<template #code>
<PreviewCode>
{{ `
<AccordionRoot class="w-full" variant="boxed" :default-value="['product-information']">
<AccordionItem raised="single" value="product-information">
<AccordionTrigger>Product Information</AccordionTrigger>
<AccordionContent>
<p class="mb-2">
Our flagship product combines cutting-edge technology with sleek
design. Built with premium materials, it offers unparalleled
performance and reliability.
</p>
<p>
Key features include advanced processing capabilities, and an
intuitive user interface designed for both beginners and
experts.
</p>
</AccordionContent>
</AccordionItem>
<AccordionItem raised="single" value="shipping-details">
<AccordionTrigger>Shipping Details</AccordionTrigger>
<AccordionContent>
<p class="mb-2">
We offer worldwide shipping through trusted courier partners.
Standard delivery takes 3-5 business days, while express
shipping ensures delivery within 1-2 business days.
</p>
<p>
All orders are carefully packaged and fully insured. Track your
shipment in real-time through our dedicated tracking portal.
</p>
</AccordionContent>
</AccordionItem>
<AccordionItem raised="single" value="return-policy">
<AccordionTrigger>Return Policy</AccordionTrigger>
<AccordionContent>
<p class="mb-2">
We stand behind our products with a comprehensive 30-day return
policy. If you're not completely satisfied, simply return the
item in its original condition.
</p>
<p>
Our hassle-free return process includes free return shipping and
full refunds processed within 48 hours of receiving the returned
item.
</p>
</AccordionContent>
</AccordionItem>
</AccordionRoot>
` }}
</PreviewCode>
</template>
</Preview>
</div>
</template>
-197
View File
@@ -1,197 +0,0 @@
<script lang="ts" setup>
import { onMounted, ref, watch } from "vue";
import debounce from "lodash/debounce";
import axios from "axios";
import {
SectionTitle,
SectionContent,
InstallPackage,
} from "@/components/docs";
import { Badge } from "@/components/ui/badge";
import { Input } from "@/components/ui/input";
import DataTable from "@/components/ui/usage/DataTable.vue";
import type { SortConfig, TableHeader } from "@/components/ui/usage/DataTable.vue";
import { api } from "@/core/services/api";
import { useApiPagination } from "@/composables/useApiPagination";
import type { PaginatedApiResponse } from "@/core/types/api";
type UserRole = {
id: string;
name: string;
guard_name: string;
};
type UserListItem = {
id: string;
name: string;
email: string;
ic_number: string;
position: string;
phone_number: string;
image_url: string | null;
status: string;
roles: UserRole[];
};
const users = ref<UserListItem[]>([]);
const loading = ref(false);
const error = ref<string | null>(null);
const search = ref("");
const statusFilter = ref("");
const sortBy = ref<SortConfig[]>([{ key: "name", order: "asc" }]);
const page = ref(1);
const itemsPerPage = ref(10);
const { pagination, applyPagination } = useApiPagination({ per_page: 10 });
const headers: TableHeader[] = [
{ title: "Name", key: "name", sortable: true },
{ title: "Email", key: "email", sortable: true },
{ title: "Position", key: "position", sortable: true },
{
title: "Role",
key: "roles",
sortable: false,
exportValue: (item) =>
item.roles?.map((role: UserRole) => role.name).join(", ") || "-",
},
{ title: "Status", key: "status", sortable: true },
];
function statusBadgeVariant(status: string) {
if (status === "active") return "success";
if (status === "inactive") return "danger";
return "pending";
}
async function fetchUsers(requestPage = page.value) {
loading.value = true;
error.value = null;
try {
const activeSort = sortBy.value[0];
const { data } = await api.get<PaginatedApiResponse<UserListItem>>(
"/v1/users",
{
params: {
page: requestPage,
per_page: itemsPerPage.value,
sort_by: activeSort?.key ?? "name",
sort_order: activeSort?.order ?? "asc",
...(search.value.trim() ? { search: search.value.trim() } : {}),
...(statusFilter.value.trim()
? { status: statusFilter.value.trim() }
: {}),
},
}
);
if (!data.success) {
throw new Error(data.message ?? "Failed to load users");
}
users.value = data.data;
applyPagination(data.pagination);
page.value = data.pagination.current_page;
} catch (err) {
users.value = [];
if (axios.isAxiosError(err)) {
const message = (err.response?.data as { message?: string } | undefined)
?.message;
error.value = message ?? err.message;
} else if (err instanceof Error) {
error.value = err.message;
} else {
error.value = "Failed to load users";
}
applyPagination({
current_page: 1,
per_page: itemsPerPage.value,
total: 0,
last_page: 1,
from: null,
to: null,
has_more_pages: false,
});
page.value = 1;
} finally {
loading.value = false;
}
}
function handleSortUpdate(value: SortConfig[]) {
sortBy.value = value;
fetchUsers(1);
}
const debouncedSearch = debounce(() => {
fetchUsers(1);
}, 400);
watch(search, () => {
debouncedSearch();
});
watch(statusFilter, () => {
fetchUsers(1);
});
watch(page, (nextPage, previousPage) => {
if (nextPage !== previousPage) {
fetchUsers(nextPage);
}
});
watch(itemsPerPage, (nextValue, previousValue) => {
if (nextValue !== previousValue) {
fetchUsers(1);
}
});
onMounted(() => {
fetchUsers(1);
});
</script>
<template>
<div id="preview" class="-mt-20 w-full space-y-4">
<div class="flex flex-wrap items-center gap-3">
<Input v-model="search" placeholder="Search name, email, IC, phone, role..." class="max-w-96" />
<Input v-model="statusFilter" placeholder="Filter status (e.g. active)" class="max-w-56" />
</div>
<p v-if="error" class="text-sm text-danger">{{ error }}</p>
<DataTable :headers="headers" :items="users" :loading="loading" :pagination="pagination" :current-sort="sortBy"
show-pagination exportable export-file-name="users" v-model:page="page" v-model:items-per-page="itemsPerPage"
@update:sort-by="handleSortUpdate">
<template #item.name="{ item }">
<span class="font-medium">{{ item.name }}</span>
</template>
<template #item.email="{ item }">
<span class="lowercase">{{ item.email }}</span>
</template>
<template #item.roles="{ item }">
{{item.roles?.map((role: UserRole) => role.name).join(", ") || "-"}}
</template>
<template #item.status="{ item }">
<Badge :variant="statusBadgeVariant(item.status)" class="capitalize">
{{ item.status }}
</Badge>
</template>
</DataTable>
</div>
<div id="installation">
<SectionTitle>Installation</SectionTitle>
<SectionContent>Install the following dependencies:</SectionContent>
<InstallPackage class="mb-0">
add xlsx jspdf jspdf-autotable file-saver
</InstallPackage>
</div>
</template>
+8 -14
View File
@@ -100,7 +100,7 @@ import {
<DatePickerTableCell v-for="(month, id) in months" :key="id" :value="month.value">
<DatePickerTableCellTrigger>{{
month.label
}}</DatePickerTableCellTrigger>
}}</DatePickerTableCellTrigger>
</DatePickerTableCell>
</DatePickerTableRow>
</DatePickerTableBody>
@@ -124,7 +124,7 @@ import {
<DatePickerTableCell v-for="(year, id) in years" :key="id" :value="year.value">
<DatePickerTableCellTrigger>{{
year.label
}}</DatePickerTableCellTrigger>
}}</DatePickerTableCellTrigger>
</DatePickerTableCell>
</DatePickerTableRow>
</DatePickerTableBody>
@@ -522,10 +522,7 @@ const api = inject<Api>("datepickerApi");
{{ `
<script lang="ts" setup>
import { cn } from "@mykopkb/core/utils/cn";
import {
NativeSelect,
NativeSelectOption,
} from "@/components/ui/native-select";
import { datePickerYearSelect } from "@mykopkb/core/styles/datepicker.styles";
import type { Api } from "@zag-js/date-picker";
import { inject } from "vue";
@@ -557,10 +554,7 @@ const api = inject<Api>("datepickerApi");
<script lang="ts" setup>
import { cn } from "@mykopkb/core/utils/cn";
import { datePickerMonthSelect } from "@mykopkb/core/styles/datepicker.styles";
import {
NativeSelect,
NativeSelectOption,
} from "@/components/ui/native-select";
import type { Api } from "@zag-js/date-picker";
import { inject } from "vue";
@@ -1312,7 +1306,7 @@ const api = inject<Api>("datepickerApi");
<DatePickerTableCell v-for="(month, id) in months" :key="id" :value="month.value">
<DatePickerTableCellTrigger>{{
month.label
}}</DatePickerTableCellTrigger>
}}</DatePickerTableCellTrigger>
</DatePickerTableCell>
</DatePickerTableRow>
</DatePickerTableBody>
@@ -1336,7 +1330,7 @@ const api = inject<Api>("datepickerApi");
<DatePickerTableCell v-for="(year, id) in years" :key="id" :value="year.value">
<DatePickerTableCellTrigger>{{
year.label
}}</DatePickerTableCellTrigger>
}}</DatePickerTableCellTrigger>
</DatePickerTableCell>
</DatePickerTableRow>
</DatePickerTableBody>
@@ -1649,7 +1643,7 @@ const api = inject<Api>("datepickerApi");
<DatePickerTableCell v-for="(month, id) in months" :key="id" :value="month.value">
<DatePickerTableCellTrigger>{{
month.label
}}</DatePickerTableCellTrigger>
}}</DatePickerTableCellTrigger>
</DatePickerTableCell>
</DatePickerTableRow>
</DatePickerTableBody>
@@ -1673,7 +1667,7 @@ const api = inject<Api>("datepickerApi");
<DatePickerTableCell v-for="(year, id) in years" :key="id" :value="year.value">
<DatePickerTableCellTrigger>{{
year.label
}}</DatePickerTableCellTrigger>
}}</DatePickerTableCellTrigger>
</DatePickerTableCell>
</DatePickerTableRow>
</DatePickerTableBody>
-210
View File
@@ -1,210 +0,0 @@
<script lang="ts" setup>
import {
NativeSelect,
NativeSelectOption,
NativeSelectOptionGroup,
} from "@/components/ui/native-select";
import {
Preview,
SectionTitle,
SectionContent,
PreviewCode,
} from "@/components/docs";
</script>
<template>
<div id="preview" class="-mt-20">
<Preview>
<template #preview>
<NativeSelect class="w-56">
<NativeSelectOption value="">Select status</NativeSelectOption>
<NativeSelectOption value="todo">Todo</NativeSelectOption>
<NativeSelectOption value="in-progress">In Progress</NativeSelectOption>
<NativeSelectOption value="done">Done</NativeSelectOption>
<NativeSelectOption value="cancelled">Cancelled</NativeSelectOption>
</NativeSelect>
</template>
<template #code>
<PreviewCode>
{{ `
<NativeSelect class="w-56">
<NativeSelectOption value="">Select status</NativeSelectOption>
<NativeSelectOption value="todo">Todo</NativeSelectOption>
<NativeSelectOption value="in-progress">In Progress</NativeSelectOption>
<NativeSelectOption value="done">Done</NativeSelectOption>
<NativeSelectOption value="cancelled">Cancelled</NativeSelectOption>
</NativeSelect>
` }}
</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/native-select/NativeSelect.vue">
{{ `
<script lang="ts" setup>
import { cn } from "@mykopkb/core/utils/cn";
import { input } from "@mykopkb/core/styles/input.styles";
import { nativeSelect } from "@mykopkb/core/styles/native-select.styles";
const { class: className, ...props } = defineProps<{
class?: string;
}>();
</script>
<template>
<select :class="cn(input, nativeSelect, className)" v-bind="{ ...props, ...$attrs }">
<slot />
</select>
</template>
` }}
</PreviewCode>
<PreviewCode title="components/ui/native-select/NativeSelectOption.vue">
{{ `
<script lang="ts" setup>
import { cn } from "@mykopkb/core/utils/cn";
import { nativeSelectOption } from "@mykopkb/core/styles/native-select.styles";
const { class: className, ...props } = defineProps<{
class?: string;
}>();
</script>
<template>
<option :class="cn(nativeSelectOption, className)" v-bind="{ ...props, ...$attrs }">
<slot />
</option>
</template>
` }}
</PreviewCode>
<PreviewCode title="components/ui/native-select/NativeSelectOptionGroup.vue">
{{ `
<script lang="ts" setup>
import { cn } from "@mykopkb/core/utils/cn";
import { NativeSelectOptGroup } from "@mykopkb/core/styles/native-select.styles";
const { class: className, ...props } = defineProps<{
class?: string;
}>();
</script>
<template>
<optgroup :class="cn(NativeSelectOptGroup, className)" v-bind="{ ...props, ...$attrs }">
<slot />
</optgroup>
</template>
` }}
</PreviewCode>
<PreviewCode title="components/ui/native-select/index.ts">
{{ `
export { default as NativeSelect } from "./NativeSelect.vue";
export { default as NativeSelectOption } from "./NativeSelectOption.vue";
export { default as NativeSelectOptionGroup } from "./NativeSelectOptionGroup.vue";
` }}
</PreviewCode>
<SectionContent>
Update the import paths to match your project setup.
</SectionContent>
</div>
<div id="usage">
<SectionTitle>Usage</SectionTitle>
<PreviewCode>
{{ `
import {
NativeSelect,
NativeSelectOption,
NativeSelectOptionGroup,
} from "@/components/ui/native-select";
` }}
</PreviewCode>
<PreviewCode>
{{ `
<NativeSelect class="w-56">
<NativeSelectOption value="">Select status</NativeSelectOption>
<NativeSelectOption value="todo">Todo</NativeSelectOption>
<NativeSelectOption value="in-progress">In Progress</NativeSelectOption>
<NativeSelectOption value="done">Done</NativeSelectOption>
<NativeSelectOption value="cancelled">Cancelled</NativeSelectOption>
</NativeSelect>
` }}
</PreviewCode>
</div>
<div id="variants">
<SectionTitle>Variants</SectionTitle>
<SectionContent>A collection of components you can use.</SectionContent>
<Preview>
<template #preview>
<NativeSelect class="w-56">
<NativeSelectOption value="">Select department</NativeSelectOption>
<NativeSelectOptionGroup label="Engineering">
<NativeSelectOption value="frontend">Frontend</NativeSelectOption>
<NativeSelectOption value="backend">Backend</NativeSelectOption>
<NativeSelectOption value="devops">DevOps</NativeSelectOption>
</NativeSelectOptionGroup>
<NativeSelectOptionGroup label="Sales">
<NativeSelectOption value="sales-rep">Sales Rep</NativeSelectOption>
<NativeSelectOption value="account-manager">Account Manager</NativeSelectOption>
<NativeSelectOption value="sales-director">Sales Director</NativeSelectOption>
</NativeSelectOptionGroup>
<NativeSelectOptionGroup label="Operations">
<NativeSelectOption value="support">Customer Support</NativeSelectOption>
<NativeSelectOption value="product-manager">Product Manager</NativeSelectOption>
<NativeSelectOption value="ops-manager">Operations Manager</NativeSelectOption>
</NativeSelectOptionGroup>
</NativeSelect>
</template>
<template #code>
<PreviewCode>
{{ `
<NativeSelect class="w-56">
<NativeSelectOption value="">Select department</NativeSelectOption>
<NativeSelectOptionGroup label="Engineering">
<NativeSelectOption value="frontend">Frontend</NativeSelectOption>
<NativeSelectOption value="backend">Backend</NativeSelectOption>
<NativeSelectOption value="devops">DevOps</NativeSelectOption>
</NativeSelectOptionGroup>
<NativeSelectOptionGroup label="Sales">
<NativeSelectOption value="sales-rep">Sales Rep</NativeSelectOption>
<NativeSelectOption value="account-manager">Account Manager</NativeSelectOption>
<NativeSelectOption value="sales-director">Sales Director</NativeSelectOption>
</NativeSelectOptionGroup>
<NativeSelectOptionGroup label="Operations">
<NativeSelectOption value="support">Customer Support</NativeSelectOption>
<NativeSelectOption value="product-manager">Product Manager</NativeSelectOption>
<NativeSelectOption value="ops-manager">Operations Manager</NativeSelectOption>
</NativeSelectOptionGroup>
</NativeSelect>
` }}
</PreviewCode>
</template>
</Preview>
<Preview>
<template #preview>
<NativeSelect class="w-56" disabled>
<NativeSelectOption value="">Select status</NativeSelectOption>
<NativeSelectOption value="todo">Todo</NativeSelectOption>
<NativeSelectOption value="in-progress">In Progress</NativeSelectOption>
<NativeSelectOption value="done">Done</NativeSelectOption>
<NativeSelectOption value="cancelled">Cancelled</NativeSelectOption>
</NativeSelect>
</template>
<template #code>
<PreviewCode>
{{ `
<NativeSelect class="w-56" disabled>
<NativeSelectOption value="">Select status</NativeSelectOption>
<NativeSelectOption value="todo">Todo</NativeSelectOption>
<NativeSelectOption value="in-progress">In Progress</NativeSelectOption>
<NativeSelectOption value="done">Done</NativeSelectOption>
<NativeSelectOption value="cancelled">Cancelled</NativeSelectOption>
</NativeSelect>
` }}
</PreviewCode>
</template>
</Preview>
</div>
</template>