feat: add loading screens, skeleton states, and table loaders across all list pages
deploy-chabokan / deploy (push) Canceled after 0s

This commit is contained in:
2026-08-19 06:02:20 +03:30
parent 859a86f1a5
commit b6fde3e541
10 changed files with 298 additions and 16 deletions
+125
View File
@@ -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>