@graphcommerce/next-ui 4.1.3 → 4.2.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,54 @@
1
1
  # Change Log
2
2
 
3
+ ## 4.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#1292](https://github.com/ho-nl/m2-pwa/pull/1292)
8
+ [`63f9b56eb`](https://github.com/ho-nl/m2-pwa/commit/63f9b56eb68ba790567ff1427e599fd2c3c8f1ee)
9
+ Thanks [@paales](https://github.com/paales)! - added responsive size to the Fab component
10
+
11
+ ### Patch Changes
12
+
13
+ - [#1292](https://github.com/ho-nl/m2-pwa/pull/1292)
14
+ [`5a1ba9e66`](https://github.com/ho-nl/m2-pwa/commit/5a1ba9e664abbac89c4f5f71f7d6d6ed1aefa5c0)
15
+ Thanks [@paales](https://github.com/paales)! - Renamed SvgIcon to IconSvg to prevent collisions
16
+ with MUI
17
+
18
+ * [#1292](https://github.com/ho-nl/m2-pwa/pull/1292)
19
+ [`990df655b`](https://github.com/ho-nl/m2-pwa/commit/990df655b73b469718d6cb5837ee65dfe2ad6a1d)
20
+ Thanks [@paales](https://github.com/paales)! - `<SearchLink />` a more lightweight (less js)
21
+ alternative for `<SearchButton />`
22
+
23
+ ## 4.1.5
24
+
25
+ ### Patch Changes
26
+
27
+ - [#1290](https://github.com/ho-nl/m2-pwa/pull/1290)
28
+ [`47ae012c1`](https://github.com/ho-nl/m2-pwa/commit/47ae012c10f5762f99019ec38409177632377a98)
29
+ Thanks [@paales](https://github.com/paales)! - `withTheme` didn’t apply styles correcty
30
+
31
+ * [#1290](https://github.com/ho-nl/m2-pwa/pull/1290)
32
+ [`39e28a4cd`](https://github.com/ho-nl/m2-pwa/commit/39e28a4cd6cdfaa4fc6dc4500ae86c14f7069150)
33
+ Thanks [@paales](https://github.com/paales)! - Allow background color on header
34
+
35
+ - [#1289](https://github.com/ho-nl/m2-pwa/pull/1289)
36
+ [`ec8026cc5`](https://github.com/ho-nl/m2-pwa/commit/ec8026cc5a5be8d97a6e5dbf208808154fa1d618)
37
+ Thanks [@LaurensFranken](https://github.com/LaurensFranken)! - add sx prop to UspsList component
38
+
39
+ * [#1290](https://github.com/ho-nl/m2-pwa/pull/1290)
40
+ [`35672d8e8`](https://github.com/ho-nl/m2-pwa/commit/35672d8e87011bf4eb049f449e86e851fc91a525)
41
+ Thanks [@paales](https://github.com/paales)! - Footer didn't accept sx prop
42
+
43
+ ## 4.1.4
44
+
45
+ ### Patch Changes
46
+
47
+ - [#1287](https://github.com/ho-nl/m2-pwa/pull/1287)
48
+ [`d17f97d3a`](https://github.com/ho-nl/m2-pwa/commit/d17f97d3a786c33a99a10e4e949251c52fdbbdac)
49
+ Thanks [@paales](https://github.com/paales)! - Allow passing sx prop to SidebarGallery and
50
+ ContentLinks
51
+
3
52
  ## 4.1.3
4
53
 
5
54
  ### 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
 
package/Footer/Footer.tsx CHANGED
@@ -18,39 +18,49 @@ const { classes, selectors } = extendableComponent('Footer', [
18
18
  ] as const)
19
19
 
20
20
  export function Footer(props: FooterProps) {
21
- const { socialLinks, storeSwitcher, customerService, copyright, ...containerProps } = props
21
+ const {
22
+ socialLinks,
23
+ storeSwitcher,
24
+ customerService,
25
+ copyright,
26
+ sx = [],
27
+ ...containerProps
28
+ } = props
22
29
 
23
30
  return (
24
31
  <Container
25
- sx={(theme) => ({
26
- gridTemplateColumns: '5fr 3fr',
27
- borderTop: `1px solid ${theme.palette.divider}`,
28
- display: 'grid',
29
- alignItems: 'center',
32
+ sx={[
33
+ (theme) => ({
34
+ gridTemplateColumns: '5fr 3fr',
35
+ borderTop: `1px solid ${theme.palette.divider}`,
36
+ display: 'grid',
37
+ alignItems: 'center',
30
38
 
31
- padding: `${theme.spacings.lg} ${theme.page.horizontal} ${theme.page.vertical}`,
32
- justifyItems: 'center',
33
- gridTemplateAreas: `
39
+ padding: `${theme.spacings.lg} ${theme.page.horizontal} ${theme.page.vertical}`,
40
+ justifyItems: 'center',
41
+ gridTemplateAreas: `
34
42
  'switcher switcher'
35
43
  'support support'
36
44
  'social social'
37
45
  'links links'
38
46
  `,
39
- gap: theme.spacings.md,
40
- '& > *': { maxWidth: 'max-content' },
47
+ gap: theme.spacings.md,
48
+ '& > *': { maxWidth: 'max-content' },
41
49
 
42
- [theme.breakpoints.up('sm')]: {
43
- gridTemplateAreas: `
50
+ [theme.breakpoints.up('sm')]: {
51
+ gridTemplateAreas: `
44
52
  'social switcher'
45
53
  'links support'
46
54
  `,
47
- justifyItems: 'start',
48
- padding: `${theme.page.vertical} ${theme.page.horizontal}`,
49
- gridTemplateColumns: 'auto auto',
50
- gridTemplateRows: 'auto',
51
- justifyContent: 'space-between',
52
- },
53
- })}
55
+ justifyItems: 'start',
56
+ padding: `${theme.page.vertical} ${theme.page.horizontal}`,
57
+ gridTemplateColumns: 'auto auto',
58
+ gridTemplateRows: 'auto',
59
+ justifyContent: 'space-between',
60
+ },
61
+ }),
62
+ ...(Array.isArray(sx) ? sx : [sx]),
63
+ ]}
54
64
  maxWidth={false}
55
65
  className={classes.root}
56
66
  {...containerProps}
@@ -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' }]}
@@ -8,14 +8,14 @@ import {
8
8
  ScrollerProvider,
9
9
  } from '@graphcommerce/framer-scroller'
10
10
  import { clientSize, useMotionValueValue } from '@graphcommerce/framer-utils'
11
- import { Fab, useTheme, alpha, Box, styled } from '@mui/material'
11
+ 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)({})
@@ -47,10 +47,17 @@ export type SidebarGalleryProps = {
47
47
  images: MotionImageAspectProps[]
48
48
  aspectRatio?: [number, number]
49
49
  routeHash?: string
50
+ sx?: SxProps<Theme>
50
51
  }
51
52
 
52
53
  export function SidebarGallery(props: SidebarGalleryProps) {
53
- const { sidebar, images, aspectRatio: [width, height] = [1, 1], routeHash = 'gallery' } = props
54
+ const {
55
+ sidebar,
56
+ images,
57
+ aspectRatio: [width, height] = [1, 1],
58
+ sx,
59
+ routeHash = 'gallery',
60
+ } = props
54
61
 
55
62
  const router = useRouter()
56
63
  const prevRoute = usePrevPageRouter()
@@ -109,7 +116,7 @@ export function SidebarGallery(props: SidebarGalleryProps) {
109
116
 
110
117
  return (
111
118
  <ScrollerProvider scrollSnapAlign='center'>
112
- <Row maxWidth={false} disableGutters className={classes.row}>
119
+ <Row maxWidth={false} disableGutters className={classes.row} sx={sx}>
113
120
  <MotionBox
114
121
  layout
115
122
  className={classes.root}
@@ -220,7 +227,7 @@ export function SidebarGallery(props: SidebarGalleryProps) {
220
227
  boxShadow: theme.shadows[6],
221
228
  }}
222
229
  >
223
- {!zoomed ? <SvgIcon src={iconFullscreen} /> : <SvgIcon src={iconFullscreenExit} />}
230
+ {!zoomed ? <IconSvg src={iconFullscreen} /> : <IconSvg src={iconFullscreenExit} />}
224
231
  </Fab>
225
232
  </MotionBox>
226
233
  <Box
@@ -240,7 +247,7 @@ export function SidebarGallery(props: SidebarGalleryProps) {
240
247
  size='small'
241
248
  className={classes.sliderButtons}
242
249
  >
243
- <SvgIcon src={iconChevronLeft} />
250
+ <IconSvg src={iconChevronLeft} />
244
251
  </ScrollerButton>
245
252
  </Box>
246
253
  <Box
@@ -259,7 +266,7 @@ export function SidebarGallery(props: SidebarGalleryProps) {
259
266
  size='small'
260
267
  className={classes.sliderButtons}
261
268
  >
262
- <SvgIcon src={iconChevronRight} />
269
+ <IconSvg src={iconChevronRight} />
263
270
  </ScrollerButton>
264
271
  </Box>
265
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
@@ -3,7 +3,7 @@ import { t } from '@lingui/macro'
3
3
  import PageLink from 'next/link'
4
4
  import { useRouter } from 'next/router'
5
5
  import { LinkOrButton, LinkOrButtonProps } from '../../Button/LinkOrButton'
6
- import { SvgIcon } from '../../SvgIcon/SvgIcon'
6
+ import { IconSvg } from '../../IconSvg'
7
7
  import { iconChevronLeft } from '../../icons'
8
8
 
9
9
  export type BackProps = Omit<LinkOrButtonProps, 'onClick' | 'children'>
@@ -28,7 +28,7 @@ export default function LayoutHeaderBack(props: BackProps) {
28
28
  const prevUp = usePrevUp()
29
29
  const { backSteps } = usePageContext()
30
30
 
31
- const backIcon = <SvgIcon src={iconChevronLeft} size='medium' />
31
+ const backIcon = <IconSvg src={iconChevronLeft} size='medium' />
32
32
  const canClickBack = backSteps > 0 && path !== prevUp?.href
33
33
 
34
34
  let label = t`Back`
@@ -1,7 +1,7 @@
1
1
  import { useGo, usePageContext } from '@graphcommerce/framer-next-pages'
2
2
  import { Trans } from '@lingui/macro'
3
3
  import { LinkOrButton } from '../../Button/LinkOrButton'
4
- import { SvgIcon } from '../../SvgIcon/SvgIcon'
4
+ import { IconSvg } from '../../IconSvg'
5
5
  import { iconClose } from '../../icons'
6
6
 
7
7
  export function useShowClose() {
@@ -19,7 +19,7 @@ export default function LayoutHeaderClose() {
19
19
  color='inherit'
20
20
  onClick={onClick}
21
21
  aria-label='Close'
22
- startIcon={<SvgIcon src={iconClose} />}
22
+ startIcon={<IconSvg src={iconClose} />}
23
23
  // className={classes.close}
24
24
  >
25
25
  <Trans>Close</Trans>
@@ -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
@@ -92,15 +94,12 @@ export function LayoutDefault(props: LayoutDefaultProps) {
92
94
  justifyContent: 'space-between',
93
95
  width: '100%',
94
96
  height: 0,
95
- zIndex: 2,
97
+ zIndex: 'drawer',
96
98
  [theme.breakpoints.up('sm')]: {
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',
@@ -336,8 +336,12 @@ 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
345
  '&.variantSmBottom.sizeSmFull': { minHeight: 'calc(100vh - 56px)' },
342
346
 
343
347
  '&.variantSmBottom': {
@@ -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,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
@@ -13,11 +13,11 @@ const ButtonItem = styled(Button)(({ theme }) => ({
13
13
  borderBottom: `1px solid ${theme.palette.divider}`,
14
14
  borderRadius: 0,
15
15
  justifyContent: 'space-between',
16
- ...theme.typography.body1,
16
+ typography: 'body1',
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>
@@ -14,13 +14,18 @@ const parts = ['root', 'scroller', 'title'] as const
14
14
  const { classes } = extendableComponent(compName, parts)
15
15
 
16
16
  export function ContentLinks(props: ContentLinksProps) {
17
- const { title, children } = props
17
+ const { title, children, sx = [] } = props
18
18
 
19
19
  return (
20
20
  <Container
21
21
  className={classes.root}
22
22
  maxWidth={false}
23
- sx={(theme) => ({ marginBottom: `${theme.spacings.md}` })}
23
+ sx={[
24
+ (theme) => ({
25
+ marginBottom: `${theme.spacings.md}`,
26
+ }),
27
+ ...(Array.isArray(sx) ? sx : [sx]),
28
+ ]}
24
29
  >
25
30
  <ScrollerProvider scrollSnapAlign='none'>
26
31
  <Scroller
@@ -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}
@@ -1,4 +1,7 @@
1
- import { css, Theme, ThemeProvider } from '@mui/material'
1
+ import { SxProps, Theme, ThemeProvider } from '@mui/material'
2
+ import React from 'react'
3
+
4
+ type WithSx = { sx?: SxProps<Theme> }
2
5
 
3
6
  /**
4
7
  * It will provide a theme for the underlying tree and will set the color/font and backgroundColor
@@ -21,22 +24,37 @@ import { css, Theme, ThemeProvider } from '@mui/material'
21
24
  * const MyPage = () => {
22
25
  * return <div>Your regular page content, but now in darkMode</div>
23
26
  * }
24
- *
25
27
  * export default withTheme(MyPage, darkTheme)
26
28
  * ```
29
+ *
30
+ * If you are trying to theme a complete page:
31
+ *
32
+ * ```tsx
33
+ * MyPage.pageOptions = {
34
+ * Layout: withTheme(LayoutFull, darkTheme),
35
+ * } as PageOptions
36
+ * ```
27
37
  */
28
- export function withTheme(Component: React.FC<{ className?: string }>, theme: Theme) {
29
- return (props: Record<string, unknown>) => (
30
- <ThemeProvider theme={theme}>
31
- <Component
32
- {...props}
33
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
34
- css={css({
35
- color: theme.palette.text.primary,
36
- backgroundColor: theme.palette.background.default,
37
- ...(theme.typography.body1 as any),
38
- })}
39
- />
40
- </ThemeProvider>
41
- )
38
+ export function withTheme<T>(
39
+ Component: (value: T & WithSx) => React.ReactElement<any, any> | null,
40
+ theme: Theme,
41
+ ): React.FC<T & WithSx> {
42
+ return (data: T & WithSx) => {
43
+ const sx = data.sx ?? []
44
+ return (
45
+ <ThemeProvider theme={theme}>
46
+ <Component
47
+ {...data}
48
+ sx={[
49
+ {
50
+ typography: 'body1',
51
+ color: theme.palette.text.primary,
52
+ backgroundColor: theme.palette.background.default,
53
+ },
54
+ ...(Array.isArray(sx) ? sx : [sx]),
55
+ ]}
56
+ />
57
+ </ThemeProvider>
58
+ )
59
+ }
42
60
  }
@@ -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/UspList/index.tsx CHANGED
@@ -1,8 +1,9 @@
1
- import { Box } from '@mui/material'
1
+ import { Box, SxProps, Theme } from '@mui/material'
2
2
  import { extendableComponent } from '../Styles'
3
3
 
4
4
  export type UspListProps = OwnerState & {
5
5
  children: React.ReactNode
6
+ sx?: SxProps<Theme>
6
7
  }
7
8
 
8
9
  type OwnerState = { size?: 'small' | 'medium' }
@@ -11,24 +12,27 @@ const parts = ['root'] as const
11
12
  const { withState } = extendableComponent<OwnerState, typeof name, typeof parts>(name, parts)
12
13
 
13
14
  export function UspList(props: UspListProps) {
14
- const { children, size } = props
15
+ const { children, size, sx = [] } = props
15
16
  const classes = withState({ size })
16
17
 
17
18
  return (
18
19
  <Box
19
20
  component='ul'
20
21
  className={classes.root}
21
- sx={(theme) => ({
22
- listStyleType: 'none',
23
- padding: 0,
24
- margin: 0,
25
- display: 'grid',
26
- alignContent: 'start',
27
- rowGap: theme.spacings.xs,
28
- '&.sizeSmall': {
29
- rowGap: '3px',
30
- },
31
- })}
22
+ sx={[
23
+ (theme) => ({
24
+ listStyleType: 'none',
25
+ padding: 0,
26
+ margin: 0,
27
+ display: 'grid',
28
+ alignContent: 'start',
29
+ rowGap: theme.spacings.xs,
30
+ '&.sizeSmall': {
31
+ rowGap: '3px',
32
+ },
33
+ }),
34
+ ...(Array.isArray(sx) ? sx : [sx]),
35
+ ]}
32
36
  >
33
37
  {children}
34
38
  </Box>
package/icons/index.tsx CHANGED
@@ -38,3 +38,4 @@ export { default as iconEmailOutline } from './envelope-alt.svg'
38
38
  export { default as icon404 } from './explore.svg'
39
39
  export { default as iconSun } from './sun.svg'
40
40
  export { default as iconMoon } from './moon.svg'
41
+ export { default as iconPlay } from './play.svg'
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.3",
5
+ "version": "4.2.0",
6
6
  "author": "",
7
7
  "license": "MIT",
8
8
  "sideEffects": false,