@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.
Files changed (34) hide show
  1. package/ActionCard/ActionCard.tsx +2 -2
  2. package/ActionCard/ActionCardList.tsx +10 -13
  3. package/ActionCard/ActionCardListForm.tsx +31 -13
  4. package/Button/LinkOrButton.tsx +2 -2
  5. package/CHANGELOG.md +10 -0
  6. package/Layout/components/LayoutHeader.tsx +16 -1
  7. package/Layout/components/LayoutHeaderContent.tsx +23 -11
  8. package/Layout/components/LayoutProvider.tsx +3 -4
  9. package/Layout/context/layoutContext.tsx +1 -1
  10. package/Layout/hooks/useScrollY.tsx +6 -1
  11. package/LayoutDefault/components/LayoutDefault.tsx +4 -3
  12. package/LayoutOverlay/components/LayoutOverlay.tsx +3 -11
  13. package/Navigation/components/NavigationOverlay.tsx +3 -3
  14. package/Overlay/components/Overlay.tsx +29 -50
  15. package/Overlay/components/OverlayBase.tsx +18 -12
  16. package/Overlay/components/OverlayContainer.tsx +32 -0
  17. package/Overlay/components/OverlaySsr.tsx +43 -0
  18. package/Overlay/components/OverlayStickyBottom.tsx +23 -0
  19. package/Overlay/utils/variantsToScrollSnapType.ts +16 -0
  20. package/OverlayOrPopperChip/OverlayOrPopperChip.tsx +115 -0
  21. package/OverlayOrPopperChip/OverlayOrPopperPanel.tsx +21 -0
  22. package/OverlayOrPopperChip/OverlayPanel.tsx +32 -0
  23. package/OverlayOrPopperChip/OverlayPanelActions.tsx +76 -0
  24. package/OverlayOrPopperChip/PopperPanel.tsx +52 -0
  25. package/OverlayOrPopperChip/PopperPanelActions.tsx +67 -0
  26. package/OverlayOrPopperChip/index.ts +3 -0
  27. package/OverlayOrPopperChip/types.ts +22 -0
  28. package/OverlayOrPopperChip/useHandleClickNotDrag.ts +36 -0
  29. package/PageMeta/PageMeta.tsx +4 -4
  30. package/Theme/MuiSlider.ts +12 -0
  31. package/hooks/useUrlQuery.ts +2 -2
  32. package/icons/index.ts +6 -4
  33. package/index.ts +1 -0
  34. 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?: false
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, selectors } = extendableComponent<
51
- HoistedActionCardProps,
52
- typeof name,
53
- typeof parts
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 (!hasValue) console.error(el, `must be an instance of ActionCard`)
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<T extends ActionCardItemBase> = Omit<
13
+ export type ActionCardListFormProps<A, F extends FieldValues = FieldValues> = Omit<
14
14
  ActionCardListProps,
15
- 'value' | 'error' | 'onChange' | 'children' | 'multiple'
15
+ 'value' | 'error' | 'onChange' | 'children'
16
16
  > &
17
- Omit<ControllerProps<any>, 'render' | 'shouldUnregister'> & {
18
- items: T[]
19
- render: React.FC<ActionCardItemRenderProps<T>>
17
+ Omit<ControllerProps<F>, 'render'> & {
18
+ items: A[]
19
+ render: React.FC<ActionCardItemRenderProps<A>>
20
20
  }
21
21
 
22
- export function ActionCardListForm<T extends ActionCardItemBase>(
23
- props: ActionCardListFormProps<T>,
24
- ) {
25
- const { required, rules, items, render, control, name, errorMessage, defaultValue, ...other } =
26
- props
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={value === item.value}
69
+ selected={onSelect(item.value, value)}
52
70
  onReset={(e) => {
53
71
  e.preventDefault()
54
72
  onChange(null)
@@ -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?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
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 { children, divider, primary, secondary, noAlign = false, switchPoint, sx = [] } = props
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 = { floatingSm: boolean; floatingMd: boolean; scrolled: boolean; divider: boolean }
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
- padding: `0 ${theme.page.horizontal}`,
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
- padding: `0 ${theme.page.horizontal}`,
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
- padding: `0 ${theme.page.horizontal}`,
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
- scroll: MotionValue<number>
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(undefined as unknown as LayoutContext)
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
- return useContext(layoutContext).scroll
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 scrollWithOffset = useTransform(
44
- [useScroll().scrollY, useScrollOffset()],
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={scrollWithOffset}>
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, ScrollSnapType } from '@graphcommerce/framer-scroller'
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 { Overlay } from '../../Overlay/components/Overlay'
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
- <Overlay
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
- </Overlay>
278
+ </OverlaySsr>
279
279
  )
280
280
  })
281
281
 
@@ -1,60 +1,39 @@
1
- import { ScrollerProvider, ScrollSnapType } from '@graphcommerce/framer-scroller'
2
- import { Box } from '@mui/material'
3
- import { useEffect, useState } from 'react'
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 OverlayProps = Omit<
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
- export function Overlay(props: OverlayProps) {
13
- const { children, variantSm = 'bottom', variantMd = 'right', active, ...otherProps } = props
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
- <Box
30
- className='Overlay'
31
- sx={{
32
- position: 'fixed',
33
- top: 0,
34
- left: 0,
35
- transform: loaded ? undefined : 'translateX(-200vw)',
36
- pointerEvents: active ? undefined : 'none',
37
- right: 0,
38
- bottom: 0,
39
- zIndex: 'drawer',
40
- '& .LayoutOverlayBase-overlayPane': {
41
- boxShadow: active ? undefined : 0,
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: 1 | -1
34
+ direction?: 1 | -1
35
35
  onClosed: () => void
36
- offsetPageY: number
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(incommingProps: LayoutOverlayBaseProps) {
73
- const props = useThemeProps({ name, props: incommingProps })
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 scrollWithoffset = useTransform(
218
- [scroll.y, positions.open.y, offsetY],
219
- ([y, openY, offsetYv]: number[]) => Math.max(0, y - openY - offsetYv + offsetPageY),
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={scrollWithoffset}>{children}</LayoutProvider>
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
+ }