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 containing all product information Show Product object structure
Unique product identifier
Product display name (e.g., “Su”, “Kola”, “Gazoz”)
Price display text (e.g., “Seçenekleri Görün” or “25 TL”)
Product image URL or path
Emoji or text placeholder when image is unavailable (e.g., ”💧”, ”🥤”)
Array of sub-product objects displayed in the modal
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:
Mouse Click : Direct click on the card
Keyboard : Press Enter or Space when focused
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 : 12 px ;
padding : 1.5 rem ;
box-shadow : 0 2 px 8 px rgba ( 0 , 0 , 0 , 0.1 );
}
.product-image {
width : 100 % ;
height : 200 px ;
margin-bottom : 1 rem ;
}
.product-name {
font-size : 1.25 rem ;
font-weight : 600 ;
margin-bottom : 0.5 rem ;
}
.product-price {
color : #2563eb ;
font-weight : 600 ;
font-size : 1.125 rem ;
}
Responsive Design
The cards are displayed in a responsive grid:
.products-grid {
display : grid ;
grid-template-columns : repeat ( auto-fill , minmax ( 280 px , 1 fr ));
gap : 2 rem ;
padding : 2 rem ;
}
Complete Source Code
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.