Add replaceable placeholder images with unique img src paths.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-13 14:11:30 +03:30
co-authored by Cursor
parent 141f1a30be
commit 1272977923
53 changed files with 302 additions and 19 deletions
+2
View File
@@ -44,6 +44,8 @@ export function AboutSanihome() {
</div>
</div>
<Placeholder
src="/images/about.svg"
alt="درباره سانی‌هوم"
width="100%"
height="360px"
label="560×360"
+2
View File
@@ -19,6 +19,8 @@ export function BlogCarousel() {
className="w-[280px] shrink-0 overflow-hidden rounded-xl border border-border bg-surface transition-shadow hover:shadow-md md:w-[300px]"
>
<Placeholder
src={post.image}
alt={post.title}
width="100%"
height="160px"
label="300×160"
@@ -20,6 +20,8 @@ export function FeaturedCategories() {
style={{ animationDelay: `${i * 60}ms` }}
>
<Placeholder
src={cat.image}
alt={cat.name}
width="72px"
height="72px"
label="72×72"
+8
View File
@@ -7,6 +7,11 @@ import { sliderSlides } from "@/data/sample";
export function HeroSlider() {
const [active, setActive] = useState(1);
const getIndex = (offset: number) => {
const len = sliderSlides.length;
return (active + offset + len) % len;
};
const positions = [
{ offset: -1, scale: "scale-[0.92]", opacity: "opacity-55", z: "z-10" },
{ offset: 0, scale: "scale-100", opacity: "opacity-100", z: "z-20" },
@@ -19,6 +24,7 @@ export function HeroSlider() {
<div className="relative h-[220px] sm:h-[280px] md:h-[340px] lg:h-[380px]">
{positions.map(({ offset, scale, opacity, z }) => {
const isCenter = offset === 0;
const slide = sliderSlides[getIndex(offset)];
return (
<div
@@ -33,6 +39,8 @@ export function HeroSlider() {
>
<div className="overflow-hidden rounded-2xl border border-border bg-surface shadow-lg">
<Placeholder
src={slide.image}
alt={slide.title}
width="100%"
label={isCenter ? "1280×380" : "880×350"}
className={`w-full rounded-none border-0 h-[200px] sm:h-[260px] md:h-[310px] ${
+1
View File
@@ -19,6 +19,7 @@ export function SamsungCarousel() {
name={product.name}
brand={product.brand}
price={product.price}
image={product.image}
variant="brand"
/>
))}
@@ -11,6 +11,8 @@ function SpecialSaleBanner() {
className="relative w-[220px] shrink-0 self-stretch overflow-hidden rounded-xl border border-border md:w-[240px]"
>
<Placeholder
src="/images/sale-banner.svg"
alt="فروش ویژه"
width="100%"
label="240×380"
className="h-full min-h-[380px] w-full rounded-none border-0"
@@ -38,6 +40,7 @@ export function SpecialSaleCarousel() {
price={product.price}
oldPrice={product.oldPrice}
discount={product.discount}
image={product.image}
variant="sale"
/>
))}
+5 -3
View File
@@ -1,9 +1,9 @@
import { Placeholder } from "@/components/ui/Placeholder";
const banners = [
{ title: "ماشین لباسشویی", subtitle: "از ۲۸ میلیون", label: "420×160" },
{ title: "جاروبرقی", subtitle: "ارسال فوری", label: "420×160" },
{ title: "لوازم آشپزخانه", subtitle: "تا ۲۰٪ تخفیف", label: "420×160" },
{ title: "ماشین لباسشویی", subtitle: "از ۲۸ میلیون", label: "420×160", image: "/images/promo-1.svg" },
{ title: "جاروبرقی", subtitle: "ارسال فوری", label: "420×160", image: "/images/promo-2.svg" },
{ title: "لوازم آشپزخانه", subtitle: "تا ۲۰٪ تخفیف", label: "420×160", image: "/images/promo-3.svg" },
];
export function ThreeBanners() {
@@ -18,6 +18,8 @@ export function ThreeBanners() {
className="group relative overflow-hidden rounded-xl border border-border bg-surface transition-all hover:border-red hover:shadow-md"
>
<Placeholder
src={banner.image}
alt={banner.title}
width="100%"
height="160px"
label={banner.label}
@@ -19,6 +19,7 @@ export function TopSellingCarousel() {
name={product.name}
brand={product.brand}
price={product.price}
image={product.image}
/>
))}
</Carousel>
+16 -2
View File
@@ -9,7 +9,14 @@ export function TwoBanners() {
href="#"
className="group relative overflow-hidden rounded-2xl border border-border bg-surface transition-shadow hover:shadow-lg"
>
<Placeholder width="100%" height="200px" label="640×200" className="w-full rounded-none border-0" />
<Placeholder
src="/images/banner-1.svg"
alt="تخفیف ویژه یخچال و فریزر"
width="100%"
height="200px"
label="640×200"
className="w-full rounded-none border-0"
/>
<div className="absolute inset-0 flex flex-col justify-end bg-gradient-to-t from-red-dark/80 to-transparent p-6">
<p className="font-display text-xs tracking-widest text-red-muted uppercase">
Summer Sale
@@ -22,7 +29,14 @@ export function TwoBanners() {
href="#"
className="group relative overflow-hidden rounded-2xl border border-border bg-surface transition-shadow hover:shadow-lg"
>
<Placeholder width="100%" height="200px" label="640×200" className="w-full rounded-none border-0" />
<Placeholder
src="/images/banner-2.svg"
alt="تلویزیون‌های هوشمند ۲۰۲۶"
width="100%"
height="200px"
label="640×200"
className="w-full rounded-none border-0"
/>
<div className="absolute inset-0 flex flex-col justify-end bg-gradient-to-t from-red-dark/80 to-transparent p-6">
<p className="font-display text-xs tracking-widest text-red-muted uppercase">
Smart TV
+14 -5
View File
@@ -1,4 +1,6 @@
type PlaceholderProps = {
src: string;
alt?: string;
width: string;
height?: string;
label?: string;
@@ -6,6 +8,8 @@ type PlaceholderProps = {
};
export function Placeholder({
src,
alt = "",
width,
height,
label,
@@ -15,12 +19,17 @@ export function Placeholder({
<div
className={`relative flex shrink-0 items-center justify-center overflow-hidden border border-dashed border-red-muted/60 bg-red-light/40 ${className}`}
style={{ width, ...(height ? { height } : {}) }}
aria-hidden="true"
>
<div className="absolute inset-0 bg-[linear-gradient(135deg,transparent_45%,rgba(220,38,38,0.06)_50%,transparent_55%)]" />
<span className="font-display relative z-10 text-xs tracking-wider text-red-dark/50 uppercase">
{label ?? `${width} × ${height ?? ""}`}
</span>
<img
src={src}
alt={alt}
className="absolute inset-0 h-full w-full object-cover"
/>
{label && (
<span className="font-display pointer-events-none relative z-10 rounded bg-white/70 px-1.5 py-0.5 text-xs tracking-wider text-red-dark/50 uppercase">
{label}
</span>
)}
</div>
);
}
+10 -1
View File
@@ -7,6 +7,7 @@ type ProductCardProps = {
price: number;
oldPrice?: number;
discount?: number;
image: string;
variant?: "sale" | "default" | "brand";
};
@@ -16,12 +17,20 @@ export function ProductCard({
price,
oldPrice,
discount,
image,
variant = "default",
}: ProductCardProps) {
return (
<article className="group w-[220px] shrink-0 overflow-hidden rounded-xl border border-border bg-surface transition-shadow hover:shadow-md md:w-[240px]">
<div className="relative">
<Placeholder width="100%" height="200px" label="240×200" className="w-full rounded-none border-0 border-b border-dashed" />
<Placeholder
src={image}
alt={name}
width="100%"
height="200px"
label="240×200"
className="w-full rounded-none border-0 border-b border-dashed"
/>
{discount && (
<span className="absolute top-3 right-3 rounded-md bg-red px-2 py-0.5 text-xs font-bold text-white">
{discount}٪