Bug real encontrado armando la prueba automatizada que valida el
commit anterior (cd72962): condicionar el ocultamiento a que el borde
superior de "También te puede gustar" cruce el TOPE del viewport
(top <= 0) no es alcanzable en páginas sin suficiente contenido/footer
debajo -- el scroll máximo del documento nunca llega a ese punto, y el
bug original (barra pegada hasta el fondo) seguía reproduciéndose en
la práctica, aunque el código "parecía" correcto.
Se compara ahora contra la ALTURA del viewport (top < innerHeight): la
barra se oculta apenas la sección empieza a asomar desde abajo, y
sigue oculta el resto del scroll sin depender de cuánto contenido haya
más abajo.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_012Y4df3Z7odr9Cg8F2nw6uV
94 lines
3.9 KiB
TypeScript
94 lines
3.9 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { ShoppingBag } from "lucide-react";
|
|
import { Button } from "@/components/ui/button";
|
|
import type { Product } from "@/lib/headless/types";
|
|
import { useCartStore } from "@/store/cart-store";
|
|
|
|
const SCROLL_THRESHOLD_PX = 80;
|
|
|
|
// Antes esta barra quedaba fija desde el primer render en mobile,
|
|
// tapando permanentemente parte de la imagen del producto solo para
|
|
// duplicar un boton que ya está visible más abajo. Ahora combina tres
|
|
// condiciones, igual que el "Buy Now" sticky de Amazon:
|
|
// - scrolled: recien aparece despues de que el usuario empieza a
|
|
// scrollear (nunca sobre la vista inicial de la imagen/hero).
|
|
// - !buttonVisible: el boton real (#product-buy-button en
|
|
// ProductPurchase, visible tambien en mobile) no esta en pantalla --
|
|
// ya sea porque todavia no se llego a el, o porque se scrolleo mas
|
|
// alla.
|
|
// - !pastRecommendations: una vez que "También te puede gustar"
|
|
// (#recommendations-section en app/product/[slug]/page.tsx) empieza a
|
|
// entrar en pantalla, la barra se oculta y sigue oculta el resto del
|
|
// scroll (footer incluido) -- pedido explicito del usuario. OJO:
|
|
// condicionar esto a que el borde superior de la seccion cruce el
|
|
// TOPE del viewport (top <= 0) rompe en paginas cortas -- si no hay
|
|
// suficiente contenido/footer debajo, nunca se puede scrollear tan
|
|
// lejos y el bug original (barra pegada hasta el fondo) segunia
|
|
// reproduciendose. En cambio, se compara contra la ALTURA del
|
|
// viewport (top < innerHeight): se oculta apenas la seccion empieza a
|
|
// asomar desde abajo, se mantiene oculta al seguir scrolleando mas
|
|
// alla (top se vuelve cada vez mas negativo, sigue siendo <
|
|
// innerHeight), y solo reaparece si se vuelve a scrollear hacia
|
|
// arriba lo suficiente como para que la seccion salga de pantalla per
|
|
// completo otra vez.
|
|
export function MobileAddToCart({ product }: { product: Product }) {
|
|
const addItem = useCartStore((state) => state.addItem);
|
|
const [buttonVisible, setButtonVisible] = useState(true);
|
|
const [pastRecommendations, setPastRecommendations] = useState(false);
|
|
const [scrolled, setScrolled] = useState(false);
|
|
const outOfStock = product.variants[0]?.available === false;
|
|
|
|
useEffect(() => {
|
|
const target = document.getElementById("product-buy-button");
|
|
if (!target) return;
|
|
const observer = new IntersectionObserver((entries) => {
|
|
const entry = entries[0];
|
|
if (entry) setButtonVisible(entry.isIntersecting);
|
|
}, { threshold: 0 });
|
|
observer.observe(target);
|
|
return () => observer.disconnect();
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const target = document.getElementById("recommendations-section");
|
|
if (!target) return;
|
|
const observer = new IntersectionObserver((entries) => {
|
|
const entry = entries[0];
|
|
if (entry) setPastRecommendations(entry.boundingClientRect.top < window.innerHeight);
|
|
}, { threshold: 0 });
|
|
observer.observe(target);
|
|
return () => observer.disconnect();
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const onScroll = () => setScrolled(window.scrollY > SCROLL_THRESHOLD_PX);
|
|
onScroll();
|
|
window.addEventListener("scroll", onScroll, { passive: true });
|
|
return () => window.removeEventListener("scroll", onScroll);
|
|
}, []);
|
|
|
|
const visible = scrolled && !buttonVisible && !pastRecommendations;
|
|
|
|
return (
|
|
<div
|
|
aria-hidden={!visible}
|
|
className={`fixed inset-x-0 bottom-0 z-40 border-t border-slate-200 bg-white/95 p-3 shadow-2xl backdrop-blur transition-transform duration-200 lg:hidden ${
|
|
visible ? "translate-y-0" : "pointer-events-none translate-y-full"
|
|
}`}
|
|
>
|
|
<Button
|
|
variant="primary"
|
|
size="lg"
|
|
className="w-full"
|
|
disabled={outOfStock}
|
|
onClick={() => addItem(product, product.variants[0]?.id)}
|
|
>
|
|
<ShoppingBag />
|
|
{outOfStock ? "Agotado" : "Añadir al carrito"}
|
|
</Button>
|
|
</div>
|
|
);
|
|
}
|