@graphcommerce/next-ui 6.2.0-canary.7 → 6.2.0-canary.70

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.
@@ -122,7 +122,6 @@ export function ActionCard(props: ActionCardProps) {
122
122
  theme.shape.borderRadius * 3,
123
123
  theme.breakpoints.values,
124
124
  ),
125
-
126
125
  '&.sizeSmall': {
127
126
  px: responsiveVal(8, 12),
128
127
  py: responsiveVal(4, 6),
@@ -144,12 +143,12 @@ export function ActionCard(props: ActionCardProps) {
144
143
  '&.variantDefault': {
145
144
  '&::after': {
146
145
  content: '""',
147
- display: 'block',
148
146
  position: 'absolute',
149
147
  width: '100%',
150
148
  left: 0,
151
149
  bottom: '-1px',
152
150
  borderBottom: `1px solid ${theme.palette.divider}`,
151
+ display: 'block',
153
152
  },
154
153
  '&.selected': {
155
154
  backgroundColor:
@@ -193,7 +192,6 @@ export function ActionCard(props: ActionCardProps) {
193
192
  '&:not(:last-of-type)': {
194
193
  marginBottom: '-1px',
195
194
  },
196
-
197
195
  '&.layoutList': {
198
196
  borderRadius: 0,
199
197
  '&:first-of-type': {
@@ -299,7 +297,8 @@ export function ActionCard(props: ActionCardProps) {
299
297
  <Box
300
298
  className={classes.title}
301
299
  sx={{
302
- typography: 'subtitle2',
300
+ '&.sizeSmall': { typography: 'body2' },
301
+ '&.sizeMedium': { typography: 'body1' },
303
302
  '&.sizeLarge': { typography: 'h6' },
304
303
  }}
305
304
  >
@@ -0,0 +1,58 @@
1
+ import { Accordion, AccordionSummary, AccordionDetails, SxProps, Theme } from '@mui/material'
2
+ import { useState, ReactNode } from 'react'
3
+ import { IconSvg } from '../IconSvg'
4
+ import { iconChevronDown } from '../icons'
5
+
6
+ export type ActionCardAccordionProps = {
7
+ summary: ReactNode
8
+ details: ReactNode
9
+ right: ReactNode
10
+ sx?: SxProps<Theme>
11
+ defaultExpanded?: boolean
12
+ }
13
+
14
+ export function ActionCardAccordion(props: ActionCardAccordionProps) {
15
+ const { summary, details, right, defaultExpanded = true, sx } = props
16
+ const [expanded, setExpanded] = useState(defaultExpanded)
17
+ const handleChange = () => setExpanded(!expanded)
18
+
19
+ return (
20
+ <Accordion
21
+ square
22
+ onChange={handleChange}
23
+ expanded={expanded}
24
+ variant='outlined'
25
+ disableGutters
26
+ sx={[
27
+ (theme) => ({
28
+ backgroundColor: 'transparent ',
29
+ '&.Mui-expanded': { my: 0 },
30
+ '::before': { display: 'none' },
31
+ border: 'none',
32
+ '&:not(.Mui-expanded)': { borderBottom: `1px solid ${theme.palette.divider}` },
33
+ }),
34
+ ...(Array.isArray(sx) ? sx : [sx]),
35
+ ]}
36
+ >
37
+ <AccordionSummary
38
+ onClick={(e) => e.preventDefault()}
39
+ expandIcon={<IconSvg src={iconChevronDown} />}
40
+ sx={{
41
+ px: 0,
42
+ typography: 'h6',
43
+ minHeight: 54,
44
+ '& .MuiAccordionSummary-content': {
45
+ display: 'flex',
46
+ justifyContent: 'space-between',
47
+ alignItems: 'center',
48
+ my: 0,
49
+ },
50
+ }}
51
+ >
52
+ <div>{summary}</div>
53
+ {right}
54
+ </AccordionSummary>
55
+ <AccordionDetails sx={{ p: 0 }}>{details}</AccordionDetails>
56
+ </Accordion>
57
+ )
58
+ }
@@ -1,7 +1,11 @@
1
- import { Alert, Box, SxProps, Theme } from '@mui/material'
1
+ import { Trans } from '@lingui/react'
2
+ import { Alert, SxProps, Theme } from '@mui/material'
2
3
  import React from 'react'
3
4
  import { isFragment } from 'react-is'
5
+ import { Button } from '../Button'
6
+ import { IconSvg } from '../IconSvg'
4
7
  import { extendableComponent } from '../Styles'
8
+ import { iconChevronDown } from '../icons'
5
9
  import { ActionCardProps } from './ActionCard'
6
10
  import { ActionCardLayout } from './ActionCardLayout'
7
11
 
@@ -22,6 +26,7 @@ type Select = {
22
26
  }
23
27
 
24
28
  export type ActionCardListProps<SelectOrMulti = MultiSelect | Select> = {
29
+ showMoreAfter?: number
25
30
  children?: React.ReactNode
26
31
  required?: boolean
27
32
  error?: boolean
@@ -57,6 +62,7 @@ export const ActionCardList = React.forwardRef<HTMLDivElement, ActionCardListPro
57
62
  const {
58
63
  children,
59
64
  required,
65
+ showMoreAfter = 1000000,
60
66
  error = false,
61
67
  errorMessage,
62
68
  size = 'medium',
@@ -67,6 +73,8 @@ export const ActionCardList = React.forwardRef<HTMLDivElement, ActionCardListPro
67
73
  sx = [],
68
74
  } = props
69
75
 
76
+ const [show, setShow] = React.useState(false)
77
+
70
78
  const handleChange: ActionCardProps['onClick'] = isMulti(props)
71
79
  ? (event, v) => {
72
80
  const { onChange, value } = props
@@ -103,7 +111,7 @@ export const ActionCardList = React.forwardRef<HTMLDivElement, ActionCardListPro
103
111
  }
104
112
 
105
113
  // Make sure the children are cardlike
106
- const childReactNodes = React.Children.toArray(children)
114
+ const childActionCards = React.Children.toArray(children)
107
115
  .filter(React.isValidElement)
108
116
  .filter(isActionCardLike)
109
117
  .filter((child) => {
@@ -121,7 +129,7 @@ export const ActionCardList = React.forwardRef<HTMLDivElement, ActionCardListPro
121
129
  })
122
130
 
123
131
  // Make sure the selected values is in the list of all possible values
124
- const value = childReactNodes.find(
132
+ const value = childActionCards.find(
125
133
  // eslint-disable-next-line react/destructuring-assignment
126
134
  (child) => child.props.value === props.value && child.props.disabled !== true,
127
135
  )?.props.value
@@ -131,9 +139,10 @@ export const ActionCardList = React.forwardRef<HTMLDivElement, ActionCardListPro
131
139
  return (
132
140
  <div ref={ref}>
133
141
  <ActionCardLayout sx={sx} className={classes.root} layout={layout}>
134
- {childReactNodes.map((child) => {
142
+ {childActionCards.map((child, index) => {
135
143
  if (collapse && Boolean(value) && !isValueSelected(child.props.value, value))
136
144
  return null
145
+ if (index && showMoreAfter && index + 1 > showMoreAfter && !show) return null
137
146
  return React.cloneElement(child, {
138
147
  onClick: handleChange,
139
148
  error,
@@ -149,6 +158,25 @@ export const ActionCardList = React.forwardRef<HTMLDivElement, ActionCardListPro
149
158
  })
150
159
  })}
151
160
  </ActionCardLayout>
161
+
162
+ {childActionCards.length > showMoreAfter && (
163
+ <Button
164
+ sx={{ width: 'fit-content' }}
165
+ color='primary'
166
+ variant='text'
167
+ onClick={() => setShow(!show)}
168
+ >
169
+ {!show ? <Trans id='More options' /> : <Trans id='Less options' />}{' '}
170
+ <IconSvg
171
+ sx={{
172
+ transform: show ? 'rotate(180deg)' : 'rotate(0deg)',
173
+ transition: 'transform 0.3s ease-in-out',
174
+ }}
175
+ src={iconChevronDown}
176
+ />
177
+ </Button>
178
+ )}
179
+
152
180
  {error && errorMessage && (
153
181
  <Alert
154
182
  severity='error'
@@ -64,7 +64,7 @@ export function ActionCardListForm<
64
64
  {items.map((item) => (
65
65
  <RenderItem
66
66
  {...item}
67
- key={item.value ?? 'tralala'}
67
+ key={item.value ?? ''}
68
68
  value={item.value}
69
69
  selected={onSelect(item.value, value)}
70
70
  onReset={(e) => {
@@ -1,4 +1,5 @@
1
1
  export * from './ActionCard'
2
+ export * from './ActionCardAccordion'
2
3
  export * from './ActionCardLayout'
3
4
  export * from './ActionCardList'
4
5
  export * from './ActionCardListForm'
@@ -8,7 +8,7 @@ import { useDateTimeFormat } from '../../hooks'
8
8
  export type BlogListItemProps = {
9
9
  asset: React.ReactNode
10
10
  url: string
11
- date: string
11
+ date: string | undefined
12
12
  title: string
13
13
  sx?: SxProps<Theme>
14
14
  }
@@ -57,18 +57,20 @@ export function BlogListItem(props: BlogListItemProps) {
57
57
  </Box>
58
58
  </Link>
59
59
 
60
- <Box
61
- component='time'
62
- className={classes.date}
63
- dateTime={date}
64
- sx={(theme) => ({
65
- display: 'inline-block',
66
- textDecoration: 'none',
67
- color: theme.palette.text.secondary,
68
- })}
69
- >
70
- {formatter.format(new Date(date))}
71
- </Box>
60
+ {date && (
61
+ <Box
62
+ component='time'
63
+ className={classes.date}
64
+ dateTime={date}
65
+ sx={(theme) => ({
66
+ display: 'inline-block',
67
+ textDecoration: 'none',
68
+ color: theme.palette.text.secondary,
69
+ })}
70
+ >
71
+ {formatter.format(new Date(date))}
72
+ </Box>
73
+ )}
72
74
 
73
75
  <Link href={`/${url}`} className={classes.title} color='inherit' underline='hover'>
74
76
  <Typography component='h2' variant='h4'>
package/CHANGELOG.md CHANGED
@@ -1,5 +1,175 @@
1
1
  # Change Log
2
2
 
3
+ ## 6.2.0-canary.70
4
+
5
+ ## 6.2.0-canary.69
6
+
7
+ ## 6.2.0-canary.68
8
+
9
+ ## 6.2.0-canary.67
10
+
11
+ ## 6.2.0-canary.66
12
+
13
+ ## 6.2.0-canary.65
14
+
15
+ ## 6.2.0-canary.64
16
+
17
+ ### Patch Changes
18
+
19
+ - [#1998](https://github.com/graphcommerce-org/graphcommerce/pull/1998) [`fdbdcb76f`](https://github.com/graphcommerce-org/graphcommerce/commit/fdbdcb76f918cf74b22253bd641a04c490ceb140) - Fixed users accidentally being able to scroll overlays out of view during open animation ([@bramvanderholst](https://github.com/bramvanderholst))
20
+
21
+ ## 6.2.0-canary.63
22
+
23
+ ## 6.2.0-canary.62
24
+
25
+ ## 6.2.0-canary.61
26
+
27
+ ## 6.2.0-canary.60
28
+
29
+ ## 6.2.0-canary.59
30
+
31
+ ## 6.2.0-canary.58
32
+
33
+ ## 6.2.0-canary.57
34
+
35
+ ## 6.2.0-canary.56
36
+
37
+ ## 6.2.0-canary.55
38
+
39
+ ## 6.2.0-canary.54
40
+
41
+ ## 6.2.0-canary.53
42
+
43
+ ## 6.2.0-canary.52
44
+
45
+ ## 6.2.0-canary.51
46
+
47
+ ## 6.2.0-canary.50
48
+
49
+ ### Minor Changes
50
+
51
+ - [`e55d8c390`](https://github.com/graphcommerce-org/graphcommerce/commit/e55d8c390d90b4bb7bab11c6a99027ac72bd7e3e) - Created a new sidebar layout system, can be configured with productFiltersLayout in the graphcommerce.config.js ([@paales](https://github.com/paales))
52
+
53
+ ## 6.2.0-canary.49
54
+
55
+ ## 6.2.0-canary.48
56
+
57
+ ### Minor Changes
58
+
59
+ - [#1961](https://github.com/graphcommerce-org/graphcommerce/pull/1961) [`4a759c662`](https://github.com/graphcommerce-org/graphcommerce/commit/4a759c66215eaa69edc342b898e05e8f92c3ba9a) - Add Open Graph meta tags to all pages ([@Giovanni-Schroevers](https://github.com/Giovanni-Schroevers))
60
+
61
+ ## 6.2.0-canary.47
62
+
63
+ ## 6.2.0-canary.46
64
+
65
+ ## 6.2.0-canary.45
66
+
67
+ ## 6.2.0-canary.44
68
+
69
+ ## 6.2.0-canary.43
70
+
71
+ ### Patch Changes
72
+
73
+ - [#1965](https://github.com/graphcommerce-org/graphcommerce/pull/1965) [`44b2911d7`](https://github.com/graphcommerce-org/graphcommerce/commit/44b2911d73fb6c6c7188f16d5890ca93877e9aa7) - Added prop to LayoutHeader to be able to hide the back button ([@bramvanderholst](https://github.com/bramvanderholst))
74
+
75
+ ## 6.2.0-canary.42
76
+
77
+ ## 6.2.0-canary.41
78
+
79
+ ### Patch Changes
80
+
81
+ - [#1960](https://github.com/graphcommerce-org/graphcommerce/pull/1960) [`f78caf5a8`](https://github.com/graphcommerce-org/graphcommerce/commit/f78caf5a83683f1ae4b901fb94bd22d50943fa2f) - Updated packages @apollo/client, react-hook-form, @emotion/\*, @lingui/\*, @mui/\* and various others. ([@paales](https://github.com/paales))
82
+
83
+ ## 6.2.0-canary.40
84
+
85
+ ## 6.2.0-canary.39
86
+
87
+ ## 6.2.0-canary.38
88
+
89
+ ### Patch Changes
90
+
91
+ - [#1958](https://github.com/graphcommerce-org/graphcommerce/pull/1958) [`0a311b6eb`](https://github.com/graphcommerce-org/graphcommerce/commit/0a311b6ebb5a52e2a7f1d2e6a0fe113904fa2d34) - Left overlays wouldn't properly snap when the overlay gets wider than the viewport ([@paales](https://github.com/paales))
92
+
93
+ ## 6.2.0-canary.37
94
+
95
+ ## 6.2.0-canary.36
96
+
97
+ ## 6.2.0-canary.35
98
+
99
+ ## 6.2.0-canary.34
100
+
101
+ ## 6.2.0-canary.33
102
+
103
+ ## 6.2.0-canary.32
104
+
105
+ ## 6.2.0-canary.31
106
+
107
+ ## 6.2.0-canary.30
108
+
109
+ ## 6.2.0-canary.29
110
+
111
+ ### Patch Changes
112
+
113
+ - [#1946](https://github.com/graphcommerce-org/graphcommerce/pull/1946) [`87260618b`](https://github.com/graphcommerce-org/graphcommerce/commit/87260618b8abaebd727ff4435abb1aea6ed33730) - Firefox: scroll snap overlays would snap to 0 when the scroll snap targets wouldn’t exactly match the possible targets. ([@paales](https://github.com/paales))
114
+
115
+ ## 6.2.0-canary.28
116
+
117
+ ## 6.2.0-canary.27
118
+
119
+ ## 6.2.0-canary.26
120
+
121
+ ## 6.2.0-canary.25
122
+
123
+ ## 6.2.0-canary.24
124
+
125
+ ## 6.2.0-canary.23
126
+
127
+ ## 6.2.0-canary.22
128
+
129
+ ### Patch Changes
130
+
131
+ - [#1939](https://github.com/graphcommerce-org/graphcommerce/pull/1939) [`0cdccf681`](https://github.com/graphcommerce-org/graphcommerce/commit/0cdccf6817d6a769f59cccb68b1b1b8b4405cbd0) - Make blogListItem date prop optional ([@JoshuaS98](https://github.com/JoshuaS98))
132
+
133
+ ## 6.2.0-canary.21
134
+
135
+ ## 6.2.0-canary.20
136
+
137
+ ## 6.2.0-canary.19
138
+
139
+ ## 6.2.0-canary.18
140
+
141
+ ## 6.2.0-canary.17
142
+
143
+ ### Patch Changes
144
+
145
+ - [#1934](https://github.com/graphcommerce-org/graphcommerce/pull/1934) [`96ac0320a`](https://github.com/graphcommerce-org/graphcommerce/commit/96ac0320a30bc55a6db5d46663d28b552fda4db6) - Fixed floating overlays not closing when clicking beside the overlay ([@bramvanderholst](https://github.com/bramvanderholst))
146
+
147
+ ## 6.2.0-canary.16
148
+
149
+ ### Patch Changes
150
+
151
+ - [#1930](https://github.com/graphcommerce-org/graphcommerce/pull/1930) [`c8d023e9e`](https://github.com/graphcommerce-org/graphcommerce/commit/c8d023e9e874131cd9f8fe192b1fca5fe1a26ee3) - Fix the close menu on search and add the option to secondary menu items ([@StefanAngenent](https://github.com/StefanAngenent))
152
+
153
+ ## 6.2.0-canary.15
154
+
155
+ ## 6.2.0-canary.14
156
+
157
+ ## 6.2.0-canary.13
158
+
159
+ ## 6.2.0-canary.12
160
+
161
+ ## 6.2.0-canary.11
162
+
163
+ ## 6.2.0-canary.10
164
+
165
+ ## 6.2.0-canary.9
166
+
167
+ ### Patch Changes
168
+
169
+ - [#1897](https://github.com/graphcommerce-org/graphcommerce/pull/1897) [`f44d7cec6`](https://github.com/graphcommerce-org/graphcommerce/commit/f44d7cec61766f4768c30d29b211a12f2846e9f6) - Overlays can now be configured to get a bgColor ([@FrankHarland](https://github.com/FrankHarland))
170
+
171
+ ## 6.2.0-canary.8
172
+
3
173
  ## 6.2.0-canary.7
4
174
 
5
175
  ## 6.2.0-canary.6
package/Fab/Fab.tsx CHANGED
@@ -23,6 +23,7 @@ export function Fab(props: FabProps) {
23
23
  sx = [],
24
24
  icon,
25
25
  circularProgress,
26
+ color = 'default',
26
27
  ...fabProps
27
28
  } = props
28
29
 
@@ -33,7 +34,7 @@ export function Fab(props: FabProps) {
33
34
  return (
34
35
  <FabBase
35
36
  type='submit'
36
- color='primary'
37
+ color={color}
37
38
  size={size}
38
39
  {...fabProps}
39
40
  disabled={disabled}
@@ -45,6 +46,7 @@ export function Fab(props: FabProps) {
45
46
  {loading && (
46
47
  <CircularProgress
47
48
  size={fabSize}
49
+ color={color !== 'primary' ? 'primary' : 'inherit'}
48
50
  {...circularProgress}
49
51
  sx={[
50
52
  { display: 'flex', placeContent: 'center', gridArea: '1/1' },
@@ -48,6 +48,7 @@ export type SidebarGalleryProps = {
48
48
  aspectRatio?: [number, number]
49
49
  routeHash?: string
50
50
  sx?: SxProps<Theme>
51
+ disableZoom?: boolean
51
52
  }
52
53
 
53
54
  export function SidebarGallery(props: SidebarGalleryProps) {
@@ -57,6 +58,7 @@ export function SidebarGallery(props: SidebarGalleryProps) {
57
58
  aspectRatio: [width, height] = [1, 1],
58
59
  sx,
59
60
  routeHash = 'gallery',
61
+ disableZoom,
60
62
  } = props
61
63
 
62
64
  const router = useRouter()
@@ -76,6 +78,9 @@ export function SidebarGallery(props: SidebarGalleryProps) {
76
78
  }, [prevRoute?.pathname, route, router, zoomed])
77
79
 
78
80
  const toggle = () => {
81
+ if (disableZoom) {
82
+ return
83
+ }
79
84
  if (!zoomed) {
80
85
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
81
86
  router.push(route, undefined, { shallow: true })
@@ -192,7 +197,7 @@ export function SidebarGallery(props: SidebarGalleryProps) {
192
197
  height: '100%',
193
198
  gridAutoColumns: `100%`,
194
199
  gridTemplateRows: `100%`,
195
- cursor: 'zoom-in',
200
+ cursor: disableZoom ? 'auto' : 'zoom-in',
196
201
  },
197
202
  zoomed && {
198
203
  height: `var(--client-size-y)`,
@@ -232,16 +237,22 @@ export function SidebarGallery(props: SidebarGalleryProps) {
232
237
  right: theme.spacings.sm,
233
238
  }}
234
239
  >
235
- <Fab
236
- size='small'
237
- className={classes.toggleIcon}
238
- disabled={!hasImages}
239
- onMouseUp={toggle}
240
- aria-label='Toggle Fullscreen'
241
- sx={{ boxShadow: 6 }}
242
- >
243
- {!zoomed ? <IconSvg src={iconFullscreen} /> : <IconSvg src={iconFullscreenExit} />}
244
- </Fab>
240
+ {!disableZoom && (
241
+ <Fab
242
+ size='small'
243
+ className={classes.toggleIcon}
244
+ disabled={!hasImages}
245
+ onMouseUp={toggle}
246
+ aria-label='Toggle Fullscreen'
247
+ sx={{ boxShadow: 6 }}
248
+ >
249
+ {!zoomed ? (
250
+ <IconSvg src={iconFullscreen} />
251
+ ) : (
252
+ <IconSvg src={iconFullscreenExit} />
253
+ )}
254
+ </Fab>
255
+ )}
245
256
  </MotionBox>
246
257
  <Box
247
258
  className={classes.centerLeft}
@@ -53,42 +53,40 @@ export function useIconSvgSize(size: keyof typeof sizes) {
53
53
  return sizes[size]
54
54
  }
55
55
 
56
- const Svg = styled('svg', { name, target: name })(() => [
57
- {
58
- userSelect: 'none',
59
- width: '1em',
60
- height: '1em',
61
- display: 'inline-block',
62
-
63
- strokeLinecap: 'square',
64
- strokeLinejoin: 'miter',
65
- strokeMiterlimit: 4,
66
-
67
- fill: 'none',
68
- stroke: 'currentColor',
69
-
70
- fontSize: '1.3em',
71
-
72
- strokeWidth: svgIconStrokeWidth(28, 148, 1.4, 0.8),
73
-
74
- '&.sizeXs': { fontSize: sizes.xs },
75
- '&.sizeSmall': { fontSize: sizes.small },
76
- '&.sizeMedium': { fontSize: sizes.medium },
77
- '&.sizeLarge': { fontSize: sizes.large },
78
- '&.sizeXl': { fontSize: sizes.xl },
79
- '&.sizeXxl': { fontSize: sizes.xxl },
80
-
81
- '&.fillIcon': {
82
- fill: 'currentColor',
83
- stroke: 'none',
84
- },
56
+ const Svg = styled('svg', { name, target: name })(() => ({
57
+ userSelect: 'none',
58
+ width: '1em',
59
+ height: '1em',
60
+ display: 'inline-block',
61
+
62
+ strokeLinecap: 'square',
63
+ strokeLinejoin: 'miter',
64
+ strokeMiterlimit: 4,
65
+
66
+ fill: 'none',
67
+ stroke: 'currentColor',
68
+
69
+ fontSize: '1.3em',
70
+
71
+ strokeWidth: svgIconStrokeWidth(28, 148, 1.4, 0.8),
72
+
73
+ '&.sizeXs': { fontSize: sizes.xs },
74
+ '&.sizeSmall': { fontSize: sizes.small },
75
+ '&.sizeMedium': { fontSize: sizes.medium },
76
+ '&.sizeLarge': { fontSize: sizes.large },
77
+ '&.sizeXl': { fontSize: sizes.xl },
78
+ '&.sizeXxl': { fontSize: sizes.xxl },
79
+
80
+ '&.fillIcon': {
81
+ fill: 'currentColor',
82
+ stroke: 'none',
85
83
  },
86
- ])
84
+ }))
87
85
 
88
86
  /**
89
87
  * IconSvg component is supposed to be used in combination with `icons`
90
88
  *
91
- * @see https://graphcommerce-docs.vercel.app/framework/icons
89
+ * @see https://www.graphcommerce.org/docs/framework/icons
92
90
  */
93
91
  export const IconSvg = forwardRef<SVGSVGElement, IconSvgProps>((props, ref) => {
94
92
  const { src, size, fillIcon, className, ...svgProps } = useThemeProps({ props, name })
@@ -26,6 +26,8 @@ export type LayoutHeaderProps = FloatingProps &
26
26
  noAlign?: boolean
27
27
 
28
28
  sx?: SxProps<Theme>
29
+
30
+ hideBackButton?: boolean
29
31
  }
30
32
 
31
33
  type ComponentStyleProps = {
@@ -46,14 +48,16 @@ export function LayoutHeader(props: LayoutHeaderProps) {
46
48
  const {
47
49
  children,
48
50
  divider,
51
+ hideBackButton = false,
49
52
  primary,
50
53
  secondary,
51
54
  noAlign = false,
52
55
  switchPoint,
53
56
  size = 'responsive',
54
57
  sx = [],
58
+ bgColor,
55
59
  } = props
56
- const showBack = useShowBack()
60
+ const showBack = useShowBack() && !hideBackButton
57
61
  const showClose = useShowClose()
58
62
 
59
63
  const floatFallback = !children
@@ -149,6 +153,7 @@ export function LayoutHeader(props: LayoutHeaderProps) {
149
153
  floatingMd={floatingMd}
150
154
  floatingSm={floatingSm}
151
155
  switchPoint={switchPoint}
156
+ bgColor={bgColor}
152
157
  >
153
158
  {children}
154
159
  </LayoutHeaderContent>
@@ -18,6 +18,7 @@ export type LayoutHeaderContentProps = FloatingProps & {
18
18
  sxBg?: SxProps<Theme>
19
19
  layout?: LayoutProps['layout']
20
20
  size?: 'small' | 'responsive'
21
+ bgColor?: 'paper' | 'default'
21
22
  } & Pick<LayoutProps, 'layout' | 'layoutDependency'>
22
23
 
23
24
  type OwnerState = {
@@ -26,7 +27,9 @@ type OwnerState = {
26
27
  scrolled: boolean
27
28
  divider: boolean
28
29
  size: 'small' | 'responsive'
30
+ bgColor?: 'paper' | 'default'
29
31
  }
32
+
30
33
  const name = 'LayoutHeaderContent' as const
31
34
  const parts = ['bg', 'content', 'left', 'center', 'right', 'divider'] as const
32
35
  const { withState } = extendableComponent<OwnerState, typeof name, typeof parts>(name, parts)
@@ -47,12 +50,13 @@ export function LayoutHeaderContent(props: LayoutHeaderContentProps) {
47
50
  layout,
48
51
  layoutDependency,
49
52
  size = 'responsive',
53
+ bgColor = 'paper',
50
54
  } = props
51
55
 
52
56
  const scroll = useScrollY()
53
57
  const scrolled = useMotionValueValue(scroll, (y) => y >= switchPoint)
54
58
 
55
- const classes = withState({ floatingSm, floatingMd, scrolled, divider: !!divider, size })
59
+ const classes = withState({ floatingSm, floatingMd, scrolled, divider: !!divider, size, bgColor })
56
60
 
57
61
  return (
58
62
  <>
@@ -63,7 +67,7 @@ export function LayoutHeaderContent(props: LayoutHeaderContentProps) {
63
67
  position: 'absolute',
64
68
  left: 0,
65
69
  width: '100%',
66
- backgroundColor: theme.palette.background.paper,
70
+ backgroundColor: theme.palette.background[bgColor],
67
71
  boxShadow: theme.shadows[1],
68
72
 
69
73
  height: theme.appShell.headerHeightSm,
@@ -127,7 +127,19 @@ export function LayoutDefault(props: LayoutDefaultProps) {
127
127
  })}
128
128
  >
129
129
  {menuFab}
130
- {cartFab}
130
+ <Box
131
+ sx={(theme) => ({
132
+ display: 'flex',
133
+ flexDirection: 'row-reverse',
134
+ gap: theme.spacings.sm,
135
+ [theme.breakpoints.up('md')]: {
136
+ flexDirection: 'column',
137
+ alignItems: 'flex-end',
138
+ },
139
+ })}
140
+ >
141
+ {cartFab}
142
+ </Box>
131
143
  </Box>
132
144
  ) : (
133
145
  <div />
@@ -1,6 +1,6 @@
1
1
  import { ListItemButton, ListItemIcon, ListItemText, SxProps, Theme } from '@mui/material'
2
2
  import { useRouter } from 'next/router'
3
- import React from 'react'
3
+ import React, { MouseEventHandler } from 'react'
4
4
  import { extendableComponent } from '../Styles'
5
5
  import { NextLink } from '../Theme'
6
6
 
@@ -9,6 +9,7 @@ export type FabMenuSecondaryItemProps = {
9
9
  children: React.ReactNode
10
10
  icon: React.ReactNode
11
11
  sx?: SxProps<Theme>
12
+ onClick?: MouseEventHandler<HTMLElement>
12
13
  }
13
14
 
14
15
  const compName = 'MenuFabSecondaryItem' as const
@@ -16,12 +17,18 @@ const parts = ['root', 'icon', 'text'] as const
16
17
  const { classes } = extendableComponent(compName, parts)
17
18
 
18
19
  export function MenuFabSecondaryItem(props: FabMenuSecondaryItemProps) {
19
- const { href, children, icon, sx = [] } = props
20
+ const { href, children, onClick, icon, sx = [] } = props
20
21
  const router = useRouter()
21
22
 
23
+ const handleClick: MouseEventHandler<HTMLElement> = (e) => {
24
+ e.preventDefault()
25
+ onClick?.(e)
26
+ return router.push(href)
27
+ }
28
+
22
29
  return (
23
30
  <ListItemButton
24
- href={href}
31
+ onClick={handleClick}
25
32
  component={NextLink}
26
33
  className={classes.root}
27
34
  sx={[{}, ...(Array.isArray(sx) ? sx : [sx])]}
@@ -1,25 +1,34 @@
1
- import { Container } from '@mui/material'
1
+ import { Container, SxProps, Theme } from '@mui/material'
2
2
  import React from 'react'
3
+ import { extendableComponent } from '../Styles'
3
4
 
4
5
  export type StickyBelowHeaderProps = {
5
6
  children: React.ReactNode
7
+ sx?: SxProps<Theme>
6
8
  }
7
9
 
10
+ const { classes } = extendableComponent('StickyBelowHeader', ['root'])
11
+
8
12
  /** - Makes the children sticky to the parent container */
9
13
  export function StickyBelowHeader(props: StickyBelowHeaderProps) {
14
+ const { sx = [] } = props
10
15
  return (
11
16
  <Container
12
- sx={(theme) => ({
13
- position: 'sticky',
14
- top: { xs: theme.appShell.headerHeightSm, md: `${theme.page.vertical} !important` },
15
- zIndex: 96,
16
- pointerEvents: 'none',
17
- '& > *': {
18
- pointerEvents: 'auto',
19
- },
20
- })}
17
+ className={classes.root}
21
18
  maxWidth={false}
22
19
  {...props}
20
+ sx={[
21
+ (theme) => ({
22
+ position: 'sticky',
23
+ top: { xs: theme.appShell.headerHeightSm, md: `${theme.page.vertical} !important` },
24
+ zIndex: 96,
25
+ pointerEvents: 'none',
26
+ '& > *': {
27
+ pointerEvents: 'auto',
28
+ },
29
+ }),
30
+ ...(Array.isArray(sx) ? sx : [sx]),
31
+ ]}
23
32
  />
24
33
  )
25
34
  }
@@ -15,7 +15,6 @@ import {
15
15
  useMotionValue,
16
16
  useTransform,
17
17
  } from 'framer-motion'
18
- import framesync from 'framesync'
19
18
  import React, { useCallback, useEffect, useRef } from 'react'
20
19
  import { LayoutProvider } from '../../Layout/components/LayoutProvider'
21
20
  import { ExtendableComponent, extendableComponent } from '../../Styles'
@@ -32,6 +31,7 @@ type StyleProps = {
32
31
  sizeMd?: LayoutOverlaySize
33
32
  justifySm?: LayoutOverlayAlign
34
33
  justifyMd?: LayoutOverlayAlign
34
+ bgColor?: 'paper' | 'default'
35
35
  }
36
36
 
37
37
  type OverridableProps = {
@@ -102,6 +102,7 @@ export function OverlayBase(incomingProps: LayoutOverlayBaseProps) {
102
102
  sxBackdrop = [],
103
103
  active,
104
104
  onClosed,
105
+ bgColor = 'paper',
105
106
  direction = 1,
106
107
  offsetPageY = 0,
107
108
  isPresent,
@@ -194,7 +195,7 @@ export function OverlayBase(incomingProps: LayoutOverlayBaseProps) {
194
195
  scroller.scrollLeft = scroll.x.getPrevious()
195
196
  scroller.scrollTop = scroll.y.getPrevious()
196
197
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
197
- scrollTo(openClosePositions().open)
198
+ scrollTo(openClosePositions().open, { stopAnimationOnScroll: false })
198
199
  }
199
200
  }
200
201
 
@@ -279,7 +280,9 @@ export function OverlayBase(incomingProps: LayoutOverlayBaseProps) {
279
280
 
280
281
  if (position.get() !== OverlayPosition.OPENED && !scroll.animating.get()) {
281
282
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
282
- scrollTo(openClosePositions().open).then(() => position.set(OverlayPosition.OPENED))
283
+ scrollTo(openClosePositions().open, { stopAnimationOnScroll: false }).then(() =>
284
+ position.set(OverlayPosition.OPENED),
285
+ )
283
286
  }
284
287
  }, [isPresent, openClosePositions, position, scroll.animating, scrollTo, scrollerRef, variant])
285
288
 
@@ -289,7 +292,7 @@ export function OverlayBase(incomingProps: LayoutOverlayBaseProps) {
289
292
  if (isPresent || !scroller) return
290
293
 
291
294
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
292
- scrollTo(openClosePositions().closed).then(() => {
295
+ scrollTo(openClosePositions().closed, { stopAnimationOnScroll: false }).then(() => {
293
296
  safeToRemove?.()
294
297
  document.body.style.overflow = ''
295
298
  })
@@ -329,10 +332,13 @@ export function OverlayBase(incomingProps: LayoutOverlayBaseProps) {
329
332
 
330
333
  const onClickAway = useCallback(
331
334
  (event: React.MouseEvent<HTMLDivElement>) => {
332
- const isTarget = event.target === beforeRef.current || event.target === overlayPaneRef.current
335
+ const isTarget =
336
+ event.target === beforeRef.current ||
337
+ event.target === overlayPaneRef.current ||
338
+ event.target === scrollerRef.current
333
339
  if (isTarget && snap.get()) closeOverlay()
334
340
  },
335
- [closeOverlay, snap],
341
+ [closeOverlay, snap, scrollerRef],
336
342
  )
337
343
 
338
344
  return (
@@ -405,7 +411,7 @@ export function OverlayBase(incomingProps: LayoutOverlayBaseProps) {
405
411
  height: '1px',
406
412
  top: 'calc(100% - 1px)',
407
413
  left: '0',
408
- background: theme.palette.background.paper,
414
+ background: theme.palette.background[bgColor],
409
415
  },
410
416
  },
411
417
  },
@@ -464,9 +470,14 @@ export function OverlayBase(incomingProps: LayoutOverlayBaseProps) {
464
470
  sx={(theme) => ({
465
471
  display: 'grid',
466
472
  gridArea: 'overlay',
467
- scrollSnapAlign: 'start',
468
473
  scrollSnapStop: 'always',
469
474
  pointerEvents: 'none',
475
+ '&.variantMdBottom, &.variantMdRight': {
476
+ scrollSnapAlign: 'end',
477
+ },
478
+ '&.variantMdLeft': {
479
+ scrollSnapAlign: 'start',
480
+ },
470
481
  [theme.breakpoints.down('md')]: {
471
482
  justifyContent: justifySm,
472
483
  alignItems: justifySm,
@@ -592,7 +603,7 @@ export function OverlayBase(incomingProps: LayoutOverlayBaseProps) {
592
603
  <Box
593
604
  className={classes.background}
594
605
  sx={(theme) => ({
595
- backgroundColor: theme.palette.background.paper,
606
+ backgroundColor: theme.palette.background[bgColor],
596
607
  paddingBottom: '0.1px',
597
608
  [theme.breakpoints.down('md')]: {
598
609
  minHeight: '100%',
@@ -31,6 +31,9 @@ export type PageMetaProps = {
31
31
  metaDescription?: string
32
32
  metaRobots?: MetaRobotsAll | MetaRobots[]
33
33
  children?: React.ReactNode
34
+ ogImage?: string | null
35
+ ogImageUseFallback?: boolean
36
+ ogType?: string | null
34
37
  }
35
38
 
36
39
  type PartialNextRouter = Pick<
@@ -90,7 +93,16 @@ export function useCanonical(incoming?: Canonical) {
90
93
 
91
94
  export function PageMeta(props: PageMetaProps) {
92
95
  const { active } = usePageContext()
93
- const { children, title, canonical: canonicalBare, metaDescription, metaRobots = ['all'] } = props
96
+ const {
97
+ children,
98
+ title,
99
+ canonical: canonicalBare,
100
+ metaDescription,
101
+ ogImage,
102
+ ogType,
103
+ ogImageUseFallback = false,
104
+ metaRobots = ['all'],
105
+ } = props
94
106
 
95
107
  const canonical = useCanonical(canonicalBare)
96
108
 
@@ -99,10 +111,20 @@ export function PageMeta(props: PageMetaProps) {
99
111
  <Head>
100
112
  <title>{title.trim()}</title>
101
113
  {metaDescription && (
102
- <meta name='description' content={metaDescription.trim()} key='meta-description' />
114
+ <>
115
+ <meta name='description' content={metaDescription.trim()} key='meta-description' />
116
+ <meta property='og:description' content={metaDescription.trim()} key='og-description' />
117
+ </>
103
118
  )}
104
119
  <meta name='robots' content={metaRobots.join(',')} key='meta-robots' />
105
120
  {canonical && <link rel='canonical' href={canonical} key='canonical' />}
121
+ <meta property='og:title' content={title.trim()} key='og-title' />
122
+ <meta property='og:type' content={ogType ?? 'website'} key='og-type' />
123
+ <meta property='og:url' content={canonical} key='og-url' />
124
+ {(ogImage || ogImageUseFallback) && (
125
+ <meta property='og:image' content={ogImage ?? '/open-graph-image.jpg'} key='og-image' />
126
+ )}
127
+
106
128
  {children}
107
129
  </Head>
108
130
  )
@@ -22,7 +22,7 @@ const { classes } = extendableComponent('Pagination', parts)
22
22
  * Read more: https://ahrefs.com/blog/rel-prev-next-pagination/
23
23
  */
24
24
  export function Pagination(props: PagePaginationProps) {
25
- const { count, page, renderLink, classes: styles, ...paginationProps } = props
25
+ const { count, page, renderLink, classes: styles, sx = [], ...paginationProps } = props
26
26
 
27
27
  const { items } = usePagination({
28
28
  count,
@@ -61,18 +61,21 @@ export function Pagination(props: PagePaginationProps) {
61
61
  return (
62
62
  <Box
63
63
  className={classes.root}
64
- sx={(theme) => ({
65
- margin: '0 auto',
66
- marginTop: theme.spacings.lg,
67
- marginBottom: theme.spacings.lg,
68
- display: 'flex',
69
- alignItems: 'center',
70
- justifyContent: 'center',
71
- gap: '6px',
72
- '& .Mui-disabled': {
73
- background: 'none',
74
- },
75
- })}
64
+ sx={[
65
+ (theme) => ({
66
+ margin: '0 auto',
67
+ marginTop: theme.spacings.lg,
68
+ marginBottom: theme.spacings.lg,
69
+ display: 'flex',
70
+ alignItems: 'center',
71
+ justifyContent: 'center',
72
+ gap: '6px',
73
+ '& .Mui-disabled': {
74
+ background: 'none',
75
+ },
76
+ }),
77
+ ...(Array.isArray(sx) ? sx : [sx]),
78
+ ]}
76
79
  >
77
80
  {page === 1 ? chevronLeft : renderLink(page - 1, chevronLeft, prevBtnProps)}
78
81
 
@@ -8,7 +8,7 @@ type FilterTypeByTypename<A extends TypeObject, Typename extends string> = A ext
8
8
  : never
9
9
  : never
10
10
 
11
- type TypeRenderMethod<P> = (props: P) => React.ReactElement | null
11
+ type TypeRenderMethod<P> = (props: P) => React.ReactNode
12
12
 
13
13
  type TypeRenderMap<
14
14
  T extends TypeObject,
@@ -70,9 +70,13 @@ export default function MessageSnackbarImpl(props: MessageSnackbarProps) {
70
70
  onClose?.()
71
71
  }
72
72
 
73
- const preventAnimationBubble: React.MouseEventHandler<HTMLButtonElement> = (e) => {
74
- e.preventDefault()
73
+ const preventAnimationBubble = (
74
+ e: React.TouchEvent<HTMLButtonElement> | React.MouseEvent<HTMLButtonElement>,
75
+ ) => {
75
76
  e.stopPropagation()
77
+ if (e.type === 'mousedown') {
78
+ e.preventDefault()
79
+ }
76
80
  }
77
81
 
78
82
  let icon = iconCheckmark
@@ -144,6 +148,7 @@ export default function MessageSnackbarImpl(props: MessageSnackbarProps) {
144
148
  size='small'
145
149
  onClick={hideSnackbar}
146
150
  onMouseDown={preventAnimationBubble}
151
+ onTouchStart={preventAnimationBubble}
147
152
  sx={(theme) => ({
148
153
  backgroundColor: lighten(theme.palette.background.paper, 0.1),
149
154
  })}
@@ -89,19 +89,19 @@ export const MuiButtonPill: ButtonVariants = [
89
89
  {
90
90
  props: { variant: 'pill', size: 'small' },
91
91
  style: ({ theme }) => ({
92
- '&:not(.Mui-disabled)': { boxShadow: theme.shadows[2] },
92
+ '&:not(.Mui-disabled):not(.MuiButton-disableElevation)': { boxShadow: theme.shadows[2] },
93
93
  }),
94
94
  },
95
95
  {
96
96
  props: { variant: 'pill', size: 'medium' },
97
97
  style: ({ theme }) => ({
98
- '&:not(.Mui-disabled)': { boxShadow: theme.shadows[4] },
98
+ '&:not(.Mui-disabled):not(.MuiButton-disableElevation)': { boxShadow: theme.shadows[4] },
99
99
  }),
100
100
  },
101
101
  {
102
102
  props: { variant: 'pill', size: 'large' },
103
103
  style: ({ theme }) => ({
104
- '&:not(.Mui-disabled)': { boxShadow: theme.shadows[6] },
104
+ '&:not(.Mui-disabled):not(.MuiButton-disableElevation)': { boxShadow: theme.shadows[6] },
105
105
  }),
106
106
  },
107
107
  {
@@ -0,0 +1,9 @@
1
+ <svg role="img" xmlns="http://www.w3.org/2000/svg" width="48px" height="48px" aria-labelledby="compareIconTitle" stroke="#2329D6" stroke-width="1" stroke-linecap="square" stroke-linejoin="miter" fill="none" color="#2329D6">
2
+ <title id="compareIconTitle">Compare</title>
3
+ <symbol id="icon" viewBox="0 0 24 24">
4
+ <path d="M12 9L22 9" />
5
+ <path d="M16.5 4.5L12 9L16.5 13.5"/>
6
+ <path d="M12 15L2 15" />
7
+ <path d="M7.5 19.5L12 15L7.5 10.5" />
8
+ </symbol>
9
+ </svg>
package/icons/index.ts CHANGED
@@ -45,3 +45,4 @@ export { default as iconSearch } from './search.svg'
45
45
  export { default as iconPhone } from './smartphone.svg'
46
46
  export { default as iconStar } from './star.svg'
47
47
  export { default as iconSun } from './sun.svg'
48
+ export { default as iconCompare } from './compare-arrows.svg'
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": "6.2.0-canary.7",
5
+ "version": "6.2.0-canary.70",
6
6
  "author": "",
7
7
  "license": "MIT",
8
8
  "sideEffects": false,
@@ -14,29 +14,30 @@
14
14
  }
15
15
  },
16
16
  "dependencies": {
17
- "@emotion/cache": "^11.10.5",
18
- "@emotion/react": "^11.10.6",
19
- "@emotion/server": "^11.4.0",
20
- "@emotion/styled": "^11.10.6",
21
- "@graphcommerce/framer-next-pages": "6.2.0-canary.7",
22
- "@graphcommerce/framer-scroller": "6.2.0-canary.7",
23
- "@graphcommerce/framer-utils": "6.2.0-canary.7",
24
- "@graphcommerce/image": "6.2.0-canary.7",
17
+ "@emotion/cache": "^11.11.0",
18
+ "@emotion/react": "^11.11.1",
19
+ "@emotion/server": "^11.11.0",
20
+ "@emotion/styled": "^11.11.0",
21
+ "@graphcommerce/framer-next-pages": "6.2.0-canary.70",
22
+ "@graphcommerce/framer-scroller": "6.2.0-canary.70",
23
+ "@graphcommerce/framer-utils": "6.2.0-canary.70",
24
+ "@graphcommerce/image": "6.2.0-canary.70",
25
25
  "cookie": "^0.5.0",
26
26
  "react-is": "^18.2.0",
27
27
  "schema-dts": "^1.1.0"
28
28
  },
29
29
  "devDependencies": {
30
- "@graphcommerce/eslint-config-pwa": "6.2.0-canary.7",
31
- "@graphcommerce/prettier-config-pwa": "6.2.0-canary.7",
32
- "@graphcommerce/typescript-config-pwa": "6.2.0-canary.7",
30
+ "@graphcommerce/eslint-config-pwa": "6.2.0-canary.70",
31
+ "@graphcommerce/prettier-config-pwa": "6.2.0-canary.70",
32
+ "@graphcommerce/typescript-config-pwa": "6.2.0-canary.70",
33
33
  "@types/cookie": "^0.5.1",
34
- "@types/react-is": "^17.0.3",
35
- "typescript": "4.9.5"
34
+ "@types/react-is": "^18.2.0",
35
+ "typescript": "5.1.3"
36
36
  },
37
37
  "peerDependencies": {
38
- "@lingui/core": "^3.13.2",
39
- "@lingui/react": "^3.13.2",
38
+ "@lingui/core": "^4.2.1",
39
+ "@lingui/react": "^4.2.1",
40
+ "@lingui/macro": "^4.2.1",
40
41
  "@mui/lab": "^5.0.0-alpha.68",
41
42
  "@mui/material": "^5.10.16",
42
43
  "framer-motion": "^10.0.0",