@graphcommerce/next-ui 9.1.0-canary.54 → 10.0.0-canary.56

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.
Files changed (37) hide show
  1. package/ActionCard/ActionCardList.tsx +2 -2
  2. package/Breadcrumbs/Breadcrumbs.tsx +4 -4
  3. package/Breadcrumbs/BreadcrumbsList.tsx +2 -2
  4. package/CHANGELOG.md +96 -0
  5. package/FramerScroller/SidebarGallery.tsx +5 -4
  6. package/Intl/DateTimeFormat/DateTimeFormat.tsx +3 -2
  7. package/Layout/components/LayoutHeaderBack.tsx +2 -2
  8. package/Layout/components/LayoutHeaderClose.tsx +11 -8
  9. package/LayoutOverlay/components/LayoutOverlayHeader2.tsx +213 -0
  10. package/LayoutOverlay/index.ts +1 -0
  11. package/Navigation/components/NavigationList.tsx +1 -1
  12. package/Navigation/components/NavigationOverlay.tsx +3 -3
  13. package/Navigation/components/NavigationTitle.tsx +2 -2
  14. package/Overlay/components/OverlayBase.tsx +3 -3
  15. package/Overlay/components/OverlayCloseButton.tsx +25 -0
  16. package/Overlay/components/OverlayHeader2.tsx +26 -0
  17. package/Overlay/components/index.ts +4 -2
  18. package/OverlayOrPopperChip/OverlayPanelActions.tsx +5 -5
  19. package/OverlayOrPopperChip/PopperPanelActions.tsx +5 -5
  20. package/Page/types.ts +1 -1
  21. package/PageMeta/canonicalize.ts +2 -1
  22. package/Pagination/Pagination.tsx +5 -2
  23. package/SkipLink/SkipLink.tsx +2 -2
  24. package/Snackbar/ErrorSnackbar.tsx +2 -2
  25. package/Snackbar/MessageSnackbarImpl.tsx +2 -2
  26. package/Styles/withEmotionCache.tsx +1 -3
  27. package/Tabs/TabItem.tsx +134 -0
  28. package/Tabs/Tabs.tsx +124 -0
  29. package/TextInputNumber/TextInputNumber.tsx +4 -4
  30. package/Theme/DarkLightModeThemeProvider.tsx +2 -2
  31. package/Theme/NextLink.tsx +4 -4
  32. package/hooks/memoDeep.ts +1 -1
  33. package/index.ts +2 -0
  34. package/package.json +34 -16
  35. package/po.d.ts +6 -0
  36. package/{types.d.ts → types.ts} +0 -9
  37. package/utils/storefrontConfig.ts +5 -3
@@ -1,4 +1,4 @@
1
- import { Trans } from '@lingui/react'
1
+ import { Trans } from '@lingui/react/macro'
2
2
  import type { SxProps, Theme } from '@mui/material'
3
3
  import { Alert } from '@mui/material'
4
4
  import React from 'react'
@@ -168,7 +168,7 @@ export const ActionCardList = React.forwardRef<HTMLDivElement, ActionCardListPro
168
168
  variant='text'
169
169
  onClick={() => setShow(!show)}
170
170
  >
171
- {!show ? <Trans id='More options' /> : <Trans id='Less options' />}{' '}
171
+ {!show ? <Trans>More options</Trans> : <Trans>Less options</Trans>}{' '}
172
172
  <IconSvg
