Skip to main content
The CartPanel component is a full-height sliding panel that displays cart items, allows quantity management, and provides checkout functionality.

Overview

Key features:
  • Sliding animation from the right side
  • Dark overlay with click-to-close
  • Product list with images and details
  • Quantity controls (increment/decrement)
  • Remove item functionality
  • Dynamic total calculation
  • GSAP-powered animations
  • Responsive design

Props

The CartPanel component does not accept props. It uses the CartContext for all state management.

Cart Context API

The component requires these context values:
carrito
Array
Array of cart items with id, nombre, precio, cantidad, imagen
carritoAbierto
boolean
Whether the cart panel is open
setCarritoAbierto
function
Function to open/close the cart panel
quitarProducto
function
Function to remove a product by id
cambiarCantidad
function
Function to adjust quantity (id, delta)
total
number
Total price of all items in cart

Implementation

import { useEffect, useRef } from "react";
import { useCart } from "../../context/CartContext/CartContext.jsx";
import gsap from "gsap";

function CartPanel() {
  const { carrito, carritoAbierto, setCarritoAbierto, quitarProducto, cambiarCantidad, total } = useCart();
  const panelRef = useRef(null);
  const overlayRef = useRef(null);

  useEffect(() => {
    if (carritoAbierto) {
      gsap.to(panelRef.current, { x: 0, duration: 0.4, ease: "power3.out" });
      gsap.to(overlayRef.current, { opacity: 1, duration: 0.3, pointerEvents: "auto" });
    } else {
      gsap.to(panelRef.current, { x: "100%", duration: 0.4, ease: "power3.in" });
      gsap.to(overlayRef.current, { opacity: 0, duration: 0.3, pointerEvents: "none" });
    }
  }, [carritoAbierto]);

  return (
    <>
      {/* Overlay */}
      <div
        ref={overlayRef}
        onClick={() => setCarritoAbierto(false)}
        className="fixed inset-0 bg-black/60 z-50"
        style={{ opacity: 0, pointerEvents: "none" }}
      />

      {/* Panel */}
      <div
        ref={panelRef}
        className="fixed top-0 right-0 h-full w-full max-w-md bg-[#0e0e0e] border-l border-white/10 z-50 flex flex-col"
        style={{ transform: "translateX(100%)" }}
      >
        {/* Header */}
        <div className="flex items-center justify-between px-6 py-5 border-b border-white/10">
          <h2 className="text-white text-lg font-semibold">Carrito</h2>
          <button onClick={() => setCarritoAbierto(false)} className="text-white/40 hover:text-white text-2xl">

          </button>
        </div>

        {/* Products */}
        <div className="flex-1 overflow-y-auto px-6 py-4 space-y-4">
          {carrito.length === 0 ? (
            <p className="text-white/30 text-sm text-center mt-20">Tu carrito está vacío</p>
          ) : (
            carrito.map(prod => (
              <div key={prod.id} className="flex gap-4 bg-white/5 rounded-2xl p-4 border border-white/10">
                <img src={prod.imagen} alt={prod.nombre} className="w-20 h-20 object-contain rounded-xl" />
                <div className="flex-1">
                  <h3 className="text-white text-sm">{prod.nombre}</h3>
                  <p className="text-white/40 text-xs">${(prod.precio * prod.cantidad).toLocaleString()}</p>
                  
                  <div className="flex items-center justify-between mt-2">
                    <div className="flex items-center gap-3 bg-white/10 rounded-full px-3 py-1">
                      <button onClick={() => cambiarCantidad(prod.id, -1)}></button>
                      <span>{prod.cantidad}</span>
                      <button onClick={() => cambiarCantidad(prod.id, 1)}>+</button>
                    </div>
                    <button onClick={() => quitarProducto(prod.id)}>Eliminar</button>
                  </div>
                </div>
              </div>
            ))
          )}
        </div>

        {/* Footer */}
        {carrito.length > 0 && (
          <div className="px-6 py-5 border-t border-white/10">
            <div className="flex justify-between">
              <span className="text-white/50">Total</span>
              <span className="text-white text-xl font-semibold">${total.toLocaleString()}</span>
            </div>
            <button className="w-full py-4 bg-white text-black font-semibold rounded-full mt-4">
              Proceder al pago
            </button>
          </div>
        )}
      </div>
    </>
  );
}

export default CartPanel;

Usage

import CartPanel from './components/CartPanel/CartPanel';
import { CartProvider } from './context/CartContext/CartContext';

function App() {
  return (
    <CartProvider>
      <CartPanel />
      {/* Other components */}
    </CartProvider>
  );
}

Animation System

The panel uses GSAP for smooth slide-in/out animations:

Opening Animation

gsap.to(panelRef.current, { x: 0, duration: 0.4, ease: "power3.out" });
gsap.to(overlayRef.current, { opacity: 1, duration: 0.3, pointerEvents: "auto" });

Closing Animation

gsap.to(panelRef.current, { x: "100%", duration: 0.4, ease: "power3.in" });
gsap.to(overlayRef.current, { opacity: 0, duration: 0.3, pointerEvents: "none" });
The initial state is set with inline styles:
style={{ transform: "translateX(100%)" }} // Panel starts off-screen
style={{ opacity: 0, pointerEvents: "none" }} // Overlay starts invisible

Cart Item Structure

Each cart item should have this structure:
{
  id: string | number,
  nombre: string,
  precio: number,
  cantidad: number,
  imagen: string
}

Quantity Controls

The quantity controls use the cambiarCantidad function:
<button onClick={() => cambiarCantidad(prod.id, -1)}></button>
<span>{prod.cantidad}</span>
<button onClick={() => cambiarCantidad(prod.id, 1)}>+</button>
The delta parameter can be:
  • 1: Increment quantity
  • -1: Decrement quantity

Styling & Customization

Panel Layout

className="fixed top-0 right-0 h-full w-full max-w-md bg-[#0e0e0e] border-l border-white/10 z-50 flex flex-col"
  • Fixed positioning on the right side
  • Full height
  • Max width of md (28rem/448px)
  • Dark background (#0e0e0e)
  • Flexbox column layout

Product Cards

className="flex gap-4 bg-white/5 rounded-2xl p-4 border border-white/10"
  • Semi-transparent white background
  • Large border radius (rounded-2xl)
  • Subtle border for depth

Checkout Button

className="w-full py-4 bg-white text-black font-semibold rounded-full hover:bg-white/90 hover:-translate-y-1"
The button includes hover effects:
  • Background opacity change
  • Upward translation (-translate-y-1)
  • Shadow enhancement
The panel is conditionally rendered based on carritoAbierto state from the CartContext.