@graphcommerce/next-ui 4.7.1 → 4.8.1

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.
@@ -0,0 +1,129 @@
1
+ import { Theme } from '@emotion/react'
2
+ import { SxProps, ButtonBase, Box } from '@mui/material'
3
+ import React, { FormEvent } from 'react'
4
+
5
+ type ActionCardProps = {
6
+ sx?: SxProps<Theme>
7
+ title?: string | React.ReactNode
8
+ image?: React.ReactNode
9
+ action?: React.ReactNode
10
+ details?: React.ReactNode
11
+ secondaryAction?: React.ReactNode
12
+ onClick?: (e: FormEvent<HTMLButtonElement>, v: string) => void
13
+ onChange?: (e: FormEvent<HTMLButtonElement>, v: string) => void
14
+ selected?: boolean
15
+ hidden?: boolean | (() => boolean)
16
+ value: string
17
+ reset?: React.ReactNode
18
+ }
19
+
20
+ export function ActionCard(props: ActionCardProps) {
21
+ const {
22
+ title,
23
+ image,
24
+ action,
25
+ details,
26
+ secondaryAction,
27
+ sx = [],
28
+ onChange,
29
+ onClick,
30
+ value,
31
+ selected,
32
+ hidden,
33
+ reset,
34
+ } = props
35
+
36
+ const handleChange = (event: FormEvent<HTMLButtonElement>) => onChange?.(event, value)
37
+ const handleClick = (event: FormEvent<HTMLButtonElement>) => {
38
+ if (onClick) {
39
+ onClick(event, value)
40
+ if (event.isDefaultPrevented()) return
41
+ }
42
+ handleChange(event)
43
+ }
44
+
45
+ const actionButtonStyles: SxProps = {
46
+ '& .MuiButton-root': {
47
+ '&.MuiButton-textSecondary': {
48
+ padding: '5px',
49
+ margin: '-5px',
50
+ '&:hover': {
51
+ background: 'none',
52
+ },
53
+ },
54
+ },
55
+ }
56
+
57
+ return (
58
+ <ButtonBase
59
+ component='button'
60
+ className='ActionCard-root'
61
+ onClick={handleClick}
62
+ onChange={handleChange}
63
+ value={value}
64
+ sx={[
65
+ {
66
+ display: 'grid',
67
+ width: '100%',
68
+ gridTemplateColumns: 'min-content',
69
+ gridTemplateAreas: `
70
+ "image title action"
71
+ "image details secondaryDetails"
72
+ "image secondaryAction additionalDetails"
73
+ "additionalContent additionalContent additionalContent"
74
+ `,
75
+ justifyContent: 'unset',
76
+ },
77
+ (theme) => ({
78
+ typography: 'body1',
79
+ textAlign: 'left',
80
+ background: theme.palette.background.paper,
81
+ padding: `calc(${theme.spacings.xs} + 1px)`,
82
+ columnGap: theme.spacings.xxs,
83
+ border: `1px solid ${theme.palette.divider}`,
84
+ borderBottomColor: `transparent`,
85
+ '&:first-of-type': {
86
+ borderTopLeftRadius: theme.shape.borderRadius,
87
+ borderTopRightRadius: theme.shape.borderRadius,
88
+ },
89
+ '&:last-of-type': {
90
+ borderBottomLeftRadius: theme.shape.borderRadius,
91
+ borderBottomRightRadius: theme.shape.borderRadius,
92
+ borderBottom: `1px solid ${theme.palette.divider}`,
93
+ },
94
+ }),
95
+ !!hidden && {
96
+ display: 'none',
97
+ },
98
+ !!selected &&
99
+ ((theme) => ({
100
+ border: `2px solid ${theme.palette.secondary.main} !important`,
101
+ borderTopLeftRadius: theme.shape.borderRadius,
102
+ borderTopRightRadius: theme.shape.borderRadius,
103
+ borderBottomLeftRadius: theme.shape.borderRadius,
104
+ borderBottomRightRadius: theme.shape.borderRadius,
105
+ padding: theme.spacings.xs,
106
+ })),
107
+ ...(Array.isArray(sx) ? sx : [sx]),
108
+ ]}
109
+ >
110
+ {image && <Box sx={{ gridArea: 'image', justifySelf: 'center', padding: 1 }}>{image}</Box>}
111
+ {title && <Box sx={{ gridArea: 'title', fontWeight: 'bold' }}>{title}</Box>}
112
+ {action && (
113
+ <Box
114
+ sx={{
115
+ gridArea: 'action',
116
+ textAlign: 'right',
117
+ ...actionButtonStyles,
118
+ }}
119
+ >
120
+ {!selected ? action : reset}
121
+ </Box>
122
+ )}
123
+ {details && <Box sx={{ gridArea: 'details', color: 'text.secondary' }}>{details}</Box>}
124
+ {secondaryAction && (
125
+ <Box sx={{ gridArea: 'secondaryAction', ...actionButtonStyles }}>{secondaryAction}</Box>
126
+ )}
127
+ </ButtonBase>
128
+ )
129
+ }
@@ -0,0 +1,110 @@
1
+ import { Box } from '@mui/material'
2
+ import React from 'react'
3
+ import { isFragment } from 'react-is'
4
+
5
+ type MultiSelect = {
6
+ multiple: true
7
+ value: string[]
8
+
9
+ onChange?: (event: React.MouseEvent<HTMLElement>, value: string[]) => void
10
+ }
11
+ type Select = {
12
+ multiple?: false
13
+ value: string
14
+
15
+ /** Value is null when deselected when not required */
16
+ onChange?: (event: React.MouseEvent<HTMLElement>, value: string | null) => void
17
+ }
18
+
19
+ type ActionCardListProps<SelectOrMulti = MultiSelect | Select> = {
20
+ children?: React.ReactNode
21
+ required?: boolean
22
+ error?: boolean
23
+ } & SelectOrMulti
24
+
25
+ function isMulti(props: ActionCardListProps): props is ActionCardListProps<MultiSelect> {
26
+ return props.multiple === true
27
+ }
28
+
29
+ function isValueSelected(value: string, candidate: string | string[]) {
30
+ if (candidate === undefined || value === undefined) return false
31
+ if (Array.isArray(candidate)) return candidate.indexOf(value) >= 0
32
+ return value === candidate
33
+ }
34
+
35
+ export function ActionCardList(props: ActionCardListProps) {
36
+ const { children, required, value, error = false } = props
37
+
38
+ const handleChange = isMulti(props)
39
+ ? (event: React.MouseEvent<HTMLElement, MouseEvent>, buttonValue: string) => {
40
+ const { onChange } = props
41
+ const index = Boolean(value) && value?.indexOf(buttonValue)
42
+ let newValue: string[]
43
+
44
+ if (Array.isArray(value) && value.length && index && index >= 0) {
45
+ newValue = value.slice()
46
+ newValue.splice(index, 1)
47
+ } else {
48
+ newValue = value ? [...value, buttonValue] : [buttonValue]
49
+ }
50
+ onChange?.(event, newValue)
51
+ }
52
+ : (event: React.MouseEvent<HTMLElement, MouseEvent>, buttonValue: string) => {
53
+ const { onChange } = props
54
+
55
+ if (value === buttonValue) return
56
+ if (required) onChange?.(event, buttonValue)
57
+ else onChange?.(event, value === buttonValue ? null : buttonValue)
58
+ }
59
+
60
+ return (
61
+ <Box
62
+ sx={[
63
+ error &&
64
+ ((theme) => ({
65
+ '& .ActionCard-root': {
66
+ borderLeft: 2,
67
+ borderRight: 2,
68
+ borderLeftColor: 'error.main',
69
+ borderRightColor: 'error.main',
70
+ paddingLeft: theme.spacings.xs,
71
+ paddingRight: theme.spacings.xs,
72
+ },
73
+ '& .ActionCard-root:first-of-type': {
74
+ borderTop: 2,
75
+ borderTopColor: 'error.main',
76
+ paddingTop: theme.spacings.xs,
77
+ },
78
+ '& .ActionCard-root:last-of-type': {
79
+ borderBottom: 2,
80
+ borderBottomColor: 'error.main',
81
+ paddingBottom: theme.spacings.xs,
82
+ },
83
+ })),
84
+ ]}
85
+ >
86
+ {React.Children.map(children, (child) => {
87
+ if (!React.isValidElement(child)) return null
88
+
89
+ if (process.env.NODE_ENV !== 'production') {
90
+ if (isFragment(child)) {
91
+ console.error(
92
+ [
93
+ "@graphcommerce/next-ui: The ActionCardList component doesn't accept a Fragment as a child.",
94
+ 'Consider providing an array instead.',
95
+ ].join('\n'),
96
+ )
97
+ }
98
+ }
99
+
100
+ return React.cloneElement(child, {
101
+ onClick: handleChange,
102
+ selected:
103
+ child.props.selected === undefined
104
+ ? isValueSelected(child.props.value as string, value)
105
+ : child.props.selected,
106
+ })
107
+ })}
108
+ </Box>
109
+ )
110
+ }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,76 @@
1
1
  # Change Log
