@graphcommerce/next-ui 4.13.1 → 4.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,7 +11,7 @@ export type ActionCardProps = {
11
11
  price?: React.ReactNode
12
12
  after?: React.ReactNode
13
13
  secondaryAction?: React.ReactNode
14
- onClick?: (e: FormEvent<HTMLElement>, v: string | number) => void
14
+ onClick?: (event: React.MouseEvent<HTMLElement>, value: string | number) => void
15
15
  selected?: boolean
16
16
  hidden?: boolean
17
17
  value: string | number
@@ -66,7 +66,7 @@ export function ActionCard(props: ActionCardProps) {
66
66
 
67
67
  const classes = withState({ hidden, disabled, selected, image: Boolean(image) })
68
68
 
69
- const handleClick = (event: FormEvent<HTMLElement>) => onClick?.(event, value)
69
+ const handleClick = (event: React.MouseEvent<HTMLElement>) => onClick?.(event, value)
70
70
 
71
71
  return (
72
72
  <ButtonBase
@@ -3,19 +3,20 @@ import { AnimatePresence } from 'framer-motion'
3
3
  import React from 'react'
4
4
  import { isFragment } from 'react-is'
5
5
  import { AnimatedRow } from '../AnimatedRow/AnimatedRow'
6
+ import { ActionCardProps } from './ActionCard'
6
7
 
7
8
  type MultiSelect = {
8
9
  multiple: true
9
- value: string[]
10
+ value: (string | number)[]
10
11
 
11
- onChange?: (event: React.MouseEvent<HTMLElement>, value: string[]) => void
12
+ onChange?: (event: React.MouseEvent<HTMLElement>, value: MultiSelect['value']) => void
12
13
  }
13
14
  type Select = {
14
15
  multiple?: false
15
- value: string
16
+ value: string | number
16
17
 
17
18
  /** Value is null when deselected when not required */
18
- onChange?: (event: React.MouseEvent<HTMLElement>, value: string | null) => void
19
+ onChange?: (event: React.MouseEvent<HTMLElement>, value: Select['value'] | null) => void
19
20
  }
20
21
 
21
22
  export type ActionCardListProps<SelectOrMulti = MultiSelect | Select> = {
@@ -29,94 +30,124 @@ function isMulti(props: ActionCardListProps): props is ActionCardListProps<Multi
29
30
  return props.multiple === true
30
31
  }
31
32
 
32
- function isValueSelected(value: string, candidate: string | string[]) {
33
- if (candidate === undefined || value === undefined) return false
33
+ function isValueSelected(
34
+ value: ActionCardProps['value'],
35
+ candidate?: Select['value'] | MultiSelect['value'],
36
+ ) {
37
+ if (candidate === undefined) return false
34
38
  if (Array.isArray(candidate)) return candidate.indexOf(value) >= 0
35
39
  return value === candidate
36
40
  }
37
41
 
38
- export function ActionCardList(props: ActionCardListProps) {
39
- const { children, required, value, error = false, errorMessage } = props
42
+ export const ActionCardList = React.forwardRef<HTMLDivElement, ActionCardListProps>(
43
+ (props, ref) => {
44
+ const { children, required, error = false, errorMessage } = props
40
45
 
41
- const handleChange = isMulti(props)
42
- ? (event: React.MouseEvent<HTMLElement, MouseEvent>, buttonValue: string) => {
43
- const { onChange } = props
44
- const index = Boolean(value) && value?.indexOf(buttonValue)
45
- let newValue: string[]
46
+ const handleChange: ActionCardProps['onClick'] = isMulti(props)
47
+ ? (event, v) => {
48
+ const { onChange, value } = props
49
+ const index = Boolean(value) && value?.indexOf(v)
50
+ let newValue: typeof value
46
51
 
47
- if (Array.isArray(value) && value.length && index && index >= 0) {
48
- newValue = value.slice()
49
- newValue.splice(index, 1)
50
- } else {
51
- newValue = value ? [...value, buttonValue] : [buttonValue]
52
+ if (value.length && index && index >= 0) {
53
+ newValue = value.slice()
54
+ newValue.splice(index, 1)
55
+ } else {
56
+ newValue = value ? [...value, v] : [v]
57
+ }
58
+ onChange?.(event, newValue)
52
59
  }
53
- onChange?.(event, newValue)
54
- }
55
- : (event: React.MouseEvent<HTMLElement, MouseEvent>, buttonValue: string) => {
56
- const { onChange } = props
60
+ : (event, v) => {
61
+ const { onChange, value } = props
57
62
 
58
- if (value === buttonValue) return
59
- if (required) onChange?.(event, buttonValue)
60
- else onChange?.(event, value === buttonValue ? null : buttonValue)
61
- }
63
+ if (value !== v) {
64
+ if (required) onChange?.(event, v)
65
+ else onChange?.(event, value === v ? null : v)
66
+ }
67
+ }
62
68
 
63
- return (
64
- <Box
65
- sx={[
66
- error &&
67
- ((theme) => ({
68
- '& .ActionCard-root': {
69
- borderLeft: 2,
70
- borderRight: 2,
71
- borderLeftColor: 'error.main',
72
- borderRightColor: 'error.main',
73
- paddingLeft: theme.spacings.xs,
74
- paddingRight: theme.spacings.xs,
75
- },
76
- '& > div:first-of-type.ActionCard-root': {
77
- borderTop: 2,
78
- borderTopColor: 'error.main',
79
- paddingTop: theme.spacings.xxs,
80
- },
81
- '& > div:last-of-type.ActionCard-root': {
82
- borderBottom: 2,
83
- borderBottomColor: 'error.main',
84
- paddingBottom: theme.spacings.xxs,
85
- },
86
- })),
87
- ]}
69
+ type ActionCardLike = React.ReactElement<
70
+ Pick<ActionCardProps, 'value' | 'selected' | 'disabled' | 'onClick'>
88
71
  >
89
- {React.Children.map(children, (child) => {
90
- if (!React.isValidElement(child)) return null
72
+ function isActionCardLike(el: React.ReactElement): el is ActionCardLike {
73
+ const hasValue = (el as ActionCardLike).props.value
91
74
 
75
+ if (process.env.NODE_ENV !== 'production') {
76
+ if (!hasValue) console.error(el, `must be an instance of ActionCard`)
77
+ }
78
+ return (el as ActionCardLike).props.value !== undefined
79
+ }
80
+
81
+ // Make sure the children are cardlike
82
+ const childReactNodes = React.Children.toArray(children)
83
+ .filter(React.isValidElement)
84
+ .filter(isActionCardLike)
85
+ .filter((child) => {
92
86
  if (process.env.NODE_ENV !== 'production') {
93
- if (isFragment(child)) {
87
+ if (isFragment(child))
94
88
  console.error(
95
89
  [
96
90
  "@graphcommerce/next-ui: The ActionCardList component doesn't accept a Fragment as a child.",
97
- 'Consider providing an array instead.',
91
+ 'Consider providing an array instead',
98
92
  ].join('\n'),
99
93
  )
100
- }
101
94
  }
102
95
 
103
- return React.cloneElement(child, {
104
- onClick: handleChange,
105
- selected:
106
- child.props.selected === undefined
107
- ? isValueSelected(child.props.value as string, value)
108
- : child.props.selected,
109
- })
110
- })}
111
- {error && (
112
- <Alert
113
- severity='error'
114
- variant='filled'
115
- sx={{ borderStartStartRadius: 0, borderStartEndRadius: 0 }}
116
- >
117
- {errorMessage}
118
- </Alert>
119
- )}
120
- </Box>
121
- )
122
- }
96
+ return !isFragment(child)
97
+ })
98
+
99
+ // Make sure the selected values is in the list of all possible values
100
+ const value = childReactNodes.find(
101
+ // eslint-disable-next-line react/destructuring-assignment
102
+ (child) => child.props.value === props.value && child.props.disabled !== true,
103
+ )?.props.value
104
+
105
+ return (
106
+ <Box
107
+ ref={ref}
108
+ sx={[
109
+ error &&
110
+ ((theme) => ({
111
+ '& .ActionCard-root': {
112
+ borderLeft: 2,
113
+ borderRight: 2,
114
+ borderLeftColor: 'error.main',
115
+ borderRightColor: 'error.main',
116
+ paddingLeft: theme.spacings.xs,
117
+ paddingRight: theme.spacings.xs,
118
+ },
119
+ '& > div:first-of-type.ActionCard-root': {
120
+ borderTop: 2,
121
+ borderTopColor: 'error.main',
122
+ paddingTop: theme.spacings.xxs,
123
+ },
124
+ '& > div:last-of-type.ActionCard-root': {
125
+ borderBottom: 2,
126
+ borderBottomColor: 'error.main',
127
+ paddingBottom: theme.spacings.xxs,
128
+ },
129
+ })),
130
+ ]}
131
+ >
132
+ {childReactNodes.map((child) =>
133
+ React.cloneElement(child, {
134
+ onClick: handleChange,
135
+ selected:
136
+ child.props.selected === undefined
137
+ ? isValueSelected(child.props.value, value)
138
+ : child.props.selected,
139
+ }),
140
+ )}
141
+ {error && (
142
+ <Alert
143
+ severity='error'
144
+ variant='filled'
145
+ sx={{ borderStartStartRadius: 0, borderStartEndRadius: 0 }}
146
+ >
147
+ {errorMessage}
148
+ </Alert>
149
+ )}
150
+ </Box>
151
+ )
152
+ },
153
+ )
@@ -34,10 +34,11 @@ export function ActionCardListForm<T extends ActionCardItemBase>(
34
34
  control={control}
35
35
  name={name}
36
36
  rules={{ required, ...rules, validate: (v) => (v ? true : errorMessage) }}
37
- render={({ field: { onChange, value }, fieldState, formState }) => (
37
+ render={({ field: { onChange, value, onBlur, ref }, fieldState, formState }) => (
38
38
  <ActionCardList
39
39
  required
40
40
  value={value}
41
+ ref={ref}
41
42
  onChange={(_, incomming) => onChange(incomming)}
42
43
  error={formState.isSubmitted && !!fieldState.error}
43
44
  errorMessage={fieldState.error?.message}
@@ -20,7 +20,7 @@ const { classes } = extendableComponent(name, parts)
20
20
  export function BlogListItem(props: BlogListItemProps) {
21
21
  const { asset, url, date, title, sx = [] } = props
22
22
 
23
- const formatter = useDateTimeFormat({ year: 'numeric', month: 'long', day: 'numeric' })
23
+ const formatter = useDateTimeFormat({ dateStyle: 'long' })
24
24
 
25
25
  return (
26
26
  <Box
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Change Log
2
2
 
3
+ ## 4.14.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#1553](https://github.com/graphcommerce-org/graphcommerce/pull/1553) [`323fdee4b`](https://github.com/graphcommerce-org/graphcommerce/commit/323fdee4b15ae23e0e84dd0588cb2c6446dcfd50) Thanks [@NickdeK](https://github.com/NickdeK)! - Added a new cookies utility to load cookies on the frontend
8
+
9
+ ### Patch Changes
10
+
11
+ - [#1553](https://github.com/graphcommerce-org/graphcommerce/pull/1553) [`afcd8e4bf`](https://github.com/graphcommerce-org/graphcommerce/commit/afcd8e4bfb7010da4d5faeed85b61991ed7975f4) Thanks [@NickdeK](https://github.com/NickdeK)! - ActionCardList will now show all options when the selected value isn't in any of the options
12
+
13
+ * [#1553](https://github.com/graphcommerce-org/graphcommerce/pull/1553) [`02e1988e5`](https://github.com/graphcommerce-org/graphcommerce/commit/02e1988e5f361c6f66ae30d3bbee38ef2ac062df) Thanks [@NickdeK](https://github.com/NickdeK)! - Make sure the useDateTimeFormat isn't giving hydration warnings
14
+
15
+ * Updated dependencies []:
16
+ - @graphcommerce/framer-scroller@2.1.24
17
+
3
18
  ## 4.13.1
4
19
 
5
20
  ### Patch Changes
package/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './ActionCard/ActionCard'
2
2
  export * from './ActionCard/ActionCardList'
3
+ export * from './ActionCard/ActionCardListForm'
3
4
  export * from './AnimatedRow/AnimatedRow'
4
5
  export * from './Blog/BlogAuthor/BlogAuthor'
5
6
  export * from './Blog/BlogContent/BlogContent'
@@ -55,3 +56,4 @@ export * from './ToggleButton/ToggleButton'
55
56
  export * from './ToggleButtonGroup/ToggleButtonGroup'
56
57
  export * from './UspList/UspList'
57
58
  export * from './UspList/UspListItem'
59
+ export * from './utils/cookie'
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.13.1",
5
+ "version": "4.14.0",
6
6
  "author": "",
7
7
  "license": "MIT",
8
8
  "sideEffects": false,
@@ -20,9 +20,10 @@
20
20
  "@emotion/server": "^11.4.0",
21
21
  "@emotion/styled": "^11.9.3",
22
22
  "@graphcommerce/framer-next-pages": "3.2.4",
23
- "@graphcommerce/framer-scroller": "2.1.23",
23
+ "@graphcommerce/framer-scroller": "2.1.24",
24
24
  "@graphcommerce/framer-utils": "3.1.4",
25
25
  "@graphcommerce/image": "3.1.7",
26
+ "cookie": "^0.5.0",
26
27
  "react-is": "^18.2.0",
27
28
  "react-schemaorg": "^2.0.0",
28
29
  "schema-dts": "^1.1.0"
@@ -42,6 +43,7 @@
42
43
  "@graphcommerce/prettier-config-pwa": "^4.0.6",
43
44
  "@graphcommerce/typescript-config-pwa": "^4.0.4",
44
45
  "@playwright/test": "^1.21.1",
46
+ "@types/cookie": "^0.5.1",
45
47
  "@types/react-is": "^17.0.3",
46
48
  "type-fest": "^2.12.2",
47
49
  "typescript": "4.7.4"
@@ -0,0 +1,33 @@
1
+ import { serialize, parse, CookieSerializeOptions } from 'cookie'
2
+
3
+ /** Read a cookie */
4
+ export function cookie(name: string): string | undefined
5
+ /** Set a cookie */
6
+ export function cookie(name: string, value: string, options?: CookieSerializeOptions): void
7
+ /** Delete a cookie */
8
+ export function cookie(name: string, value: null): void
9
+ /** Function to handle the three different cases */
10
+ export function cookie(name: string, value?: string | null, options?: CookieSerializeOptions) {
11
+ if (typeof window === 'undefined') {
12
+ return undefined
13
+ }
14
+
15
+ // Read a cookie
16
+ if (typeof value === 'undefined') return parse(document.cookie)[name]
17
+
18
+ // Set a cookie
19
+ if (typeof value === 'string') {
20
+ const serialized = serialize(name, value, { path: '/', maxAge: 31536000, ...options })
21
+ document.cookie = serialized
22
+ return undefined
23
+ }
24
+
25
+ // Delete a cookie
26
+ if (value === null) {
27
+ const serialized = serialize(name, '', { path: '/', maxAge: 0 })
28
+ document.cookie = serialized
29
+ return undefined
30
+ }
31
+
32
+ return undefined
33
+ }