@graphcommerce/next-ui 4.1.5 → 4.2.2

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,64 @@
1
1
  # Change Log
2
2
 
3
+ ## 4.2.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [#1296](https://github.com/ho-nl/m2-pwa/pull/1296)
8
+ [`a9cff2ce6`](https://github.com/ho-nl/m2-pwa/commit/a9cff2ce63fce5b86e9fd6bf63c10c782326d50e)
9
+ Thanks [@paales](https://github.com/paales)! - Make sure the page is max height when no menuFab or
10
+ cartFab is present
11
+
12
+ * [#1296](https://github.com/ho-nl/m2-pwa/pull/1296)
13
+ [`8473123fa`](https://github.com/ho-nl/m2-pwa/commit/8473123fa7d3f3eb1d282d9b4205c803a88010ea)
14
+ Thanks [@paales](https://github.com/paales)! - implement handling for canonical URLs based on
15
+ NEXT_PUBLIC_SITE_URL
16
+
17
+ - [#1296](https://github.com/ho-nl/m2-pwa/pull/1296)
18
+ [`50e205c51`](https://github.com/ho-nl/m2-pwa/commit/50e205c51f4d0d67d41d22fd70e8ed9a0996489e)
19
+ Thanks [@paales](https://github.com/paales)! - make sure the scroll performance of galleries in
20
+ safari is better
21
+
22
+ ## 4.2.1
23
+
24
+ ### Patch Changes
25
+
26
+ - [#1294](https://github.com/ho-nl/m2-pwa/pull/1294)
27
+ [`19f33e0aa`](https://github.com/ho-nl/m2-pwa/commit/19f33e0aaf4e3121edd444926d08b6459d3ef400)
28
+ Thanks [@paales](https://github.com/paales)! - Make sure the minHeight of overlays always have the
29
+ correct height, even if the content changes size
30
+
31
+ * [#1294](https://github.com/ho-nl/m2-pwa/pull/1294)
32
+ [`aea787542`](https://github.com/ho-nl/m2-pwa/commit/aea787542484a0480a48031fcc4a9a5566c6bfc7)
33
+ Thanks [@paales](https://github.com/paales)! - Make sure the labels of LayoutHeaderBack/Close
34
+ aren’t rendered on mobile
35
+
36
+ * Updated dependencies
37
+ [[`4e28c8afd`](https://github.com/ho-nl/m2-pwa/commit/4e28c8afd9cead3577dd0eff97b5c44ba4c1c862),
38
+ [`afb993244`](https://github.com/ho-nl/m2-pwa/commit/afb993244aabc8135ce54a79743cbf63bc5677d3)]:
39
+ - @graphcommerce/framer-scroller@2.0.5
40
+ - @graphcommerce/framer-next-pages@3.1.1
41
+
42
+ ## 4.2.0
43
+
44
+ ### Minor Changes
45
+
46
+ - [#1292](https://github.com/ho-nl/m2-pwa/pull/1292)
47
+ [`63f9b56eb`](https://github.com/ho-nl/m2-pwa/commit/63f9b56eb68ba790567ff1427e599fd2c3c8f1ee)
48
+ Thanks [@paales](https://github.com/paales)! - added responsive size to the Fab component
49
+
50
+ ### Patch Changes
51
+
52
+ - [#1292](https://github.com/ho-nl/m2-pwa/pull/1292)
53
+ [`5a1ba9e66`](https://github.com/ho-nl/m2-pwa/commit/5a1ba9e664abbac89c4f5f71f7d6d6ed1aefa5c0)
54
+ Thanks [@paales](https://github.com/paales)! - Renamed SvgIcon to IconSvg to prevent collisions
55
+ with MUI
56
+
57
+ * [#1292](https://github.com/ho-nl/m2-pwa/pull/1292)
58
+ [`990df655b`](https://github.com/ho-nl/m2-pwa/commit/990df655b73b469718d6cb5837ee65dfe2ad6a1d)
59
+ Thanks [@paales](https://github.com/paales)! - `<SearchLink />` a more lightweight (less js)
60
+ alternative for `<SearchButton />`
61
+
3
62
  ## 4.1.5
4
63
 
5
64
  ### Patch Changes
@@ -1,9 +1,9 @@
1
1
  import { Chip, ChipProps, Menu, MenuProps, menuClasses, SxProps, Theme } from '@mui/material'
2
2
  import React, { PropsWithChildren, useState } from 'react'
3
+ import { IconSvg } from '../IconSvg'
3
4
  import { SectionHeader } from '../SectionHeader'
4
5
  import { extendableComponent } from '../Styles'
5
6
  import { responsiveVal } from '../Styles/responsiveVal'
6
- import { SvgIcon } from '../SvgIcon/SvgIcon'
7
7
  import { iconChevronDown, iconChevronUp, iconCancelAlt } from '../icons'
8
8
 
9
9
  const { classes, selectors } = extendableComponent('FilterEqual', ['chip'] as const)
@@ -32,9 +32,9 @@ export function ChipMenu(props: ChipMenuProps) {
32
32
 
33
33
  const [openEl, setOpenEl] = useState<null | HTMLElement>(null)
34
34
 
35
- let deleteIcon = <SvgIcon src={iconChevronDown} size='medium' />
36
- if (selected) deleteIcon = <SvgIcon src={iconCancelAlt} size='medium' fillIcon />
37
- if (openEl) deleteIcon = <SvgIcon src={iconChevronUp} size='medium' />
35
+ let deleteIcon = <IconSvg src={iconChevronDown} size='medium' />
36
+ if (selected) deleteIcon = <IconSvg src={iconCancelAlt} size='medium' fillIcon />
37
+ if (openEl) deleteIcon = <IconSvg src={iconChevronUp} size='medium' />
38
38
 
39
39
  const selectedAndMenuHidden = selected && !openEl && !!selectedLabel
40
40
 
@@ -1,8 +1,11 @@
1
- import { SvgIconProps } from '@mui/material'
2
- import { SvgIcon } from '../SvgIcon/SvgIcon'
1
+ import { IconSvg, IconSvgProps } from '../IconSvg'
3
2
  import { iconCheckmark } from '../icons'
4
3
 
5
- export type InputCheckmarkProps = { show?: boolean; select?: boolean } & Omit<SvgIconProps, 'src'>
4
+ export type InputCheckmarkProps = {
5
+ show?: boolean
6
+ select?: boolean
7
+ children?: React.ReactNode
8
+ } & Omit<IconSvgProps, 'src'>
6
9
 
7
10
  /**
8
11
  * When the `valid` prop is passed it will render a CheckIcon, else it will render children.
@@ -16,7 +19,7 @@ export function InputCheckmark(props: InputCheckmarkProps) {
16
19
 
17
20
  if (!show) return <>{children}</>
18
21
  return (
19
- <SvgIcon
22
+ <IconSvg
20
23
  src={iconCheckmark}
21
24
  className='InputCheckmark'
22
25
  sx={[{ stroke: '#01D26A' }, select && { marginRight: '15px' }]}
@@ -12,10 +12,10 @@ import { Fab, useTheme, alpha, Box, styled, SxProps, Theme } from '@mui/material
12
12
  import { m, useDomEvent, useMotionValue } from 'framer-motion'
13
13
  import { useRouter } from 'next/router'
14
14
  import React, { useEffect, useRef } from 'react'
15
+ import { IconSvg } from '../IconSvg'
15
16
  import { Row } from '../Row'
16
17
  import { extendableComponent } from '../Styles'
17
18
  import { responsiveVal } from '../Styles/responsiveVal'
18
- import { SvgIcon } from '../SvgIcon/SvgIcon'
19
19
  import { iconChevronLeft, iconChevronRight, iconFullscreen, iconFullscreenExit } from '../icons'
20
20
 
21
21
  const MotionBox = styled(m.div)({})
@@ -227,7 +227,7 @@ export function SidebarGallery(props: SidebarGalleryProps) {
227
227
  boxShadow: theme.shadows[6],
228
228
  }}
229
229
  >
230
- {!zoomed ? <SvgIcon src={iconFullscreen} /> : <SvgIcon src={iconFullscreenExit} />}
230
+ {!zoomed ? <IconSvg src={iconFullscreen} /> : <IconSvg src={iconFullscreenExit} />}
231
231
  </Fab>
232
232
  </MotionBox>
233
233
  <Box
@@ -247,7 +247,7 @@ export function SidebarGallery(props: SidebarGalleryProps) {
247
247
  size='small'
248
248
  className={classes.sliderButtons}
249
249
  >
250
- <SvgIcon src={iconChevronLeft} />
250
+ <IconSvg src={iconChevronLeft} />
251
251
  </ScrollerButton>
252
252
  </Box>
253
253
  <Box
@@ -266,7 +266,7 @@ export function SidebarGallery(props: SidebarGalleryProps) {
266
266
  size='small'
267
267
  className={classes.sliderButtons}
268
268
  >
269
- <SvgIcon src={iconChevronRight} />
269
+ <IconSvg src={iconChevronRight} />
270
270
  </ScrollerButton>
271
271
  </Box>
272
272
 
@@ -7,10 +7,10 @@ import {
7
7
  } from '@graphcommerce/framer-scroller'
8
8
  import { Box, SxProps, Theme } from '@mui/material'
9
9
  import { ReactNode } from 'react'
10
+ import { IconSvg } from '../IconSvg'
10
11
  import { Row } from '../Row'
11
12
  import { extendableComponent } from '../Styles/extendableComponent'
12
13
  import { responsiveVal } from '../Styles/responsiveVal'
13
- import { SvgIcon } from '../SvgIcon/SvgIcon'
14
14
  import { iconChevronLeft, iconChevronRight } from '../icons'
15
15
 
16
16
  const { classes, selectors } = extendableComponent('SidebarSlider', [
@@ -87,7 +87,7 @@ export function SidebarSlider(props: SidebarSliderProps) {
87
87
  sx={{ display: { xs: 'none', md: 'flex' } }}
88
88
  size={buttonSize}
89
89
  >
90
- <SvgIcon src={iconChevronLeft} />
90
+ <IconSvg src={iconChevronLeft} />
91
91
  </ScrollerButton>
92
92
  </Box>
93
93
  <Box
@@ -106,7 +106,7 @@ export function SidebarSlider(props: SidebarSliderProps) {
106
106
  sx={{ display: { xs: 'none', md: 'flex' } }}
107
107
  size={buttonSize}
108
108
  >
109
- <SvgIcon src={iconChevronRight} />
109
+ <IconSvg src={iconChevronRight} />
110
110
  </ScrollerButton>
111
111
  </Box>
112
112
  </Box>
@@ -1,6 +1,6 @@
1
1
  import { Box, SxProps, Theme, Typography } from '@mui/material'
2
+ import { IconSvg, IconSvgProps } from '../IconSvg'
2
3
  import { extendableComponent } from '../Styles'
3
- import { SvgIcon, SvgIconProps } from '../SvgIcon/SvgIcon'
4
4
 
5
5
  // TODO: remove all occurrences. deprecated component
6
6
 
@@ -13,7 +13,7 @@ type IconHeaderProps = {
13
13
  stayInline?: boolean
14
14
  ellipsis?: boolean
15
15
  sx?: SxProps<Theme>
16
- } & Pick<SvgIconProps, 'src'>
16
+ } & Pick<IconSvgProps, 'src'>
17
17
 
18
18
  type IconHeaderHeadings = 'h2' | 'h4' | 'h5'
19
19
 
@@ -71,7 +71,7 @@ export function IconHeader(props: IconHeaderProps) {
71
71
  },
72
72
  ]}
73
73
  >
74
- <SvgIcon src={src} />
74
+ <IconSvg src={src} />
75
75
  <Typography
76
76
  variant={variants[size]}
77
77
  component='h2'
@@ -1,11 +1,11 @@
1
1
  import { ImageProps, srcToString, StaticImport } from '@graphcommerce/image'
2
- import { styled, SxProps, Theme, useTheme } from '@mui/material'
2
+ import { styled, SxProps, Theme, useTheme, useThemeProps } from '@mui/material'
3
3
  import { ComponentProps, forwardRef } from 'react'
4
4
  import { extendableComponent, ExtendableComponent } from '../Styles/extendableComponent'
5
5
  import { responsiveVal as rv } from '../Styles/responsiveVal'
6
6
  import { svgIconStrokeWidth } from './svgIconStrokeWidth'
7
7
 
8
- const name = 'SvgIcon'
8
+ const name = 'IconSvg'
9
9
  const parts = ['root'] as const
10
10
  type StyleProps = {
11
11
  size?: 'default' | 'inherit' | 'xxl' | 'xl' | 'large' | 'medium' | 'small' | 'xs'
@@ -16,7 +16,7 @@ const { withState } = extendableComponent<StyleProps, typeof name, typeof parts>
16
16
  /** Expose the component to be exendable in your theme.components */
17
17
  declare module '@mui/material/styles/components' {
18
18
  interface Components {
19
- SvgIcon?: ExtendableComponent<StyleProps> & {
19
+ IconSvg?: ExtendableComponent<StyleProps> & {
20
20
  /**
21
21
  * To override an icon with your own icon, provide the original src and the replacement src.
22
22
  *
@@ -36,7 +36,7 @@ declare module '@mui/material/styles/components' {
36
36
  }
37
37
  }
38
38
 
39
- export type SvgIconProps = StyleProps &
39
+ export type IconSvgProps = StyleProps &
40
40
  Pick<ImageProps, 'src'> &
41
41
  Pick<ComponentProps<'svg'>, 'className' | 'style'> & { sx?: SxProps<Theme> }
42
42
 
@@ -73,15 +73,15 @@ const Svg = styled('svg', { name, target: name })(() => [
73
73
  ])
74
74
 
75
75
  /**
76
- * SvgIcon component is supposed to be used in combination with `icons`
76
+ * IconSvg component is supposed to be used in combination with `icons`
77
77
  *
78
78
  * @see https://graphcommerce-docs.vercel.app/framework/icons
79
79
  */
80
- export const SvgIcon = forwardRef<SVGSVGElement, SvgIconProps>((props, ref) => {
81
- const { src, size, fillIcon, className, ...svgProps } = props
80
+ export const IconSvg = forwardRef<SVGSVGElement, IconSvgProps>((props, ref) => {
81
+ const { src, size, fillIcon, className, ...svgProps } = useThemeProps({ props, name })
82
82
 
83
83
  const srcWithOverride =
84
- (useTheme().components?.SvgIcon?.overrides ?? []).find(
84
+ (useTheme().components?.IconSvg?.overrides ?? []).find(
85
85
  ([overrideSrc]) => overrideSrc === src,
86
86
  )?.[1] ?? src
87
87
 
@@ -98,4 +98,7 @@ export const SvgIcon = forwardRef<SVGSVGElement, SvgIconProps>((props, ref) => {
98
98
  </Svg>
99
99
  )
100
100
  })
101
- SvgIcon.displayName = 'SvgIcon'
101
+ IconSvg.displayName = 'IconSvg'
102
+
103
+ /** @deprecated SvgIcon is renamed to IconSvg, no API changes */
104
+ export const SvgIcon = IconSvg
@@ -1,2 +1,2 @@
1
- export * from './SvgIcon'
1
+ export * from './IconSvg'
2
2
  export * from './svgIconStrokeWidth'
File without changes
@@ -1,9 +1,10 @@
1
1
  import { useUp, usePrevUp, usePageContext } from '@graphcommerce/framer-next-pages'
2
2
  import { t } from '@lingui/macro'
3
+ import { Box } from '@mui/material'
3
4
  import PageLink from 'next/link'
4
5
  import { useRouter } from 'next/router'
5
6
  import { LinkOrButton, LinkOrButtonProps } from '../../Button/LinkOrButton'
6
- import { SvgIcon } from '../../SvgIcon/SvgIcon'
7
+ import { IconSvg } from '../../IconSvg'
7
8
  import { iconChevronLeft } from '../../icons'
8
9
 
9
10
  export type BackProps = Omit<LinkOrButtonProps, 'onClick' | 'children'>
@@ -28,7 +29,7 @@ export default function LayoutHeaderBack(props: BackProps) {
28
29
  const prevUp = usePrevUp()
29
30
  const { backSteps } = usePageContext()
30
31
 
31
- const backIcon = <SvgIcon src={iconChevronLeft} size='medium' />
32
+ const backIcon = <IconSvg src={iconChevronLeft} size='medium' />
32
33
  const canClickBack = backSteps > 0 && path !== prevUp?.href
33
34
 
34
35
  let label = t`Back`
@@ -45,7 +46,9 @@ export default function LayoutHeaderBack(props: BackProps) {
45
46
  aria-label={label}
46
47
  {...props}
47
48
  >
48
- {label}
49
+ <Box component='span' sx={{ display: { xs: 'none', md: 'inline' } }}>
50
+ {label}
51
+ </Box>
49
52
  </LinkOrButton>
50
53
  )
51
54
  }
@@ -60,7 +63,9 @@ export default function LayoutHeaderBack(props: BackProps) {
60
63
  color='inherit'
61
64
  {...props}
62
65
  >
63
- {up.title}
66
+ <Box component='span' sx={{ display: { xs: 'none', md: 'inline' } }}>
67
+ {up.title}
68
+ </Box>
64
69
  </LinkOrButton>
65
70
  </PageLink>
66
71
  )
@@ -1,7 +1,8 @@
1
1
  import { useGo, usePageContext } from '@graphcommerce/framer-next-pages'
2
2
  import { Trans } from '@lingui/macro'
3
+ import { Box } from '@mui/material'
3
4
  import { LinkOrButton } from '../../Button/LinkOrButton'
4
- import { SvgIcon } from '../../SvgIcon/SvgIcon'
5
+ import { IconSvg } from '../../IconSvg'
5
6
  import { iconClose } from '../../icons'
6
7
 
7
8
  export function useShowClose() {
@@ -19,10 +20,12 @@ export default function LayoutHeaderClose() {
19
20
  color='inherit'
20
21
  onClick={onClick}
21
22
  aria-label='Close'
22
- startIcon={<SvgIcon src={iconClose} />}
23
+ startIcon={<IconSvg src={iconClose} size='medium' />}
23
24
  // className={classes.close}
24
25
  >
25
- <Trans>Close</Trans>
26
+ <Box component='span' sx={{ display: { xs: 'none', md: 'inline' } }}>
27
+ <Trans>Close</Trans>
28
+ </Box>
26
29
  </LinkOrButton>
27
30
  )
28
31
  }
@@ -1,7 +1,7 @@
1
1
  import { Box, SxProps, Theme, Typography, TypographyProps } from '@mui/material'
2
2
  import React from 'react'
3
+ import { IconSvg, IconSvgProps } from '../../IconSvg'
3
4
  import { extendableComponent, responsiveVal } from '../../Styles'
4
- import { SvgIcon, SvgIconProps } from '../../SvgIcon/SvgIcon'
5
5
 
6
6
  type OwnerState = {
7
7
  size?: 'small' | 'medium'
@@ -17,7 +17,7 @@ const { withState } = extendableComponent<OwnerState, 'LayoutTitle', typeof part
17
17
 
18
18
  export type TitleProps = {
19
19
  children: React.ReactNode
20
- icon?: SvgIconProps['src']
20
+ icon?: IconSvgProps['src']
21
21
  variant?: TypographyProps['variant']
22
22
  component?: React.ElementType
23
23
  sx?: SxProps<Theme>
@@ -69,7 +69,7 @@ export const LayoutTitle = React.forwardRef<HTMLDivElement, TitleProps>((props,
69
69
  ]}
70
70
  >
71
71
  {icon && (
72
- <SvgIcon src={icon} size={size === 'small' ? 'large' : 'xl'} className={classes.icon} />
72
+ <IconSvg src={icon} size={size === 'small' ? 'large' : 'xl'} className={classes.icon} />
73
73
  )}
74
74
  <Typography
75
75
  ref={ref}
@@ -3,6 +3,7 @@ import { Box, SxProps, Theme } from '@mui/material'
3
3
  import { useTransform, useViewportScroll } from 'framer-motion'
4
4
  import LayoutProvider from '../../Layout/components/LayoutProvider'
5
5
  import { extendableComponent, responsiveVal } from '../../Styles'
6
+ import { useFabSize } from '../../Theme'
6
7
 
7
8
  export type LayoutDefaultProps = {
8
9
  className?: string
@@ -32,7 +33,8 @@ export function LayoutDefault(props: LayoutDefaultProps) {
32
33
  const scrollWithOffset = useTransform(useViewportScroll().scrollY, (y) => y + offset)
33
34
 
34
35
  const classes = withState({ noSticky })
35
- const fabIconSize = responsiveVal(42, 56) // @todo generalize this
36
+
37
+ const fabIconSize = useFabSize('responsive')
36
38
 
37
39
  return (
38
40
  <Box
@@ -84,7 +86,7 @@ export function LayoutDefault(props: LayoutDefaultProps) {
84
86
  >
85
87
  {header}
86
88
  </Box>
87
- {(menuFab || cartFab) && (
89
+ {menuFab || cartFab ? (
88
90
  <Box
89
91
  className={classes.fabs}
90
92
  sx={(theme) => ({
@@ -97,10 +99,7 @@ export function LayoutDefault(props: LayoutDefaultProps) {
97
99
  padding: `0 ${theme.page.horizontal}`,
98
100
  position: 'sticky',
99
101
  marginTop: `calc(${theme.appShell.headerHeightMd} * -1 + calc(${fabIconSize} / 2))`,
100
- top: `calc(${theme.appShell.headerHeightMd} / 2 - ${responsiveVal(
101
- 42 / 2,
102
- 56 / 2,
103
- )})`,
102
+ top: `calc(${theme.appShell.headerHeightMd} / 2 - (${fabIconSize} / 2))`,
104
103
  },
105
104
  [theme.breakpoints.down('md')]: {
106
105
  position: 'fixed',
@@ -116,6 +115,8 @@ export function LayoutDefault(props: LayoutDefaultProps) {
116
115
  {menuFab}
117
116
  {cartFab}
118
117
  </Box>
118
+ ) : (
119
+ <div />
119
120
  )}
120
121
  <div className={classes.children}>{children}</div>
121
122
  <div className={classes.footer}>{footer}</div>
@@ -336,9 +336,15 @@ export function LayoutOverlayBase(props: LayoutOverlayBaseProps) {
336
336
  [theme.breakpoints.down('md')]: {
337
337
  minWidth: '80vw',
338
338
 
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
+ */
339
344
  '&.sizeSmFull, &.sizeSmMinimal': { paddingBottom: 56 },
340
-
341
- '&.variantSmBottom.sizeSmFull': { minHeight: 'calc(100vh - 56px)' },
345
+ '&.variantSmBottom.sizeSmFull': {
346
+ minHeight: `calc(100vh - ${theme.appShell.headerHeightSm} * 0.5)`,
347
+ },
342
348
 
343
349
  '&.variantSmBottom': {
344
350
  borderTopLeftRadius: `${theme.shape.borderRadius * 4}px`,
@@ -369,7 +375,7 @@ export function LayoutOverlayBase(props: LayoutOverlayBaseProps) {
369
375
  },
370
376
 
371
377
  '&.sizeMdFull.variantMdBottom': {
372
- minHeight: `calc(100vh - ${theme.appShell.headerHeightMd})`,
378
+ minHeight: `calc(100vh + ${theme.appShell.headerHeightMd} - (${theme.appShell.appBarHeightMd} - ${theme.appShell.appBarInnerHeightMd}) * 0.5)`,
373
379
  },
374
380
  '&.sizeMdFull.variantMdLeft': {
375
381
  paddingBottom: '1px',
@@ -1,14 +1,14 @@
1
1
  import { Scroller, ScrollerButton, ScrollerProvider } from '@graphcommerce/framer-scroller'
2
2
  import { Box, BoxProps } from '@mui/material'
3
3
  import React from 'react'
4
+ import { IconSvg, IconSvgProps } from '../IconSvg'
4
5
  import { extendableComponent } from '../Styles/extendableComponent'
5
- import { SvgIcon, SvgIconProps } from '../SvgIcon/SvgIcon'
6
6
  import { iconChevronLeft, iconChevronRight } from '../icons'
7
7
 
8
8
  export type MenuTabsProps = {
9
9
  children: React.ReactNode
10
- iconLeft?: SvgIconProps['src']
11
- iconRight?: SvgIconProps['src']
10
+ iconLeft?: IconSvgProps['src']
11
+ iconRight?: IconSvgProps['src']
12
12
  } & Pick<BoxProps, 'sx'>
13
13
 
14
14
  const { classes, selectors } = extendableComponent('DesktopNavBar', [
@@ -74,7 +74,7 @@ export function DesktopNavBar(props: MenuTabsProps) {
74
74
  size='small'
75
75
  className={classes.left}
76
76
  >
77
- <SvgIcon src={iconLeft ?? iconChevronLeft} />
77
+ <IconSvg src={iconLeft ?? iconChevronLeft} />
78
78
  </ScrollerButton>
79
79
  </Box>
80
80
 
@@ -103,7 +103,7 @@ export function DesktopNavBar(props: MenuTabsProps) {
103
103
  size='small'
104
104
  className={classes.right}
105
105
  >
106
- <SvgIcon src={iconRight ?? iconChevronRight} />
106
+ <IconSvg src={iconRight ?? iconChevronRight} />
107
107
  </ScrollerButton>
108
108
  </Box>
109
109
  </Box>
@@ -2,9 +2,10 @@ import { Divider, Fab, ListItem, Menu, styled, Box, SxProps, Theme } from '@mui/
2
2
  import { m } from 'framer-motion'
3
3
  import { useRouter } from 'next/router'
4
4
  import React, { useEffect } from 'react'
5
+ import { IconSvg } from '../IconSvg'
5
6
  import { extendableComponent } from '../Styles/extendableComponent'
6
7
  import { responsiveVal } from '../Styles/responsiveVal'
7
- import { SvgIcon } from '../SvgIcon/SvgIcon'
8
+ import { useFabSize } from '../Theme'
8
9
  import { iconMenu, iconClose } from '../icons'
9
10
  import { useFabAnimation } from './useFabAnimation'
10
11
 
@@ -26,8 +27,6 @@ const { classes, selectors } = extendableComponent('MenuFab', [
26
27
  'menu',
27
28
  ] as const)
28
29
 
29
- const fabIconSize = responsiveVal(42, 56) // @todo generalize this
30
-
31
30
  export function MenuFab(props: MenuFabProps) {
32
31
  const { children, secondary, search, menuIcon, closeIcon, sx = [] } = props
33
32
  const router = useRouter()
@@ -40,6 +39,7 @@ export function MenuFab(props: MenuFabProps) {
40
39
  router.events.on('routeChangeStart', clear)
41
40
  return () => router.events.off('routeChangeStart', clear)
42
41
  }, [router])
42
+ const fabIconSize = useFabSize('responsive')
43
43
 
44
44
  return (
45
45
  <Box sx={[{ width: fabIconSize, height: fabIconSize }, ...(Array.isArray(sx) ? sx : [sx])]}>
@@ -57,6 +57,7 @@ export function MenuFab(props: MenuFabProps) {
57
57
  color='inherit'
58
58
  aria-label='Open Menu'
59
59
  onClick={(event) => setOpenEl(event.currentTarget)}
60
+ size='responsive'
60
61
  sx={(theme) => ({
61
62
  boxShadow: 'none',
62
63
  '&:hover, &:focus': {
@@ -64,18 +65,16 @@ export function MenuFab(props: MenuFabProps) {
64
65
  background: theme.palette.text.primary,
65
66
  },
66
67
  background: theme.palette.text.primary,
67
- width: fabIconSize,
68
- height: fabIconSize,
69
68
  pointerEvents: 'all',
70
69
  color: theme.palette.background.paper,
71
70
  })}
72
71
  className={classes.fab}
73
72
  >
74
73
  {closeIcon ?? (
75
- <SvgIcon src={iconClose} size='large' sx={{ display: openEl ? 'block' : 'none' }} />
74
+ <IconSvg src={iconClose} size='large' sx={{ display: openEl ? 'block' : 'none' }} />
76
75
  )}
77
76
  {menuIcon ?? (
78
- <SvgIcon src={iconMenu} size='large' sx={{ display: openEl ? 'none' : 'block' }} />
77
+ <IconSvg src={iconMenu} size='large' sx={{ display: openEl ? 'none' : 'block' }} />
79
78
  )}
80
79
  </Fab>
81
80
  <MotionDiv
@@ -1,8 +1,12 @@
1
- import { Fab, FabProps, styled } from '@mui/material'
1
+ import { Fab, FabProps } from '@mui/material'
2
2
 
3
- export const PlaceholderFab = styled((props: Omit<FabProps, 'children'>) => (
4
- <Fab {...props}>
5
- {/* eslint-disable-next-line react/jsx-no-useless-fragment */}
6
- <></>
7
- </Fab>
8
- ))({ visibility: 'hidden', pointerEvents: 'none' })
3
+ export function PlaceholderFab(props: Omit<FabProps, 'children'>) {
4
+ const { sx = [] } = props
5
+ return (
6
+ <Fab
7
+ size='responsive'
8
+ {...props}
9
+ sx={[{ visibility: 'hidden', pointerEvents: 'none' }, ...(Array.isArray(sx) ? sx : [sx])]}
10
+ />
11
+ )
12
+ }
@@ -1,5 +1,12 @@
1
1
  import { usePageContext } from '@graphcommerce/framer-next-pages'
2
+ import {
3
+ resolveHref,
4
+ getDomainLocale,
5
+ addBasePath,
6
+ addLocale,
7
+ } from 'next/dist/shared/lib/router/router'
2
8
  import Head from 'next/head'
9
+ import { useRouter } from 'next/router'
3
10
 
4
11
  // https://developers.google.com/search/docs/advanced/robots/robots_meta_tag#directives
5
12
  export type MetaRobots =
@@ -15,19 +22,60 @@ export type MetaRobots =
15
22
  | `max-video-preview:${number}`
16
23
  type MetaRobotsAll = ['all' | 'none']
17
24
 
25
+ type Canonical = `http://${string}` | `https://${string}` | `/${string}` | string
26
+
18
27
  export type PageMetaProps = {
19
28
  title: string
20
- canonical?: `http://${string}` | `https://${string}` | string
29
+ canonical?: Canonical
21
30
  metaDescription?: string
22
31
  metaRobots?: MetaRobotsAll | MetaRobots[]
23
32
  }
24
33
 
34
+ export function useCanonical(incomming?: Canonical) {
35
+ const router = useRouter()
36
+ let canonical = incomming
37
+
38
+ if (!canonical) return canonical
39
+
40
+ if (canonical?.startsWith('/')) {
41
+ if (!process.env.NEXT_PUBLIC_SITE_URL) {
42
+ if (process.env.NODE_ENV !== 'production') {
43
+ throw Error('NEXT_PUBLIC_SITE_URL is not defined in .env')
44
+ }
45
+ }
46
+
47
+ let [href, as] = resolveHref(router, canonical, true)
48
+
49
+ const curLocale = router.locale
50
+
51
+ // Copied from here https://github.com/vercel/next.js/blob/canary/packages/next/client/link.tsx#L313-L327
52
+ const localeDomain =
53
+ router &&
54
+ router.isLocaleDomain &&
55
+ getDomainLocale(as, curLocale, router && router.locales, router && router.domainLocales)
56
+
57
+ href = localeDomain || addBasePath(addLocale(as, curLocale, router && router.defaultLocale))
58
+
59
+ canonical = `${process.env.NEXT_PUBLIC_SITE_URL}${href}`
60
+ }
61
+
62
+ if (canonical && !(canonical ?? 'http').startsWith('http')) {
63
+ if (process.env.NODE_ENV !== 'production') {
64
+ throw new Error(
65
+ `canonical must start with '/', 'http://' or 'https://', '${canonical}' given`,
66
+ )
67
+ }
68
+ canonical = undefined
69
+ }
70
+
71
+ return canonical
72
+ }
73
+
25
74
  export function PageMeta(props: PageMetaProps) {
26
75
  const { active } = usePageContext()
27
- const { title, canonical, metaDescription, metaRobots = ['all'] } = props
76
+ const { title, canonical: canonicalBare, metaDescription, metaRobots = ['all'] } = props
28
77
 
29
- if (!(canonical ?? 'http').startsWith('http'))
30
- throw new Error(`canonical must start with http:// or https://, '${canonical}' given`)
78
+ const canonical = useCanonical(canonicalBare)
31
79
 
32
80
  if (!active) return null
33
81
  return (
@@ -1,8 +1,8 @@
1
1
  import { PaginationProps, Box, SxProps, Theme, IconButton } from '@mui/material'
2
2
  import usePagination, { UsePaginationItem } from '@mui/material/usePagination'
3
3
  import React from 'react'
4
+ import { IconSvg } from '../IconSvg'
4
5
  import { extendableComponent } from '../Styles'
5
- import { SvgIcon } from '../SvgIcon/SvgIcon'
6
6
  import { iconChevronLeft, iconChevronRight } from '../icons'
7
7
 
8
8
  export type PagePaginationProps = {
@@ -41,7 +41,7 @@ export function Pagination(props: PagePaginationProps) {
41
41
  aria-label='Previous page'
42
42
  className={classes.button}
43
43
  >
44
- <SvgIcon src={iconChevronLeft} className={classes.icon} size='medium' />
44
+ <IconSvg src={iconChevronLeft} className={classes.icon} size='medium' />
45
45
  </IconButton>
46
46
  )
47
47
 
@@ -53,7 +53,7 @@ export function Pagination(props: PagePaginationProps) {
53
53
  aria-label='Next page'
54
54
  className={classes.button}
55
55
  >
56
- <SvgIcon src={iconChevronRight} className={classes.icon} size='medium' />
56
+ <IconSvg src={iconChevronRight} className={classes.icon} size='medium' />
57
57
  </IconButton>
58
58
  )
59
59
 
@@ -1,7 +1,7 @@
1
1
  import { Button, ButtonProps, styled } from '@mui/material'
2
2
  import PageLink from 'next/link'
3
3
  import React from 'react'
4
- import { SvgIcon } from '../../SvgIcon/SvgIcon'
4
+ import { IconSvg } from '../../IconSvg'
5
5
  import { iconChevronRight } from '../../icons'
6
6
 
7
7
  export type ButtonLinkProps = { url: string; endIcon?: React.ReactNode } & ButtonProps
@@ -17,7 +17,7 @@ const ButtonItem = styled(Button)(({ theme }) => ({
17
17
  }))
18
18
 
19
19
  export function ButtonLinkListItem(props: ButtonLinkProps) {
20
- const { children, url, endIcon = <SvgIcon src={iconChevronRight} />, ...buttonProps } = props
20
+ const { children, url, endIcon = <IconSvg src={iconChevronRight} />, ...buttonProps } = props
21
21
 
22
22
  return (
23
23
  <PageLink href={url} passHref>
@@ -10,8 +10,8 @@ import {
10
10
  Theme,
11
11
  } from '@mui/material'
12
12
  import React, { useEffect, useState } from 'react'
13
+ import { IconSvg } from '../IconSvg'
13
14
  import { extendableComponent } from '../Styles'
14
- import { SvgIcon } from '../SvgIcon/SvgIcon'
15
15
  import { iconClose, iconCheckmark, iconSadFace } from '../icons'
16
16
 
17
17
  type Size = 'normal' | 'wide'
@@ -110,14 +110,14 @@ export default function MessageSnackbarImpl(props: MessageSnackbarImplProps) {
110
110
  md: 'min-content 1fr max-content auto',
111
111
  },
112
112
  typography: 'subtitle1',
113
- '&.SvgIcon': {
113
+ '&.IconSvg': {
114
114
  gridArea: 'children',
115
115
  },
116
116
  },
117
117
  })}
118
118
  message={
119
119
  <>
120
- <SvgIcon src={icon} size='large' />
120
+ <IconSvg src={icon} size='large' />
121
121
  <Box gridArea='children'>{children}</Box>
122
122
  {/* </Box> */}
123
123
  {action && (
@@ -134,7 +134,7 @@ export default function MessageSnackbarImpl(props: MessageSnackbarImplProps) {
134
134
  backgroundColor: lighten(theme.palette.background.paper, 0.1),
135
135
  })}
136
136
  >
137
- <SvgIcon src={iconClose} />
137
+ <IconSvg src={iconClose} />
138
138
  </Fab>
139
139
  </>
140
140
  }
@@ -1,6 +1,6 @@
1
1
  import { RatingProps, Rating } from '@mui/material'
2
+ import { IconSvg } from '../IconSvg'
2
3
  import { extendableComponent } from '../Styles'
3
- import { SvgIcon } from '../SvgIcon/SvgIcon'
4
4
  import { iconStar } from '../icons'
5
5
 
6
6
  export type StarRatingFieldProps = {
@@ -22,7 +22,7 @@ export function StarRatingField(props: StarRatingFieldProps) {
22
22
  max={5}
23
23
  size='small'
24
24
  emptyIcon={
25
- <SvgIcon
25
+ <IconSvg
26
26
  src={iconStar}
27
27
  size='large'
28
28
  className={classes.startEmpty}
@@ -30,7 +30,7 @@ export function StarRatingField(props: StarRatingFieldProps) {
30
30
  />
31
31
  }
32
32
  icon={
33
- <SvgIcon
33
+ <IconSvg
34
34
  src={iconStar}
35
35
  size='large'
36
36
  className={classes.starFull}
@@ -9,9 +9,9 @@ import {
9
9
  Theme,
10
10
  } from '@mui/material'
11
11
  import { ChangeEvent, Ref, useCallback, useEffect, useRef, useState } from 'react'
12
+ import { IconSvg } from '../IconSvg'
12
13
  import { extendableComponent } from '../Styles'
13
14
  import { responsiveVal } from '../Styles/responsiveVal'
14
- import { SvgIcon } from '../SvgIcon/SvgIcon'
15
15
  import { iconMin, iconPlus } from '../icons'
16
16
 
17
17
  export type IconButtonPropsOmit = Omit<
@@ -123,7 +123,7 @@ export function TextInputNumber(props: TextInputNumberProps) {
123
123
  {...DownProps}
124
124
  className={`${classes.button} ${DownProps.className ?? ''}`}
125
125
  >
126
- {DownProps.children ?? <SvgIcon src={iconMin} size='small' />}
126
+ {DownProps.children ?? <IconSvg src={iconMin} size='small' />}
127
127
  </IconButton>
128
128
  ),
129
129
  endAdornment: (
@@ -139,7 +139,7 @@ export function TextInputNumber(props: TextInputNumberProps) {
139
139
  {...UpProps}
140
140
  className={`${classes.button} ${UpProps.className ?? ''}`}
141
141
  >
142
- {UpProps.children ?? <SvgIcon src={iconPlus} size='small' />}
142
+ {UpProps.children ?? <IconSvg src={iconPlus} size='small' />}
143
143
  </IconButton>
144
144
  ),
145
145
  }}
@@ -12,7 +12,7 @@ import {
12
12
  } from '@mui/material'
13
13
  import { useRouter } from 'next/router'
14
14
  import { createContext, useContext, useEffect, useMemo, useState } from 'react'
15
- import { SvgIcon } from '../SvgIcon/SvgIcon'
15
+ import { IconSvg } from '../IconSvg'
16
16
  import { iconMoon, iconSun } from '../icons'
17
17
 
18
18
  type Mode = 'dark' | 'light'
@@ -88,7 +88,7 @@ export function DarkLightModeToggleFab(props: Omit<FabProps, 'onClick'>) {
88
88
  const { currentMode, toggle } = useColorMode()
89
89
  return (
90
90
  <Fab size='large' color='inherit' onClick={toggle} {...props}>
91
- <SvgIcon src={currentMode === 'light' ? iconMoon : iconSun} size='large' />
91
+ <IconSvg src={currentMode === 'light' ? iconMoon : iconSun} size='large' />
92
92
  </Fab>
93
93
  )
94
94
  }
@@ -105,7 +105,7 @@ export function DarkLightModeMenuSecondaryItem(props: ListItemButtonProps) {
105
105
  return (
106
106
  <ListItemButton {...props} sx={[{}, ...(Array.isArray(sx) ? sx : [sx])]} dense onClick={toggle}>
107
107
  <ListItemIcon sx={{ minWidth: 'unset', paddingRight: '8px' }}>
108
- <SvgIcon src={currentMode === 'light' ? iconMoon : iconSun} size='medium' />
108
+ <IconSvg src={currentMode === 'light' ? iconMoon : iconSun} size='medium' />
109
109
  </ListItemIcon>
110
110
  <ListItemText>
111
111
  {currentMode === 'light' ? (
@@ -0,0 +1,69 @@
1
+ import { ComponentsVariants, FabProps, Theme, useTheme } from '@mui/material'
2
+ import { responsiveVal } from '../Styles'
3
+
4
+ type FabSize = NonNullable<FabProps['size']>
5
+
6
+ type FabSizes = {
7
+ [key in FabSize]: string
8
+ }
9
+
10
+ /** Expose the component to be exendable in your theme.components */
11
+ declare module '@mui/material/styles/components' {
12
+ interface Components {
13
+ /**
14
+ * @todo We would rather use MuiFab to override these fields, but I can't get it to work,
15
+ * getting 'Subsequent property declarations must have the same type.'
16
+ */
17
+ MuiFabExtra?: {
18
+ sizes?: Partial<FabSizes>
19
+ }
20
+ }
21
+ }
22
+
23
+ const defaultSizes: FabSizes = {
24
+ /**
25
+ * Default values picked from MUI:
26
+ * https://github.com/mui/material-ui/blob/master/packages/mui-material/src/Fab/Fab.js
27
+ */
28
+ small: '40px',
29
+ medium: '48px',
30
+ large: '56px',
31
+ responsive: responsiveVal(40, 56),
32
+ }
33
+
34
+ function fabSize(size: FabSize, theme: Theme) {
35
+ return theme.components?.MuiFabExtra?.sizes?.[size] ?? defaultSizes[size]
36
+ }
37
+
38
+ export const useFabSize = (size: FabSize) => {
39
+ const theme = useTheme()
40
+ return fabSize(size, theme)
41
+ }
42
+
43
+ declare module '@mui/material/Fab' {
44
+ interface FabPropsSizeOverrides {
45
+ responsive: true
46
+ }
47
+ }
48
+
49
+ function fabWidthHeight(size: FabSize, theme: Theme) {
50
+ return {
51
+ width: fabSize(size, theme),
52
+ height: fabSize(size, theme),
53
+ }
54
+ }
55
+
56
+ type FabVariants = NonNullable<ComponentsVariants['MuiFab']>
57
+
58
+ const sizes: FabSize[] = ['small', 'medium', 'large', 'responsive']
59
+
60
+ /**
61
+ * This defines the sizes for the added responsive variant.
62
+ *
63
+ * To override the sizes, please do not add variant declarations direcly, but modify
64
+ * `yourTheme.components.MuiFabExtra.sizes` instead.
65
+ */
66
+ export const MuiFabSizes: FabVariants = sizes.map((size) => ({
67
+ props: { size },
68
+ style: ({ theme }) => fabWidthHeight(size, theme),
69
+ }))
package/Theme/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './MuiSlider'
2
2
  export * from './MuiButton'
3
3
  export * from './MuiSnackbar'
4
+ export * from './MuiFab'
4
5
  export * from './themeDefaults'
5
6
  export * from './DarkLightModeThemeProvider'
package/index.ts CHANGED
@@ -58,7 +58,7 @@ export * from './Snackbar/MessageSnackbarImpl'
58
58
  export * from './StarRatingField'
59
59
  export * from './Stepper/Stepper'
60
60
  export * from './Styles'
61
- export * from './SvgIcon'
61
+ export * from './IconSvg'
62
62
  export * from './TextInputNumber'
63
63
  export * from './Theme'
64
64
  export * from './TimeAgo'
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.1.5",
5
+ "version": "4.2.2",
6
6
  "author": "",
7
7
  "license": "MIT",
8
8
  "sideEffects": false,
@@ -19,8 +19,8 @@
19
19
  "@emotion/react": "^11.7.1",
20
20
  "@emotion/server": "^11.4.0",
21
21
  "@emotion/styled": "^11.6.0",
22
- "@graphcommerce/framer-next-pages": "^3.1.0",
23
- "@graphcommerce/framer-scroller": "^2.0.4",
22
+ "@graphcommerce/framer-next-pages": "^3.1.1",
23
+ "@graphcommerce/framer-scroller": "^2.0.5",
24
24
  "@graphcommerce/framer-utils": "^3.0.3",
25
25
  "@graphcommerce/image": "^3.1.0",
26
26
  "react-is": "^17.0.2",