ifixkart-storefront/components/ui/Skeleton.tsx

345 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React from 'react';
import { cn } from '@/lib/utils';
/** Base pulsing block */
export function Skeleton({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn('animate-pulse rounded-md bg-gray-200/80', className)}
{...props}
/>
);
}
export function SkeletonText({
lines = 3,
className,
}: {
lines?: number;
className?: string;
}) {
return (
<div className={cn('space-y-2', className)}>
{Array.from({ length: lines }).map((_, i) => (
<Skeleton
key={i}
className={cn('h-3', i === lines - 1 ? 'w-2/3' : 'w-full')}
/>
))}
</div>
);
}
/** Product card placeholder */
export function ProductCardSkeleton({ className }: { className?: string }) {
return (
<div className={cn('p-4 md:p-5 flex flex-col min-h-[340px]', className)}>
<Skeleton className="w-full h-[150px] mb-4 rounded-lg" />
<Skeleton className="h-4 w-4/5 mb-2" />
<Skeleton className="h-4 w-3/5 mb-3" />
<Skeleton className="h-3 w-24 mb-2" />
<Skeleton className="h-4 w-20 mt-auto" />
</div>
);
}
/** Horizontal featured mini-card */
export function FeaturedCardSkeleton() {
return (
<div className="flex items-center gap-3 p-3 border border-gray-100 rounded-md h-[122px]">
<Skeleton className="w-[76px] h-[76px] shrink-0 rounded" />
<div className="flex-1 space-y-2">
<Skeleton className="h-3.5 w-4/5" />
<Skeleton className="h-3 w-20" />
<Skeleton className="h-3.5 w-16" />
<Skeleton className="h-2.5 w-3/4" />
</div>
</div>
);
}
/** Section header with title + action */
export function SectionHeaderSkeleton() {
return (
<div className="flex items-center justify-between pb-3.5 border-b border-[#e5e5e5] mb-5">
<Skeleton className="h-5 w-44" />
<Skeleton className="h-4 w-20" />
</div>
);
}
/** Brand logo tile */
export function BrandTileSkeleton() {
return <Skeleton className="h-[72px] md:h-[80px] rounded-lg" />;
}
/** Category circle */
export function CategorySkeleton() {
return (
<div className="flex flex-col items-center gap-2">
<Skeleton className="w-16 h-16 rounded-full" />
<Skeleton className="h-3 w-14" />
</div>
);
}
/** Promo banner pair */
export function PromoBannerSkeleton() {
return (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 md:gap-5">
{[0, 1].map((i) => (
<Skeleton key={i} className="min-h-[200px] md:min-h-[220px] rounded-xl" />
))}
</div>
);
}
/** Shop / catalog grid with sidebar */
export function CatalogSkeleton() {
return (
<div className="container mx-auto px-4 sm:px-6 lg:px-8 py-6">
<div className="flex gap-6">
<div className="hidden lg:block w-[240px] shrink-0 space-y-4">
<Skeleton className="h-6 w-32 mb-2" />
{Array.from({ length: 8 }).map((_, i) => (
<Skeleton key={i} className="h-4 w-full" />
))}
<Skeleton className="h-6 w-28 mt-6 mb-2" />
{Array.from({ length: 5 }).map((_, i) => (
<Skeleton key={i} className="h-4 w-3/4" />
))}
</div>
<div className="flex-1 min-w-0">
<div className="flex justify-between mb-4">
<Skeleton className="h-5 w-40" />
<Skeleton className="h-9 w-36 rounded" />
</div>
<div className="grid grid-cols-2 md:grid-cols-3 xl:grid-cols-4 border border-[#e8e8e8]">
{Array.from({ length: 8 }).map((_, i) => (
<div
key={i}
className="border-r border-b border-[#e8e8e8] last:border-r-0"
>
<ProductCardSkeleton />
</div>
))}
</div>
</div>
</div>
</div>
);
}
/** PDP skeleton */
export function ProductDetailSkeleton() {
return (
<div className="container mx-auto px-4 sm:px-6 lg:px-8 py-8">
<Skeleton className="h-4 w-48 mb-6" />
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8 lg:gap-10">
<div className="lg:col-span-6 flex gap-3">
<div className="hidden md:flex flex-col gap-2">
{[1, 2, 3, 4].map((i) => (
<Skeleton key={i} className="w-16 h-16 rounded-lg" />
))}
</div>
<Skeleton className="flex-1 h-[380px] md:h-[420px] rounded-xl" />
</div>
<div className="lg:col-span-6 space-y-4">
<Skeleton className="h-4 w-24" />
<Skeleton className="h-8 w-4/5" />
<Skeleton className="h-4 w-32" />
<Skeleton className="h-10 w-40" />
<Skeleton className="h-20 w-full rounded-lg" />
<div className="flex gap-3 pt-2">
<Skeleton className="h-12 w-28 rounded-md" />
<Skeleton className="h-12 flex-1 rounded-md" />
</div>
</div>
</div>
<Skeleton className="h-10 w-full max-w-lg mt-10 mb-4" />
<Skeleton className="h-48 w-full rounded-lg" />
</div>
);
}
/** Cart / checkout style page */
export function CartPageSkeleton() {
return (
<div className="container mx-auto px-4 sm:px-6 lg:px-8 py-8">
<Skeleton className="h-7 w-48 mb-6" />
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6">
<div className="lg:col-span-8 space-y-3">
{Array.from({ length: 3 }).map((_, i) => (
<div
key={i}
className="flex gap-4 p-4 bg-white border border-gray-100 rounded-lg"
>
<Skeleton className="w-20 h-20 rounded-md shrink-0" />
<div className="flex-1 space-y-2">
<Skeleton className="h-4 w-3/4" />
<Skeleton className="h-3 w-24" />
<Skeleton className="h-8 w-28" />
</div>
<Skeleton className="h-5 w-16" />
</div>
))}
</div>
<div className="lg:col-span-4">
<div className="bg-white border border-gray-100 rounded-lg p-5 space-y-3">
<Skeleton className="h-5 w-32" />
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-full" />
<Skeleton className="h-10 w-full rounded-md mt-4" />
</div>
</div>
</div>
</div>
);
}
/** Full homepage skeleton matching storefront sections */
export function HomePageSkeleton() {
return (
<div className="w-full min-h-screen bg-white">
{/* Announcement */}
<Skeleton className="h-8 w-full rounded-none" />
{/* Header */}
<div className="border-b border-gray-100">
<div className="container mx-auto px-4 sm:px-6 lg:px-8 py-4 flex items-center justify-between gap-4">
<Skeleton className="h-8 w-28" />
<Skeleton className="hidden md:block h-10 flex-1 max-w-xl rounded-md" />
<div className="flex gap-3">
<Skeleton className="h-8 w-8 rounded-full" />
<Skeleton className="h-8 w-8 rounded-full" />
<Skeleton className="h-8 w-8 rounded-full" />
</div>
</div>
</div>
{/* Nav */}
<div className="border-b border-gray-100">
<div className="container mx-auto px-4 sm:px-6 lg:px-8 py-3 flex gap-4">
<Skeleton className="h-9 w-36 rounded-md" />
{Array.from({ length: 5 }).map((_, i) => (
<Skeleton key={i} className="hidden md:block h-4 w-16 self-center" />
))}
</div>
</div>
<div className="container mx-auto px-4 sm:px-6 lg:px-8 space-y-8 py-6">
{/* Hero */}
<Skeleton className="w-full h-[220px] md:h-[360px] rounded-xl" />
{/* Promo cards ×3 */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{[0, 1, 2].map((i) => (
<Skeleton key={i} className="h-[200px] rounded-2xl" />
))}
</div>
{/* Categories */}
<div>
<SectionHeaderSkeleton />
<div className="flex gap-6 overflow-hidden">
{Array.from({ length: 8 }).map((_, i) => (
<CategorySkeleton key={i} />
))}
</div>
</div>
{/* New arrivals */}
<div>
<SectionHeaderSkeleton />
<div className="border border-[#e5e5e5] grid grid-cols-2 md:grid-cols-4 lg:grid-cols-5">
{Array.from({ length: 5 }).map((_, i) => (
<div
key={i}
className="border-r border-[#e5e5e5] last:border-r-0"
>
<ProductCardSkeleton />
</div>
))}
</div>
</div>
{/* Promo banners */}
<PromoBannerSkeleton />
{/* Hot deals */}
<div className="flex flex-col lg:flex-row gap-5">
<Skeleton className="w-full lg:w-[310px] h-[380px] rounded-md shrink-0" />
<div className="flex-1">
<SectionHeaderSkeleton />
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{Array.from({ length: 6 }).map((_, i) => (
<FeaturedCardSkeleton key={i} />
))}
</div>
</div>
</div>
{/* Brands */}
<div>
<SectionHeaderSkeleton />
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-3 md:gap-4">
{Array.from({ length: 10 }).map((_, i) => (
<BrandTileSkeleton key={i} />
))}
</div>
</div>
{/* Features */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3 md:gap-4">
{Array.from({ length: 4 }).map((_, i) => (
<Skeleton key={i} className="h-[88px] rounded-lg" />
))}
</div>
</div>
</div>
);
}
/** Generic content page skeleton */
export function PageSkeleton({
variant = 'home',
}: {
variant?: 'home' | 'catalog' | 'product' | 'cart' | 'simple';
}) {
if (variant === 'catalog') {
return (
<div className="min-h-screen bg-white pt-4">
<CatalogSkeleton />
</div>
);
}
if (variant === 'product') {
return (
<div className="min-h-screen bg-white">
<ProductDetailSkeleton />
</div>
);
}
if (variant === 'cart') {
return (
<div className="min-h-screen bg-[#f4f7fe]">
<CartPageSkeleton />
</div>
);
}
if (variant === 'simple') {
return (
<div className="min-h-screen bg-white">
<div className="container mx-auto px-4 py-10 space-y-4">
<Skeleton className="h-8 w-56" />
<Skeleton className="h-4 w-96 max-w-full" />
<Skeleton className="h-64 w-full rounded-xl mt-6" />
</div>
</div>
);
}
return <HomePageSkeleton />;
}