diff --git a/templates/hydrogen-theme/app/components/Drawer.tsx b/templates/hydrogen-theme/app/components/Drawer.tsx new file mode 100644 index 0000000..8de3b52 --- /dev/null +++ b/templates/hydrogen-theme/app/components/Drawer.tsx @@ -0,0 +1,113 @@ +import {Dialog, Transition} from '@headlessui/react'; +import {cx} from 'class-variance-authority'; +import {Fragment, useState} from 'react'; + +import {IconClose} from './icons/IconClose'; + +/** + * Drawer component that opens on user click. + * @param heading - string. Shown at the top of the drawer. + * @param open - boolean state. if true opens the drawer. + * @param onClose - function should set the open state. + * @param openFrom - right, left + * @param children - react children node. + */ +export function Drawer({ + children, + heading, + onClose, + open, + openFrom = 'right', +}: { + children: React.ReactNode; + heading?: string; + onClose: () => void; + open: boolean; + openFrom: 'left' | 'right'; +}) { + const offScreen = { + left: '-translate-x-full', + right: 'translate-x-full', + }; + + return ( + + + +
+ + +
+
+
+ +
+ +
+ {heading !== null && ( + + {heading} + + )} + +
+ {children} +
+
+
+
+
+
+
+
+ ); +} + +/* Use for associating arialabelledby with the title*/ +Drawer.Title = Dialog.Title; + +export function useDrawer(openDefault = false) { + const [isOpen, setIsOpen] = useState(openDefault); + + function openDrawer() { + setIsOpen(true); + } + + function closeDrawer() { + setIsOpen(false); + } + + return { + closeDrawer, + isOpen, + openDrawer, + }; +} diff --git a/templates/hydrogen-theme/app/components/cart/Cart.tsx b/templates/hydrogen-theme/app/components/cart/Cart.tsx index 286b2de..05648da 100644 --- a/templates/hydrogen-theme/app/components/cart/Cart.tsx +++ b/templates/hydrogen-theme/app/components/cart/Cart.tsx @@ -10,7 +10,7 @@ export function Cart({ layout, onClose, }: { - cart: CartType | null; + cart?: CartType | null; layout: CartLayouts; onClose?: () => void; }) { diff --git a/templates/hydrogen-theme/app/components/cart/CartDetails.tsx b/templates/hydrogen-theme/app/components/cart/CartDetails.tsx index a39c104..ac4950f 100644 --- a/templates/hydrogen-theme/app/components/cart/CartDetails.tsx +++ b/templates/hydrogen-theme/app/components/cart/CartDetails.tsx @@ -15,27 +15,36 @@ export function CartDetails({ cart, layout, }: { - cart: CartType | null; + cart?: CartType | null; layout: CartLayouts; }) { // @todo: get optimistic cart cost const cartHasItems = !!cart && cart.totalQuantity > 0; - const container = { - drawer: cx('grid grid-cols-1 grid-rows-[1fr_auto]'), - page: cx( - 'w-full pb-12 grid md:grid-cols-2 md:items-start gap-8 md:gap-8 lg:gap-12', - ), - }; return ( -
- + +
+ +
{cartHasItems && ( )} +
+ ); +} + +function CartDetailsLayout(props: { + children: React.ReactNode; + layout: CartLayouts; +}) { + return props.layout === 'drawer' ? ( + <>{props.children} + ) : ( +
+ {props.children}
); } diff --git a/templates/hydrogen-theme/app/components/cart/CartEmpty.tsx b/templates/hydrogen-theme/app/components/cart/CartEmpty.tsx index b0ccbfd..2f47ea4 100644 --- a/templates/hydrogen-theme/app/components/cart/CartEmpty.tsx +++ b/templates/hydrogen-theme/app/components/cart/CartEmpty.tsx @@ -17,7 +17,7 @@ export function CartEmpty({ }) { const container = { drawer: cx([ - 'content-start gap-4 px-6 pb-8 transition overflow-y-scroll md:gap-12 md:px-12md:pb-12', + 'p-5 content-start gap-4 pb-8 transition flex-1 overflow-y-scroll md:gap-12 md:pb-12', ]), page: cx([ !hidden && 'grid', diff --git a/templates/hydrogen-theme/app/components/cart/CartLines.tsx b/templates/hydrogen-theme/app/components/cart/CartLines.tsx index cf73dd6..46c63b8 100644 --- a/templates/hydrogen-theme/app/components/cart/CartLines.tsx +++ b/templates/hydrogen-theme/app/components/cart/CartLines.tsx @@ -22,7 +22,7 @@ export function CartLines({ const className = cx([ layout === 'page' ? 'flex-grow md:translate-y-4' - : 'px-6 pb-6 sm-max:pt-2 overflow-auto transition md:px-12', + : 'px-6 py-6 overflow-auto transition md:px-12', ]); return ( diff --git a/templates/hydrogen-theme/app/components/icons/IconClose.tsx b/templates/hydrogen-theme/app/components/icons/IconClose.tsx new file mode 100644 index 0000000..76fa00c --- /dev/null +++ b/templates/hydrogen-theme/app/components/icons/IconClose.tsx @@ -0,0 +1,25 @@ +import type {IconProps} from './Icon'; + +import {Icon} from './Icon'; + +export function IconClose(props: IconProps) { + return ( + + Close + + + + ); +} diff --git a/templates/hydrogen-theme/app/components/layout/CartCount.tsx b/templates/hydrogen-theme/app/components/layout/CartCount.tsx index 281a1bd..8aa22c8 100644 --- a/templates/hydrogen-theme/app/components/layout/CartCount.tsx +++ b/templates/hydrogen-theme/app/components/layout/CartCount.tsx @@ -8,19 +8,21 @@ import {useRootLoaderData} from '~/hooks/useRootLoaderData'; import {IconBag} from '../icons/IconBag'; -export function CartCount() { +export function CartCount(props: {openCart: () => void}) { const rootData = useRootLoaderData(); return ( - }> + }> - {(cart) => } + {(cart) => ( + + )} ); } -function Badge(props: {count: number}) { +function Badge(props: {count: number; openCart: () => void}) { const {count} = props; const isHydrated = useIsHydrated(); const path = useLocalePath({path: '/cart'}); @@ -46,12 +48,16 @@ function Badge(props: {count: number}) { [count, isHydrated], ); - return ( - + const buttonClass = cx([ + 'relative flex size-8 items-center justify-center focus:ring-primary/5', + ]); + + return isHydrated ? ( + + ) : ( + {BadgeCounter} ); diff --git a/templates/hydrogen-theme/app/components/layout/Header.tsx b/templates/hydrogen-theme/app/components/layout/Header.tsx index 909b9fe..ea85bba 100644 --- a/templates/hydrogen-theme/app/components/layout/Header.tsx +++ b/templates/hydrogen-theme/app/components/layout/Header.tsx @@ -1,17 +1,58 @@ -import type {CSSProperties} from 'react'; - -import {Link} from '@remix-run/react'; +import {Await, Link} from '@remix-run/react'; +import {CartForm} from '@shopify/hydrogen'; import {cx} from 'class-variance-authority'; +import {type CSSProperties, Suspense, useEffect} from 'react'; +import {useCartFetchers} from '~/hooks/useCartFetchers'; +import {useRootLoaderData} from '~/hooks/useRootLoaderData'; import {useSanityRoot} from '~/hooks/useSanityRoot'; import {useSettingsCssVars} from '~/hooks/useSettingsCssVars'; +import {Drawer, useDrawer} from '../Drawer'; +import {Cart} from '../cart/Cart'; import {headerVariants} from '../cva/header'; import {Navigation} from '../navigation/Navigation'; import {CartCount} from './CartCount'; import {Logo} from './Logo'; export function Header() { + const { + closeDrawer: closeCart, + isOpen: isCartOpen, + openDrawer: openCart, + } = useDrawer(); + + const addToCartFetchers = useCartFetchers(CartForm.ACTIONS.LinesAdd); + + // toggle cart drawer when adding to cart + useEffect(() => { + if (isCartOpen || !addToCartFetchers.length) return; + openCart(); + }, [addToCartFetchers, isCartOpen, openCart]); + + return ( + <> + + + + ); +} + +function CartDrawer({isOpen, onClose}: {isOpen: boolean; onClose: () => void}) { + const rootData = useRootLoaderData(); + + return ( + + Loading...
}> + + {(cart) => } + + + + ); +} + +function DesktopHeader(props: {openCart: () => void}) { const {data} = useSanityRoot(); const header = data?.header; const logoWidth = header?.desktopLogoWidth @@ -21,7 +62,6 @@ export function Header() { const cssVars = useSettingsCssVars({ settings: header, }); - return (
- +
diff --git a/templates/hydrogen-theme/app/hooks/useCartFetchers.tsx b/templates/hydrogen-theme/app/hooks/useCartFetchers.tsx new file mode 100644 index 0000000..019ed54 --- /dev/null +++ b/templates/hydrogen-theme/app/hooks/useCartFetchers.tsx @@ -0,0 +1,17 @@ +import {useFetchers} from '@remix-run/react'; +import {CartForm} from '@shopify/hydrogen'; + +export function useCartFetchers(actionName: string) { + const fetchers = useFetchers(); + const cartFetchers = []; + + for (const fetcher of fetchers) { + if (fetcher.formData) { + const formInputs = CartForm.getFormInput(fetcher.formData); + if (formInputs.action === actionName) { + cartFetchers.push(fetcher); + } + } + } + return cartFetchers; +}