feat: add loading screens, skeleton states, and table loaders across all list pages
deploy-chabokan / deploy (push) Canceled after 0s
deploy-chabokan / deploy (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
type: {
|
||||
type: String,
|
||||
default: 'table' // 'table' | 'cards' | 'split' | 'spinner'
|
||||
},
|
||||
rows: {
|
||||
type: Number,
|
||||
default: 6
|
||||
},
|
||||
hasImage: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
showHeader: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
message: {
|
||||
type: String,
|
||||
default: 'در حال بارگذاری اطلاعات...'
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="list-loading-container w-full animate-pulse">
|
||||
<!-- Optional Top Header Shimmer -->
|
||||
<div v-if="showHeader" class="flex flex-wrap items-center justify-between gap-4 mb-4">
|
||||
<Skeleton width="12rem" height="2rem" class="rounded-xl" />
|
||||
<Skeleton width="16rem" height="2.5rem" class="rounded-xl" />
|
||||
</div>
|
||||
|
||||
<!-- ── TABLE SKELETON ── -->
|
||||
<div
|
||||
v-if="type === 'table'"
|
||||
class="rounded-2xl border border-surface-200 dark:border-surface-700 overflow-hidden bg-surface-0 dark:bg-surface-900 shadow-sm"
|
||||
>
|
||||
<!-- Table Header Bar -->
|
||||
<div class="p-4 bg-surface-50 dark:bg-surface-800/60 border-b border-surface-200 dark:border-surface-700 flex flex-wrap items-center justify-between gap-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<Skeleton width="2rem" height="2rem" class="rounded-lg" />
|
||||
<Skeleton width="10rem" height="1.5rem" class="rounded-lg" />
|
||||
</div>
|
||||
<Skeleton width="14rem" height="2.25rem" class="rounded-xl" />
|
||||
</div>
|
||||
|
||||
<!-- Table Rows -->
|
||||
<div class="divide-y divide-surface-100 dark:divide-surface-800">
|
||||
<div v-for="r in rows" :key="r" class="p-4 flex items-center gap-4">
|
||||
<Skeleton width="1.75rem" height="1.75rem" class="rounded-md shrink-0" />
|
||||
<Skeleton v-if="hasImage" width="3.5rem" height="3.5rem" class="rounded-xl shrink-0" />
|
||||
<div class="flex-1 grid grid-cols-2 sm:grid-cols-4 gap-4 items-center">
|
||||
<Skeleton width="75%" height="1.25rem" class="rounded-md" />
|
||||
<Skeleton width="55%" height="1.25rem" class="rounded-md" />
|
||||
<Skeleton width="65%" height="1.25rem" class="rounded-md hidden sm:block" />
|
||||
<Skeleton width="45%" height="1.25rem" class="rounded-md hidden sm:block" />
|
||||
</div>
|
||||
<div class="flex items-center gap-2 shrink-0">
|
||||
<Skeleton width="2.25rem" height="2.25rem" class="rounded-full" />
|
||||
<Skeleton width="2.25rem" height="2.25rem" class="rounded-full" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── CARDS GRID SKELETON ── -->
|
||||
<div v-else-if="type === 'cards'" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||||
<div
|
||||
v-for="c in rows"
|
||||
:key="c"
|
||||
class="card p-4 rounded-2xl border border-surface-200 dark:border-surface-700 bg-surface-0 dark:bg-surface-900 shadow-sm flex flex-col gap-3"
|
||||
>
|
||||
<Skeleton width="100%" height="12rem" class="rounded-xl" />
|
||||
<div class="flex items-center justify-between gap-2 mt-1">
|
||||
<Skeleton width="60%" height="1.5rem" class="rounded-md" />
|
||||
<Skeleton width="25%" height="1.25rem" class="rounded-full" />
|
||||
</div>
|
||||
<Skeleton width="40%" height="1rem" class="rounded-md" />
|
||||
<div class="flex items-center justify-between pt-3 border-t border-surface-100 dark:border-surface-800 mt-auto">
|
||||
<Skeleton width="30%" height="1.25rem" class="rounded-md" />
|
||||
<Skeleton width="2rem" height="2rem" class="rounded-lg" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── SPLIT VIEW SKELETON (Cards + Map Placeholder) ── -->
|
||||
<div v-else-if="type === 'split'" class="grid grid-cols-1 lg:grid-cols-12 gap-6 h-[750px]">
|
||||
<!-- Left Cards list skeleton -->
|
||||
<div class="lg:col-span-7 flex flex-col gap-4 overflow-hidden">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<Skeleton width="10rem" height="1.5rem" class="rounded-md" />
|
||||
<Skeleton width="6rem" height="1.25rem" class="rounded-md" />
|
||||
</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div
|
||||
v-for="i in 4"
|
||||
:key="i"
|
||||
class="p-4 rounded-2xl border border-surface-200 dark:border-surface-700 bg-surface-0 dark:bg-surface-900 shadow-sm flex flex-col gap-3"
|
||||
>
|
||||
<Skeleton width="100%" height="10rem" class="rounded-xl" />
|
||||
<Skeleton width="70%" height="1.25rem" class="rounded-md" />
|
||||
<Skeleton width="45%" height="1rem" class="rounded-md" />
|
||||
<div class="flex justify-between items-center pt-2 border-t border-surface-100 dark:border-surface-800">
|
||||
<Skeleton width="35%" height="1rem" class="rounded-md" />
|
||||
<Skeleton width="2rem" height="2rem" class="rounded-lg" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Right Map skeleton -->
|
||||
<div class="lg:col-span-5 h-full rounded-2xl border border-surface-200 dark:border-surface-700 overflow-hidden bg-surface-100 dark:bg-surface-800 flex flex-col items-center justify-center p-8 text-center">
|
||||
<i class="pi pi-map text-5xl text-surface-400 dark:text-surface-600 mb-4 animate-bounce"></i>
|
||||
<Skeleton width="12rem" height="1.5rem" class="rounded-md mb-2" />
|
||||
<Skeleton width="8rem" height="1rem" class="rounded-md" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── SPINNER / OVERLAY SKELETON ── -->
|
||||
<div v-else class="flex flex-col items-center justify-center py-16 gap-4">
|
||||
<ProgressSpinner style="width: 48px; height: 48px" strokeWidth="4" />
|
||||
<span class="text-sm font-medium text-surface-500 dark:text-surface-400">{{ message }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user