@graphcommerce/next-ui 3.25.3 → 4.0.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.
Files changed (138) hide show
  1. package/AnimatedRow/index.tsx +6 -3
  2. package/ApolloError/ApolloErrorAlert.tsx +20 -21
  3. package/ApolloError/ApolloErrorFullPage.tsx +3 -4
  4. package/ApolloError/ApolloErrorSnackbar.tsx +3 -3
  5. package/Blog/BlogAuthor/index.tsx +42 -48
  6. package/Blog/BlogContent/index.tsx +6 -23
  7. package/Blog/BlogHeader/index.tsx +26 -23
  8. package/Blog/BlogList/index.tsx +7 -25
  9. package/Blog/BlogListItem/index.tsx +63 -52
  10. package/Blog/BlogTags/BlogTag.tsx +27 -0
  11. package/Blog/BlogTags/index.tsx +7 -32
  12. package/Blog/BlogTitle/index.tsx +7 -21
  13. package/Button/Button.tsx +5 -0
  14. package/Button/LinkOrButton.tsx +80 -0
  15. package/Button/index.tsx +2 -177
  16. package/CHANGELOG.md +490 -651
  17. package/ChipMenu/index.tsx +71 -77
  18. package/ContainerWithHeader/index.tsx +33 -30
  19. package/FlagAvatar/index.tsx +2 -12
  20. package/Footer/Footer.tsx +105 -88
  21. package/Footer/index.ts +0 -1
  22. package/Form/Form.tsx +35 -0
  23. package/Form/FormActions.tsx +9 -14
  24. package/Form/FormDivider.tsx +8 -13
  25. package/Form/FormHeader.tsx +5 -26
  26. package/Form/FormRow.tsx +8 -13
  27. package/Form/InputCheckmark.tsx +9 -22
  28. package/FramerScroller/SidebarGallery.tsx +340 -0
  29. package/FramerScroller/SidebarSlider.tsx +107 -0
  30. package/FramerScroller/index.ts +2 -0
  31. package/FullPageMessage/index.tsx +52 -48
  32. package/Highlight/index.tsx +1 -1
  33. package/IconHeader/index.tsx +63 -58
  34. package/JsonLd/index.tsx +3 -2
  35. package/Layout/components/LayoutHeader.tsx +75 -82
  36. package/Layout/components/LayoutHeaderBack.tsx +21 -12
  37. package/Layout/components/LayoutHeaderClose.tsx +9 -22
  38. package/Layout/components/LayoutHeaderContent.tsx +190 -154
  39. package/Layout/components/LayoutHeadertypes.ts +1 -1
  40. package/Layout/components/LayoutTitle.tsx +60 -55
  41. package/LayoutDefault/components/LayoutDefault.tsx +96 -85
  42. package/LayoutOverlay/components/LayoutOverlay.tsx +2 -8
  43. package/LayoutOverlay/components/LayoutOverlayBase.tsx +233 -239
  44. package/LayoutOverlay/test/LayoutOverlayDemo.tsx +1 -1
  45. package/LayoutParts/DesktopHeaderBadge.tsx +28 -0
  46. package/LayoutParts/DesktopNavActions.tsx +15 -0
  47. package/LayoutParts/DesktopNavBar.tsx +113 -0
  48. package/LayoutParts/DesktopNavBarItem.tsx +44 -0
  49. package/{AppShell → LayoutParts}/GlobalHead.tsx +1 -2
  50. package/LayoutParts/Logo.tsx +77 -0
  51. package/LayoutParts/MenuFab.tsx +131 -0
  52. package/LayoutParts/MenuFabItem.tsx +25 -0
  53. package/LayoutParts/MenuFabSecondaryItem.tsx +37 -0
  54. package/{AppShell/PlaceholderFab/index.tsx → LayoutParts/PlaceholderFab.tsx} +1 -1
  55. package/LayoutParts/StickyBelowHeader.tsx +25 -0
  56. package/LayoutParts/index.ts +12 -0
  57. package/{AppShell → LayoutParts}/useFabAnimation.ts +0 -0
  58. package/Page/CssAndFramerMotionProvider.tsx +21 -0
  59. package/Page/index.ts +2 -0
  60. package/Page/types.ts +2 -8
  61. package/PageLoadIndicator/index.tsx +25 -30
  62. package/PageMeta/index.tsx +1 -1
  63. package/Pagination/index.tsx +37 -54
  64. package/RenderType/index.tsx +1 -1
  65. package/Row/ButtonLinkList/ButtonLinkList.tsx +35 -37
  66. package/Row/ButtonLinkList/ButtonLinkListItem.tsx +16 -33
  67. package/Row/ColumnOne/index.tsx +5 -10
  68. package/Row/ColumnOneBoxed/index.tsx +18 -19
  69. package/Row/ColumnOneCentered/index.tsx +3 -4
  70. package/Row/ColumnThree/index.tsx +62 -57
  71. package/Row/ColumnTwo/index.tsx +37 -32
  72. package/Row/ColumnTwoSpread/index.tsx +28 -37
  73. package/Row/ColumnTwoWithTop/index.tsx +37 -43
  74. package/Row/ContentLinks/index.tsx +24 -25
  75. package/Row/HeroBanner/index.tsx +98 -82
  76. package/Row/IconBlocks/IconBlock/index.tsx +45 -37
  77. package/Row/IconBlocks/index.tsx +29 -44
  78. package/Row/ImageText/index.tsx +71 -67
  79. package/Row/ImageTextBoxed/index.tsx +66 -65
  80. package/Row/ParagraphWithSidebarSlide/index.tsx +80 -76
  81. package/Row/Quote/index.tsx +3 -3
  82. package/Row/SpecialBanner/index.tsx +97 -97
  83. package/Row/index.tsx +4 -9
  84. package/SectionContainer/index.tsx +32 -31
  85. package/SectionHeader/index.tsx +41 -43
  86. package/Separator/index.tsx +19 -18
  87. package/Snackbar/MessageSnackbar.tsx +1 -2
  88. package/Snackbar/MessageSnackbarImpl.tsx +68 -115
  89. package/StarRatingField/index.tsx +24 -25
  90. package/Stepper/Stepper.tsx +34 -32
  91. package/Styles/EmotionProvider.tsx +14 -0
  92. package/Styles/breakpointVal.tsx +16 -10
  93. package/Styles/extendableComponent.ts +70 -0
  94. package/Styles/index.tsx +9 -2
  95. package/Styles/withEmotionCache.tsx +36 -0
  96. package/Styles/withTheme.tsx +15 -24
  97. package/SvgIcon/SvgIcon.tsx +60 -0
  98. package/TextInputNumber/index.tsx +49 -50
  99. package/Theme/MuiButton.ts +128 -0
  100. package/Theme/MuiSlider.ts +28 -0
  101. package/Theme/MuiSnackbar.ts +31 -0
  102. package/Theme/{types.ts → createTheme.ts} +8 -2
  103. package/Theme/index.ts +4 -0
  104. package/Theme/themeDefaults.ts +51 -0
  105. package/TimeAgo/index.tsx +1 -1
  106. package/ToggleButton/index.tsx +43 -49
  107. package/ToggleButtonGroup/index.tsx +39 -39
  108. package/UspList/UspListItem.tsx +56 -46
  109. package/UspList/index.tsx +29 -26
  110. package/docs/building-components.mdx +3 -0
  111. package/docs/components/ComponentBasic.tsx +26 -0
  112. package/docs/components/ComponentChild.tsx +48 -0
  113. package/docs/components/ComponentChildVariant.tsx +54 -0
  114. package/docs/components/ComponentChildVariantExtendable.tsx +62 -0
  115. package/docs/components/ComponentStylable.tsx +32 -0
  116. package/docs/pages/building-components.tsx +62 -0
  117. package/index.ts +27 -81
  118. package/package.json +25 -27
  119. package/types.d.ts +1 -1
  120. package/AppShell/AppShellSticky/index.tsx +0 -38
  121. package/AppShell/DesktopNavActions.tsx +0 -32
  122. package/AppShell/DesktopNavBar.tsx +0 -158
  123. package/AppShell/Logo.tsx +0 -46
  124. package/AppShell/Menu.tsx +0 -7
  125. package/AppShell/MenuFab.tsx +0 -162
  126. package/AppShell/MenuFabSecondaryItem.tsx +0 -32
  127. package/AppShell/index.ts +0 -15
  128. package/AspectRatioContainer/index.tsx +0 -27
  129. package/Footer/SocialIcon.tsx +0 -23
  130. package/Form/index.tsx +0 -67
  131. package/FramerScroller/components/SidebarGallery.tsx +0 -317
  132. package/FramerScroller/components/SidebarSlider.tsx +0 -97
  133. package/Page/App.tsx +0 -17
  134. package/Page/Document.tsx +0 -24
  135. package/StyledBadge/index.tsx +0 -20
  136. package/Styles/classesPicker.ts +0 -41
  137. package/SvgImage/SvgImageSimple.tsx +0 -100
  138. package/SvgImage/index.tsx +0 -74
