Shared/ProductStatusBadge
All Variants
Discontinued Card Treatment
Discontinued Ribbon
Sold Ribbon
Unavailable Ribbon
Component Source
product-status-badge.tsx
import type { MercariStatus } from '@/src/types/product';
interface ProductStatusBadgeProps {
status: MercariStatus | null | undefined;
variant?: 'ribbon' | 'card-treatment';
}
/**
* Status badge overlay for product listing images.
* Shows a diagonal banner with status text over the product image.
*
* - sold: red banner + white "SOLD" text
* - discontinued: gray banner + white "DISCONTINUED" text
* - unavailable: gray banner + white "N/A" text
*
* variant='card-treatment' suppresses the overlay for discontinued items —
* the parent card applies a grayscale image filter instead (epic #233/#234,
* follow-up tweak: pattern lines + DISCONTINUED label removed per feedback).
* Other statuses still render the ribbon regardless of variant.
*/
export function ProductStatusBadge({ status, variant = 'ribbon' }: ProductStatusBadgeProps) {
if (!status || status === 'incoming' || status === 'askToBuy') {
return null;
}
if (variant === 'card-treatment' && status === 'discontinued') {
return null;
}
const isSold = status === 'sold';
const BANNER_TEXT: Record<string, string> = {
sold: 'SOLD',
discontinued: 'DISCONTINUED',
unavailable: 'N/A',
};
const bannerText = BANNER_TEXT[status] ?? 'N/A';
return (
<div className={'absolute top-0 left-0 w-full h-full overflow-hidden pointer-events-none'}>
<div
className={`absolute top-[12px] left-[-35px] sm:top-[18px] sm:left-[-40px] md:top-[22px] md:left-[-45px] w-[140px] sm:w-[160px] md:w-[180px] ${isSold ? 'bg-zd-sold' : 'bg-zd-literal-gray'} text-white text-center py-[3px] md:py-[4px] text-[10px] sm:text-[11px] md:text-xs leading-normal font-bold font-futura transform -rotate-45 shadow-md`}
>
{bannerText}
</div>
</div>
);
}
Story Source
product-status-badge.stories.tsx
import { ProductStatusBadge } from './product-status-badge';
/**
* ProductStatusBadge Component
*
* Status badge overlay for product listing images.
* Two variants:
* - ribbon (default): diagonal banner — used in nav-style cards (home featured, related-products)
* - card-treatment: discontinued returns null (grayscale-only treatment is applied by the
* parent card via image filter); other statuses still render the ribbon (epic #233/#234,
* follow-up tweak: pattern lines + DISCONTINUED label removed per feedback)
*/
export const meta = {
title: 'Shared/ProductStatusBadge',
};
const ImageBox = ({ children }: { children: preact.ComponentChildren }) => (
<div className="relative w-[200px] h-[200px] border border-zd-white bg-zd-gray/20 overflow-hidden">
<img
src="https://placehold.co/200x200/333/ccc?text=Product"
alt="placeholder"
className="w-full h-full object-cover"
/>
{children}
</div>
);
/**
* SOLD ribbon — red diagonal banner
*/
export const SoldRibbon = () => (
<ImageBox>
<ProductStatusBadge status="sold" />
</ImageBox>
);
/**
* Unavailable ribbon — gray diagonal banner
*/
export const UnavailableRibbon = () => (
<ImageBox>
<ProductStatusBadge status="unavailable" />
</ImageBox>
);
/**
* Discontinued ribbon — legacy/nav style, gray diagonal banner
*/
export const DiscontinuedRibbon = () => (
<ImageBox>
<ProductStatusBadge status="discontinued" variant="ribbon" />
</ImageBox>
);
/**
* Discontinued card-treatment — renders nothing; the parent card applies a grayscale
* image filter instead. Used in the main product listing and filter grid contexts.
*/
export const DiscontinuedCardTreatment = () => (
<ImageBox>
<ProductStatusBadge status="discontinued" variant="card-treatment" />
</ImageBox>
);
/**
* All variants side by side
*/
export const AllVariants = () => (
<div className="flex flex-wrap gap-vgap-md">
<div className="flex flex-col gap-vgap-xs items-center">
<span className="text-xs font-futura text-zd-white">SOLD ribbon</span>
<ImageBox>
<ProductStatusBadge status="sold" />
</ImageBox>
</div>
<div className="flex flex-col gap-vgap-xs items-center">
<span className="text-xs font-futura text-zd-white">Unavailable ribbon</span>
<ImageBox>
<ProductStatusBadge status="unavailable" />
</ImageBox>
</div>
<div className="flex flex-col gap-vgap-xs items-center">
<span className="text-xs font-futura text-zd-white">Discontinued ribbon (nav)</span>
<ImageBox>
<ProductStatusBadge status="discontinued" variant="ribbon" />
</ImageBox>
</div>
<div className="flex flex-col gap-vgap-xs items-center">
<span className="text-xs font-futura text-zd-white">
Discontinued card-treatment (renders null)
</span>
<ImageBox>
<ProductStatusBadge status="discontinued" variant="card-treatment" />
</ImageBox>
</div>
</div>
);