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 (
+
+
+
+ );
+}
+
+/* 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;
+}