@graphcommerce/next-ui 4.0.1 → 4.1.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,113 @@
1
1
  # Change Log
2
2
 
3
+ ## 4.1.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [`973ff8645`](https://github.com/ho-nl/m2-pwa/commit/973ff86452a70ade9f4db13fdda6e963d7220e96)
8
+ Thanks [@paales](https://github.com/paales)! - made packages public
9
+
10
+ * [#1278](https://github.com/ho-nl/m2-pwa/pull/1278)
11
+ [`81ea406d5`](https://github.com/ho-nl/m2-pwa/commit/81ea406d54d6b5c662c030a7fea444abc4117a20)
12
+ Thanks [@ErwinOtten](https://github.com/ErwinOtten)! - Upgraded dependencies to the latest version
13
+
14
+ - [#1281](https://github.com/ho-nl/m2-pwa/pull/1281)
15
+ [`3a719c88c`](https://github.com/ho-nl/m2-pwa/commit/3a719c88cad1eab58602de28c41adc0fc4827e1d)
16
+ Thanks [@paales](https://github.com/paales)! - Make sure we're able to style the backdrop and the
17
+ regular overlay for LayoutOverlay
18
+
19
+ * [#1284](https://github.com/ho-nl/m2-pwa/pull/1284)
20
+ [`5ffcb56bf`](https://github.com/ho-nl/m2-pwa/commit/5ffcb56bfcbe49ebeaf24f9341e819a145ab9a14)
21
+ Thanks [@paales](https://github.com/paales)! - SvgIcon is now more extenable and flexible:
22
+
23
+ - It will automatically calculate the stroke-width of the SVG based on the rendered size, allowing
24
+ for a more flexible use for icons.
25
+
26
+ - Make SvgIcon themable in your own Theme.
27
+
28
+ - Create overrides for components that will be used throughout the app.
29
+
30
+ * Updated dependencies
31
+ [[`4bb963d75`](https://github.com/ho-nl/m2-pwa/commit/4bb963d7595b5ce6e3a4924cc2e3e8b0210cdcd6),
32
+ [`973ff8645`](https://github.com/ho-nl/m2-pwa/commit/973ff86452a70ade9f4db13fdda6e963d7220e96),
33
+ [`81ea406d5`](https://github.com/ho-nl/m2-pwa/commit/81ea406d54d6b5c662c030a7fea444abc4117a20),
34
+ [`5ffcb56bf`](https://github.com/ho-nl/m2-pwa/commit/5ffcb56bfcbe49ebeaf24f9341e819a145ab9a14)]:
35
+ - @graphcommerce/framer-next-pages@3.1.0
36
+ - @graphcommerce/framer-scroller@2.0.3
37
+ - @graphcommerce/framer-utils@3.0.3
38
+ - @graphcommerce/image@3.1.0
39
+
40
+ ## 4.1.1
41
+
42
+ ### Patch Changes
43
+
44
+ - [#1274](https://github.com/ho-nl/m2-pwa/pull/1274)
45
+ [`381e4c86a`](https://github.com/ho-nl/m2-pwa/commit/381e4c86a8321ce96e1fa5c7d3c0a0c0ff3e02c7)
46
+ Thanks [@paales](https://github.com/paales)! - Moved `ApolloErrorAlert`, `ApolloErrorFullPage` and
47
+ `ApolloErrorSnackbar` to the ecommerce-ui package.
48
+
49
+ Created `ComposedSubmitButton` and `ComposedSubmitLinkOrButton` to reduce complexity from
50
+ `magento-graphcms` example.
51
+
52
+ Removed dependency an `@graphcommerce/react-hook-form` from `magento-graphcms` example.
53
+
54
+ Added dependency `@graphcommerce/ecommerce-ui` from `magento-graphcms` example.
55
+
56
+ * [#1276](https://github.com/ho-nl/m2-pwa/pull/1276)
57
+ [`ce09388e0`](https://github.com/ho-nl/m2-pwa/commit/ce09388e0d7ef33aee660612340f6fbae15ceec2)
58
+ Thanks [@paales](https://github.com/paales)! - We've moved lots of internal packages from
59
+ `dependencies` to `peerDependencies`. The result of this is that there will be significantly less
60
+ duplicate packages in the node_modules folders.
61
+
62
+ - [#1276](https://github.com/ho-nl/m2-pwa/pull/1276)
63
+ [`e7c8e2756`](https://github.com/ho-nl/m2-pwa/commit/e7c8e2756d637cbcd2e793d62ef5721d35d9fa7b)
64
+ Thanks [@paales](https://github.com/paales)! - CartFab positioning was incorrect
65
+
66
+ * [#1276](https://github.com/ho-nl/m2-pwa/pull/1276)
67
+ [`52a45bba4`](https://github.com/ho-nl/m2-pwa/commit/52a45bba4dc6dd6df3c81f5023df7d23ed8a534d)
68
+ Thanks [@paales](https://github.com/paales)! - Upgraded to
69
+ [NextJS 12.1](https://nextjs.org/blog/next-12-1)! This is just for compatibility, but we'll be
70
+ implementing
71
+ [On-demand Incremental Static Regeneration](https://nextjs.org/blog/next-12-1#on-demand-incremental-static-regeneration-beta)
72
+ soon.
73
+
74
+ This will greatly reduce the requirement to rebuid stuff and we'll add a management UI on the
75
+ frontend to be able to revalidate pages manually.
76
+
77
+ * Updated dependencies
78
+ [[`381e4c86a`](https://github.com/ho-nl/m2-pwa/commit/381e4c86a8321ce96e1fa5c7d3c0a0c0ff3e02c7),
79
+ [`ce09388e0`](https://github.com/ho-nl/m2-pwa/commit/ce09388e0d7ef33aee660612340f6fbae15ceec2),
80
+ [`52a45bba4`](https://github.com/ho-nl/m2-pwa/commit/52a45bba4dc6dd6df3c81f5023df7d23ed8a534d)]:
81
+ - @graphcommerce/framer-next-pages@3.0.2
82
+ - @graphcommerce/framer-scroller@2.0.2
83
+ - @graphcommerce/framer-utils@3.0.2
84
+ - @graphcommerce/image@3.0.2
85
+
86
+ ## 4.1.0
87
+
88
+ ### Minor Changes
89
+
90
+ - [#1273](https://github.com/ho-nl/m2-pwa/pull/1273)
91
+ [`8c4e4f7cd`](https://github.com/ho-nl/m2-pwa/commit/8c4e4f7cdd2fa4252788fbc9889d0803bba20eef)
92
+ Thanks [@paales](https://github.com/paales)! - Added darkmode support! ☀️🌑, adds a toggle to the
93
+ hamburger menu.
94
+
95
+ ### Patch Changes
96
+
97
+ - [#1271](https://github.com/ho-nl/m2-pwa/pull/1271)
98
+ [`e0008d60d`](https://github.com/ho-nl/m2-pwa/commit/e0008d60d712603219129dd411d1985bf1ebbed1)
99
+ Thanks [@paales](https://github.com/paales)! - make sure the CartFab and MenuFab are stylable with
100
+ sx
101
+
102
+ * [#1271](https://github.com/ho-nl/m2-pwa/pull/1271)
103
+ [`5d9f8320f`](https://github.com/ho-nl/m2-pwa/commit/5d9f8320ff9621d7357fbe01319ab0cafdf9095d)
104
+ Thanks [@paales](https://github.com/paales)! - prevent layout from breaking when url has params
105
+
106
+ - [#1271](https://github.com/ho-nl/m2-pwa/pull/1271)
107
+ [`5082b8c81`](https://github.com/ho-nl/m2-pwa/commit/5082b8c8191cc3e0b4627678bf837af093513d57)
108
+ Thanks [@paales](https://github.com/paales)! - Prevent showing back button on homepage when query
109
+ parameter is present
110
+
3
111
  ## 4.0.1
4
112
 
5
113
  ### Patch Changes
package/Form/Form.tsx CHANGED
@@ -30,6 +30,8 @@ const styles = ({ theme, contained = false, background }: { theme: Theme } & For
30
30
  },
31
31
  ])
32
32
 
33
- export const Form = styled('form')<FormStyleProps>(styles)
33
+ export const Form = styled('form', {
34
+ shouldForwardProp: (prop) => prop !== 'contained',
35
+ })<FormStyleProps>(styles)
34
36
 
35
37
  export const FormDiv = styled('div')<FormStyleProps>(styles)
@@ -1,5 +1,4 @@
1
1
  import { useUp, usePrevUp, usePageContext } from '@graphcommerce/framer-next-pages'
2
- import { usePrevPageRouter } from '@graphcommerce/framer-next-pages/hooks/usePrevPageRouter'
3
2
  import { t } from '@lingui/macro'
4
3
  import PageLink from 'next/link'
5
4
  import { useRouter } from 'next/router'
@@ -10,31 +9,31 @@ import { iconChevronLeft } from '../../icons'
10
9
  export type BackProps = Omit<LinkOrButtonProps, 'onClick' | 'children'>
11
10
 
12
11
  export function useShowBack() {
13
- const router = useRouter()
12
+ const path = useRouter().asPath.split('?')[0]
14
13
  const up = useUp()
15
14
  const prevUp = usePrevUp()
16
15
  const { backSteps } = usePageContext()
17
16
 
18
- const canClickBack = backSteps > 0 && router.asPath !== prevUp?.href
17
+ const canClickBack = backSteps > 0 && path !== prevUp?.href
19
18
 
20
19
  if (canClickBack) return true
21
- if (up?.href && up.href !== router.asPath) return true
20
+ if (up?.href && up.href !== path) return true
22
21
  return false
23
22
  }
24
23
 
25
24
  export default function LayoutHeaderBack(props: BackProps) {
26
25
  const router = useRouter()
26
+ const path = router.asPath.split('?')[0]
27
27
  const up = useUp()
28
- const prevRouter = usePrevPageRouter()
29
28
  const prevUp = usePrevUp()
30
29
  const { backSteps } = usePageContext()
31
30
 
32
31
  const backIcon = <SvgIcon src={iconChevronLeft} size='medium' />
33
- const canClickBack = backSteps > 0 && router.asPath !== prevUp?.href
32
+ const canClickBack = backSteps > 0 && path !== prevUp?.href
34
33
 
35
34
  let label = t`Back`
36
- if (up?.href === prevRouter?.asPath && up?.title) label = up.title
37
- if (prevUp?.href === prevRouter?.asPath && prevUp?.title) label = prevUp.title
35
+ if (up?.href === path && up?.title) label = up.title
36
+ if (prevUp?.href === path && prevUp?.title) label = prevUp.title
38
37
 
39
38
  if (canClickBack) {
40
39
  return (
@@ -51,7 +50,7 @@ export default function LayoutHeaderBack(props: BackProps) {
51
50
  )
52
51
  }
53
52
 
54
- if (up?.href && up.href !== router.asPath)
53
+ if (up?.href && up.href !== path)
55
54
  return (
56
55
  <PageLink href={up.href} passHref>
57
56
  <LinkOrButton
@@ -97,7 +97,10 @@ export function LayoutDefault(props: LayoutDefaultProps) {
97
97
  padding: `0 ${theme.page.horizontal}`,
98
98
  position: 'sticky',
99
99
  marginTop: `calc(${theme.appShell.headerHeightMd} * -1 + calc(${fabIconSize} / 2))`,
100
- top: `calc(${theme.appShell.headerHeightMd} / 2 - 28px)`,
100
+ top: `calc(${theme.appShell.headerHeightMd} / 2 - ${responsiveVal(
101
+ 42 / 2,
102
+ 56 / 2,
103
+ )})`,
101
104
  },
102
105
  [theme.breakpoints.down('md')]: {
103
106
  position: 'fixed',
@@ -25,6 +25,7 @@ export type LayoutOverlayBaseProps = {
25
25
  children?: React.ReactNode
26
26
  className?: string
27
27
  sx?: SxProps<Theme>
28
+ sxBackdrop?: SxProps<Theme>
28
29
  } & StyleProps
29
30
 
30
31
  enum OverlayPosition {
@@ -50,6 +51,7 @@ export function LayoutOverlayBase(props: LayoutOverlayBaseProps) {
50
51
  justifySm = 'stretch',
51
52
  justifyMd = 'stretch',
52
53
  sx = [],
54
+ sxBackdrop = [],
53
55
  } = props
54
56
 
55
57
  const { scrollerRef, snap } = useScrollerContext()
@@ -104,6 +106,7 @@ export function LayoutOverlayBase(props: LayoutOverlayBaseProps) {
104
106
 
105
107
  const resize = () => {
106
108
  if (positions.open.visible.get() !== 1) return
109
+
107
110
  scroller.scrollLeft = positions.open.x.get()
108
111
  scroller.scrollTop = positions.open.y.get()
109
112
  }
@@ -187,7 +190,7 @@ export function LayoutOverlayBase(props: LayoutOverlayBaseProps) {
187
190
  WebkitTapHighlightColor: 'transparent',
188
191
  willChange: 'opacity',
189
192
  },
190
- ...(Array.isArray(sx) ? sx : [sx]),
193
+ ...(Array.isArray(sxBackdrop) ? sxBackdrop : [sxBackdrop]),
191
194
  ]}
192
195
  />
193
196
  <Scroller
@@ -195,62 +198,65 @@ export function LayoutOverlayBase(props: LayoutOverlayBaseProps) {
195
198
  grid={false}
196
199
  hideScrollbar
197
200
  onClick={onClickAway}
198
- sx={(theme) => ({
199
- display: 'grid',
200
- '&.canGrab': {
201
- cursor: 'default',
202
- },
203
- '&.mdSnapDirInline': {
204
- overflow: 'auto',
205
- },
206
-
207
- height: '100vh',
208
- '@supports (-webkit-touch-callout: none)': {
209
- height: '-webkit-fill-available',
210
- },
211
-
212
- [theme.breakpoints.down('md')]: {
213
- '&.variantSmLeft': {
214
- gridTemplate: `"overlay beforeOverlay"`,
215
- borderTopRightRadius: theme.shape.borderRadius * 3,
216
- borderBottomRightRadius: theme.shape.borderRadius * 3,
217
- },
218
- '&.variantSmRight': {
219
- gridTemplate: `"beforeOverlay overlay"`,
220
- borderTopLeftRadius: theme.shape.borderRadius * 3,
221
- borderBottomLeftRadius: theme.shape.borderRadius * 3,
222
- },
223
- '&.variantSmBottom': {
224
- borderTopLeftRadius: theme.shape.borderRadius * 3,
225
- borderTopRightRadius: theme.shape.borderRadius * 3,
226
- gridTemplate: `"beforeOverlay" "overlay"`,
227
- height: '100vh',
228
- '@supports (-webkit-touch-callout: none)': {
229
- height: '-webkit-fill-available',
230
- },
201
+ sx={[
202
+ (theme) => ({
203
+ display: 'grid',
204
+ '&.canGrab': {
205
+ cursor: 'default',
231
206
  },
232
- },
233
- [theme.breakpoints.up('md')]: {
234
- '&.variantMdLeft': {
235
- gridTemplate: `"overlay beforeOverlay"`,
236
- borderTopRightRadius: theme.shape.borderRadius * 4,
237
- borderBottomRightRadius: theme.shape.borderRadius * 4,
207
+ '&.mdSnapDirInline': {
208
+ overflow: 'auto',
238
209
  },
239
- '&.variantMdRight': {
240
- gridTemplate: `"beforeOverlay overlay"`,
241
- borderTopLeftRadius: theme.shape.borderRadius * 4,
242
- borderBottomLeftRadius: theme.shape.borderRadius * 4,
210
+
211
+ height: '100vh',
212
+ '@supports (-webkit-touch-callout: none)': {
213
+ height: '-webkit-fill-available',
243
214
  },
244
- '&.variantMdBottom': {
245
- borderTopLeftRadius: theme.shape.borderRadius * 4,
246
- borderTopRightRadius: theme.shape.borderRadius * 4,
247
- [theme.breakpoints.up('md')]: {
215
+
216
+ [theme.breakpoints.down('md')]: {
217
+ '&.variantSmLeft': {
218
+ gridTemplate: `"overlay beforeOverlay"`,
219
+ borderTopRightRadius: theme.shape.borderRadius * 3,
220
+ borderBottomRightRadius: theme.shape.borderRadius * 3,
221
+ },
222
+ '&.variantSmRight': {
223
+ gridTemplate: `"beforeOverlay overlay"`,
224
+ borderTopLeftRadius: theme.shape.borderRadius * 3,
225
+ borderBottomLeftRadius: theme.shape.borderRadius * 3,
226
+ },
227
+ '&.variantSmBottom': {
228
+ borderTopLeftRadius: theme.shape.borderRadius * 3,
229
+ borderTopRightRadius: theme.shape.borderRadius * 3,
248
230
  gridTemplate: `"beforeOverlay" "overlay"`,
249
231
  height: '100vh',
232
+ '@supports (-webkit-touch-callout: none)': {
233
+ height: '-webkit-fill-available',
234
+ },
250
235
  },
251
236
  },
252
- },
253
- })}
237
+ [theme.breakpoints.up('md')]: {
238
+ '&.variantMdLeft': {
239
+ gridTemplate: `"overlay beforeOverlay"`,
240
+ borderTopRightRadius: theme.shape.borderRadius * 4,
241
+ borderBottomRightRadius: theme.shape.borderRadius * 4,
242
+ },
243
+ '&.variantMdRight': {
244
+ gridTemplate: `"beforeOverlay overlay"`,
245
+ borderTopLeftRadius: theme.shape.borderRadius * 4,
246
+ borderBottomLeftRadius: theme.shape.borderRadius * 4,
247
+ },
248
+ '&.variantMdBottom': {
249
+ borderTopLeftRadius: theme.shape.borderRadius * 4,
250
+ borderTopRightRadius: theme.shape.borderRadius * 4,
251
+ [theme.breakpoints.up('md')]: {
252
+ gridTemplate: `"beforeOverlay" "overlay"`,
253
+ height: '100vh',
254
+ },
255
+ },
256
+ },
257
+ }),
258
+ ...(Array.isArray(sx) ? sx : [sx]),
259
+ ]}
254
260
  >
255
261
  <Box
256
262
  onClick={onClickAway}
@@ -3,7 +3,7 @@ import { useRouter } from 'next/router'
3
3
  import { useCallback } from 'react'
4
4
  import { LayoutOverlay, LayoutOverlayProps } from '../components/LayoutOverlay'
5
5
 
6
- export type LayoutOverlayState = Omit<LayoutOverlayProps, 'children' | 'sx'>
6
+ export type LayoutOverlayState = Omit<LayoutOverlayProps, 'children' | 'sx' | 'sxBackdrop'>
7
7
 
8
8
  function useQueryState<T extends ParsedUrlQuery>(builder: (query: T) => T) {
9
9
  const { query, replace } = useRouter()
@@ -63,7 +63,7 @@ export const Logo = forwardRef<HTMLDivElement, LogoProps>((props, ref) => {
63
63
  />
64
64
  )
65
65
 
66
- return router.asPath === '/' ? (
66
+ return router.asPath.split('?')[0] === '/' ? (
67
67
  <LogoContainer ref={ref} sx={sx} className={classes.parent}>
68
68
  {img}
69
69
  </LogoContainer>
@@ -1,4 +1,4 @@
1
- import { Divider, Fab, ListItem, Menu, styled, Box } from '@mui/material'
1
+ import { Divider, Fab, ListItem, Menu, styled, Box, SxProps, Theme } from '@mui/material'
2
2
  import { m } from 'framer-motion'
3
3
  import { useRouter } from 'next/router'
4
4
  import React, { useEffect } from 'react'
@@ -16,10 +16,11 @@ export type MenuFabProps = {
16
16
  search?: React.ReactNode
17
17
  menuIcon?: React.ReactNode
18
18
  closeIcon?: React.ReactNode
19
+ sx?: SxProps<Theme>
19
20
  }
20
21
 
21
22
  const { classes, selectors } = extendableComponent('MenuFab', [
22
- 'root',
23
+ 'wrapper',
23
24
  'fab',
24
25
  'shadow',
25
26
  'menu',
@@ -28,7 +29,7 @@ const { classes, selectors } = extendableComponent('MenuFab', [
28
29
  const fabIconSize = responsiveVal(42, 56) // @todo generalize this
29
30
 
30
31
  export function MenuFab(props: MenuFabProps) {
31
- const { children, secondary, search, menuIcon, closeIcon } = props
32
+ const { children, secondary, search, menuIcon, closeIcon, sx = [] } = props
32
33
  const router = useRouter()
33
34
  const [openEl, setOpenEl] = React.useState<null | HTMLElement>(null)
34
35
 
@@ -41,9 +42,9 @@ export function MenuFab(props: MenuFabProps) {
41
42
  }, [router])
42
43
 
43
44
  return (
44
- <Box sx={{ width: fabIconSize, height: fabIconSize }}>
45
+ <Box sx={[{ width: fabIconSize, height: fabIconSize }, ...(Array.isArray(sx) ? sx : [sx])]}>
45
46
  <MotionDiv
46
- className={classes.root}
47
+ className={classes.wrapper}
47
48
  sx={(theme) => ({
48
49
  [theme.breakpoints.down('md')]: {
49
50
  opacity: '1 !important',
@@ -55,7 +56,6 @@ export function MenuFab(props: MenuFabProps) {
55
56
  <Fab
56
57
  color='inherit'
57
58
  aria-label='Open Menu'
58
- size='medium'
59
59
  onClick={(event) => setOpenEl(event.currentTarget)}
60
60
  sx={(theme) => ({
61
61
  boxShadow: 'none',
@@ -72,10 +72,10 @@ export function MenuFab(props: MenuFabProps) {
72
72
  className={classes.fab}
73
73
  >
74
74
  {closeIcon ?? (
75
- <SvgIcon src={iconClose} size='medium' sx={{ display: openEl ? 'block' : 'none' }} />
75
+ <SvgIcon src={iconClose} size='large' sx={{ display: openEl ? 'block' : 'none' }} />
76
76
  )}
77
77
  {menuIcon ?? (
78
- <SvgIcon src={iconMenu} size='medium' sx={{ display: openEl ? 'none' : 'block' }} />
78
+ <SvgIcon src={iconMenu} size='large' sx={{ display: openEl ? 'none' : 'block' }} />
79
79
  )}
80
80
  </Fab>
81
81
  <MotionDiv
@@ -8,8 +8,8 @@ export type MenuFabItemProps = Omit<ListItemButtonProps<'a'>, 'href' | 'button'>
8
8
  export function MenuFabItem(props: MenuFabItemProps) {
9
9
  const { href, children, sx = [], ...listItemProps } = props
10
10
  const hrefString = href.toString()
11
- const { asPath } = useRouter()
12
- const active = hrefString === '/' ? asPath === hrefString : asPath.startsWith(hrefString)
11
+ const path = useRouter().asPath.split('?')[0]
12
+ const active = hrefString === '/' ? path === hrefString : path.startsWith(hrefString)
13
13
 
14
14
  return (
15
15
  <PageLink key={href.toString()} href={href} passHref>
package/Page/types.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { ParsedUrlQuery } from 'querystring'
2
- import { NormalizedCacheObject } from '@graphcommerce/graphql'
3
2
  import { UpPage } from '@graphcommerce/framer-next-pages/types'
3
+ // todo: remove references to GraphQL
4
+ import { NormalizedCacheObject } from '@graphcommerce/graphql'
4
5
  import { GetStaticProps as GetStaticPropsNext } from 'next'
5
6
 
6
7
  type AnyObj = Record<string, unknown>
@@ -1,8 +1,17 @@
1
1
  import { capitalize, Interpolation, Theme } from '@mui/material'
2
- import React from 'react'
3
2
 
4
3
  export type ExtendableComponent<StyleProps extends Record<string, unknown>> = {
4
+ /**
5
+ * Allows you to override the props of a component globally
6
+ *
7
+ * @see https://mui.com/customization/theme-components/#adding-new-component-variants
8
+ */
5
9
  defaultProps?: Partial<StyleProps>
10
+ /**
11
+ * Allows you to define custom styling for a variant.
12
+ *
13
+ * @see https://mui.com/customization/theme-components/#adding-new-component-variants
14
+ */
6
15
  variants?: { props: Partial<StyleProps>; style: Interpolation<{ theme: Theme }> }[]
7
16
  }
8
17
 
@@ -1,60 +1,101 @@
1
- import { ImageProps, srcToString } from '@graphcommerce/image'
2
- import { Box, SxProps, Theme } from '@mui/material'
1
+ import { ImageProps, srcToString, StaticImport } from '@graphcommerce/image'
2
+ import { styled, SxProps, Theme, useTheme } from '@mui/material'
3
3
  import { ComponentProps, forwardRef } from 'react'
4
- import { ExtendableComponent } from '../Styles/extendableComponent'
4
+ import { extendableComponent, ExtendableComponent } from '../Styles/extendableComponent'
5
5
  import { responsiveVal as rv } from '../Styles/responsiveVal'
6
+ import { svgIconStrokeWidth } from './svgIconStrokeWidth'
6
7
 
7
8
  const name = 'SvgIcon'
9
+ const parts = ['root'] as const
8
10
  type StyleProps = {
9
11
  size?: 'default' | 'inherit' | 'xxl' | 'xl' | 'large' | 'medium' | 'small' | 'xs'
10
12
  fillIcon?: boolean
11
13
  }
14
+ const { withState } = extendableComponent<StyleProps, typeof name, typeof parts>(name, parts)
12
15
 
13
- // Expose the component to be exendable in your theme.components
16
+ /** Expose the component to be exendable in your theme.components */
14
17
  declare module '@mui/material/styles/components' {
15
18
  interface Components {
16
- SvgIcon?: ExtendableComponent<StyleProps>
19
+ SvgIcon?: ExtendableComponent<StyleProps> & {
20
+ /**
21
+ * To override an icon with your own icon, provide the original src and the replacement src.
22
+ *
23
+ * ```tsx
24
+ * import { originalIcon, originalIcon2 } from '@graphcommerce/image'
25
+ * import myIcon from './myIcon.svg'
26
+ * import myIcon2 from './myIcon2.svg'
27
+ *
28
+ * overrides: [
29
+ * [originalIcon, myIcon],
30
+ * [originalIcon2, myIcon2],
31
+ * ]
32
+ * ```
33
+ */
34
+ overrides?: [StaticImport | string, StaticImport | string][]
35
+ }
17
36
  }
18
37
  }
19
38
 
20
39
  export type SvgIconProps = StyleProps &
21
40
  Pick<ImageProps, 'src'> &
22
- Pick<ComponentProps<'svg'>, 'className'> & { sx?: SxProps<Theme> }
41
+ Pick<ComponentProps<'svg'>, 'className' | 'style'> & { sx?: SxProps<Theme> }
23
42
 
24
- /** SvgIcon component is supposed to be used in combination with `icons` */
43
+ const Svg = styled('svg', { name, target: name })(() => [
44
+ {
45
+ userSelect: 'none',
46
+ width: '1em',
47
+ height: '1em',
48
+ display: 'inline-block',
49
+
50
+ strokeLinecap: 'square',
51
+ strokeLinejoin: 'miter',
52
+ strokeMiterlimit: 4,
53
+
54
+ fill: 'none',
55
+ stroke: 'currentColor',
56
+
57
+ fontSize: '1.3em',
58
+
59
+ strokeWidth: svgIconStrokeWidth(28, 148, 1.4, 0.8),
60
+
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) },
67
+
68
+ '&.fillIcon': {
69
+ fill: 'currentColor',
70
+ stroke: 'none',
71
+ },
72
+ },
73
+ ])
74
+
75
+ /**
76
+ * SvgIcon component is supposed to be used in combination with `icons`
77
+ *
78
+ * @see https://graphcommerce-docs.vercel.app/framework/icons
79
+ */
25
80
  export const SvgIcon = forwardRef<SVGSVGElement, SvgIconProps>((props, ref) => {
26
- const { className, src, size, fillIcon, sx = [], ...svgProps } = props
81
+ const { src, size, fillIcon, className, ...svgProps } = props
82
+
83
+ const srcWithOverride =
84
+ (useTheme().components?.SvgIcon?.overrides ?? []).find(
85
+ ([overrideSrc]) => overrideSrc === src,
86
+ )?.[1] ?? src
87
+
88
+ const classes = withState({ size, fillIcon })
27
89
 
28
90
  return (
29
- <Box
30
- component='svg'
91
+ <Svg
31
92
  ref={ref}
32
93
  aria-hidden='true'
33
- className={`${name} ${className ?? ''}`}
94
+ className={`${classes.root} ${className ?? ''}`}
34
95
  {...svgProps}
35
- sx={[
36
- {
37
- userSelect: 'none',
38
- width: '1.3em',
39
- height: '1.3em',
40
- strokeWidth: 1.8,
41
- strokeLinecap: 'square',
42
- strokeLinejoin: 'miter',
43
- fill: 'none',
44
- stroke: 'currentColor',
45
- },
46
- size === 'xs' && { width: rv(11, 13), height: rv(11, 13), strokeWidth: 2.1 },
47
- size === 'small' && { width: rv(12, 16), height: rv(12, 16), strokeWidth: 2.1 },
48
- size === 'medium' && { width: rv(22, 24), height: rv(22, 24), strokeWidth: 1.8 },
49
- size === 'large' && { width: rv(24, 28), height: rv(24, 28), strokeWidth: 1.4 },
50
- size === 'xl' && { width: rv(38, 62), height: rv(38, 62), strokeWidth: 1.1 },
51
- size === 'xxl' && { width: rv(96, 148), height: rv(96, 148), strokeWidth: 0.8 },
52
- fillIcon === true && { fill: 'currentColor', stroke: `none` },
53
- ...(Array.isArray(sx) ? sx : [sx]),
54
- ]}
55
96
  >
56
- <use href={`${srcToString(src)}#icon`} />
57
- </Box>
97
+ <use href={`${srcToString(srcWithOverride)}#icon`} />
98
+ </Svg>
58
99
  )
59
100
  })
60
101
  SvgIcon.displayName = 'SvgIcon'
@@ -0,0 +1,2 @@
1
+ export * from './SvgIcon'
2
+ export * from './svgIconStrokeWidth'
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Responsive strokeWidth calculation:
3
+ *
4
+ * - We want the stoke width to be inverse to the width of the element
5
+ * - When the width is 96 we want strokeWidth to be 0.5
6
+ * - When the width is 48 we want strokeWidth to be 1
7
+ * - When the width is 24 we want strokeWidth to be 2
8
+ * - To achieve this we have actual width of the element as defined by '1em'.
9
+ * - We use the calc property to calculate the strokeWidth.
10
+ *
11
+ * Sensible values are:
12
+ *
13
+ * - `lowSize`: 10-20
14
+ * - `highSize`: 50-150
15
+ * - `lowStroke`: 0.5-2
16
+ * - `highStroke`: 0.2-1
17
+ */
18
+ export function svgIconStrokeWidth(
19
+ lowSize: number,
20
+ highSize: number,
21
+ lowStroke: number,
22
+ highStroke: number,
23
+ ) {
24
+ const val = `calc(${lowStroke}px - ((1em - ${lowSize}px) / (${highSize} - ${lowSize}) * (${lowStroke} - ${highStroke})))`
25
+ return val
26
+ }
@@ -0,0 +1,119 @@
1
+ import { Trans } from '@lingui/macro'
2
+ import {
3
+ Theme,
4
+ ThemeProvider,
5
+ useMediaQuery,
6
+ Fab,
7
+ FabProps,
8
+ ListItemButton,
9
+ ListItemIcon,
10
+ ListItemText,
11
+ ListItemButtonProps,
12
+ } from '@mui/material'
13
+ import { useRouter } from 'next/router'
14
+ import { createContext, useContext, useEffect, useMemo, useState } from 'react'
15
+ import { SvgIcon } from '../SvgIcon/SvgIcon'
16
+ import { iconMoon, iconSun } from '../icons'
17
+
18
+ type Mode = 'dark' | 'light'
19
+ type UserMode = 'auto' | Mode
20
+
21
+ type ColorModeContext = {
22
+ userMode: UserMode
23
+ browserMode: Mode
24
+ currentMode: Mode
25
+ toggle: () => void
26
+ }
27
+
28
+ export const colorModeContext = createContext(undefined as unknown as ColorModeContext)
29
+ colorModeContext.displayName = 'ColorModeContext'
30
+
31
+ type ThemeProviderProps = {
32
+ // eslint-disable-next-line react/no-unused-prop-types
33
+ light: Theme
34
+ // eslint-disable-next-line react/no-unused-prop-types
35
+ dark: Theme
36
+
37
+ children: React.ReactNode
38
+ }
39
+
40
+ /**
41
+ * Wrapper around `import { ThemeProvider } from '@mui/material'`
42
+ *
43
+ * The multi DarkLightModeThemeProvider allows switching between light and dark mode based on URL
44
+ * and on user input.
45
+ *
46
+ * If you *just* wan't a single theme, use the import { ThemeProvider } from '@mui/material' instead.
47
+ */
48
+ export function DarkLightModeThemeProvider(props: ThemeProviderProps) {
49
+ const { children, light, dark } = props
50
+
51
+ // todo: Save this in local storage
52
+ const [userMode, setUserMode] = useState<UserMode>('auto')
53
+ const browserMode: Mode = useMediaQuery('(prefers-color-scheme: dark)') ? 'dark' : 'light'
54
+
55
+ // If the user has set a mode, use that. Otherwise, use the browser mode.
56
+ const currentMode = userMode === 'auto' ? browserMode : userMode
57
+ const theme = currentMode === 'light' ? light : dark
58
+
59
+ // If a URL parameter is present, switch from auto to light or dark mode
60
+ const { asPath } = useRouter()
61
+ useEffect(() => {
62
+ if (asPath.includes('darkmode')) setUserMode('dark')
63
+ }, [asPath])
64
+
65
+ // Create the context
66
+ const colorContext: ColorModeContext = useMemo(
67
+ () => ({
68
+ browserMode,
69
+ userMode,
70
+ currentMode,
71
+ toggle: () => setUserMode(currentMode === 'light' ? 'dark' : 'light'),
72
+ }),
73
+ [browserMode, currentMode, userMode],
74
+ )
75
+
76
+ return (
77
+ <colorModeContext.Provider value={colorContext}>
78
+ <ThemeProvider theme={theme}>{children}</ThemeProvider>
79
+ </colorModeContext.Provider>
80
+ )
81
+ }
82
+
83
+ export function useColorMode() {
84
+ return useContext(colorModeContext)
85
+ }
86
+
87
+ export function DarkLightModeToggleFab(props: Omit<FabProps, 'onClick'>) {
88
+ const { currentMode, toggle } = useColorMode()
89
+ return (
90
+ <Fab size='large' color='inherit' onClick={toggle} {...props}>
91
+ <SvgIcon src={currentMode === 'light' ? iconMoon : iconSun} size='large' />
92
+ </Fab>
93
+ )
94
+ }
95
+
96
+ /**
97
+ * A button that switches between light and dark mode
98
+ *
99
+ * To disable this functionality
100
+ */
101
+ export function DarkLightModeMenuSecondaryItem(props: ListItemButtonProps) {
102
+ const { sx = [] } = props
103
+ const { currentMode, toggle } = useColorMode()
104
+
105
+ return (
106
+ <ListItemButton {...props} sx={[{}, ...(Array.isArray(sx) ? sx : [sx])]} dense onClick={toggle}>
107
+ <ListItemIcon sx={{ minWidth: 'unset', paddingRight: '8px' }}>
108
+ <SvgIcon src={currentMode === 'light' ? iconMoon : iconSun} size='medium' />
109
+ </ListItemIcon>
110
+ <ListItemText>
111
+ {currentMode === 'light' ? (
112
+ <Trans>Switch to Dark Mode</Trans>
113
+ ) : (
114
+ <Trans>Switch to Light Mode</Trans>
115
+ )}
116
+ </ListItemText>
117
+ </ListItemButton>
118
+ )
119
+ }
package/Theme/index.ts CHANGED
@@ -2,3 +2,4 @@ export * from './MuiSlider'
2
2
  export * from './MuiButton'
3
3
  export * from './MuiSnackbar'
4
4
  export * from './themeDefaults'
5
+ export * from './DarkLightModeThemeProvider'
package/icons/index.tsx CHANGED
@@ -36,3 +36,5 @@ export { default as iconParty } from './happy-face.svg'
36
36
  export { default as iconStar } from './star.svg'
37
37
  export { default as iconEmailOutline } from './envelope-alt.svg'
38
38
  export { default as icon404 } from './explore.svg'
39
+ export { default as iconSun } from './sun.svg'
40
+ export { default as iconMoon } from './moon.svg'
package/index.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  export * from './AnimatedRow'
2
- export * from './ApolloError'
3
2
  export * from './Blog/BlogAuthor'
4
3
  export * from './Blog/BlogContent'
5
4
  export * from './Blog/BlogHeader'
@@ -59,7 +58,7 @@ export * from './Snackbar/MessageSnackbarImpl'
59
58
  export * from './StarRatingField'
60
59
  export * from './Stepper/Stepper'
61
60
  export * from './Styles'
62
- export * from './SvgIcon/SvgIcon'
61
+ export * from './SvgIcon'
63
62
  export * from './TextInputNumber'
64
63
  export * from './Theme'
65
64
  export * from './TimeAgo'
package/package.json CHANGED
@@ -2,13 +2,16 @@
2
2
  "name": "@graphcommerce/next-ui",
3
3
  "homepage": "https://www.graphcommerce.org/",
4
4
  "repository": "github:graphcommerce-org/graphcommerce",
5
- "version": "4.0.1",
5
+ "version": "4.1.2",
6
6
  "author": "",
7
7
  "license": "MIT",
8
- "scripts": {
9
- "dev": "next",
10
- "build": "next build",
11
- "start": "next start"
8
+ "sideEffects": false,
9
+ "prettier": "@graphcommerce/prettier-config-pwa",
10
+ "eslintConfig": {
11
+ "extends": "@graphcommerce/eslint-config-pwa",
12
+ "parserOptions": {
13
+ "project": "./tsconfig.json"
14
+ }
12
15
  },
13
16
  "dependencies": {
14
17
  "@emotion/babel-preset-css-prop": "^11.2.0",
@@ -16,40 +19,31 @@
16
19
  "@emotion/react": "^11.7.1",
17
20
  "@emotion/server": "^11.4.0",
18
21
  "@emotion/styled": "^11.6.0",
19
- "@graphcommerce/framer-next-pages": "^3.0.1",
20
- "@graphcommerce/framer-scroller": "^2.0.1",
21
- "@graphcommerce/framer-utils": "^3.0.1",
22
- "@graphcommerce/graphql": "^3.0.1",
23
- "@graphcommerce/image": "^3.0.1",
22
+ "@graphcommerce/framer-next-pages": "^3.1.0",
23
+ "@graphcommerce/framer-scroller": "^2.0.3",
24
+ "@graphcommerce/framer-utils": "^3.0.3",
25
+ "@graphcommerce/image": "^3.1.0",
26
+ "react-is": "^17.0.2",
27
+ "react-schemaorg": "^2.0.0",
28
+ "schema-dts": "^1.0.0",
29
+ "type-fest": "^2.12.0"
30
+ },
31
+ "peerDependencies": {
24
32
  "@lingui/macro": "^3.13.2",
25
33
  "@mui/base": "^5.0.0-alpha.68",
26
34
  "@mui/lab": "^5.0.0-alpha.68",
27
35
  "@mui/material": "^5.4.1",
28
36
  "framer-motion": "^6.2.4",
29
- "graphql": "^16.3.0",
30
37
  "next": "^12.0.10",
31
38
  "react": "^17.0.2",
32
- "react-dom": "^17.0.2",
33
- "react-is": "^17.0.2",
34
- "react-schemaorg": "^2.0.0",
35
- "schema-dts": "^1.0.0",
36
- "type-fest": "^2.11.2"
39
+ "react-dom": "^17.0.2"
37
40
  },
38
41
  "devDependencies": {
39
- "@graphcommerce/eslint-config-pwa": "^4.0.1",
40
- "@graphcommerce/prettier-config-pwa": "^4.0.1",
41
- "@graphcommerce/typescript-config-pwa": "^4.0.1",
42
- "@playwright/test": "^1.18.1",
42
+ "@graphcommerce/eslint-config-pwa": "^4.0.3",
43
+ "@graphcommerce/prettier-config-pwa": "^4.0.2",
44
+ "@graphcommerce/typescript-config-pwa": "^4.0.2",
45
+ "@playwright/test": "^1.19.1",
43
46
  "@types/react-is": "^17.0.3",
44
- "graphql-tag": "2.12.6",
45
47
  "typescript": "^4.5.5"
46
- },
47
- "sideEffects": false,
48
- "prettier": "@graphcommerce/prettier-config-pwa",
49
- "eslintConfig": {
50
- "extends": "@graphcommerce/eslint-config-pwa",
51
- "parserOptions": {
52
- "project": "./tsconfig.json"
53
- }
54
48
  }
55
49
  }
@@ -1,58 +0,0 @@
1
- import { ApolloError } from '@graphcommerce/graphql'
2
- import { AlertProps, Alert, Box, SxProps, Theme } from '@mui/material'
3
- import { AnimatePresence } from 'framer-motion'
4
- import { AnimatedRow } from '../AnimatedRow'
5
- import { extendableComponent } from '../Styles/extendableComponent'
6
-
7
- const { classes, selectors } = extendableComponent('ApolloErrorAlert', ['root', 'alert'] as const)
8
-
9
- export type ApolloErrorAlertProps = {
10
- error?: ApolloError
11
- graphqlErrorAlertProps?: Omit<AlertProps, 'severity'>
12
- networkErrorAlertProps?: Omit<AlertProps, 'severity'>
13
- sx?: SxProps<Theme>
14
- }
15
- export default function ApolloErrorAlert(props: ApolloErrorAlertProps) {
16
- const { error, graphqlErrorAlertProps, networkErrorAlertProps, sx } = props
17
-
18
- return (
19
- <AnimatePresence initial={false}>
20
- {error && (
21
- <AnimatedRow key='alerts'>
22
- <Box sx={sx} className={classes.root}>
23
- <AnimatePresence initial={false}>
24
- {error.graphQLErrors.map((e, index) => (
25
- // eslint-disable-next-line react/no-array-index-key
26
- <AnimatedRow key={index}>
27
- <div className={classes.alert}>
28
- <Alert severity='error' {...graphqlErrorAlertProps}>
29
- {e.message}
30
- </Alert>
31
- </div>
32
- </AnimatedRow>
33
- ))}
34
- {error.networkError && (
35
- <AnimatedRow key='networkError'>
36
- <Box
37
- sx={(theme) => ({
38
- paddingTop: theme.spacings.xxs,
39
- paddingBottom: theme.spacings.xxs,
40
- })}
41
- className={classes.alert}
42
- key='networkError'
43
- >
44
- <Alert severity='error' {...networkErrorAlertProps}>
45
- Network Error: {error.networkError.message}
46
- </Alert>
47
- </Box>
48
- </AnimatedRow>
49
- )}
50
- </AnimatePresence>
51
- </Box>
52
- </AnimatedRow>
53
- )}
54
- </AnimatePresence>
55
- )
56
- }
57
-
58
- ApolloErrorAlert.selectors = selectors
@@ -1,31 +0,0 @@
1
- import { ApolloError } from '@graphcommerce/graphql'
2
- import { AlertProps } from '@mui/material'
3
- import { FullPageMessage, FullPageMessageProps } from '../FullPageMessage'
4
- import ApolloErrorAlert from './ApolloErrorAlert'
5
-
6
- export type ApolloErrorFullPageProps = {
7
- error?: ApolloError
8
- graphqlErrorAlertProps?: Omit<AlertProps, 'severity'>
9
- networkErrorAlertProps?: Omit<AlertProps, 'severity'>
10
- } & Omit<FullPageMessageProps, 'title' | 'description'>
11
-
12
- export default function ApolloErrorFullPage(props: ApolloErrorFullPageProps) {
13
- const {
14
- error,
15
- graphqlErrorAlertProps,
16
- networkErrorAlertProps,
17
- children,
18
- ...fullPageMessageProps
19
- } = props
20
-
21
- const singleError = error?.graphQLErrors.length === 1
22
-
23
- return (
24
- <FullPageMessage
25
- title={singleError ? error?.graphQLErrors[0].message : 'Several errors occured'}
26
- {...fullPageMessageProps}
27
- >
28
- {singleError ? children : <ApolloErrorAlert error={error} />}
29
- </FullPageMessage>
30
- )
31
- }
@@ -1,35 +0,0 @@
1
- import { ApolloError } from '@graphcommerce/graphql'
2
- import { Trans } from '@lingui/macro'
3
- import { Button } from '@mui/material'
4
- import { MessageSnackbar } from '../Snackbar/MessageSnackbar'
5
- import { MessageSnackbarImplProps } from '../Snackbar/MessageSnackbarImpl'
6
-
7
- export type ApolloErrorSnackbarProps = {
8
- error?: ApolloError
9
- } & Pick<MessageSnackbarImplProps, 'action' | 'onClose'>
10
-
11
- export default function ApolloErrorSnackbar(props: ApolloErrorSnackbarProps) {
12
- const { error, action, ...passedProps } = props
13
-
14
- if (!error) return null
15
- return (
16
- <MessageSnackbar
17
- variant='pill'
18
- severity='error'
19
- {...passedProps}
20
- open={!!error}
21
- action={
22
- action ?? (
23
- <Button size='medium' variant='pill' color='secondary'>
24
- <Trans>Ok</Trans>
25
- </Button>
26
- )
27
- }
28
- >
29
- <>
30
- {error.graphQLErrors.map((e) => e.message)}
31
- {error.networkError && <>Network Error: {error.networkError.message}</>}
32
- </>
33
- </MessageSnackbar>
34
- )
35
- }
@@ -1,6 +0,0 @@
1
- export * from './ApolloErrorAlert'
2
- export { default as ApolloErrorAlert } from './ApolloErrorAlert'
3
- export * from './ApolloErrorFullPage'
4
- export { default as ApolloErrorFullPage } from './ApolloErrorFullPage'
5
- export * from './ApolloErrorSnackbar'
6
- export { default as ApolloErrorSnackbar } from './ApolloErrorSnackbar'