-
Notifications
You must be signed in to change notification settings - Fork 72
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Sticky AddToCart Component on Product Page (GCOM-1194) #2036
Draft
carlocarels90
wants to merge
28
commits into
canary
Choose a base branch
from
feature/GCOM-1194
base: canary
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
28 commits
Select commit
Hold shift + click to select a range
386d146
chore(GCOM-1194): create a ProductPageWrapper component so we can mod…
carlocarels90 7c6d478
chore(GCOM-1194): create a plugin StickyAddProductsToCartButton for P…
carlocarels90 df6b9a3
chore(GCOM-1194): create a StickyAddToCart component
carlocarels90 2baa32a
refactor: rename props
carlocarels90 6a5be77
chore(GCOM-1194): Add 'forceScrolled' option to show the ‘scrolled' s…
carlocarels90 2bbeb6a
chore: add url to the ProductPageGallery fragment
carlocarels90 2a2b5f4
chore(GCOM-1194): Pass a reference from the AddProductToCartButton to…
carlocarels90 56ae512
chore: make the forceScrolled optional
carlocarels90 4439bd8
chore(GCOM-1194): create a enableStickyAddToCart config
carlocarels90 0c0292d
chore: add default value for the forceScrolled option
carlocarels90 32161a0
chore(GCOM-1194): create ConfigurableStickyAddToCart plugin for enhan…
carlocarels90 3a82c31
chore(GCOM-1194): move StickyAddToCart to the AddProductsToCartForm s…
carlocarels90 0d2c0db
chore(GCOM-1194): add styling for the stick add to cart + remove the …
carlocarels90 2f22eba
refactor: remove unused files
carlocarels90 58011ed
refactor: move changes to a plugin
carlocarels90 c490771
refactor: remove unused files
carlocarels90 e9cf329
refactor: undo changes for the product page.
carlocarels90 2ff9d4e
refactor: remove forceScrolled option
carlocarels90 c8b86a7
chore(GCOM-1194): calculate the cartButtonRef offset to fill the swit…
carlocarels90 5a2f1d7
chore(GCOM-1194): add StickyAddToCartDestination when the enableStick…
carlocarels90 4284880
chore(GCOM-1194): add transform animation
carlocarels90 1fbe69f
chore(GCOM-1194): inject missing StickyAddToCart data to the UseAddPr…
carlocarels90 623f911
refactor: remove unused plugin
carlocarels90 f84716c
chore(GCOM-1194): create plugin sends the cartButtonRef within the cr…
carlocarels90 15a5b96
chore: hide product name on mobile, since it’s duplicated
carlocarels90 fd5714a
refactor: replace useLayoutEffect for useEffect + move createPortal o…
carlocarels90 4c8001e
chore: add changeset
carlocarels90 e81ffd5
refactor: remove forwardedRef and duplicated hook
carlocarels90 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
--- | ||
'@graphcommerce/magento-product': minor | ||
'@graphcommerce/magento-product-configurable': patch | ||
'@graphcommerce/next-config': patch | ||
'@graphcommerce/next-ui': patch | ||
'@graphcommerce/docs': patch | ||
--- | ||
|
||
When the 'enableStickyAddToCart' option is enabled, a sticky bar will appear if the default add-to-cart button goes out of the visible viewport. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
18 changes: 18 additions & 0 deletions
18
...ento-product-configurable/plugins/ConfigurableProductPage/ConfigurableStickyAddToCart.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import type { AddToCartItemSelector, StickyAddToCart } from '@graphcommerce/magento-product' | ||
import type { IfConfig, ReactPlugin } from '@graphcommerce/next-config' | ||
import { useConfigurableSelectedVariant } from '../../hooks' | ||
|
||
export const component = 'StickyAddToCart' | ||
export const exported = '@graphcommerce/magento-product/components/StickyAddToCart/StickyAddToCart' | ||
export const ifConfig: IfConfig = 'enableStickyAddToCart' | ||
|
||
type PluginType = ReactPlugin<typeof StickyAddToCart, AddToCartItemSelector> | ||
|
||
const ConfigurableStickyAddToCart: PluginType = (props) => { | ||
const { Prev, product, ...rest } = props | ||
const variant = useConfigurableSelectedVariant({ url_key: product.url_key, index: 0 }) | ||
|
||
return <Prev product={variant ?? product} {...rest} /> | ||
} | ||
|
||
export const Plugin = ConfigurableStickyAddToCart |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
173 changes: 173 additions & 0 deletions
173
packages/magento-product/components/StickyAddToCart/StickyAddToCart.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
import { Image } from '@graphcommerce/image' | ||
import { LayoutHeader, responsiveVal } from '@graphcommerce/next-ui' | ||
import { Box, Typography } from '@mui/material' | ||
import { useEffect, useState } from 'react' | ||
import { AddProductsToCartButton, AddProductsToCartError } from '../AddProductsToCart' | ||
import { | ||
ProductPageAddToCartQuantityRow, | ||
ProductPageAddToCartRowProps, | ||
} from '../ProductPage/ProductPageAddToCartRow' | ||
import { ProductPageName } from '../ProductPageName' | ||
import { ProductPagePrice } from '../ProductPagePrice' | ||
|
||
export type StickyAddToCartProps = Partial<ProductPageAddToCartRowProps> & { | ||
cartButtonRef?: React.RefObject<HTMLElement> | ||
} | ||
|
||
export function StickyAddToCart(props: StickyAddToCartProps) { | ||
const { product, cartButtonRef } = props | ||
|
||
const mainImage = product?.media_gallery?.[0] | ||
const [switchPointPosition, setSwitchPointPosition] = useState<number>() | ||
|
||
useEffect(() => { | ||
if (!cartButtonRef?.current) return () => {} | ||
|
||
const handleResize = () => { | ||
const cartButtonBottomRelativeToViewport = | ||
cartButtonRef?.current?.getBoundingClientRect().bottom | ||
|
||
if (cartButtonBottomRelativeToViewport) { | ||
// Calculate the distance from the top of the page to the bottom position of cartButtonRef | ||
// We add the current scroll position to get an absolute distance | ||
const cartButtonAbsolutePosition = cartButtonBottomRelativeToViewport + window.scrollY | ||
|
||
// Set the switchPoint value to the calculated absolute position | ||
setSwitchPointPosition(cartButtonAbsolutePosition) | ||
} | ||
} | ||
|
||
window.addEventListener('resize', handleResize) | ||
handleResize() | ||
|
||
return () => { | ||
window.removeEventListener('resize', handleResize) | ||
} | ||
}, [cartButtonRef]) | ||
|
||
return ( | ||
product && | ||
switchPointPosition && ( | ||
<LayoutHeader | ||
hideBackButton | ||
switchPoint={switchPointPosition} | ||
size='responsive' | ||
sx={(theme) => ({ | ||
width: { md: '60vw', lg: '65vw' }, | ||
inset: 0, | ||
margin: 'auto', | ||
transform: { | ||
xs: 'none', | ||
md: `translateY(calc(${theme.appShell.appBarHeightMd} - ${theme.appShell.headerHeightSm} - 10px))`, | ||
}, | ||
|
||
[theme.breakpoints.down('md')]: { | ||
height: `calc(${theme.appShell.headerHeightSm} + 15px)`, | ||
mt: 0, | ||
}, | ||
|
||
'& .LayoutHeaderContent-content': { | ||
display: 'flex', | ||
inset: 0, | ||
margin: 'auto', | ||
px: '12px', | ||
}, | ||
|
||
'& .LayoutHeaderContent-center > div': { | ||
width: '100%', | ||
}, | ||
|
||
'& .LayoutHeaderContent-bg': { | ||
borderRadius: { xs: 0, md: theme.shape.borderRadius }, | ||
boxShadow: theme.shadows[6], | ||
transform: 'translateY(-50px)', | ||
transition: '0.3s ease-in-out', | ||
|
||
'&.scrolled': { | ||
opacity: 1, | ||
transform: 'none', | ||
}, | ||
|
||
[theme.breakpoints.down('md')]: { | ||
height: `calc(${theme.appShell.headerHeightSm} + 15px)`, | ||
}, | ||
}, | ||
|
||
'& .LayoutHeaderContent-center': { | ||
display: 'flex', | ||
width: '100%', | ||
transform: 'translateY(-50px)', | ||
transition: '0.3s ease-in-out', | ||
|
||
'&.scrolled': { | ||
opacity: 1, | ||
transform: 'none', | ||
}, | ||
}, | ||
|
||
'& .LayoutHeaderContent-right': { | ||
display: 'none', | ||
}, | ||
})} | ||
> | ||
<Box | ||
sx={{ | ||
display: 'flex', | ||
justifyContent: 'space-between', | ||
alignItems: 'center', | ||
gap: (theme) => theme.spacings.sm, | ||
}} | ||
> | ||
{mainImage?.url && ( | ||
<Image | ||
alt={mainImage.label ?? ''} | ||
layout='fixed' | ||
width={60} | ||
height={60} | ||
src={mainImage.url} | ||
sx={{ | ||
verticalAlign: 'middle', | ||
width: responsiveVal(46, 60), | ||
height: responsiveVal(46, 60), | ||
}} | ||
/> | ||
)} | ||
<Typography | ||
variant='h4' | ||
sx={(theme) => ({ | ||
display: { xs: 'none', md: 'block' }, | ||
fontSize: { xs: theme.typography.h5.fontSize, md: theme.typography.h4.fontSize }, | ||
fontWeight: 'bold', | ||
overflow: 'hidden', | ||
textOverflow: 'ellipsis', | ||
whiteSpace: 'nowrap', | ||
})} | ||
> | ||
<ProductPageName product={{ name: product?.name }} /> | ||
</Typography> | ||
<ProductPageAddToCartQuantityRow product={product} sx={{ ml: 'auto' }}> | ||
<AddProductsToCartError> | ||
<Typography | ||
component='div' | ||
variant='h3' | ||
lineHeight='1' | ||
sx={{ | ||
display: { lg: 'flex' }, | ||
gap: { xs: 1, lg: 2 }, | ||
typography: { xs: 'body1', md: 'h6', lg: 'h5' }, | ||
lineHeight: { xs: '1.2em', md: '1.7em' }, | ||
'& .incl-vat': { | ||
opacity: 0.4, | ||
}, | ||
}} | ||
> | ||
<ProductPagePrice product={product} /> | ||
</Typography> | ||
</AddProductsToCartError> | ||
</ProductPageAddToCartQuantityRow> | ||
<AddProductsToCartButton color='primary' size='medium' product={product} sx={{}} /> | ||
</Box> | ||
</LayoutHeader> | ||
) | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
fragment StickyAddToCart on ProductInterface @inject(into: ["UseAddProductsToCartAction"]) { | ||
...ProductPageGallery | ||
...ProductPagePrice | ||
} |
41 changes: 41 additions & 0 deletions
41
packages/magento-product/plugins/StickyAddToCart/StickyProductPageAddToCartRow.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import type { IfConfig, ReactPlugin } from '@graphcommerce/next-config' | ||
import { Box } from '@mui/material' | ||
import { useEffect, useRef, useState } from 'react' | ||
import { createPortal } from 'react-dom' | ||
import { ProductPageAddToCartRow, StickyAddToCart } from '../../components' | ||
|
||
export const component = 'ProductPageAddToCartActionsRow' | ||
export const exported = | ||
'@graphcommerce/magento-product/components/ProductPage/ProductPageAddToCartRow' | ||
export const ifConfig: IfConfig = 'enableStickyAddToCart' | ||
|
||
type PluginType = ReactPlugin<typeof ProductPageAddToCartRow> | ||
|
||
const StickyProductPageAddToCartRow: PluginType = (props) => { | ||
const { Prev, children, product, ...rest } = props | ||
|
||
const cartButtonRef = useRef<HTMLElement | null>(null) | ||
const [target, setTarget] = useState<HTMLElement | null>(null) | ||
|
||
const stickyAddToCart = <StickyAddToCart product={product} cartButtonRef={cartButtonRef} /> | ||
|
||
useEffect(() => { | ||
const stickyTarget = globalThis?.document?.getElementById('StickyAddToCartDestination') | ||
if (stickyTarget) { | ||
setTarget(stickyTarget) | ||
} | ||
}, []) | ||
|
||
return ( | ||
<> | ||
{target && createPortal(stickyAddToCart, target)} | ||
<Box ref={cartButtonRef}> | ||
<Prev product={product} {...rest}> | ||
{children} | ||
</Prev> | ||
</Box> | ||
</> | ||
) | ||
} | ||
|
||
export const Plugin = StickyProductPageAddToCartRow |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This plugin is self-referencing. So the plugin is targeting the current package. Shouldn't we make this a simple if statement somewhere? @carlocarels90 / @mikekeehnen.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could, but we could also make it a separate package right @paales? This would make it a plug and play solution
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mikekeehnen if we do this, we don't need the config file I guess? Because it will apply only when you add this package to your project?