@graphcommerce/next-ui 4.11.1 → 4.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -82,7 +82,7 @@ export function ActionCard(props: ActionCardProps) {
82
82
  gridTemplateAreas: `
83
83
  "image title action"
84
84
  "image details ${price ? 'price' : 'details'}"
85
- "image secondaryActio additionalDetails"
85
+ "image secondaryAction additionalDetails"
86
86
  "after after after"
87
87
  `,
88
88
  justifyContent: 'unset',
package/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  # Change Log
2
2
 
3
+ ## 4.13.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#1522](https://github.com/graphcommerce-org/graphcommerce/pull/1522) [`8d8fda262`](https://github.com/graphcommerce-org/graphcommerce/commit/8d8fda2623e561cb43441110c67ffa34b692668a) Thanks [@ErwinOtten](https://github.com/ErwinOtten)! - Introducing a new Navigation component that builds on the existing navigation component and tries to address the 'mega menu' question where there are tons of categories that need to be navigated quickly.
8
+
9
+ * [#1522](https://github.com/graphcommerce-org/graphcommerce/pull/1522) [`cefa7b365`](https://github.com/graphcommerce-org/graphcommerce/commit/cefa7b3652b55108d2178927e3c5d98a111cf373) Thanks [@ErwinOtten](https://github.com/ErwinOtten)! - Introducting a new Overlay component with is the generic part of LayoutOverlay into OverlayBase. The new Overlay is used to render the new Navigation component.
10
+
11
+ ### Patch Changes
12
+
13
+ - Updated dependencies [[`584b683a2`](https://github.com/graphcommerce-org/graphcommerce/commit/584b683a2aedcdf5067644c8dcc0e63a5b9e894c)]:
14
+ - @graphcommerce/framer-scroller@2.1.22
15
+
16
+ ## 4.12.0
17
+
18
+ ### Minor Changes
19
+
20
+ - [#1534](https://github.com/graphcommerce-org/graphcommerce/pull/1534) [`c756f42e5`](https://github.com/graphcommerce-org/graphcommerce/commit/c756f42e503761a497e4a5a7a02d02141df231c3) Thanks [@mikekeehnen](https://github.com/mikekeehnen)! - Added the method title to the action card title for shipping methods.
21
+
22
+ ### Patch Changes
23
+
24
+ - Updated dependencies []:
25
+ - @graphcommerce/framer-scroller@2.1.21
26
+
27
+ ## 4.11.2
28
+
29
+ ### Patch Changes
30
+
31
+ - [#1538](https://github.com/graphcommerce-org/graphcommerce/pull/1538) [`fe4baa42d`](https://github.com/graphcommerce-org/graphcommerce/commit/fe4baa42db0081ed960d62aef688bd36a7ac974f) Thanks [@paales](https://github.com/paales)! - add missing translations
32
+
33
+ - Updated dependencies []:
34
+ - @graphcommerce/framer-scroller@2.1.20
35
+
3
36
  ## 4.11.1
4
37
 
5
38
  ### Patch Changes
@@ -60,7 +60,7 @@ export function LayoutHeader(props: LayoutHeaderProps) {
60
60
  let left = secondary
61
61
  let right = primary
62
62
 
63
- if (back) left = back
63
+ if (back && !secondary) left = back
64
64
 
65
65
  if (!right) right = close
66
66
  else if (!left) left = close
@@ -46,7 +46,7 @@ export function LayoutHeaderBack(props: BackProps) {
46
46
  const backIcon = <IconSvg src={iconChevronLeft} size='medium' />
47
47
  const canClickBack = backSteps > 0 && path !== prevUp?.href
48
48
 
49
- let label = i18n._(/* i18n */ `Back`)
49
+ let label = i18n._(/* i18n */ 'Back')
50
50
  if (up?.href === path && up?.title) label = up.title
51
51
  if (prevUp?.href === path && prevUp?.title) label = prevUp.title
52
52
 
@@ -1,10 +1,13 @@
1
1
  import { useMotionValueValue } from '@graphcommerce/framer-utils'
2
- import { Box, SxProps, Theme } from '@mui/material'
2
+ import { Box, styled, SxProps, Theme } from '@mui/material'
3
+ import { m } from 'framer-motion'
3
4
  import React, { useRef } from 'react'
4
5
  import { extendableComponent } from '../../Styles'
5
6
  import { useScrollY } from '../hooks/useScrollY'
6
7
  import { FloatingProps } from './LayoutHeadertypes'
7
8
 
9
+ const MotionDiv = styled(m.div)({})
10
+
8
11
  export type LayoutHeaderContentProps = FloatingProps & {
9
12
  children?: React.ReactNode
10
13
  left?: React.ReactNode
@@ -139,7 +142,7 @@ export function LayoutHeaderContent(props: LayoutHeaderContentProps) {
139
142
  justifyContent: 'start',
140
143
  })}
141
144
  >
142
- {left}
145
+ <MotionDiv layout='position'>{left}</MotionDiv>
143
146
  </Box>
144
147
  )}
145
148
  <Box
@@ -172,7 +175,7 @@ export function LayoutHeaderContent(props: LayoutHeaderContentProps) {
172
175
  },
173
176
  })}
174
177
  >
175
- {children}
178
+ <MotionDiv layout='position'>{children}</MotionDiv>
176
179
  </Box>
177
180
  <Box
178
181
  className={classes.right}
@@ -188,7 +191,7 @@ export function LayoutHeaderContent(props: LayoutHeaderContentProps) {
188
191
  justifyContent: 'end',
189
192
  })}
190
193
  >
191
- {right}
194
+ <MotionDiv layout='position'>{right}</MotionDiv>
192
195
  </Box>
