@graphcommerce/next-ui 4.3.1 → 4.5.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/CHANGELOG.md CHANGED
@@ -1,5 +1,57 @@
1
1
  # Change Log
2
2
 
3
+ ## 4.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#1368](https://github.com/graphcommerce-org/graphcommerce/pull/1368) [`892018809`](https://github.com/graphcommerce-org/graphcommerce/commit/8920188093d0422ec50580e408dc28ac5f93e46a) Thanks [@paales](https://github.com/paales)! - Added a new <ErrorSnackbar /> component to more easily create an error snackbar.
8
+
9
+ ### Patch Changes
10
+
11
+ - [#1368](https://github.com/graphcommerce-org/graphcommerce/pull/1368) [`892018809`](https://github.com/graphcommerce-org/graphcommerce/commit/8920188093d0422ec50580e408dc28ac5f93e46a) Thanks [@paales](https://github.com/paales)! - <FormDiv contained/> would throw an error that contained isn't a recognized prop
12
+
13
+ * [#1369](https://github.com/graphcommerce-org/graphcommerce/pull/1369) [`ae6449502`](https://github.com/graphcommerce-org/graphcommerce/commit/ae64495024a455bbe5188588604368c1542840c9) Thanks [@paales](https://github.com/paales)! - Upgraded dependencies
14
+
15
+ * Updated dependencies [[`ae6449502`](https://github.com/graphcommerce-org/graphcommerce/commit/ae64495024a455bbe5188588604368c1542840c9)]:
16
+ - @graphcommerce/framer-next-pages@3.1.4
17
+ - @graphcommerce/framer-scroller@2.1.3
18
+ - @graphcommerce/framer-utils@3.1.1
19
+ - @graphcommerce/image@3.1.2
20
+
21
+ ## 4.4.0
22
+
23
+ ### Minor Changes
24
+
25
+ - [#1363](https://github.com/graphcommerce-org/graphcommerce/pull/1363) [`f67da3cfb`](https://github.com/graphcommerce-org/graphcommerce/commit/f67da3cfbe2dcf5ea23519d088c5aa0074029182) Thanks [@paales](https://github.com/paales)! - Export useIconSvgSize from IconSvg so that other components can use it's size
26
+
27
+ ### Patch Changes
28
+
29
+ - [#1360](https://github.com/graphcommerce-org/graphcommerce/pull/1360) [`49a2d6617`](https://github.com/graphcommerce-org/graphcommerce/commit/49a2d661712e1787fba46c6195f7b559189e23d9) Thanks [@paales](https://github.com/paales)! - Make sure Cart and menu isn’t hidden on landscape
30
+
31
+ * [#1363](https://github.com/graphcommerce-org/graphcommerce/pull/1363) [`218766869`](https://github.com/graphcommerce-org/graphcommerce/commit/218766869f7468c067a590857c942f3819f8add4) Thanks [@paales](https://github.com/paales)! - Use a Fab for the LayoutHeaderClose so it isn't as obtrusive on desktop
32
+
33
+ - [#1353](https://github.com/graphcommerce-org/graphcommerce/pull/1353) [`0e5ee7ba8`](https://github.com/graphcommerce-org/graphcommerce/commit/0e5ee7ba89698e5e711001e846ed182528060cba) Thanks [@paales](https://github.com/paales)! - Eslint: enable rules that were previously disabled and make fixes
34
+
35
+ * [#1360](https://github.com/graphcommerce-org/graphcommerce/pull/1360) [`829b8690b`](https://github.com/graphcommerce-org/graphcommerce/commit/829b8690bc5d0a46e596299e4120e9837a9f179c) Thanks [@paales](https://github.com/paales)! - Lots of fixes for LayoutOverlay:
36
+
37
+ - When interacting with an overlay it causes browser resizes on mobile and causing a janky experience.
38
+ - Allow interaction with the previous layer after it has been closed, instead of waiting for the actual route to complete.
39
+ - Allow scrolling to the the bottom in the overlay when the height is just a bit higher than the window.
40
+ - Allow sheet positioning for bottom for the overlay: mdSpacingTop, smSpacingTop.
41
+ - Add scrollSnapStop:always to the actual overlay pane, so that when scrolling up it will not just close the overlay. Requiring a second swipe to close the overlay.
42
+ - Remove spacing on the bottom of the overlay that was introduced for Android, not nessesary anymore because of the clientSizeCssVar.y
43
+
44
+ * Updated dependencies [[`829b8690b`](https://github.com/graphcommerce-org/graphcommerce/commit/829b8690bc5d0a46e596299e4120e9837a9f179c), [`c9f7ac026`](https://github.com/graphcommerce-org/graphcommerce/commit/c9f7ac026b49047eca05be208b515f364e21571c), [`829b8690b`](https://github.com/graphcommerce-org/graphcommerce/commit/829b8690bc5d0a46e596299e4120e9837a9f179c), [`0e5ee7ba8`](https://github.com/graphcommerce-org/graphcommerce/commit/0e5ee7ba89698e5e711001e846ed182528060cba), [`829b8690b`](https://github.com/graphcommerce-org/graphcommerce/commit/829b8690bc5d0a46e596299e4120e9837a9f179c)]:
45
+ - @graphcommerce/framer-scroller@2.1.2
46
+ - @graphcommerce/framer-next-pages@3.1.3
47
+ - @graphcommerce/framer-utils@3.1.0
48
+
49
+ ## 4.3.2
50
+
51
+ ### Patch Changes
52
+
53
+ - [#1343](https://github.com/graphcommerce-org/graphcommerce/pull/1343) [`b76d0892a`](https://github.com/graphcommerce-org/graphcommerce/commit/b76d0892a11bd916aefd46ba72c2da00e38ce45b) Thanks [@ErwinOtten](https://github.com/ErwinOtten)! - Canonicals could contain a double slash in the URL
54
+
3
55
  ## 4.3.1
4
56
 
5
57
  ### Patch Changes
package/Form/Form.tsx CHANGED
@@ -34,4 +34,6 @@ export const Form = styled('form', {
34
34
  shouldForwardProp: (prop) => prop !== 'contained',
35
35
  })<FormStyleProps>(styles)
36
36
 
37
- export const FormDiv = styled('div')<FormStyleProps>(styles)
37
+ export const FormDiv = styled('div', {
38
+ shouldForwardProp: (prop) => prop !== 'contained',
39
+ })<FormStyleProps>(styles)
@@ -17,6 +17,7 @@ export type InputCheckmarkProps = {
17
17
  export function InputCheckmark(props: InputCheckmarkProps) {
18
18
  const { show, children, select = false } = props
19
19
 
20
+ // eslint-disable-next-line react/jsx-no-useless-fragment
20
21
  if (!show) return <>{children}</>
21
22
  return (
22
23
  <IconSvg
@@ -40,6 +40,19 @@ export type IconSvgProps = StyleProps &
40
40
  Pick<ImageProps, 'src'> &
41
41
  Pick<ComponentProps<'svg'>, 'className' | 'style'> & { sx?: SxProps<Theme> }
42
42
 
43
+ export const sizes = {
44
+ xs: rv(11, 13),
45
+ small: rv(12, 16),
46
+ medium: rv(22, 24),
47
+ large: rv(24, 28),
48
+ xl: rv(38, 62),
49
+ xxl: rv(96, 148),
50
+ } as const
51
+
52
+ export function useIconSvgSize(size: keyof typeof sizes) {
53
+ return sizes[size]
54
+ }
55
+
43
56
  const Svg = styled('svg', { name, target: name })(() => [
44
57
  {
45
58
  userSelect: 'none',
@@ -58,12 +71,12 @@ const Svg = styled('svg', { name, target: name })(() => [
58
71
 
59
72
  strokeWidth: svgIconStrokeWidth(28, 148, 1.4, 0.8),
60
73
 
61
- '&.sizeXs': { fontSize: rv(11, 13) },
62
- '&.sizeSmall': { fontSize: rv(12, 16) },
63
- '&.sizeMedium': { fontSize: rv(22, 24) },
64
- '&.sizeLarge': { fontSize: rv(24, 28) },
65
- '&.sizeXl': { fontSize: rv(38, 62) },
66
- '&.sizeXxl': { fontSize: rv(96, 148) },
74
+ '&.sizeXs': { fontSize: sizes.xs },
75
+ '&.sizeSmall': { fontSize: sizes.small },
76
+ '&.sizeMedium': { fontSize: sizes.medium },
77
+ '&.sizeLarge': { fontSize: sizes.large },
78
+ '&.sizeXl': { fontSize: sizes.xl },
79
+ '&.sizeXxl': { fontSize: sizes.xxl },
67
80
 
68
81
  '&.fillIcon': {
69
82
  fill: 'currentColor',
@@ -1,8 +1,8 @@
1
1
  import { useGo, usePageContext } from '@graphcommerce/framer-next-pages'
2
- import { Trans } from '@lingui/macro'
3
- import { Box } from '@mui/material'
4
- import { LinkOrButton } from '../../Button/LinkOrButton'
5
- import { IconSvg } from '../../IconSvg'
2
+ import { Fab } from '@mui/material'
3
+ import { useState } from 'react'
4
+ import { IconSvg, useIconSvgSize } from '../../IconSvg'
5
+ import { useFabSize } from '../../Theme'
6
6
  import { iconClose } from '../../icons'
7
7
 
8
8
  export function useShowClose() {
@@ -12,20 +12,28 @@ export function useShowClose() {
12
12
 
13
13
  export function LayoutHeaderClose() {
14
14
  const { closeSteps } = usePageContext()
15
- const onClick = useGo(closeSteps * -1)
15
+ const [disabled, setDisabled] = useState(false)
16
+ const go = useGo(closeSteps * -1)
17
+ const onClick = () => {
18
+ setDisabled(true)
19
+ go()
20
+ }
21
+
22
+ const fabSize = useFabSize('responsive')
23
+ const svgSize = useIconSvgSize('large')
16
24
 
17
25
  return (
18
- <LinkOrButton
19
- button={{ type: 'button', variant: 'pill' }}
20
- color='inherit'
26
+ <Fab
21
27
  onClick={onClick}
22
- aria-label='Close'
23
- startIcon={<IconSvg src={iconClose} size='medium' />}
24
- // className={classes.close}
28
+ sx={{
29
+ boxShadow: 'none',
30
+ marginLeft: `calc((${fabSize} - ${svgSize}) * -0.5)`,
31
+ marginRight: `calc((${fabSize} - ${svgSize}) * -0.5)`,
32
+ }}
33
+ size='responsive'
34
+ disabled={disabled}
25
35
  >
26
- <Box component='span' sx={{ display: { xs: 'none', md: 'inline' } }}>
27
- <Trans>Close</Trans>
28
- </Box>
29
- </LinkOrButton>
36
+ <IconSvg src={iconClose} size='large' />
37
+ </Fab>
30
38
  )
31
39
  }
@@ -9,6 +9,7 @@ export type LayoutProviderProps = {
9
9
 
10
10
  export function LayoutProvider(props: LayoutProviderProps) {
11
11
  const { children, scroll } = props
12
+
12
13
  return (
13
14
  <layoutContext.Provider value={useMemo(() => ({ scroll }), [scroll])}>
14
15
  {children}
@@ -116,7 +116,7 @@ export function LayoutDefault(props: LayoutDefaultProps) {
116
116
  top: 'unset',
117
117
  bottom: `calc(20px + ${fabIconSize})`,
118
118
  padding: `0 20px`,
119
- '@media (max-height: 530px)': {
119
+ '@media (max-height: 530px) and (orientation: portrait)': {
120
120
  display: 'none',
121
121
  },
122
122
  },
@@ -1,11 +1,15 @@
1
1
  import { useGo, usePageContext, useScrollOffset } from '@graphcommerce/framer-next-pages'
2
2
  import { Scroller, useScrollerContext, useScrollTo } from '@graphcommerce/framer-scroller'
3
- import { useElementScroll, useIsomorphicLayoutEffect } from '@graphcommerce/framer-utils'
4
- import { Box, styled, SxProps, Theme } from '@mui/material'
3
+ import {
4
+ clientSizeCssVar,
5
+ useElementScroll,
6
+ useIsomorphicLayoutEffect,
7
+ } from '@graphcommerce/framer-utils'
8
+ import { Box, styled, SxProps, Theme, useTheme, useThemeProps } from '@mui/material'
5
9
  import { m, useDomEvent, useMotionValue, usePresence, useTransform } from 'framer-motion'
6
10
  import React, { useCallback, useEffect, useRef } from 'react'
7
11
  import { LayoutProvider } from '../../Layout/components/LayoutProvider'
8
- import { extendableComponent } from '../../Styles'
12
+ import { ExtendableComponent, extendableComponent } from '../../Styles'
9
13
  import { useOverlayPosition } from '../hooks/useOverlayPosition'
10
14
 
11
15
  export type LayoutOverlayVariant = 'left' | 'bottom' | 'right'
@@ -21,12 +25,18 @@ type StyleProps = {
21
25
  justifyMd?: LayoutOverlayAlign
22
26
  }
23
27
 
28
+ type OverridableProps = {
29
+ mdSpacingTop?: (theme: Theme) => string
30
+ smSpacingTop?: (theme: Theme) => string
31
+ }
32
+
24
33
  export type LayoutOverlayBaseProps = {
25
34
  children?: React.ReactNode
26
35
  className?: string
27
36
  sx?: SxProps<Theme>
28
37
  sxBackdrop?: SxProps<Theme>
29
- } & StyleProps
38
+ } & StyleProps &
39
+ OverridableProps
30
40
 
31
41
  enum OverlayPosition {
32
42
  UNOPENED = -1,
@@ -38,9 +48,22 @@ const name = 'LayoutOverlayBase' as const
38
48
  const parts = ['scroller', 'backdrop', 'overlay', 'overlayPane', 'beforeOverlay'] as const
39
49
  const { withState } = extendableComponent<StyleProps, typeof name, typeof parts>(name, parts)
40
50
 
51
+ /** Expose the component to be exendable in your theme.components */
52
+ declare module '@mui/material/styles/components' {
53
+ interface Components {
54
+ LayoutOverlayBase?: Pick<ExtendableComponent<OverridableProps & StyleProps>, 'defaultProps'>
55
+ }
56
+ }
57
+
41
58
  const MotionDiv = styled(m.div)({})
42
59
 
43
- export function LayoutOverlayBase(props: LayoutOverlayBaseProps) {
60
+ const clearScrollLock = () => {
61
+ document.body.style.overflow = ''
62
+ }
63
+
64
+ export function LayoutOverlayBase(incommingProps: LayoutOverlayBaseProps) {
65
+ const props = useThemeProps({ name, props: incommingProps })
66
+
44
67
  const {
45
68
  children,
46
69
  variantSm,
@@ -54,6 +77,14 @@ export function LayoutOverlayBase(props: LayoutOverlayBaseProps) {
54
77
  sxBackdrop = [],
55
78
  } = props
56
79
 
80
+ const th = useTheme()
81
+ const mdSpacingTop = (
82
+ props.mdSpacingTop ?? ((theme) => `calc(${theme.appShell.headerHeightMd} * 0.5)`)
83
+ )(th)
84
+ const smSpacingTop = (
85
+ props.smSpacingTop ?? ((theme) => `calc(${theme.appShell.headerHeightSm} * 0.5)`)
86
+ )(th)
87
+
57
88
  const { scrollerRef, snap } = useScrollerContext()
58
89
  const positions = useOverlayPosition()
59
90
  const scrollTo = useScrollTo()
@@ -65,25 +96,21 @@ export function LayoutOverlayBase(props: LayoutOverlayBaseProps) {
65
96
 
66
97
  const position = useMotionValue<OverlayPosition>(OverlayPosition.UNOPENED)
67
98
 
68
- const classes = withState({
69
- variantSm,
70
- variantMd,
71
- sizeSm,
72
- sizeMd,
73
- justifySm,
74
- justifyMd,
75
- })
99
+ const classes = withState({ variantSm, variantMd, sizeSm, sizeMd, justifySm, justifyMd })
76
100
 
77
101
  const overlayRef = useRef<HTMLDivElement>(null)
78
102
 
79
103
  const scroll = useElementScroll(scrollerRef)
80
104
 
105
+ // When the component is mounted, we need to set the initial position of the overlay.
81
106
  useIsomorphicLayoutEffect(() => {
82
107
  const scroller = scrollerRef.current
83
- if (!scroller || !isPresent) return
108
+ if (!scroller || !isPresent) return undefined
84
109
 
85
110
  const open = { x: positions.open.x.get(), y: positions.open.y.get() }
86
111
 
112
+ document.body.style.overflow = 'hidden'
113
+
87
114
  if (direction === 1 && position.get() !== OverlayPosition.OPENED) {
88
115
  scroller.scrollLeft = positions.closed.x.get()
89
116
  scroller.scrollTop = positions.closed.y.get()
@@ -97,6 +124,8 @@ export function LayoutOverlayBase(props: LayoutOverlayBaseProps) {
97
124
  scroller.scrollLeft = open.x
98
125
  scroller.scrollTop = open.y
99
126
  }
127
+
128
+ return clearScrollLock
100
129
  }, [direction, isPresent, position, positions, scrollTo, scrollerRef])
101
130
 
102
131
  // Make sure the overlay stays open when resizing the window.
@@ -121,6 +150,7 @@ export function LayoutOverlayBase(props: LayoutOverlayBaseProps) {
121
150
  useEffect(() => {
122
151
  if (isPresent) return
123
152
  position.set(OverlayPosition.CLOSED)
153
+ clearScrollLock()
124
154
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
125
155
  scrollTo({
126
156
  x: positions.closed.x.get(),
@@ -132,6 +162,7 @@ export function LayoutOverlayBase(props: LayoutOverlayBaseProps) {
132
162
  const closeOverlay = useCallback(() => {
133
163
  if (position.get() !== OverlayPosition.OPENED) return
134
164
  position.set(OverlayPosition.CLOSED)
165
+ clearScrollLock()
135
166
  close()
136
167
  }, [close, position])
137
168
 
@@ -170,11 +201,15 @@ export function LayoutOverlayBase(props: LayoutOverlayBaseProps) {
170
201
  [closeOverlay, scrollerRef, snap],
171
202
  )
172
203
 
204
+ const pointerEvents = useTransform(position, (p) =>
205
+ p === OverlayPosition.CLOSED ? 'none' : 'auto',
206
+ )
207
+
173
208
  return (
174
209
  <>
175
210
  <MotionDiv
176
211
  className={classes.backdrop}
177
- style={{ opacity: positions.open.visible }}
212
+ style={{ opacity: positions.open.visible, pointerEvents }}
178
213
  sx={[
179
214
  {
180
215
  zIndex: -1,
@@ -198,8 +233,10 @@ export function LayoutOverlayBase(props: LayoutOverlayBaseProps) {
198
233
  grid={false}
199
234
  hideScrollbar
200
235
  onClick={onClickAway}
236
+ style={{ pointerEvents }}
201
237
  sx={[
202
238
  (theme) => ({
239
+ overscrollBehavior: 'contain',
203
240
  display: 'grid',
204
241
  '&.canGrab': {
205
242
  cursor: 'default',
@@ -208,10 +245,7 @@ export function LayoutOverlayBase(props: LayoutOverlayBaseProps) {
208
245
  overflow: 'auto',
209
246
  },
210
247
 
211
- height: '100vh',
212
- '@supports (-webkit-touch-callout: none)': {
213
- height: '-webkit-fill-available',
214
- },
248
+ height: clientSizeCssVar.y,
215
249
 
216
250
  [theme.breakpoints.down('md')]: {
217
251
  '&.variantSmLeft': {
@@ -228,10 +262,7 @@ export function LayoutOverlayBase(props: LayoutOverlayBaseProps) {
228
262
  borderTopLeftRadius: theme.shape.borderRadius * 3,
229
263
  borderTopRightRadius: theme.shape.borderRadius * 3,
230
264
  gridTemplate: `"beforeOverlay" "overlay"`,
231
- height: '100vh',
232
- '@supports (-webkit-touch-callout: none)': {
233
- height: '-webkit-fill-available',
234
- },
265
+ height: clientSizeCssVar.y,
235
266
  },
236
267
  },
237
268
  [theme.breakpoints.up('md')]: {
@@ -250,7 +281,7 @@ export function LayoutOverlayBase(props: LayoutOverlayBaseProps) {
250
281
  borderTopRightRadius: theme.shape.borderRadius * 4,
251
282
  [theme.breakpoints.up('md')]: {
252
283
  gridTemplate: `"beforeOverlay" "overlay"`,
253
- height: '100vh',
284
+ height: clientSizeCssVar.y,
254
285
  },
255
286
  },
256
287
  },
@@ -273,10 +304,7 @@ export function LayoutOverlayBase(props: LayoutOverlayBaseProps) {
273
304
  width: '100vw',
274
305
  },
275
306
  '&.variantSmBottom': {
276
- height: '100vh',
277
- '@supports (-webkit-touch-callout: none)': {
278
- height: '-webkit-fill-available',
279
- },
307
+ height: clientSizeCssVar.y,
280
308
  },
281
309
  },
282
310
  [theme.breakpoints.up('md')]: {
@@ -284,7 +312,7 @@ export function LayoutOverlayBase(props: LayoutOverlayBaseProps) {
284
312
  width: '100vw',
285
313
  },
286
314
  '&.variantMdBottom': {
287
- height: '100vh',
315
+ height: clientSizeCssVar.y,
288
316
  },
289
317
  },
290
318
  })}
@@ -297,14 +325,15 @@ export function LayoutOverlayBase(props: LayoutOverlayBaseProps) {
297
325
  pointerEvents: 'none',
298
326
  gridArea: 'overlay',
299
327
  scrollSnapAlign: 'start',
328
+ scrollSnapStop: 'always',
300
329
 
301
330
  [theme.breakpoints.down('md')]: {
302
331
  justifyContent: justifySm,
303
332
  alignItems: justifySm,
304
333
 
305
334
  '&.variantSmBottom': {
306
- marginTop: `calc(${theme.appShell.headerHeightSm} * 0.5 * -1)`,
307
- paddingTop: `calc(${theme.appShell.headerHeightSm} * 0.5)`,
335
+ marginTop: `calc(${smSpacingTop} * -1)`,
336
+ paddingTop: smSpacingTop,
308
337
  },
309
338
 
310
339
  '&.sizeSmFloating': {
@@ -316,8 +345,8 @@ export function LayoutOverlayBase(props: LayoutOverlayBaseProps) {
316
345
  alignItems: justifyMd,
317
346
 
318
347
  '&.variantMdBottom': {
319
- marginTop: `calc(${theme.appShell.headerHeightMd} + (${theme.appShell.appBarHeightMd} - ${theme.appShell.appBarInnerHeightMd}) * 0.5)`,
320
- paddingTop: `calc(${theme.appShell.headerHeightMd} + (${theme.appShell.appBarHeightMd} - ${theme.appShell.appBarInnerHeightMd}) * -0.5)`,
348
+ paddingTop: mdSpacingTop,
349
+ marginTop: `calc(${mdSpacingTop} * -1)`,
321
350
  display: 'grid',
322
351
  },
323
352
  '&.sizeMdFloating': {
@@ -332,22 +361,19 @@ export function LayoutOverlayBase(props: LayoutOverlayBaseProps) {
332
361
  pointerEvents: 'all',
333
362
  backgroundColor: theme.palette.background.paper,
334
363
  boxShadow: theme.shadows[24],
335
- // scrollSnapAlign: 'end',
364
+ scrollSnapAlign: 'end',
336
365
  [theme.breakpoints.down('md')]: {
337
366
  minWidth: '80vw',
367
+ '&:not(.sizeMdFull)': {
368
+ width: 'max-content',
369
+ },
338
370
 
339
- /**
340
- * The top bar on Google Chrome is about 56 pixels high. If we do not provide this
341
- * padding we'll run into the issue that the user can't scroll to the bottom. We
342
- * can't change this value with JS as that causes much jank
343
- */
344
- '&.sizeSmFull, &.sizeSmMinimal': { paddingBottom: '56px' },
345
371
  '&.variantSmBottom.sizeSmFull': {
346
- minHeight: `calc(100vh - ${theme.appShell.headerHeightSm} * 0.5)`,
372
+ minHeight: `calc(${clientSizeCssVar.y} - ${smSpacingTop})`,
347
373
  },
348
374
 
349
375
  '&.variantSmBottom': {
350
- borderTopLeftRadius: `${theme.shape.borderRadius * 4}px`,
376
+ borderTopLeftRadius: `${theme.shape.borderRadius * 3}px`,
351
377
  borderTopRightRadius: `${theme.shape.borderRadius * 3}px`,
352
378
  },
353
379
  '&.sizeSmFloating': {
@@ -355,42 +381,33 @@ export function LayoutOverlayBase(props: LayoutOverlayBaseProps) {
355
381
  },
356
382
  '&.variantSmLeft.sizeSmFull': {
357
383
  paddingBottom: '1px',
358
- minHeight: '100vh',
359
- '@supports (-webkit-touch-callout: none)': {
360
- minHeight: '-webkit-fill-available',
361
- },
384
+ minHeight: clientSizeCssVar.y,
362
385
  },
363
386
  '&.variantSmRight.sizeSmFull': {
364
387
  paddingBottom: '1px',
365
- minHeight: '100vh',
388
+ minHeight: clientSizeCssVar.y,
366
389
  scrollSnapAlign: 'end',
367
- '@supports (-webkit-touch-callout: none)': {
368
- minHeight: '-webkit-fill-available',
369
- },
370
390
  },
371
391
  },
372
392
  [theme.breakpoints.up('md')]: {
373
393
  '&.sizeMdFull': {
374
394
  minWidth: 'max(600px, 50vw)',
375
395
  },
396
+ '&:not(.sizeMdFull)': {
397
+ width: 'max-content',
398
+ },
376
399
 
377
400
  '&.sizeMdFull.variantMdBottom': {
378
- minHeight: `calc(100vh + ${theme.appShell.headerHeightMd} - (${theme.appShell.appBarHeightMd} - ${theme.appShell.appBarInnerHeightMd}) * 0.5)`,
401
+ minHeight: `calc(${clientSizeCssVar.y} - ${mdSpacingTop})`,
379
402
  },
380
403
  '&.sizeMdFull.variantMdLeft': {
381
404
  paddingBottom: '1px',
382
- minHeight: '100vh',
383
- '@supports (-webkit-touch-callout: none)': {
384
- minHeight: '-webkit-fill-available',
385
- },
405
+ minHeight: clientSizeCssVar.y,
386
406
  },
387
407
  '&.sizeMdFull.variantMdRight': {
388
408
  paddingBottom: '1px',
389
- minHeight: '100vh',
409
+ minHeight: clientSizeCssVar.y,
390
410
  scrollSnapAlign: 'end',
391
- '@supports (-webkit-touch-callout: none)': {
392
- minHeight: '-webkit-fill-available',
393
- },
394
411
  },
395
412
 
396
413
  '&.variantMdBottom': {
@@ -1,23 +1,14 @@
1
- import { ParsedUrlQuery } from 'querystring'
2
- import { useRouter } from 'next/router'
3
1
  import { useCallback } from 'react'
2
+ import { useUrlQuery } from '../../useUrlQuery/useUrlQuery'
4
3
  import { LayoutOverlay, LayoutOverlayProps } from '../components/LayoutOverlay'
5
4
 
6
- export type LayoutOverlayState = Omit<LayoutOverlayProps, 'children' | 'sx' | 'sxBackdrop'>
7
-
8
- function useQueryState<T extends ParsedUrlQuery>(builder: (query: T) => T) {
9
- const { query, replace } = useRouter()
10
- const queryState = builder(query as T)
11
-
12
- const setRouterQuery = (partialQuery: T) => {
13
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
14
- replace({ query: { ...queryState, ...partialQuery } }, undefined, { shallow: true })
15
- }
16
- return [queryState, setRouterQuery] as const
17
- }
5
+ export type LayoutOverlayState = Omit<
6
+ LayoutOverlayProps,
7
+ 'children' | 'sx' | 'sxBackdrop' | 'mdSpacingTop' | 'smSpacingTop'
8
+ >
18
9
 
19
10
  export function useLayoutState() {
20
- const [routerQuery, setRouterQuery] = useQueryState<LayoutOverlayState>(
11
+ const [routerQuery, setRouterQuery] = useUrlQuery<LayoutOverlayState>(
21
12
  useCallback(
22
13
  ({ sizeMd, sizeSm, justifyMd, justifySm, variantMd, variantSm }) => ({
23
14
  sizeMd,
@@ -65,7 +65,12 @@ export function useCanonical(incomming?: Canonical) {
65
65
 
66
66
  href = localeDomain || addBasePath(addLocale(as, curLocale, router && router.defaultLocale))
67
67
 
68
- canonical = `${process.env.NEXT_PUBLIC_SITE_URL}${href}`
68
+ let siteUrl = process.env.NEXT_PUBLIC_SITE_URL
69
+ if (siteUrl && siteUrl.endsWith('/')) {
70
+ siteUrl = siteUrl.slice(0, -1)
71
+ }
72
+
73
+ canonical = `${siteUrl}${href}`
69
74
  }
70
75
 
71
76
  if (!canonical.startsWith('http')) {
@@ -0,0 +1,24 @@
1
+ import { Trans } from '@lingui/macro'
2
+ import { Button } from '@mui/material'
3
+ import { MessageSnackbar } from './MessageSnackbar'
4
+ import { MessageSnackbarImplProps } from './MessageSnackbarImpl'
5
+
6
+ export type ErrorSnackbarProps = MessageSnackbarImplProps
7
+
8
+ export function ErrorSnackbar(props: ErrorSnackbarProps) {
9
+ const { action, ...passedProps } = props
10
+ return (
11
+ <MessageSnackbar
12
+ variant='pill'
13
+ severity='error'
14
+ {...passedProps}
15
+ action={
16
+ action ?? (
17
+ <Button size='medium' variant='pill' color='secondary'>
18
+ <Trans>Ok</Trans>
19
+ </Button>
20
+ )
21
+ }
22
+ />
23
+ )
24
+ }
@@ -41,7 +41,7 @@ export function withTheme<T>(
41
41
  theme: Theme,
42
42
  ): React.FC<T & WithSx> {
43
43
  return (data: T & WithSx) => {
44
- const sx = data.sx ?? []
44
+ const { sx = [] } = data
45
45
  return (
46
46
  <ThemeProvider theme={theme}>
47
47
  <Component
@@ -42,7 +42,7 @@ export function TextInputNumber(props: TextInputNumberProps) {
42
42
  const classes = withState({})
43
43
 
44
44
  const ref = useRef<HTMLInputElement>(null)
45
- const forkRef = useForkRef<HTMLInputElement>(ref, inputRef as Ref<HTMLInputElement>)
45
+ const forkRef = useForkRef(ref, inputRef as Ref<HTMLInputElement>)
46
46
 
47
47
  const [direction, setDirection] = useState<'up' | 'down' | 'runUp' | 'runDown' | null>(null)
48
48
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
package/index.ts CHANGED
@@ -22,6 +22,7 @@ export * from './FullPageMessage/FullPageMessage'
22
22
  export * from './Highlight/Highlight'
23
23
  export * from './IconHeader/IconHeader'
24
24
  export * from './icons'
25
+ export * from './IconSvg'
25
26
  export * from './JsonLd/JsonLd'
26
27
  export * from './Layout'
27
28
  export * from './LayoutDefault'
@@ -37,15 +38,16 @@ export * from './SectionContainer/SectionContainer'
37
38
  export * from './SectionHeader/SectionHeader'
38
39
  export * from './Separator/Separator'
39
40
  export * from './Snackbar/MessageSnackbar'
41
+ export * from './Snackbar/ErrorSnackbar'
40
42
  export * from './Snackbar/MessageSnackbarImpl'
41
43
  export * from './StarRatingField/StarRatingField'
42
44
  export * from './Stepper/Stepper'
43
45
  export * from './Styles'
44
- export * from './IconSvg'
45
46
  export * from './TextInputNumber/TextInputNumber'
46
47
  export * from './Theme'
47
48
  export * from './TimeAgo/TimeAgo'
48
49
  export * from './ToggleButton/ToggleButton'
49
50
  export * from './ToggleButtonGroup/ToggleButtonGroup'
51
+ export * from './useUrlQuery/useUrlQuery'
50
52
  export * from './UspList/UspList'
51
53
  export * from './UspList/UspListItem'
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@graphcommerce/next-ui",
3
3
  "homepage": "https://www.graphcommerce.org/",
4
4
  "repository": "github:graphcommerce-org/graphcommerce",
5
- "version": "4.3.1",
5
+ "version": "4.5.0",
6
6
  "author": "",
7
7
  "license": "MIT",
8
8
  "sideEffects": false,
@@ -19,14 +19,13 @@
19
19
  "@emotion/react": "^11.8.2",
20
20
  "@emotion/server": "^11.4.0",
21
21
  "@emotion/styled": "^11.6.0",
22
- "@graphcommerce/framer-next-pages": "^3.1.2",
23
- "@graphcommerce/framer-scroller": "^2.1.0",
24
- "@graphcommerce/framer-utils": "^3.0.5",
25
- "@graphcommerce/image": "^3.1.1",
26
- "react-is": "^17.0.2",
22
+ "@graphcommerce/framer-next-pages": "^3.1.4",
23
+ "@graphcommerce/framer-scroller": "^2.1.3",
24
+ "@graphcommerce/framer-utils": "^3.1.1",
25
+ "@graphcommerce/image": "^3.1.2",
26
+ "react-is": "^18.0.0",
27
27
  "react-schemaorg": "^2.0.0",
28
- "schema-dts": "^1.1.0",
29
- "type-fest": "^2.12.0"
28
+ "schema-dts": "^1.1.0"
30
29
  },
31
30
  "peerDependencies": {
32
31
  "@lingui/macro": "^3.13.2",
@@ -38,11 +37,12 @@
38
37
  "react-dom": "^17.0.2"
39
38
  },
40
39
  "devDependencies": {
41
- "@graphcommerce/eslint-config-pwa": "^4.0.6",
42
- "@graphcommerce/prettier-config-pwa": "^4.0.4",
40
+ "@graphcommerce/eslint-config-pwa": "^4.1.3",
41
+ "@graphcommerce/prettier-config-pwa": "^4.0.5",
43
42
  "@graphcommerce/typescript-config-pwa": "^4.0.2",
44
- "@playwright/test": "^1.19.2",
43
+ "@playwright/test": "^1.20.1",
45
44
  "@types/react-is": "^17.0.3",
46
- "typescript": "^4.6.2"
45
+ "type-fest": "2.12.1",
46
+ "typescript": "4.6.3"
47
47
  }
48
48
  }
@@ -0,0 +1,19 @@
1
+ import { ParsedUrlQuery } from 'querystring'
2
+ import { useRouter } from 'next/router'
3
+ import { useCallback } from 'react'
4
+
5
+ export function useUrlQuery<T extends ParsedUrlQuery>(builder: (query: T) => T = (query) => query) {
6
+ const { query, replace } = useRouter()
7
+ const queryState = builder(query as T)
8
+
9
+ const setRouterQuery = useCallback(
10
+ (partialQuery: T) => {
11
+ if (JSON.stringify(queryState) === JSON.stringify(partialQuery)) return
12
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
13
+ replace({ query: { ...queryState, ...partialQuery } }, undefined, { shallow: true })
14
+ },
15
+ [queryState, replace],
16
+ )
17
+
18
+ return [queryState, setRouterQuery] as const
19
+ }