@graphcommerce/next-ui 8.0.0-canary.73 → 8.0.0-canary.74
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 +7 -0
- package/Footer/Footer.tsx +97 -94
- package/LazyHydrate/LazyHydrate.tsx +72 -0
- package/LazyHydrate/index.ts +1 -0
- package/Navigation/components/NavigationOverlay.tsx +0 -4
- package/Navigation/components/NavigationProvider.tsx +18 -4
- package/index.ts +3 -2
- package/package.json +8 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## 8.0.0-canary.74
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#2133](https://github.com/graphcommerce-org/graphcommerce/pull/2133) [`133f908`](https://github.com/graphcommerce-org/graphcommerce/commit/133f908200a79589036420f2925835724522cab8) - Added lazy hydration to improve total blocking time. Added LazyHydrate component which can be wrapped around other components you want to lazy hydrate.
|
|
8
|
+
([@Jessevdpoel](https://github.com/Jessevdpoel))
|
|
9
|
+
|
|
3
10
|
## 8.0.0-canary.73
|
|
4
11
|
|
|
5
12
|
## 8.0.0-canary.72
|
package/Footer/Footer.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ContainerProps, Container, Box } from '@mui/material'
|
|
2
2
|
import React from 'react'
|
|
3
|
+
import { LazyHydrate } from '../LazyHydrate'
|
|
3
4
|
import { extendableComponent } from '../Styles'
|
|
4
5
|
|
|
5
6
|
export type FooterProps = {
|
|
@@ -30,110 +31,112 @@ export function Footer(props: FooterProps) {
|
|
|
30
31
|
} = props
|
|
31
32
|
|
|
32
33
|
return (
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
34
|
+
<LazyHydrate>
|
|
35
|
+
<Container
|
|
36
|
+
sx={[
|
|
37
|
+
(theme) => ({
|
|
38
|
+
gridTemplateColumns: '5fr 3fr',
|
|
39
|
+
borderTop: `1px solid ${theme.palette.divider}`,
|
|
40
|
+
display: 'grid',
|
|
41
|
+
alignItems: 'center',
|
|
42
|
+
padding: `${theme.spacings.lg} ${theme.page.horizontal} ${theme.page.vertical}`,
|
|
43
|
+
justifyItems: 'center',
|
|
44
|
+
gridTemplateAreas: `
|
|
43
45
|
'switcher switcher'
|
|
44
46
|
'support support'
|
|
45
47
|
'social social'
|
|
46
48
|
'links links'
|
|
47
49
|
`,
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
gap: theme.spacings.md,
|
|
51
|
+
'& > *': { maxWidth: 'max-content' },
|
|
50
52
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
[theme.breakpoints.up('md')]: {
|
|
54
|
+
gap: theme.spacings.sm,
|
|
55
|
+
gridTemplateAreas: `
|
|
54
56
|
'social switcher'
|
|
55
57
|
'links support'
|
|
56
58
|
`,
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
},
|
|
63
|
-
}),
|
|
64
|
-
...(Array.isArray(sx) ? sx : [sx]),
|
|
65
|
-
]}
|
|
66
|
-
maxWidth={false}
|
|
67
|
-
className={classes.root}
|
|
68
|
-
{...containerProps}
|
|
69
|
-
>
|
|
70
|
-
{socialLinks && (
|
|
71
|
-
<Box
|
|
72
|
-
sx={(theme) => ({
|
|
73
|
-
display: 'grid',
|
|
74
|
-
justifyContent: 'start',
|
|
75
|
-
gridAutoFlow: 'column',
|
|
76
|
-
gridArea: 'social',
|
|
77
|
-
gap: { xs: `0 ${theme.spacings.xs}`, md: `0 ${theme.spacings.xs}` },
|
|
78
|
-
'& > *': {
|
|
79
|
-
minWidth: 'min-content',
|
|
80
|
-
},
|
|
81
|
-
})}
|
|
82
|
-
className={classes.social}
|
|
83
|
-
>
|
|
84
|
-
{socialLinks}
|
|
85
|
-
</Box>
|
|
86
|
-
)}
|
|
87
|
-
{storeSwitcher && (
|
|
88
|
-
<Box
|
|
89
|
-
sx={(theme) => ({
|
|
90
|
-
gridArea: 'switcher',
|
|
91
|
-
justifySelf: 'end',
|
|
92
|
-
[theme.breakpoints.down('md')]: {
|
|
93
|
-
justifySelf: 'center',
|
|
94
|
-
},
|
|
95
|
-
})}
|
|
96
|
-
className={classes.storeSwitcher}
|
|
97
|
-
>
|
|
98
|
-
{storeSwitcher}
|
|
99
|
-
</Box>
|
|
100
|
-
)}
|
|
101
|
-
{customerService && (
|
|
102
|
-
<Box
|
|
103
|
-
sx={(theme) => ({
|
|
104
|
-
gridArea: 'support',
|
|
105
|
-
justifySelf: 'flex-end',
|
|
106
|
-
[theme.breakpoints.down('md')]: {
|
|
107
|
-
justifySelf: 'center',
|
|
108
|
-
},
|
|
109
|
-
})}
|
|
110
|
-
className={classes.support}
|
|
111
|
-
>
|
|
112
|
-
{customerService}
|
|
113
|
-
</Box>
|
|
114
|
-
)}
|
|
115
|
-
{copyright && (
|
|
116
|
-
<Box
|
|
117
|
-
sx={(theme) => ({
|
|
118
|
-
typography: 'body2',
|
|
119
|
-
display: 'grid',
|
|
120
|
-
gridAutoFlow: 'column',
|
|
121
|
-
alignContent: 'center',
|
|
122
|
-
gridArea: 'links',
|
|
123
|
-
gap: theme.spacings.sm,
|
|
124
|
-
[theme.breakpoints.down('md')]: {
|
|
125
|
-
gridAutoFlow: 'row',
|
|
126
|
-
textAlign: 'center',
|
|
127
|
-
gap: `8px`,
|
|
59
|
+
justifyItems: 'start',
|
|
60
|
+
padding: `${theme.page.vertical} ${theme.page.horizontal}`,
|
|
61
|
+
gridTemplateColumns: 'auto auto',
|
|
62
|
+
gridTemplateRows: 'auto',
|
|
63
|
+
justifyContent: 'space-between',
|
|
128
64
|
},
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
65
|
+
}),
|
|
66
|
+
...(Array.isArray(sx) ? sx : [sx]),
|
|
67
|
+
]}
|
|
68
|
+
maxWidth={false}
|
|
69
|
+
className={classes.root}
|
|
70
|
+
{...containerProps}
|
|
71
|
+
>
|
|
72
|
+
{socialLinks && (
|
|
73
|
+
<Box
|
|
74
|
+
sx={(theme) => ({
|
|
75
|
+
display: 'grid',
|
|
76
|
+
justifyContent: 'start',
|
|
77
|
+
gridAutoFlow: 'column',
|
|
78
|
+
gridArea: 'social',
|
|
79
|
+
gap: { xs: `0 ${theme.spacings.xs}`, md: `0 ${theme.spacings.xs}` },
|
|
80
|
+
'& > *': {
|
|
81
|
+
minWidth: 'min-content',
|
|
82
|
+
},
|
|
83
|
+
})}
|
|
84
|
+
className={classes.social}
|
|
85
|
+
>
|
|
86
|
+
{socialLinks}
|
|
87
|
+
</Box>
|
|
88
|
+
)}
|
|
89
|
+
{storeSwitcher && (
|
|
90
|
+
<Box
|
|
91
|
+
sx={(theme) => ({
|
|
92
|
+
gridArea: 'switcher',
|
|
93
|
+
justifySelf: 'end',
|
|
94
|
+
[theme.breakpoints.down('md')]: {
|
|
95
|
+
justifySelf: 'center',
|
|
96
|
+
},
|
|
97
|
+
})}
|
|
98
|
+
className={classes.storeSwitcher}
|
|
99
|
+
>
|
|
100
|
+
{storeSwitcher}
|
|
101
|
+
</Box>
|
|
102
|
+
)}
|
|
103
|
+
{customerService && (
|
|
104
|
+
<Box
|
|
105
|
+
sx={(theme) => ({
|
|
106
|
+
gridArea: 'support',
|
|
107
|
+
justifySelf: 'flex-end',
|
|
108
|
+
[theme.breakpoints.down('md')]: {
|
|
109
|
+
justifySelf: 'center',
|
|
110
|
+
},
|
|
111
|
+
})}
|
|
112
|
+
className={classes.support}
|
|
113
|
+
>
|
|
114
|
+
{customerService}
|
|
115
|
+
</Box>
|
|
116
|
+
)}
|
|
117
|
+
{copyright && (
|
|
118
|
+
<Box
|
|
119
|
+
sx={(theme) => ({
|
|
120
|
+
typography: 'body2',
|
|
121
|
+
display: 'grid',
|
|
122
|
+
gridAutoFlow: 'column',
|
|
123
|
+
alignContent: 'center',
|
|
124
|
+
gridArea: 'links',
|
|
125
|
+
gap: theme.spacings.sm,
|
|
126
|
+
[theme.breakpoints.down('md')]: {
|
|
127
|
+
gridAutoFlow: 'row',
|
|
128
|
+
textAlign: 'center',
|
|
129
|
+
gap: `8px`,
|
|
130
|
+
},
|
|
131
|
+
})}
|
|
132
|
+
className={classes.copyright}
|
|
133
|
+
>
|
|
134
|
+
{copyright}
|
|
135
|
+
</Box>
|
|
136
|
+
)}
|
|
137
|
+
{children}
|
|
138
|
+
</Container>
|
|
139
|
+
</LazyHydrate>
|
|
137
140
|
)
|
|
138
141
|
}
|
|
139
142
|
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import React, { useState, useRef, startTransition, useLayoutEffect, useEffect } from 'react'
|
|
2
|
+
|
|
3
|
+
// Make sure the server doesn't choke on the useLayoutEffect
|
|
4
|
+
export const useLayoutEffect2 = typeof window !== 'undefined' ? useLayoutEffect : useEffect
|
|
5
|
+
|
|
6
|
+
export type LazyHydrateProps = {
|
|
7
|
+
/**
|
|
8
|
+
* The content is always rendered on the server and on the client it uses the server rendered HTML until it is hydrated.
|
|
9
|
+
*/
|
|
10
|
+
children: React.ReactNode
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* When a boolean is provided, the IntersectionObserver is disabled and hydrates the component when the value becomes true.
|
|
14
|
+
*
|
|
15
|
+
* For example:
|
|
16
|
+
* - Disable the hydration functionality completely: `<LazyHydrate hydrated={true}>`
|
|
17
|
+
* - Hydrate the component on some state `<LazyHydrate hydrated={someState}>` where someState initially is false and later becomes true.
|
|
18
|
+
*/
|
|
19
|
+
hydrated?: boolean
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* LazyHydrate can defer the hydration of a component until it becomes visible.
|
|
24
|
+
* OR manually by using the hydrated prop.
|
|
25
|
+
* This can be a way to improve the TBT of a page.
|
|
26
|
+
*/
|
|
27
|
+
export function LazyHydrate(props: LazyHydrateProps) {
|
|
28
|
+
const { hydrated, children } = props
|
|
29
|
+
const rootRef = useRef<HTMLElement>(null)
|
|
30
|
+
|
|
31
|
+
const [isHydrated, setIsHydrated] = useState(hydrated || false)
|
|
32
|
+
if (!isHydrated && hydrated) setIsHydrated(true)
|
|
33
|
+
|
|
34
|
+
useLayoutEffect2(() => {
|
|
35
|
+
// If we are manually hydrating, we watch that value and do not use the IntersectionObserver
|
|
36
|
+
if (isHydrated || !rootRef.current) return undefined
|
|
37
|
+
|
|
38
|
+
// If the element wasn't rendered on the server, we hydrate it immediately
|
|
39
|
+
if (!rootRef.current?.hasAttribute('data-lazy-hydrate')) {
|
|
40
|
+
setIsHydrated(true)
|
|
41
|
+
return undefined
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// The user has opted to manually hydrate the component
|
|
45
|
+
if (hydrated !== undefined) return undefined
|
|
46
|
+
|
|
47
|
+
const observer = new IntersectionObserver(
|
|
48
|
+
([entry]) => {
|
|
49
|
+
if (entry.isIntersecting && entry.intersectionRatio > 0) {
|
|
50
|
+
startTransition(() => setIsHydrated(true))
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
{ rootMargin: '200px' },
|
|
54
|
+
)
|
|
55
|
+
observer.observe(rootRef.current)
|
|
56
|
+
|
|
57
|
+
return () => observer.disconnect()
|
|
58
|
+
}, [hydrated, isHydrated])
|
|
59
|
+
|
|
60
|
+
if (isHydrated) {
|
|
61
|
+
return <section>{children}</section>
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (typeof window === 'undefined') {
|
|
65
|
+
return <section data-lazy-hydrate>{children}</section>
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
// eslint-disable-next-line react/no-danger
|
|
70
|
+
<section ref={rootRef} dangerouslySetInnerHTML={{ __html: '' }} suppressHydrationWarning />
|
|
71
|
+
)
|
|
72
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './LazyHydrate'
|
|
@@ -77,10 +77,6 @@ export const NavigationOverlay = React.memo((props: NavigationOverlayProps) => {
|
|
|
77
77
|
c ? false : s !== false,
|
|
78
78
|
)
|
|
79
79
|
|
|
80
|
-
useEffect(() => {
|
|
81
|
-
animating.set(true)
|
|
82
|
-
}, [activeAndNotClosing, animating])
|
|
83
|
-
|
|
84
80
|
const afterClose = useEventCallback(() => {
|
|
85
81
|
if (!closing.get()) return
|
|
86
82
|
setTimeout(() => {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { MotionConfig, useMotionValue } from 'framer-motion'
|
|
1
|
+
import { MotionConfig, useMotionValue, useTransform } from 'framer-motion'
|
|
2
2
|
import React, { useMemo } from 'react'
|
|
3
3
|
import { isElement } from 'react-is'
|
|
4
|
+
import { LazyHydrate } from '../../LazyHydrate'
|
|
5
|
+
import { nonNullable } from '../../RenderType/nonNullable'
|
|
4
6
|
import {
|
|
5
7
|
NavigationNode,
|
|
6
8
|
NavigationContextType,
|
|
@@ -9,8 +11,9 @@ import {
|
|
|
9
11
|
NavigationNodeType,
|
|
10
12
|
NavigationNodeComponent,
|
|
11
13
|
} from '../hooks/useNavigation'
|
|
14
|
+
import { useMotionValueValue } from '@graphcommerce/framer-utils'
|
|
12
15
|
|
|
13
|
-
export type
|
|
16
|
+
export type NavigationProviderBaseProps = {
|
|
14
17
|
items: (NavigationNode | React.ReactElement)[]
|
|
15
18
|
hideRootOnNavigate?: boolean
|
|
16
19
|
closeAfterNavigate?: boolean
|
|
@@ -20,9 +23,9 @@ export type NavigationProviderProps = {
|
|
|
20
23
|
serverRenderDepth?: number
|
|
21
24
|
}
|
|
22
25
|
|
|
23
|
-
|
|
26
|
+
export type NavigationProviderProps = NavigationProviderBaseProps & { hold?: boolean }
|
|
24
27
|
|
|
25
|
-
|
|
28
|
+
const NavigationProviderBase = React.memo<NavigationProviderBaseProps>((props) => {
|
|
26
29
|
const {
|
|
27
30
|
items,
|
|
28
31
|
hideRootOnNavigate = true,
|
|
@@ -73,3 +76,14 @@ export const NavigationProvider = React.memo<NavigationProviderProps>((props) =>
|
|
|
73
76
|
</MotionConfig>
|
|
74
77
|
)
|
|
75
78
|
})
|
|
79
|
+
|
|
80
|
+
export function NavigationProvider(props: NavigationProviderProps) {
|
|
81
|
+
const { selection } = props
|
|
82
|
+
const hydrateManually = useMotionValueValue(selection, (s) => s !== false)
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<LazyHydrate hydrated={hydrateManually}>
|
|
86
|
+
<NavigationProviderBase {...props} />
|
|
87
|
+
</LazyHydrate>
|
|
88
|
+
)
|
|
89
|
+
}
|
package/index.ts
CHANGED
|
@@ -22,15 +22,14 @@ 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'
|
|
26
25
|
export * from './IconHeader/IconHeader'
|
|
27
|
-
export * from './icons'
|
|
28
26
|
export * from './IconSvg'
|
|
29
27
|
export * from './JsonLd/JsonLd'
|
|
30
28
|
export * from './Layout'
|
|
31
29
|
export * from './LayoutDefault'
|
|
32
30
|
export * from './LayoutOverlay'
|
|
33
31
|
export * from './LayoutParts'
|
|
32
|
+
export * from './LazyHydrate'
|
|
34
33
|
export * from './Navigation'
|
|
35
34
|
export * from './Overlay'
|
|
36
35
|
export * from './OverlayOrPopperChip'
|
|
@@ -56,4 +55,6 @@ export * from './ToggleButton/ToggleButton'
|
|
|
56
55
|
export * from './ToggleButtonGroup/ToggleButtonGroup'
|
|
57
56
|
export * from './UspList/UspList'
|
|
58
57
|
export * from './UspList/UspListItem'
|
|
58
|
+
export * from './hooks'
|
|
59
|
+
export * from './icons'
|
|
59
60
|
export * from './utils/cookie'
|
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": "8.0.0-canary.
|
|
5
|
+
"version": "8.0.0-canary.74",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"prettier": "@graphcommerce/prettier-config-pwa",
|
|
8
8
|
"eslintConfig": {
|
|
@@ -26,13 +26,13 @@
|
|
|
26
26
|
"typescript": "5.3.3"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
|
-
"@graphcommerce/eslint-config-pwa": "^8.0.0-canary.
|
|
30
|
-
"@graphcommerce/framer-next-pages": "^8.0.0-canary.
|
|
31
|
-
"@graphcommerce/framer-scroller": "^8.0.0-canary.
|
|
32
|
-
"@graphcommerce/framer-utils": "^8.0.0-canary.
|
|
33
|
-
"@graphcommerce/image": "^8.0.0-canary.
|
|
34
|
-
"@graphcommerce/prettier-config-pwa": "^8.0.0-canary.
|
|
35
|
-
"@graphcommerce/typescript-config-pwa": "^8.0.0-canary.
|
|
29
|
+
"@graphcommerce/eslint-config-pwa": "^8.0.0-canary.74",
|
|
30
|
+
"@graphcommerce/framer-next-pages": "^8.0.0-canary.74",
|
|
31
|
+
"@graphcommerce/framer-scroller": "^8.0.0-canary.74",
|
|
32
|
+
"@graphcommerce/framer-utils": "^8.0.0-canary.74",
|
|
33
|
+
"@graphcommerce/image": "^8.0.0-canary.74",
|
|
34
|
+
"@graphcommerce/prettier-config-pwa": "^8.0.0-canary.74",
|
|
35
|
+
"@graphcommerce/typescript-config-pwa": "^8.0.0-canary.74",
|
|
36
36
|
"@lingui/core": "^4.2.1",
|
|
37
37
|
"@lingui/macro": "^4.2.1",
|
|
38
38
|
"@lingui/react": "^4.2.1",
|