Skip to main content

Overview

The ProductCard component displays individual products in a grid layout. When clicked, it opens a ProductModal to show detailed sub-product options. Each card features hover animations, accessibility support, and internationalization.

Component Location

File: src/App.js:157-220 The ProductCard is defined inline within the main App.js file and is used to render all products from the PRODUCTS data array.

Props

product
object
required
Product object containing all product information

State Management

The component manages modal visibility with local state:
const [isModalOpen, setIsModalOpen] = useState(false);
  • isModalOpen: Boolean controlling ProductModal visibility
  • Opens on card click or Enter/Space key press
  • Closes via modal’s onClose callback

Usage Example

Basic Implementation

import React from 'react';
import ProductCard from './components/ProductCard';

function App() {
  const product = {
    id: '1',
    name: 'Su',
    price: 'Seçenekleri Görün',
    image: '/images/water.png',
    imagePlaceholder: '💧',
    subProducts: [
      { id: '11', name: '19L Damacana', price: '25 TL', image: '/images/damacana.png' },
      { id: '12', name: '5L Pet', price: '15 TL', image: '/images/5l.png' }
    ]
  };

  return (
    <div className="products-grid">
      <ProductCard product={product} />
    </div>
  );
}

Multiple Products

import { PRODUCTS } from './data/products';

function ProductsGrid() {
  return (
    <main className="main-content">
      <div className="products-grid">
        {PRODUCTS.map((product) => (
          <ProductCard key={product.id} product={product} />
        ))}
      </div>
    </main>
  );
}

Component Behavior

Click Interaction

The card opens the modal in several ways:
  1. Mouse Click: Direct click on the card
  2. Keyboard: Press Enter or Space when focused
  3. Accessibility: Proper ARIA labels for screen readers
const handleCardClick = () => {
  setIsModalOpen(true);
};

const handleCloseModal = () => {
  setIsModalOpen(false);
};

Visual Feedback

The card provides interactive visual feedback:
  • Hover: Scales up (105%) and adds shadow
  • Active: Scales down (95%) for tactile feedback
  • Transition: Smooth 300ms animation
<div 
  className="
    product-card group relative cursor-pointer
    transform transition-all duration-300
    hover:scale-105 hover:shadow-2xl
    active:scale-95
  "
>

Accessibility Features

Keyboard Support

onKeyDown={(e) => {
  if (e.key === 'Enter' || e.key === ' ') {
    e.preventDefault();
    handleCardClick();
  }
}}

ARIA Attributes

role="button"
tabIndex={0}
aria-label={`${product.name} ürün seçeneklerini görüntüle`}
The card is fully keyboard navigable. Users can tab to the card and press Enter or Space to open the modal, making it accessible to keyboard-only users.

Internationalization

The component supports language switching:
import { t } from './config/language';

<div className="product-price">
  {product.price === "Seçenekleri Görün" ? t('seeOptions') : product.price}
</div>
Supported translations:
  • tr: “Seçenekleri Görün”
  • en: “See Options”

Child Components

ProductImage

Displays product image with lazy loading and fallback:
<ProductImage 
  src={product.image}
  alt={`${product.name} ürün resmi`}
  placeholder={product.imagePlaceholder}
  className="image-placeholder"
/>
See ProductImage documentation for details.

ProductModal

Rendered conditionally based on isModalOpen state:
<ProductModal 
  product={product}
  isOpen={isModalOpen}
  onClose={handleCloseModal}
/>
See ProductModal documentation for details.

Styling

CSS Classes

.product-card {
  background: white;
  border-radius: 12px;
  padding: 1.5rem;
  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}

.product-image {
  width: 100%;
  height: 200px;
  margin-bottom: 1rem;
}

.product-name {
  font-size: 1.25rem;
  font-weight: 600;
  margin-bottom: 0.5rem;
}

.product-price {
  color: #2563eb;
  font-weight: 600;
  font-size: 1.125rem;
}

Responsive Design

The cards are displayed in a responsive grid:
.products-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 2rem;
  padding: 2rem;
}

Complete Source Code

src/App.js
const ProductCard = ({ product }) => {
  const [isModalOpen, setIsModalOpen] = useState(false);

  const handleCardClick = () => {
    setIsModalOpen(true);
  };

  const handleCloseModal = () => {
    setIsModalOpen(false);
  };

  return (
    <>
      <div 
        onClick={handleCardClick}
        className="
          product-card group relative cursor-pointer
          transform transition-all duration-300
          hover:scale-105 hover:shadow-2xl
          active:scale-95
        "
        role="button"
        tabIndex={0}
        onKeyDown={(e) => {
          if (e.key === 'Enter' || e.key === ' ') {
            e.preventDefault();
            handleCardClick();
          }
        }}
        aria-label={`${product.name} ürün seçeneklerini görüntüle`}
      >
        <div className="product-image">
          <ProductImage 
            src={product.image}
            alt={`${product.name} ürün resmi`}
            placeholder={product.imagePlaceholder}
            className="image-placeholder"
          />
        </div>
        
        <div className="product-name">{product.name}</div>
        
        <div className="product-price">
          {product.price === "Seçenekleri Görün" ? t('seeOptions') : product.price}
        </div>
        
        <div className="mt-4 text-center">
          <div className="text-blue-600 font-semibold text-lg opacity-70 group-hover:opacity-100 transition-opacity duration-300">
            {/* Optional: Click hint */}
          </div>
        </div>
      </div>

      <ProductModal 
        product={product}
        isOpen={isModalOpen}
        onClose={handleCloseModal}
      />
    </>
  );
};

Best Practices

Performance: The ProductModal is only rendered when isModalOpen is true, preventing unnecessary DOM elements.
Accessibility: Always include the product.imagePlaceholder for products, ensuring a fallback when images fail to load.
Do not remove the e.preventDefault() from keyboard handlers - it prevents page scrolling when using Space key.

Build docs developers (and LLMs) love