feat(home): mobile 1.5 card horizontal slider with scrollbar for featured products
Some checks failed
Deploy Canina / deploy (push) Successful in 2m21s
E2E Playwright Tests / Run Full E2E & Security Suites (push) Failing after 0s

This commit is contained in:
parsa aghaei 2026-09-06 11:25:21 +03:30
parent 881f75a372
commit 01a538ca5a
3 changed files with 661 additions and 653 deletions

View File

@ -52,16 +52,16 @@ function ProductCard({ product, priority = false }: { product: Product; priority
viewport={{ once: true }}
transition={{ duration: 0.3, ease: "easeOut" }}
onClick={() => router.push(`/shop/${product.slug || product.id}`)}
className="group bg-white/90 backdrop-blur-md rounded-2xl sm:rounded-3xl border border-medical-gray-200/80 overflow-hidden hover:shadow-xl hover:shadow-canina-blue/10 hover:border-canina-blue/30 transition-all duration-300 flex flex-col cursor-pointer relative"
className="group bg-white/90 backdrop-blur-md rounded-2xl sm:rounded-3xl border border-medical-gray-200/80 overflow-hidden hover:shadow-xl hover:shadow-canina-blue/10 hover:border-canina-blue/30 transition-all duration-300 flex flex-col cursor-pointer relative w-full"
>
{/* Category Badge */}
<div className="absolute top-3 right-3 sm:top-4 sm:right-4 z-30 px-2.5 py-1 bg-white/90 backdrop-blur-md rounded-full border border-medical-gray-200/80 text-2xs sm:text-xs font-black text-medical-gray-700 uppercase tracking-wider shadow-xs group-hover:bg-canina-blue group-hover:text-white transition-all">
<div className="absolute top-2.5 right-2.5 sm:top-4 sm:right-4 z-30 px-2 py-0.5 sm:px-2.5 sm:py-1 bg-white/90 backdrop-blur-md rounded-full border border-medical-gray-200/80 text-[10px] sm:text-xs font-black text-medical-gray-700 uppercase tracking-wider shadow-xs group-hover:bg-canina-blue group-hover:text-white transition-all">
{product.category}
</div>
{/* Compatibility Tag */}
{compatibility && (
<div className={`absolute top-[2.8rem] sm:top-[3.25rem] right-3 sm:right-4 z-10 text-3xs font-black uppercase tracking-widest px-2.5 py-0.5 rounded-full shadow-xs flex items-center gap-1 ${
<div className={`absolute top-[2.4rem] sm:top-[3.25rem] right-2.5 sm:right-4 z-10 text-[9px] sm:text-3xs font-black uppercase tracking-widest px-2 py-0.5 sm:px-2.5 sm:py-0.5 rounded-full shadow-xs flex items-center gap-1 ${
compatibility.type === "alert" ? "bg-amber-100 text-amber-700" :
compatibility.type === "success" ? "bg-green-100 text-green-700" : "bg-medical-gray-100 text-medical-gray-600"
}`}>
@ -88,26 +88,26 @@ function ProductCard({ product, priority = false }: { product: Product; priority
)}
{/* Image Container */}
<div className="relative aspect-[16/11] sm:aspect-[4/3] bg-medical-gray-50 flex items-center justify-center overflow-hidden p-1 sm:p-2">
<div className="relative aspect-[16/11] sm:aspect-[4/3] bg-medical-gray-50 flex items-center justify-center overflow-hidden p-2 sm:p-3">
<SafeImage
src={product.image}
alt={nameFa}
priority={priority}
sizes="(max-width: 640px) 180px, (max-width: 1024px) 240px, 280px)"
sizes="(max-width: 640px) 240px, (max-width: 1024px) 240px, 280px)"
className="w-full h-full group-hover:scale-105 transition-transform duration-500"
imgClassName="object-contain w-full h-full max-h-[160px] sm:max-h-[200px] mx-auto"
imgClassName="object-contain w-full h-full max-h-[130px] sm:max-h-[200px] mx-auto"
/>
{/* Species Badges (Dog / Cat) */}
<div className="absolute bottom-2 left-2 flex gap-1 z-10">
{forDog && (
<div className="w-6 h-6 bg-white/90 backdrop-blur-xs rounded-md flex items-center justify-center text-medical-gray-500 border border-medical-gray-200 shadow-2xs" title="مناسب برای سگ">
<Dog className="w-3.5 h-3.5 text-canina-blue" />
<div className="w-5 h-5 sm:w-6 sm:h-6 bg-white/90 backdrop-blur-xs rounded-md flex items-center justify-center text-medical-gray-500 border border-medical-gray-200 shadow-2xs" title="مناسب برای سگ">
<Dog className="w-3 h-3 sm:w-3.5 sm:h-3.5 text-canina-blue" />
</div>
)}
{forCat && (
<div className="w-6 h-6 bg-white/90 backdrop-blur-xs rounded-md flex items-center justify-center text-medical-gray-500 border border-medical-gray-200 shadow-2xs" title="مناسب برای گربه">
<Cat className="w-3.5 h-3.5 text-canina-blue" />
<div className="w-5 h-5 sm:w-6 sm:h-6 bg-white/90 backdrop-blur-xs rounded-md flex items-center justify-center text-medical-gray-500 border border-medical-gray-200 shadow-2xs" title="مناسب برای گربه">
<Cat className="w-3 h-3 sm:w-3.5 sm:h-3.5 text-canina-blue" />
</div>
)}
</div>
@ -137,35 +137,35 @@ function ProductCard({ product, priority = false }: { product: Product; priority
</div>
{/* Content */}
<div className="px-3.5 py-3 sm:px-5 sm:py-4 flex flex-col flex-1 gap-2 sm:gap-3">
<div className="p-3 sm:px-5 sm:py-4 flex flex-col flex-1 gap-1.5 sm:gap-3">
{/* Dual-language Name */}
<div className="flex flex-col gap-0.5">
<h3 dir="rtl" className="text-xs sm:text-sm font-black text-medical-gray-900 group-hover:text-canina-blue transition-colors leading-snug text-right line-clamp-1 sm:line-clamp-2 font-vazir">
{nameFa}
</h3>
{nameEn && (
<span dir="ltr" className="text-2xs sm:text-xs-plus font-semibold text-slate-400 font-sans text-left block line-clamp-1">
<span dir="ltr" className="text-[10px] sm:text-xs-plus font-semibold text-slate-400 font-sans text-left block line-clamp-1">
{nameEn}
</span>
)}
</div>
{/* Short description */}
<p className="text-xs-plus sm:text-xs text-medical-gray-500 leading-relaxed line-clamp-1 sm:line-clamp-2 flex-1">
<p className="text-[11px] sm:text-xs text-medical-gray-500 leading-relaxed line-clamp-2 flex-1">
{product.shortDescription || (product.description ? product.description.replace(/<[^>]*>/g, '') : '')}
</p>
{/* Footer: Price + CTA */}
<div className="flex items-center justify-between pt-2 sm:pt-3 border-t border-medical-gray-100">
<div className="flex items-center justify-between pt-2 sm:pt-3 border-t border-medical-gray-100 mt-auto">
<div className="text-xs sm:text-sm md:text-base font-black text-medical-gray-900 font-vazir whitespace-nowrap">
{showPrices ? (typeof product.price === 'string' || typeof product.price === 'number' ? String(product.price) : 'تماس بگیرید') : 'تماس بگیرید'}
</div>
{showPreorderBtn ? (
<span className="text-2xs sm:text-xs-plus font-black text-amber-600 bg-amber-50 px-2.5 py-1 rounded-full border border-amber-200">
<span className="text-[10px] sm:text-xs-plus font-black text-amber-600 bg-amber-50 px-2 py-0.5 sm:px-2.5 sm:py-1 rounded-full border border-amber-200">
ثبت پیشخرید
</span>
) : (
<span className="text-2xs sm:text-xs-plus font-black text-canina-blue uppercase tracking-wider border-b border-canina-blue pb-0.5 group-hover:text-medical-gray-900 group-hover:border-medical-gray-900 transition-all whitespace-nowrap">
<span className="text-[10px] sm:text-xs-plus font-black text-canina-blue uppercase tracking-wider border-b border-canina-blue pb-0.5 group-hover:text-medical-gray-900 group-hover:border-medical-gray-900 transition-all whitespace-nowrap">
{getText("product_view_details", "مشاهده جزئیات")}
</span>
)}
@ -210,13 +210,21 @@ export default function FeaturedProducts() {
</button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<div className="flex md:grid md:grid-cols-2 lg:grid-cols-4 gap-4 md:gap-6 overflow-x-auto md:overflow-visible pb-4 md:pb-0 micro-hscroll -mx-4 px-4 md:mx-0 md:px-0 select-none touch-pan-x snap-x snap-mandatory md:snap-none">
{isLoading ? (
Array.from({ length: 4 }).map((_, i) => <ProductCardSkeleton key={i} />)
Array.from({ length: 4 }).map((_, i) => (
<div key={i} className="w-[68vw] sm:w-[50vw] md:w-auto flex-shrink-0 snap-start">
<ProductCardSkeleton />
</div>
))
) : products.length > 0 ? (
products.map((p, i) => <ProductCard key={p.id} product={p} priority={i < 2} />)
products.map((p, i) => (
<div key={p.id} className="w-[68vw] sm:w-[50vw] md:w-auto flex-shrink-0 snap-start flex">
<ProductCard product={p} priority={i < 2} />
</div>
))
) : (
<div className="col-span-full py-16 px-8 flex flex-col items-center justify-center text-center bg-white rounded-[2.5rem] border border-medical-gray-200 shadow-xl max-w-lg mx-auto">
<div className="col-span-full py-16 px-8 flex flex-col items-center justify-center text-center bg-white rounded-[2.5rem] border border-medical-gray-200 shadow-xl max-w-lg mx-auto w-full">
<AlertCircle className="w-12 h-12 text-canina-teal mb-4" />
<h3 className="text-xl font-black text-medical-gray-900 mb-2 font-vazir">
{getText("featured_no_products_title", "هیچ محصولی یافت نشد")}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff