@graphcommerce/next-ui 4.11.2 → 4.13.1
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 +1 -1
- package/CHANGELOG.md +40 -0
- package/FramerScroller/SidebarGallery.tsx +12 -1
- package/Layout/components/LayoutHeader.tsx +1 -1
- package/Layout/components/LayoutHeaderContent.tsx +9 -4
- package/LayoutDefault/components/LayoutDefault.tsx +5 -3
- package/LayoutOverlay/components/LayoutOverlay.tsx +25 -6
- package/LayoutParts/DesktopNavBar.tsx +1 -1
- package/LayoutParts/DesktopNavBarItem.tsx +38 -4
- package/LayoutParts/MenuFabSecondaryItem.tsx +2 -1
- package/Navigation/components/NavigationFab.tsx +106 -0
- package/Navigation/components/NavigationItem.tsx +147 -0
- package/Navigation/components/NavigationList.tsx +50 -0
- package/Navigation/components/NavigationOverlay.tsx +237 -0
- package/Navigation/components/NavigationProvider.tsx +66 -0
- package/Navigation/hooks/useNavigation.ts +58 -0
- package/Navigation/index.ts +4 -0
- package/Overlay/components/Overlay.tsx +61 -0
- package/{LayoutOverlay/components/LayoutOverlayBase.tsx → Overlay/components/OverlayBase.tsx} +29 -15
- package/Overlay/components/index.ts +2 -0
- package/{LayoutOverlay → Overlay}/hooks/useOverlayPosition.ts +0 -0
- package/Overlay/index.ts +1 -0
- package/PageMeta/PageMeta.tsx +4 -6
- package/Theme/DarkLightModeThemeProvider.tsx +1 -5
- package/index.ts +5 -3
- package/package.json +10 -10
|
@@ -82,7 +82,7 @@ export function ActionCard(props: ActionCardProps) {
|
|
|
82
82
|
gridTemplateAreas: `
|
|
83
83
|
"image title action"
|
|
84
84
|
"image details ${price ? 'price' : 'details'}"
|
|
85
|
-
"image
|
|
85
|
+
"image secondaryAction additionalDetails"
|
|
86
86
|
"after after after"
|
|
87
87
|
`,
|
|
88
88
|
justifyContent: 'unset',
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## 4.13.1
|
|
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) [`c5c539c44`](https://github.com/graphcommerce-org/graphcommerce/commit/c5c539c44eeac524cd62ce649e132d2e00333794) Thanks [@paales](https://github.com/paales)! - Make sure the gallery doesn't scroll when overlays are opened
|
|
10
|
+
|
|
11
|
+
- [#1552](https://github.com/graphcommerce-org/graphcommerce/pull/1552) [`6f69bc54c`](https://github.com/graphcommerce-org/graphcommerce/commit/6f69bc54c6e0224452817c532ae58d9c332b61ea) Thanks [@paales](https://github.com/paales)! - Prevent back button scrolling when navigating between overlays
|
|
12
|
+
|
|
13
|
+
* [#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
|
|
14
|
+
|
|
15
|
+
* Updated dependencies [[`18054c441`](https://github.com/graphcommerce-org/graphcommerce/commit/18054c441962ba750bed3acc39ab46c8d3a341ce), [`21886d6fa`](https://github.com/graphcommerce-org/graphcommerce/commit/21886d6fa64a48d9e932bfaf8d138c9b13c36e43), [`b4936e961`](https://github.com/graphcommerce-org/graphcommerce/commit/b4936e96175fe80717895822e245274db05638bd)]:
|
|
16
|
+
- @graphcommerce/framer-next-pages@3.2.4
|
|
17
|
+
- @graphcommerce/framer-scroller@2.1.23
|
|
18
|
+
|
|
19
|
+
## 4.13.0
|
|
20
|
+
|
|
21
|
+
### Minor Changes
|
|
22
|
+
|
|
23
|
+
- [#1522](https://github.com/graphcommerce-org/graphcommerce/pull/1522) [`8d8fda262`](https://github.com/graphcommerce-org/graphcommerce/commit/8d8fda2623e561cb43441110c67ffa34b692668a) Thanks [@ErwinOtten](https://github.com/ErwinOtten)! - Introducing a new Navigation component that builds on the existing navigation component and tries to address the 'mega menu' question where there are tons of categories that need to be navigated quickly.
|
|
24
|
+
|
|
25
|
+
* [#1522](https://github.com/graphcommerce-org/graphcommerce/pull/1522) [`cefa7b365`](https://github.com/graphcommerce-org/graphcommerce/commit/cefa7b3652b55108d2178927e3c5d98a111cf373) Thanks [@ErwinOtten](https://github.com/ErwinOtten)! - Introducting a new Overlay component with is the generic part of LayoutOverlay into OverlayBase. The new Overlay is used to render the new Navigation component.
|
|
26
|
+
|
|
27
|
+
### Patch Changes
|
|
28
|
+
|
|
29
|
+
- Updated dependencies [[`584b683a2`](https://github.com/graphcommerce-org/graphcommerce/commit/584b683a2aedcdf5067644c8dcc0e63a5b9e894c)]:
|
|
30
|
+
- @graphcommerce/framer-scroller@2.1.22
|
|
31
|
+
|
|
32
|
+
## 4.12.0
|
|
33
|
+
|
|
34
|
+
### Minor Changes
|
|
35
|
+
|
|
36
|
+
- [#1534](https://github.com/graphcommerce-org/graphcommerce/pull/1534) [`c756f42e5`](https://github.com/graphcommerce-org/graphcommerce/commit/c756f42e503761a497e4a5a7a02d02141df231c3) Thanks [@mikekeehnen](https://github.com/mikekeehnen)! - Added the method title to the action card title for shipping methods.
|
|
37
|
+
|
|
38
|
+
### Patch Changes
|
|
39
|
+
|
|
40
|
+
- Updated dependencies []:
|
|
41
|
+
- @graphcommerce/framer-scroller@2.1.21
|
|
42
|
+
|
|
3
43
|
## 4.11.2
|
|
4
44
|
|
|
5
45
|
### Patch Changes
|
|
@@ -119,6 +119,7 @@ export function SidebarGallery(props: SidebarGalleryProps) {
|
|
|
119
119
|
<Row maxWidth={false} disableGutters className={classes.row} sx={sx}>
|
|
120
120
|
<MotionBox
|
|
121
121
|
layout
|
|
122
|
+
layoutDependency={zoomed}
|
|
122
123
|
className={classes.root}
|
|
123
124
|
sx={[
|
|
124
125
|
{
|
|
@@ -146,6 +147,7 @@ export function SidebarGallery(props: SidebarGalleryProps) {
|
|
|
146
147
|
>
|
|
147
148
|
<MotionBox
|
|
148
149
|
layout
|
|
150
|
+
layoutDependency={zoomed}
|
|
149
151
|
className={classes.scrollerContainer}
|
|
150
152
|
sx={[
|
|
151
153
|
{
|
|
@@ -193,6 +195,7 @@ export function SidebarGallery(props: SidebarGalleryProps) {
|
|
|
193
195
|
<MotionImageAspect
|
|
194
196
|
key={typeof image.src === 'string' ? image.src : idx}
|
|
195
197
|
layout
|
|
198
|
+
layoutDependency={zoomed}
|
|
196
199
|
src={image.src}
|
|
197
200
|
width={image.width}
|
|
198
201
|
height={image.height}
|
|
@@ -209,6 +212,7 @@ export function SidebarGallery(props: SidebarGalleryProps) {
|
|
|
209
212
|
</Scroller>
|
|
210
213
|
<MotionBox
|
|
211
214
|
layout
|
|
215
|
+
layoutDependency={zoomed}
|
|
212
216
|
className={classes.topRight}
|
|
213
217
|
sx={{
|
|
214
218
|
display: 'grid',
|
|
@@ -243,6 +247,7 @@ export function SidebarGallery(props: SidebarGalleryProps) {
|
|
|
243
247
|
>
|
|
244
248
|
<ScrollerButton
|
|
245
249
|
layout
|
|
250
|
+
layoutDependency={zoomed}
|
|
246
251
|
direction='left'
|
|
247
252
|
size='small'
|
|
248
253
|
className={classes.sliderButtons}
|
|
@@ -262,6 +267,7 @@ export function SidebarGallery(props: SidebarGalleryProps) {
|
|
|
262
267
|
>
|
|
263
268
|
<ScrollerButton
|
|
264
269
|
layout
|
|
270
|
+
layoutDependency={zoomed}
|
|
265
271
|
direction='right'
|
|
266
272
|
size='small'
|
|
267
273
|
className={classes.sliderButtons}
|
|
@@ -286,7 +292,11 @@ export function SidebarGallery(props: SidebarGalleryProps) {
|
|
|
286
292
|
},
|
|
287
293
|
}}
|
|
288
294
|
>
|
|
289
|
-
<ScrollerDots
|
|
295
|
+
<ScrollerDots
|
|
296
|
+
layout
|
|
297
|
+
layoutDependency={zoomed}
|
|
298
|
+
sx={{ backgroundColor: 'background.paper', boxShadow: 6 }}
|
|
299
|
+
/>
|
|
290
300
|
</Box>
|
|
291
301
|
</MotionBox>
|
|
292
302
|
|
|
@@ -319,6 +329,7 @@ export function SidebarGallery(props: SidebarGalleryProps) {
|
|
|
319
329
|
>
|
|
320
330
|
<MotionBox
|
|
321
331
|
layout
|
|
332
|
+
layoutDependency={zoomed}
|
|
322
333
|
className={classes.sidebar}
|
|
323
334
|
sx={{
|
|
324
335
|
boxSizing: 'border-box',
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { useMotionValueValue } from '@graphcommerce/framer-utils'
|
|
2
|
-
import { Box, SxProps, Theme } from '@mui/material'
|
|
2
|
+
import { Box, styled, SxProps, Theme } from '@mui/material'
|
|
3
|
+
import { LayoutProps, m } from 'framer-motion'
|
|
3
4
|
import React, { useRef } from 'react'
|
|
4
5
|
import { extendableComponent } from '../../Styles'
|
|
5
6
|
import { useScrollY } from '../hooks/useScrollY'
|
|
6
7
|
import { FloatingProps } from './LayoutHeadertypes'
|
|
7
8
|
|
|
9
|
+
const MotionDiv = styled(m.div)({})
|
|
10
|
+
|
|
8
11
|
export type LayoutHeaderContentProps = FloatingProps & {
|
|
9
12
|
children?: React.ReactNode
|
|
10
13
|
left?: React.ReactNode
|
|
@@ -13,6 +16,7 @@ export type LayoutHeaderContentProps = FloatingProps & {
|
|
|
13
16
|
switchPoint?: number
|
|
14
17
|
sx?: SxProps<Theme>
|
|
15
18
|
sxBg?: SxProps<Theme>
|
|
19
|
+
layout?: LayoutProps['layout']
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
type OwnerState = { floatingSm: boolean; floatingMd: boolean; scrolled: boolean; divider: boolean }
|
|
@@ -33,6 +37,7 @@ export function LayoutHeaderContent(props: LayoutHeaderContentProps) {
|
|
|
33
37
|
switchPoint = 50,
|
|
34
38
|
sx = [],
|
|
35
39
|
sxBg = [],
|
|
40
|
+
layout,
|
|
36
41
|
} = props
|
|
37
42
|
|
|
38
43
|
const scroll = useScrollY()
|
|
@@ -139,7 +144,7 @@ export function LayoutHeaderContent(props: LayoutHeaderContentProps) {
|
|
|
139
144
|
justifyContent: 'start',
|
|
140
145
|
})}
|
|
141
146
|
>
|
|
142
|
-
{left}
|
|
147
|
+
<MotionDiv layout={layout}>{left}</MotionDiv>
|
|
143
148
|
</Box>
|
|
144
149
|
)}
|
|
145
150
|
<Box
|
|
@@ -172,7 +177,7 @@ export function LayoutHeaderContent(props: LayoutHeaderContentProps) {
|
|
|
172
177
|
},
|
|
173
178
|
})}
|
|
174
179
|
>
|
|
175
|
-
{children}
|
|
180
|
+
<MotionDiv layout={layout}>{children}</MotionDiv>
|
|
176
181
|
</Box>
|
|
177
182
|
<Box
|
|
178
183
|
className={classes.right}
|
|
@@ -188,7 +193,7 @@ export function LayoutHeaderContent(props: LayoutHeaderContentProps) {
|
|
|
188
193
|
justifyContent: 'end',
|
|
189
194
|
})}
|
|
190
195
|
>
|
|
191
|
-
{right}
|
|
196
|
+
<MotionDiv layout={layout}>{right}</MotionDiv>
|
|
192
197
|
</Box>
|
|
193
198
|
{divider && (
|
|
194
199
|
<Box
|
|
@@ -39,8 +39,10 @@ export function LayoutDefault(props: LayoutDefaultProps) {
|
|
|
39
39
|
sx = [],
|
|
40
40
|
} = props
|
|
41
41
|
|
|
42
|
-
const
|
|
43
|
-
|
|
42
|
+
const scrollWithOffset = useTransform(
|
|
43
|
+
[useViewportScroll().scrollY, useScrollOffset()],
|
|
44
|
+
([y, offset]: number[]) => y + offset,
|
|
45
|
+
)
|
|
44
46
|
|
|
45
47
|
const classes = withState({ noSticky })
|
|
46
48
|
|
|
@@ -104,7 +106,7 @@ export function LayoutDefault(props: LayoutDefaultProps) {
|
|
|
104
106
|
justifyContent: 'space-between',
|
|
105
107
|
width: '100%',
|
|
106
108
|
height: 0,
|
|
107
|
-
zIndex: '
|
|
109
|
+
zIndex: 'speedDial',
|
|
108
110
|
[theme.breakpoints.up('sm')]: {
|
|
109
111
|
padding: `0 ${theme.page.horizontal}`,
|
|
110
112
|
position: 'sticky',
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
import { usePageContext, useGo, useScrollOffset } from '@graphcommerce/framer-next-pages'
|
|
1
2
|
import { ScrollerProvider, ScrollSnapType } from '@graphcommerce/framer-scroller'
|
|
3
|
+
import { useMotionValueValue } from '@graphcommerce/framer-utils'
|
|
4
|
+
import { usePresence } from 'framer-motion'
|
|
2
5
|
import type { SetOptional } from 'type-fest'
|
|
3
|
-
import {
|
|
6
|
+
import { OverlayBase, LayoutOverlayBaseProps } from '../../Overlay/components/OverlayBase'
|
|
4
7
|
|
|
5
|
-
export type
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
export type LayoutOverlayProps = Omit<
|
|
9
|
+
SetOptional<LayoutOverlayBaseProps, 'variantSm' | 'variantMd'>,
|
|
10
|
+
'active' | 'direction' | 'onClosed' | 'offsetPageY' | 'isPresent' | 'safeToRemove'
|
|
11
|
+
>
|
|
8
12
|
|
|
9
13
|
export function LayoutOverlay(props: LayoutOverlayProps) {
|
|
10
14
|
const { children, variantSm = 'bottom', variantMd = 'right', ...otherProps } = props
|
|
@@ -14,11 +18,26 @@ export function LayoutOverlay(props: LayoutOverlayProps) {
|
|
|
14
18
|
const scrollSnapTypeMd: ScrollSnapType =
|
|
15
19
|
variantMd === 'left' || variantMd === 'right' ? 'inline mandatory' : 'block proximity'
|
|
16
20
|
|
|
21
|
+
const { closeSteps, active, direction } = usePageContext()
|
|
22
|
+
const onCloseHandler = useGo(closeSteps * -1)
|
|
23
|
+
const offsetPageY = useMotionValueValue(useScrollOffset(), (v) => v)
|
|
24
|
+
const [isPresent, safeToRemove] = usePresence()
|
|
25
|
+
|
|
17
26
|
return (
|
|
18
27
|
<ScrollerProvider scrollSnapTypeSm={scrollSnapTypeSm} scrollSnapTypeMd={scrollSnapTypeMd}>
|
|
19
|
-
<
|
|
28
|
+
<OverlayBase
|
|
29
|
+
active={active}
|
|
30
|
+
direction={direction}
|
|
31
|
+
onClosed={onCloseHandler}
|
|
32
|
+
offsetPageY={offsetPageY}
|
|
33
|
+
variantMd={variantMd}
|
|
34
|
+
variantSm={variantSm}
|
|
35
|
+
isPresent={isPresent}
|
|
36
|
+
safeToRemove={safeToRemove}
|
|
37
|
+
{...otherProps}
|
|
38
|
+
>
|
|
20
39
|
{children}
|
|
21
|
-
</
|
|
40
|
+
</OverlayBase>
|
|
22
41
|
</ScrollerProvider>
|
|
23
42
|
)
|
|
24
43
|
}
|
|
@@ -43,7 +43,7 @@ export function DesktopNavBar(props: MenuTabsProps) {
|
|
|
43
43
|
sx={(theme) => ({
|
|
44
44
|
gridArea: `1 / 1 / 1 / 4`,
|
|
45
45
|
columnGap: theme.spacings.md,
|
|
46
|
-
padding: `0 ${theme.spacings.
|
|
46
|
+
padding: `0 ${theme.spacings.md}`,
|
|
47
47
|
gridAutoColumns: 'min-content',
|
|
48
48
|
})}
|
|
49
49
|
className={classes.scroller}
|
|
@@ -5,11 +5,45 @@ import { extendableComponent } from '../Styles/extendableComponent'
|
|
|
5
5
|
|
|
6
6
|
const { classes, selectors } = extendableComponent('DesktopNavItem', ['root', 'line'] as const)
|
|
7
7
|
|
|
8
|
-
export type
|
|
8
|
+
export type DesktopNavItemLinkProps = LinkProps<'a'> & Pick<PageLinkProps, 'href'>
|
|
9
|
+
export type DesktopNavItemButtonProps = LinkProps<'div'> & {
|
|
10
|
+
onClick: LinkProps<'button'>['onClick']
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function isLinkProps(
|
|
14
|
+
props: DesktopNavItemLinkProps | DesktopNavItemButtonProps,
|
|
15
|
+
): props is DesktopNavItemLinkProps {
|
|
16
|
+
return 'href' in props
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function DesktopNavItem(props: DesktopNavItemLinkProps | DesktopNavItemButtonProps) {
|
|
20
|
+
const router = useRouter()
|
|
21
|
+
|
|
22
|
+
if (!isLinkProps(props)) {
|
|
23
|
+
const { onClick, children, sx = [], ...linkProps } = props
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<Link
|
|
27
|
+
className={classes.root}
|
|
28
|
+
component='div'
|
|
29
|
+
variant='h6'
|
|
30
|
+
color='text.primary'
|
|
31
|
+
underline='none'
|
|
32
|
+
{...linkProps}
|
|
33
|
+
onClick={onClick}
|
|
34
|
+
sx={[
|
|
35
|
+
{ whiteSpace: 'nowrap', paddingTop: '6px', cursor: 'pointer' },
|
|
36
|
+
...(Array.isArray(sx) ? sx : [sx]),
|
|
37
|
+
]}
|
|
38
|
+
>
|
|
39
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>{children}</Box>
|
|
40
|
+
</Link>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
9
43
|
|
|
10
|
-
export function DesktopNavItem(props: DesktopNavItemProps) {
|
|
11
44
|
const { href, children, sx = [], ...linkProps } = props
|
|
12
|
-
|
|
45
|
+
|
|
46
|
+
const active = router.asPath.startsWith(href.toString())
|
|
13
47
|
|
|
14
48
|
return (
|
|
15
49
|
<PageLink href={href} passHref>
|
|
@@ -21,7 +55,7 @@ export function DesktopNavItem(props: DesktopNavItemProps) {
|
|
|
21
55
|
{...linkProps}
|
|
22
56
|
sx={[{ whiteSpace: 'nowrap', paddingTop: '6px' }, ...(Array.isArray(sx) ? sx : [sx])]}
|
|
23
57
|
>
|
|
24
|
-
{children}
|
|
58
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>{children}</Box>
|
|
25
59
|
<Box
|
|
26
60
|
component='span'
|
|
27
61
|
className={classes.line}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ListItemButton, ListItemIcon, ListItemText, SxProps, Theme } from '@mui/material'
|
|
2
2
|
import PageLink from 'next/link'
|
|
3
|
-
import
|
|
3
|
+
import { useRouter } from 'next/router'
|
|
4
4
|
import React from 'react'
|
|
5
5
|
import { extendableComponent } from '../Styles'
|
|
6
6
|
|
|
@@ -17,6 +17,7 @@ const { classes } = extendableComponent(compName, parts)
|
|
|
17
17
|
|
|
18
18
|
export function MenuFabSecondaryItem(props: FabMenuSecondaryItemProps) {
|
|
19
19
|
const { href, children, icon, sx = [] } = props
|
|
20
|
+
const router = useRouter()
|
|
20
21
|
|
|
21
22
|
return (
|
|
22
23
|
<PageLink href={href} passHref>
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { useMotionValueValue } from '@graphcommerce/framer-utils'
|
|
2
|
+
import { Fab, styled, Box, SxProps, Theme, FabProps } from '@mui/material'
|
|
3
|
+
import { m } from 'framer-motion'
|
|
4
|
+
import { useRouter } from 'next/router'
|
|
5
|
+
import React, { useEffect } from 'react'
|
|
6
|
+
import { IconSvg } from '../../IconSvg'
|
|
7
|
+
import { useScrollY } from '../../Layout/hooks/useScrollY'
|
|
8
|
+
import { useFabAnimation } from '../../LayoutParts/useFabAnimation'
|
|
9
|
+
import { extendableComponent } from '../../Styles/extendableComponent'
|
|
10
|
+
import { useFabSize } from '../../Theme'
|
|
11
|
+
import { iconMenu, iconClose } from '../../icons'
|
|
12
|
+
|
|
13
|
+
const MotionDiv = styled(m.div)({})
|
|
14
|
+
|
|
15
|
+
export type NavigationFabProps = {
|
|
16
|
+
menuIcon?: React.ReactNode
|
|
17
|
+
closeIcon?: React.ReactNode
|
|
18
|
+
sx?: SxProps<Theme>
|
|
19
|
+
} & Pick<FabProps, 'color' | 'size' | 'variant' | 'onClick'>
|
|
20
|
+
|
|
21
|
+
const name = 'MenuFab'
|
|
22
|
+
const parts = ['wrapper', 'fab', 'shadow', 'menu'] as const
|
|
23
|
+
type OwnerState = {
|
|
24
|
+
scrolled: boolean
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const { withState } = extendableComponent<OwnerState, typeof name, typeof parts>(name, parts)
|
|
28
|
+
|
|
29
|
+
export function NavigationFab(props: NavigationFabProps) {
|
|
30
|
+
const { menuIcon, closeIcon, sx = [], ...fabProps } = props
|
|
31
|
+
const router = useRouter()
|
|
32
|
+
const [openEl, setOpenEl] = React.useState<null | HTMLElement>(null)
|
|
33
|
+
|
|
34
|
+
const { opacity, shadowOpacity } = useFabAnimation()
|
|
35
|
+
const scrollY = useScrollY()
|
|
36
|
+
const scrolled = useMotionValueValue(scrollY, (y) => y > 10)
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
const clear = () => setOpenEl(null)
|
|
40
|
+
router.events.on('routeChangeStart', clear)
|
|
41
|
+
return () => router.events.off('routeChangeStart', clear)
|
|
42
|
+
}, [router.events])
|
|
43
|
+
|
|
44
|
+
const fabIconSize = useFabSize('responsive')
|
|
45
|
+
|
|
46
|
+
const classes = withState({ scrolled })
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<Box
|
|
50
|
+
sx={[
|
|
51
|
+
{ position: 'relative', width: fabIconSize, height: fabIconSize },
|
|
52
|
+
...(Array.isArray(sx) ? sx : [sx]),
|
|
53
|
+
]}
|
|
54
|
+
>
|
|
55
|
+
<MotionDiv
|
|
56
|
+
className={classes.wrapper}
|
|
57
|
+
sx={(theme) => ({
|
|
58
|
+
[theme.breakpoints.down('md')]: {
|
|
59
|
+
opacity: '1 !important',
|
|
60
|
+
transform: 'none !important',
|
|
61
|
+
},
|
|
62
|
+
})}
|
|
63
|
+
style={{ opacity }}
|
|
64
|
+
>
|
|
65
|
+
<Fab
|
|
66
|
+
color='inherit'
|
|
67
|
+
aria-label='Open Menu'
|
|
68
|
+
size='responsive'
|
|
69
|
+
sx={(theme) => ({
|
|
70
|
+
boxShadow: 'none',
|
|
71
|
+
'&:hover, &:focus': {
|
|
72
|
+
boxShadow: 'none',
|
|
73
|
+
background: theme.palette.text.primary,
|
|
74
|
+
},
|
|
75
|
+
background: theme.palette.text.primary,
|
|
76
|
+
pointerEvents: 'all',
|
|
77
|
+
color: theme.palette.background.paper,
|
|
78
|
+
})}
|
|
79
|
+
className={classes.fab}
|
|
80
|
+
{...fabProps}
|
|
81
|
+
>
|
|
82
|
+
{closeIcon ?? (
|
|
83
|
+
<IconSvg src={iconClose} size='large' sx={{ display: openEl ? 'block' : 'none' }} />
|
|
84
|
+
)}
|
|
85
|
+
{menuIcon ?? (
|
|
86
|
+
<IconSvg src={iconMenu} size='large' sx={{ display: openEl ? 'none' : 'block' }} />
|
|
87
|
+
)}
|
|
88
|
+
</Fab>
|
|
89
|
+
<MotionDiv
|
|
90
|
+
sx={(theme) => ({
|
|
91
|
+
pointerEvents: 'none',
|
|
92
|
+
borderRadius: '99em',
|
|
93
|
+
position: 'absolute',
|
|
94
|
+
height: '100%',
|
|
95
|
+
width: '100%',
|
|
96
|
+
boxShadow: theme.shadows[6],
|
|
97
|
+
top: 0,
|
|
98
|
+
[theme.breakpoints.down('md')]: { opacity: '1 !important' },
|
|
99
|
+
})}
|
|
100
|
+
className={classes.shadow}
|
|
101
|
+
style={{ opacity: shadowOpacity }}
|
|
102
|
+
/>
|
|
103
|
+
</MotionDiv>
|
|
104
|
+
</Box>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-use-before-define */
|
|
2
|
+
import { Box, ListItemButton, styled, useEventCallback } from '@mui/material'
|
|
3
|
+
import PageLink from 'next/link'
|
|
4
|
+
import { IconSvg } from '../../IconSvg'
|
|
5
|
+
import { extendableComponent } from '../../Styles/extendableComponent'
|
|
6
|
+
import { iconChevronRight } from '../../icons'
|
|
7
|
+
import {
|
|
8
|
+
isNavigationButton,
|
|
9
|
+
isNavigationComponent,
|
|
10
|
+
isNavigationHref,
|
|
11
|
+
NavigationNode,
|
|
12
|
+
NavigationPath,
|
|
13
|
+
useNavigation,
|
|
14
|
+
} from '../hooks/useNavigation'
|
|
15
|
+
import type { NavigationList } from './NavigationList'
|
|
16
|
+
|
|
17
|
+
type OwnerState = {
|
|
18
|
+
first?: boolean
|
|
19
|
+
last?: boolean
|
|
20
|
+
// It is actually used.
|
|
21
|
+
// eslint-disable-next-line react/no-unused-prop-types
|
|
22
|
+
column: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type NavigationItemProps = NavigationNode & {
|
|
26
|
+
parentPath: NavigationPath
|
|
27
|
+
idx: number
|
|
28
|
+
NavigationList: typeof NavigationList
|
|
29
|
+
} & OwnerState
|
|
30
|
+
|
|
31
|
+
const componentName = 'NavigationItem'
|
|
32
|
+
const parts = ['li', 'ul', 'item'] as const
|
|
33
|
+
|
|
34
|
+
const { withState } = extendableComponent<OwnerState, typeof componentName, typeof parts>(
|
|
35
|
+
componentName,
|
|
36
|
+
parts,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
const NavigationLI = styled('li')({ display: 'contents' })
|
|
40
|
+
|
|
41
|
+
export function NavigationItem(props: NavigationItemProps) {
|
|
42
|
+
const { id, parentPath, idx, first, last, NavigationList } = props
|
|
43
|
+
|
|
44
|
+
const row = idx + 1
|
|
45
|
+
const { selected, select, hideRootOnNavigate, onClose } = useNavigation()
|
|
46
|
+
|
|
47
|
+
const itemPath = [...parentPath, id]
|
|
48
|
+
const isSelected = selected.slice(0, itemPath.length).join('/') === itemPath.join('/')
|
|
49
|
+
|
|
50
|
+
const hidingRoot = hideRootOnNavigate && selected.length > 0
|
|
51
|
+
const hideItem = hidingRoot && itemPath.length === 1
|
|
52
|
+
|
|
53
|
+
const column = hidingRoot ? itemPath.length - 1 : itemPath.length
|
|
54
|
+
const classes = withState({ first, last, column: itemPath.length })
|
|
55
|
+
|
|
56
|
+
const onCloseHandler: React.MouseEventHandler<HTMLAnchorElement> = useEventCallback((e) => {
|
|
57
|
+
if (!isNavigationHref(props)) return
|
|
58
|
+
const { href } = props
|
|
59
|
+
onClose?.(e, href)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
if (isNavigationButton(props)) {
|
|
63
|
+
const { childItems, name } = props
|
|
64
|
+
return (
|
|
65
|
+
<NavigationLI className={classes.li}>
|
|
66
|
+
<ListItemButton
|
|
67
|
+
className={classes.item}
|
|
68
|
+
role='button'
|
|
69
|
+
sx={{
|
|
70
|
+
gridRowStart: row,
|
|
71
|
+
gridColumnStart: column,
|
|
72
|
+
gap: (theme) => theme.spacings.xxs,
|
|
73
|
+
display: hideItem ? 'none' : 'flex',
|
|
74
|
+
}}
|
|
75
|
+
disabled={isSelected}
|
|
76
|
+
tabIndex={selected.join(',').includes(parentPath.join(',')) ? undefined : -1}
|
|
77
|
+
onClick={(e) => {
|
|
78
|
+
e.preventDefault()
|
|
79
|
+
if (!isSelected) select(itemPath)
|
|
80
|
+
}}
|
|
81
|
+
>
|
|
82
|
+
<Box
|
|
83
|
+
component='span'
|
|
84
|
+
sx={{
|
|
85
|
+
whiteSpace: 'nowrap',
|
|
86
|
+
overflowX: 'hidden',
|
|
87
|
+
textOverflow: 'ellipsis',
|
|
88
|
+
flexGrow: 1,
|
|
89
|
+
}}
|
|
90
|
+
>
|
|
91
|
+
{name}
|
|
92
|
+
</Box>
|
|
93
|
+
<IconSvg src={iconChevronRight} sx={{ flexShrink: 0 }} />
|
|
94
|
+
</ListItemButton>
|
|
95
|
+
|
|
96
|
+
<NavigationList items={childItems} selected={isSelected} parentPath={itemPath} />
|
|
97
|
+
</NavigationLI>
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (isNavigationHref(props)) {
|
|
102
|
+
const { name, href } = props
|
|
103
|
+
return (
|
|
104
|
+
<NavigationLI sx={[hideItem && { display: 'none' }]} className={classes.li}>
|
|
105
|
+
<PageLink href={href} passHref>
|
|
106
|
+
<ListItemButton
|
|
107
|
+
className={classes.item}
|
|
108
|
+
component='a'
|
|
109
|
+
sx={(theme) => ({
|
|
110
|
+
gridRowStart: row,
|
|
111
|
+
gridColumnStart: column,
|
|
112
|
+
gap: theme.spacings.xxs,
|
|
113
|
+
})}
|
|
114
|
+
tabIndex={selected.join(',').includes(parentPath.join(',')) ? undefined : -1}
|
|
115
|
+
onClick={onCloseHandler}
|
|
116
|
+
>
|
|
117
|
+
<Box
|
|
118
|
+
component='span'
|
|
119
|
+
sx={{
|
|
120
|
+
whiteSpace: 'nowrap',
|
|
121
|
+
overflowX: 'hidden',
|
|
122
|
+
textOverflow: 'ellipsis',
|
|
123
|
+
}}
|
|
124
|
+
>
|
|
125
|
+
{name}
|
|
126
|
+
</Box>
|
|
127
|
+
</ListItemButton>
|
|
128
|
+
</PageLink>
|
|
129
|
+
</NavigationLI>
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (isNavigationComponent(props)) {
|
|
134
|
+
const { component } = props
|
|
135
|
+
return (
|
|
136
|
+
<NavigationLI sx={[hideItem && { display: 'none' }]} className={classes.li}>
|
|
137
|
+
<Box sx={{ gridRowStart: row, gridColumnStart: column }} className={classes.item}>
|
|
138
|
+
{component}
|
|
139
|
+
</Box>
|
|
140
|
+
</NavigationLI>
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (process.env.NODE_ENV !== 'production') throw Error('NavigationItem: unknown type')
|
|
145
|
+
|
|
146
|
+
return null
|
|
147
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { styled } from '@mui/material'
|
|
2
|
+
import { extendableComponent } from '../../Styles/extendableComponent'
|
|
3
|
+
import { NavigationNode, NavigationPath } from '../hooks/useNavigation'
|
|
4
|
+
import { NavigationItem } from './NavigationItem'
|
|
5
|
+
|
|
6
|
+
const NavigationUList = styled('ul')({})
|
|
7
|
+
|
|
8
|
+
type NavigationItemsProps = {
|
|
9
|
+
parentPath?: NavigationPath
|
|
10
|
+
items: NavigationNode[]
|
|
11
|
+
selected?: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type OwnerState = {
|
|
15
|
+
column: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const name = 'NavigationList'
|
|
19
|
+
const parts = ['root'] as const
|
|
20
|
+
const { withState } = extendableComponent<OwnerState, typeof name, typeof parts>(name, parts)
|
|
21
|
+
|
|
22
|
+
// const componentName = 'NavigationItem'
|
|
23
|
+
// const parts = ['li', 'ul', 'item'] as const
|
|
24
|
+
|
|
25
|
+
export function NavigationList(props: NavigationItemsProps) {
|
|
26
|
+
const { items, parentPath = [], selected = false } = props
|
|
27
|
+
|
|
28
|
+
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
|
+
>
|
|
36
|
+
{items.map((item, idx) => (
|
|
37
|
+
<NavigationItem
|
|
38
|
+
NavigationList={NavigationList}
|
|
39
|
+
key={item.id}
|
|
40
|
+
{...item}
|
|
41
|
+
parentPath={parentPath}
|
|
42
|
+
idx={idx}
|
|
43
|
+
first={idx === 0}
|
|
44
|
+
last={idx === items.length - 1}
|
|
45
|
+
column={0}
|
|
46
|
+
/>
|
|
47
|
+
))}
|
|
48
|
+
</NavigationUList>
|
|
49
|
+
)
|
|
50
|
+
}
|