@graphcommerce/framer-next-pages 3.2.3 → 3.2.4
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 +8 -0
- package/components/Page.tsx +7 -6
- package/components/Pages.tsx +34 -15
- package/context/pageContext.ts +1 -1
- package/hooks/useScrollOffsetY.ts +11 -3
- package/package.json +3 -3
- package/types.ts +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## 3.2.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#1552](https://github.com/graphcommerce-org/graphcommerce/pull/1552) [`18054c441`](https://github.com/graphcommerce-org/graphcommerce/commit/18054c441962ba750bed3acc39ab46c8d3a341ce) Thanks [@paales](https://github.com/paales)! - Updated to Next.js v12.2.2 and other packages and made compatible
|
|
8
|
+
|
|
9
|
+
* [#1552](https://github.com/graphcommerce-org/graphcommerce/pull/1552) [`21886d6fa`](https://github.com/graphcommerce-org/graphcommerce/commit/21886d6fa64a48d9e932bfaf8d138c9b13c36e43) Thanks [@paales](https://github.com/paales)! - Fix page stacking and scroll restoration when navigating
|
|
10
|
+
|
|
3
11
|
## 3.2.3
|
|
4
12
|
|
|
5
13
|
### Patch Changes
|
package/components/Page.tsx
CHANGED
|
@@ -3,38 +3,39 @@ import { m, useIsPresent } from 'framer-motion'
|
|
|
3
3
|
import React from 'react'
|
|
4
4
|
import type { PageItem } from '../types'
|
|
5
5
|
|
|
6
|
-
export type PageProps = Pick<PageItem, '
|
|
6
|
+
export type PageProps = Pick<PageItem, 'routerKey'> & {
|
|
7
7
|
active: boolean
|
|
8
8
|
children: React.ReactNode
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export function scrollPos(
|
|
12
|
-
const scroll = global.window?.sessionStorage[`__next_scroll_${
|
|
11
|
+
export function scrollPos(key: string): { x: number; y: number } {
|
|
12
|
+
const scroll = global.window?.sessionStorage[`__next_scroll_${key}`]
|
|
13
13
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
14
14
|
return scroll ? JSON.parse(scroll) : { x: 0, y: 0 }
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export function Page(props: PageProps) {
|
|
18
|
-
const { active,
|
|
18
|
+
const { active, routerKey, children } = props
|
|
19
19
|
const isPresent = useIsPresent()
|
|
20
20
|
|
|
21
21
|
/** The active Page doesn't get any special treatment */
|
|
22
22
|
let top: number | undefined
|
|
23
23
|
|
|
24
24
|
/** If the Page isn't active, we offset the page */
|
|
25
|
-
if (!active) top = scrollPos(
|
|
25
|
+
if (!active) top = scrollPos(routerKey).y * -1
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* If the Page isn't present as a child of <AnimatePresence/>, but it is still present in the DOM,
|
|
29
29
|
* we're navigating back, so we need to offset it.
|
|
30
30
|
*/
|
|
31
|
-
if (!isPresent) top = scrollPos(
|
|
31
|
+
if (!isPresent) top = scrollPos(routerKey).y
|
|
32
32
|
|
|
33
33
|
const position = active && isPresent ? 'absolute' : 'fixed'
|
|
34
34
|
const zIndex = active ? 1 : undefined
|
|
35
35
|
|
|
36
36
|
return (
|
|
37
37
|
<m.div
|
|
38
|
+
layoutScroll
|
|
38
39
|
style={{ position, top, zIndex, minHeight: clientSizeCssVar.y, left: 0, right: 0 }}
|
|
39
40
|
// @ts-expect-error inert is not in the type definition yet
|
|
40
41
|
inert={!active ? 'true' : undefined}
|
package/components/Pages.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { clientSizeCssVar, useClientSizeCssVar } from '@graphcommerce/framer-utils'
|
|
2
2
|
import { AnimatePresence, m } from 'framer-motion'
|
|
3
3
|
import { requestIdleCallback, cancelIdleCallback } from 'next/dist/client/request-idle-callback'
|
|
4
|
+
import { HistoryState, PrivateRouteInfo } from 'next/dist/shared/lib/router/router'
|
|
4
5
|
import { AppPropsType } from 'next/dist/shared/lib/utils'
|
|
5
6
|
import { NextRouter, Router } from 'next/router'
|
|
6
7
|
import { useEffect, useRef, useState } from 'react'
|
|
@@ -44,14 +45,23 @@ function getPageInfo(router: NextRouter) {
|
|
|
44
45
|
export function FramerNextPages(props: PagesProps) {
|
|
45
46
|
const { router, Component, pageProps: incomingProps, fallback = '/', fallbackRoute = '/' } = props
|
|
46
47
|
|
|
48
|
+
// @ts-expect-error Key of the route is still private, should be fixed in https://github.com/vercel/next.js/pull/37192
|
|
49
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
50
|
+
const key = router._key as string
|
|
51
|
+
|
|
47
52
|
useClientSizeCssVar()
|
|
48
53
|
const items = useRef<PageItem[]>([])
|
|
49
|
-
|
|
54
|
+
|
|
55
|
+
const routerKeys = items.current.map((item) => item.routerKey)
|
|
56
|
+
if (routerKeys.indexOf(key) === -1) routerKeys.push(key)
|
|
57
|
+
const idx = routerKeys.indexOf(key)
|
|
58
|
+
|
|
50
59
|
const prevHistory = useRef<number>(-1)
|
|
51
|
-
const [fb, setFallback] = useState<PageItem>()
|
|
52
60
|
const direction = idx > prevHistory.current ? 1 : -1
|
|
53
61
|
prevHistory.current = idx
|
|
54
62
|
|
|
63
|
+
const [fb, setFallback] = useState<PageItem>()
|
|
64
|
+
|
|
55
65
|
/** We never need to render anything beyong the current idx and we can safely omit everything */
|
|
56
66
|
items.current = items.current.slice(0, idx + 1)
|
|
57
67
|
|
|
@@ -76,6 +86,7 @@ export function FramerNextPages(props: PagesProps) {
|
|
|
76
86
|
sharedKey: Component.pageOptions?.sharedKey?.(pageInfo) ?? pageInfo.pathname,
|
|
77
87
|
overlayGroup: Component.pageOptions?.overlayGroup,
|
|
78
88
|
historyIdx: idx,
|
|
89
|
+
routerKey: key,
|
|
79
90
|
routerContext: {
|
|
80
91
|
pageInfo,
|
|
81
92
|
prevPage: items.current[idx - 1]?.routerContext,
|
|
@@ -102,16 +113,23 @@ export function FramerNextPages(props: PagesProps) {
|
|
|
102
113
|
// todo: implement fallback loading for up property
|
|
103
114
|
// const up = items.current[0].PageComponent.pageOptions?.up?.href ?? '/'
|
|
104
115
|
const up = '/'
|
|
105
|
-
const info = await (router as Router).getRouteInfo(
|
|
106
|
-
fallbackRoute,
|
|
107
|
-
fallback,
|
|
108
|
-
{},
|
|
109
|
-
fallback,
|
|
110
|
-
fallback,
|
|
111
|
-
{ shallow: false },
|
|
112
|
-
router.locale,
|
|
113
|
-
false,
|
|
114
|
-
|
|
116
|
+
const info = await (router as Router).getRouteInfo({
|
|
117
|
+
route: fallbackRoute,
|
|
118
|
+
pathname: fallback,
|
|
119
|
+
query: {},
|
|
120
|
+
as: fallback,
|
|
121
|
+
resolvedAs: fallback,
|
|
122
|
+
routeProps: { shallow: false },
|
|
123
|
+
locale: router.locale,
|
|
124
|
+
hasMiddleware: false,
|
|
125
|
+
isPreview: false,
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
const isPrivateRouteInfo = (
|
|
129
|
+
infoResult: Awaited<ReturnType<Router['getRouteInfo']>>,
|
|
130
|
+
): infoResult is PrivateRouteInfo => 'Component' in infoResult
|
|
131
|
+
|
|
132
|
+
if (!isPrivateRouteInfo(info)) return
|
|
115
133
|
|
|
116
134
|
const pageInfo = { asPath: up, pathname: up, locale: router.locale, query: {} }
|
|
117
135
|
const Fallback = info.Component as PageComponent
|
|
@@ -122,6 +140,7 @@ export function FramerNextPages(props: PagesProps) {
|
|
|
122
140
|
sharedKey: Fallback.pageOptions?.sharedKey?.(pageInfo) ?? pageInfo.pathname,
|
|
123
141
|
overlayGroup: Fallback.pageOptions?.overlayGroup,
|
|
124
142
|
historyIdx: -1,
|
|
143
|
+
routerKey: 'fallback',
|
|
125
144
|
routerContext: {
|
|
126
145
|
pageInfo,
|
|
127
146
|
up: Fallback.pageOptions?.up ?? info.props?.pageProps?.up,
|
|
@@ -192,7 +211,7 @@ and pass it as a param in <FramerNextPages fallbackRoute='/[...url]' /> in your
|
|
|
192
211
|
/>
|
|
193
212
|
<AnimatePresence initial={false}>
|
|
194
213
|
{renderItems.map((item, itemIdx) => {
|
|
195
|
-
const { historyIdx, sharedKey, overlayGroup } = item
|
|
214
|
+
const { historyIdx, sharedKey, overlayGroup, routerKey } = item
|
|
196
215
|
const active = itemIdx === renderItems.length - 1
|
|
197
216
|
const depth = itemIdx - (renderItems.length - 1)
|
|
198
217
|
const closeIdx = renderItems[itemIdx - 1]?.historyIdx ?? -1
|
|
@@ -204,9 +223,9 @@ and pass it as a param in <FramerNextPages fallbackRoute='/[...url]' /> in your
|
|
|
204
223
|
key={sharedKey}
|
|
205
224
|
// We're actually rerendering here but since the actual page renderer is memoized we can safely do this
|
|
206
225
|
// eslint-disable-next-line react/jsx-no-constructed-context-values
|
|
207
|
-
value={{ depth, active, direction, closeSteps, backSteps,
|
|
226
|
+
value={{ depth, active, direction, closeSteps, backSteps, routerKey, overlayGroup }}
|
|
208
227
|
>
|
|
209
|
-
<Page active={active}
|
|
228
|
+
<Page active={active} routerKey={routerKey}>
|
|
210
229
|
<PageRenderer {...item} />
|
|
211
230
|
</Page>
|
|
212
231
|
</pageContext.Provider>
|
package/context/pageContext.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useMotionValue } from 'framer-motion'
|
|
2
|
+
import { useEffect, useMemo } from 'react'
|
|
2
3
|
import { scrollPos } from '../components/Page'
|
|
3
4
|
import { usePageContext } from './usePageContext'
|
|
4
5
|
|
|
@@ -7,8 +8,15 @@ import { usePageContext } from './usePageContext'
|
|
|
7
8
|
* scroll based elements.
|
|
8
9
|
*/
|
|
9
10
|
export function useScrollOffset() {
|
|
10
|
-
const { active,
|
|
11
|
+
const { active, routerKey } = usePageContext()
|
|
12
|
+
|
|
13
|
+
const scrolloffset = useMotionValue(0)
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (active) scrolloffset.set(0)
|
|
17
|
+
else scrolloffset.set(scrollPos(routerKey).y)
|
|
18
|
+
}, [active, routerKey, scrolloffset])
|
|
11
19
|
|
|
12
20
|
// When the page is rendered in the background we should use it's scroll offset while rendering
|
|
13
|
-
return
|
|
21
|
+
return scrolloffset
|
|
14
22
|
}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@graphcommerce/framer-next-pages",
|
|
3
3
|
"homepage": "https://www.graphcommerce.org/",
|
|
4
4
|
"repository": "github:graphcommerce-org/graphcommerce",
|
|
5
|
-
"version": "3.2.
|
|
5
|
+
"version": "3.2.4",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"prettier": "@graphcommerce/prettier-config-pwa",
|
|
8
8
|
"eslintConfig": {
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
"@graphcommerce/framer-utils": "3.1.4"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
|
-
"@graphcommerce/eslint-config-pwa": "^4.1.
|
|
18
|
+
"@graphcommerce/eslint-config-pwa": "^4.1.9",
|
|
19
19
|
"@graphcommerce/prettier-config-pwa": "^4.0.6",
|
|
20
|
-
"@graphcommerce/typescript-config-pwa": "^4.0.
|
|
20
|
+
"@graphcommerce/typescript-config-pwa": "^4.0.4",
|
|
21
21
|
"@playwright/test": "^1.21.1"
|
|
22
22
|
},
|
|
23
23
|
"peerDependencies": {
|
package/types.ts
CHANGED
|
@@ -80,9 +80,9 @@ export type PageOptions<T extends Record<string, unknown> = Record<string, unkno
|
|
|
80
80
|
* FramerNextPages uses Framer Motion's <AnimatePresence/> to animate pages in and out. From the
|
|
81
81
|
* [docs](https://www.framer.com/api/motion/animate-presence/#usage):
|
|
82
82
|
*
|
|
83
|
-
*
|
|
83
|
+
* _In React, changing a component's key makes React treat it as an entirely new component. So the
|
|
84
84
|
* old one is unmounted before the new one is mounted. So by changing the key of a single child of
|
|
85
|
-
* AnimatePresence, we can easily make animated page transitions!
|
|
85
|
+
* AnimatePresence, we can easily make animated page transitions! 🎉_
|
|
86
86
|
*
|
|
87
87
|
* To create transitions we need to let React know if we should create a new component. We do this
|
|
88
88
|
* by specifying a 'sharedKey'. By default this key is the same as the pathname:
|
|
@@ -143,6 +143,7 @@ export type PageItem = {
|
|
|
143
143
|
routerOverride?: Partial<NextRouter>
|
|
144
144
|
PageComponent: PageComponent
|
|
145
145
|
historyIdx: number
|
|
146
|
+
routerKey: string
|
|
146
147
|
sharedKey: string
|
|
147
148
|
pageProps?: Record<string, unknown>
|
|
148
149
|
routerContext: PageContext
|