2
2
 
3
+ ## 4.8.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#1477](https://github.com/graphcommerce-org/graphcommerce/pull/1477) [`a9df81310`](https://github.com/graphcommerce-org/graphcommerce/commit/a9df81310c051876dd82fb2819105dece47cc213) Thanks [@paales](https://github.com/paales)! - Revert faulty background color on LayoutDefault
8
+
9
+ * [#1477](https://github.com/graphcommerce-org/graphcommerce/pull/1477) [`f167f9963`](https://github.com/graphcommerce-org/graphcommerce/commit/f167f99630966a7de43717937d43669e66132494) Thanks [@paales](https://github.com/paales)! - LayoutOverlay performance improvements
10
+
11
+ * Updated dependencies [[`55c2dcde7`](https://github.com/graphcommerce-org/graphcommerce/commit/55c2dcde7869ee51b84494af653b3edfd43904a4), [`597e2f413`](https://github.com/graphcommerce-org/graphcommerce/commit/597e2f413bdb5b76793b40ab631ce61390e26e81), [`f167f9963`](https://github.com/graphcommerce-org/graphcommerce/commit/f167f99630966a7de43717937d43669e66132494)]:
12
+ - @graphcommerce/framer-scroller@2.1.12
13
+ - @graphcommerce/framer-next-pages@3.2.2
14
+ - @graphcommerce/framer-utils@3.1.3
15
+ - @graphcommerce/image@3.1.6
16
+
17
+ ## 4.8.0
18
+
19
+ ### Minor Changes
20
+
21
+ - [#1462](https://github.com/graphcommerce-org/graphcommerce/pull/1462) [`3ac90b57c`](https://github.com/graphcommerce-org/graphcommerce/commit/3ac90b57c68b96f9d81771d6664ed9435a28fc1d) Thanks [@mikekeehnen](https://github.com/mikekeehnen)! - Added translation for the pagination
22
+
23
+ ### Patch Changes
24
+
25
+ - [#1467](https://github.com/graphcommerce-org/graphcommerce/pull/1467) [`0363b9671`](https://github.com/graphcommerce-org/graphcommerce/commit/0363b9671db7c2932321d97faf6f1eb385238397) Thanks [@timhofman](https://github.com/timhofman)! - optional feedback message upon adding products to wishlist
26
+
27
+ - Updated dependencies []:
28
+ - @graphcommerce/framer-scroller@2.1.11
29
+
30
+ ## 4.7.2
31
+
32
+ ### Patch Changes
33
+
34
+ - [#1451](https://github.com/graphcommerce-org/graphcommerce/pull/1451) [`f698ff85d`](https://github.com/graphcommerce-org/graphcommerce/commit/f698ff85df6bb0922288471bb3c81856091b8061) Thanks [@paales](https://github.com/paales)! - Removed all occurences of @lingui/macro and moved to @lingui/macro / @lingui/core in preparation to move to swc.
35
+
36
+ Since we've removed @lingui/macro, all occurences need to be replaced with @lingui/core and @lingui/react.
37
+
38
+ All occurences of `<Trans>` and `t` need to be replaced:
39
+
40
+ ```tsx
41
+ import { Trans, t } from '@lingui/macro'
42
+
43
+ function MyComponent() {
44
+ const foo = 'bar'
45
+ return (
46
+ <div aria-label={t`Account ${foo}`}>
47
+ <Trans>My Translation {foo}</Trans>
48
+ </div>
49
+ )
50
+ }
51
+ ```
52
+
53
+ Needs to be replaced with:
54
+
55
+ ```tsx
56
+ import { Trans } from '@lingui/react'
57
+ import { i18n } from '@lingui/core'
58
+
59
+ function MyComponent() {
60
+ const foo = 'bar'
61
+ return (
62
+ <div aria-label={i18n._(/* i18n */ `Account {foo}`, { foo })}>
63
+ <Trans key='My Translation {foo}' values={{ foo }}></Trans>
64
+ </div>
65
+ )
66
+ }
67
+ ```
68
+
69
+ [More examples for Trans](https://lingui.js.org/ref/macro.html#examples-of-jsx-macros) and [more examples for `t`](https://lingui.js.org/ref/macro.html#examples-of-js-macros)
70
+
71
+ - Updated dependencies [[`f698ff85d`](https://github.com/graphcommerce-org/graphcommerce/commit/f698ff85df6bb0922288471bb3c81856091b8061)]:
72
+ - @graphcommerce/framer-scroller@2.1.10
73
+
3
74
  ## 4.7.1
4
75
 
5
76
  ### Patch Changes
@@ -1,5 +1,5 @@
1
1
  import { useUp, usePrevUp, usePageContext } from '@graphcommerce/framer-next-pages'
2
- import { t } from '@lingui/macro'
2
+ import { i18n } from '@lingui/core'
3
3
  import { Box, SxProps, Theme } from '@mui/material'
4
4
  import PageLink from 'next/link'
5
5
  import { useRouter } from 'next/router'
@@ -46,7 +46,7 @@ export function LayoutHeaderBack(props: BackProps) {
46
46
  const backIcon = <IconSvg src={iconChevronLeft} size='medium' />
47
47
  const canClickBack = backSteps > 0 && path !== prevUp?.href
48
48
 
49
- let label = t`Back`
49
+ let label = i18n._(/* i18n */ `Back`)
50
50
  if (up?.href === path && up?.title) label = up.title
51
51
  if (prevUp?.href === path && prevUp?.title) label = prevUp.title
52
52
 
@@ -177,7 +177,6 @@ export function LayoutOverlayBase(incommingProps: LayoutOverlayBaseProps) {
177
177
  useEffect(() => positions.open.visible.onChange((o) => o === 0 && closeOverlay()))
178
178
 
179
179
  // Measure the offset of the overlay in the scroller.
180
-
181
180
  const offsetY = useMotionValue(0)
182
181
  useEffect(() => {
183
182
  if (!overlayRef.current) return () => {}
@@ -195,21 +194,20 @@ export function LayoutOverlayBase(incommingProps: LayoutOverlayBaseProps) {
195
194
 
196
195
  const onClickAway = useCallback(
197
196
  (event: React.MouseEvent<HTMLDivElement>) => {
198
- const isTarget = event.target === scrollerRef.current || event.target === beforeRef.current
197
+ const isTarget =
198
+ event.target === scrollerRef.current ||
199
+ event.target === beforeRef.current ||
200
+ event.target === overlayRef.current
199
201
  if (isTarget && snap.get()) closeOverlay()
200
202
  },
201
203
  [closeOverlay, scrollerRef, snap],
202
204
  )
203
205
 
204
- const pointerEvents = useTransform(position, (p) =>
205
- p === OverlayPosition.CLOSED ? 'none' : 'auto',
206
- )
207
-
208
206
  return (
209
207
  <>
210
208
  <MotionDiv
211
209
  className={classes.backdrop}
212
- style={{ opacity: positions.open.visible, pointerEvents }}
210
+ style={{ opacity: positions.open.visible }}
213
211
  sx={[
214
212
  {
215
213
  zIndex: -1,
@@ -233,7 +231,6 @@ export function LayoutOverlayBase(incommingProps: LayoutOverlayBaseProps) {
233
231
  grid={false}
234
232
  hideScrollbar
235
233
  onClick={onClickAway}
236
- style={{ pointerEvents }}
237
234
  sx={[
238
235
  (theme) => ({
239
236
  overscrollBehavior: 'contain',
@@ -320,9 +317,9 @@ export function LayoutOverlayBase(incommingProps: LayoutOverlayBaseProps) {
320
317
  <Box
321
318
  className={classes.overlay}
322
319
  ref={overlayRef}
320
+ onClick={onClickAway}
323
321
  sx={(theme) => ({
324
322
  display: 'grid',
325
- pointerEvents: 'none',
326
323
  gridArea: 'overlay',
327
324
  scrollSnapAlign: 'start',
328
325
  scrollSnapStop: 'always',
@@ -1,3 +1,4 @@
1
+ import { Trans } from '@lingui/react'
1
2
  import { PaginationProps, Box, SxProps, Theme, IconButton } from '@mui/material'
2
3
  import usePagination, { UsePaginationItem } from '@mui/material/usePagination'
3
4
  import React from 'react'
@@ -75,7 +76,9 @@ export function Pagination(props: PagePaginationProps) {
75
76
  >
76
77
  {page === 1 ? chevronLeft : renderLink(page - 1, chevronLeft, prevBtnProps)}
77
78
 
78
- <Box typography='body1'>Page {`${page} of ${Math.max(1, count)}`}</Box>
79
+ <Box typography='body1'>
80
+ <Trans id='Page {page} of {count}' values={{ page, count: Math.max(1, count) }} />
81
+ </Box>
79
82
 
80
83
  {page === count ? chevronRight : renderLink(page + 1, chevronRight, nextBtnProps)}
81
84
  </Box>
@@ -1,4 +1,4 @@
1
- import { Trans } from '@lingui/macro'
1
+ import { Trans } from '@lingui/react'
2
2
  import { Button } from '@mui/material'
3
3
  import { MessageSnackbar } from './MessageSnackbar'
4
4
  import { MessageSnackbarImplProps } from './MessageSnackbarImpl'
@@ -15,7 +15,7 @@ export function ErrorSnackbar(props: ErrorSnackbarProps) {
15
15
  action={
16
16
  action ?? (
17
17
  <Button size='medium' variant='pill' color='secondary'>
18
- <Trans>Ok</Trans>
18
+ <Trans id='Ok' />
19
19
  </Button>
20
20
  )
21
21
  }
@@ -64,11 +64,18 @@ export default function MessageSnackbarImpl(props: MessageSnackbarImplProps) {
64
64
  setShowSnackbar(!!open)
65
65
  }, [open])
66
66
 
67
- const hideSnackbar = () => {
67
+ const hideSnackbar = (e) => {
68
+ e.preventDefault()
69
+
68
70
  setShowSnackbar(false)
69
71
  onClose?.()
70
72
  }
71
73
 
74
+ const preventAnimationBubble: React.MouseEventHandler<HTMLButtonElement> = (e) => {
75
+ e.preventDefault()
76
+ e.stopPropagation()
77
+ }
78
+
72
79
  let icon = iconCheckmark
73
80
  if (severity === 'error') icon = iconSadFace
74
81
 
@@ -131,6 +138,7 @@ export default function MessageSnackbarImpl(props: MessageSnackbarImplProps) {
131
138
  aria-label='Close'
132
139
  size='small'
133
140
  onClick={hideSnackbar}
141
+ onMouseDown={preventAnimationBubble}
134
142
  sx={(theme) => ({
135
143
  backgroundColor: lighten(theme.palette.background.paper, 0.1),
136
144
  })}
@@ -1,4 +1,4 @@
1
- import { Trans } from '@lingui/macro'
1
+ import { Trans } from '@lingui/react'
2
2
  import {
3
3
  Theme,
4
4
  ThemeProvider,
@@ -43,7 +43,7 @@ type ThemeProviderProps = {
43
43
  * The multi DarkLightModeThemeProvider allows switching between light and dark mode based on URL
44
44
  * and on user input.
45
45
  *
46
- * If you *just* wan't a single theme, use the import { ThemeProvider } from '@mui/material' instead.
46
+ * If you _just_ wan't a single theme, use the import { ThemeProvider } from '@mui/material' instead.
47
47
  */
48
48
  export function DarkLightModeThemeProvider(props: ThemeProviderProps) {
49
49
  const { children, light, dark } = props
@@ -109,9 +109,9 @@ export function DarkLightModeMenuSecondaryItem(props: ListItemButtonProps) {
109
109
  </ListItemIcon>
110
110
  <ListItemText>
111
111
  {currentMode === 'light' ? (
112
- <Trans>Switch to Dark Mode</Trans>
112
+ <Trans id='Switch to Dark Mode' />
113
113
  ) : (
114
- <Trans>Switch to Light Mode</Trans>
114
+ <Trans id='Switch to Light Mode' />
115
115
  )}
116
116
  </ListItemText>
117
117
  </ListItemButton>
package/index.ts CHANGED
@@ -1,3 +1,5 @@
1
+ export * from './ActionCard/ActionCardList'
2
+ export * from './ActionCard/ActionCard'
1
3
  export * from './AnimatedRow/AnimatedRow'
2
4
  export * from './Blog/BlogAuthor/BlogAuthor'
3
5
  export * from './Blog/BlogContent/BlogContent'
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.7.1",
5
+ "version": "4.8.1",
6
6
  "author": "",
7
7
  "license": "MIT",
8
8
  "sideEffects": false,
@@ -19,16 +19,17 @@
19
19
  "@emotion/react": "^11.9.0",
20
20
  "@emotion/server": "^11.4.0",
21
21
  "@emotion/styled": "^11.6.0",
22
- "@graphcommerce/framer-next-pages": "3.2.1",
23
- "@graphcommerce/framer-scroller": "2.1.9",
24
- "@graphcommerce/framer-utils": "3.1.2",
25
- "@graphcommerce/image": "3.1.5",
22
+ "@graphcommerce/framer-next-pages": "3.2.2",
23
+ "@graphcommerce/framer-scroller": "2.1.12",
24
+ "@graphcommerce/framer-utils": "3.1.3",
25
+ "@graphcommerce/image": "3.1.6",
26
26
  "react-is": "^17.0.0",
27
27
  "react-schemaorg": "^2.0.0",
28
28
  "schema-dts": "^1.1.0"
29
29
  },
30
30
  "peerDependencies": {
31
- "@lingui/macro": "^3.13.2",
31
+ "@lingui/react": "^3.13.2",
32
+ "@lingui/core": "^3.13.2",
32
33
  "@mui/lab": "^5.0.0-alpha.68",
33
34
  "@mui/material": "5.5.3",
34
35
  "framer-motion": "^6.2.4",
@@ -37,9 +38,9 @@
37
38
  "react-dom": "^17.0.2"
38
39
  },
39
40
  "devDependencies": {
40
- "@graphcommerce/eslint-config-pwa": "^4.1.6",
41
+ "@graphcommerce/eslint-config-pwa": "^4.1.7",
41
42
  "@graphcommerce/prettier-config-pwa": "^4.0.6",
42
- "@graphcommerce/typescript-config-pwa": "^4.0.2",
43
+ "@graphcommerce/typescript-config-pwa": "^4.0.3",
43
44
  "@playwright/test": "^1.21.1",
44
45
  "@types/react-is": "^17.0.3",
45
46
  "type-fest": "^2.12.2",