From 9f5e76575e3932f8fcd8689d8ef42e4c44b923d1 Mon Sep 17 00:00:00 2001 From: Bram van der Holst Date: Thu, 28 May 2026 16:56:06 +0200 Subject: [PATCH 1/7] Added disableScrollEffects prop to CartFab & NavigationFab for easier customization of the header --- .changeset/kind-days-punch.md | 6 +++ .../components/CartFab/CartFab.tsx | 8 ++- .../components/CartFab/CartFabStatic.tsx | 49 +++++++++++++++++++ .../Navigation/components/NavigationFab.tsx | 24 ++++++--- 4 files changed, 78 insertions(+), 9 deletions(-) create mode 100644 .changeset/kind-days-punch.md create mode 100644 packages/magento-cart/components/CartFab/CartFabStatic.tsx diff --git a/.changeset/kind-days-punch.md b/.changeset/kind-days-punch.md new file mode 100644 index 00000000000..bed1f67c87e --- /dev/null +++ b/.changeset/kind-days-punch.md @@ -0,0 +1,6 @@ +--- +'@graphcommerce/magento-cart': patch +'@graphcommerce/next-ui': patch +--- + +Added disableScrollEffects prop to CartFab & NavigationFab for easier customization of the header diff --git a/packages/magento-cart/components/CartFab/CartFab.tsx b/packages/magento-cart/components/CartFab/CartFab.tsx index 045a740d150..f58a1959ef0 100644 --- a/packages/magento-cart/components/CartFab/CartFab.tsx +++ b/packages/magento-cart/components/CartFab/CartFab.tsx @@ -15,12 +15,14 @@ import React from 'react' import { useCartEnabled, useCartShouldLoginToContinue } from '../../hooks' import { useCartQuery } from '../../hooks/useCartQuery' import { CartFabDocument } from './CartFab.gql' +import { CartFabStatic } from './CartFabStatic' import type { CartTotalQuantityFragment } from './CartTotalQuantity.gql' export type CartFabProps = { icon?: React.ReactNode sx?: SxProps BadgeProps?: BadgeProps + disableScrollEffects?: boolean } & Pick export type CartFabContentProps = CartFabProps & CartTotalQuantityFragment @@ -108,5 +110,9 @@ export function CartFab(props: CartFabProps) { }) if (!cartEnabled) return null - return + const { disableScrollEffects, ...rest } = props + if (disableScrollEffects) + return + + return } diff --git a/packages/magento-cart/components/CartFab/CartFabStatic.tsx b/packages/magento-cart/components/CartFab/CartFabStatic.tsx new file mode 100644 index 00000000000..2156428d045 --- /dev/null +++ b/packages/magento-cart/components/CartFab/CartFabStatic.tsx @@ -0,0 +1,49 @@ +import { + DesktopHeaderBadge, + extendableComponent, + iconShoppingBag, + IconSvg, + sxx, +} from '@graphcommerce/next-ui' +import { t } from '@lingui/core/macro' +import { Fab } from '@mui/material' +import type { CartFabContentProps } from './CartFab' + +export type CartFabStaticProps = Omit + +const { classes } = extendableComponent('CartFab', ['root', 'cart', 'shadow'] as const) + +export function CartFabStatic(props: CartFabStaticProps) { + const { total_quantity, icon, sx = [], BadgeProps, ...fabProps } = props + + const cartIcon = icon ?? + + return ( + ({ + [theme.breakpoints.down('md')]: { + backgroundColor: theme.vars.palette.background.paper, + }, + }), + sx, + )} + {...fabProps} + > + + {cartIcon} + + + ) +} diff --git a/packages/next-ui/Navigation/components/NavigationFab.tsx b/packages/next-ui/Navigation/components/NavigationFab.tsx index 73f3323bde6..98dd21e5c1f 100644 --- a/packages/next-ui/Navigation/components/NavigationFab.tsx +++ b/packages/next-ui/Navigation/components/NavigationFab.tsx @@ -17,6 +17,7 @@ const MotionDiv = styled(m.div)({}) export type NavigationFabProps = { menuIcon?: React.ReactNode closeIcon?: React.ReactNode + disableScrollEffects?: boolean sx?: SxProps } & Pick @@ -29,7 +30,7 @@ type OwnerState = { const { withState } = extendableComponent(name, parts) export function NavigationFab(props: NavigationFabProps) { - const { menuIcon, closeIcon, sx = [], ...fabProps } = props + const { menuIcon, closeIcon, disableScrollEffects, sx = [], ...fabProps } = props const router = useRouter() const [openEl, setOpenEl] = React.useState(null) @@ -53,12 +54,19 @@ export function NavigationFab(props: NavigationFabProps) { From 53d8255731ba891d5c2ff2335648ed2e22e5ae71 Mon Sep 17 00:00:00 2001 From: Bram van der Holst Date: Thu, 28 May 2026 14:29:36 +0200 Subject: [PATCH 2/7] feat(header-v2): copy old LayoutDefault from next-ui to be used as new local LayoutDefault in new LayoutNavigation Unchaged for now (except import paths). Changes coming in next commits. --- .../components/Layout/LayoutDefault.tsx | 165 ++++++++++++++++++ .../components/Layout/LayoutNavigation.tsx | 3 +- .../components/Layout/LayoutDefault.tsx | 165 ++++++++++++++++++ .../components/Layout/LayoutNavigation.tsx | 3 +- .../components/Layout/LayoutDefault.tsx | 165 ++++++++++++++++++ .../components/Layout/LayoutNavigation.tsx | 3 +- 6 files changed, 498 insertions(+), 6 deletions(-) create mode 100644 examples/magento-graphcms/components/Layout/LayoutDefault.tsx create mode 100644 examples/magento-open-source/components/Layout/LayoutDefault.tsx create mode 100644 examples/magento-storyblok/components/Layout/LayoutDefault.tsx diff --git a/examples/magento-graphcms/components/Layout/LayoutDefault.tsx b/examples/magento-graphcms/components/Layout/LayoutDefault.tsx new file mode 100644 index 00000000000..8f2fe5288e6 --- /dev/null +++ b/examples/magento-graphcms/components/Layout/LayoutDefault.tsx @@ -0,0 +1,165 @@ +import { useScrollOffset } from '@graphcommerce/framer-next-pages' +import { dvh } from '@graphcommerce/framer-utils' +import { + Container, + extendableComponent, + LayoutProvider, + SkipLink, + sxx, + useFabSize, +} from '@graphcommerce/next-ui' +import type { SxProps, Theme } from '@mui/material' +import { Box } from '@mui/material' +import { useScroll, useTransform } from 'framer-motion' + +export type LayoutDefaultProps = { + className?: string + beforeHeader?: React.ReactNode + header: React.ReactNode + footer: React.ReactNode + menuFab?: React.ReactNode + cartFab?: React.ReactNode + children?: React.ReactNode + noSticky?: boolean + sx?: SxProps +} & OwnerState + +type OwnerState = { + noSticky?: boolean +} +const parts = ['root', 'fabs', 'header', 'children', 'footer'] as const +const { withState } = extendableComponent( + 'LayoutDefault', + parts, +) + +export function LayoutDefault(props: LayoutDefaultProps) { + const { + children, + header, + beforeHeader, + footer, + menuFab, + cartFab, + noSticky, + className, + sx = [], + } = props + + const { scrollY } = useScroll() + const scrollYOffset = useTransform( + [scrollY, useScrollOffset()], + ([y, offset]: number[]) => y + offset, + ) + + const classes = withState({ noSticky }) + const fabIconSize = useFabSize('responsive') + + return ( + ({ + minHeight: dvh(100), + '@supports (-webkit-touch-callout: none)': { + minHeight: '-webkit-fill-available', + }, + display: 'grid', + gridTemplateRows: { xs: 'auto 1fr auto', md: 'auto auto 1fr auto' }, + gridTemplateColumns: '100%', + background: theme.vars.palette.background.default, + }), + sx, + )} + > + + + {beforeHeader} + ({ + zIndex: theme.zIndex.appBar - 1, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + height: theme.appShell.headerHeightSm, + pointerEvents: 'none', + '& > *': { + pointerEvents: 'all', + }, + [theme.breakpoints.up('md')]: { + height: theme.appShell.headerHeightMd, + top: 0, + display: 'flex', + justifyContent: 'left', + width: '100%', + }, + '&.sticky': { + [theme.breakpoints.down('md')]: { + position: 'sticky', + top: 0, + }, + }, + })} + > + {header} + + {menuFab || cartFab ? ( + ({ + display: 'flex', + justifyContent: 'space-between', + width: '100%', + height: 0, + zIndex: 'speedDial', + [theme.breakpoints.up('sm')]: { + position: 'sticky', + marginTop: `calc(${theme.appShell.headerHeightMd} * -1 - calc(${fabIconSize} / 2))`, + top: `calc(${theme.appShell.headerHeightMd} / 2 - (${fabIconSize} / 2))`, + }, + [theme.breakpoints.down('md')]: { + position: 'fixed', + top: 'unset', + bottom: `calc(20px + ${fabIconSize})`, + padding: '0 20px', + '@media (max-height: 530px) and (orientation: portrait)': { + display: 'none', + }, + }, + })} + > + {menuFab} + {cartFab && ( + ({ + display: 'flex', + flexDirection: 'row-reverse', + gap: theme.spacings.sm, + [theme.breakpoints.up('md')]: { + flexDirection: 'column', + alignItems: 'flex-end', + }, + })} + > + {cartFab} + + )} + + ) : ( +
+ )} +
+
+ {children} +
+
{footer}
+ + + ) +} diff --git a/examples/magento-graphcms/components/Layout/LayoutNavigation.tsx b/examples/magento-graphcms/components/Layout/LayoutNavigation.tsx index 89a5fa95457..765473bbe82 100644 --- a/examples/magento-graphcms/components/Layout/LayoutNavigation.tsx +++ b/examples/magento-graphcms/components/Layout/LayoutNavigation.tsx @@ -6,8 +6,6 @@ import { WishlistFab, WishlistMenuFabItem } from '@graphcommerce/magento-wishlis import { DesktopNavActions, DesktopNavBar, - LayoutDefault, - LayoutDefaultProps, iconCustomerService, iconHeart, NavigationFab, @@ -36,6 +34,7 @@ import { StoreSwitcherFab, StoreSwitcherMenuFabSecondaryItem, } from '@graphcommerce/magento-store' +import { LayoutDefault, type LayoutDefaultProps } from './LayoutDefault' export type LayoutNavigationProps = LayoutQuery & Omit diff --git a/examples/magento-open-source/components/Layout/LayoutDefault.tsx b/examples/magento-open-source/components/Layout/LayoutDefault.tsx new file mode 100644 index 00000000000..8f2fe5288e6 --- /dev/null +++ b/examples/magento-open-source/components/Layout/LayoutDefault.tsx @@ -0,0 +1,165 @@ +import { useScrollOffset } from '@graphcommerce/framer-next-pages' +import { dvh } from '@graphcommerce/framer-utils' +import { + Container, + extendableComponent, + LayoutProvider, + SkipLink, + sxx, + useFabSize, +} from '@graphcommerce/next-ui' +import type { SxProps, Theme } from '@mui/material' +import { Box } from '@mui/material' +import { useScroll, useTransform } from 'framer-motion' + +export type LayoutDefaultProps = { + className?: string + beforeHeader?: React.ReactNode + header: React.ReactNode + footer: React.ReactNode + menuFab?: React.ReactNode + cartFab?: React.ReactNode + children?: React.ReactNode + noSticky?: boolean + sx?: SxProps +} & OwnerState + +type OwnerState = { + noSticky?: boolean +} +const parts = ['root', 'fabs', 'header', 'children', 'footer'] as const +const { withState } = extendableComponent( + 'LayoutDefault', + parts, +) + +export function LayoutDefault(props: LayoutDefaultProps) { + const { + children, + header, + beforeHeader, + footer, + menuFab, + cartFab, + noSticky, + className, + sx = [], + } = props + + const { scrollY } = useScroll() + const scrollYOffset = useTransform( + [scrollY, useScrollOffset()], + ([y, offset]: number[]) => y + offset, + ) + + const classes = withState({ noSticky }) + const fabIconSize = useFabSize('responsive') + + return ( + ({ + minHeight: dvh(100), + '@supports (-webkit-touch-callout: none)': { + minHeight: '-webkit-fill-available', + }, + display: 'grid', + gridTemplateRows: { xs: 'auto 1fr auto', md: 'auto auto 1fr auto' }, + gridTemplateColumns: '100%', + background: theme.vars.palette.background.default, + }), + sx, + )} + > + + + {beforeHeader} + ({ + zIndex: theme.zIndex.appBar - 1, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + height: theme.appShell.headerHeightSm, + pointerEvents: 'none', + '& > *': { + pointerEvents: 'all', + }, + [theme.breakpoints.up('md')]: { + height: theme.appShell.headerHeightMd, + top: 0, + display: 'flex', + justifyContent: 'left', + width: '100%', + }, + '&.sticky': { + [theme.breakpoints.down('md')]: { + position: 'sticky', + top: 0, + }, + }, + })} + > + {header} + + {menuFab || cartFab ? ( + ({ + display: 'flex', + justifyContent: 'space-between', + width: '100%', + height: 0, + zIndex: 'speedDial', + [theme.breakpoints.up('sm')]: { + position: 'sticky', + marginTop: `calc(${theme.appShell.headerHeightMd} * -1 - calc(${fabIconSize} / 2))`, + top: `calc(${theme.appShell.headerHeightMd} / 2 - (${fabIconSize} / 2))`, + }, + [theme.breakpoints.down('md')]: { + position: 'fixed', + top: 'unset', + bottom: `calc(20px + ${fabIconSize})`, + padding: '0 20px', + '@media (max-height: 530px) and (orientation: portrait)': { + display: 'none', + }, + }, + })} + > + {menuFab} + {cartFab && ( + ({ + display: 'flex', + flexDirection: 'row-reverse', + gap: theme.spacings.sm, + [theme.breakpoints.up('md')]: { + flexDirection: 'column', + alignItems: 'flex-end', + }, + })} + > + {cartFab} + + )} + + ) : ( +
+ )} +
+
+ {children} +
+
{footer}
+ + + ) +} diff --git a/examples/magento-open-source/components/Layout/LayoutNavigation.tsx b/examples/magento-open-source/components/Layout/LayoutNavigation.tsx index 485654a5516..1d387590b10 100644 --- a/examples/magento-open-source/components/Layout/LayoutNavigation.tsx +++ b/examples/magento-open-source/components/Layout/LayoutNavigation.tsx @@ -9,7 +9,6 @@ import { StoreSwitcherMenuFabSecondaryItem, } from '@graphcommerce/magento-store' import { WishlistFab, WishlistMenuFabItem } from '@graphcommerce/magento-wishlist' -import type { LayoutDefaultProps } from '@graphcommerce/next-ui' import { DarkLightModeMenuSecondaryItem, DesktopNavActions, @@ -19,7 +18,6 @@ import { iconCustomerService, iconHeart, IconSvg, - LayoutDefault, MenuFabSecondaryItem, MobileTopRight, NavigationFab, @@ -36,6 +34,7 @@ import { useRouter } from 'next/router' import { productListRenderer } from '../ProductListItems/productListRenderer' import { Footer } from './Footer' import type { LayoutQuery } from './Layout.gql' +import { LayoutDefault, type LayoutDefaultProps } from './LayoutDefault' import { Logo } from './Logo' export type LayoutNavigationProps = LayoutQuery & diff --git a/examples/magento-storyblok/components/Layout/LayoutDefault.tsx b/examples/magento-storyblok/components/Layout/LayoutDefault.tsx new file mode 100644 index 00000000000..8f2fe5288e6 --- /dev/null +++ b/examples/magento-storyblok/components/Layout/LayoutDefault.tsx @@ -0,0 +1,165 @@ +import { useScrollOffset } from '@graphcommerce/framer-next-pages' +import { dvh } from '@graphcommerce/framer-utils' +import { + Container, + extendableComponent, + LayoutProvider, + SkipLink, + sxx, + useFabSize, +} from '@graphcommerce/next-ui' +import type { SxProps, Theme } from '@mui/material' +import { Box } from '@mui/material' +import { useScroll, useTransform } from 'framer-motion' + +export type LayoutDefaultProps = { + className?: string + beforeHeader?: React.ReactNode + header: React.ReactNode + footer: React.ReactNode + menuFab?: React.ReactNode + cartFab?: React.ReactNode + children?: React.ReactNode + noSticky?: boolean + sx?: SxProps +} & OwnerState + +type OwnerState = { + noSticky?: boolean +} +const parts = ['root', 'fabs', 'header', 'children', 'footer'] as const +const { withState } = extendableComponent( + 'LayoutDefault', + parts, +) + +export function LayoutDefault(props: LayoutDefaultProps) { + const { + children, + header, + beforeHeader, + footer, + menuFab, + cartFab, + noSticky, + className, + sx = [], + } = props + + const { scrollY } = useScroll() + const scrollYOffset = useTransform( + [scrollY, useScrollOffset()], + ([y, offset]: number[]) => y + offset, + ) + + const classes = withState({ noSticky }) + const fabIconSize = useFabSize('responsive') + + return ( + ({ + minHeight: dvh(100), + '@supports (-webkit-touch-callout: none)': { + minHeight: '-webkit-fill-available', + }, + display: 'grid', + gridTemplateRows: { xs: 'auto 1fr auto', md: 'auto auto 1fr auto' }, + gridTemplateColumns: '100%', + background: theme.vars.palette.background.default, + }), + sx, + )} + > + + + {beforeHeader} + ({ + zIndex: theme.zIndex.appBar - 1, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + height: theme.appShell.headerHeightSm, + pointerEvents: 'none', + '& > *': { + pointerEvents: 'all', + }, + [theme.breakpoints.up('md')]: { + height: theme.appShell.headerHeightMd, + top: 0, + display: 'flex', + justifyContent: 'left', + width: '100%', + }, + '&.sticky': { + [theme.breakpoints.down('md')]: { + position: 'sticky', + top: 0, + }, + }, + })} + > + {header} + + {menuFab || cartFab ? ( + ({ + display: 'flex', + justifyContent: 'space-between', + width: '100%', + height: 0, + zIndex: 'speedDial', + [theme.breakpoints.up('sm')]: { + position: 'sticky', + marginTop: `calc(${theme.appShell.headerHeightMd} * -1 - calc(${fabIconSize} / 2))`, + top: `calc(${theme.appShell.headerHeightMd} / 2 - (${fabIconSize} / 2))`, + }, + [theme.breakpoints.down('md')]: { + position: 'fixed', + top: 'unset', + bottom: `calc(20px + ${fabIconSize})`, + padding: '0 20px', + '@media (max-height: 530px) and (orientation: portrait)': { + display: 'none', + }, + }, + })} + > + {menuFab} + {cartFab && ( + ({ + display: 'flex', + flexDirection: 'row-reverse', + gap: theme.spacings.sm, + [theme.breakpoints.up('md')]: { + flexDirection: 'column', + alignItems: 'flex-end', + }, + })} + > + {cartFab} + + )} + + ) : ( +
+ )} +
+
+ {children} +
+
{footer}
+ + + ) +} diff --git a/examples/magento-storyblok/components/Layout/LayoutNavigation.tsx b/examples/magento-storyblok/components/Layout/LayoutNavigation.tsx index d12d98866fa..039c5a8ead1 100644 --- a/examples/magento-storyblok/components/Layout/LayoutNavigation.tsx +++ b/examples/magento-storyblok/components/Layout/LayoutNavigation.tsx @@ -8,7 +8,6 @@ import { StoreSwitcherMenuFabSecondaryItem, } from '@graphcommerce/magento-store' import { WishlistFab, WishlistMenuFabItem } from '@graphcommerce/magento-wishlist' -import type { LayoutDefaultProps } from '@graphcommerce/next-ui' import { DarkLightModeMenuSecondaryItem, DesktopNavActions, @@ -18,7 +17,6 @@ import { iconCustomerService, iconHeart, IconSvg, - LayoutDefault, MenuFabSecondaryItem, MobileTopRight, NavigationFab, @@ -37,6 +35,7 @@ import { GlobalConfigProvider } from '../Storyblok/GlobalConfigProvider' import type { StoryblokGlobalConfig } from '../Storyblok/types' import { Footer } from './Footer' import type { LayoutQuery } from './Layout.gql' +import { LayoutDefault, type LayoutDefaultProps } from './LayoutDefault' import { Logo } from './Logo' export type LayoutNavigationProps = LayoutQuery & From bdaa6ec6aa2669b74fc6702ad46336db4c154b7c Mon Sep 17 00:00:00 2001 From: Bram van der Holst Date: Wed, 3 Jun 2026 14:02:23 +0200 Subject: [PATCH 3/7] feat(header-v2): split LayoutNavigation into more usable / composable components MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LayoutNavigation was a big file that mixed the navigation overlay, the header content, and the layout shell. Customizing the header — sticky behavior, owning the container styling, or adding a beforeHeader slot — required forking the whole file or copying chunks of @graphcommerce/next-ui's LayoutDefault. The header is now four reusable pieces: - HeaderContainer the wrapper, lifted out of LayoutDefault so each project owns its header container. - Header the header content (Logo, DesktopNavBar, DesktopNavActions, MobileTopRight). - MenuOverlay the NavigationProvider + NavigationOverlay. - LayoutDefault a local copy that renders the `header` slot raw, since the wrapping now lives in HeaderContainer. Keeping LayoutDefault as a project-local copy makes the layout shell itself customizable: tweaking the grid, the fab container, the beforeHeader slot or the sticky behavior no longer requires patching @graphcommerce/next-ui or copying the file — the example owns it. Combined with the extracted Header / MenuOverlay / HeaderContainer, projects can override any single piece without forking the rest. LayoutNavigation becomes a slim orchestrator that composes them. LayoutMinimal is updated to use the same local LayoutDefault + HeaderContainer so both layouts share the canonical header pattern. There is no visual change: HeaderContainer carries the same styling that the header slot of next-ui's LayoutDefault used to apply. --- .changeset/true-suits-design.md | 8 + .../components/Layout/Header.tsx | 82 +++++++++ .../components/Layout/HeaderContainer.tsx | 40 +++++ .../components/Layout/LayoutDefault.tsx | 33 +--- .../components/Layout/LayoutMinimal.tsx | 9 +- .../components/Layout/LayoutNavigation.tsx | 152 +---------------- .../components/Layout/MenuOverlay.tsx | 87 ++++++++++ .../components/Layout/Header.tsx | 84 ++++++++++ .../components/Layout/HeaderContainer.tsx | 45 +++++ .../components/Layout/LayoutDefault.tsx | 33 +--- .../components/Layout/LayoutMinimal.tsx | 10 +- .../components/Layout/LayoutNavigation.tsx | 150 +---------------- .../components/Layout/MenuOverlay.tsx | 86 ++++++++++ .../components/Layout/Header.tsx | 89 ++++++++++ .../components/Layout/HeaderContainer.tsx | 45 +++++ .../components/Layout/LayoutDefault.tsx | 33 +--- .../components/Layout/LayoutMinimal.tsx | 10 +- .../components/Layout/LayoutNavigation.tsx | 156 +----------------- .../components/Layout/MenuOverlay.tsx | 87 ++++++++++ .../components/LayoutDefault.tsx | 8 + 20 files changed, 704 insertions(+), 543 deletions(-) create mode 100644 .changeset/true-suits-design.md create mode 100644 examples/magento-graphcms/components/Layout/Header.tsx create mode 100644 examples/magento-graphcms/components/Layout/HeaderContainer.tsx create mode 100644 examples/magento-graphcms/components/Layout/MenuOverlay.tsx create mode 100644 examples/magento-open-source/components/Layout/Header.tsx create mode 100644 examples/magento-open-source/components/Layout/HeaderContainer.tsx create mode 100644 examples/magento-open-source/components/Layout/MenuOverlay.tsx create mode 100644 examples/magento-storyblok/components/Layout/Header.tsx create mode 100644 examples/magento-storyblok/components/Layout/HeaderContainer.tsx create mode 100644 examples/magento-storyblok/components/Layout/MenuOverlay.tsx diff --git a/.changeset/true-suits-design.md b/.changeset/true-suits-design.md new file mode 100644 index 00000000000..59c594e0d12 --- /dev/null +++ b/.changeset/true-suits-design.md @@ -0,0 +1,8 @@ +--- +'@graphcommerce/magento-open-source': minor +'@graphcommerce/magento-storyblok': minor +'@graphcommerce/magento-graphcms': minor +'@graphcommerce/next-ui': minor +--- + +Refactored `LayoutNavigation` into composable pieces (`Header`, `HeaderContainer`, `MenuOverlay`, project-local `LayoutDefault`). `LayoutDefault` / `LayoutDefaultProps` in `@graphcommerce/next-ui` are marked `@deprecated` — the canonical version now lives locally in `components/Layout/`. diff --git a/examples/magento-graphcms/components/Layout/Header.tsx b/examples/magento-graphcms/components/Layout/Header.tsx new file mode 100644 index 00000000000..9d9886d6232 --- /dev/null +++ b/examples/magento-graphcms/components/Layout/Header.tsx @@ -0,0 +1,82 @@ +import { useCartEnabled } from '@graphcommerce/magento-cart' +import { CustomerFab } from '@graphcommerce/magento-customer' +import { SearchFab, SearchField } from '@graphcommerce/magento-search' +import { StoreSwitcherButton, StoreSwitcherFab } from '@graphcommerce/magento-store' +import { WishlistFab } from '@graphcommerce/magento-wishlist' +import { + DesktopNavActions, + DesktopNavBar, + DesktopNavItem, + iconChevronDown, + iconCustomerService, + iconHeart, + IconSvg, + MobileTopRight, + PlaceholderFab, + type UseNavigationSelection, +} from '@graphcommerce/next-ui' +import { t } from '@lingui/core/macro' +import { Trans } from '@lingui/react/macro' +import { Fab } from '@mui/material' +import { productListRenderer } from '../ProductListItems/productListRenderer' +import { HeaderContainer } from './HeaderContainer' +import type { LayoutQuery } from './Layout.gql' +import { Logo } from './Logo' + +export type HeaderProps = LayoutQuery & { selection: UseNavigationSelection } + +export function Header(props: HeaderProps) { + const { menu, selection } = props + const cartEnabled = useCartEnabled() + + return ( + + + + + {menu?.items?.[0]?.children?.slice(0, 2).map((item) => ( + + {item?.name} + + ))} + + selection.set([menu?.items?.[0]?.uid || ''])} + onKeyUp={(evt) => { + if (evt.key === 'Enter') { + selection.set([menu?.items?.[0]?.uid || '']) + } + }} + tabIndex={0} + > + {menu?.items?.[0]?.name} + + + + + Blog + + + + + + + + + + } /> + + {/* The placeholder exists because the CartFab is sticky but we want to reserve the space for the */} + {cartEnabled && } + + + + + + + + ) +} diff --git a/examples/magento-graphcms/components/Layout/HeaderContainer.tsx b/examples/magento-graphcms/components/Layout/HeaderContainer.tsx new file mode 100644 index 00000000000..790de26bde8 --- /dev/null +++ b/examples/magento-graphcms/components/Layout/HeaderContainer.tsx @@ -0,0 +1,40 @@ +import type { ContainerSizingProps } from '@graphcommerce/next-ui' +import { Container, sxx } from '@graphcommerce/next-ui' + +export type HeaderContainerProps = ContainerSizingProps + +export function HeaderContainer(props: HeaderContainerProps) { + const { children, sx, ...containerProps } = props + + return ( + ({ + zIndex: theme.zIndex.appBar - 1, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + height: theme.appShell.headerHeightSm, + pointerEvents: 'none', + '& > *': { + pointerEvents: 'all', + }, + [theme.breakpoints.up('md')]: { + height: theme.appShell.headerHeightMd, + top: 0, + display: 'flex', + justifyContent: 'left', + width: '100%', + }, + }), + sx, + )} + > + {children} + + ) +} diff --git a/examples/magento-graphcms/components/Layout/LayoutDefault.tsx b/examples/magento-graphcms/components/Layout/LayoutDefault.tsx index 8f2fe5288e6..2de89aaf05d 100644 --- a/examples/magento-graphcms/components/Layout/LayoutDefault.tsx +++ b/examples/magento-graphcms/components/Layout/LayoutDefault.tsx @@ -75,38 +75,7 @@ export function LayoutDefault(props: LayoutDefaultProps) { {beforeHeader} - ({ - zIndex: theme.zIndex.appBar - 1, - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - height: theme.appShell.headerHeightSm, - pointerEvents: 'none', - '& > *': { - pointerEvents: 'all', - }, - [theme.breakpoints.up('md')]: { - height: theme.appShell.headerHeightMd, - top: 0, - display: 'flex', - justifyContent: 'left', - width: '100%', - }, - '&.sticky': { - [theme.breakpoints.down('md')]: { - position: 'sticky', - top: 0, - }, - }, - })} - > - {header} - + {header} {menuFab || cartFab ? ( } + header={ + + + + } footer={