Fixes cart closing and reopening with first interaction (#1053)

* Works

* Adds animation back
This commit is contained in:
Michael Novotny 2023-06-21 15:13:10 -05:00 committed by GitHub
parent dd9d5497d9
commit 9678306b23
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 73 additions and 117 deletions

View File

@ -1,64 +0,0 @@
'use client';
import { useEffect, useRef, useState } from 'react';
import { useCookies } from 'react-cookie';
import CartIcon from 'components/icons/cart';
import CartModal from './modal';
import type { Cart } from 'lib/shopify/types';
export default function CartButton({
cart,
cartIdUpdated
}: {
cart: Cart;
cartIdUpdated: boolean;
}) {
const [, setCookie] = useCookies(['cartId']);
const [cartIsOpen, setCartIsOpen] = useState(false);
const quantityRef = useRef(cart.totalQuantity);
// Temporary hack to update the `cartId` cookie when it changes since we cannot update it
// on the server-side (yet).
useEffect(() => {
if (cartIdUpdated) {
setCookie('cartId', cart.id, {
path: '/',
sameSite: 'strict',
secure: process.env.NODE_ENV === 'production'
});
}
return;
}, [setCookie, cartIdUpdated, cart.id]);
useEffect(() => {
// Open cart modal when when quantity changes.
if (cart.totalQuantity !== quantityRef.current) {
// But only if it's not already open (quantity also changes when editing items in cart).
if (!cartIsOpen) {
setCartIsOpen(true);
}
// Always update the quantity reference
quantityRef.current = cart.totalQuantity;
}
}, [cartIsOpen, cart.totalQuantity, quantityRef]);
return (
<>
<CartModal isOpen={cartIsOpen} onClose={() => setCartIsOpen(false)} cart={cart} />
<button
aria-label="Open cart"
onClick={() => {
setCartIsOpen(true);
}}
className="relative right-0 top-0"
data-testid="open-cart"
>
<CartIcon quantity={cart.totalQuantity} />
</button>
</>
);
}

View File

@ -1,6 +1,6 @@
import { createCart, getCart } from 'lib/shopify';
import { cookies } from 'next/headers';
import CartButton from './button';
import CartModal from './modal';
export default async function Cart() {
const cartId = cookies().get('cartId')?.value;
@ -19,5 +19,5 @@ export default async function Cart() {
cartIdUpdated = true;
}
return <CartButton cart={cart} cartIdUpdated={cartIdUpdated} />;
return <CartModal cart={cart} cartIdUpdated={cartIdUpdated} />;
}

View File

@ -1,14 +1,18 @@
import { Dialog } from '@headlessui/react';
import { AnimatePresence, motion } from 'framer-motion';
'use client';
import { Dialog, Transition } from '@headlessui/react';
import Image from 'next/image';
import Link from 'next/link';
import CartIcon from 'components/icons/cart';
import CloseIcon from 'components/icons/close';
import ShoppingBagIcon from 'components/icons/shopping-bag';
import Price from 'components/price';
import { DEFAULT_OPTION } from 'lib/constants';
import type { Cart } from 'lib/shopify/types';
import { createUrl } from 'lib/utils';
import { Fragment, useEffect, useRef, useState } from 'react';
import { useCookies } from 'react-cookie';
import DeleteItemButton from './delete-item-button';
import EditItemQuantityButton from './edit-item-quantity-button';
@ -16,53 +20,70 @@ type MerchandiseSearchParams = {
[key: string]: string;
};
export default function CartModal({
isOpen,
onClose,
cart
}: {
isOpen: boolean;
onClose: () => void;
cart: Cart;
}) {
return (
<AnimatePresence initial={false}>
{isOpen && (
<Dialog
as={motion.div}
initial="closed"
animate="open"
exit="closed"
key="dialog"
static
open={isOpen}
onClose={onClose}
className="relative z-50"
>
<motion.div
variants={{
open: { opacity: 1, backdropFilter: 'blur(0.5px)' },
closed: { opacity: 0, backdropFilter: 'blur(0px)' }
}}
className="fixed inset-0 bg-black/30"
aria-hidden="true"
/>
export default function CartModal({ cart, cartIdUpdated }: { cart: Cart; cartIdUpdated: boolean }) {
const [, setCookie] = useCookies(['cartId']);
const [cartIsOpen, setCartIsOpen] = useState(false);
const quantityRef = useRef(cart.totalQuantity);
const openCart = () => setCartIsOpen(true);
const closeCart = () => setCartIsOpen(false);
<div className="fixed inset-0 flex justify-end" data-testid="cart">
<Dialog.Panel
as={motion.div}
variants={{
open: { translateX: 0 },
closed: { translateX: '100%' }
}}
transition={{ type: 'spring', bounce: 0, duration: 0.3 }}
className="flex w-full flex-col bg-white p-8 text-black dark:bg-black dark:text-white md:w-3/5 lg:w-2/5"
>
useEffect(() => {
if (cartIdUpdated) {
setCookie('cartId', cart.id, {
path: '/',
sameSite: 'strict',
secure: process.env.NODE_ENV === 'production'
});
}
return;
}, [setCookie, cartIdUpdated, cart.id]);
useEffect(() => {
// Open cart modal when when quantity changes.
if (cart.totalQuantity !== quantityRef.current) {
// But only if it's not already open (quantity also changes when editing items in cart).
if (!cartIsOpen) {
setCartIsOpen(true);
}
// Always update the quantity reference
quantityRef.current = cart.totalQuantity;
}
}, [cartIsOpen, cart.totalQuantity, quantityRef]);
return (
<>
<button aria-label="Open cart" onClick={openCart} data-testid="open-cart">
<CartIcon quantity={cart.totalQuantity} />
</button>
<Transition show={cartIsOpen}>
<Dialog onClose={closeCart} className="relative z-50" data-testid="cart">
<Transition.Child
as={Fragment}
enter="transition-all ease-in-out duration-300"
enterFrom="opacity-0 backdrop-blur-none"
enterTo="opacity-100 backdrop-blur-[.5px]"
leave="transition-all ease-in-out duration-200"
leaveFrom="opacity-100 backdrop-blur-[.5px]"
leaveTo="opacity-0 backdrop-blur-none"
>
<div className="fixed inset-0 bg-black/30" aria-hidden="true" />
</Transition.Child>
<Transition.Child
as={Fragment}
enter="transition-all ease-in-out duration-300"
enterFrom="translate-x-full"
enterTo="translate-x-0"
leave="transition-all ease-in-out duration-200"
leaveFrom="translate-x-0"
leaveTo="translate-x-full"
>
<Dialog.Panel className="fixed bottom-0 right-0 top-0 flex h-full w-full flex-col bg-white p-6 text-black dark:bg-black dark:text-white md:w-3/5 lg:w-2/5">
<div className="flex items-center justify-between">
<p className="text-lg font-bold">My Cart</p>
<button
aria-label="Close cart"
onClick={onClose}
onClick={closeCart}
className="text-black transition-colors hover:text-gray-500 dark:text-gray-100"
data-testid="close-cart"
>
@ -75,8 +96,7 @@ export default function CartModal({
<ShoppingBagIcon className="h-16" />
<p className="mt-6 text-center text-2xl font-bold">Your cart is empty.</p>
</div>
) : null}
{cart.lines.length !== 0 ? (
) : (
<div className="flex h-full flex-col justify-between overflow-hidden">
<ul className="flex-grow overflow-auto p-6">
{cart.lines.map((item, i) => {
@ -98,7 +118,7 @@ export default function CartModal({
<Link
className="flex flex-row space-x-4 py-4"
href={merchandiseUrl}
onClick={onClose}
onClick={closeCart}
>
<div className="relative h-16 w-16 cursor-pointer overflow-hidden bg-white">
<Image
@ -177,11 +197,11 @@ export default function CartModal({
<span>Proceed to Checkout</span>
</a>
</div>
) : null}
)}
</Dialog.Panel>
</div>
</Transition.Child>
</Dialog>
)}
</AnimatePresence>
</Transition>
</>
);
}