@graphcommerce/next-ui 5.2.0-canary.6 → 5.2.0-canary.7
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 +2 -2
- package/ActionCard/ActionCardList.tsx +10 -13
- package/ActionCard/ActionCardListForm.tsx +31 -13
- package/Button/LinkOrButton.tsx +2 -2
- package/CHANGELOG.md +10 -0
- package/Layout/components/LayoutHeader.tsx +16 -1
- package/Layout/components/LayoutHeaderContent.tsx +23 -11
- package/Layout/components/LayoutProvider.tsx +3 -4
- package/Layout/context/layoutContext.tsx +1 -1
- package/Layout/hooks/useScrollY.tsx +6 -1
- package/LayoutDefault/components/LayoutDefault.tsx +4 -3
- package/LayoutOverlay/components/LayoutOverlay.tsx +3 -11
- package/Navigation/components/NavigationOverlay.tsx +3 -3
- package/Overlay/components/Overlay.tsx +29 -50
- package/Overlay/components/OverlayBase.tsx +18 -12
- package/Overlay/components/OverlayContainer.tsx +32 -0
- package/Overlay/components/OverlaySsr.tsx +43 -0
- package/Overlay/components/OverlayStickyBottom.tsx +23 -0
- package/Overlay/utils/variantsToScrollSnapType.ts +16 -0
- package/OverlayOrPopperChip/OverlayOrPopperChip.tsx +115 -0
- package/OverlayOrPopperChip/OverlayOrPopperPanel.tsx +21 -0
- package/OverlayOrPopperChip/OverlayPanel.tsx +32 -0
- package/OverlayOrPopperChip/OverlayPanelActions.tsx +76 -0
- package/OverlayOrPopperChip/PopperPanel.tsx +52 -0
- package/OverlayOrPopperChip/PopperPanelActions.tsx +67 -0
- package/OverlayOrPopperChip/index.ts +3 -0
- package/OverlayOrPopperChip/types.ts +22 -0
- package/OverlayOrPopperChip/useHandleClickNotDrag.ts +36 -0
- package/PageMeta/PageMeta.tsx +4 -4
- package/Theme/MuiSlider.ts +12 -0
- package/hooks/useUrlQuery.ts +2 -2
- package/icons/index.ts +6 -4
- package/index.ts +1 -0
- package/package.json +8 -8
|
@@ -29,9 +29,9 @@ export type ActionCardProps = {
|
|
|
29
29
|
price?: React.ReactNode
|
|
30
30
|
after?: React.ReactNode
|
|
31
31
|
secondaryAction?: React.ReactNode
|
|
32
|
-
onClick?: (event: React.MouseEvent<HTMLElement>, value: string | number) => void
|
|
32
|
+
onClick?: (event: React.MouseEvent<HTMLElement>, value: string | number | null) => void
|
|
33
33
|
selected?: boolean
|
|
34
|
-
value: string | number
|
|
34
|
+
value: string | number | null
|
|
35
35
|
reset?: React.ReactNode
|
|
36
36
|
disabled?: boolean
|
|
37
37
|
error?: boolean
|
|
@@ -8,13 +8,13 @@ import { ActionCardLayout } from './ActionCardLayout'
|
|
|
8
8
|
type MultiSelect = {
|
|
9
9
|
multiple: true
|
|
10
10
|
collapse?: false
|
|
11
|
-
value: (string | number)[]
|
|
11
|
+
value: (string | number | null)[]
|
|
12
12
|
|
|
13
13
|
onChange?: (event: React.MouseEvent<HTMLElement>, value: MultiSelect['value']) => void
|
|
14
14
|
}
|
|
15
15
|
type Select = {
|
|
16
|
-
multiple?:
|
|
17
|
-
value: string | number
|
|
16
|
+
multiple?: boolean
|
|
17
|
+
value: string | number | null
|
|
18
18
|
collapse?: boolean
|
|
19
19
|
|
|
20
20
|
/** Value is null when deselected when not required */
|
|
@@ -47,11 +47,10 @@ type HoistedActionCardProps = Pick<ActionCardProps, 'color' | 'variant' | 'size'
|
|
|
47
47
|
|
|
48
48
|
const parts = ['root'] as const
|
|
49
49
|
const name = 'ActionCardList'
|
|
50
|
-
const { withState
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
>(name, parts)
|
|
50
|
+
const { withState } = extendableComponent<HoistedActionCardProps, typeof name, typeof parts>(
|
|
51
|
+
name,
|
|
52
|
+
parts,
|
|
53
|
+
)
|
|
55
54
|
|
|
56
55
|
export const ActionCardList = React.forwardRef<HTMLDivElement, ActionCardListProps>(
|
|
57
56
|
(props, ref) => {
|
|
@@ -73,8 +72,7 @@ export const ActionCardList = React.forwardRef<HTMLDivElement, ActionCardListPro
|
|
|
73
72
|
const { onChange, value } = props
|
|
74
73
|
const index = Boolean(value) && value?.indexOf(v)
|
|
75
74
|
let newValue: typeof value
|
|
76
|
-
|
|
77
|
-
if (value.length && index && index >= 0) {
|
|
75
|
+
if (value?.length && index !== false && index >= 0) {
|
|
78
76
|
newValue = value.slice()
|
|
79
77
|
newValue.splice(index, 1)
|
|
80
78
|
} else {
|
|
@@ -84,7 +82,6 @@ export const ActionCardList = React.forwardRef<HTMLDivElement, ActionCardListPro
|
|
|
84
82
|
}
|
|
85
83
|
: (event, v) => {
|
|
86
84
|
const { onChange, value } = props
|
|
87
|
-
|
|
88
85
|
if (value !== v) {
|
|
89
86
|
if (required) onChange?.(event, v)
|
|
90
87
|
else onChange?.(event, value === v ? null : v)
|
|
@@ -100,7 +97,7 @@ export const ActionCardList = React.forwardRef<HTMLDivElement, ActionCardListPro
|
|
|
100
97
|
const hasValue = (el as ActionCardLike).props.value
|
|
101
98
|
|
|
102
99
|
if (process.env.NODE_ENV !== 'production') {
|
|
103
|
-
if (
|
|
100
|
+
if (hasValue === undefined) console.error(el, `must be an instance of ActionCard`)
|
|
104
101
|
}
|
|
105
102
|
return (el as ActionCardLike).props.value !== undefined
|
|
106
103
|
}
|
|
@@ -132,7 +129,7 @@ export const ActionCardList = React.forwardRef<HTMLDivElement, ActionCardListPro
|
|
|
132
129
|
const classes = withState({ size, color, variant, layout })
|
|
133
130
|
|
|
134
131
|
return (
|
|
135
|
-
<div>
|
|
132
|
+
<div ref={ref}>
|
|
136
133
|
<ActionCardLayout sx={sx} className={classes.root} layout={layout}>
|
|
137
134
|
{childReactNodes.map((child) => {
|
|
138
135
|
if (collapse && Boolean(value) && !isValueSelected(child.props.value, value))
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* eslint-disable import/no-extraneous-dependencies */
|
|
2
|
-
import { Controller, ControllerProps } from '@graphcommerce/react-hook-form'
|
|
2
|
+
import { Controller, ControllerProps, FieldValues } from '@graphcommerce/react-hook-form'
|
|
3
3
|
import React, { MouseEventHandler } from 'react'
|
|
4
4
|
import { ActionCardProps } from './ActionCard'
|
|
5
5
|
import { ActionCardList, ActionCardListProps } from './ActionCardList'
|
|
@@ -10,22 +10,39 @@ export type ActionCardItemRenderProps<T> = ActionCardProps & {
|
|
|
10
10
|
onReset: MouseEventHandler<HTMLAnchorElement> & MouseEventHandler<HTMLSpanElement>
|
|
11
11
|
} & T
|
|
12
12
|
|
|
13
|
-
export type ActionCardListFormProps<
|
|
13
|
+
export type ActionCardListFormProps<A, F extends FieldValues = FieldValues> = Omit<
|
|
14
14
|
ActionCardListProps,
|
|
15
|
-
'value' | 'error' | 'onChange' | 'children'
|
|
15
|
+
'value' | 'error' | 'onChange' | 'children'
|
|
16
16
|
> &
|
|
17
|
-
Omit<ControllerProps<
|
|
18
|
-
items:
|
|
19
|
-
render: React.FC<ActionCardItemRenderProps<
|
|
17
|
+
Omit<ControllerProps<F>, 'render'> & {
|
|
18
|
+
items: A[]
|
|
19
|
+
render: React.FC<ActionCardItemRenderProps<A>>
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
export function ActionCardListForm<
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
export function ActionCardListForm<
|
|
23
|
+
T extends ActionCardItemBase,
|
|
24
|
+
F extends FieldValues = FieldValues,
|
|
25
|
+
>(props: ActionCardListFormProps<T, F>) {
|
|
26
|
+
const {
|
|
27
|
+
required,
|
|
28
|
+
rules,
|
|
29
|
+
items,
|
|
30
|
+
render,
|
|
31
|
+
control,
|
|
32
|
+
name,
|
|
33
|
+
errorMessage,
|
|
34
|
+
defaultValue,
|
|
35
|
+
multiple,
|
|
36
|
+
...other
|
|
37
|
+
} = props
|
|
27
38
|
const RenderItem = render as React.FC<ActionCardItemRenderProps<ActionCardItemBase>>
|
|
28
39
|
|
|
40
|
+
function onSelect(itemValue: unknown, selectValues: unknown) {
|
|
41
|
+
return multiple
|
|
42
|
+
? Array.isArray(selectValues) && selectValues.some((selectValue) => selectValue === itemValue)
|
|
43
|
+
: selectValues === itemValue
|
|
44
|
+
}
|
|
45
|
+
|
|
29
46
|
return (
|
|
30
47
|
<Controller
|
|
31
48
|
{...props}
|
|
@@ -36,6 +53,7 @@ export function ActionCardListForm<T extends ActionCardItemBase>(
|
|
|
36
53
|
render={({ field: { onChange, value, ref }, fieldState, formState }) => (
|
|
37
54
|
<ActionCardList
|
|
38
55
|
{...other}
|
|
56
|
+
multiple={multiple}
|
|
39
57
|
required={required}
|
|
40
58
|
value={value}
|
|
41
59
|
ref={ref}
|
|
@@ -46,9 +64,9 @@ export function ActionCardListForm<T extends ActionCardItemBase>(
|
|
|
46
64
|
{items.map((item) => (
|
|
47
65
|
<RenderItem
|
|
48
66
|
{...item}
|
|
49
|
-
key={item.value}
|
|
67
|
+
key={item.value ?? 'tralala'}
|
|
50
68
|
value={item.value}
|
|
51
|
-
selected={
|
|
69
|
+
selected={onSelect(item.value, value)}
|
|
52
70
|
onReset={(e) => {
|
|
53
71
|
e.preventDefault()
|
|
54
72
|
onChange(null)
|
package/Button/LinkOrButton.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Link, LinkProps, useForkRef } from '@mui/material'
|
|
1
|
+
import { Breakpoint, Link, LinkProps, useForkRef } from '@mui/material'
|
|
2
2
|
import React, { useRef } from 'react'
|
|
3
3
|
import type { ConditionalExcept } from 'type-fest'
|
|
4
4
|
import { Button, ButtonProps } from './Button'
|
|
@@ -13,7 +13,7 @@ type SharedProperties<A, B> = OmitNever<
|
|
|
13
13
|
export type LinkOrButtonProps = {
|
|
14
14
|
button?: ButtonProps
|
|
15
15
|
link?: LinkProps
|
|
16
|
-
breakpoint?:
|
|
16
|
+
breakpoint?: Breakpoint
|
|
17
17
|
disabled?: boolean
|
|
18
18
|
} & SharedProperties<
|
|
19
19
|
Omit<ButtonProps, 'sx'>,
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## 5.2.0-canary.7
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#1749](https://github.com/graphcommerce-org/graphcommerce/pull/1749) [`0cc472915`](https://github.com/graphcommerce-org/graphcommerce/commit/0cc4729154d316227a41712b5f0adf514768e91f) - Added new filter UI and behaviour. Filters will appear as a popper on the md and up breakpoints and as an overlay on sm and below breakpoints. Filters now have an Apply button instead of applying directly. ([@paales](https://github.com/paales))
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- [#1749](https://github.com/graphcommerce-org/graphcommerce/pull/1749) [`16e91da42`](https://github.com/graphcommerce-org/graphcommerce/commit/16e91da42dcb454ea4761d1780b9338c88ef1463) - Fix spelling error incomming to incoming ([@paales](https://github.com/paales))
|
|
12
|
+
|
|
3
13
|
## 5.2.0-canary.6
|
|
4
14
|
|
|
5
15
|
## 5.2.0-canary.5
|
|
@@ -34,6 +34,7 @@ type ComponentStyleProps = {
|
|
|
34
34
|
children: boolean
|
|
35
35
|
floatingSm: boolean
|
|
36
36
|
floatingMd: boolean
|
|
37
|
+
size: 'small' | 'responsive'
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
const { selectors, withState } = extendableComponent<ComponentStyleProps, 'LayoutHeader'>(
|
|
@@ -42,7 +43,16 @@ const { selectors, withState } = extendableComponent<ComponentStyleProps, 'Layou
|
|
|
42
43
|
)
|
|
43
44
|
|
|
44
45
|
export function LayoutHeader(props: LayoutHeaderProps) {
|
|
45
|
-
const {
|
|
46
|
+
const {
|
|
47
|
+
children,
|
|
48
|
+
divider,
|
|
49
|
+
primary,
|
|
50
|
+
secondary,
|
|
51
|
+
noAlign = false,
|
|
52
|
+
switchPoint,
|
|
53
|
+
size = 'responsive',
|
|
54
|
+
sx = [],
|
|
55
|
+
} = props
|
|
46
56
|
const showBack = useShowBack()
|
|
47
57
|
const showClose = useShowClose()
|
|
48
58
|
|
|
@@ -73,6 +83,7 @@ export function LayoutHeader(props: LayoutHeaderProps) {
|
|
|
73
83
|
noAlign,
|
|
74
84
|
children: !!children,
|
|
75
85
|
divider: !!divider,
|
|
86
|
+
size,
|
|
76
87
|
})
|
|
77
88
|
|
|
78
89
|
return (
|
|
@@ -118,6 +129,9 @@ export function LayoutHeader(props: LayoutHeaderProps) {
|
|
|
118
129
|
marginTop: 0,
|
|
119
130
|
height: theme.appShell.appBarHeightMd,
|
|
120
131
|
marginBottom: `calc(${theme.appShell.appBarHeightMd} * -1)`,
|
|
132
|
+
'&.sizeSmall': {
|
|
133
|
+
height: theme.appShell.headerHeightSm,
|
|
134
|
+
},
|
|
121
135
|
},
|
|
122
136
|
'&.divider': {
|
|
123
137
|
marginBottom: 0,
|
|
@@ -128,6 +142,7 @@ export function LayoutHeader(props: LayoutHeaderProps) {
|
|
|
128
142
|
]}
|
|
129
143
|
>
|
|
130
144
|
<LayoutHeaderContent
|
|
145
|
+
size={size}
|
|
131
146
|
left={left}
|
|
132
147
|
right={right}
|
|
133
148
|
divider={divider}
|
|
@@ -17,9 +17,16 @@ export type LayoutHeaderContentProps = FloatingProps & {
|
|
|
17
17
|
sx?: SxProps<Theme>
|
|
18
18
|
sxBg?: SxProps<Theme>
|
|
19
19
|
layout?: LayoutProps['layout']
|
|
20
|
+
size?: 'small' | 'responsive'
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
type OwnerState = {
|
|
23
|
+
type OwnerState = {
|
|
24
|
+
floatingSm: boolean
|
|
25
|
+
floatingMd: boolean
|
|
26
|
+
scrolled: boolean
|
|
27
|
+
divider: boolean
|
|
28
|
+
size: 'small' | 'responsive'
|
|
29
|
+
}
|
|
23
30
|
const name = 'LayoutHeaderContent' as const
|
|
24
31
|
const parts = ['bg', 'content', 'left', 'center', 'right', 'divider'] as const
|
|
25
32
|
const { withState } = extendableComponent<OwnerState, typeof name, typeof parts>(name, parts)
|
|
@@ -38,17 +45,13 @@ export function LayoutHeaderContent(props: LayoutHeaderContentProps) {
|
|
|
38
45
|
sx = [],
|
|
39
46
|
sxBg = [],
|
|
40
47
|
layout,
|
|
48
|
+
size = 'responsive',
|
|
41
49
|
} = props
|
|
42
50
|
|
|
43
51
|
const scroll = useScrollY()
|
|
44
52
|
const scrolled = useMotionValueValue(scroll, (y) => y >= switchPoint)
|
|
45
53
|
|
|
46
|
-
const classes = withState({
|
|
47
|
-
floatingSm,
|
|
48
|
-
floatingMd,
|
|
49
|
-
scrolled,
|
|
50
|
-
divider: !!divider,
|
|
51
|
-
})
|
|
54
|
+
const classes = withState({ floatingSm, floatingMd, scrolled, divider: !!divider, size })
|
|
52
55
|
|
|
53
56
|
return (
|
|
54
57
|
<>
|
|
@@ -66,6 +69,9 @@ export function LayoutHeaderContent(props: LayoutHeaderContentProps) {
|
|
|
66
69
|
[theme.breakpoints.up('md')]: {
|
|
67
70
|
height: theme.appShell.appBarHeightMd,
|
|
68
71
|
},
|
|
72
|
+
'&.sizeSmall': {
|
|
73
|
+
height: theme.appShell.headerHeightSm,
|
|
74
|
+
},
|
|
69
75
|
borderTopLeftRadius: theme.shape.borderRadius * 3,
|
|
70
76
|
borderTopRightRadius: theme.shape.borderRadius * 3,
|
|
71
77
|
|
|
@@ -108,22 +114,28 @@ export function LayoutHeaderContent(props: LayoutHeaderContentProps) {
|
|
|
108
114
|
// columnGap: theme.spacings.xs,
|
|
109
115
|
|
|
110
116
|
height: theme.appShell.headerHeightSm,
|
|
111
|
-
|
|
112
|
-
|
|
117
|
+
px: theme.page.horizontal,
|
|
113
118
|
[theme.breakpoints.up('md')]: {
|
|
114
119
|
height: theme.appShell.appBarHeightMd,
|
|
115
120
|
},
|
|
121
|
+
'&.sizeSmall': {
|
|
122
|
+
height: theme.appShell.headerHeightSm,
|
|
123
|
+
px: 2,
|
|
124
|
+
[theme.breakpoints.up('md')]: {
|
|
125
|
+
px: 2,
|
|
126
|
+
},
|
|
127
|
+
},
|
|
116
128
|
|
|
117
129
|
'&.floatingSm': {
|
|
118
130
|
[theme.breakpoints.down('md')]: {
|
|
119
|
-
|
|
131
|
+
px: theme.page.horizontal,
|
|
120
132
|
background: 'none',
|
|
121
133
|
pointerEvents: 'none',
|
|
122
134
|
},
|
|
123
135
|
},
|
|
124
136
|
'&.floatingMd': {
|
|
125
137
|
[theme.breakpoints.up('md')]: {
|
|
126
|
-
|
|
138
|
+
px: theme.page.horizontal,
|
|
127
139
|
background: 'none',
|
|
128
140
|
pointerEvents: 'none',
|
|
129
141
|
},
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import { MotionValue } from 'framer-motion'
|
|
2
1
|
import { useMemo } from 'react'
|
|
3
2
|
import { layoutContext } from '../context/layoutContext'
|
|
3
|
+
import { LayoutContext } from '../types'
|
|
4
4
|
|
|
5
5
|
export type LayoutProviderProps = {
|
|
6
6
|
children: React.ReactNode
|
|
7
|
-
|
|
8
|
-
}
|
|
7
|
+
} & LayoutContext
|
|
9
8
|
|
|
10
9
|
export function LayoutProvider(props: LayoutProviderProps) {
|
|
11
|
-
const { children, scroll } = props
|
|
10
|
+
const { children, scroll: scroll } = props
|
|
12
11
|
|
|
13
12
|
return (
|
|
14
13
|
<layoutContext.Provider value={useMemo(() => ({ scroll }), [scroll])}>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { LayoutContext } from '../types'
|
|
3
3
|
|
|
4
|
-
export const layoutContext = React.createContext
|
|
4
|
+
export const layoutContext = React.createContext<null | LayoutContext>(null)
|
|
5
5
|
layoutContext.displayName = 'layoutContext'
|
|
@@ -2,5 +2,10 @@ import { useContext } from 'react'
|
|
|
2
2
|
import { layoutContext } from '../context/layoutContext'
|
|
3
3
|
|
|
4
4
|
export function useScrollY() {
|
|
5
|
-
|
|
5
|
+
const context = useContext(layoutContext)
|
|
6
|
+
|
|
7
|
+
if (!context) {
|
|
8
|
+
throw new Error('useScrollY must be used within a LayoutProvider')
|
|
9
|
+
}
|
|
10
|
+
return context.scroll
|
|
6
11
|
}
|
|
@@ -40,8 +40,9 @@ export function LayoutDefault(props: LayoutDefaultProps) {
|
|
|
40
40
|
sx = [],
|
|
41
41
|
} = props
|
|
42
42
|
|
|
43
|
-
const
|
|
44
|
-
|
|
43
|
+
const { scrollY } = useScroll()
|
|
44
|
+
const scrollYOffset = useTransform(
|
|
45
|
+
[scrollY, useScrollOffset()],
|
|
45
46
|
([y, offset]: number[]) => y + offset,
|
|
46
47
|
)
|
|
47
48
|
|
|
@@ -66,7 +67,7 @@ export function LayoutDefault(props: LayoutDefaultProps) {
|
|
|
66
67
|
...(Array.isArray(sx) ? sx : [sx]),
|
|
67
68
|
]}
|
|
68
69
|
>
|
|
69
|
-
<LayoutProvider scroll={
|
|
70
|
+
<LayoutProvider scroll={scrollYOffset}>
|
|
70
71
|
{beforeHeader}
|
|
71
72
|
<Box
|
|
72
73
|
component='header'
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { usePageContext, useGo, useScrollOffset } from '@graphcommerce/framer-next-pages'
|
|
2
|
-
import { ScrollerProvider
|
|
2
|
+
import { ScrollerProvider } from '@graphcommerce/framer-scroller'
|
|
3
3
|
import { useMotionValueValue } from '@graphcommerce/framer-utils'
|
|
4
4
|
import { useEventCallback } from '@mui/material'
|
|
5
5
|
import { usePresence } from 'framer-motion'
|
|
6
6
|
import type { SetOptional } from 'type-fest'
|
|
7
7
|
import { OverlayBase, LayoutOverlayBaseProps } from '../../Overlay/components/OverlayBase'
|
|
8
|
+
import { variantsToScrollSnapType } from '../../Overlay/utils/variantsToScrollSnapType'
|
|
8
9
|
|
|
9
10
|
export type LayoutOverlayProps = Omit<
|
|
10
11
|
SetOptional<LayoutOverlayBaseProps, 'variantSm' | 'variantMd'>,
|
|
@@ -14,22 +15,13 @@ export type LayoutOverlayProps = Omit<
|
|
|
14
15
|
export function LayoutOverlay(props: LayoutOverlayProps) {
|
|
15
16
|
const { children, variantSm = 'bottom', variantMd = 'right', ...otherProps } = props
|
|
16
17
|
|
|
17
|
-
const scrollSnapTypeSm: ScrollSnapType =
|
|
18
|
-
variantSm === 'left' || variantSm === 'right' ? 'inline mandatory' : 'block proximity'
|
|
19
|
-
const scrollSnapTypeMd: ScrollSnapType =
|
|
20
|
-
variantMd === 'left' || variantMd === 'right' ? 'inline mandatory' : 'block proximity'
|
|
21
|
-
|
|
22
18
|
const { closeSteps, active, direction } = usePageContext()
|
|
23
19
|
const onCloseHandler = useGo(closeSteps * -1)
|
|
24
20
|
const offsetPageY = useMotionValueValue(useScrollOffset(), (v) => v)
|
|
25
21
|
const [isPresent, safeToRemove] = usePresence()
|
|
26
22
|
|
|
27
23
|
return (
|
|
28
|
-
<ScrollerProvider
|
|
29
|
-
scrollSnapTypeSm={scrollSnapTypeSm}
|
|
30
|
-
scrollSnapTypeMd={scrollSnapTypeMd}
|
|
31
|
-
_inititalSnap={false}
|
|
32
|
-
>
|
|
24
|
+
<ScrollerProvider {...variantsToScrollSnapType(props)} _inititalSnap={false}>
|
|
33
25
|
<OverlayBase
|
|
34
26
|
active={active}
|
|
35
27
|
direction={direction}
|
|
@@ -7,7 +7,7 @@ import type { LiteralUnion } from 'type-fest'
|
|
|
7
7
|
import { IconSvg, useIconSvgSize } from '../../IconSvg'
|
|
8
8
|
import { LayoutHeaderContent } from '../../Layout/components/LayoutHeaderContent'
|
|
9
9
|
import { LayoutTitle } from '../../Layout/components/LayoutTitle'
|
|
10
|
-
import {
|
|
10
|
+
import { OverlaySsr } from '../../Overlay/components/OverlaySsr'
|
|
11
11
|
import { extendableComponent } from '../../Styles/extendableComponent'
|
|
12
12
|
import { useFabSize } from '../../Theme'
|
|
13
13
|
import { useMatchMedia } from '../../hooks'
|
|
@@ -90,7 +90,7 @@ export const NavigationOverlay = React.memo((props: NavigationOverlayProps) => {
|
|
|
90
90
|
if (selectedLevel === -1 && serverRenderDepth <= 0) return null
|
|
91
91
|
|
|
92
92
|
return (
|
|
93
|
-
<
|
|
93
|
+
<OverlaySsr
|
|
94
94
|
className={classes.root}
|
|
95
95
|
active={activeAndNotClosing}
|
|
96
96
|
safeToRemove={afterClose}
|
|
@@ -275,7 +275,7 @@ export const NavigationOverlay = React.memo((props: NavigationOverlayProps) => {
|
|
|
275
275
|
</Box>
|
|
276
276
|
</Box>
|
|
277
277
|
</MotionDiv>
|
|
278
|
-
</
|
|
278
|
+
</OverlaySsr>
|
|
279
279
|
)
|
|
280
280
|
})
|
|
281
281
|
|
|
@@ -1,60 +1,39 @@
|
|
|
1
|
-
import { ScrollerProvider
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { ScrollerProvider } from '@graphcommerce/framer-scroller'
|
|
2
|
+
import { Portal } from '@mui/material'
|
|
3
|
+
import { AnimatePresence, usePresence } from 'framer-motion'
|
|
4
4
|
import type { SetOptional } from 'type-fest'
|
|
5
|
+
import { variantsToScrollSnapType } from '../utils/variantsToScrollSnapType'
|
|
5
6
|
import { OverlayBase, LayoutOverlayBaseProps } from './OverlayBase'
|
|
7
|
+
import { OverlayContainer } from './OverlayContainer'
|
|
6
8
|
|
|
7
|
-
export type
|
|
9
|
+
export type OverlayTmpProps = Omit<
|
|
8
10
|
SetOptional<LayoutOverlayBaseProps, 'variantSm' | 'variantMd'>,
|
|
9
|
-
'direction' | 'offsetPageY' | 'isPresent'
|
|
11
|
+
'direction' | 'offsetPageY' | 'isPresent' | 'safeToRemove'
|
|
10
12
|
>
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
const {
|
|
14
|
-
|
|
15
|
-
const scrollSnapTypeSm: ScrollSnapType =
|
|
16
|
-
variantSm === 'left' || variantSm === 'right' ? 'inline mandatory' : 'block proximity'
|
|
17
|
-
const scrollSnapTypeMd: ScrollSnapType =
|
|
18
|
-
variantMd === 'left' || variantMd === 'right' ? 'inline mandatory' : 'block proximity'
|
|
19
|
-
|
|
20
|
-
// todo: The overlay is always present in the DOM and the initial scroll position is set to 0.
|
|
21
|
-
// This means in this case that the overlay is visisble. LayoutOverlayBase sets the scroll position to 320 with JS.
|
|
22
|
-
// This would cause the overlay to be visisble for a moment before the scroll position is set.
|
|
23
|
-
// The solution is to set the the first scroll-snap-align and scroll-snap-stop to the open position of the overlay.
|
|
24
|
-
// However: We have control of the LayoutOverlayBase, we do not have control of all the child components so that solution will not work..
|
|
25
|
-
const [loaded, setLoaded] = useState(false)
|
|
26
|
-
useEffect(() => setLoaded(true), [])
|
|
14
|
+
function OverlayUsePresence(props: OverlayTmpProps) {
|
|
15
|
+
const { variantSm = 'bottom', variantMd = 'right', active, ...otherProps } = props
|
|
16
|
+
const [isPresent, safeToRemove] = usePresence()
|
|
27
17
|
|
|
28
18
|
return (
|
|
29
|
-
<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}}
|
|
44
|
-
>
|
|
45
|
-
<ScrollerProvider scrollSnapTypeSm={scrollSnapTypeSm} scrollSnapTypeMd={scrollSnapTypeMd}>
|
|
46
|
-
<OverlayBase
|
|
47
|
-
offsetPageY={0}
|
|
48
|
-
variantMd={variantMd}
|
|
49
|
-
variantSm={variantSm}
|
|
50
|
-
direction={1}
|
|
51
|
-
active={active}
|
|
52
|
-
isPresent={active}
|
|
53
|
-
{...otherProps}
|
|
54
|
-
>
|
|
55
|
-
{children}
|
|
56
|
-
</OverlayBase>
|
|
57
|
-
</ScrollerProvider>
|
|
58
|
-
</Box>
|
|
19
|
+
<Portal>
|
|
20
|
+
<OverlayContainer active={active}>
|
|
21
|
+
<ScrollerProvider {...variantsToScrollSnapType(props)}>
|
|
22
|
+
<OverlayBase
|
|
23
|
+
active={active}
|
|
24
|
+
variantMd={variantMd}
|
|
25
|
+
variantSm={variantSm}
|
|
26
|
+
isPresent={isPresent}
|
|
27
|
+
safeToRemove={safeToRemove}
|
|
28
|
+
{...otherProps}
|
|
29
|
+
/>
|
|
30
|
+
</ScrollerProvider>
|
|
31
|
+
</OverlayContainer>
|
|
32
|
+
</Portal>
|
|
59
33
|
)
|
|
60
34
|
}
|
|
35
|
+
|
|
36
|
+
export function Overlay(props: OverlayTmpProps) {
|
|
37
|
+
const { active } = props
|
|
38
|
+
return <AnimatePresence>{active && <OverlayUsePresence {...props} />}</AnimatePresence>
|
|
39
|
+
}
|
|
@@ -2,7 +2,7 @@ import { Scroller, useScrollerContext, useScrollTo } from '@graphcommerce/framer
|
|
|
2
2
|
import { dvh, dvw, useIsomorphicLayoutEffect } from '@graphcommerce/framer-utils'
|
|
3
3
|
import { Box, styled, SxProps, Theme, useTheme, useThemeProps } from '@mui/material'
|
|
4
4
|
import { m, MotionProps, useDomEvent, useMotionValue, useTransform } from 'framer-motion'
|
|
5
|
-
import React, { useCallback, useEffect, useRef } from 'react'
|
|
5
|
+
import React, { useCallback, useContext, useEffect, useMemo, useRef } from 'react'
|
|
6
6
|
import { LayoutProvider } from '../../Layout/components/LayoutProvider'
|
|
7
7
|
import { ExtendableComponent, extendableComponent } from '../../Styles'
|
|
8
8
|
import { useOverlayPosition } from '../hooks/useOverlayPosition'
|
|
@@ -26,14 +26,14 @@ type OverridableProps = {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export type LayoutOverlayBaseProps = {
|
|
29
|
-
children?: React.ReactNode
|
|
29
|
+
children?: React.ReactNode | (() => React.ReactNode)
|
|
30
30
|
className?: string
|
|
31
31
|
sx?: SxProps<Theme>
|
|
32
32
|
sxBackdrop?: SxProps<Theme>
|
|
33
33
|
active: boolean
|
|
34
|
-
direction
|
|
34
|
+
direction?: 1 | -1
|
|
35
35
|
onClosed: () => void
|
|
36
|
-
offsetPageY
|
|
36
|
+
offsetPageY?: number
|
|
37
37
|
isPresent: boolean
|
|
38
38
|
safeToRemove?: (() => void) | null | undefined
|
|
39
39
|
overlayPaneProps?: MotionProps
|
|
@@ -69,8 +69,8 @@ const clearScrollLock = () => {
|
|
|
69
69
|
document.body.style.overflow = ''
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
export function OverlayBase(
|
|
73
|
-
const props = useThemeProps({ name, props:
|
|
72
|
+
export function OverlayBase(incomingProps: LayoutOverlayBaseProps) {
|
|
73
|
+
const props = useThemeProps({ name, props: incomingProps })
|
|
74
74
|
|
|
75
75
|
const {
|
|
76
76
|
children,
|
|
@@ -85,8 +85,8 @@ export function OverlayBase(incommingProps: LayoutOverlayBaseProps) {
|
|
|
85
85
|
sxBackdrop = [],
|
|
86
86
|
active,
|
|
87
87
|
onClosed,
|
|
88
|
-
direction,
|
|
89
|
-
offsetPageY,
|
|
88
|
+
direction = 1,
|
|
89
|
+
offsetPageY = 0,
|
|
90
90
|
isPresent,
|
|
91
91
|
safeToRemove,
|
|
92
92
|
overlayPaneProps,
|
|
@@ -214,9 +214,9 @@ export function OverlayBase(incommingProps: LayoutOverlayBaseProps) {
|
|
|
214
214
|
}, [offsetY])
|
|
215
215
|
|
|
216
216
|
// Create the exact position for the LayoutProvider which offsets the top of the overlay
|
|
217
|
-
const
|
|
218
|
-
[scroll.y, positions.open.y
|
|
219
|
-
([y, openY
|
|
217
|
+
const scrollYOffset = useTransform(
|
|
218
|
+
[scroll.y, positions.open.y],
|
|
219
|
+
([y, openY]: number[]) => y - openY + offsetPageY,
|
|
220
220
|
)
|
|
221
221
|
|
|
222
222
|
const onClickAway = useCallback(
|
|
@@ -447,13 +447,19 @@ export function OverlayBase(incommingProps: LayoutOverlayBaseProps) {
|
|
|
447
447
|
maxWidth: dvw(100),
|
|
448
448
|
},
|
|
449
449
|
|
|
450
|
+
'&.variantMdBottom.sizeMdFloating': {
|
|
451
|
+
width: widthMd,
|
|
452
|
+
},
|
|
453
|
+
|
|
450
454
|
'&.sizeMdFloating': {
|
|
451
455
|
borderRadius: `${theme.shape.borderRadius * 4}px`,
|
|
452
456
|
},
|
|
453
457
|
},
|
|
454
458
|
})}
|
|
455
459
|
>
|
|
456
|
-
<LayoutProvider scroll={
|
|
460
|
+
<LayoutProvider scroll={scrollYOffset}>
|
|
461
|
+
{active && (typeof children === 'function' ? children() : children)}
|
|
462
|
+
</LayoutProvider>
|
|
457
463
|
</MotionDiv>
|
|
458
464
|
</Box>
|
|
459
465
|
</Scroller>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Box } from '@mui/material'
|
|
2
|
+
import { LayoutOverlayBaseProps } from './OverlayBase'
|
|
3
|
+
|
|
4
|
+
type OverlayContainerProps = Pick<LayoutOverlayBaseProps, 'active'> & {
|
|
5
|
+
hidden?: boolean
|
|
6
|
+
children: React.ReactNode
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function OverlayContainer(props: OverlayContainerProps) {
|
|
10
|
+
const { children, active, hidden } = props
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<Box
|
|
14
|
+
className='Overlay'
|
|
15
|
+
sx={{
|
|
16
|
+
position: 'fixed',
|
|
17
|
+
top: 0,
|
|
18
|
+
left: 0,
|
|
19
|
+
transform: hidden ? 'translateX(-200vw)' : undefined,
|
|
20
|
+
pointerEvents: active ? undefined : 'none',
|
|
21
|
+
right: 0,
|
|
22
|
+
bottom: 0,
|
|
23
|
+
zIndex: 'drawer',
|
|
24
|
+
'& .LayoutOverlayBase-overlayPane': {
|
|
25
|
+
boxShadow: active ? undefined : 0,
|
|
26
|
+
},
|
|
27
|
+
}}
|
|
28
|
+
>
|
|
29
|
+
{children}
|
|
30
|
+
</Box>
|
|
31
|
+
)
|
|
32
|
+
}
|