Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/kind-days-punch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@graphcommerce/magento-cart': patch
'@graphcommerce/next-ui': patch
---

Added disableScrollEffects prop to CartFab & NavigationFab for easier customization of the header
8 changes: 8 additions & 0 deletions .changeset/true-suits-design.md
Original file line number Diff line number Diff line change
@@ -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.
5 changes: 5 additions & 0 deletions .changeset/yummy-spies-change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphcommerce/next-ui': patch
---

Changed Footer props type to allow setting footer container props
82 changes: 82 additions & 0 deletions examples/magento-graphcms/components/Layout/Header.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<HeaderContainer>
<Logo />

<DesktopNavBar>
{menu?.items?.[0]?.children?.slice(0, 2).map((item) => (
<DesktopNavItem key={item?.uid} href={`/${item?.url_path}`}>
{item?.name}
</DesktopNavItem>
))}

<DesktopNavItem
onClick={() => selection.set([menu?.items?.[0]?.uid || ''])}
onKeyUp={(evt) => {
if (evt.key === 'Enter') {
selection.set([menu?.items?.[0]?.uid || ''])
}
}}
tabIndex={0}
>
{menu?.items?.[0]?.name}
<IconSvg src={iconChevronDown} />
</DesktopNavItem>

<DesktopNavItem href='/blog'>
<Trans>Blog</Trans>
</DesktopNavItem>
</DesktopNavBar>

<DesktopNavActions>
<SearchField
formControl={{ sx: { width: '400px' } }}
searchField={{ productListRenderer }}
/>
<StoreSwitcherButton />
<Fab href='/service' aria-label={t`Customer Service`} size='large' color='inherit'>
<IconSvg src={iconCustomerService} size='large' />
</Fab>
<WishlistFab icon={<IconSvg src={iconHeart} size='large' />} />
<CustomerFab guestHref='/account/signin' authHref='/account' />
{/* The placeholder exists because the CartFab is sticky but we want to reserve the space for the <CartFab /> */}
{cartEnabled && <PlaceholderFab />}
</DesktopNavActions>

<MobileTopRight>
<StoreSwitcherFab size='responsiveMedium' />
<SearchFab size='responsiveMedium' />
</MobileTopRight>
</HeaderContainer>
)
}
40 changes: 40 additions & 0 deletions examples/magento-graphcms/components/Layout/HeaderContainer.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Container
sizing='shell'
maxWidth={false}
component='header'
{...containerProps}
sx={sxx(
(theme) => ({
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}
</Container>
)
}
134 changes: 134 additions & 0 deletions examples/magento-graphcms/components/Layout/LayoutDefault.tsx
Original file line number Diff line number Diff line change
@@ -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<Theme>
} & OwnerState

type OwnerState = {
noSticky?: boolean
}
const parts = ['root', 'fabs', 'header', 'children', 'footer'] as const
const { withState } = extendableComponent<OwnerState, 'LayoutDefault', typeof parts>(
'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 (
<Box
className={`${classes.root} ${className ?? ''}`}
sx={sxx(
(theme) => ({
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,
)}
>
<SkipLink />
<LayoutProvider scroll={scrollYOffset}>
{beforeHeader}
{header}
{menuFab || cartFab ? (
<Container
sizing='shell'
maxWidth={false}
className={classes.fabs}
sx={(theme) => ({
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 && (
<Box
sx={(theme) => ({
display: 'flex',
flexDirection: 'row-reverse',
gap: theme.spacings.sm,
[theme.breakpoints.up('md')]: {
flexDirection: 'column',
alignItems: 'flex-end',
},
})}
>
{cartFab}
</Box>
)}
</Container>
) : (
<div />
)}
<div className={classes.children}>
<div id='skip-nav' tabIndex={-1} />
{children}
</div>
<div className={classes.footer}>{footer}</div>
</LayoutProvider>
</Box>
)
}
9 changes: 7 additions & 2 deletions examples/magento-graphcms/components/Layout/LayoutMinimal.tsx
Original file line number Diff line number Diff line change
@@ -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 &
Expand All @@ -12,7 +13,11 @@ export function LayoutMinimal(props: LayoutMinimalProps) {
return (
<LayoutDefault
{...uiProps}
header={<Logo />}
header={
<HeaderContainer>
<Logo />
</HeaderContainer>
}
footer={<Footer footer={footer} />}
sx={(theme) => ({ background: theme.vars.palette.background.paper })}
>
Expand Down
Loading
Loading