173
173
  sx={{
174
174
  transform: show ? 'rotate(180deg)' : 'rotate(0deg)',
@@ -1,5 +1,5 @@
1
- import { i18n } from '@lingui/core'
2
- import { Trans } from '@lingui/react'
1
+ import { t } from '@lingui/core/macro'
2
+ import { Trans } from '@lingui/react/macro'
3
3
  import type {
4
4
  LinkProps,
5
5
  BreadcrumbsProps as MuiBreadcrumbProps,
@@ -71,7 +71,7 @@ export function Breadcrumbs(props: BreadcrumbsProps) {
71
71
  return (
72
72
  <MuiBreadcrumbs
73
73
  {...rest}
74
- aria-label={i18n._(/* i18n*/ 'Breadcrumbs')}
74
+ aria-label={t`Breadcrumbs`}
75
75
  maxItems={maxItems}
76
76
  color='inherit'
77
77
  sx={[
@@ -167,7 +167,7 @@ export function Breadcrumbs(props: BreadcrumbsProps) {
167
167
  {...linkProps}
168
168
  sx={[...(Array.isArray(itemSx) ? itemSx : [itemSx])]}
169
169
  >
170
- <Trans id='Home' />
170
+ <Trans>Home</Trans>
171
171
  </Link>
172
172
  )}
173
173
  {breadcrumbLinks.map((breadcrumb) => (
@@ -1,4 +1,4 @@
1
- import { Trans } from '@lingui/react'
1
+ import { Trans } from '@lingui/react/macro'
2
2
  import { alpha, Box, Link, useTheme } from '@mui/material'
3
3
  import type { KeyboardEvent } from 'react'
4
4
  import { useEffect, useRef } from 'react'
@@ -74,7 +74,7 @@ export function BreadcrumbsList(props: PopperBreadcrumbsListProps) {
74
74
  },
75
75
  }}
76
76
  >
77
- <Trans id='Home' />
77
+ <Trans>Home</Trans>
78
78
  </Link>
79
79
  {breadcrumbs.slice(0, breadcrumbs.length).map((breadcrumb) => (
80
80
  <Link
package/CHANGELOG.md CHANGED
@@ -1,5 +1,101 @@
1
1
  # Change Log
2
2
 
3
+ ## 10.0.0-canary.56
4
+
5
+ ### Major Changes
6
+
7
+ - [#2546](https://github.com/graphcommerce-org/graphcommerce/pull/2546) [`ed9332a`](https://github.com/graphcommerce-org/graphcommerce/commit/ed9332a7f78966d932041d9a7725641edc92b28d) - ## GraphCommerce 10 - Turbopack Support
8
+
9
+ This major release brings full Turbopack compatibility, dramatically improving development speed.
10
+
11
+ ### 🚀 Turbopack-Compatible Interceptor System
12
+
13
+ The entire plugin/interceptor system has been rewritten to work with Turbopack:
14
+
15
+ - **No more Webpack plugins** - Removed `InterceptorPlugin` webpack plugin entirely
16
+ - **File-based interception** - Original files are moved to `.original.tsx` and replaced with interceptor content
17
+ - **Direct imports** - Interceptors import from `.original` files instead of embedding source
18
+ - **New CLI commands**:
19
+ - `graphcommerce codegen-interceptors` - Generate interceptor files
20
+ - `graphcommerce cleanup-interceptors` - Reset interceptor system, restore original files
21
+ - **Stable file hashing** - Deterministic interceptor generation for better caching
22
+
23
+ ### ⚙️ Treeshakable Configuration System
24
+
25
+ Replaced Webpack `DefinePlugin`-based `import.meta.graphCommerce` with a new generated configuration system:
26
+
27
+ - **New `codegen-config-values` command** - Generates TypeScript files with precise typing
28
+ - **Schema-driven** - Dynamically introspects Zod schemas to determine all available properties
29
+ - **Fully treeshakable** - Unused config values are eliminated from the bundle
30
+ - **Type-safe** - Uses `Get<GraphCommerceConfig, 'path'>` for nested property access
31
+ - **Separate files for nested objects** - Optimal treeshaking for complex configurations
32
+
33
+ ### 🔧 withGraphCommerce Changes
34
+
35
+ - **Removed** `InterceptorPlugin` - No longer needed with file-based interception
36
+ - **Removed** `DefinePlugin` for `import.meta.graphCommerce` - Replaced with generated config
37
+ - **Removed** `@mui/*` alias rewrites - No longer required
38
+ - **Added** Turbopack loader rules for `.yaml`, `.yml`, and `.po` files
39
+ - **Added** `serverExternalPackages` for all `@whatwg-node/*` packages
40
+ - **Added** `optimizePackageImports` for better bundle optimization
41
+ - **Added** `images.qualities: [52, 75]` for Next.js image optimization
42
+
43
+ ### 📦 Lingui Configuration
44
+
45
+ - **Renamed** `lingui.config.js` → `lingui.config.ts` with TypeScript support
46
+ - **Updated** `@graphcommerce/lingui-next/config` to TypeScript with proper exports
47
+ - **Simplified** formatter options
48
+
49
+ ### ⚛️ React 19 & Next.js 16 Compatibility
50
+
51
+ - Updated `RefObject<T>` types for React 19 (now includes `null` by default)
52
+ - Replaced deprecated `React.VFC` with `React.FC`
53
+ - Fixed `useRef` calls to require explicit initial values
54
+ - Updated `MutableRefObject` usage in `framer-scroller`
55
+
56
+ ### 📋 ESLint 9 Flat Config
57
+
58
+ - Migrated from legacy `.eslintrc` to new flat config format (`eslint.config.mjs`)
59
+ - Updated `@typescript-eslint/*` packages to v8
60
+ - Fixed AST selector for `SxProps` rule (`typeParameters` → `typeArguments`)
61
+
62
+ ### 🔄 Apollo Client
63
+
64
+ - Fixed deprecated `name` option → `clientAwareness: { name: 'ssr' }`
65
+ - Updated error handling types to accept `ApolloError | null | undefined`
66
+
67
+ ### ⚠️ Breaking Changes
68
+
69
+ - **Node.js 24.x not supported** - Restricted to `>=20 <24.0.0` due to [nodejs/undici#4290](https://github.com/nodejs/undici/issues/4290)
70
+ - **Interceptor files changed** - Original components now at `.original.tsx`
71
+ - **Config access changed** - Use generated config values instead of `import.meta.graphCommerce`
72
+ - **ESLint config format** - Must use flat config (`eslint.config.mjs`)
73
+ - **Lingui config** - Rename `lingui.config.js` to `lingui.config.ts`
74
+
75
+ ### 🗑️ Removed
76
+
77
+ - `InterceptorPlugin` webpack plugin
78
+ - `configToImportMeta` utility
79
+ - Webpack `DefinePlugin` usage for config
80
+ - `@mui/*` modern alias rewrites
81
+ - Debug plugins (`CircularDependencyPlugin`, `DuplicatesPlugin`) ([@paales](https://github.com/paales))
82
+
83
+ ## 9.1.0-canary.55
84
+
85
+ ### Patch Changes
86
+
87
+ - [#2539](https://github.com/graphcommerce-org/graphcommerce/pull/2539) [`87fc3c2`](https://github.com/graphcommerce-org/graphcommerce/commit/87fc3c28165c7c66b48882b0f044bbc9b63b9846) - Created new Tabs and TabItem component to be used for MultiCart setup ([@paales](https://github.com/paales))
88
+
89
+ - [#2539](https://github.com/graphcommerce-org/graphcommerce/pull/2539) [`286a20e`](https://github.com/graphcommerce-org/graphcommerce/commit/286a20e01f2a565f058415fa1c8dfbb2eeb3163b) - Added an OverlayCloseButton and implemented it for various locations. ([@paales](https://github.com/paales))
90
+
91
+ - [#2539](https://github.com/graphcommerce-org/graphcommerce/pull/2539) [`65dcefb`](https://github.com/graphcommerce-org/graphcommerce/commit/65dcefb8740166fd5df662e0e895c65d70273393) - Solve hydration error because multiple literals could be in a DateTimeFormat ([@paales](https://github.com/paales))
92
+
93
+ - [#2539](https://github.com/graphcommerce-org/graphcommerce/pull/2539) [`88fd114`](https://github.com/graphcommerce-org/graphcommerce/commit/88fd11485f9368e79d277fa45942e58214f794a6) - Created a LayoutOverlayHeader2 that does not support any floating modes or something and thus is simpler to customize. ([@paales](https://github.com/paales))
94
+
95
+ - [#2539](https://github.com/graphcommerce-org/graphcommerce/pull/2539) [`23793aa`](https://github.com/graphcommerce-org/graphcommerce/commit/23793aab26455b1bea0d1b3b37c96a228b656bc4) - Prevent excessive rerender when multiple images with the same url are in a product ([@paales](https://github.com/paales))
96
+
97
+ - [#2539](https://github.com/graphcommerce-org/graphcommerce/pull/2539) [`a419257`](https://github.com/graphcommerce-org/graphcommerce/commit/a4192571eb2332630ba3d103f61ff69dac8b2e5c) - Solve issue where the sidebar wasn't 100% width on the PDP on mobile ([@paales](https://github.com/paales))
98
+
3
99
  ## 9.1.0-canary.54
4
100
 
5
101
  ## 9.1.0-canary.53
@@ -10,6 +10,7 @@ import {
10
10
  unstable_usePreventScroll as usePreventScroll,
11
11
  } from '@graphcommerce/framer-scroller'
12
12
  import { dvh } from '@graphcommerce/framer-utils'
13
+ import { sidebarGallery } from '@graphcommerce/next-config/config'
13
14
  import type { SxProps, Theme } from '@mui/material'
14
15
  import { Box, Fab, styled, Unstable_TrapFocus as TrapFocus, useTheme } from '@mui/material'
15
16
  import { m, useDomEvent, useMotionValue } from 'framer-motion'
@@ -242,7 +243,8 @@ export function SidebarGallery(props: SidebarGalleryProps) {
242
243
  >
243
244
  {images.map((image, idx) => (
244
245
  <MotionImageAspect
245
- key={typeof image.src === 'string' ? image.src : idx}
246
+ // eslint-disable-next-line react/no-array-index-key
247
+ key={idx}
246
248
  layout
247
249
  layoutDependency={zoomed}
248
250
  src={image.src}
@@ -349,8 +351,7 @@ export function SidebarGallery(props: SidebarGalleryProps) {
349
351
  },
350
352
  }}
351
353
  >
352
- {import.meta.graphCommerce.sidebarGallery?.paginationVariant ===
353
- 'THUMBNAILS_BOTTOM' ? (
354
+ {sidebarGallery?.paginationVariant === 'THUMBNAILS_BOTTOM' ? (
354
355
  <ScrollerThumbnails layoutDependency={zoomed} images={images} />
355
356
  ) : (
356
357
  <ScrollerDots layout />
@@ -362,7 +363,7 @@ export function SidebarGallery(props: SidebarGalleryProps) {
362
363
  className={classes.sidebarWrapper}
363
364
  sx={[
364
365
  {
365
- width: sidebarSize,
366
+ width: { xs: '100%', md: sidebarSize },
366
367
  gridArea: 'right',
367
368
  boxSizing: 'content-box',
368
369
  display: 'grid',
@@ -19,8 +19,9 @@ export function DateTimeFormat(props: DateTimeFormatProps) {
19
19
  return (
20
20
  <Box component='span' className='DateTimeFormat' suppressHydrationWarning sx={sx}>
21
21
  {dateValue &&
22
- formatter.formatToParts(dateValue).map((part) => (
23
- <span className={part.type} key={part.type} suppressHydrationWarning>
22
+ formatter.formatToParts(dateValue).map((part, index) => (
23
+ // eslint-disable-next-line react/no-array-index-key
24
+ <span className={part.type} key={`${part.type}-${index}`} suppressHydrationWarning>
24
25
  {part.value}
25
26
  </span>
26
27
  ))}
@@ -4,7 +4,7 @@ import {
4
4
  usePrevUp,
5
5
  useUp,
6
6
  } from '@graphcommerce/framer-next-pages'
7
- import { i18n } from '@lingui/core'
7
+ import { t } from '@lingui/core/macro'
8
8
  import type { SxProps, Theme } from '@mui/material'
9
9
  import { Box } from '@mui/material'
10
10
  import { useRouter } from 'next/router'
@@ -55,7 +55,7 @@ export function LayoutHeaderBack(props: BackProps) {
55
55
  const backIcon = <IconSvg src={iconChevronLeft} size='medium' />
56
56
  const canClickBack = backSteps > 0 && path !== prevUp?.href && !disableBackNavigation
57
57
 
58
- let label = i18n._(/* i18n */ 'Back')
58
+ let label = t`Back`
59
59
  if (up?.href === path && up?.title) label = up.title
60
60
  if (prevUp?.href === path && prevUp?.title) label = prevUp.title
61
61
 
@@ -1,13 +1,16 @@
1
1
  import { useGo, usePageContext } from '@graphcommerce/framer-next-pages'
2
- import { i18n } from '@lingui/core'
3
- import { Fab } from '@mui/material'
2
+ import { t } from '@lingui/core/macro'
3
+ import { Fab, type ButtonProps } from '@mui/material'
4
4
  import { useState } from 'react'
5
+ import { Button } from '../../Button'
6
+ import type { FabProps } from '../../Fab'
5
7
  import { iconClose } from '../../icons'
6
8
  import { IconSvg, useIconSvgSize } from '../../IconSvg'
7
9
  import { useFabSize } from '../../Theme'
8
10
 
9
11
  export type LayoutHeaderCloseProps = {
10
12
  onClose?: () => void
13
+ size?: FabProps['size']
11
14
  }
12
15
 
13
16
  export function useShowClose() {
@@ -16,31 +19,31 @@ export function useShowClose() {
16
19
  }
17
20
 
18
21
  export function LayoutHeaderClose(props: LayoutHeaderCloseProps) {
19
- const { onClose } = props
22
+ const { onClose, size = 'responsive' } = props
20
23
  const { closeSteps } = usePageContext()
21
24
  const [disabled, setDisabled] = useState(false)
22
25
  const go = useGo(closeSteps * -1)
23
26
  const onClick = () => {
24
27
  setDisabled(true)
25
-
26
28
  return onClose ? onClose() : go()
27
29
  }
28
30
 
29
- const fabSize = useFabSize('responsive')
30
- const svgSize = useIconSvgSize('large')
31
+ const fabSize = useFabSize(size)
32
+ const svgSize = useIconSvgSize(size === 'large' ? 'large' : 'medium')
31
33
 
32
34
  return (
33
35
  <Fab
34
36
  onClick={onClick}
37
+ className='LayoutHeaderClose-root'
35
38
  sx={{
36
39
  boxShadow: 'none',
37
40
  marginLeft: `calc((${fabSize} - ${svgSize}) * -0.5)`,
38
41
  marginRight: `calc((${fabSize} - ${svgSize}) * -0.5)`,
39
42
  background: 'none',
40
43
  }}
41
- size='responsive'
44
+ size={size}
42
45
  disabled={disabled}
43
- aria-label={i18n._(/* i18n */ 'Close')}
46
+ aria-label={t`Close`}
44
47
  >
45
48
  <IconSvg src={iconClose} size='large' />
46
49
  </Fab>
@@ -0,0 +1,213 @@
1
+ import type { SxProps, Theme } from '@mui/material'
2
+ import { Box } from '@mui/material'
3
+ import React, { useRef } from 'react'
4
+ import type { BackProps } from '../../Layout/components/LayoutHeaderBack'
5
+ import { LayoutHeaderBack, useShowBack } from '../../Layout/components/LayoutHeaderBack'
6
+ import { LayoutHeaderClose, useShowClose } from '../../Layout/components/LayoutHeaderClose'
7
+ import { extendableComponent } from '../../Styles'
8
+ import { sxx } from '../../utils/sxx'
9
+
10
+ export type LayoutOverlayHeader2Props = Pick<BackProps, 'disableBackNavigation'> & {
11
+ /** Main content to display in the center */
12
+ children: React.ReactNode
13
+
14
+ disableChildrenPadding?: boolean
15
+
16
+ /** Button to display on the left side of the title */
17
+ primary?: React.ReactNode
18
+
19
+ /** Button to display on the right side of the title */
20
+ secondary?: React.ReactNode
21
+
22
+ /** Hide the back button */
23
+ hideBackButton?: boolean
24
+
25
+ /** Justify alignment for center content */
26
+ justify?: 'start' | 'center' | 'end'
27
+
28
+ /** Custom styles for the root element */
29
+ sx?: SxProps<Theme>
30
+
31
+ closeLocation?: 'left' | 'right'
32
+
33
+ size?: 'small' | 'responsive'
34
+ }
35
+
36
+ const name = 'LayoutOverlayHeader2'
37
+ const parts = ['root', 'bg', 'content', 'left', 'center', 'right'] as const
38
+
39
+ type State = {
40
+ left: boolean
41
+ right: boolean
42
+ childrenPadding: boolean
43
+ size: 'small' | 'responsive'
44
+ }
45
+
46
+ const { withState } = extendableComponent<State, typeof name, typeof parts>(name, parts)
47
+
48
+ export const LayoutOverlayHeader2 = React.memo<LayoutOverlayHeader2Props>((props) => {
49
+ const ref = useRef<HTMLDivElement>(null)
50
+
51
+ const {
52
+ children,
53
+ hideBackButton = false,
54
+ primary,
55
+ secondary,
56
+ justify = 'center',
57
+ closeLocation = 'right',
58
+ sx = [],
59
+ disableBackNavigation,
60
+ disableChildrenPadding = false,
61
+ size = 'responsive',
62
+ } = props
63
+
64
+ const showBack = useShowBack() && !hideBackButton
65
+ const showClose = useShowClose()
66
+
67
+ const close = showClose && <LayoutHeaderClose size={size === 'small' ? 'small' : 'responsive'} />
68
+ const back = showBack && <LayoutHeaderBack disableBackNavigation={disableBackNavigation} />
69
+
70
+ let left = secondary
71
+ let right = primary
72
+
73
+ if (back && !secondary) left = back
74
+
75
+ // Handle close button positioning based on closeLocation
76
+ if (close && closeLocation === 'left' && !left) left = close
77
+ if (close && closeLocation === 'right' && !right) right = close
78
+ if (close && closeLocation === 'right' && !left && right) left = close
79
+ if (!left && !right && !children) return null
80
+
81
+ const classes = withState({
82
+ left: !!left,
83
+ right: !!right,
84
+ childrenPadding: !disableChildrenPadding,
85
+ size,
86
+ })
87
+
88
+ return (
89
+ <Box
90
+ className={classes.root}
91
+ sx={sxx(
92
+ (theme) => ({
93
+ zIndex: children ? theme.zIndex.appBar : theme.zIndex.appBar - 2,
94
+ position: 'sticky',
95
+ left: 0,
96
+ right: 0,
97
+ top: 0,
98
+ marginTop: 0,
99
+
100
+ // Special positioning for bottom overlays
101
+ [theme.breakpoints.down('md')]: {
102
+ height: theme.appShell.headerHeightSm,
103
+ '.variantSmBottom.sizeSmFull &, .variantSmBottom.sizeSmMinimal &': {
104
+ top: `calc(${theme.appShell.headerHeightSm} * 0.5 * -1)`,
105
+ },
106
+ },
107
+ [theme.breakpoints.up('md')]: {
108
+ height: theme.appShell.appBarHeightMd,
109
+ '.variantMdBottom.sizeMdFull &, .variantMdBottom.sizeMdMinimal &': {
110
+ top: `calc(${theme.appShell.appBarHeightMd} * 0.5 * -1)`,
111
+ },
112
+
113
+ '&.sizeSmall': {
114
+ height: theme.appShell.headerHeightSm,
115
+ },
116
+ },
117
+ }),
118
+ sx,
119
+ )}
120
+ >
121
+ <Box
122
+ className={classes.content}
123
+ ref={ref}
124
+ sx={sxx((theme) => ({
125
+ position: 'absolute',
126
+ inset: 0,
127
+ width: '100%',
128
+ display: 'grid',
129
+ alignItems: 'center',
130
+ gap: theme.spacings.xs,
131
+ height: theme.appShell.headerHeightSm,
132
+ [theme.breakpoints.up('md')]: {
133
+ height: theme.appShell.appBarHeightMd,
134
+ '&.sizeSmall': {
135
+ height: theme.appShell.headerHeightSm,
136
+ },
137
+ },
138
+
139
+ // Default: center only
140
+ gridTemplateAreas: '"center"',
141
+ gridTemplateColumns: '1fr',
142
+
143
+ // Left only
144
+ '&.left:not(.right)': {
145
+ gridTemplateAreas: '"left center"',
146
+ gridTemplateColumns: justify === 'center' ? '1fr max-content' : 'auto 1fr',
147
+ },
148
+
149
+ // Right only
150
+ '&.right:not(.left)': {
151
+ gridTemplateAreas: '"center right"',
152
+ gridTemplateColumns: justify === 'center' ? 'max-content 1fr' : '1fr auto',
153
+ },
154
+
155
+ // Both left and right
156
+ '&.left.right': {
157
+ gridTemplateAreas: '"left center right"',
158
+ gridTemplateColumns: justify === 'center' ? '1fr max-content 1fr' : 'auto 1fr auto',
159
+ },
160
+ }))}
161
+ >
162
+ {left && (
163
+ <Box
164
+ className={classes.left}
165
+ sx={(theme) => ({
166
+ pl: theme.spacings.sm,
167
+ display: 'grid',
168
+ gridAutoFlow: 'column',
169
+ gap: theme.spacings.sm,
170
+ gridArea: 'left',
171
+ justifyContent: 'start',
172
+ })}
173
+ >
174
+ {left}
175
+ </Box>
176
+ )}
177
+
178
+ <Box
179
+ className={classes.center}
180
+ sx={(theme) => ({
181
+ display: 'grid',
182
+ gridAutoFlow: 'column',
183
+ gap: theme.spacings.sm,
184
+ gridArea: 'center',
185
+ overflow: 'hidden',
186
+ height: '100%',
187
+ '&.childrenPadding:not(.right)': { pr: theme.spacings.sm },
188
+ '&.childrenPadding:not(.left)': { pl: theme.spacings.sm },
189
+ })}
190
+ >
191
+ {children}
192
+ </Box>
193
+
194
+ {right && (
195
+ <Box
196
+ className={classes.right}
197
+ sx={(theme) => ({
198
+ '& > *': { width: 'min-content' },
199
+ display: 'grid',
200
+ gridAutoFlow: 'column',
201
+ gap: theme.spacings.sm,
202
+ gridArea: 'right',
203
+ justifyContent: 'end',
204
+ pr: theme.spacings.sm,
205
+ })}
206
+ >
207
+ {right}
208
+ </Box>
209
+ )}
210
+ </Box>
211
+ </Box>
212
+ )
213
+ })
@@ -1,2 +1,3 @@
1
1
  export * from './components/LayoutOverlay'
2
2
  export * from './components/LayoutOverlayHeader'
3
+ export * from './components/LayoutOverlayHeader2'
@@ -39,8 +39,8 @@ export const NavigationList = React.memo<NavigationItemsProps>((props) => {
39
39
  {items.map((item, idx) => (
40
40
  <NavigationItem
41
41
  NavigationList={NavigationList}
42
- key={item.id}
43
42
  {...item}
43
+ key={item.id ?? idx}
44
44
  parentPath={parentPath}
45
45
  idx={idx}
46
46
  first={idx === 0}
@@ -1,5 +1,5 @@
1
1
  import { dvw, useMotionSelector, useMotionValueValue } from '@graphcommerce/framer-utils'
2
- import { i18n } from '@lingui/core'
2
+ import { t } from '@lingui/core/macro'
3
3
  import type { SxProps, Theme } from '@mui/material'
4
4
  import { Box, Fab, styled, useEventCallback, useTheme } from '@mui/material'
5
5
  import { m } from 'framer-motion'
@@ -160,7 +160,7 @@ export const NavigationOverlay = React.memo((props: NavigationOverlayProps) => {
160
160
  onClick={handleOnBack}
161
161
  sx={{ boxShadow: 'none', my: fabMarginY }}
162
162
  size='responsive'
163
- aria-label={i18n._(/* i18n */ 'Back')}
163
+ aria-label={t`Back`}
164
164
  >
165
165
  <IconSvg src={iconChevronLeft} size='large' aria-hidden />
166
166
  </Fab>
@@ -173,7 +173,7 @@ export const NavigationOverlay = React.memo((props: NavigationOverlayProps) => {
173
173
  onClick={handleClose}
174
174
  sx={{ boxShadow: 'none', my: fabMarginY }}
175
175
  size='responsive'
176
- aria-label={i18n._(/* i18n */ 'Close')}
176
+ aria-label={t`Close`}
177
177
  ref={a11yFocusRef}
178
178
  >
179
179
  <IconSvg src={iconClose} size='large' aria-hidden />
@@ -1,5 +1,5 @@
1
1
  import { useMotionValueValue } from '@graphcommerce/framer-utils'
2
- import { Trans } from '@lingui/react'
2
+ import { Trans } from '@lingui/react/macro'
3
3
  import type {
4
4
  NavigationNodeButton,
5
5
  NavigationNodeHref,
@@ -35,6 +35,6 @@ function findCurrent(
35
35
  export function NavigationTitle() {
36
36
  const { selection, items } = useNavigation()
37
37
  return (
38
- <>{useMotionValueValue(selection, (v) => findCurrent(items, v))?.name ?? <Trans id='Menu' />}</>
38
+ <>{useMotionValueValue(selection, (v) => findCurrent(items, v))?.name ?? <Trans>Menu</Trans>}</>
39
39
  )
40
40
  }
@@ -160,7 +160,7 @@ export function OverlayBase(incomingProps: LayoutOverlayBaseProps) {
160
160
  () => (match.up('md') ? variantMd : variantSm),
161
161
  [match, variantMd, variantSm],
162
162
  )
163
- const prevVariant = useRef<LayoutOverlayVariant>()
163
+ const prevVariant = useRef<LayoutOverlayVariant | null>(null)
164
164
 
165
165
  const openClosePositions = useCallback((): {
166
166
  open: [number, number]
@@ -418,7 +418,7 @@ export function OverlayBase(incomingProps: LayoutOverlayBaseProps) {
418
418
  return (
419
419
  <>
420
420
  <MotionDiv
421
- inert={active ? undefined : ('true' as unknown as boolean)}
421
+ inert={active}
422
422
  className={classes.backdrop}
423
423
  style={{ opacity: positions.open.visible }}
424
424
  sx={[
@@ -440,7 +440,7 @@ export function OverlayBase(incomingProps: LayoutOverlayBaseProps) {
440
440
  ]}
441
441
  />
442
442
  <Scroller
443
- inert={disableInert || active ? undefined : ('true' as unknown as boolean)}
443
+ inert={disableInert || active ? false : true}
444
444
  className={`${classes.scroller} ${className ?? ''}`}
445
445
  grid={false}
446
446
  onClick={onClickAway}
@@ -0,0 +1,25 @@
1
+ import { useGo, usePageContext } from '@graphcommerce/framer-next-pages'
2
+ import { t } from '@lingui/core/macro'
3
+ import { useState } from 'react'
4
+ import { Button, type ButtonProps } from '../../Button'
5
+
6
+ export function OverlayCloseButton(props: ButtonProps) {
7
+ const { disabled, ...buttonProps } = props
8
+ const { closeSteps } = usePageContext()
9
+ const [isDisabled, setIsDisabled] = useState(disabled)
10
+ const go = useGo(closeSteps * -1)
11
+
12
+ const onClick = () => {
13
+ setIsDisabled(true)
14
+ go()
15
+ }
16
+
17
+ return (
18
+ <Button
19
+ onClick={onClick}
20
+ disabled={isDisabled}
21
+ aria-label={t`Close overlay`}
22
+ {...buttonProps}
23
+ />
24
+ )
25
+ }
@@ -0,0 +1,26 @@
1
+ import { LayoutTitle, type TitleProps } from '../../Layout'
2
+ import { LayoutHeaderClose } from '../../Layout/components/LayoutHeaderClose'
3
+ import { LayoutOverlayHeader2, type LayoutOverlayHeader2Props } from '../../LayoutOverlay'
4
+
5
+ export type OverlayHeader2Props = Omit<LayoutOverlayHeader2Props, 'hideBackButton'> & {
6
+ onClose: () => void
7
+ icon?: TitleProps['icon']
8
+ }
9
+
10
+ export function OverlayHeader2(props: OverlayHeader2Props) {
11
+ const { children, onClose, primary, secondary, icon, ...rest } = props
12
+
13
+ return (
14
+ <LayoutOverlayHeader2
15
+ hideBackButton
16
+ size='small'
17
+ primary={primary ?? <LayoutHeaderClose onClose={onClose} size='small' />}
18
+ secondary={primary ? <LayoutHeaderClose onClose={onClose} size='small' /> : secondary}
19
+ {...rest}
20
+ >
21
+ <LayoutTitle size='small' component='span' icon={icon}>
22
+ {children}
23
+ </LayoutTitle>
24
+ </LayoutOverlayHeader2>
25
+ )
26
+ }
@@ -1,4 +1,6 @@
1
- export * from './OverlayBase'
2
1
  export * from './Overlay'
3
- export * from './OverlayStickyBottom'
2
+ export * from './OverlayBase'
3
+ export * from './OverlayCloseButton'
4
4
  export * from './OverlayHeader'
5
+ export * from './OverlayHeader2'
6
+ export * from './OverlayStickyBottom'