193
196
  {divider && (
194
197
  <Box
@@ -104,7 +104,7 @@ export function LayoutDefault(props: LayoutDefaultProps) {
104
104
  justifyContent: 'space-between',
105
105
  width: '100%',
106
106
  height: 0,
107
- zIndex: 'drawer',
107
+ zIndex: 'speedDial',
108
108
  [theme.breakpoints.up('sm')]: {
109
109
  padding: `0 ${theme.page.horizontal}`,
110
110
  position: 'sticky',
@@ -1,10 +1,13 @@
1
+ import { usePageContext, useGo, useScrollOffset } from '@graphcommerce/framer-next-pages'
1
2
  import { ScrollerProvider, ScrollSnapType } from '@graphcommerce/framer-scroller'
3
+ import { usePresence } from 'framer-motion'
2
4
  import type { SetOptional } from 'type-fest'
3
- import { LayoutOverlayBase, LayoutOverlayBaseProps } from './LayoutOverlayBase'
5
+ import { OverlayBase, LayoutOverlayBaseProps } from '../../Overlay/components/OverlayBase'
4
6
 
5
- export type { LayoutOverlayVariant } from './LayoutOverlayBase'
6
-
7
- export type LayoutOverlayProps = SetOptional<LayoutOverlayBaseProps, 'variantSm' | 'variantMd'>
7
+ export type LayoutOverlayProps = Omit<
8
+ SetOptional<LayoutOverlayBaseProps, 'variantSm' | 'variantMd'>,
9
+ 'active' | 'direction' | 'onClosed' | 'offsetPageY' | 'isPresent' | 'safeToRemove'
10
+ >
8
11
 
9
12
  export function LayoutOverlay(props: LayoutOverlayProps) {
10
13
  const { children, variantSm = 'bottom', variantMd = 'right', ...otherProps } = props
@@ -14,11 +17,26 @@ export function LayoutOverlay(props: LayoutOverlayProps) {
14
17
  const scrollSnapTypeMd: ScrollSnapType =
15
18
  variantMd === 'left' || variantMd === 'right' ? 'inline mandatory' : 'block proximity'
16
19
 
20
+ const { closeSteps, active, direction } = usePageContext()
21
+ const onCloseHandler = useGo(closeSteps * -1)
22
+ const offsetPageY = useScrollOffset().y
23
+ const [isPresent, safeToRemove] = usePresence()
24
+
17
25
  return (
18
26
  <ScrollerProvider scrollSnapTypeSm={scrollSnapTypeSm} scrollSnapTypeMd={scrollSnapTypeMd}>
19
- <LayoutOverlayBase variantMd={variantMd} variantSm={variantSm} {...otherProps}>
27
+ <OverlayBase
28
+ active={active}
29
+ direction={direction}
30
+ onClosed={onCloseHandler}
31
+ offsetPageY={offsetPageY}
32
+ variantMd={variantMd}
33
+ variantSm={variantSm}
34
+ isPresent={isPresent}
35
+ safeToRemove={safeToRemove}
36
+ {...otherProps}
37
+ >
20
38
  {children}
21
- </LayoutOverlayBase>
39
+ </OverlayBase>
22
40
  </ScrollerProvider>
23
41
  )
24
42
  }
@@ -43,7 +43,7 @@ export function DesktopNavBar(props: MenuTabsProps) {
43
43
  sx={(theme) => ({
44
44
  gridArea: `1 / 1 / 1 / 4`,
45
45
  columnGap: theme.spacings.md,
46
- padding: `0 ${theme.spacings.sm}`,
46
+ padding: `0 ${theme.spacings.md}`,
47
47
  gridAutoColumns: 'min-content',
48
48
  })}
49
49
  className={classes.scroller}
@@ -5,11 +5,45 @@ import { extendableComponent } from '../Styles/extendableComponent'
5
5
 
6
6
  const { classes, selectors } = extendableComponent('DesktopNavItem', ['root', 'line'] as const)
7
7
 
8
- export type DesktopNavItemProps = LinkProps & Pick<PageLinkProps, 'href'>
8
+ export type DesktopNavItemLinkProps = LinkProps<'a'> & Pick<PageLinkProps, 'href'>
9
+ export type DesktopNavItemButtonProps = LinkProps<'div'> & {
10
+ onClick: LinkProps<'button'>['onClick']
11
+ }
12
+
13
+ function isLinkProps(
14
+ props: DesktopNavItemLinkProps | DesktopNavItemButtonProps,
15
+ ): props is DesktopNavItemLinkProps {
16
+ return 'href' in props
17
+ }
18
+
19
+ export function DesktopNavItem(props: DesktopNavItemLinkProps | DesktopNavItemButtonProps) {
20
+ const router = useRouter()
21
+
22
+ if (!isLinkProps(props)) {
23
+ const { onClick, children, sx = [], ...linkProps } = props
24
+
25
+ return (
26
+ <Link
27
+ className={classes.root}
28
+ component='div'
29
+ variant='h6'
30
+ color='text.primary'
31
+ underline='none'
32
+ {...linkProps}
33
+ onClick={onClick}
34
+ sx={[
35
+ { whiteSpace: 'nowrap', paddingTop: '6px', cursor: 'pointer' },
36
+ ...(Array.isArray(sx) ? sx : [sx]),
37
+ ]}
38
+ >
39
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>{children}</Box>
40
+ </Link>
41
+ )
42
+ }
9
43
 
10
- export function DesktopNavItem(props: DesktopNavItemProps) {
11
44
  const { href, children, sx = [], ...linkProps } = props
12
- const active = useRouter().asPath.startsWith(href.toString())
45
+
46
+ const active = router.asPath.startsWith(href.toString())
13
47
 
14
48
  return (
15
49
  <PageLink href={href} passHref>
@@ -21,7 +55,7 @@ export function DesktopNavItem(props: DesktopNavItemProps) {
21
55
  {...linkProps}
22
56
  sx={[{ whiteSpace: 'nowrap', paddingTop: '6px' }, ...(Array.isArray(sx) ? sx : [sx])]}
23
57
  >
24
- {children}
58
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>{children}</Box>
25
59
  <Box
26
60
  component='span'
27
61
  className={classes.line}
@@ -1,6 +1,6 @@
1
1
  import { ListItemButton, ListItemIcon, ListItemText, SxProps, Theme } from '@mui/material'
2
2
  import PageLink from 'next/link'
3
- import router from 'next/router'
3
+ import { useRouter } from 'next/router'
4
4
  import React from 'react'
5
5
  import { extendableComponent } from '../Styles'
6
6
 
@@ -17,6 +17,7 @@ const { classes } = extendableComponent(compName, parts)
17
17
 
18
18
  export function MenuFabSecondaryItem(props: FabMenuSecondaryItemProps) {
19
19
  const { href, children, icon, sx = [] } = props
20
+ const router = useRouter()
20
21
 
21
22
  return (
22
23
  <PageLink href={href} passHref>
@@ -0,0 +1,106 @@
1
+ import { useMotionValueValue } from '@graphcommerce/framer-utils'
2
+ import { Fab, styled, Box, SxProps, Theme, FabProps } from '@mui/material'
3
+ import { m } from 'framer-motion'
4
+ import { useRouter } from 'next/router'
5
+ import React, { useEffect } from 'react'
6
+ import { IconSvg } from '../../IconSvg'
7
+ import { useScrollY } from '../../Layout/hooks/useScrollY'
8
+ import { useFabAnimation } from '../../LayoutParts/useFabAnimation'
9
+ import { extendableComponent } from '../../Styles/extendableComponent'
10
+ import { useFabSize } from '../../Theme'
11
+ import { iconMenu, iconClose } from '../../icons'
12
+
13
+ const MotionDiv = styled(m.div)({})
14
+
15
+ export type NavigationFabProps = {
16
+ menuIcon?: React.ReactNode
17
+ closeIcon?: React.ReactNode
18
+ sx?: SxProps<Theme>
19
+ } & Pick<FabProps, 'color' | 'size' | 'variant' | 'onClick'>
20
+
21
+ const name = 'MenuFab'
22
+ const parts = ['wrapper', 'fab', 'shadow', 'menu'] as const
23
+ type OwnerState = {
24
+ scrolled: boolean
25
+ }
26
+
27
+ const { withState } = extendableComponent<OwnerState, typeof name, typeof parts>(name, parts)
28
+
29
+ export function NavigationFab(props: NavigationFabProps) {
30
+ const { menuIcon, closeIcon, sx = [], ...fabProps } = props
31
+ const router = useRouter()
32
+ const [openEl, setOpenEl] = React.useState<null | HTMLElement>(null)
33
+
34
+ const { opacity, shadowOpacity } = useFabAnimation()
35
+ const scrollY = useScrollY()
36
+ const scrolled = useMotionValueValue(scrollY, (y) => y > 10)
37
+
38
+ useEffect(() => {
39
+ const clear = () => setOpenEl(null)
40
+ router.events.on('routeChangeStart', clear)
41
+ return () => router.events.off('routeChangeStart', clear)
42
+ }, [router.events])
43
+
44
+ const fabIconSize = useFabSize('responsive')
45
+
46
+ const classes = withState({ scrolled })
47
+
48
+ return (
49
+ <Box
50
+ sx={[
51
+ { position: 'relative', width: fabIconSize, height: fabIconSize },
52
+ ...(Array.isArray(sx) ? sx : [sx]),
53
+ ]}
54
+ >
55
+ <MotionDiv
56
+ className={classes.wrapper}
57
+ sx={(theme) => ({
58
+ [theme.breakpoints.down('md')]: {
59
+ opacity: '1 !important',
60
+ transform: 'none !important',
61
+ },
62
+ })}
63
+ style={{ opacity }}
64
+ >
65
+ <Fab
66
+ color='inherit'
67
+ aria-label='Open Menu'
68
+ size='responsive'
69
+ sx={(theme) => ({
70
+ boxShadow: 'none',
71
+ '&:hover, &:focus': {
72
+ boxShadow: 'none',
73
+ background: theme.palette.text.primary,
74
+ },
75
+ background: theme.palette.text.primary,
76
+ pointerEvents: 'all',
77
+ color: theme.palette.background.paper,
78
+ })}
79
+ className={classes.fab}
80
+ {...fabProps}
81
+ >
82
+ {closeIcon ?? (
83
+ <IconSvg src={iconClose} size='large' sx={{ display: openEl ? 'block' : 'none' }} />
84
+ )}
85
+ {menuIcon ?? (
86
+ <IconSvg src={iconMenu} size='large' sx={{ display: openEl ? 'none' : 'block' }} />
87
+ )}
88
+ </Fab>
89
+ <MotionDiv
90
+ sx={(theme) => ({
91
+ pointerEvents: 'none',
92
+ borderRadius: '99em',
93
+ position: 'absolute',
94
+ height: '100%',
95
+ width: '100%',
96
+ boxShadow: theme.shadows[6],
97
+ top: 0,
98
+ [theme.breakpoints.down('md')]: { opacity: '1 !important' },
99
+ })}
100
+ className={classes.shadow}
101
+ style={{ opacity: shadowOpacity }}
102
+ />
103
+ </MotionDiv>
104
+ </Box>
105
+ )
106
+ }
@@ -0,0 +1,147 @@
1
+ /* eslint-disable @typescript-eslint/no-use-before-define */
2
+ import { Box, ListItemButton, styled, useEventCallback } from '@mui/material'
3
+ import PageLink from 'next/link'
4
+ import { IconSvg } from '../../IconSvg'
5
+ import { extendableComponent } from '../../Styles/extendableComponent'
6
+ import { iconChevronRight } from '../../icons'
7
+ import {
8
+ isNavigationButton,
9
+ isNavigationComponent,
10
+ isNavigationHref,
11
+ NavigationNode,
12
+ NavigationPath,
13
+ useNavigation,
14
+ } from '../hooks/useNavigation'
15
+ import type { NavigationList } from './NavigationList'
16
+
17
+ type OwnerState = {
18
+ first?: boolean
19
+ last?: boolean
20
+ // It is actually used.
21
+ // eslint-disable-next-line react/no-unused-prop-types
22
+ column: number
23
+ }
24
+
25
+ type NavigationItemProps = NavigationNode & {
26
+ parentPath: NavigationPath
27
+ idx: number
28
+ NavigationList: typeof NavigationList
29
+ } & OwnerState
30
+
31
+ const componentName = 'NavigationItem'
32
+ const parts = ['li', 'ul', 'item'] as const
33
+
34
+ const { withState } = extendableComponent<OwnerState, typeof componentName, typeof parts>(
35
+ componentName,
36
+ parts,
37
+ )
38
+
39
+ const NavigationLI = styled('li')({ display: 'contents' })
40
+
41
+ export function NavigationItem(props: NavigationItemProps) {
42
+ const { id, parentPath, idx, first, last, NavigationList } = props
43
+
44
+ const row = idx + 1
45
+ const { selected, select, hideRootOnNavigate, onClose } = useNavigation()
46
+
47
+ const itemPath = [...parentPath, id]
48
+ const isSelected = selected.slice(0, itemPath.length).join('/') === itemPath.join('/')
49
+
50
+ const hidingRoot = hideRootOnNavigate && selected.length > 0
51
+ const hideItem = hidingRoot && itemPath.length === 1
52
+
53
+ const column = hidingRoot ? itemPath.length - 1 : itemPath.length
54
+ const classes = withState({ first, last, column: itemPath.length })
55
+
56
+ const onCloseHandler: React.MouseEventHandler<HTMLAnchorElement> = useEventCallback((e) => {
57
+ if (!isNavigationHref(props)) return
58
+ const { href } = props
59
+ onClose?.(e, href)
60
+ })
61
+
62
+ if (isNavigationButton(props)) {
63
+ const { childItems, name } = props
64
+ return (
65
+ <NavigationLI className={classes.li}>
66
+ <ListItemButton
67
+ className={classes.item}
68
+ role='button'
69
+ sx={{
70
+ gridRowStart: row,
71
+ gridColumnStart: column,
72
+ gap: (theme) => theme.spacings.xxs,
73
+ display: hideItem ? 'none' : 'flex',
74
+ }}
75
+ disabled={isSelected}
76
+ tabIndex={selected.join(',').includes(parentPath.join(',')) ? undefined : -1}
77
+ onClick={(e) => {
78
+ e.preventDefault()
79
+ if (!isSelected) select(itemPath)
80
+ }}
81
+ >
82
+ <Box
83
+ component='span'
84
+ sx={{
85
+ whiteSpace: 'nowrap',
86
+ overflowX: 'hidden',
87
+ textOverflow: 'ellipsis',
88
+ flexGrow: 1,
89
+ }}
90
+ >
91
+ {name}
92
+ </Box>
93
+ <IconSvg src={iconChevronRight} sx={{ flexShrink: 0 }} />
94
+ </ListItemButton>
95
+
96
+ <NavigationList items={childItems} selected={isSelected} parentPath={itemPath} />
97
+ </NavigationLI>
98
+ )
99
+ }
100
+
101
+ if (isNavigationHref(props)) {
102
+ const { name, href } = props
103
+ return (
104
+ <NavigationLI sx={[hideItem && { display: 'none' }]} className={classes.li}>
105
+ <PageLink href={href} passHref>
106
+ <ListItemButton
107
+ className={classes.item}
108
+ component='a'
109
+ sx={(theme) => ({
110
+ gridRowStart: row,
111
+ gridColumnStart: column,
112
+ gap: theme.spacings.xxs,
113
+ })}
114
+ tabIndex={selected.join(',').includes(parentPath.join(',')) ? undefined : -1}
115
+ onClick={onCloseHandler}
116
+ >
117
+ <Box
118
+ component='span'
119
+ sx={{
120
+ whiteSpace: 'nowrap',
121
+ overflowX: 'hidden',
122
+ textOverflow: 'ellipsis',
123
+ }}
124
+ >
125
+ {name}
126
+ </Box>
127
+ </ListItemButton>
128
+ </PageLink>
129
+ </NavigationLI>
130
+ )
131
+ }
132
+
133
+ if (isNavigationComponent(props)) {
134
+ const { component } = props
135
+ return (
136
+ <NavigationLI sx={[hideItem && { display: 'none' }]} className={classes.li}>
137
+ <Box sx={{ gridRowStart: row, gridColumnStart: column }} className={classes.item}>
138
+ {component}
139
+ </Box>
140
+ </NavigationLI>
141
+ )
142
+ }
143
+
144
+ if (process.env.NODE_ENV !== 'production') throw Error('NavigationItem: unknown type')
145
+
146
+ return null
147
+ }
@@ -0,0 +1,50 @@
1
+ import { styled } from '@mui/material'
2
+ import { extendableComponent } from '../../Styles/extendableComponent'
3
+ import { NavigationNode, NavigationPath } from '../hooks/useNavigation'
4
+ import { NavigationItem } from './NavigationItem'
5
+
6
+ const NavigationUList = styled('ul')({})
7
+
8
+ type NavigationItemsProps = {
9
+ parentPath?: NavigationPath
10
+ items: NavigationNode[]
11
+ selected?: boolean
12
+ }
13
+
14
+ type OwnerState = {
15
+ column: number
16
+ }
17
+
18
+ const name = 'NavigationList'
19
+ const parts = ['root'] as const
20
+ const { withState } = extendableComponent<OwnerState, typeof name, typeof parts>(name, parts)
21
+
22
+ // const componentName = 'NavigationItem'
23
+ // const parts = ['li', 'ul', 'item'] as const
24
+
25
+ export function NavigationList(props: NavigationItemsProps) {
26
+ const { items, parentPath = [], selected = false } = props
27
+
28
+ return (
29
+ <NavigationUList
30
+ sx={[
31
+ { display: 'block', position: 'absolute', left: '-10000px', top: '-10000px' },
32
+ selected && { display: 'contents' },
33
+ ]}
34
+ className={withState({ column: 0 }).root}
35
+ >
36
+ {items.map((item, idx) => (
37
+ <NavigationItem
38
+ NavigationList={NavigationList}
39
+ key={item.id}
40
+ {...item}
41
+ parentPath={parentPath}
42
+ idx={idx}
43
+ first={idx === 0}
44
+ last={idx === items.length - 1}
45
+ column={0}
46
+ />
47
+ ))}
48
+ </NavigationUList>
49
+ )
50
+ }
@@ -0,0 +1,236 @@
1
+ import styled from '@emotion/styled'
2
+ import { i18n } from '@lingui/core'
3
+ import { Trans } from '@lingui/react'
4
+ import { Box, Fab, SxProps, Theme, useEventCallback, useMediaQuery } from '@mui/material'
5
+ import { m } from 'framer-motion'
6
+ import { IconSvg, useIconSvgSize } from '../../IconSvg'
7
+ import { LayoutHeaderContent } from '../../Layout/components/LayoutHeaderContent'
8
+ import { LayoutTitle } from '../../Layout/components/LayoutTitle'
9
+ import { Overlay } from '../../Overlay/components/Overlay'
10
+ import { extendableComponent } from '../../Styles/extendableComponent'
11
+ import { useFabSize } from '../../Theme'
12
+ import { iconClose, iconChevronLeft } from '../../icons'
13
+ import {
14
+ isNavigationButton,
15
+ isNavigationComponent,
16
+ NavigationContextType,
17
+ NavigationNodeButton,
18
+ NavigationNodeHref,
19
+ useNavigation,
20
+ } from '../hooks/useNavigation'
21
+ import { NavigationList } from './NavigationList'
22
+
23
+ type NavigationOverlayProps = {
24
+ active: boolean
25
+ sx?: SxProps<Theme>
26
+ stretchColumns?: boolean
27
+ itemWidth: string
28
+ }
29
+
30
+ function findCurrent(
31
+ items: NavigationContextType['items'],
32
+ selected: NavigationContextType['selected'],
33
+ ): NavigationNodeHref | NavigationNodeButton | undefined {
34
+ const lastItem = selected.slice(-1)[0]
35
+
36
+ if (!lastItem) return undefined
37
+
38
+ for (const item of items) {
39
+ // eslint-disable-next-line no-continue
40
+ if (isNavigationComponent(item)) continue
41
+
42
+ // If the item is the current one, return it
43
+ if (item.id === lastItem) return item
44
+
45
+ // Recursively find item
46
+ if (isNavigationButton(item)) return findCurrent(item.childItems, selected)
47
+ }
48
+ return undefined
49
+ }
50
+
51
+ const MotionDiv = styled(m.div)()
52
+
53
+ const componentName = 'Navigation'
54
+ const parts = ['root', 'navigation', 'header', 'column'] as const
55
+ const { classes } = extendableComponent(componentName, parts)
56
+
57
+ export function NavigationOverlay(props: NavigationOverlayProps) {
58
+ const { active, sx, stretchColumns, itemWidth } = props
59
+ const { selected, select, items, onClose } = useNavigation()
60
+
61
+ const fabSize = useFabSize('responsive')
62
+ const svgSize = useIconSvgSize('large')
63
+
64
+ const isMobile = useMediaQuery<Theme>((theme) => theme.breakpoints.down('md'))
65
+ const handleOnBack = useEventCallback(() => {
66
+ if (isMobile) select(selected.slice(0, -1))
67
+ else select([])
68
+ })
69
+
70
+ const showBack = selected.length > 0
71
+
72
+ return (
73
+ <Overlay
74
+ className={classes.root}
75
+ active={active}
76
+ onClosed={onClose}
77
+ variantSm='left'
78
+ sizeSm='floating'
79
+ justifySm='start'
80
+ variantMd='left'
81
+ sizeMd='floating'
82
+ justifyMd='start'
83
+ sx={{
84
+ zIndex: 'drawer',
85
+ '& .LayoutOverlayBase-overlayPane': {
86
+ minWidth: 'auto !important',
87
+ width: 'max-content',
88
+ overflow: 'hidden',
89
+ display: 'grid',
90
+ gridTemplateRows: 'auto 1fr',
91
+ },
92
+ }}
93
+ >
94
+ <MotionDiv layout style={{ display: 'grid' }}>
95
+ <Box
96
+ className={classes.header}
97
+ sx={(theme) => ({
98
+ top: 0,
99
+ position: 'sticky',
100
+ height: { xs: theme.appShell.headerHeightSm, md: theme.appShell.appBarHeightMd },
101
+ zIndex: 1,
102
+ })}
103
+ >
104
+ <LayoutHeaderContent
105
+ floatingMd={false}
106
+ floatingSm={false}
107
+ switchPoint={0}
108
+ left={
109
+ showBack && (
110
+ <Fab
111
+ color='inherit'
112
+ onClick={handleOnBack}
113
+ sx={{
114
+ boxShadow: 'none',
115
+ marginLeft: `calc((${fabSize} - ${svgSize}) * -0.5)`,
116
+ marginRight: `calc((${fabSize} - ${svgSize}) * -0.5)`,
117
+ }}
118
+ size='responsive'
119
+ aria-label={i18n._(/* i18n */ 'Back')}
120
+ >
121
+ <IconSvg src={iconChevronLeft} size='large' aria-hidden />
122
+ </Fab>
123
+ )
124
+ }
125
+ right={
126
+ <Fab
127
+ color='inherit'
128
+ onClick={() => onClose()}
129
+ sx={{
130
+ boxShadow: 'none',
131
+ marginLeft: `calc((${fabSize} - ${svgSize}) * -0.5)`,
132
+ marginRight: `calc((${fabSize} - ${svgSize}) * -0.5)`,
133
+ }}
134
+ size='responsive'
135
+ aria-label={i18n._(/* i18n */ 'Close')}
136
+ >
137
+ <IconSvg src={iconClose} size='large' aria-hidden />
138
+ </Fab>
139
+ }
140
+ >
141
+ <LayoutTitle size='small' component='span'>
142
+ {findCurrent(items, selected)?.name ?? <Trans id='Menu' />}
143
+ </LayoutTitle>
144
+ </LayoutHeaderContent>
145
+ </Box>
146
+ </MotionDiv>
147
+
148
+ <MotionDiv layout='position' style={{ display: 'grid' }}>
149
+ <Box
150
+ sx={(theme) => ({
151
+ display: 'grid',
152
+ alignItems: !stretchColumns ? 'start' : undefined,
153
+
154
+ [theme.breakpoints.down('md')]: {
155
+ overflow: 'hidden',
156
+ scrollSnapType: 'x mandatory',
157
+ width: `calc(${theme.spacings.md} + ${theme.spacings.md} + ${itemWidth})`,
158
+ },
159
+ '& .NavigationItem-item': {
160
+ width: itemWidth,
161
+ },
162
+ })}
163
+ >
164
+ <Box
165
+ className={classes.navigation}
166
+ sx={[
167
+ (theme) => ({
168
+ py: theme.spacings.md,
169
+ display: 'grid',
170
+ gridAutoFlow: 'column',
171
+ scrollSnapAlign: 'end',
172
+ '& > ul > li > a, & > ul > li > [role=button]': {
173
+ '& span': {
174
+ typography: 'h2',
175
+ },
176
+ // '& svg': { display: 'none' },
177
+ },
178
+ '& .Navigation-column': {},
179
+ '& .NavigationItem-item': {
180
+ mx: theme.spacings.md,
181
+ whiteSpace: 'nowrap',
182
+ },
183
+ '& .NavigationItem-item.first': {
184
+ // mt: theme.spacings.md,
185
+ },
186
+ '& .Navigation-column:first-of-type': {
187
+ boxShadow: 'none',
188
+ },
189
+ }),
190
+ ...(Array.isArray(sx) ? sx : [sx]),
191
+ ]}
192
+ >
193
+ {selected.length >= 0 && (
194
+ <Box
195
+ sx={(theme) => ({
196
+ gridArea: '1 / 1 / 999 / 2',
197
+ boxShadow: `inset 1px 0 ${theme.palette.divider}`,
198
+ })}
199
+ className={classes.column}
200
+ />
201
+ )}
202
+ {selected.length >= 1 && (
203
+ <Box
204
+ sx={(theme) => ({
205
+ gridArea: '1 / 2 / 999 / 3',
206
+ boxShadow: `inset 1px 0 ${theme.palette.divider}`,
207
+ })}
208
+ className={classes.column}
209
+ />
210
+ )}
211
+ {selected.length >= 2 && (
212
+ <Box
213
+ sx={(theme) => ({
214
+ gridArea: '1 / 3 / 999 / 4',
215
+ boxShadow: `inset 1px 0 ${theme.palette.divider}`,
216
+ })}
217
+ className={classes.column}
218
+ />
219
+ )}
220
+ {selected.length >= 3 && (
221
+ <Box
222
+ sx={(theme) => ({
223
+ gridArea: '1 / 4 / 999 / 5',
224
+ boxShadow: `inset 1px 0 ${theme.palette.divider}`,
225
+ })}
226
+ className={classes.column}
227
+ />
228
+ )}
229
+
230
+ <NavigationList items={items} selected />
231
+ </Box>
232
+ </Box>
233
+ </MotionDiv>
234
+ </Overlay>
235
+ )
236
+ }
@@ -0,0 +1,66 @@
1
+ import { useEventCallback } from '@mui/material'
2
+ import { MotionConfig } from 'framer-motion'
3
+ import { useState, useMemo } from 'react'
4
+ import { isElement } from 'react-is'
5
+ import {
6
+ NavigationNode,
7
+ NavigationPath,
8
+ NavigationContextType,
9
+ NavigationContext,
10
+ NavigationSelect,
11
+ } from '../hooks/useNavigation'
12
+
13
+ export type NavigationProviderProps = {
14
+ items: (NavigationNode | React.ReactElement)[]
15
+ hideRootOnNavigate?: boolean
16
+ closeAfterNavigate?: boolean
17
+ children?: React.ReactNode
18
+ animationDuration?: number
19
+ onChange?: NavigationSelect
20
+ onClose?: NavigationContextType['onClose']
21
+ }
22
+
23
+ const nonNullable = <T,>(value: T): value is NonNullable<T> => value !== null && value !== undefined
24
+
25
+ export function NavigationProvider(props: NavigationProviderProps) {
26
+ const {
27
+ items,
28
+ onChange,
29
+ hideRootOnNavigate = true,
30
+ closeAfterNavigate = false,
31
+ animationDuration = 0.275,
32
+ children,
33
+ onClose: onCloseUnstable,
34
+ } = props
35
+
36
+ const [selected, setSelected] = useState<NavigationPath>([])
37
+
38
+ const select = useEventCallback((incomming: NavigationPath) => {
39
+ setSelected(incomming)
40
+ onChange?.(incomming)
41
+ })
42
+
43
+ const onClose: NavigationContextType['onClose'] = useEventCallback((e, href) => {
44
+ onCloseUnstable?.(e, href)
45
+ setTimeout(() => select([]), animationDuration * 1000)
46
+ })
47
+
48
+ const value = useMemo<NavigationContextType>(
49
+ () => ({
50
+ hideRootOnNavigate,
51
+ selected,
52
+ select,
53
+ items: items
54
+ .map((item, index) => (isElement(item) ? { id: item.key ?? index, component: item } : item))
55
+ .filter(nonNullable),
56
+ onClose,
57
+ }),
58
+ [hideRootOnNavigate, selected, select, items, onClose],
59
+ )
60
+
61
+ return (
62
+ <MotionConfig transition={{ duration: animationDuration }}>
63
+ <NavigationContext.Provider value={value}>{children}</NavigationContext.Provider>
64
+ </MotionConfig>
65
+ )
66
+ }
@@ -0,0 +1,58 @@
1
+ import { createContext, useContext } from 'react'
2
+
3
+ export type NavigationId = string | number
4
+ export type NavigationPath = NavigationId[]
5
+ export type NavigationSelect = (selected: NavigationPath) => void
6
+ export type NavigationRender = React.FC<
7
+ (NavigationNodeComponent | NavigationNodeHref) & { children?: React.ReactNode }
8
+ >
9
+
10
+ export type NavigationOnClose = (
11
+ event?: React.MouseEvent<HTMLAnchorElement>,
12
+ href?: string | undefined,
13
+ ) => void
14
+ export type NavigationContextType = {
15
+ selected: NavigationPath
16
+ select: NavigationSelect
17
+ items: NavigationNode[]
18
+ hideRootOnNavigate: boolean
19
+ onClose: NavigationOnClose
20
+ }
21
+
22
+ type NavigationNodeBase = {
23
+ id: NavigationId
24
+ }
25
+
26
+ export type NavigationNodeHref = NavigationNodeBase & {
27
+ name: string
28
+ href: string
29
+ }
30
+
31
+ export type NavigationNodeButton = NavigationNodeBase & {
32
+ name: string
33
+ childItems: NavigationNode[]
34
+ }
35
+
36
+ export type NavigationNodeComponent = NavigationNodeBase & {
37
+ component: React.ReactNode
38
+ }
39
+
40
+ export type NavigationNode = NavigationNodeHref | NavigationNodeButton | NavigationNodeComponent
41
+
42
+ export function isNavigationHref(node: NavigationNodeBase): node is NavigationNodeHref {
43
+ return 'href' in node
44
+ }
45
+
46
+ export function isNavigationButton(node: NavigationNodeBase): node is NavigationNodeButton {
47
+ return (node as NavigationNodeButton).childItems?.length > 0
48
+ }
49
+
50
+ export function isNavigationComponent(node: NavigationNodeBase): node is NavigationNodeComponent {
51
+ return 'component' in node
52
+ }
53
+
54
+ export const NavigationContext = createContext(undefined as unknown as NavigationContextType)
55
+
56
+ export function useNavigation() {
57
+ return useContext(NavigationContext)
58
+ }
@@ -0,0 +1,4 @@
1
+ export * from './components/NavigationOverlay'
2
+ export * from './components/NavigationFab'
3
+ export * from './hooks/useNavigation'
4
+ export * from './components/NavigationProvider'
@@ -0,0 +1,61 @@
1
+ import { ScrollerProvider, ScrollSnapType } from '@graphcommerce/framer-scroller'
2
+ import { Box } from '@mui/material'
3
+ import { useEffect, useState } from 'react'
4
+ import type { SetOptional } from 'type-fest'
5
+ import { OverlayBase, LayoutOverlayBaseProps } from './OverlayBase'
6
+
7
+ export type OverlayProps = Omit<
8
+ SetOptional<LayoutOverlayBaseProps, 'variantSm' | 'variantMd'>,
9
+ 'direction' | 'offsetPageY' | 'isPresent' | 'safeToRemove'
10
+ >
11
+
12
+ export function Overlay(props: OverlayProps) {
13
+ const { children, variantSm = 'bottom', variantMd = 'right', active, ...otherProps } = props
14
+
15
+ const scrollSnapTypeSm: ScrollSnapType =
16
+ variantSm === 'left' || variantSm === 'right' ? 'inline mandatory' : 'block proximity'
17
+ const scrollSnapTypeMd: ScrollSnapType =
18
+ variantMd === 'left' || variantMd === 'right' ? 'inline mandatory' : 'block proximity'
19
+
20
+ // todo: The overlay is always present in the DOM and the initial scroll position is set to 0.
21
+ // This means in this case that the overlay is visisble. LayoutOverlayBase sets the scroll position to 320 with JS.
22
+ // This would cause the overlay to be visisble for a moment before the scroll position is set.
23
+ // The solution is to set the the first scroll-snap-align and scroll-snap-stop to the open position of the overlay.
24
+ // However: We have control of the LayoutOverlayBase, we do not have control of all the child components so that solution will not work..
25
+ const [loaded, setLoaded] = useState(false)
26
+ useEffect(() => setLoaded(true), [])
27
+
28
+ return (
29
+ <Box
30
+ className='Overlay'
31
+ sx={{
32
+ position: 'fixed',
33
+ top: 0,
34
+ left: 0,
35
+ transform: loaded ? undefined : 'translateX(-200vw)',
36
+ pointerEvents: active ? undefined : 'none',
37
+ right: 0,
38
+ bottom: 0,
39
+ zIndex: 'drawer',
40
+ '& .LayoutOverlayBase-overlayPane': {
41
+ boxShadow: active ? undefined : 0,
42
+ },
43
+ }}
44
+ >
45
+ <ScrollerProvider scrollSnapTypeSm={scrollSnapTypeSm} scrollSnapTypeMd={scrollSnapTypeMd}>
46
+ <OverlayBase
47
+ offsetPageY={0}
48
+ variantMd={variantMd}
49
+ variantSm={variantSm}
50
+ direction={1}
51
+ active={active}
52
+ isPresent={active}
53
+ safeToRemove={undefined}
54
+ {...otherProps}
55
+ >
56
+ {children}
57
+ </OverlayBase>
58
+ </ScrollerProvider>
59
+ </Box>
60
+ )
61
+ }
@@ -1,4 +1,3 @@
1
- import { useGo, usePageContext, useScrollOffset } from '@graphcommerce/framer-next-pages'
2
1
  import { Scroller, useScrollerContext, useScrollTo } from '@graphcommerce/framer-scroller'
3
2
  import {
4
3
  clientSizeCssVar,
@@ -6,7 +5,7 @@ import {
6
5
  useIsomorphicLayoutEffect,
7
6
  } from '@graphcommerce/framer-utils'
8
7
  import { Box, styled, SxProps, Theme, useTheme, useThemeProps } from '@mui/material'
9
- import { m, useDomEvent, useMotionValue, usePresence, useTransform } from 'framer-motion'
8
+ import { m, useDomEvent, useMotionValue, useTransform } from 'framer-motion'
10
9
  import React, { useCallback, useEffect, useRef } from 'react'
11
10
  import { LayoutProvider } from '../../Layout/components/LayoutProvider'
12
11
  import { ExtendableComponent, extendableComponent } from '../../Styles'
@@ -35,6 +34,12 @@ export type LayoutOverlayBaseProps = {
35
34
  className?: string
36
35
  sx?: SxProps<Theme>
37
36
  sxBackdrop?: SxProps<Theme>
37
+ active: boolean
38
+ direction: 1 | -1
39
+ onClosed: () => void
40
+ offsetPageY: number
41
+ isPresent: boolean
42
+ safeToRemove: (() => void) | null | undefined
38
43
  } & StyleProps &
39
44
  OverridableProps
40
45
 
@@ -61,7 +66,7 @@ const clearScrollLock = () => {
61
66
  document.body.style.overflow = ''
62
67
  }
63
68
 
64
- export function LayoutOverlayBase(incommingProps: LayoutOverlayBaseProps) {
69
+ export function OverlayBase(incommingProps: LayoutOverlayBaseProps) {
65
70
  const props = useThemeProps({ name, props: incommingProps })
66
71
 
67
72
  const {
@@ -75,6 +80,12 @@ export function LayoutOverlayBase(incommingProps: LayoutOverlayBaseProps) {
75
80
  justifyMd = 'stretch',
76
81
  sx = [],
77
82
  sxBackdrop = [],
83
+ active,
84
+ onClosed,
85
+ direction,
86
+ offsetPageY,
87
+ isPresent,
88
+ safeToRemove,
78
89
  } = props
79
90
 
80
91
  const th = useTheme()
@@ -88,12 +99,8 @@ export function LayoutOverlayBase(incommingProps: LayoutOverlayBaseProps) {
88
99
  const { scrollerRef, snap } = useScrollerContext()
89
100
  const positions = useOverlayPosition()
90
101
  const scrollTo = useScrollTo()
91
- const [isPresent, safeToRemove] = usePresence()
92
102
  const beforeRef = useRef<HTMLDivElement>(null)
93
103
 
94
- const { closeSteps, active, direction } = usePageContext()
95
- const close = useGo(closeSteps * -1)
96
-
97
104
  const position = useMotionValue<OverlayPosition>(OverlayPosition.UNOPENED)
98
105
 
99
106
  const classes = withState({ variantSm, variantMd, sizeSm, sizeMd, justifySm, justifyMd })
@@ -105,7 +112,15 @@ export function LayoutOverlayBase(incommingProps: LayoutOverlayBaseProps) {
105
112
  // When the component is mounted, we need to set the initial position of the overlay.
106
113
  useIsomorphicLayoutEffect(() => {
107
114
  const scroller = scrollerRef.current
108
- if (!scroller || !isPresent) return undefined
115
+
116
+ if (!scroller) return undefined
117
+
118
+ if (!isPresent && position.get() === OverlayPosition.UNOPENED) {
119
+ scroller.scrollLeft = positions.closed.x.get()
120
+ scroller.scrollTop = positions.closed.y.get()
121
+ }
122
+
123
+ if (!isPresent) return undefined
109
124
 
110
125
  const open = { x: positions.open.x.get(), y: positions.open.y.get() }
111
126
 
@@ -148,7 +163,7 @@ export function LayoutOverlayBase(incommingProps: LayoutOverlayBaseProps) {
148
163
 
149
164
  // When the overlay is closed by navigating away, we're closing the overlay.
150
165
  useEffect(() => {
151
- if (isPresent) return
166
+ if (isPresent || position.get() === OverlayPosition.UNOPENED) return
152
167
  position.set(OverlayPosition.CLOSED)
153
168
  clearScrollLock()
154
169
 
@@ -164,8 +179,8 @@ export function LayoutOverlayBase(incommingProps: LayoutOverlayBaseProps) {
164
179
  if (position.get() !== OverlayPosition.OPENED) return
165
180
  position.set(OverlayPosition.CLOSED)
166
181
  clearScrollLock()
167
- close()
168
- }, [close, position])
182
+ onClosed()
183
+ }, [onClosed, position])
169
184
 
170
185
  // Handle escape key
171
186
  const windowRef = useRef(typeof window !== 'undefined' ? window : null)
@@ -187,7 +202,6 @@ export function LayoutOverlayBase(incommingProps: LayoutOverlayBaseProps) {
187
202
  }, [offsetY])
188
203
 
189
204
  // Create the exact position for the LayoutProvider which offsets the top of the overlay
190
- const offsetPageY = useScrollOffset().y
191
205
  const scrollWithoffset = useTransform(
192
206
  [scroll.y, positions.open.y, offsetY],
193
207
  ([y, openY, offsetYv]: number[]) => Math.max(0, y - openY - offsetYv + offsetPageY),
@@ -333,7 +347,6 @@ export function LayoutOverlayBase(incommingProps: LayoutOverlayBaseProps) {
333
347
  marginTop: `calc(${smSpacingTop} * -1)`,
334
348
  paddingTop: smSpacingTop,
335
349
  },
336
-
337
350
  '&.sizeSmFloating': {
338
351
  padding: `${theme.page.vertical} ${theme.page.horizontal}`,
339
352
  },
@@ -353,7 +366,8 @@ export function LayoutOverlayBase(incommingProps: LayoutOverlayBaseProps) {
353
366
  },
354
367
  })}
355
368
  >
356
- <Box
369
+ <MotionDiv
370
+ layout
357
371
  className={classes.overlayPane}
358
372
  sx={(theme) => ({
359
373
  pointerEvents: 'all',
@@ -419,7 +433,7 @@ export function LayoutOverlayBase(incommingProps: LayoutOverlayBaseProps) {
419
433
  })}
420
434
  >
421
435
  <LayoutProvider scroll={scrollWithoffset}>{children}</LayoutProvider>
422
- </Box>
436
+ </MotionDiv>
423
437
  </Box>
424
438
  </Scroller>
425
439
  </>
@@ -0,0 +1,2 @@
1
+ export * from './OverlayBase'
2
+ export * from './Overlay'
@@ -0,0 +1 @@
1
+ export * from './components'
@@ -108,11 +108,7 @@ export function DarkLightModeMenuSecondaryItem(props: ListItemButtonProps) {
108
108
  <IconSvg src={currentMode === 'light' ? iconMoon : iconSun} size='medium' />
109
109
  </ListItemIcon>
110
110
  <ListItemText>
111
- {currentMode === 'light' ? (
112
- <Trans id='Switch to Dark Mode' />
113
- ) : (
114
- <Trans id='Switch to Light Mode' />
115
- )}
111
+ {currentMode === 'light' ? <Trans id='Dark Mode' /> : <Trans id='Light Mode' />}
116
112
  </ListItemText>
117
113
  </ListItemButton>
118
114
  )
package/index.ts CHANGED
@@ -1,5 +1,5 @@
1
- export * from './ActionCard/ActionCardList'
2
1
  export * from './ActionCard/ActionCard'
2
+ export * from './ActionCard/ActionCardList'
3
3
  export * from './AnimatedRow/AnimatedRow'
4
4
  export * from './Blog/BlogAuthor/BlogAuthor'
5
5
  export * from './Blog/BlogContent/BlogContent'
@@ -22,6 +22,7 @@ export * from './Form/InputCheckmark'
22
22
  export * from './FramerScroller'
23
23
  export * from './FullPageMessage/FullPageMessage'
24
24
  export * from './Highlight/Highlight'
25
+ export * from './hooks'
25
26
  export * from './IconHeader/IconHeader'
26
27
  export * from './icons'
27
28
  export * from './IconSvg'
@@ -30,6 +31,8 @@ export * from './Layout'
30
31
  export * from './LayoutDefault'
31
32
  export * from './LayoutOverlay'
32
33
  export * from './LayoutParts'
34
+ export * from './Navigation'
35
+ export * from './Overlay'
33
36
  export * from './Page'
34
37
  export * from './PageLoadIndicator/PageLoadIndicator'
35
38
  export * from './PageMeta/PageMeta'
@@ -39,8 +42,8 @@ export * from './Row'
39
42
  export * from './SectionContainer/SectionContainer'
40
43
  export * from './SectionHeader/SectionHeader'
41
44
  export * from './Separator/Separator'
42
- export * from './Snackbar/MessageSnackbar'
43
45
  export * from './Snackbar/ErrorSnackbar'
46
+ export * from './Snackbar/MessageSnackbar'
44
47
  export * from './Snackbar/MessageSnackbarImpl'
45
48
  export * from './StarRatingField/StarRatingField'
46
49
  export * from './Stepper/Stepper'
@@ -50,6 +53,5 @@ export * from './Theme'
50
53
  export * from './TimeAgo/TimeAgo'
51
54
  export * from './ToggleButton/ToggleButton'
52
55
  export * from './ToggleButtonGroup/ToggleButtonGroup'
53
- export * from './hooks'
54
56
  export * from './UspList/UspList'
55
57
  export * from './UspList/UspListItem'
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@graphcommerce/next-ui",
3
3
  "homepage": "https://www.graphcommerce.org/",
4
4
  "repository": "github:graphcommerce-org/graphcommerce",
5
- "version": "4.11.1",
5
+ "version": "4.13.0",
6
6
  "author": "",
7
7
  "license": "MIT",
8
8
  "sideEffects": false,
@@ -20,7 +20,7 @@
20
20
  "@emotion/server": "^11.4.0",
21
21
  "@emotion/styled": "^11.6.0",
22
22
  "@graphcommerce/framer-next-pages": "3.2.3",
23
- "@graphcommerce/framer-scroller": "2.1.19",
23
+ "@graphcommerce/framer-scroller": "2.1.22",
24
24
  "@graphcommerce/framer-utils": "3.1.4",
25
25
  "@graphcommerce/image": "3.1.7",
26
26
  "react-is": "^18.1.0",