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>
);