@graphcommerce/next-ui 9.1.0-canary.54 → 9.1.0-canary.55

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/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Change Log
2
2
 
3
+ ## 9.1.0-canary.55
4
+
5
+ ### Patch Changes
6
+
7
+ - [#2539](https://github.com/graphcommerce-org/graphcommerce/pull/2539) [`87fc3c2`](https://github.com/graphcommerce-org/graphcommerce/commit/87fc3c28165c7c66b48882b0f044bbc9b63b9846) - Created new Tabs and TabItem component to be used for MultiCart setup ([@paales](https://github.com/paales))
8
+
9
+ - [#2539](https://github.com/graphcommerce-org/graphcommerce/pull/2539) [`286a20e`](https://github.com/graphcommerce-org/graphcommerce/commit/286a20e01f2a565f058415fa1c8dfbb2eeb3163b) - Added an OverlayCloseButton and implemented it for various locations. ([@paales](https://github.com/paales))
10
+
11
+ - [#2539](https://github.com/graphcommerce-org/graphcommerce/pull/2539) [`65dcefb`](https://github.com/graphcommerce-org/graphcommerce/commit/65dcefb8740166fd5df662e0e895c65d70273393) - Solve hydration error because multiple literals could be in a DateTimeFormat ([@paales](https://github.com/paales))
12
+
13
+ - [#2539](https://github.com/graphcommerce-org/graphcommerce/pull/2539) [`88fd114`](https://github.com/graphcommerce-org/graphcommerce/commit/88fd11485f9368e79d277fa45942e58214f794a6) - Created a LayoutOverlayHeader2 that does not support any floating modes or something and thus is simpler to customize. ([@paales](https://github.com/paales))
14
+
15
+ - [#2539](https://github.com/graphcommerce-org/graphcommerce/pull/2539) [`23793aa`](https://github.com/graphcommerce-org/graphcommerce/commit/23793aab26455b1bea0d1b3b37c96a228b656bc4) - Prevent excessive rerender when multiple images with the same url are in a product ([@paales](https://github.com/paales))
16
+
17
+ - [#2539](https://github.com/graphcommerce-org/graphcommerce/pull/2539) [`a419257`](https://github.com/graphcommerce-org/graphcommerce/commit/a4192571eb2332630ba3d103f61ff69dac8b2e5c) - Solve issue where the sidebar wasn't 100% width on the PDP on mobile ([@paales](https://github.com/paales))
18
+
3
19
  ## 9.1.0-canary.54
4
20
 
5
21
  ## 9.1.0-canary.53
@@ -242,7 +242,8 @@ export function SidebarGallery(props: SidebarGalleryProps) {
242
242
  >
243
243
  {images.map((image, idx) => (
244
244
  <MotionImageAspect
245
- key={typeof image.src === 'string' ? image.src : idx}
245
+ // eslint-disable-next-line react/no-array-index-key
246
+ key={idx}
246
247
  layout
247
248
  layoutDependency={zoomed}
248
249
  src={image.src}
@@ -362,7 +363,7 @@ export function SidebarGallery(props: SidebarGalleryProps) {
362
363
  className={classes.sidebarWrapper}
363
364
  sx={[
364
365
  {
365
- width: sidebarSize,
366
+ width: { xs: '100%', md: sidebarSize },
366
367
  gridArea: 'right',
367
368
  boxSizing: 'content-box',
368
369
  display: 'grid',
@@ -19,8 +19,9 @@ export function DateTimeFormat(props: DateTimeFormatProps) {
19
19
  return (
20
20
  <Box component='span' className='DateTimeFormat' suppressHydrationWarning sx={sx}>
21
21
  {dateValue &&
22
- formatter.formatToParts(dateValue).map((part) => (
23
- <span className={part.type} key={part.type} suppressHydrationWarning>
22
+ formatter.formatToParts(dateValue).map((part, index) => (
23
+ // eslint-disable-next-line react/no-array-index-key
24
+ <span className={part.type} key={`${part.type}-${index}`} suppressHydrationWarning>
24
25
  {part.value}
25
26
  </span>
26
27
  ))}
@@ -1,13 +1,16 @@
1
1
  import { useGo, usePageContext } from '@graphcommerce/framer-next-pages'
2
2
  import { i18n } from '@lingui/core'
3
- import { Fab } from '@mui/material'
3
+ import { Fab, type ButtonProps } from '@mui/material'
4
4
  import { useState } from 'react'
5
+ import { Button } from '../../Button'
6
+ import type { FabProps } from '../../Fab'
5
7
  import { iconClose } from '../../icons'
6
8
  import { IconSvg, useIconSvgSize } from '../../IconSvg'
7
9
  import { useFabSize } from '../../Theme'
8
10
 
9
11
  export type LayoutHeaderCloseProps = {
10
12
  onClose?: () => void
13
+ size?: FabProps['size']
11
14
  }
12
15
 
13
16
  export function useShowClose() {
@@ -16,29 +19,29 @@ export function useShowClose() {
16
19
  }
17
20
 
18
21
  export function LayoutHeaderClose(props: LayoutHeaderCloseProps) {
19
- const { onClose } = props
22
+ const { onClose, size = 'responsive' } = props
20
23
  const { closeSteps } = usePageContext()
21
24
  const [disabled, setDisabled] = useState(false)
22
25
  const go = useGo(closeSteps * -1)
23
26
  const onClick = () => {
24
27
  setDisabled(true)
25
-
26
28
  return onClose ? onClose() : go()
27
29
  }
28
30
 
29
- const fabSize = useFabSize('responsive')
30
- const svgSize = useIconSvgSize('large')
31
+ const fabSize = useFabSize(size)
32
+ const svgSize = useIconSvgSize(size === 'large' ? 'large' : 'medium')
31
33
 
32
34
  return (
33
35
  <Fab
34
36
  onClick={onClick}
37
+ className='LayoutHeaderClose-root'
35
38
  sx={{
36
39
  boxShadow: 'none',
37
40
  marginLeft: `calc((${fabSize} - ${svgSize}) * -0.5)`,
38
41
  marginRight: `calc((${fabSize} - ${svgSize}) * -0.5)`,
39
42
  background: 'none',
40
43
  }}
41
- size='responsive'
44
+ size={size}
42
45
  disabled={disabled}
43
46
  aria-label={i18n._(/* i18n */ 'Close')}
44
47
  >
@@ -0,0 +1,213 @@
1
+ import type { SxProps, Theme } from '@mui/material'
2
+ import { Box } from '@mui/material'
3
+ import React, { useRef } from 'react'
4
+ import type { BackProps } from '../../Layout/components/LayoutHeaderBack'
5
+ import { LayoutHeaderBack, useShowBack } from '../../Layout/components/LayoutHeaderBack'
6
+ import { LayoutHeaderClose, useShowClose } from '../../Layout/components/LayoutHeaderClose'
7
+ import { extendableComponent } from '../../Styles'
8
+ import { sxx } from '../../utils/sxx'
9
+
10
+ export type LayoutOverlayHeader2Props = Pick<BackProps, 'disableBackNavigation'> & {
11
+ /** Main content to display in the center */
12
+ children: React.ReactNode
13
+
14
+ disableChildrenPadding?: boolean
15
+
16
+ /** Button to display on the left side of the title */
17
+ primary?: React.ReactNode
18
+
19
+ /** Button to display on the right side of the title */
20
+ secondary?: React.ReactNode
21
+
22
+ /** Hide the back button */
23
+ hideBackButton?: boolean
24
+
25
+ /** Justify alignment for center content */
26
+ justify?: 'start' | 'center' | 'end'
27
+
28
+ /** Custom styles for the root element */
29
+ sx?: SxProps<Theme>
30
+
31
+ closeLocation?: 'left' | 'right'
32
+
33
+ size?: 'small' | 'responsive'
34
+ }
35
+
36
+ const name = 'LayoutOverlayHeader2'
37
+ const parts = ['root', 'bg', 'content', 'left', 'center', 'right'] as const
38
+
39
+ type State = {
40
+ left: boolean
41
+ right: boolean
42
+ childrenPadding: boolean
43
+ size: 'small' | 'responsive'
44
+ }
45
+
46
+ const { withState } = extendableComponent<State, typeof name, typeof parts>(name, parts)
47
+
48
+ export const LayoutOverlayHeader2 = React.memo<LayoutOverlayHeader2Props>((props) => {
49
+ const ref = useRef<HTMLDivElement>(null)
50
+
51
+ const {
52
+ children,
53
+ hideBackButton = false,
54
+ primary,
55
+ secondary,
56
+ justify = 'center',
57
+ closeLocation = 'right',
58
+ sx = [],
59
+ disableBackNavigation,
60
+ disableChildrenPadding = false,
61
+ size = 'responsive',
62
+ } = props
63
+
64
+ const showBack = useShowBack() && !hideBackButton
65
+ const showClose = useShowClose()
66
+
67
+ const close = showClose && <LayoutHeaderClose size={size === 'small' ? 'small' : 'responsive'} />
68
+ const back = showBack && <LayoutHeaderBack disableBackNavigation={disableBackNavigation} />
69
+
70
+ let left = secondary
71
+ let right = primary
72
+
73
+ if (back && !secondary) left = back
74
+
75
+ // Handle close button positioning based on closeLocation
76
+ if (close && closeLocation === 'left' && !left) left = close
77
+ if (close && closeLocation === 'right' && !right) right = close
78
+ if (close && closeLocation === 'right' && !left && right) left = close
79
+ if (!left && !right && !children) return null
80
+
81
+ const classes = withState({
82
+ left: !!left,
83
+ right: !!right,
84
+ childrenPadding: !disableChildrenPadding,
85
+ size,
86
+ })
87
+
88
+ return (
89
+ <Box
90
+ className={classes.root}
91
+ sx={sxx(
92
+ (theme) => ({
93
+ zIndex: children ? theme.zIndex.appBar : theme.zIndex.appBar - 2,
94
+ position: 'sticky',
95
+ left: 0,
96
+ right: 0,
97
+ top: 0,
98
+ marginTop: 0,
99
+
100
+ // Special positioning for bottom overlays
101
+ [theme.breakpoints.down('md')]: {
102
+ height: theme.appShell.headerHeightSm,
103
+ '.variantSmBottom.sizeSmFull &, .variantSmBottom.sizeSmMinimal &': {
104
+ top: `calc(${theme.appShell.headerHeightSm} * 0.5 * -1)`,
105
+ },
106
+ },
107
+ [theme.breakpoints.up('md')]: {
108
+ height: theme.appShell.appBarHeightMd,
109
+ '.variantMdBottom.sizeMdFull &, .variantMdBottom.sizeMdMinimal &': {
110
+ top: `calc(${theme.appShell.appBarHeightMd} * 0.5 * -1)`,
111
+ },
112
+
113
+ '&.sizeSmall': {
114
+ height: theme.appShell.headerHeightSm,
115
+ },
116
+ },
117
+ }),
118
+ sx,
119
+ )}
120
+ >
121
+ <Box
122
+ className={classes.content}
123
+ ref={ref}
124
+ sx={sxx((theme) => ({
125
+ position: 'absolute',
126
+ inset: 0,
127
+ width: '100%',
128
+ display: 'grid',
129
+ alignItems: 'center',
130
+ gap: theme.spacings.xs,
131
+ height: theme.appShell.headerHeightSm,
132
+ [theme.breakpoints.up('md')]: {
133
+ height: theme.appShell.appBarHeightMd,
134
+ '&.sizeSmall': {
135
+ height: theme.appShell.headerHeightSm,
136
+ },
137
+ },
138
+
139
+ // Default: center only
140
+ gridTemplateAreas: '"center"',
141
+ gridTemplateColumns: '1fr',
142
+
143
+ // Left only
144
+ '&.left:not(.right)': {
145
+ gridTemplateAreas: '"left center"',
146
+ gridTemplateColumns: justify === 'center' ? '1fr max-content' : 'auto 1fr',
147
+ },
148
+
149
+ // Right only
150
+ '&.right:not(.left)': {
151
+ gridTemplateAreas: '"center right"',
152
+ gridTemplateColumns: justify === 'center' ? 'max-content 1fr' : '1fr auto',
153
+ },
154
+
155
+ // Both left and right
156
+ '&.left.right': {
157
+ gridTemplateAreas: '"left center right"',
158
+ gridTemplateColumns: justify === 'center' ? '1fr max-content 1fr' : 'auto 1fr auto',
159
+ },
160
+ }))}
161
+ >
162
+ {left && (
163
+ <Box
164
+ className={classes.left}
165
+ sx={(theme) => ({
166
+ pl: theme.spacings.sm,
167
+ display: 'grid',
168
+ gridAutoFlow: 'column',
169
+ gap: theme.spacings.sm,
170
+ gridArea: 'left',
171
+ justifyContent: 'start',
172
+ })}
173
+ >
174
+ {left}
175
+ </Box>
176
+ )}
177
+
178
+ <Box
179
+ className={classes.center}
180
+ sx={(theme) => ({
181
+ display: 'grid',
182
+ gridAutoFlow: 'column',
183
+ gap: theme.spacings.sm,
184
+ gridArea: 'center',
185
+ overflow: 'hidden',
186
+ height: '100%',
187
+ '&.childrenPadding:not(.right)': { pr: theme.spacings.sm },
188
+ '&.childrenPadding:not(.left)': { pl: theme.spacings.sm },
189
+ })}
190
+ >
191
+ {children}
192
+ </Box>
193
+
194
+ {right && (
195
+ <Box
196
+ className={classes.right}
197
+ sx={(theme) => ({
198
+ '& > *': { width: 'min-content' },
199
+ display: 'grid',
200
+ gridAutoFlow: 'column',
201
+ gap: theme.spacings.sm,
202
+ gridArea: 'right',
203
+ justifyContent: 'end',
204
+ pr: theme.spacings.sm,
205
+ })}
206
+ >
207
+ {right}
208
+ </Box>
209
+ )}
210
+ </Box>
211
+ </Box>
212
+ )
213
+ })
@@ -1,2 +1,3 @@
1
1
  export * from './components/LayoutOverlay'
2
2
  export * from './components/LayoutOverlayHeader'
3
+ export * from './components/LayoutOverlayHeader2'
@@ -0,0 +1,25 @@
1
+ import { useGo, usePageContext } from '@graphcommerce/framer-next-pages'
2
+ import { i18n } from '@lingui/core'
3
+ import { useState } from 'react'
4
+ import { Button, type ButtonProps } from '../../Button'
5
+
6
+ export function OverlayCloseButton(props: ButtonProps) {
7
+ const { disabled, ...buttonProps } = props
8
+ const { closeSteps } = usePageContext()
9
+ const [isDisabled, setIsDisabled] = useState(disabled)
10
+ const go = useGo(closeSteps * -1)
11
+
12
+ const onClick = () => {
13
+ setIsDisabled(true)
14
+ go()
15
+ }
16
+
17
+ return (
18
+ <Button
19
+ onClick={onClick}
20
+ disabled={isDisabled}
21
+ aria-label={i18n._(/* i18n */ 'Close overlay')}
22
+ {...buttonProps}
23
+ />
24
+ )
25
+ }
@@ -0,0 +1,26 @@
1
+ import { LayoutTitle, type TitleProps } from '../../Layout'
2
+ import { LayoutHeaderClose } from '../../Layout/components/LayoutHeaderClose'
3
+ import { LayoutOverlayHeader2, type LayoutOverlayHeader2Props } from '../../LayoutOverlay'
4
+
5
+ export type OverlayHeader2Props = Omit<LayoutOverlayHeader2Props, 'hideBackButton'> & {
6
+ onClose: () => void
7
+ icon?: TitleProps['icon']
8
+ }
9
+
10
+ export function OverlayHeader2(props: OverlayHeader2Props) {
11
+ const { children, onClose, primary, secondary, icon, ...rest } = props
12
+
13
+ return (
14
+ <LayoutOverlayHeader2
15
+ hideBackButton
16
+ size='small'
17
+ primary={primary ?? <LayoutHeaderClose onClose={onClose} size='small' />}
18
+ secondary={primary ? <LayoutHeaderClose onClose={onClose} size='small' /> : secondary}
19
+ {...rest}
20
+ >
21
+ <LayoutTitle size='small' component='span' icon={icon}>
22
+ {children}
23
+ </LayoutTitle>
24
+ </LayoutOverlayHeader2>
25
+ )
26
+ }
@@ -1,4 +1,6 @@
1
- export * from './OverlayBase'
2
1
  export * from './Overlay'
3
- export * from './OverlayStickyBottom'
2
+ export * from './OverlayBase'
3
+ export * from './OverlayCloseButton'
4
4
  export * from './OverlayHeader'
5
+ export * from './OverlayHeader2'
6
+ export * from './OverlayStickyBottom'
@@ -0,0 +1,134 @@
1
+ import { Box, ButtonBase, type SxProps, type Theme } from '@mui/material'
2
+ import { alpha, useTheme } from '@mui/material/styles'
3
+ import { forwardRef } from 'react'
4
+ import { extendableComponent } from '../Styles/extendableComponent'
5
+ import { responsiveVal } from '../Styles/responsiveVal'
6
+ import { sxx } from '../utils/sxx'
7
+
8
+ export type TabItemVariant = 'chrome'
9
+
10
+ export type TabItemProps = {
11
+ /** Whether this tab is selected */
12
+ selected: boolean
13
+ /** Content to display inside the tab */
14
+ children: React.ReactNode
15
+ /** Click handler */
16
+ onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void
17
+
18
+ /** Spacing for the tab */
19
+ spacing?: string
20
+ /** Custom styling */
21
+ sx?: SxProps<Theme>
22
+ /** Whether to disable ripple effect */
23
+ disableRipple?: boolean
24
+ /** Additional className */
25
+ className?: string
26
+
27
+ color: (theme: Theme) => string
28
+ variant?: 'chrome'
29
+ }
30
+
31
+ type State = {
32
+ selected: boolean
33
+ variant: 'chrome'
34
+ }
35
+
36
+ const name = 'TabItem'
37
+ const parts = ['root', 'content'] as const
38
+ const { withState } = extendableComponent<State, typeof name, typeof parts>(name, parts)
39
+
40
+ export const TabItem = forwardRef<HTMLButtonElement, TabItemProps>((props, ref) => {
41
+ const {
42
+ selected,
43
+ children,
44
+ onClick,
45
+ spacing = responsiveVal(4, 20),
46
+ sx,
47
+ disableRipple = true,
48
+ className,
49
+ color = (theme) => theme.palette.background.default,
50
+ variant = 'chrome',
51
+ ...other
52
+ } = props
53
+
54
+ const classes = withState({ selected, variant })
55
+
56
+ return (
57
+ <Box
58
+ ref={ref}
59
+ component={ButtonBase}
60
+ onClick={selected ? undefined : onClick}
61
+ className={`${classes.root} ${className ?? ''}`}
62
+ disableRipple={selected || disableRipple}
63
+ role='tab'
64
+ aria-selected={selected}
65
+ // tabindex={0}
66
+ sx={sxx(
67
+ (theme) => ({
68
+ position: 'relative',
69
+ textDecoration: 'none',
70
+ color: 'text.primary',
71
+ typography: 'subtitle1',
72
+ mt: spacing,
73
+ height: `calc(100% - ${spacing})`,
74
+ pb: spacing,
75
+ mx: `calc(${spacing} / 2)`,
76
+ transition: 'background-color 0.2s ease-in-out',
77
+
78
+ '&:hover:not(.selected) .TabItem-content': {
79
+ bgcolor: alpha(color(theme), 0.5),
80
+ },
81
+ '&:focus:not(.selected) .TabItem-content': {
82
+ bgcolor: alpha(color(theme), 0.5),
83
+ },
84
+ '&::before': {
85
+ opacity: 0,
86
+ transition: 'opacity 0.2s ease-in-out',
87
+ content: '""',
88
+ position: 'absolute',
89
+ bottom: 0,
90
+ left: '-10px',
91
+ width: '10px',
92
+ height: '10px',
93
+ background: `radial-gradient(circle at top left, transparent 10px, ${color(theme)} 10px)`,
94
+ },
95
+ '&::after': {
96
+ opacity: 0,
97
+ transition: 'opacity 0.2s ease-in-out',
98
+ content: '""',
99
+ position: 'absolute',
100
+ bottom: 0,
101
+ right: '-10px',
102
+ width: '10px',
103
+ height: '10px',
104
+ background: `radial-gradient(circle at top right, transparent 10px, ${color(theme)} 10px)`,
105
+ },
106
+ borderStartStartRadius: '0.5em',
107
+ borderStartEndRadius: '0.5em',
108
+ '&.selected': {
109
+ bgcolor: color(theme),
110
+ '&::before': { opacity: 1 },
111
+ '&::after': { opacity: 1 },
112
+ },
113
+ }),
114
+ sx,
115
+ )}
116
+ {...other}
117
+ >
118
+ <Box
119
+ className={classes.content}
120
+ sx={{
121
+ display: 'flex',
122
+ alignItems: 'center',
123
+ height: '100%',
124
+ px: spacing,
125
+ gap: `calc(${spacing} / 2)`,
126
+ borderRadius: '0.5em',
127
+ transition: 'background-color 0.2s ease-in-out',
128
+ }}
129
+ >
130
+ {children}
131
+ </Box>
132
+ </Box>
133
+ )
134
+ })
package/Tabs/Tabs.tsx ADDED
@@ -0,0 +1,124 @@
1
+ import { Scroller, ScrollerButton, ScrollerProvider } from '@graphcommerce/framer-scroller'
2
+ import { Box, type SxProps, type Theme } from '@mui/material'
3
+ import React from 'react'
4
+ import { iconChevronLeft, iconChevronRight } from '../icons'
5
+ import { IconSvg } from '../IconSvg'
6
+ import { extendableComponent } from '../Styles/extendableComponent'
7
+ import { sxx } from '../utils/sxx'
8
+
9
+ export type TabsProps = {
10
+ children: React.ReactNode
11
+ sx?: SxProps<Theme>
12
+ /** Additional content to show before tabs */
13
+ startContent?: React.ReactNode
14
+ /** Additional content to show after tabs */
15
+ endContent?: React.ReactNode
16
+ /** Whether to show separators between tabs */
17
+ showSeparators?: boolean
18
+ /** Custom separator component */
19
+ separator?: React.ReactNode
20
+ }
21
+
22
+ const { classes } = extendableComponent('Tabs', ['root', 'scroller', 'left', 'right', 'button'])
23
+
24
+ export function Tabs(props: TabsProps) {
25
+ const { children, sx, startContent, endContent, showSeparators = true, separator } = props
26
+
27
+ const defaultSeparator = (
28
+ <Box
29
+ className='separator'
30
+ sx={{ width: '2px', height: '1em', bgcolor: 'divider', mx: '-1px' }}
31
+ />
32
+ )
33
+
34
+ const separatorElement = separator ?? defaultSeparator
35
+
36
+ return (
37
+ <Box
38
+ className={classes.root}
39
+ sx={sxx(
40
+ {
41
+ alignItems: 'stretch',
42
+ position: 'relative',
43
+ pointerEvents: 'all',
44
+ gridTemplateColumns: 'auto 1fr auto',
45
+ display: 'grid',
46
+ gridAutoFlow: 'column',
47
+ },
48
+ sx,
49
+ )}
50
+ >
51
+ <ScrollerProvider scrollSnapAlign='none'>
52
+ <Scroller
53
+ hideScrollbar
54
+ sx={{
55
+ gridArea: '1 / 1 / 1 / 4',
56
+ gridAutoColumns: 'min-content',
57
+ alignItems: 'center',
58
+ // Hide the Separator that comes before or after the selected tab.
59
+ '& .separator': { transition: 'opacity 0.2s ease-in-out' },
60
+ '& .TabItem-root.selected + .separator': { opacity: 0 },
61
+ '& .separator:has(+ .TabItem-root.selected)': { opacity: 0 },
62
+ // Same for hover:
63
+ '& .TabItem-root:hover + .separator': { opacity: 0 },
64
+ '& .separator:has(+ .TabItem-root:hover)': { opacity: 0 },
65
+ }}
66
+ className={classes.scroller}
67
+ role='tablist'
68
+ >
69
+ {startContent}
70
+
71
+ {showSeparators && startContent && separatorElement}
72
+
73
+ {React.Children.map(children, (child, index) => (
74
+ // eslint-disable-next-line react/no-array-index-key
75
+ <React.Fragment key={index}>
76
+ {showSeparators && index > 0 && separatorElement}
77
+ {child}
78
+ </React.Fragment>
79
+ ))}
80
+
81
+ {showSeparators && endContent && separatorElement}
82
+
83
+ {endContent}
84
+ </Scroller>
85
+
86
+ <ScrollerButton
87
+ sxContainer={{
88
+ gridArea: '1 / 1 / 1 / 2',
89
+ display: 'flex',
90
+ alignItems: 'center',
91
+ justifyContent: 'center',
92
+ pointerEvents: 'none',
93
+ '& > *': { pointerEvents: 'all' },
94
+ }}
95
+ sx={{ pointerEvents: 'all' }}
96
+ direction='left'
97
+ size='small'
98
+ tabIndex={-1}
99
+ className={`${classes.left} ${classes.button}`}
100
+ >
101
+ <IconSvg src={iconChevronLeft} />
102
+ </ScrollerButton>
103
+
104
+ <ScrollerButton
105
+ sxContainer={{
106
+ gridArea: '1 / 3 / 1 / 4',
107
+ display: 'flex',
108
+ alignItems: 'center',
109
+ justifyContent: 'center',
110
+ pointerEvents: 'none',
111
+ '& > *': { pointerEvents: 'all' },
112
+ }}
113
+ sx={{ pointerEvents: 'all' }}
114
+ direction='right'
115
+ size='small'
116
+ tabIndex={-1}
117
+ className={`${classes.right} ${classes.button}`}
118
+ >
119
+ <IconSvg src={iconChevronRight} />
120
+ </ScrollerButton>
121
+ </ScrollerProvider>
122
+ </Box>
123
+ )
124
+ }
package/index.ts CHANGED
@@ -59,6 +59,8 @@ export * from './Stepper/Stepper'
59
59
  export * from './Styles'
60
60
  export * from './TextInputNumber/TextInputNumber'
61
61
  export * from './Theme'
62
+ export * from './Tabs/TabItem'
63
+ export * from './Tabs/Tabs'
62
64
  export * from './TimeAgo/TimeAgo'
63
65
  export * from './ToggleButton/ToggleButton'
64
66
  export * from './ToggleButtonGroup/ToggleButtonGroup'
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": "9.1.0-canary.54",
5
+ "version": "9.1.0-canary.55",
6
6
  "sideEffects": false,
7
7
  "prettier": "@graphcommerce/prettier-config-pwa",
8
8
  "eslintConfig": {
@@ -24,13 +24,13 @@
24
24
  "@emotion/react": "^11",
25
25
  "@emotion/server": "^11",
26
26
  "@emotion/styled": "^11",
27
- "@graphcommerce/eslint-config-pwa": "^9.1.0-canary.54",
28
- "@graphcommerce/framer-next-pages": "^9.1.0-canary.54",
29
- "@graphcommerce/framer-scroller": "^9.1.0-canary.54",
30
- "@graphcommerce/framer-utils": "^9.1.0-canary.54",
31
- "@graphcommerce/image": "^9.1.0-canary.54",
32
- "@graphcommerce/prettier-config-pwa": "^9.1.0-canary.54",
33
- "@graphcommerce/typescript-config-pwa": "^9.1.0-canary.54",
27
+ "@graphcommerce/eslint-config-pwa": "^9.1.0-canary.55",
28
+ "@graphcommerce/framer-next-pages": "^9.1.0-canary.55",
29
+ "@graphcommerce/framer-scroller": "^9.1.0-canary.55",
30
+ "@graphcommerce/framer-utils": "^9.1.0-canary.55",
31
+ "@graphcommerce/image": "^9.1.0-canary.55",
32
+ "@graphcommerce/prettier-config-pwa": "^9.1.0-canary.55",
33
+ "@graphcommerce/typescript-config-pwa": "^9.1.0-canary.55",
34
34
  "@lingui/core": "^4.2.1",
35
35
  "@lingui/macro": "^4.2.1",
36
36
  "@lingui/react": "^4.2.1",