@graphcommerce/next-ui 4.27.0 → 4.28.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Change Log
2
2
 
3
+ ## 4.28.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#1662](https://github.com/graphcommerce-org/graphcommerce/pull/1662) [`0c21c5c23`](https://github.com/graphcommerce-org/graphcommerce/commit/0c21c5c233ebab15f6629c234e3de1cc8c0452e1) Thanks [@paales](https://github.com/paales)! - Implement serverRenderDepth prop to the Navigation to limit initial render time and TBT
8
+
9
+ * [#1662](https://github.com/graphcommerce-org/graphcommerce/pull/1662) [`f5eae0afd`](https://github.com/graphcommerce-org/graphcommerce/commit/f5eae0afdbd474b1f81c450425ffadf2d025187a) Thanks [@paales](https://github.com/paales)! - Move to useMatchMedia to have a simple boolean utility that allows to match to a certain breakpoint
10
+
11
+ ### Patch Changes
12
+
13
+ - [#1662](https://github.com/graphcommerce-org/graphcommerce/pull/1662) [`de8925aa9`](https://github.com/graphcommerce-org/graphcommerce/commit/de8925aa910b191c62041530c68c697a58a1e52d) Thanks [@paales](https://github.com/paales)! - Allow for a custom Component for magentoMenuToNavigation and allow React.ReactNode for items
14
+
15
+ - Updated dependencies [[`f5eae0afd`](https://github.com/graphcommerce-org/graphcommerce/commit/f5eae0afdbd474b1f81c450425ffadf2d025187a), [`9e0ca73eb`](https://github.com/graphcommerce-org/graphcommerce/commit/9e0ca73eb50ded578f4a98e40a7eb920bf8ab421)]:
16
+ - @graphcommerce/framer-scroller@2.1.40
17
+ - @graphcommerce/framer-next-pages@3.3.1
18
+
3
19
  ## 4.27.0
4
20
 
5
21
  ### Minor Changes
@@ -10,7 +10,7 @@ import {
10
10
  import { Fab, useTheme, Box, styled, SxProps, Theme } from '@mui/material'
11
11
  import { m, useDomEvent, useMotionValue } from 'framer-motion'
12
12
  import { useRouter } from 'next/router'
13
- import React, { useEffect, useMemo, useRef, useState } from 'react'
13
+ import React, { useEffect, useRef } from 'react'
14
14
  import { IconSvg } from '../IconSvg'
15
15
  import { Row } from '../Row/Row'
16
16
  import { extendableComponent } from '../Styles'
@@ -107,9 +107,8 @@ export function SidebarGallery(props: SidebarGalleryProps) {
107
107
 
108
108
  const headerHeight = `${theme.appShell.headerHeightSm} - ${theme.spacings.sm} * 2`
109
109
  const galleryMargin = theme.spacings.lg
110
- const extraSpacing = theme.spacings.md
111
110
 
112
- const maxHeight = `calc(100vh - ${headerHeight} - ${galleryMargin} - ${extraSpacing})`
111
+ const maxHeight = `calc(100vh - ${headerHeight} - ${galleryMargin})`
113
112
  const ratio = `calc(${height} / ${width} * 100%)`
114
113
 
115
114
  const hasImages = images.length > 0
@@ -200,7 +199,7 @@ export function SidebarGallery(props: SidebarGalleryProps) {
200
199
  width={image.width}
201
200
  height={image.height}
202
201
  loading={idx === 0 ? 'eager' : 'lazy'}
203
- sx={{ display: 'block' }}
202
+ sx={{ display: 'block', objectFit: 'contain' }}
204
203
  sizes={{
205
204
  0: '100vw',
206
205
  [theme.breakpoints.values.md]: zoomed ? '100vw' : '60vw',
@@ -1,18 +1,11 @@
1
1
  /* eslint-disable @typescript-eslint/no-use-before-define */
2
2
  import { useMotionValueValue } from '@graphcommerce/framer-utils'
3
- import {
4
- Box,
5
- ListItemButton,
6
- styled,
7
- Theme,
8
- useEventCallback,
9
- useMediaQuery,
10
- alpha,
11
- } from '@mui/material'
3
+ import { alpha, Box, ListItemButton, styled, useEventCallback, useTheme } from '@mui/material'
12
4
  import PageLink from 'next/link'
13
5
  import React from 'react'
14
6
  import { IconSvg } from '../../IconSvg'
15
7
  import { extendableComponent } from '../../Styles/extendableComponent'
8
+ import { useMatchMedia } from '../../hooks'
16
9
  import { iconChevronRight } from '../../icons'
17
10
  import {
18
11
  isNavigationButton,
@@ -54,9 +47,9 @@ const NavigationLI = styled('li')({ display: 'contents' })
54
47
 
55
48
  export const NavigationItem = React.memo<NavigationItemProps>((props) => {
56
49
  const { id, parentPath, idx, first, last, NavigationList, mouseEvent } = props
50
+ const { selection, hideRootOnNavigate, closing, animating, serverRenderDepth } = useNavigation()
57
51
 
58
52
  const row = idx + 1
59
- const { selection, hideRootOnNavigate, closing, animating } = useNavigation()
60
53
 
61
54
  const itemPath = [...(parentPath ? parentPath.split(',') : []), id]
62
55
 
@@ -83,13 +76,18 @@ export const NavigationItem = React.memo<NavigationItemProps>((props) => {
83
76
  closing.set(true)
84
77
  })
85
78
 
86
- const isDesktop = useMediaQuery<Theme>((theme) => theme.breakpoints.up('md'))
79
+ const matchMedia = useMatchMedia()
87
80
 
88
81
  if (isNavigationButton(props)) {
89
- const { childItems, name } = props
82
+ const { childItems, name, href } = props
83
+
84
+ const skipChildren = itemPath.length + 1 > serverRenderDepth && !isSelected && !!href
85
+
90
86
  return (
91
87
  <NavigationLI className={classes.li}>
92
88
  <ListItemButton
89
+ component={href ? 'a' : 'div'}
90
+ href={href || undefined}
93
91
  className={classes.item}
94
92
  role='button'
95
93
  sx={[
@@ -116,14 +114,14 @@ export const NavigationItem = React.memo<NavigationItemProps>((props) => {
116
114
  tabIndex={tabIndex}
117
115
  onClick={(e) => {
118
116
  e.preventDefault()
119
- if (!isSelected && animating.get() === false) {
117
+ if (!isSelected && !animating.get()) {
120
118
  selection.set(itemPath)
121
119
  }
122
120
  }}
123
121
  onMouseMove={
124
- itemPath.length > 1 && mouseEvent === 'hover'
122
+ (itemPath.length > 1 || !hideRootOnNavigate) && mouseEvent === 'hover'
125
123
  ? (e) => {
126
- if (isDesktop && animating.get() === false && !isSelected) {
124
+ if (!isSelected && !animating.get() && matchMedia.up('md')) {
127
125
  e.preventDefault()
128
126
  setTimeout(() => selection.set(itemPath), 0)
129
127
  }
@@ -145,18 +143,21 @@ export const NavigationItem = React.memo<NavigationItemProps>((props) => {
145
143
  <IconSvg src={iconChevronRight} sx={{ flexShrink: 0 }} />
146
144
  </ListItemButton>
147
145
 
148
- <NavigationList
149
- items={childItems}
150
- selected={isSelected}
151
- parentPath={itemPath.join(',')}
152
- mouseEvent={mouseEvent}
153
- />
146
+ {!skipChildren && (
147
+ <NavigationList
148
+ items={childItems}
149
+ selected={isSelected}
150
+ parentPath={itemPath.join(',')}
151
+ mouseEvent={mouseEvent}
152
+ />
153
+ )}
154
154
  </NavigationLI>
155
155
  )
156
156
  }
157
157
 
158
158
  if (isNavigationHref(props)) {
159
159
  const { name, href } = props
160
+
160
161
  return (
161
162
  <NavigationLI sx={[hideItem && { display: 'none' }]} className={classes.li}>
162
163
  <PageLink href={href} passHref prefetch={false}>
@@ -198,8 +199,6 @@ export const NavigationItem = React.memo<NavigationItemProps>((props) => {
198
199
  )
199
200
  }
200
201
 
201
- if (process.env.NODE_ENV !== 'production') throw Error('NavigationItem: unknown type')
202
-
203
202
  return null
204
203
  })
205
204
 
@@ -11,6 +11,7 @@ import { LayoutTitle } from '../../Layout/components/LayoutTitle'
11
11
  import { Overlay } from '../../Overlay/components/Overlay'
12
12
  import { extendableComponent } from '../../Styles/extendableComponent'
13
13
  import { useFabSize } from '../../Theme'
14
+ import { useMatchMedia } from '../../hooks'
14
15
  import { iconClose, iconChevronLeft } from '../../icons'
15
16
  import { useNavigation } from '../hooks/useNavigation'
16
17
  import { mouseEventPref } from './NavigationItem'
@@ -57,14 +58,15 @@ export const NavigationOverlay = React.memo<NavigationOverlayProps>((props) => {
57
58
  mouseEvent,
58
59
  itemPadding = 'md',
59
60
  } = props
60
- const { selection, items, animating, closing } = useNavigation()
61
+ const { selection, items, animating, closing, serverRenderDepth } = useNavigation()
61
62
 
62
63
  const fabSize = useFabSize('responsive')
63
64
  const svgSize = useIconSvgSize('large')
64
65
 
65
- const theme2 = useTheme()
66
+ const matchMedia = useMatchMedia()
67
+
66
68
  const handleOnBack = useEventCallback(() => {
67
- if (window.matchMedia(`(max-width: ${theme2.breakpoints.values.md}px)`).matches) {
69
+ if (matchMedia.down('md')) {
68
70
  const current = selection.get()
69
71
  selection.set(current !== false ? current.slice(0, -1) : false)
70
72
  } else selection.set([])
@@ -87,6 +89,8 @@ export const NavigationOverlay = React.memo<NavigationOverlayProps>((props) => {
87
89
 
88
90
  const handleClose = useEventCallback(() => closing.set(true))
89
91
 
92
+ if (selectedLevel === -1 && serverRenderDepth <= 0) return null
93
+
90
94
  return (
91
95
  <Overlay
92
96
  className={classes.root}
@@ -6,6 +6,8 @@ import {
6
6
  NavigationContextType,
7
7
  NavigationContext,
8
8
  UseNavigationSelection,
9
+ NavigationNodeType,
10
+ NavigationNodeComponent,
9
11
  } from '../hooks/useNavigation'
10
12
 
11
13
  export type NavigationProviderProps = {
@@ -15,6 +17,7 @@ export type NavigationProviderProps = {
15
17
  children?: React.ReactNode
16
18
  animationDuration?: number
17
19
  selection: UseNavigationSelection
20
+ serverRenderDepth?: number
18
21
  }
19
22
 
20
23
  const nonNullable = <T,>(value: T): value is NonNullable<T> => value !== null && value !== undefined
@@ -27,6 +30,7 @@ export const NavigationProvider = React.memo<NavigationProviderProps>((props) =>
27
30
  animationDuration = 0.225,
28
31
  children,
29
32
  selection,
33
+ serverRenderDepth = 2,
30
34
  } = props
31
35
 
32
36
  const animating = useMotionValue(false)
@@ -39,10 +43,19 @@ export const NavigationProvider = React.memo<NavigationProviderProps>((props) =>
39
43
  animating,
40
44
  closing,
41
45
  items: items
42
- .map((item, index) => (isElement(item) ? { id: item.key ?? index, component: item } : item))
46
+ .map((item, index) =>
47
+ isElement(item)
48
+ ? ({
49
+ type: NavigationNodeType.COMPONENT,
50
+ id: item.key ?? index,
51
+ component: item,
52
+ } as NavigationNodeComponent)
53
+ : item,
54
+ )
43
55
  .filter(nonNullable),
56
+ serverRenderDepth,
44
57
  }),
45
- [hideRootOnNavigate, selection, animating, closing, items],
58
+ [hideRootOnNavigate, selection, animating, closing, items, serverRenderDepth],
46
59
  )
47
60
 
48
61
  return (
@@ -1,5 +1,5 @@
1
1
  import { MotionValue, useMotionValue } from 'framer-motion'
2
- import { createContext, MutableRefObject, useContext } from 'react'
2
+ import React, { createContext, MutableRefObject, useContext } from 'react'
3
3
 
4
4
  export type NavigationId = string | number
5
5
  export type NavigationPath = NavigationId[]
@@ -19,38 +19,51 @@ export type NavigationContextType = {
19
19
  hideRootOnNavigate: boolean
20
20
  animating: MotionValue<boolean>
21
21
  closing: MotionValue<boolean>
22
+ serverRenderDepth: number
22
23
  }
23
24
 
24
25
  type NavigationNodeBase = {
26
+ type?: NavigationNodeType
25
27
  id: NavigationId
26
28
  }
27
29
 
30
+ export enum NavigationNodeType {
31
+ LINK,
32
+ BUTTON,
33
+ COMPONENT,
34
+ }
35
+
28
36
  export type NavigationNodeHref = NavigationNodeBase & {
29
- name: string
37
+ name: React.ReactNode
30
38
  href: string
31
39
  }
32
40
 
33
41
  export type NavigationNodeButton = NavigationNodeBase & {
34
- name: string
42
+ name: React.ReactNode
43
+ type: NavigationNodeType.BUTTON
44
+ href?: string
35
45
  childItems: NavigationNode[]
36
46
  }
37
47
 
38
48
  export type NavigationNodeComponent = NavigationNodeBase & {
49
+ type: NavigationNodeType.COMPONENT
39
50
  component: React.ReactNode
40
51
  }
41
52
 
42
53
  export type NavigationNode = NavigationNodeHref | NavigationNodeButton | NavigationNodeComponent
43
54
 
44
55
  export function isNavigationHref(node: NavigationNodeBase): node is NavigationNodeHref {
45
- return 'href' in node
56
+ return 'href' in node && node.type !== NavigationNodeType.BUTTON
46
57
  }
47
58
 
48
59
  export function isNavigationButton(node: NavigationNodeBase): node is NavigationNodeButton {
49
- return (node as NavigationNodeButton).childItems?.length > 0
60
+ return (
61
+ node.type === NavigationNodeType.BUTTON && (node as NavigationNodeButton).childItems?.length > 0
62
+ )
50
63
  }
51
64
 
52
65
  export function isNavigationComponent(node: NavigationNodeBase): node is NavigationNodeComponent {
53
- return 'component' in node
66
+ return node.type === NavigationNodeType.COMPONENT && 'component' in node
54
67
  }
55
68
 
56
69
  export const NavigationContext = createContext(undefined as unknown as NavigationContextType)
package/hooks/index.ts CHANGED
@@ -2,3 +2,4 @@ export * from './useDateTimeFormat'
2
2
  export * from './useNumberFormat'
3
3
  export * from './useUrlQuery'
4
4
  export * from './useMemoDeep'
5
+ export * from './useMatchMedia'
@@ -0,0 +1,17 @@
1
+ import { Breakpoint, useTheme } from '@mui/material'
2
+ import { useMemo } from 'react'
3
+
4
+ export function useMatchMedia() {
5
+ const theme = useTheme()
6
+
7
+ return useMemo(() => {
8
+ const callback = (direction: 'up' | 'down', breakpointKey: number | Breakpoint) =>
9
+ window.matchMedia(theme.breakpoints[direction](breakpointKey).replace(/^@media( ?)/m, ''))
10
+ .matches
11
+
12
+ return {
13
+ down: (key: number | Breakpoint) => callback('down', key),
14
+ up: (key: number | Breakpoint) => callback('up', key),
15
+ }
16
+ }, [theme.breakpoints])
17
+ }
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.27.0",
5
+ "version": "4.28.0",
6
6
  "author": "",
7
7
  "license": "MIT",
8
8
  "sideEffects": false,
@@ -19,8 +19,8 @@
19
19
  "@emotion/react": "^11.9.3",
20
20
  "@emotion/server": "^11.4.0",
21
21
  "@emotion/styled": "^11.9.3",
22
- "@graphcommerce/framer-next-pages": "3.3.0",
23
- "@graphcommerce/framer-scroller": "2.1.39",
22
+ "@graphcommerce/framer-next-pages": "3.3.1",
23
+ "@graphcommerce/framer-scroller": "2.1.40",
24
24
  "@graphcommerce/framer-utils": "3.2.0",
25
25
  "@graphcommerce/image": "3.1.9",
26
26
  "cookie": "^0.5.0",