Welcome to Day 12 of the Daily UI Challenge! Today we'll be building a modern e-commerce shop interface using React and Tailwind CSS. We'll create a responsive product grid with filtering capabilities and a shopping cart preview.
The Challenge
Create an e-commerce shop interface that includes:
- A grid of products with images, prices, and "Add to Cart" buttons
- A category filter
- A shopping cart preview
- Responsive design for mobile and desktop
Let's start building
First, let's create our main component and the necessary state management:
import React, { useState } from 'react';
import { ShoppingCart } from 'lucide-react';
const products = [
{
id: 1,
name: "Vintage Camera",
price: 299.99,
category: "electronics",
image: "/api/placeholder/200/200"
},
{
id: 2,
name: "Leather Backpack",
price: 79.99,
category: "accessories",
image: "/api/placeholder/200/200"
},
{
id: 3,
name: "Mechanical Keyboard",
price: 159.99,
category: "electronics",
image: "/api/placeholder/200/200"
},
{
id: 4,
name: "Cotton T-Shirt",
price: 24.99,
category: "clothing",
image: "/api/placeholder/200/200"
}
];
const categories = ["all", "electronics", "accessories", "clothing"];
const Shop = () => {
const [selectedCategory, setSelectedCategory] = useState("all");
const [cart, setCart] = useState([]);
const [isCartOpen, setIsCartOpen] = useState(false);
// Filter products based on selected category
const filteredProducts = selectedCategory === "all"
? products
: products.filter(product => product.category === selectedCategory);
// Add item to cart
const addToCart = (product) => {
setCart(prevCart => {
const existingItem = prevCart.find(item => item.id === product.id);
if (existingItem) {
return prevCart.map(item =>
item.id === product.id
? { ...item, quantity: item.quantity + 1 }
: item
);
}
return [...prevCart, { ...product, quantity: 1 }];
});
};
// Calculate total price
const cartTotal = cart.reduce((total, item) =>
total + (item.price * item.quantity), 0
);
return (
<div className="container mx-auto px-4 py-8">
{/* Shop Header */}
<div className="flex justify-between items-center mb-8">
<h1 className="text-3xl font-bold">Our Shop</h1>
<button
onClick={() => setIsCartOpen(!isCartOpen)}
className="relative p-2"
>
<ShoppingCart size={24} />
{cart.length > 0 && (
<span className="absolute -top-1 -right-1 bg-red-500 text-white rounded-full w-5 h-5 text-xs flex items-center justify-center">
{cart.reduce((total, item) => total + item.quantity, 0)}
</span>
)}
</button>
</div>
{/* Category Filter */}
<div className="flex gap-4 mb-8">
{categories.map(category => (
<button
key={category}
onClick={() => setSelectedCategory(category)}
className={`px-4 py-2 rounded-full ${
selectedCategory === category
? "bg-blue-500 text-white"
: "bg-gray-200"
}`}
>
{category.charAt(0).toUpperCase() + category.slice(1)}
</button>
))}
</div>
{/* Product Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{filteredProducts.map(product => (
<div
key={product.id}
className="border rounded-lg p-4 hover:shadow-lg transition-shadow"
>
<img
src={product.image}
alt={product.name}
className="w-full h-48 object-cover rounded-lg mb-4"
/>
<h3 className="text-lg font-semibold">{product.name}</h3>
<p className="text-gray-600">${product.price.toFixed(2)}</p>
<button
onClick={() => addToCart(product)}
className="mt-4 w-full bg-blue-500 text-white py-2 rounded-lg hover:bg-blue-600 transition-colors"
>
Add to Cart
</button>
</div>
))}
</div>
{/* Cart Preview */}
{isCartOpen && (
<div className="fixed top-0 right-0 h-full w-80 bg-white shadow-lg p-4 overflow-y-auto">
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-bold">Shopping Cart</h2>
<button
onClick={() => setIsCartOpen(false)}
className="text-gray-500"
>
×
</button>
</div>
{cart.length === 0 ? (
<p>Your cart is empty</p>
) : (
<>
{cart.map(item => (
<div key={item.id} className="flex items-center gap-4 mb-4">
<img
src={item.image}
alt={item.name}
className="w-16 h-16 object-cover rounded"
/>
<div>
<h3 className="font-semibold">{item.name}</h3>
<p className="text-sm text-gray-600">
${item.price.toFixed(2)} × {item.quantity}
</p>
</div>
</div>
))}
<div className="border-t pt-4 mt-4">
<p className="text-lg font-bold">
Total: ${cartTotal.toFixed(2)}
</p>
</div>
</>
)}
</div>
)}
</div>
);
};
export default Shop;
Breaking down the components
State management
We're using React's useState hook to manage three pieces of state:
selectedCategory: Tracks the current category filter
cart: Stores the items in the shopping cart
isCartOpen: Controls the cart sidebar visibility
Category Filter
The category filter uses buttons that update the selectedCategory state. The selected category gets a different style to indicate the active filter.
Product Grid
Products are displayed in a responsive grid using CSS Grid. Each product card shows:
Product image
Name
Price
Add to Cart button
Shopping Cart
The cart preview slides in from the right side when clicked. It shows:
List of items with quantities
Total price
Empty cart message when applicable
Styling
We're using Tailwind CSS for styling, which provides:
Responsive design with breakpoints
Hover and transition effects
Clean, consistent spacing
Easy-to-maintain utility classes
Key Features
Responsive design
Grid adapts from 1 to 3 columns based on screen size
Cart sidebar is mobile-friendly
Interactive Elements
Category Filtering
Add to Cart Functionality
Cart Quantity badge
Sliding Cart Preview
State Management
Efficient cart updates
Category Filtering
Cart total calculation
Further Improvements
Here are some ways I could enhance this shop:
Add quantity controls in the cart
Implement a checkout process
Add product search functionality
Include product details modal
Add animations for cart interactions
Implement persistent cart storage
Playing the code
Go to codesandbox.io
Create a new sandbox
Next.js template
Install dependencies on the terminal
Dependencies are external packages/libraries that a project needs to work properly
npm install lucide-react @shadcn/ui
- Replace pages/index.js with this code:
import Shop from '../components/Shop';
export default function Home() {
return <Shop />;
}
Create new file named components/Shop.jsx and paste the full shop code from earlier in this article
Add required Next.js configuration - create next.config.js:
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
domains: ['api.placeholder.com'],
},
};
module.exports = nextConfig;
The placeholder images will automatically work through CodeSandbox's API proxy.
Conclusion
This e-commerce shop component demonstrates key React concepts like state management, conditional rendering, and component organization. It provides a solid foundation that you can build upon for more complex e-commerce applications.
Remember to:
Keep components modular
Maintain consistent styling
Consider user experience
Handle edge cases
Implement proper error handling
Happy coding and see you next challenge
Top comments (0)