@graphcommerce/next-ui 4.13.1 → 4.15.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.
- package/ActionCard/ActionCard.tsx +2 -2
- package/ActionCard/ActionCardList.tsx +107 -76
- package/ActionCard/ActionCardListForm.tsx +2 -1
- package/Blog/BlogListItem/BlogListItem.tsx +1 -1
- package/CHANGELOG.md +42 -0
- package/LayoutOverlay/test/LayoutOverlayDemo.tsx +1 -1
- package/Navigation/components/NavigationItem.tsx +51 -12
- package/Navigation/components/NavigationList.tsx +4 -3
- package/Navigation/components/NavigationOverlay.tsx +71 -16
- package/Navigation/components/NavigationProvider.tsx +7 -2
- package/Navigation/hooks/useNavigation.ts +2 -1
- package/Overlay/components/OverlayBase.tsx +4 -1
- package/TextInputNumber/TextInputNumber.tsx +0 -1
- package/index.ts +2 -0
- package/package.json +4 -2
- package/utils/cookie.ts +33 -0
|
@@ -11,7 +11,7 @@ export type ActionCardProps = {
|
|
|
11
11
|
price?: React.ReactNode
|
|
12
12
|
after?: React.ReactNode
|
|
13
13
|
secondaryAction?: React.ReactNode
|
|
14
|
-
onClick?: (
|
|
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:
|
|
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:
|
|
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:
|
|
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(
|
|
33
|
-
|
|
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
|
|
39
|
-
|
|
42
|
+
export const ActionCardList = React.forwardRef<HTMLDivElement, ActionCardListProps>(
|
|
43
|
+
(props, ref) => {
|
|
44
|
+
const { children, required, error = false, errorMessage } = props
|
|
40
45
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
: (event: React.MouseEvent<HTMLElement, MouseEvent>, buttonValue: string) => {
|
|
56
|
-
const { onChange } = props
|
|
60
|
+
: (event, v) => {
|
|
61
|
+
const { onChange, value } = props
|
|
57
62
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
63
|
+
if (value !== v) {
|
|
64
|
+
if (required) onChange?.(event, v)
|
|
65
|
+
else onChange?.(event, value === v ? null : v)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
62
68
|
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
90
|
-
|
|
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
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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({
|
|
23
|
+
const formatter = useDateTimeFormat({ dateStyle: 'long' })
|
|
24
24
|
|
|
25
25
|
return (
|
|
26
26
|
<Box
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## 4.15.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#1570](https://github.com/graphcommerce-org/graphcommerce/pull/1570) [`a88f166f0`](https://github.com/graphcommerce-org/graphcommerce/commit/a88f166f0115c58254fe47171da51a5850658a32) Thanks [@paales](https://github.com/paales)! - Solve issue where chrome would report duplicate ids
|
|
8
|
+
|
|
9
|
+
- Updated dependencies []:
|
|
10
|
+
- @graphcommerce/framer-scroller@2.1.26
|
|
11
|
+
|
|
12
|
+
## 4.15.0
|
|
13
|
+
|
|
14
|
+
### Minor Changes
|
|
15
|
+
|
|
16
|
+
- [#1566](https://github.com/graphcommerce-org/graphcommerce/pull/1566) [`e167992df`](https://github.com/graphcommerce-org/graphcommerce/commit/e167992dfdc6964a392af719667f8a188626ab1b) Thanks [@ErwinOtten](https://github.com/ErwinOtten)! - Introduced `@graphcommerce/next-ui/navigation` component.
|
|
17
|
+
|
|
18
|
+
- Navigation is always present in the DOM
|
|
19
|
+
- Configurable in LayoutNavigation.tsx
|
|
20
|
+
- Show categories directly, or nest them in a 'products' button
|
|
21
|
+
- Choose prefered mouseEvent: click or hover
|
|
22
|
+
|
|
23
|
+
* [#1566](https://github.com/graphcommerce-org/graphcommerce/pull/1566) [`9c2504b4e`](https://github.com/graphcommerce-org/graphcommerce/commit/9c2504b4ed75f41d3003c4d3339814010e85e37e) Thanks [@ErwinOtten](https://github.com/ErwinOtten)! - publish navigation
|
|
24
|
+
|
|
25
|
+
### Patch Changes
|
|
26
|
+
|
|
27
|
+
- Updated dependencies []:
|
|
28
|
+
- @graphcommerce/framer-scroller@2.1.25
|
|
29
|
+
|
|
30
|
+
## 4.14.0
|
|
31
|
+
|
|
32
|
+
### Minor Changes
|
|
33
|
+
|
|
34
|
+
- [#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
|
|
35
|
+
|
|
36
|
+
### Patch Changes
|
|
37
|
+
|
|
38
|
+
- [#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
|
|
39
|
+
|
|
40
|
+
* [#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
|
|
41
|
+
|
|
42
|
+
* Updated dependencies []:
|
|
43
|
+
- @graphcommerce/framer-scroller@2.1.24
|
|
44
|
+
|
|
3
45
|
## 4.13.1
|
|
4
46
|
|
|
5
47
|
### Patch Changes
|
|
@@ -3,7 +3,7 @@ import { LayoutOverlay, LayoutOverlayProps } from '../components/LayoutOverlay'
|
|
|
3
3
|
|
|
4
4
|
export type LayoutOverlayState = Omit<
|
|
5
5
|
LayoutOverlayProps,
|
|
6
|
-
'children' | 'sx' | 'sxBackdrop' | 'mdSpacingTop' | 'smSpacingTop'
|
|
6
|
+
'children' | 'sx' | 'sxBackdrop' | 'mdSpacingTop' | 'smSpacingTop' | 'overlayPaneProps'
|
|
7
7
|
>
|
|
8
8
|
|
|
9
9
|
export function useLayoutState() {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-use-before-define */
|
|
2
|
-
import { Box, ListItemButton, styled, useEventCallback } from '@mui/material'
|
|
2
|
+
import { Box, ListItemButton, styled, Theme, useEventCallback, useMediaQuery } from '@mui/material'
|
|
3
3
|
import PageLink from 'next/link'
|
|
4
|
+
import { useEffect } from 'react'
|
|
4
5
|
import { IconSvg } from '../../IconSvg'
|
|
5
6
|
import { extendableComponent } from '../../Styles/extendableComponent'
|
|
6
7
|
import { iconChevronRight } from '../../icons'
|
|
@@ -26,7 +27,12 @@ type NavigationItemProps = NavigationNode & {
|
|
|
26
27
|
parentPath: NavigationPath
|
|
27
28
|
idx: number
|
|
28
29
|
NavigationList: typeof NavigationList
|
|
29
|
-
} & OwnerState
|
|
30
|
+
} & OwnerState &
|
|
31
|
+
mouseEventPref
|
|
32
|
+
|
|
33
|
+
export type mouseEventPref = {
|
|
34
|
+
mouseEvent: 'click' | 'hover'
|
|
35
|
+
}
|
|
30
36
|
|
|
31
37
|
const componentName = 'NavigationItem'
|
|
32
38
|
const parts = ['li', 'ul', 'item'] as const
|
|
@@ -39,10 +45,10 @@ const { withState } = extendableComponent<OwnerState, typeof componentName, type
|
|
|
39
45
|
const NavigationLI = styled('li')({ display: 'contents' })
|
|
40
46
|
|
|
41
47
|
export function NavigationItem(props: NavigationItemProps) {
|
|
42
|
-
const { id, parentPath, idx, first, last, NavigationList } = props
|
|
48
|
+
const { id, parentPath, idx, first, last, NavigationList, mouseEvent } = props
|
|
43
49
|
|
|
44
50
|
const row = idx + 1
|
|
45
|
-
const { selected, select, hideRootOnNavigate, onClose } = useNavigation()
|
|
51
|
+
const { selected, select, hideRootOnNavigate, onClose, animating } = useNavigation()
|
|
46
52
|
|
|
47
53
|
const itemPath = [...parentPath, id]
|
|
48
54
|
const isSelected = selected.slice(0, itemPath.length).join('/') === itemPath.join('/')
|
|
@@ -59,6 +65,8 @@ export function NavigationItem(props: NavigationItemProps) {
|
|
|
59
65
|
onClose?.(e, href)
|
|
60
66
|
})
|
|
61
67
|
|
|
68
|
+
const isDesktop = useMediaQuery<Theme>((theme) => theme.breakpoints.up('md'))
|
|
69
|
+
|
|
62
70
|
if (isNavigationButton(props)) {
|
|
63
71
|
const { childItems, name } = props
|
|
64
72
|
return (
|
|
@@ -66,18 +74,44 @@ export function NavigationItem(props: NavigationItemProps) {
|
|
|
66
74
|
<ListItemButton
|
|
67
75
|
className={classes.item}
|
|
68
76
|
role='button'
|
|
69
|
-
sx={
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
77
|
+
sx={[
|
|
78
|
+
(theme) => ({
|
|
79
|
+
gridRowStart: row,
|
|
80
|
+
gridColumnStart: column,
|
|
81
|
+
gap: theme.spacings.xxs,
|
|
82
|
+
display: hideItem ? 'none' : 'flex',
|
|
83
|
+
'&.Mui-disabled': {
|
|
84
|
+
opacity: 1,
|
|
85
|
+
background: theme.palette.action.hover,
|
|
86
|
+
},
|
|
87
|
+
}),
|
|
88
|
+
mouseEvent === 'hover'
|
|
89
|
+
? {
|
|
90
|
+
'&.Mui-disabled': {
|
|
91
|
+
cursor: 'pointer',
|
|
92
|
+
pointerEvents: 'auto',
|
|
93
|
+
},
|
|
94
|
+
}
|
|
95
|
+
: {},
|
|
96
|
+
]}
|
|
75
97
|
disabled={isSelected}
|
|
76
98
|
tabIndex={selected.join(',').includes(parentPath.join(',')) ? undefined : -1}
|
|
77
99
|
onClick={(e) => {
|
|
78
100
|
e.preventDefault()
|
|
79
|
-
if (!isSelected)
|
|
101
|
+
if (!isSelected && animating.current === false) {
|
|
102
|
+
select(itemPath)
|
|
103
|
+
}
|
|
80
104
|
}}
|
|
105
|
+
onMouseEnter={
|
|
106
|
+
itemPath.length > 1 && mouseEvent === 'hover'
|
|
107
|
+
? (e) => {
|
|
108
|
+
if (isDesktop && animating.current === false && !isSelected) {
|
|
109
|
+
e.preventDefault()
|
|
110
|
+
setTimeout(() => select(itemPath), 0)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
: undefined
|
|
114
|
+
}
|
|
81
115
|
>
|
|
82
116
|
<Box
|
|
83
117
|
component='span'
|
|
@@ -93,7 +127,12 @@ export function NavigationItem(props: NavigationItemProps) {
|
|
|
93
127
|
<IconSvg src={iconChevronRight} sx={{ flexShrink: 0 }} />
|
|
94
128
|
</ListItemButton>
|
|
95
129
|
|
|
96
|
-
<NavigationList
|
|
130
|
+
<NavigationList
|
|
131
|
+
items={childItems}
|
|
132
|
+
selected={isSelected}
|
|
133
|
+
parentPath={itemPath}
|
|
134
|
+
mouseEvent={mouseEvent}
|
|
135
|
+
/>
|
|
97
136
|
</NavigationLI>
|
|
98
137
|
)
|
|
99
138
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { styled } from '@mui/material'
|
|
2
2
|
import { extendableComponent } from '../../Styles/extendableComponent'
|
|
3
3
|
import { NavigationNode, NavigationPath } from '../hooks/useNavigation'
|
|
4
|
-
import { NavigationItem } from './NavigationItem'
|
|
4
|
+
import { NavigationItem, mouseEventPref } from './NavigationItem'
|
|
5
5
|
|
|
6
6
|
const NavigationUList = styled('ul')({})
|
|
7
7
|
|
|
@@ -9,7 +9,7 @@ type NavigationItemsProps = {
|
|
|
9
9
|
parentPath?: NavigationPath
|
|
10
10
|
items: NavigationNode[]
|
|
11
11
|
selected?: boolean
|
|
12
|
-
}
|
|
12
|
+
} & mouseEventPref
|
|
13
13
|
|
|
14
14
|
type OwnerState = {
|
|
15
15
|
column: number
|
|
@@ -23,7 +23,7 @@ const { withState } = extendableComponent<OwnerState, typeof name, typeof parts>
|
|
|
23
23
|
// const parts = ['li', 'ul', 'item'] as const
|
|
24
24
|
|
|
25
25
|
export function NavigationList(props: NavigationItemsProps) {
|
|
26
|
-
const { items, parentPath = [], selected = false } = props
|
|
26
|
+
const { items, parentPath = [], selected = false, mouseEvent } = props
|
|
27
27
|
|
|
28
28
|
return (
|
|
29
29
|
<NavigationUList
|
|
@@ -43,6 +43,7 @@ export function NavigationList(props: NavigationItemsProps) {
|
|
|
43
43
|
first={idx === 0}
|
|
44
44
|
last={idx === items.length - 1}
|
|
45
45
|
column={0}
|
|
46
|
+
mouseEvent={mouseEvent}
|
|
46
47
|
/>
|
|
47
48
|
))}
|
|
48
49
|
</NavigationUList>
|
|
@@ -3,6 +3,7 @@ import { i18n } from '@lingui/core'
|
|
|
3
3
|
import { Trans } from '@lingui/react'
|
|
4
4
|
import { Box, Fab, SxProps, Theme, useEventCallback, useMediaQuery } from '@mui/material'
|
|
5
5
|
import { m } from 'framer-motion'
|
|
6
|
+
import { useState } from 'react'
|
|
6
7
|
import { IconSvg, useIconSvgSize } from '../../IconSvg'
|
|
7
8
|
import { LayoutHeaderContent } from '../../Layout/components/LayoutHeaderContent'
|
|
8
9
|
import { LayoutTitle } from '../../Layout/components/LayoutTitle'
|
|
@@ -18,14 +19,26 @@ import {
|
|
|
18
19
|
NavigationNodeHref,
|
|
19
20
|
useNavigation,
|
|
20
21
|
} from '../hooks/useNavigation'
|
|
22
|
+
import { mouseEventPref } from './NavigationItem'
|
|
21
23
|
import { NavigationList } from './NavigationList'
|
|
22
24
|
|
|
25
|
+
type LayoutOverlayVariant = 'left' | 'bottom' | 'right'
|
|
26
|
+
type LayoutOverlaySize = 'floating' | 'minimal' | 'full'
|
|
27
|
+
type LayoutOverlayAlign = 'start' | 'end' | 'center' | 'stretch'
|
|
28
|
+
|
|
23
29
|
type NavigationOverlayProps = {
|
|
24
30
|
active: boolean
|
|
25
31
|
sx?: SxProps<Theme>
|
|
26
32
|
stretchColumns?: boolean
|
|
27
|
-
|
|
28
|
-
|
|
33
|
+
variantSm: LayoutOverlayVariant
|
|
34
|
+
variantMd: LayoutOverlayVariant
|
|
35
|
+
sizeSm?: LayoutOverlaySize
|
|
36
|
+
sizeMd?: LayoutOverlaySize
|
|
37
|
+
justifySm?: LayoutOverlayAlign
|
|
38
|
+
justifyMd?: LayoutOverlayAlign
|
|
39
|
+
itemWidthSm?: string
|
|
40
|
+
itemWidthMd?: string
|
|
41
|
+
} & mouseEventPref
|
|
29
42
|
|
|
30
43
|
function findCurrent(
|
|
31
44
|
items: NavigationContextType['items'],
|
|
@@ -55,8 +68,21 @@ const parts = ['root', 'navigation', 'header', 'column'] as const
|
|
|
55
68
|
const { classes } = extendableComponent(componentName, parts)
|
|
56
69
|
|
|
57
70
|
export function NavigationOverlay(props: NavigationOverlayProps) {
|
|
58
|
-
const {
|
|
59
|
-
|
|
71
|
+
const {
|
|
72
|
+
active,
|
|
73
|
+
sx,
|
|
74
|
+
stretchColumns,
|
|
75
|
+
variantMd,
|
|
76
|
+
variantSm,
|
|
77
|
+
justifyMd,
|
|
78
|
+
justifySm,
|
|
79
|
+
sizeMd,
|
|
80
|
+
sizeSm,
|
|
81
|
+
itemWidthSm,
|
|
82
|
+
itemWidthMd,
|
|
83
|
+
mouseEvent,
|
|
84
|
+
} = props
|
|
85
|
+
const { selected, select, items, onClose, animating } = useNavigation()
|
|
60
86
|
|
|
61
87
|
const fabSize = useFabSize('responsive')
|
|
62
88
|
const svgSize = useIconSvgSize('large')
|
|
@@ -74,12 +100,20 @@ export function NavigationOverlay(props: NavigationOverlayProps) {
|
|
|
74
100
|
className={classes.root}
|
|
75
101
|
active={active}
|
|
76
102
|
onClosed={onClose}
|
|
77
|
-
variantSm=
|
|
78
|
-
sizeSm=
|
|
79
|
-
justifySm=
|
|
80
|
-
variantMd=
|
|
81
|
-
sizeMd=
|
|
82
|
-
justifyMd=
|
|
103
|
+
variantSm={variantSm}
|
|
104
|
+
sizeSm={sizeSm}
|
|
105
|
+
justifySm={justifySm}
|
|
106
|
+
variantMd={variantMd}
|
|
107
|
+
sizeMd={sizeMd}
|
|
108
|
+
justifyMd={justifyMd}
|
|
109
|
+
overlayPaneProps={{
|
|
110
|
+
onLayoutAnimationStart: () => {
|
|
111
|
+
animating.current = true
|
|
112
|
+
},
|
|
113
|
+
onLayoutAnimationComplete: () => {
|
|
114
|
+
animating.current = false
|
|
115
|
+
},
|
|
116
|
+
}}
|
|
83
117
|
sx={{
|
|
84
118
|
zIndex: 'drawer',
|
|
85
119
|
'& .LayoutOverlayBase-overlayPane': {
|
|
@@ -151,14 +185,35 @@ export function NavigationOverlay(props: NavigationOverlayProps) {
|
|
|
151
185
|
sx={(theme) => ({
|
|
152
186
|
display: 'grid',
|
|
153
187
|
alignItems: !stretchColumns ? 'start' : undefined,
|
|
154
|
-
|
|
188
|
+
'& .NavigationItem-item': {
|
|
189
|
+
// eslint-disable-next-line no-nested-ternary
|
|
190
|
+
width: itemWidthMd
|
|
191
|
+
? selected.length >= 1
|
|
192
|
+
? `calc(${itemWidthMd} + 1px)`
|
|
193
|
+
: itemWidthMd
|
|
194
|
+
: 'auto',
|
|
195
|
+
},
|
|
155
196
|
[theme.breakpoints.down('md')]: {
|
|
197
|
+
width:
|
|
198
|
+
sizeSm !== 'floating'
|
|
199
|
+
? `calc(${itemWidthSm || '100vw'} + ${selected.length}px)`
|
|
200
|
+
: `calc(${itemWidthSm || '100vw'} - ${theme.page.horizontal} - ${
|
|
201
|
+
theme.page.horizontal
|
|
202
|
+
})`,
|
|
203
|
+
minWidth: 200,
|
|
156
204
|
overflow: 'hidden',
|
|
157
205
|
scrollSnapType: 'x mandatory',
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
206
|
+
'& .NavigationItem-item': {
|
|
207
|
+
width:
|
|
208
|
+
sizeSm !== 'floating'
|
|
209
|
+
? `calc(${itemWidthSm || '100vw'} - ${theme.spacings.md} - ${
|
|
210
|
+
theme.spacings.md
|
|
211
|
+
} + ${selected.length}px)`
|
|
212
|
+
: `calc(${itemWidthSm || '100vw'} - ${theme.spacings.md} - ${
|
|
213
|
+
theme.spacings.md
|
|
214
|
+
} - ${theme.page.horizontal} - ${theme.page.horizontal})`,
|
|
215
|
+
minWidth: `calc(${200}px - ${theme.spacings.md} - ${theme.spacings.md})`,
|
|
216
|
+
},
|
|
162
217
|
},
|
|
163
218
|
})}
|
|
164
219
|
>
|
|
@@ -228,7 +283,7 @@ export function NavigationOverlay(props: NavigationOverlayProps) {
|
|
|
228
283
|
/>
|
|
229
284
|
)}
|
|
230
285
|
|
|
231
|
-
<NavigationList items={items} selected />
|
|
286
|
+
<NavigationList items={items} selected mouseEvent={mouseEvent} />
|
|
232
287
|
</Box>
|
|
233
288
|
</Box>
|
|
234
289
|
</MotionDiv>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useEventCallback } from '@mui/material'
|
|
2
2
|
import { MotionConfig } from 'framer-motion'
|
|
3
|
-
import { useState, useMemo } from 'react'
|
|
3
|
+
import { useState, useMemo, SetStateAction, useRef } from 'react'
|
|
4
4
|
import { isElement } from 'react-is'
|
|
5
5
|
import {
|
|
6
6
|
NavigationNode,
|
|
@@ -16,6 +16,8 @@ export type NavigationProviderProps = {
|
|
|
16
16
|
closeAfterNavigate?: boolean
|
|
17
17
|
children?: React.ReactNode
|
|
18
18
|
animationDuration?: number
|
|
19
|
+
selected: NavigationPath
|
|
20
|
+
setSelected: (value: SetStateAction<NavigationPath>) => void
|
|
19
21
|
onChange?: NavigationSelect
|
|
20
22
|
onClose?: NavigationContextType['onClose']
|
|
21
23
|
}
|
|
@@ -31,9 +33,11 @@ export function NavigationProvider(props: NavigationProviderProps) {
|
|
|
31
33
|
animationDuration = 0.275,
|
|
32
34
|
children,
|
|
33
35
|
onClose: onCloseUnstable,
|
|
36
|
+
selected,
|
|
37
|
+
setSelected,
|
|
34
38
|
} = props
|
|
35
39
|
|
|
36
|
-
const
|
|
40
|
+
const animating = useRef(false)
|
|
37
41
|
|
|
38
42
|
const select = useEventCallback((incomming: NavigationPath) => {
|
|
39
43
|
setSelected(incomming)
|
|
@@ -50,6 +54,7 @@ export function NavigationProvider(props: NavigationProviderProps) {
|
|
|
50
54
|
hideRootOnNavigate,
|
|
51
55
|
selected,
|
|
52
56
|
select,
|
|
57
|
+
animating,
|
|
53
58
|
items: items
|
|
54
59
|
.map((item, index) => (isElement(item) ? { id: item.key ?? index, component: item } : item))
|
|
55
60
|
.filter(nonNullable),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createContext, useContext } from 'react'
|
|
1
|
+
import { createContext, MutableRefObject, SetStateAction, useContext } from 'react'
|
|
2
2
|
|
|
3
3
|
export type NavigationId = string | number
|
|
4
4
|
export type NavigationPath = NavigationId[]
|
|
@@ -17,6 +17,7 @@ export type NavigationContextType = {
|
|
|
17
17
|
items: NavigationNode[]
|
|
18
18
|
hideRootOnNavigate: boolean
|
|
19
19
|
onClose: NavigationOnClose
|
|
20
|
+
animating: MutableRefObject<boolean>
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
type NavigationNodeBase = {
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
useIsomorphicLayoutEffect,
|
|
6
6
|
} from '@graphcommerce/framer-utils'
|
|
7
7
|
import { Box, styled, SxProps, Theme, useTheme, useThemeProps } from '@mui/material'
|
|
8
|
-
import { m, useDomEvent, useMotionValue, useTransform } from 'framer-motion'
|
|
8
|
+
import { m, MotionProps, useDomEvent, useMotionValue, useTransform } from 'framer-motion'
|
|
9
9
|
import React, { useCallback, useEffect, useRef } from 'react'
|
|
10
10
|
import { LayoutProvider } from '../../Layout/components/LayoutProvider'
|
|
11
11
|
import { ExtendableComponent, extendableComponent } from '../../Styles'
|
|
@@ -40,6 +40,7 @@ export type LayoutOverlayBaseProps = {
|
|
|
40
40
|
offsetPageY: number
|
|
41
41
|
isPresent: boolean
|
|
42
42
|
safeToRemove: (() => void) | null | undefined
|
|
43
|
+
overlayPaneProps?: MotionProps
|
|
43
44
|
} & StyleProps &
|
|
44
45
|
OverridableProps
|
|
45
46
|
|
|
@@ -86,6 +87,7 @@ export function OverlayBase(incommingProps: LayoutOverlayBaseProps) {
|
|
|
86
87
|
offsetPageY,
|
|
87
88
|
isPresent,
|
|
88
89
|
safeToRemove,
|
|
90
|
+
overlayPaneProps,
|
|
89
91
|
} = props
|
|
90
92
|
|
|
91
93
|
const th = useTheme()
|
|
@@ -367,6 +369,7 @@ export function OverlayBase(incommingProps: LayoutOverlayBaseProps) {
|
|
|
367
369
|
})}
|
|
368
370
|
>
|
|
369
371
|
<MotionDiv
|
|
372
|
+
{...overlayPaneProps}
|
|
370
373
|
layout
|
|
371
374
|
className={classes.overlayPane}
|
|
372
375
|
sx={(theme) => ({
|
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.
|
|
5
|
+
"version": "4.15.1",
|
|
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
|
+
"@graphcommerce/framer-scroller": "2.1.26",
|
|
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"
|
package/utils/cookie.ts
ADDED
|
@@ -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
|
+
}
|