@graphcommerce/next-ui 4.8.1 → 4.8.4

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.
@@ -2,21 +2,32 @@ import { Theme } from '@emotion/react'
2
2
  import { SxProps, ButtonBase, Box } from '@mui/material'
3
3
  import React, { FormEvent } from 'react'
4
4
 
5
- type ActionCardProps = {
5
+ export type ActionCardProps = {
6
6
  sx?: SxProps<Theme>
7
7
  title?: string | React.ReactNode
8
8
  image?: React.ReactNode
9
9
  action?: React.ReactNode
10
10
  details?: React.ReactNode
11
11
  secondaryAction?: React.ReactNode
12
- onClick?: (e: FormEvent<HTMLButtonElement>, v: string) => void
13
- onChange?: (e: FormEvent<HTMLButtonElement>, v: string) => void
12
+ onClick?: (e: FormEvent<HTMLElement>, v: string | number) => void
14
13
  selected?: boolean
15
14
  hidden?: boolean | (() => boolean)
16
- value: string
15
+ value: string | number
17
16
  reset?: React.ReactNode
18
17
  }
19
18
 
19
+ const actionButtonStyles: SxProps = {
20
+ '& .MuiButton-root': {
21
+ '&.MuiButton-textSecondary': {
22
+ padding: '5px',
23
+ margin: '-5px',
24
+ '&:hover': {
25
+ background: 'none',
26
+ },
27
+ },
28
+ },
29
+ }
30
+
20
31
  export function ActionCard(props: ActionCardProps) {
21
32
  const {
22
33
  title,
@@ -25,7 +36,6 @@ export function ActionCard(props: ActionCardProps) {
25
36
  details,
26
37
  secondaryAction,
27
38
  sx = [],
28
- onChange,
29
39
  onClick,
30
40
  value,
31
41
  selected,
@@ -33,52 +43,31 @@ export function ActionCard(props: ActionCardProps) {
33
43
  reset,
34
44
  } = props
35
45
 
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
- }
46
+ const handleClick = (event: FormEvent<HTMLElement>) => onClick?.(event, value)
56
47
 
57
48
  return (
58
49
  <ButtonBase
59
- component='button'
50
+ component='div'
60
51
  className='ActionCard-root'
61
52
  onClick={handleClick}
62
- onChange={handleChange}
63
- value={value}
64
53
  sx={[
65
- {
54
+ (theme) => ({
66
55
  display: 'grid',
67
56
  width: '100%',
68
57
  gridTemplateColumns: 'min-content',
69
- gridTemplateAreas: `
70
- "image title action"
71
- "image details secondaryDetails"
72
- "image secondaryAction additionalDetails"
73
- "additionalContent additionalContent additionalContent"
74
- `,
58
+ gridTemplateAreas: {
59
+ xs: `
60
+ "image title action"
61
+ "image details details"
62
+ "image secondaryAction additionalDetails"
63
+ "additionalContent additionalContent additionalContent"
64
+ `,
65
+ },
75
66
  justifyContent: 'unset',
76
- },
77
- (theme) => ({
78
67
  typography: 'body1',
79
- textAlign: 'left',
68
+ // textAlign: 'left',
80
69
  background: theme.palette.background.paper,
81
- padding: `calc(${theme.spacings.xs} + 1px)`,
70
+ padding: `calc(${theme.spacings.xxs} + 1px) calc(${theme.spacings.xs} + 1px)`,
82
71
  columnGap: theme.spacings.xxs,
83
72
  border: `1px solid ${theme.palette.divider}`,
84
73
  borderBottomColor: `transparent`,
@@ -102,7 +91,7 @@ export function ActionCard(props: ActionCardProps) {
102
91
  borderTopRightRadius: theme.shape.borderRadius,
103
92
  borderBottomLeftRadius: theme.shape.borderRadius,
104
93
  borderBottomRightRadius: theme.shape.borderRadius,
105
- padding: theme.spacings.xs,
94
+ padding: `${theme.spacings.xxs} ${theme.spacings.xs}`,
106
95
  })),
107
96
  ...(Array.isArray(sx) ? sx : [sx]),
108
97
  ]}
@@ -1,6 +1,8 @@
1
- import { Box } from '@mui/material'
1
+ import { Alert, Box, FormHelperText } from '@mui/material'
2
+ import { AnimatePresence } from 'framer-motion'
2
3
  import React from 'react'
3
4
  import { isFragment } from 'react-is'
5
+ import { AnimatedRow } from '../AnimatedRow/AnimatedRow'
4
6
 
5
7
  type MultiSelect = {
6
8
  multiple: true
@@ -16,10 +18,11 @@ type Select = {
16
18
  onChange?: (event: React.MouseEvent<HTMLElement>, value: string | null) => void
17
19
  }
18
20
 
19
- type ActionCardListProps<SelectOrMulti = MultiSelect | Select> = {
21
+ export type ActionCardListProps<SelectOrMulti = MultiSelect | Select> = {
20
22
  children?: React.ReactNode
21
23
  required?: boolean
22
24
  error?: boolean
25
+ errorMessage?: string
23
26
  } & SelectOrMulti
24
27
 
25
28
  function isMulti(props: ActionCardListProps): props is ActionCardListProps<MultiSelect> {
@@ -33,7 +36,7 @@ function isValueSelected(value: string, candidate: string | string[]) {
33
36
  }
34
37
 
35
38
  export function ActionCardList(props: ActionCardListProps) {
36
- const { children, required, value, error = false } = props
39
+ const { children, required, value, error = false, errorMessage } = props
37
40
 
38
41
  const handleChange = isMulti(props)
39
42
  ? (event: React.MouseEvent<HTMLElement, MouseEvent>, buttonValue: string) => {
@@ -70,15 +73,15 @@ export function ActionCardList(props: ActionCardListProps) {
70
73
  paddingLeft: theme.spacings.xs,
71
74
  paddingRight: theme.spacings.xs,
72
75
  },
73
- '& .ActionCard-root:first-of-type': {
76
+ '& > div:first-of-type.ActionCard-root': {
74
77
  borderTop: 2,
75
78
  borderTopColor: 'error.main',
76
- paddingTop: theme.spacings.xs,
79
+ paddingTop: theme.spacings.xxs,
77
80
  },
78
- '& .ActionCard-root:last-of-type': {
81
+ '& > div:last-of-type.ActionCard-root': {
79
82
  borderBottom: 2,
80
83
  borderBottomColor: 'error.main',
81
- paddingBottom: theme.spacings.xs,
84
+ paddingBottom: theme.spacings.xxs,
82
85
  },
83
86
  })),
84
87
  ]}
@@ -105,6 +108,15 @@ export function ActionCardList(props: ActionCardListProps) {
105
108
  : child.props.selected,
106
109
  })
107
110
  })}
111
+ {error && (
112
+ <Alert
113
+ severity='error'
114
+ variant='filled'
115
+ sx={{ borderStartStartRadius: 0, borderStartEndRadius: 0 }}
116
+ >
117
+ {errorMessage}
118
+ </Alert>
119
+ )}
108
120
  </Box>
109
121
  )
110
122
  }
@@ -0,0 +1,63 @@
1
+ /* eslint-disable import/no-extraneous-dependencies */
2
+ import { Controller, ControllerProps } from '@graphcommerce/react-hook-form'
3
+ import React from 'react'
4
+ import { ActionCardProps } from './ActionCard'
5
+ import { ActionCardList, ActionCardListProps } from './ActionCardList'
6
+
7
+ export type ActionCardItemBase = Pick<ActionCardProps, 'value'>
8
+
9
+ export type ActionCardItemRenderer<T> = Pick<ActionCardProps, 'selected' | 'hidden' | 'value'> & {
10
+ onReset: React.MouseEventHandler<HTMLButtonElement>
11
+ } & T
12
+
13
+ export type ActionCardListFormProps<T extends ActionCardItemBase> = Omit<
14
+ ActionCardListProps,
15
+ 'value'
16
+ > &
17
+ Omit<ControllerProps<any>, 'render'> & {
18
+ items: T[]
19
+ render: React.VFC<ActionCardItemRenderer<T>>
20
+ }
21
+
22
+ export function ActionCardListForm<T extends ActionCardItemBase>(
23
+ props: ActionCardListFormProps<T>,
24
+ ) {
25
+ const { required, rules, items, render, control, name, errorMessage } = props
26
+ const RenderItem = render as React.VFC<ActionCardItemRenderer<ActionCardItemBase>>
27
+
28
+ return (
29
+ <Controller
30
+ {...props}
31
+ control={control}
32
+ name={name}
33
+ rules={{
34
+ required,
35
+ ...rules,
36
+ validate: (v) => (v ? true : 'Please select a shipping address'),
37
+ }}
38
+ render={({ field: { onChange, value }, fieldState, formState }) => (
39
+ <ActionCardList
40
+ required
41
+ value={value}
42
+ onChange={(_, incomming) => onChange(incomming)}
43
+ error={formState.isSubmitted && !!fieldState.error}
44
+ errorMessage={errorMessage}
45
+ >
46
+ {items.map((item) => (
47
+ <RenderItem
48
+ {...item}
49
+ key={item.value}
50
+ value={item.value}
51
+ selected={value === item.value}
52
+ hidden={!!value && value !== item.value}
53
+ onReset={(e) => {
54
+ e.preventDefault()
55
+ onChange(null)
56
+ }}
57
+ />
58
+ ))}
59
+ </ActionCardList>
60
+ )}
61
+ />
62
+ )
63
+ }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # Change Log
2
2
 
3
+ ## 4.8.4
4
+
5
+ ### Patch Changes
6
+
7
+ - [#1509](https://github.com/graphcommerce-org/graphcommerce/pull/1509) [`0ab7c5465`](https://github.com/graphcommerce-org/graphcommerce/commit/0ab7c5465441cba9bf8cd185a6790ce2f443f4ed) Thanks [@paales](https://github.com/paales)! - SidebarGallery improvements (product page):
8
+
9
+ - Prevent vertical scrolling
10
+ - Disable zoom fab when there are no images
11
+ - Hide scroller dots when there in only one image
12
+ - Make sure the prev/next buttons are shown as expected
13
+
14
+ - Updated dependencies [[`0ab7c5465`](https://github.com/graphcommerce-org/graphcommerce/commit/0ab7c5465441cba9bf8cd185a6790ce2f443f4ed)]:
15
+ - @graphcommerce/framer-scroller@2.1.15
16
+
17
+ ## 4.8.3
18
+
19
+ ### Patch Changes
20
+
21
+ - [#1487](https://github.com/graphcommerce-org/graphcommerce/pull/1487) [`afc67103d`](https://github.com/graphcommerce-org/graphcommerce/commit/afc67103d0e00583e274465036fd287537f95e79) Thanks [@paales](https://github.com/paales)! - When additing an additional breakpoint it would throw a typescript error
22
+
23
+ - Updated dependencies []:
24
+ - @graphcommerce/framer-scroller@2.1.14
25
+
26
+ ## 4.8.2
27
+
28
+ ### Patch Changes
29
+
30
+ - [#1485](https://github.com/graphcommerce-org/graphcommerce/pull/1485) [`c8c246b8a`](https://github.com/graphcommerce-org/graphcommerce/commit/c8c246b8aaab0621b68a2fca2a1c529a56fad962) Thanks [@paales](https://github.com/paales)! - TextInputNumber: when adding a label it should be displayed properly
31
+
32
+ - Updated dependencies []:
33
+ - @graphcommerce/framer-scroller@2.1.13
34
+
3
35
  ## 4.8.1
4
36
 
5
37
  ### Patch Changes
@@ -114,6 +114,8 @@ export function SidebarGallery(props: SidebarGalleryProps) {
114
114
  const maxHeight = `calc(100vh - ${headerHeight} - ${galleryMargin} - ${extraSpacing})`
115
115
  const ratio = `calc(${height} / ${width} * 100%)`
116
116
 
117
+ const hasImages = images.length > 0
118
+
117
119
  return (
118
120
  <ScrollerProvider scrollSnapAlign='center'>
119
121
  <Row maxWidth={false} disableGutters className={classes.row} sx={sx}>
@@ -197,6 +199,7 @@ export function SidebarGallery(props: SidebarGalleryProps) {
197
199
  width={image.width}
198
200
  height={image.height}
199
201
  loading={idx === 0 ? 'eager' : 'lazy'}
202
+ sx={{ display: 'block' }}
200
203
  sizes={{
201
204
  0: '100vw',
202
205
  [theme.breakpoints.values.md]: zoomed ? '100vw' : '60vw',
@@ -221,11 +224,10 @@ export function SidebarGallery(props: SidebarGalleryProps) {
221
224
  <Fab
222
225
  size='small'
223
226
  className={classes.toggleIcon}
227
+ disabled={!hasImages}
224
228
  onMouseUp={toggle}
225
229
  aria-label='Toggle Fullscreen'
226
- sx={{
227
- boxShadow: theme.shadows[6],
228
- }}
230
+ sx={{ boxShadow: 6 }}
229
231
  >
230
232
  {!zoomed ? <IconSvg src={iconFullscreen} /> : <IconSvg src={iconFullscreenExit} />}
231
233
  </Fab>
@@ -286,13 +288,7 @@ export function SidebarGallery(props: SidebarGalleryProps) {
286
288
  },
287
289
  }}
288
290
  >
289
- <ScrollerDots
290
- layout
291
- sx={{
292
- background: alpha(theme.palette.background.paper, 1),
293
- boxShadow: theme.shadows[6],
294
- }}
295
- />
291
+ <ScrollerDots layout sx={{ backgroundColor: 'background.paper', boxShadow: 6 }} />
296
292
  </Box>
297
293
  </MotionBox>
298
294
 
@@ -97,13 +97,10 @@ export function TextInputNumber(props: TextInputNumberProps) {
97
97
  width: responsiveVal(80, 120),
98
98
  backgroundColor: 'inherit',
99
99
  },
100
-
101
100
  ...(Array.isArray(sx) ? sx : [sx]),
102
101
  ]}
103
102
  autoComplete='off'
104
- label={' '}
105
103
  id='quantity-input'
106
- InputLabelProps={{ shrink: false }}
107
104
  InputProps={{
108
105
  ...textFieldProps.InputProps,
109
106
  startAdornment: (
@@ -1,10 +1,9 @@
1
- import { BreakpointsOptions, experimental_sx, SxProps, Theme } from '@mui/material'
1
+ import { experimental_sx, SxProps, Theme } from '@mui/material'
2
2
  import { Shadows } from '@mui/material/styles/shadows'
3
- import type { SetRequired } from 'type-fest'
4
3
 
5
4
  // https://material.io/design/environment/elevation.html#default-elevations
6
5
 
7
- const breakpoints: SetRequired<BreakpointsOptions, 'values'> = {
6
+ const breakpoints = {
8
7
  values: {
9
8
  xs: 0,
10
9
  sm: 600,
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.8.1",
5
+ "version": "4.8.4",
6
6
  "author": "",
7
7
  "license": "MIT",
8
8
  "sideEffects": false,
@@ -20,7 +20,7 @@
20
20
  "@emotion/server": "^11.4.0",
21
21
  "@emotion/styled": "^11.6.0",
22
22
  "@graphcommerce/framer-next-pages": "3.2.2",
23
- "@graphcommerce/framer-scroller": "2.1.12",
23
+ "@graphcommerce/framer-scroller": "2.1.15",
24
24
  "@graphcommerce/framer-utils": "3.1.3",
25
25
  "@graphcommerce/image": "3.1.6",
26
26
  "react-is": "^17.0.0",