Dev/v1.2 (#4)
Co-authored-by: ISMAIL MASSERAN <topaz@Mac.dlinkrouter.local> Reviewed-on: #4
This commit was merged in pull request #4.
This commit is contained in:
@@ -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']
|
||||
|
||||
|
||||
@@ -7,30 +7,28 @@ 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 props = defineProps<Partial<Props> & { class?: string; asChild?: boolean }>();
|
||||
|
||||
const service = useMachine(pagination.machine, {
|
||||
...props,
|
||||
count,
|
||||
pageSize,
|
||||
siblingCount,
|
||||
id: crypto.randomUUID(),
|
||||
const paginationId = crypto.randomUUID();
|
||||
|
||||
const machineProps = computed(() => {
|
||||
const { class: _class, asChild: _asChild, id: _id, ...rest } = props;
|
||||
|
||||
return {
|
||||
...rest,
|
||||
id: paginationId,
|
||||
};
|
||||
});
|
||||
|
||||
const service = useMachine(pagination.machine, machineProps);
|
||||
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" />
|
||||
<Slot :class="cn(paginationRoot, props.class)" v-bind="{ ...$attrs, ...api.getRootProps() }">
|
||||
<slot v-if="props.asChild" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export const paginationRoot = 'flex gap-1'
|
||||
export const paginationItem = [
|
||||
'h-10 px-4 py-2 inline-flex items-center justify-center rounded-xl cursor-pointer hover:bg-foreground/5',
|
||||
'data-[selected]:border data-[selected]:bg-background data-[selected]:border-foreground/10 data-[selected]:font-medium data-[selected]:shadow-md/5',
|
||||
'h-10 min-w-10 px-3 py-2 inline-flex items-center justify-center rounded-xl cursor-pointer text-foreground hover:bg-foreground/5',
|
||||
'data-[selected]:border data-[selected]:bg-primary/10 data-[selected]:border-primary/30 data-[selected]:text-primary data-[selected]:font-semibold data-[selected]:shadow-md/5',
|
||||
'data-[disabled]:opacity-70',
|
||||
]
|
||||
export const paginationPrevTrigger = paginationItem
|
||||
|
||||
@@ -94,25 +94,29 @@ const props = withDefaults(defineProps<{
|
||||
items: TableItem[];
|
||||
loading?: boolean;
|
||||
itemsPerPage?: number;
|
||||
page?: number;
|
||||
itemKey?: string;
|
||||
pagination?: PaginationData | null;
|
||||
showPagination?: boolean;
|
||||
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: () => [],
|
||||
loading: false,
|
||||
itemsPerPage: 10,
|
||||
page: 1,
|
||||
itemKey: 'id',
|
||||
pagination: null,
|
||||
showPagination: false,
|
||||
currentSort: () => [],
|
||||
exportable: false,
|
||||
exportFileName: 'table-data'
|
||||
exportFileName: 'table-data',
|
||||
scrollable: true,
|
||||
maxHeight: 'min(70dvh, 640px)',
|
||||
});
|
||||
|
||||
// Define model props
|
||||
@@ -122,7 +126,6 @@ const itemsPerPageModel = defineModel<number>('items-per-page');
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
'update:sort-by': [value: SortConfig[]];
|
||||
'update:page': [value: number];
|
||||
'update:items-per-page': [value: number];
|
||||
'update:column-widths': [value: Record<string, number>];
|
||||
'export-error': [error: Error];
|
||||
@@ -449,7 +452,6 @@ const setItemsPerPageValue = (details: { value: string[] }) => {
|
||||
|
||||
const handlePageChange = (details: { page: number }) => {
|
||||
pageModel.value = details.page;
|
||||
emit('update:page', details.page);
|
||||
};
|
||||
|
||||
const getItemKey = (item: TableItem, index: number) => {
|
||||
@@ -461,16 +463,18 @@ const currentPage = computed(
|
||||
() => pageModel.value ?? props.pagination?.current_page ?? 1,
|
||||
);
|
||||
|
||||
const isFirstPage = computed(() => currentPage.value <= 1);
|
||||
|
||||
const isLastPage = computed(
|
||||
() => currentPage.value >= (props.pagination?.last_page ?? 1),
|
||||
);
|
||||
|
||||
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;
|
||||
|
||||
@@ -743,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"
|
||||
@@ -851,28 +863,21 @@ watch(sortBy, (newSort) => {
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<PaginationRoot class="flex items-center gap-2" :count="pagination.total" :pageSize="pagination.per_page"
|
||||
:page="currentPage" :siblingCount="1" :onPageChange="handlePageChange">
|
||||
<PaginationPrevTrigger asChild>
|
||||
<Button size="sm" look="outline" variant="secondary" :disabled="isFirstPage">
|
||||
<ArrowLeft class="size-4" />
|
||||
</Button>
|
||||
<PaginationRoot class="flex items-center gap-2" :count="pagination.total" :page-size="pagination.per_page"
|
||||
:page="currentPage" :sibling-count="1" :on-page-change="handlePageChange">
|
||||
<PaginationPrevTrigger>
|
||||
<ArrowLeft class="size-4" />
|
||||
</PaginationPrevTrigger>
|
||||
<PaginationContext v-slot="{ pagination: paginationApi }">
|
||||
<template v-for="(pageItem, index) in paginationApi?.pages" :key="index">
|
||||
<PaginationItem v-if="pageItem.type === 'page'" v-bind="{ ...pageItem }" asChild>
|
||||
<Button size="sm" :look="pageItem.value === currentPage ? 'filled' : 'outline'"
|
||||
:variant="pageItem.value === currentPage ? 'primary' : 'secondary'">
|
||||
{{ pageItem.value }}
|
||||
</Button>
|
||||
<PaginationItem v-if="pageItem.type === 'page'" v-bind="{ ...pageItem }">
|
||||
{{ pageItem.value }}
|
||||
</PaginationItem>
|
||||
<PaginationEllipsis v-else :index="index" />
|
||||
</template>
|
||||
</PaginationContext>
|
||||
<PaginationNextTrigger asChild>
|
||||
<Button size="sm" look="outline" variant="secondary" :disabled="isLastPage">
|
||||
<ArrowRight class="size-4" />
|
||||
</Button>
|
||||
<PaginationNextTrigger>
|
||||
<ArrowRight class="size-4" />
|
||||
</PaginationNextTrigger>
|
||||
</PaginationRoot>
|
||||
</div>
|
||||
@@ -881,8 +886,8 @@ watch(sortBy, (newSort) => {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.custom-data-table {
|
||||
overflow: hidden;
|
||||
.data-table-scroll-area > :deep(div) {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.sortable-indicator {
|
||||
|
||||
Reference in New Issue
Block a user