package/AppShell/Logo.tsx DELETED
@@ -1,46 +0,0 @@
1
- import { Image, ImageProps } from '@graphcommerce/image'
2
- import { makeStyles, Theme } from '@material-ui/core'
3
- import PageLink from 'next/link'
4
- import { useRouter } from 'next/router'
5
- import React from 'react'
6
- import { UseStyles } from '../Styles'
7
-
8
- const useStyles = makeStyles(
9
- (theme: Theme) => ({
10
- logo: {},
11
- parent: {
12
- height: '100%',
13
- width: 'max-content',
14
- display: 'flex',
15
- alignItems: 'center',
16
- margin: '0 auto',
17
- justifyContent: 'center',
18
- [theme.breakpoints.up('md')]: {
19
- display: 'flex',
20
- margin: 'unset',
21
- justifyContent: 'left',
22
- },
23
- },
24
- }),
25
- { name: 'Logo' },
26
- )
27
-
28
- export type LogoProps = { href?: `/${string}`; image: ImageProps } & UseStyles<typeof useStyles>
29
-
30
- export default function Logo(props: LogoProps) {
31
- const { href, image } = props
32
- const router = useRouter()
33
- const classes = useStyles(props)
34
-
35
- return router.asPath.split('?')[0] === '/' ? (
36
- <div className={classes.parent}>
37
- <Image layout='fixed' loading='eager' {...image} className={classes.logo} />
38
- </div>
39
- ) : (
40
- <PageLink href={href ?? '/'} passHref>
41
- <a className={classes.parent} aria-label='Logo'>
42
- <Image layout='fixed' loading='eager' {...image} className={classes.logo} />
43
- </a>
44
- </PageLink>
45
- )
46
- }
package/AppShell/Menu.tsx DELETED
@@ -1,7 +0,0 @@
1
- import { LinkProps } from 'next/link'
2
-
3
- type MenuItemProps = LinkProps & { children: React.ReactNode }
4
-
5
- export type MenuProps = {
6
- menu: MenuItemProps[]
7
- }
@@ -1,162 +0,0 @@
1
- import {
2
- Divider,
3
- Fab,
4
- List,
5
- ListItem,
6
- ListItemText,
7
- makeStyles,
8
- Menu,
9
- Theme,
10
- } from '@material-ui/core'
11
- import { m } from 'framer-motion'
12
- import PageLink from 'next/link'
13
- import { useRouter } from 'next/router'
14
- import React, { useEffect } from 'react'
15
- import { UseStyles } from '../Styles'
16
- import { responsiveVal } from '../Styles/responsiveVal'
17
- import SvgImageSimple from '../SvgImage/SvgImageSimple'
18
- import { iconMenu, iconClose } from '../icons'
19
- import { MenuProps } from './Menu'
20
- import { useFabAnimation } from './useFabAnimation'
21
-
22
- const useStyles = makeStyles(
23
- (theme: Theme) => ({
24
- menuWrapper: {
25
- left: theme.page.horizontal,
26
- [theme.breakpoints.down('sm')]: {
27
- transform: 'none !important',
28
- opacity: '1 !important',
29
- },
30
- },
31
- menuFab: {
32
- boxShadow: 'none',
33
- '&:hover, &:focus': {
34
- boxShadow: 'none',
35
- background: theme.palette.text.primary,
36
- },
37
- background: theme.palette.text.primary,
38
- width: responsiveVal(42, 56),
39
- height: responsiveVal(42, 56),
40
- pointerEvents: 'all',
41
- color: theme.palette.background.paper,
42
- },
43
- shadow: {
44
- pointerEvents: 'none',
45
- borderRadius: '99em',
46
- position: 'absolute',
47
- height: '100%',
48
- width: '100%',
49
- boxShadow: theme.shadows[6],
50
- top: 0,
51
- [theme.breakpoints.down('sm')]: {
52
- opacity: '1 !important',
53
- },
54
- },
55
- menu: {
56
- backgroundColor: theme.palette.background.paper,
57
- color: theme.palette.text.primary,
58
- minWidth: responsiveVal(200, 280),
59
- marginTop: `calc(${responsiveVal(42, 56)} + 3px)`,
60
- [theme.breakpoints.down('sm')]: {
61
- marginTop: `calc((${responsiveVal(42, 56)} + 12px) * -1)`,
62
- },
63
- },
64
- menuItemText: {
65
- ...theme.typography.h4,
66
- lineHeight: 1.1,
67
- },
68
- menuItem: {},
69
- }),
70
- { name: 'Menu' },
71
- )
72
-
73
- export type MenuFabProps = MenuProps &
74
- UseStyles<typeof useStyles> & {
75
- children?: React.ReactNode
76
- search?: React.ReactNode
77
- menuIcon?: React.ReactNode
78
- closeIcon?: React.ReactNode
79
- }
80
-
81
- export default function MenuFab(props: MenuFabProps) {
82
- const { menu, children, search, menuIcon, closeIcon } = props
83
- const classes = useStyles(props)
84
- const router = useRouter()
85
- const [openEl, setOpenEl] = React.useState<null | HTMLElement>(null)
86
-
87
- const { opacity, scale, shadowOpacity } = useFabAnimation()
88
-
89
- useEffect(() => {
90
- const clear = () => setOpenEl(null)
91
- router.events.on('routeChangeStart', clear)
92
- return () => router.events.off('routeChangeStart', clear)
93
- }, [router])
94
-
95
- return (
96
- <m.div className={classes.menuWrapper} style={{ scale, opacity }}>
97
- <Fab
98
- color='inherit'
99
- aria-label='Open Menu'
100
- size='medium'
101
- onClick={(event) => setOpenEl(event.currentTarget)}
102
- className={classes.menuFab}
103
- >
104
- {closeIcon ?? (
105
- <SvgImageSimple src={iconClose} inverted style={{ display: openEl ? 'block' : 'none' }} />
106
- )}
107
- {menuIcon ?? (
108
- <SvgImageSimple src={iconMenu} inverted style={{ display: openEl ? 'none' : 'block' }} />
109
- )}
110
- </Fab>
111
- <m.div className={classes.shadow} style={{ opacity: shadowOpacity }} />
112
-
113
- <Menu
114
- anchorEl={openEl}
115
- open={!!openEl}
116
- onClose={() => setOpenEl(null)}
117
- classes={{ paper: classes.menu }}
118
- disableScrollLock
119
- transitionDuration={{
120
- appear: 175,
121
- enter: 175,
122
- exit: 175,
123
- }}
124
- >
125
- {search && (
126
- <List>
127
- <ListItem dense>{search}</ListItem>
128
- </List>
129
- )}
130
- <List>
131
- <PageLink href='/' passHref>
132
- <ListItem
133
- button
134
- dense
135
- selected={router.asPath.split('?')[0] === '/'}
136
- classes={{ root: classes.menuItem }}
137
- >
138
- <ListItemText classes={{ primary: classes.menuItemText }}>Home</ListItemText>
139
- </ListItem>
140
- </PageLink>
141
-
142
- {menu.map(({ href, children: itemChildren, ...linkProps }) => (
143
- <PageLink key={href.toString()} href={href} {...linkProps} passHref>
144
- <ListItem
145
- button
146
- dense
147
- selected={router.asPath.startsWith(href.toString())}
148
- classes={{ root: classes.menuItem }}
149
- >
150
- <ListItemText classes={{ primary: classes.menuItemText }}>
151
- {itemChildren}
152
- </ListItemText>
153
- </ListItem>
154
- </PageLink>
155
- ))}
156
- </List>
157
- <Divider variant='middle' />
158
- <List component='div'>{children}</List>
159
- </Menu>
160
- </m.div>
161
- )
162
- }
@@ -1,32 +0,0 @@
1
- import { ListItem, ListItemIcon, ListItemText, makeStyles } from '@material-ui/core'
2
- import PageLink from 'next/link'
3
- import router from 'next/router'
4
- import React from 'react'
5
-
6
- const useStyles = makeStyles(
7
- {
8
- listItemText: {},
9
- icon: { minWidth: 30 },
10
- },
11
- { name: 'FabMenuSecondaryItem' },
12
- )
13
-
14
- export type FabMenuSecondaryItemProps = {
15
- href: string
16
- children: React.ReactNode
17
- icon: React.ReactNode
18
- }
19
-
20
- export function MenuFabSecondaryItem(props: FabMenuSecondaryItemProps) {
21
- const { href, children, icon } = props
22
- const classes = useStyles()
23
-
24
- return (
25
- <PageLink href={href} passHref>
26
- <ListItem component='a' dense button selected={router.asPath.startsWith(href)}>
27
- <ListItemIcon classes={{ root: classes.icon }}>{icon}</ListItemIcon>
28
- <ListItemText primary={children} classes={{ primary: classes.listItemText }} />
29
- </ListItem>
30
- </PageLink>
31
- )
32
- }
package/AppShell/index.ts DELETED
@@ -1,15 +0,0 @@
1
- export { default as AppShellSticky } from './AppShellSticky'
2
- export { default as DesktopNavActions } from './DesktopNavActions'
3
-
4
- export * from './DesktopNavBar'
5
- export { default as DesktopNavBar } from './DesktopNavBar'
6
-
7
- export { default as Logo } from './Logo'
8
- export * from './Logo'
9
- export * from './Menu'
10
- export * from './MenuFab'
11
- export { default as MenuFab } from './MenuFab'
12
- export * from './MenuFabSecondaryItem'
13
- export * from './PlaceholderFab'
14
- export * from './GlobalHead'
15
- export * from './useFabAnimation'
@@ -1,27 +0,0 @@
1
- import { makeStyles } from '@material-ui/core'
2
- import React from 'react'
3
- import { UseStyles } from '../Styles'
4
-
5
- const useStyles = makeStyles({
6
- root: ({ width, height }: Props) => ({
7
- position: `relative`,
8
- paddingTop: `calc(100% / ${width} * ${height})`,
9
- '& img, & video': {
10
- position: 'absolute',
11
- left: 0,
12
- top: 0,
13
- width: `100%`,
14
- height: `auto`,
15
- },
16
- }),
17
- })
18
-
19
- export type Props = { width: number; height: number }
20
- export type AspectRatioContainerProps = Props & UseStyles<typeof useStyles>
21
-
22
- const AspectRatioContainer: React.FC<AspectRatioContainerProps> = ({ children, ...styleProps }) => {
23
- const classes = useStyles(styleProps)
24
- return <div className={classes.root}>{children}</div>
25
- }
26
-
27
- export default AspectRatioContainer
@@ -1,23 +0,0 @@
1
- import { makeStyles, Theme } from '@material-ui/core'
2
- import React from 'react'
3
- import { UseStyles } from '../Styles'
4
- import SvgImage, { SvgImageProps } from '../SvgImage'
5
-
6
- const useStyles = makeStyles(
7
- (theme: Theme) => ({
8
- root: {
9
- filter: theme.palette.type === 'light' ? undefined : 'brightness(1) invert(1)',
10
- },
11
- }),
12
- {
13
- name: 'SocialIcon',
14
- },
15
- )
16
-
17
- type SocialIconProps = UseStyles<typeof useStyles> & SvgImageProps
18
-
19
- export function SocialIcon(props: SocialIconProps) {
20
- const classes = useStyles(props)
21
-
22
- return <SvgImage {...props} className={classes.root} />
23
- }
package/Form/index.tsx DELETED
@@ -1,67 +0,0 @@
1
- import { darken, lighten, makeStyles, Theme } from '@material-ui/core'
2
- import clsx from 'clsx'
3
- import React from 'react'
4
- import { UseStyles } from '../Styles'
5
- import { responsiveVal } from '../Styles/responsiveVal'
6
-
7
- const useStyles = makeStyles(
8
- (theme: Theme) => ({
9
- root: {
10
- display: 'grid',
11
- alignItems: 'center',
12
- padding: `${theme.spacings.xxs} 0`,
13
- },
14
- secondary: {
15
- background: theme.palette.secondary.light,
16
- },
17
- default: {
18
- background:
19
- theme.palette.type === 'light'
20
- ? darken(theme.palette.background.default, 0.03)
21
- : lighten(theme.palette.background.default, 0.1),
22
- },
23
- contained: {
24
- padding: `${theme.spacings.xxs} ${theme.spacings.sm}`,
25
- // paddingTop: theme.spacings.md,
26
- overflow: 'hidden',
27
- borderRadius: responsiveVal(theme.shape.borderRadius * 3, theme.shape.borderRadius * 4),
28
- },
29
- }),
30
- { name: 'Form' },
31
- )
32
-
33
- export type BaseFormProps = {
34
- contained?: boolean
35
- background?: 'secondary' | 'default'
36
- children: React.ReactNode
37
- } & UseStyles<typeof useStyles>
38
-
39
- export type FormFormProps = BaseFormProps & JSX.IntrinsicElements['form']
40
-
41
- export const Form = React.forwardRef<HTMLFormElement, FormFormProps>((props, ref) => {
42
- const classes = useStyles(props)
43
- const { contained, background, ...formProps } = props
44
-
45
- return (
46
- <form ref={ref} {...formProps} className={clsx(classes.root, contained && classes.contained)} />
47
- )
48
- })
49
-
50
- export type DivFormProps = BaseFormProps & JSX.IntrinsicElements['div']
51
-
52
- export const FormDiv = React.forwardRef<HTMLDivElement, DivFormProps>((props, ref) => {
53
- const classes = useStyles(props)
54
- const { contained, background = 'default', classes: _classes, ...formProps } = props
55
-
56
- return (
57
- <div
58
- ref={ref}
59
- {...formProps}
60
- className={clsx(
61
- classes.root,
62
- contained && classes.contained,
63
- contained && classes[background],
64
- )}
65
- />
66
- )
67
- })
@@ -1,317 +0,0 @@
1
- import { usePrevPageRouter } from '@graphcommerce/framer-next-pages/hooks/usePrevPageRouter'
2
- import {
3
- MotionImageAspect,
4
- MotionImageAspectProps,
5
- Scroller,
6
- ScrollerButton,
7
- ScrollerDots,
8
- ScrollerProvider,
9
- } from '@graphcommerce/framer-scroller'
10
- import { clientSize, useMotionValueValue } from '@graphcommerce/framer-utils'
11
- import { Fab, makeStyles, Theme, useTheme, alpha } from '@material-ui/core'
12
- import { m, useDomEvent, useMotionValue } from 'framer-motion'
13
- import { useRouter } from 'next/router'
14
- import React, { useEffect, useRef } from 'react'
15
- import Row from '../../Row'
16
- import { UseStyles } from '../../Styles'
17
- import { classesPicker } from '../../Styles/classesPicker'
18
- import { responsiveVal } from '../../Styles/responsiveVal'
19
- import SvgImageSimple from '../../SvgImage/SvgImageSimple'
20
- import { iconChevronLeft, iconChevronRight, iconFullscreen, iconFullscreenExit } from '../../icons'
21
-
22
- type StyleProps = {
23
- aspectRatio: [number, number]
24
- clientHeight: number
25
- classes?: Record<string, unknown>
26
- }
27
-
28
- const useStyles = makeStyles(
29
- (theme: Theme) => ({
30
- root: {
31
- willChange: 'transform',
32
- display: 'grid',
33
- [theme.breakpoints.up('md')]: {
34
- gridTemplateColumns: '1fr auto',
35
- },
36
- background:
37
- theme.palette.type === 'light'
38
- ? theme.palette.background.image
39
- : theme.palette.background.paper,
40
- paddingRight: `calc((100% - ${theme.breakpoints.values.lg}px) / 2)`,
41
- },
42
- rootZoomed: {
43
- position: 'relative',
44
- zIndex: theme.zIndex.modal,
45
- marginTop: `calc(${theme.appShell.headerHeightSm} * -1)`,
46
- [theme.breakpoints.up('md')]: {
47
- marginTop: `calc(${theme.appShell.headerHeightMd} * -1 - ${theme.spacings.lg})`,
48
- },
49
- paddingRight: 0,
50
- },
51
- scrollerContainer: ({ aspectRatio: [width, height] }: StyleProps) => {
52
- const headerHeight = `${theme.appShell.headerHeightSm} - ${theme.spacings.sm} * 2`
53
- const galleryMargin = theme.spacings.lg
54
- const extraSpacing = theme.spacings.md
55
-
56
- const maxHeight = `calc(100vh - ${headerHeight} - ${galleryMargin} - ${extraSpacing})`
57
- const ratio = `calc(${height} / ${width} * 100%)`
58
-
59
- return {
60
- willChange: 'transform',
61
- height: 0, // https://stackoverflow.com/questions/44770074/css-grid-row-height-safari-bug
62
- backgroundColor: theme.palette.background.image,
63
- position: 'relative',
64
- minHeight: '100%',
65
- paddingTop: `min(${ratio}, ${maxHeight})`,
66
- [theme.breakpoints.down('sm')]: {
67
- width: '100vw',
68
- },
69
- }
70
- },
71
- scrollerContainerZoomed: ({ clientHeight }: StyleProps) => ({
72
- paddingTop: clientHeight,
73
- }),
74
- scroller: {
75
- willChange: 'transform',
76
- position: 'absolute',
77
- top: 0,
78
- width: '100%',
79
- height: '100%',
80
- gridAutoColumns: `100%`,
81
- gridTemplateRows: `100%`,
82
- cursor: 'zoom-in',
83
- },
84
- scrollerZoomed: ({ clientHeight }: StyleProps) => ({
85
- height: clientHeight,
86
- cursor: 'inherit',
87
- }),
88
- sidebarWrapper: {
89
- boxSizing: 'content-box',
90
- display: 'grid',
91
- justifyItems: 'start',
92
- alignContent: 'center',
93
- position: 'relative',
94
- [theme.breakpoints.up('md')]: {
95
- width: `calc(${responsiveVal(300, 500, theme.breakpoints.values.lg)} + ${
96
- theme.page.horizontal
97
- } * 2)`,
98
- },
99
- },
100
- sidebarWrapperZoomed: {
101
- [theme.breakpoints.up('md')]: {
102
- marginLeft: `calc((${responsiveVal(300, 500, theme.breakpoints.values.lg)} + ${
103
- theme.page.horizontal
104
- } * 2) * -1)`,
105
- left: `calc(${responsiveVal(300, 500, theme.breakpoints.values.lg)} + ${
106
- theme.page.horizontal
107
- } * 2)`,
108
- },
109
- },
110
- sidebar: {
111
- boxSizing: 'border-box',
112
- width: '100%',
113
- padding: `${theme.spacings.lg} ${theme.page.horizontal}`,
114
- [theme.breakpoints.up('md')]: {
115
- paddingLeft: theme.spacings.lg,
116
- },
117
- },
118
- bottomCenter: {
119
- display: 'grid',
120
- gridAutoFlow: 'column',
121
- gap: theme.spacings.xxs,
122
- position: 'absolute',
123
- bottom: theme.spacings.xxs,
124
- justifyContent: 'center',
125
- width: '100%',
126
- pointerEvents: 'none',
127
- '& > *': {
128
- pointerEvents: 'all',
129
- },
130
- },
131
- sliderButtons: {
132
- [theme.breakpoints.down('sm')]: {
133
- display: 'none',
134
- },
135
- },
136
- toggleIcon: {
137
- boxShadow: theme.shadows[6],
138
- },
139
- topRight: {
140
- display: 'grid',
141
- gridAutoFlow: 'column',
142
- top: theme.spacings.sm,
143
- gap: theme.spacings.xxs,
144
- position: 'absolute',
145
- right: theme.spacings.sm,
146
- },
147
- centerLeft: {
148
- display: 'grid',
149
- gridAutoFlow: 'row',
150
- gap: theme.spacings.xxs,
151
- position: 'absolute',
152
- left: theme.spacings.sm,
153
- top: `calc(50% - 28px)`,
154
- },
155
- centerRight: {
156
- display: 'grid',
157
- gap: theme.spacings.xxs,
158
- position: 'absolute',
159
- right: theme.spacings.sm,
160
- top: `calc(50% - 28px)`,
161
- },
162
- dots: {
163
- background: alpha(theme.palette.background.paper, 1),
164
- boxShadow: theme.shadows[6],
165
- },
166
- }),
167
- { name: 'SidebarGallery' },
168
- )
169
-
170
- export type SidebarGalleryProps = {
171
- sidebar: React.ReactNode
172
- images: MotionImageAspectProps[]
173
- aspectRatio?: [number, number]
174
- routeHash?: string
175
- } & UseStyles<typeof useStyles>
176
-
177
- export default function SidebarGallery(props: SidebarGalleryProps) {
178
- const {
179
- sidebar,
180
- images,
181
- aspectRatio = [1, 1],
182
- routeHash = 'gallery',
183
- classes: classesBase,
184
- } = props
185
-
186
- const router = useRouter()
187
- const prevRoute = usePrevPageRouter()
188
- const clientHeight = useMotionValueValue(clientSize.y, (y) => y)
189
- const classes = useStyles({ clientHeight, aspectRatio, classes: classesBase })
190
-
191
- const route = `#${routeHash}`
192
- // We're using the URL to manage the state of the gallery.
193
- const zoomed = router.asPath.endsWith(route)
194
-
195
- // cleanup if someone enters the page with #gallery
196
- useEffect(() => {
197
- if (!prevRoute?.pathname && zoomed) {
198
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
199
- router.replace(router.asPath.replace(route, ''))
200
- }
201
- }, [prevRoute?.pathname, route, router, zoomed])
202
-
203
- const toggle = () => {
204
- if (!zoomed) {
205
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
206
- router.push(route, undefined, { shallow: true })
207
- document.body.style.overflow = 'hidden'
208
- window.scrollTo({ top: 0, behavior: 'smooth' })
209
- } else {
210
- router.back()
211
- }
212
- }
213
-
214
- const className = classesPicker(classes, { zoomed })
215
- const theme = useTheme()
216
- const windowRef = useRef(typeof window !== 'undefined' ? window : null)
217
-
218
- const handleEscapeKey = (e: KeyboardEvent | Event) => {
219
- if (zoomed && (e as KeyboardEvent)?.key === 'Escape') toggle()
220
- }
221
-
222
- const dragStart = useMotionValue<number>(0)
223
- const onMouseDownScroller: React.MouseEventHandler<HTMLDivElement> = (e) => {
224
- if (dragStart.get() === e.clientX) return
225
- dragStart.set(e.clientX)
226
- }
227
- const onMouseUpScroller: React.MouseEventHandler<HTMLDivElement> = (e) => {
228
- const currentDragLoc = e.clientX
229
- if (Math.abs(currentDragLoc - dragStart.get()) < 8) toggle()
230
- }
231
-
232
- useDomEvent(windowRef, 'keyup', handleEscapeKey, { passive: true })
233
-
234
- return (
235
- <ScrollerProvider scrollSnapAlign='center'>
236
- <Row maxWidth={false} disableGutters>
237
- <m.div layout {...className('root')}>
238
- <m.div
239
- layout
240
- {...className('scrollerContainer')}
241
- onLayoutAnimationComplete={() => {
242
- if (!zoomed) document.body.style.overflow = ''
243
- }}
244
- >
245
- <Scroller
246
- {...className('scroller')}
247
- hideScrollbar
248
- onMouseDown={onMouseDownScroller}
249
- onMouseUp={onMouseUpScroller}
250
- >
251
- {images.map((image, idx) => (
252
- <MotionImageAspect
253
- key={typeof image.src === 'string' ? image.src : idx}
254
- layout
255
- src={image.src}
256
- width={image.width}
257
- height={image.height}
258
- loading={idx === 0 ? 'eager' : 'lazy'}
259
- sizes={{
260
- 0: '100vw',
261
- [theme.breakpoints.values.md]: zoomed ? '100vw' : '60vw',
262
- }}
263
- alt={image.alt || `Product Image ${idx}` || undefined}
264
- dontReportWronglySizedImages
265
- />
266
- ))}
267
- </Scroller>
268
- <m.div layout className={classes.topRight}>
269
- <Fab
270
- size='small'
271
- className={classes.toggleIcon}
272
- onMouseUp={toggle}
273
- aria-label='Toggle Fullscreen'
274
- >
275
- {!zoomed ? (
276
- <SvgImageSimple src={iconFullscreen} />
277
- ) : (
278
- <SvgImageSimple src={iconFullscreenExit} />
279
- )}
280
- </Fab>
281
- </m.div>
282
- <div className={classes.centerLeft}>
283
- <ScrollerButton
284
- layout
285
- direction='left'
286
- size='small'
287
- className={classes.sliderButtons}
288
- >
289
- <SvgImageSimple src={iconChevronLeft} />
290
- </ScrollerButton>
291
- </div>
292
- <div className={classes.centerRight}>
293
- <ScrollerButton
294
- layout
295
- direction='right'
296
- size='small'
297
- className={classes.sliderButtons}
298
- >
299
- <SvgImageSimple src={iconChevronRight} />
300
- </ScrollerButton>
301
- </div>
302
-
303
- <div className={classes.bottomCenter}>
304
- <ScrollerDots layout classes={{ dots: classes.dots }} />
305
- </div>
306
- </m.div>
307
-
308
- <div {...className('sidebarWrapper')}>
309
- <m.div layout {...className('sidebar')}>
310
- {sidebar}
311
- </m.div>
312
- </div>
313
- </m.div>
314
- </Row>
315
- </ScrollerProvider>
316
- )
317
- }