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/.changeset/true-suits-design.md b/.changeset/true-suits-design.md new file mode 100644 index 00000000000..7d71d6e0848 --- /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`. If you are upgrading and do not want these changes, you can just discard them. This is just a structural change for more ease of use. No visually change. diff --git a/.changeset/yummy-spies-change.md b/.changeset/yummy-spies-change.md new file mode 100644 index 00000000000..a835c9dc8d7 --- /dev/null +++ b/.changeset/yummy-spies-change.md @@ -0,0 +1,5 @@ +--- +'@graphcommerce/next-ui': patch +--- + +Changed Footer props type to allow setting footer container props 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 new file mode 100644 index 00000000000..2de89aaf05d --- /dev/null +++ b/examples/magento-graphcms/components/Layout/LayoutDefault.tsx @@ -0,0 +1,134 @@ +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} + {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/LayoutMinimal.tsx b/examples/magento-graphcms/components/Layout/LayoutMinimal.tsx index fd013541eaf..714433b4f1c 100644 --- a/examples/magento-graphcms/components/Layout/LayoutMinimal.tsx +++ b/examples/magento-graphcms/components/Layout/LayoutMinimal.tsx @@ -1,6 +1,7 @@ -import { LayoutDefault, LayoutDefaultProps } from '@graphcommerce/next-ui' import { Footer } from './Footer' +import { HeaderContainer } from './HeaderContainer' import { LayoutQuery } from './Layout.gql' +import { LayoutDefault, type LayoutDefaultProps } from './LayoutDefault' import { Logo } from './Logo' export type LayoutMinimalProps = LayoutQuery & @@ -12,7 +13,11 @@ export function LayoutMinimal(props: LayoutMinimalProps) { return ( } + header={ + + + + } footer={