@graphcommerce/next-ui 4.20.0 → 4.23.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/ActionCard/ActionCard.tsx +46 -12
- package/ActionCard/ActionCardList.tsx +17 -15
- package/ActionCard/ActionCardListForm.tsx +1 -2
- package/AnimatedRow/AnimatedRow.tsx +1 -0
- package/Blog/BlogHeader/BlogHeader.tsx +7 -1
- package/Blog/BlogListItem/BlogListItem.tsx +5 -1
- package/Blog/BlogTags/BlogTag.tsx +2 -3
- package/CHANGELOG.md +57 -0
- package/Footer/Footer.tsx +3 -2
- package/Form/Form.tsx +7 -1
- package/Form/FormActions.tsx +1 -1
- package/FramerScroller/SidebarGallery.tsx +1 -5
- package/IconSvg/IconSvg.tsx +4 -4
- package/Layout/components/LayoutHeaderClose.tsx +2 -0
- package/Layout/components/LayoutHeaderContent.tsx +3 -1
- package/LayoutDefault/components/LayoutDefault.tsx +3 -3
- package/LayoutOverlay/components/LayoutOverlay.tsx +2 -1
- package/LayoutParts/DesktopHeaderBadge.tsx +3 -3
- package/Navigation/components/NavigationItem.tsx +32 -18
- package/Navigation/components/NavigationList.tsx +21 -15
- package/Navigation/components/NavigationOverlay.tsx +65 -37
- package/Navigation/components/NavigationProvider.tsx +12 -30
- package/Navigation/hooks/useNavigation.ts +33 -6
- package/Overlay/components/Overlay.tsx +1 -2
- package/Overlay/components/OverlayBase.tsx +19 -19
- package/Overlay/hooks/useOverlayPosition.ts +6 -1
- package/Row/HeroBanner/HeroBanner.tsx +7 -2
- package/Row/ImageText/ImageText.tsx +10 -3
- package/Row/ImageTextBoxed/ImageTextBoxed.tsx +7 -1
- package/Row/ParagraphWithSidebarSlide/ParagraphWithSidebarSlide.tsx +4 -2
- package/Row/SpecialBanner/SpecialBanner.tsx +10 -2
- package/Snackbar/MessageSnackbarImpl.tsx +9 -5
- package/Stepper/Stepper.tsx +1 -1
- package/Styles/breakpointVal.tsx +1 -1
- package/TextInputNumber/TextInputNumber.tsx +5 -4
- package/Theme/MuiButton.ts +12 -3
- package/Theme/MuiFab.ts +2 -2
- package/ToggleButton/ToggleButton.tsx +15 -5
- package/hooks/index.ts +1 -0
- package/hooks/useMemoDeep.ts +15 -0
- package/package.json +6 -6
|
@@ -1,38 +1,40 @@
|
|
|
1
1
|
import { styled } from '@mui/material'
|
|
2
|
+
import React from 'react'
|
|
2
3
|
import { extendableComponent } from '../../Styles/extendableComponent'
|
|
3
|
-
import { NavigationNode
|
|
4
|
+
import { NavigationNode } from '../hooks/useNavigation'
|
|
4
5
|
import { NavigationItem, mouseEventPref } from './NavigationItem'
|
|
5
6
|
|
|
6
|
-
const NavigationUList = styled('ul')({
|
|
7
|
+
const NavigationUList = styled('ul')({
|
|
8
|
+
display: 'block',
|
|
9
|
+
position: 'absolute',
|
|
10
|
+
left: '-10000px',
|
|
11
|
+
top: '-10000px',
|
|
12
|
+
'&.selected': {
|
|
13
|
+
display: 'contents',
|
|
14
|
+
},
|
|
15
|
+
})
|
|
7
16
|
|
|
8
17
|
type NavigationItemsProps = {
|
|
9
|
-
parentPath?:
|
|
18
|
+
parentPath?: string
|
|
10
19
|
items: NavigationNode[]
|
|
11
20
|
selected?: boolean
|
|
12
21
|
} & mouseEventPref
|
|
13
22
|
|
|
14
23
|
type OwnerState = {
|
|
15
24
|
column: number
|
|
25
|
+
selected: boolean
|
|
16
26
|
}
|
|
17
27
|
|
|
18
28
|
const name = 'NavigationList'
|
|
19
29
|
const parts = ['root'] as const
|
|
20
30
|
const { withState } = extendableComponent<OwnerState, typeof name, typeof parts>(name, parts)
|
|
21
31
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
export function NavigationList(props: NavigationItemsProps) {
|
|
26
|
-
const { items, parentPath = [], selected = false, mouseEvent } = props
|
|
32
|
+
export const NavigationList = React.memo<NavigationItemsProps>((props) => {
|
|
33
|
+
const { items, parentPath = '', selected = false, mouseEvent } = props
|
|
27
34
|
|
|
35
|
+
const classes = withState({ column: 0, selected })
|
|
28
36
|
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
|
-
>
|
|
37
|
+
<NavigationUList className={classes.root}>
|
|
36
38
|
{items.map((item, idx) => (
|
|
37
39
|
<NavigationItem
|
|
38
40
|
NavigationList={NavigationList}
|
|
@@ -48,4 +50,8 @@ export function NavigationList(props: NavigationItemsProps) {
|
|
|
48
50
|
))}
|
|
49
51
|
</NavigationUList>
|
|
50
52
|
)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
56
|
+
NavigationList.displayName = 'NavigationList'
|
|
51
57
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import styled from '@emotion/styled'
|
|
2
|
+
import { useMotionValueValue, useMotionSelector } from '@graphcommerce/framer-utils'
|
|
2
3
|
import { i18n } from '@lingui/core'
|
|
3
4
|
import { Trans } from '@lingui/react'
|
|
4
5
|
import { Box, Fab, SxProps, Theme, useEventCallback, useMediaQuery } from '@mui/material'
|
|
5
|
-
import { m } from 'framer-motion'
|
|
6
|
-
import { useEffect, useState } from 'react'
|
|
6
|
+
import { m, useMotionValue } from 'framer-motion'
|
|
7
|
+
import React, { startTransition, useEffect, useRef, useState } from 'react'
|
|
8
|
+
import type { LiteralUnion } from 'type-fest'
|
|
7
9
|
import { IconSvg, useIconSvgSize } from '../../IconSvg'
|
|
8
10
|
import { LayoutHeaderContent } from '../../Layout/components/LayoutHeaderContent'
|
|
9
11
|
import { LayoutTitle } from '../../Layout/components/LayoutTitle'
|
|
@@ -17,6 +19,7 @@ import {
|
|
|
17
19
|
NavigationContextType,
|
|
18
20
|
NavigationNodeButton,
|
|
19
21
|
NavigationNodeHref,
|
|
22
|
+
NavigationPath,
|
|
20
23
|
useNavigation,
|
|
21
24
|
} from '../hooks/useNavigation'
|
|
22
25
|
import { mouseEventPref } from './NavigationItem'
|
|
@@ -25,9 +28,9 @@ import { NavigationList } from './NavigationList'
|
|
|
25
28
|
type LayoutOverlayVariant = 'left' | 'bottom' | 'right'
|
|
26
29
|
type LayoutOverlaySize = 'floating' | 'minimal' | 'full'
|
|
27
30
|
type LayoutOverlayAlign = 'start' | 'end' | 'center' | 'stretch'
|
|
31
|
+
type ItemPadding = LiteralUnion<keyof Theme['spacings'], string | number>
|
|
28
32
|
|
|
29
33
|
type NavigationOverlayProps = {
|
|
30
|
-
active: boolean
|
|
31
34
|
sx?: SxProps<Theme>
|
|
32
35
|
stretchColumns?: boolean
|
|
33
36
|
variantSm: LayoutOverlayVariant
|
|
@@ -38,12 +41,14 @@ type NavigationOverlayProps = {
|
|
|
38
41
|
justifyMd?: LayoutOverlayAlign
|
|
39
42
|
itemWidthSm?: string
|
|
40
43
|
itemWidthMd?: string
|
|
44
|
+
itemPadding?: ItemPadding
|
|
41
45
|
} & mouseEventPref
|
|
42
46
|
|
|
43
47
|
function findCurrent(
|
|
44
48
|
items: NavigationContextType['items'],
|
|
45
|
-
selected:
|
|
49
|
+
selected: NavigationPath | false,
|
|
46
50
|
): NavigationNodeHref | NavigationNodeButton | undefined {
|
|
51
|
+
if (selected === false) return undefined
|
|
47
52
|
const lastItem = selected.slice(-1)[0]
|
|
48
53
|
|
|
49
54
|
if (!lastItem) return undefined
|
|
@@ -67,9 +72,8 @@ const componentName = 'Navigation'
|
|
|
67
72
|
const parts = ['root', 'navigation', 'header', 'column'] as const
|
|
68
73
|
const { classes } = extendableComponent(componentName, parts)
|
|
69
74
|
|
|
70
|
-
export
|
|
75
|
+
export const NavigationOverlay = React.memo<NavigationOverlayProps>((props) => {
|
|
71
76
|
const {
|
|
72
|
-
active,
|
|
73
77
|
sx,
|
|
74
78
|
stretchColumns,
|
|
75
79
|
variantMd,
|
|
@@ -81,29 +85,44 @@ export function NavigationOverlay(props: NavigationOverlayProps) {
|
|
|
81
85
|
itemWidthSm,
|
|
82
86
|
itemWidthMd,
|
|
83
87
|
mouseEvent,
|
|
88
|
+
itemPadding = 'md',
|
|
84
89
|
} = props
|
|
85
|
-
const {
|
|
90
|
+
const { selection, items, animating, closing } = useNavigation()
|
|
86
91
|
|
|
87
92
|
const fabSize = useFabSize('responsive')
|
|
88
93
|
const svgSize = useIconSvgSize('large')
|
|
89
94
|
|
|
90
95
|
const isMobile = useMediaQuery<Theme>((theme) => theme.breakpoints.down('md'))
|
|
91
96
|
const handleOnBack = useEventCallback(() => {
|
|
92
|
-
if (isMobile)
|
|
93
|
-
|
|
97
|
+
if (isMobile) {
|
|
98
|
+
const current = selection.get()
|
|
99
|
+
selection.set(current !== false ? current.slice(0, -1) : false)
|
|
100
|
+
} else selection.set([])
|
|
94
101
|
})
|
|
95
102
|
|
|
103
|
+
const selectedLevel = useMotionValueValue(selection, (s) => (s === false ? -1 : s.length))
|
|
104
|
+
const activeAndNotClosing = useMotionSelector([selection, closing], ([s, c]) =>
|
|
105
|
+
c ? false : s !== false,
|
|
106
|
+
)
|
|
107
|
+
|
|
96
108
|
useEffect(() => {
|
|
97
|
-
animating.
|
|
98
|
-
}, [
|
|
109
|
+
animating.set(false)
|
|
110
|
+
}, [activeAndNotClosing, animating])
|
|
111
|
+
|
|
112
|
+
const afterClose = useEventCallback(() => {
|
|
113
|
+
if (!closing.get()) return
|
|
114
|
+
closing.set(false)
|
|
115
|
+
selection.set(false)
|
|
116
|
+
})
|
|
99
117
|
|
|
100
|
-
const
|
|
118
|
+
const handleClose = useEventCallback(() => closing.set(true))
|
|
101
119
|
|
|
102
120
|
return (
|
|
103
121
|
<Overlay
|
|
104
122
|
className={classes.root}
|
|
105
|
-
active={
|
|
106
|
-
|
|
123
|
+
active={activeAndNotClosing}
|
|
124
|
+
safeToRemove={afterClose}
|
|
125
|
+
onClosed={handleClose}
|
|
107
126
|
variantSm={variantSm}
|
|
108
127
|
sizeSm={sizeSm}
|
|
109
128
|
justifySm={justifySm}
|
|
@@ -111,13 +130,14 @@ export function NavigationOverlay(props: NavigationOverlayProps) {
|
|
|
111
130
|
sizeMd={sizeMd}
|
|
112
131
|
justifyMd={justifyMd}
|
|
113
132
|
overlayPaneProps={{
|
|
133
|
+
layout: true,
|
|
134
|
+
initial: false,
|
|
114
135
|
onLayoutAnimationStart: () => {
|
|
115
|
-
animating.
|
|
136
|
+
animating.set(true)
|
|
116
137
|
},
|
|
117
138
|
onLayoutAnimationComplete: () => {
|
|
118
|
-
animating.
|
|
139
|
+
animating.set(false)
|
|
119
140
|
},
|
|
120
|
-
layout: true,
|
|
121
141
|
}}
|
|
122
142
|
sx={{
|
|
123
143
|
zIndex: 'drawer',
|
|
@@ -146,7 +166,7 @@ export function NavigationOverlay(props: NavigationOverlayProps) {
|
|
|
146
166
|
switchPoint={0}
|
|
147
167
|
layout='position'
|
|
148
168
|
left={
|
|
149
|
-
|
|
169
|
+
selectedLevel > 0 && (
|
|
150
170
|
<Fab
|
|
151
171
|
color='inherit'
|
|
152
172
|
onClick={handleOnBack}
|
|
@@ -165,7 +185,7 @@ export function NavigationOverlay(props: NavigationOverlayProps) {
|
|
|
165
185
|
right={
|
|
166
186
|
<Fab
|
|
167
187
|
color='inherit'
|
|
168
|
-
onClick={
|
|
188
|
+
onClick={handleClose}
|
|
169
189
|
sx={{
|
|
170
190
|
boxShadow: 'none',
|
|
171
191
|
marginLeft: `calc((${fabSize} - ${svgSize}) * -0.5)`,
|
|
@@ -179,7 +199,7 @@ export function NavigationOverlay(props: NavigationOverlayProps) {
|
|
|
179
199
|
}
|
|
180
200
|
>
|
|
181
201
|
<LayoutTitle size='small' component='span'>
|
|
182
|
-
{findCurrent(items,
|
|
202
|
+
{findCurrent(items, selection.get())?.name ?? <Trans id='Menu' />}
|
|
183
203
|
</LayoutTitle>
|
|
184
204
|
</LayoutHeaderContent>
|
|
185
205
|
</Box>
|
|
@@ -193,15 +213,15 @@ export function NavigationOverlay(props: NavigationOverlayProps) {
|
|
|
193
213
|
'& .NavigationItem-item': {
|
|
194
214
|
// eslint-disable-next-line no-nested-ternary
|
|
195
215
|
width: itemWidthMd
|
|
196
|
-
?
|
|
216
|
+
? selectedLevel >= 1
|
|
197
217
|
? `calc(${itemWidthMd} + 1px)`
|
|
198
218
|
: itemWidthMd
|
|
199
|
-
: '
|
|
219
|
+
: 'stretch',
|
|
200
220
|
},
|
|
201
221
|
[theme.breakpoints.down('md')]: {
|
|
202
222
|
width:
|
|
203
223
|
sizeSm !== 'floating'
|
|
204
|
-
? `calc(${itemWidthSm || '100vw'} + ${
|
|
224
|
+
? `calc(${itemWidthSm || '100vw'} + ${selectedLevel}px)`
|
|
205
225
|
: `calc(${itemWidthSm || '100vw'} - ${theme.page.horizontal} - ${
|
|
206
226
|
theme.page.horizontal
|
|
207
227
|
})`,
|
|
@@ -211,13 +231,17 @@ export function NavigationOverlay(props: NavigationOverlayProps) {
|
|
|
211
231
|
'& .NavigationItem-item': {
|
|
212
232
|
width:
|
|
213
233
|
sizeSm !== 'floating'
|
|
214
|
-
? `calc(${itemWidthSm || '100vw'} - ${
|
|
215
|
-
theme.spacings
|
|
216
|
-
}
|
|
217
|
-
: `calc(${itemWidthSm || '100vw'} - ${
|
|
218
|
-
theme.spacings
|
|
219
|
-
} - ${theme.
|
|
220
|
-
|
|
234
|
+
? `calc(${itemWidthSm || '100vw'} - ${
|
|
235
|
+
theme.spacings[itemPadding] ?? itemPadding
|
|
236
|
+
} - ${theme.spacings[itemPadding] ?? itemPadding} + ${selectedLevel}px)`
|
|
237
|
+
: `calc(${itemWidthSm || '100vw'} - ${
|
|
238
|
+
theme.spacings[itemPadding] ?? itemPadding
|
|
239
|
+
} - ${theme.spacings[itemPadding] ?? itemPadding} - ${
|
|
240
|
+
theme.page.horizontal
|
|
241
|
+
} - ${theme.page.horizontal})`,
|
|
242
|
+
minWidth: `calc(${200}px - ${theme.spacings[itemPadding] ?? itemPadding} - ${
|
|
243
|
+
theme.spacings[itemPadding] ?? itemPadding
|
|
244
|
+
})`,
|
|
221
245
|
},
|
|
222
246
|
},
|
|
223
247
|
})}
|
|
@@ -226,7 +250,7 @@ export function NavigationOverlay(props: NavigationOverlayProps) {
|
|
|
226
250
|
className={classes.navigation}
|
|
227
251
|
sx={[
|
|
228
252
|
(theme) => ({
|
|
229
|
-
py: theme.spacings
|
|
253
|
+
py: theme.spacings[itemPadding] ?? itemPadding,
|
|
230
254
|
display: 'grid',
|
|
231
255
|
gridAutoFlow: 'column',
|
|
232
256
|
scrollSnapAlign: 'end',
|
|
@@ -238,11 +262,11 @@ export function NavigationOverlay(props: NavigationOverlayProps) {
|
|
|
238
262
|
},
|
|
239
263
|
'& .Navigation-column': {},
|
|
240
264
|
'& .NavigationItem-item': {
|
|
241
|
-
mx: theme.spacings
|
|
265
|
+
mx: theme.spacings[itemPadding] ?? itemPadding,
|
|
242
266
|
whiteSpace: 'nowrap',
|
|
243
267
|
},
|
|
244
268
|
'& .NavigationItem-item.first': {
|
|
245
|
-
// mt:
|
|
269
|
+
// mt: paddingMd,
|
|
246
270
|
},
|
|
247
271
|
'& .Navigation-column:first-of-type': {
|
|
248
272
|
boxShadow: 'none',
|
|
@@ -251,7 +275,7 @@ export function NavigationOverlay(props: NavigationOverlayProps) {
|
|
|
251
275
|
...(Array.isArray(sx) ? sx : [sx]),
|
|
252
276
|
]}
|
|
253
277
|
>
|
|
254
|
-
{
|
|
278
|
+
{selectedLevel >= 0 && (
|
|
255
279
|
<Box
|
|
256
280
|
sx={(theme) => ({
|
|
257
281
|
gridArea: '1 / 1 / 999 / 2',
|
|
@@ -260,7 +284,7 @@ export function NavigationOverlay(props: NavigationOverlayProps) {
|
|
|
260
284
|
className={classes.column}
|
|
261
285
|
/>
|
|
262
286
|
)}
|
|
263
|
-
{
|
|
287
|
+
{selectedLevel >= 1 && (
|
|
264
288
|
<Box
|
|
265
289
|
sx={(theme) => ({
|
|
266
290
|
gridArea: '1 / 2 / 999 / 3',
|
|
@@ -269,7 +293,7 @@ export function NavigationOverlay(props: NavigationOverlayProps) {
|
|
|
269
293
|
className={classes.column}
|
|
270
294
|
/>
|
|
271
295
|
)}
|
|
272
|
-
{
|
|
296
|
+
{selectedLevel >= 2 && (
|
|
273
297
|
<Box
|
|
274
298
|
sx={(theme) => ({
|
|
275
299
|
gridArea: '1 / 3 / 999 / 4',
|
|
@@ -278,7 +302,7 @@ export function NavigationOverlay(props: NavigationOverlayProps) {
|
|
|
278
302
|
className={classes.column}
|
|
279
303
|
/>
|
|
280
304
|
)}
|
|
281
|
-
{
|
|
305
|
+
{selectedLevel >= 3 && (
|
|
282
306
|
<Box
|
|
283
307
|
sx={(theme) => ({
|
|
284
308
|
gridArea: '1 / 4 / 999 / 5',
|
|
@@ -294,4 +318,8 @@ export function NavigationOverlay(props: NavigationOverlayProps) {
|
|
|
294
318
|
</MotionDiv>
|
|
295
319
|
</Overlay>
|
|
296
320
|
)
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
324
|
+
NavigationOverlay.displayName = 'NavigationOverlay'
|
|
297
325
|
}
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { useState, useMemo, SetStateAction, useRef } from 'react'
|
|
1
|
+
import { MotionConfig, useMotionValue } from 'framer-motion'
|
|
2
|
+
import React, { useMemo, useRef } from 'react'
|
|
4
3
|
import { isElement } from 'react-is'
|
|
5
4
|
import {
|
|
6
5
|
NavigationNode,
|
|
7
|
-
NavigationPath,
|
|
8
6
|
NavigationContextType,
|
|
9
7
|
NavigationContext,
|
|
10
|
-
|
|
8
|
+
UseNavigationSelection,
|
|
11
9
|
} from '../hooks/useNavigation'
|
|
12
10
|
|
|
13
11
|
export type NavigationProviderProps = {
|
|
@@ -16,51 +14,35 @@ export type NavigationProviderProps = {
|
|
|
16
14
|
closeAfterNavigate?: boolean
|
|
17
15
|
children?: React.ReactNode
|
|
18
16
|
animationDuration?: number
|
|
19
|
-
|
|
20
|
-
setSelected: (value: SetStateAction<NavigationPath>) => void
|
|
21
|
-
onChange?: NavigationSelect
|
|
22
|
-
onClose?: NavigationContextType['onClose']
|
|
17
|
+
selection: UseNavigationSelection
|
|
23
18
|
}
|
|
24
19
|
|
|
25
20
|
const nonNullable = <T,>(value: T): value is NonNullable<T> => value !== null && value !== undefined
|
|
26
21
|
|
|
27
|
-
export
|
|
22
|
+
export const NavigationProvider = React.memo<NavigationProviderProps>((props) => {
|
|
28
23
|
const {
|
|
29
24
|
items,
|
|
30
|
-
onChange,
|
|
31
25
|
hideRootOnNavigate = true,
|
|
32
26
|
closeAfterNavigate = false,
|
|
33
27
|
animationDuration = 0.275,
|
|
34
28
|
children,
|
|
35
|
-
|
|
36
|
-
selected,
|
|
37
|
-
setSelected,
|
|
29
|
+
selection,
|
|
38
30
|
} = props
|
|
39
31
|
|
|
40
|
-
const animating =
|
|
41
|
-
|
|
42
|
-
const select = useEventCallback((incomming: NavigationPath) => {
|
|
43
|
-
setSelected(incomming)
|
|
44
|
-
onChange?.(incomming)
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
const onClose: NavigationContextType['onClose'] = useEventCallback((e, href) => {
|
|
48
|
-
onCloseUnstable?.(e, href)
|
|
49
|
-
setTimeout(() => select([]), animationDuration * 1000)
|
|
50
|
-
})
|
|
32
|
+
const animating = useMotionValue(false)
|
|
33
|
+
const closing = useMotionValue(false)
|
|
51
34
|
|
|
52
35
|
const value = useMemo<NavigationContextType>(
|
|
53
36
|
() => ({
|
|
54
37
|
hideRootOnNavigate,
|
|
55
|
-
|
|
56
|
-
select,
|
|
38
|
+
selection,
|
|
57
39
|
animating,
|
|
40
|
+
closing,
|
|
58
41
|
items: items
|
|
59
42
|
.map((item, index) => (isElement(item) ? { id: item.key ?? index, component: item } : item))
|
|
60
43
|
.filter(nonNullable),
|
|
61
|
-
onClose,
|
|
62
44
|
}),
|
|
63
|
-
[hideRootOnNavigate,
|
|
45
|
+
[hideRootOnNavigate, selection, animating, closing, items],
|
|
64
46
|
)
|
|
65
47
|
|
|
66
48
|
return (
|
|
@@ -68,4 +50,4 @@ export function NavigationProvider(props: NavigationProviderProps) {
|
|
|
68
50
|
<NavigationContext.Provider value={value}>{children}</NavigationContext.Provider>
|
|
69
51
|
</MotionConfig>
|
|
70
52
|
)
|
|
71
|
-
}
|
|
53
|
+
})
|
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { MotionValue, useMotionValue } from 'framer-motion'
|
|
2
|
+
import { createContext, MutableRefObject, useContext } from 'react'
|
|
2
3
|
|
|
3
4
|
export type NavigationId = string | number
|
|
4
5
|
export type NavigationPath = NavigationId[]
|
|
5
|
-
export type NavigationSelect = (selected: NavigationPath) => void
|
|
6
6
|
export type NavigationRender = React.FC<
|
|
7
7
|
(NavigationNodeComponent | NavigationNodeHref) & { children?: React.ReactNode }
|
|
8
8
|
>
|
|
9
9
|
|
|
10
|
+
export type UseNavigationSelection = MotionValue<NavigationPath | false>
|
|
11
|
+
|
|
10
12
|
export type NavigationOnClose = (
|
|
11
13
|
event?: React.MouseEvent<HTMLAnchorElement>,
|
|
12
14
|
href?: string | undefined,
|
|
13
15
|
) => void
|
|
14
16
|
export type NavigationContextType = {
|
|
15
|
-
|
|
16
|
-
select: NavigationSelect
|
|
17
|
+
selection: UseNavigationSelection
|
|
17
18
|
items: NavigationNode[]
|
|
18
19
|
hideRootOnNavigate: boolean
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
animating: MotionValue<boolean>
|
|
21
|
+
closing: MotionValue<boolean>
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
type NavigationNodeBase = {
|
|
@@ -57,3 +58,29 @@ export const NavigationContext = createContext(undefined as unknown as Navigatio
|
|
|
57
58
|
export function useNavigation() {
|
|
58
59
|
return useContext(NavigationContext)
|
|
59
60
|
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* To prevent excessive rerenders we're not using plain React useState, but we're using a reactive
|
|
64
|
+
* motion value (could easily be any other reactive variable like Zustand, MobX, etc).
|
|
65
|
+
*
|
|
66
|
+
* Usage:
|
|
67
|
+
*
|
|
68
|
+
* ```tsx
|
|
69
|
+
* const selection = useNavigationSelection()
|
|
70
|
+
*
|
|
71
|
+
* function onClose() {
|
|
72
|
+
* selection.set(false)
|
|
73
|
+
* }
|
|
74
|
+
*
|
|
75
|
+
* function openRoot() {
|
|
76
|
+
* selection.set([])
|
|
77
|
+
* }
|
|
78
|
+
*
|
|
79
|
+
* function openPath() {
|
|
80
|
+
* selection.set(['my-path'])
|
|
81
|
+
* }
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export function useNavigationSelection(): UseNavigationSelection {
|
|
85
|
+
return useMotionValue<NavigationPath | false>(false)
|
|
86
|
+
}
|
|
@@ -6,7 +6,7 @@ import { OverlayBase, LayoutOverlayBaseProps } from './OverlayBase'
|
|
|
6
6
|
|
|
7
7
|
export type OverlayProps = Omit<
|
|
8
8
|
SetOptional<LayoutOverlayBaseProps, 'variantSm' | 'variantMd'>,
|
|
9
|
-
'direction' | 'offsetPageY' | 'isPresent'
|
|
9
|
+
'direction' | 'offsetPageY' | 'isPresent'
|
|
10
10
|
>
|
|
11
11
|
|
|
12
12
|
export function Overlay(props: OverlayProps) {
|
|
@@ -50,7 +50,6 @@ export function Overlay(props: OverlayProps) {
|
|
|
50
50
|
direction={1}
|
|
51
51
|
active={active}
|
|
52
52
|
isPresent={active}
|
|
53
|
-
safeToRemove={undefined}
|
|
54
53
|
{...otherProps}
|
|
55
54
|
>
|
|
56
55
|
{children}
|
|
@@ -39,7 +39,7 @@ export type LayoutOverlayBaseProps = {
|
|
|
39
39
|
onClosed: () => void
|
|
40
40
|
offsetPageY: number
|
|
41
41
|
isPresent: boolean
|
|
42
|
-
safeToRemove
|
|
42
|
+
safeToRemove?: (() => void) | null | undefined
|
|
43
43
|
overlayPaneProps?: MotionProps
|
|
44
44
|
} & StyleProps &
|
|
45
45
|
OverridableProps
|
|
@@ -115,14 +115,7 @@ export function OverlayBase(incommingProps: LayoutOverlayBaseProps) {
|
|
|
115
115
|
useIsomorphicLayoutEffect(() => {
|
|
116
116
|
const scroller = scrollerRef.current
|
|
117
117
|
|
|
118
|
-
if (!scroller) return undefined
|
|
119
|
-
|
|
120
|
-
if (!isPresent && position.get() === OverlayPosition.UNOPENED) {
|
|
121
|
-
scroller.scrollLeft = positions.closed.x.get()
|
|
122
|
-
scroller.scrollTop = positions.closed.y.get()
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (!isPresent) return undefined
|
|
118
|
+
if (!scroller || !isPresent) return undefined
|
|
126
119
|
|
|
127
120
|
const open = { x: positions.open.x.get(), y: positions.open.y.get() }
|
|
128
121
|
|
|
@@ -170,16 +163,23 @@ export function OverlayBase(incommingProps: LayoutOverlayBaseProps) {
|
|
|
170
163
|
|
|
171
164
|
// When the overlay is closed by navigating away, we're closing the overlay.
|
|
172
165
|
useEffect(() => {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
clearScrollLock()
|
|
166
|
+
const scroller = scrollerRef.current
|
|
167
|
+
if (isPresent || !scroller) return
|
|
176
168
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
169
|
+
if (position.get() === OverlayPosition.UNOPENED) {
|
|
170
|
+
position.set(OverlayPosition.CLOSED)
|
|
171
|
+
clearScrollLock()
|
|
172
|
+
scroller.scrollLeft = positions.closed.x.get()
|
|
173
|
+
scroller.scrollTop = positions.closed.y.get()
|
|
174
|
+
safeToRemove?.()
|
|
175
|
+
} else {
|
|
176
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
177
|
+
scrollTo({
|
|
178
|
+
x: positions.closed.x.get(),
|
|
179
|
+
y: positions.closed.y.get(),
|
|
180
|
+
}).then(() => safeToRemove?.())
|
|
181
|
+
}
|
|
182
|
+
}, [isPresent, position, positions, safeToRemove, scrollTo, scrollerRef])
|
|
183
183
|
|
|
184
184
|
// Only go back to a previous page if the overlay isn't closed.
|
|
185
185
|
const closeOverlay = useCallback(() => {
|
|
@@ -384,7 +384,7 @@ export function OverlayBase(incommingProps: LayoutOverlayBaseProps) {
|
|
|
384
384
|
[theme.breakpoints.down('md')]: {
|
|
385
385
|
minWidth: '80vw',
|
|
386
386
|
'&:not(.sizeMdFull)': {
|
|
387
|
-
width: '
|
|
387
|
+
width: 'auto',
|
|
388
388
|
},
|
|
389
389
|
|
|
390
390
|
'&.variantSmBottom.sizeSmFull': {
|
|
@@ -26,6 +26,7 @@ export function useOverlayPosition() {
|
|
|
26
26
|
|
|
27
27
|
const measure = () => {
|
|
28
28
|
const positions = getScrollSnapPositions()
|
|
29
|
+
|
|
29
30
|
state.open.x.set(positions.x[1] ?? 0)
|
|
30
31
|
state.closed.x.set(positions.x[0])
|
|
31
32
|
state.open.y.set(positions.y[1] ?? 0)
|
|
@@ -50,10 +51,14 @@ export function useOverlayPosition() {
|
|
|
50
51
|
|
|
51
52
|
const xC = state.closed.x.get()
|
|
52
53
|
const xO = state.open.x.get()
|
|
54
|
+
|
|
53
55
|
const visX = xO === xC ? 1 : Math.max(0, Math.min(1, (x - xC) / (xO - xC)))
|
|
54
56
|
|
|
57
|
+
let vis = visY * visX
|
|
58
|
+
if (xC === 0 && xO === 0 && yC === 0 && yO === 0) vis = 0
|
|
59
|
+
|
|
55
60
|
// todo: visibility sometimes flickers
|
|
56
|
-
state.open.visible.set(
|
|
61
|
+
state.open.visible.set(vis)
|
|
57
62
|
}
|
|
58
63
|
|
|
59
64
|
const cancelY = scroll.y.onChange(calc)
|
|
@@ -3,6 +3,7 @@ import { m, useTransform } from 'framer-motion'
|
|
|
3
3
|
import React from 'react'
|
|
4
4
|
import { useScrollY } from '../../Layout/hooks/useScrollY'
|
|
5
5
|
import { extendableComponent } from '../../Styles'
|
|
6
|
+
import { breakpointVal } from '../../Styles/breakpointVal'
|
|
6
7
|
import { responsiveVal } from '../../Styles/responsiveVal'
|
|
7
8
|
import { Row } from '../Row'
|
|
8
9
|
|
|
@@ -84,9 +85,11 @@ export function HeroBanner(props: HeroBannerProps) {
|
|
|
84
85
|
width: '100%',
|
|
85
86
|
height: '100%',
|
|
86
87
|
[theme.breakpoints.down('md')]: {
|
|
87
|
-
|
|
88
|
+
...breakpointVal(
|
|
89
|
+
'borderRadius',
|
|
88
90
|
theme.shape.borderRadius * 2,
|
|
89
91
|
theme.shape.borderRadius * 3,
|
|
92
|
+
theme.breakpoints.values,
|
|
90
93
|
),
|
|
91
94
|
},
|
|
92
95
|
},
|
|
@@ -99,9 +102,11 @@ export function HeroBanner(props: HeroBannerProps) {
|
|
|
99
102
|
style={{ width: !matches ? width : 0, borderRadius }}
|
|
100
103
|
className={classes.animated}
|
|
101
104
|
sx={(theme) => ({
|
|
102
|
-
|
|
105
|
+
...breakpointVal(
|
|
106
|
+
'borderRadius',
|
|
103
107
|
theme.shape.borderRadius * 2,
|
|
104
108
|
theme.shape.borderRadius * 3,
|
|
109
|
+
theme.breakpoints.values,
|
|
105
110
|
),
|
|
106
111
|
overflow: 'hidden',
|
|
107
112
|
transform: 'translateZ(0)',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Box, SxProps, Theme } from '@mui/material'
|
|
2
2
|
import React from 'react'
|
|
3
3
|
import { extendableComponent } from '../../Styles'
|
|
4
|
-
import {
|
|
4
|
+
import { breakpointVal } from '../../Styles/breakpointVal'
|
|
5
5
|
import { Row } from '../Row'
|
|
6
6
|
|
|
7
7
|
export type ImageTextProps = {
|
|
@@ -37,7 +37,12 @@ export function ImageText(props: ImageTextProps) {
|
|
|
37
37
|
background: 'none',
|
|
38
38
|
gridTemplateColumns: '1fr 1fr',
|
|
39
39
|
},
|
|
40
|
-
|
|
40
|
+
...breakpointVal(
|
|
41
|
+
'borderRadius',
|
|
42
|
+
theme.shape.borderRadius * 2,
|
|
43
|
+
theme.shape.borderRadius * 3,
|
|
44
|
+
theme.breakpoints.values,
|
|
45
|
+
),
|
|
41
46
|
})}
|
|
42
47
|
>
|
|
43
48
|
<Box
|
|
@@ -49,9 +54,11 @@ export function ImageText(props: ImageTextProps) {
|
|
|
49
54
|
height: '100%',
|
|
50
55
|
width: '100%',
|
|
51
56
|
objectFit: 'cover',
|
|
52
|
-
|
|
57
|
+
...breakpointVal(
|
|
58
|
+
'borderRadius',
|
|
53
59
|
theme.shape.borderRadius * 2,
|
|
54
60
|
theme.shape.borderRadius * 3,
|
|
61
|
+
theme.breakpoints.values,
|
|
55
62
|
),
|
|
56
63
|
},
|
|
57
64
|
})}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Box, SxProps, Theme } from '@mui/material'
|
|
2
2
|
import React from 'react'
|
|
3
3
|
import { extendableComponent } from '../../Styles'
|
|
4
|
+
import { breakpointVal } from '../../Styles/breakpointVal'
|
|
4
5
|
import { responsiveVal } from '../../Styles/responsiveVal'
|
|
5
6
|
import { Row } from '../Row'
|
|
6
7
|
|
|
@@ -32,7 +33,12 @@ export function ImageTextBoxed(props: ImageTextBoxedProps) {
|
|
|
32
33
|
gridTemplateColumns: '1fr auto',
|
|
33
34
|
columnGap: `${theme.spacings.lg}`,
|
|
34
35
|
},
|
|
35
|
-
|
|
36
|
+
...breakpointVal(
|
|
37
|
+
'borderRadius',
|
|
38
|
+
theme.shape.borderRadius * 2,
|
|
39
|
+
theme.shape.borderRadius * 3,
|
|
40
|
+
theme.breakpoints.values,
|
|
41
|
+
),
|
|
36
42
|
overflow: 'hidden',
|
|
37
43
|
})}
|
|
38
44
|
>
|