From 386d146173f2066edc3a2ef3fcf639b1c2951f48 Mon Sep 17 00:00:00 2001 From: Carlo Carels Date: Tue, 22 Aug 2023 14:40:44 +0200 Subject: [PATCH 01/28] chore(GCOM-1194): create a ProductPageWrapper component so we can modify this component with plugins --- examples/magento-graphcms/pages/p/[url].tsx | 5 +++-- .../ProductPageWrapper/ProductPageWrapper.tsx | 13 +++++++++++++ packages/magento-product/components/index.ts | 1 + 3 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 packages/magento-product/components/ProductPageWrapper/ProductPageWrapper.tsx diff --git a/examples/magento-graphcms/pages/p/[url].tsx b/examples/magento-graphcms/pages/p/[url].tsx index 1a3612f469..9fc0715817 100644 --- a/examples/magento-graphcms/pages/p/[url].tsx +++ b/examples/magento-graphcms/pages/p/[url].tsx @@ -23,6 +23,7 @@ import { ProductPagePriceTiers, ProductShortDescription, ProductSidebarDelivery, + ProductPageWrapper, } from '@graphcommerce/magento-product' import { BundleProductOptions } from '@graphcommerce/magento-product-bundle' import { @@ -69,7 +70,7 @@ function ProductPage(props: Props) { if (!product?.sku || !product.url_key) return null return ( - <> + @@ -185,7 +186,7 @@ function ProductPage(props: Props) { }} /> )} - + ) } diff --git a/packages/magento-product/components/ProductPageWrapper/ProductPageWrapper.tsx b/packages/magento-product/components/ProductPageWrapper/ProductPageWrapper.tsx new file mode 100644 index 0000000000..c81b8aac0f --- /dev/null +++ b/packages/magento-product/components/ProductPageWrapper/ProductPageWrapper.tsx @@ -0,0 +1,13 @@ +import { Box } from '@mui/material' +import { AddProductsToCartButtonProps } from '../AddProductsToCart' + +export type ProductPageWrapperProps = { + product?: Pick + children?: React.ReactNode +} + +export function ProductPageWrapper(props: ProductPageWrapperProps) { + const { product, children } = props + + return {children} +} diff --git a/packages/magento-product/components/index.ts b/packages/magento-product/components/index.ts index b52c8fc361..9e89662621 100644 --- a/packages/magento-product/components/index.ts +++ b/packages/magento-product/components/index.ts @@ -38,3 +38,4 @@ export * from './ProductStaticPaths/getSitemapPaths' export * from './ProductUpsells/UpsellProducts.gql' export * from './ProductWeight/ProductWeight' export * from './ProductListPrice' +export * from './ProductPageWrapper/ProductPageWrapper' From 7c6d478fcb1d3038da6c30357efd214b7a4e0c56 Mon Sep 17 00:00:00 2001 From: Carlo Carels Date: Tue, 22 Aug 2023 14:41:32 +0200 Subject: [PATCH 02/28] chore(GCOM-1194): create a plugin StickyAddProductsToCartButton for ProductPageWrapper --- packages/magento-product/components/index.ts | 1 + .../StickyAddProductsToCartButton.tsx | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 packages/magento-product/plugins/ProductPageWrapper/StickyAddProductsToCartButton.tsx diff --git a/packages/magento-product/components/index.ts b/packages/magento-product/components/index.ts index 9e89662621..99ae0d790a 100644 --- a/packages/magento-product/components/index.ts +++ b/packages/magento-product/components/index.ts @@ -39,3 +39,4 @@ export * from './ProductUpsells/UpsellProducts.gql' export * from './ProductWeight/ProductWeight' export * from './ProductListPrice' export * from './ProductPageWrapper/ProductPageWrapper' +export * from './StickyAddToCart/StickyAddToCart' diff --git a/packages/magento-product/plugins/ProductPageWrapper/StickyAddProductsToCartButton.tsx b/packages/magento-product/plugins/ProductPageWrapper/StickyAddProductsToCartButton.tsx new file mode 100644 index 0000000000..0aec414215 --- /dev/null +++ b/packages/magento-product/plugins/ProductPageWrapper/StickyAddProductsToCartButton.tsx @@ -0,0 +1,24 @@ +import type { IfConfig, ReactPlugin } from '@graphcommerce/next-config' +import { AddProductsToCartButton, AddToCartItemSelector, StickyAddToCart } from '../../components' + +export const component = 'ProductPageWrapper' +export const exported = + '@graphcommerce/magento-product/components/ProductPageWrapper/ProductPageWrapper' +// export const ifConfig: IfConfig = 'configurableVariantValues.content' + +type PluginType = ReactPlugin + +const StickyAddProductsToCartButton: PluginType = (props) => { + const { Prev, product, children, ...rest } = props + + return ( + <> + + {children} + + + + ) +} + +export const Plugin = StickyAddProductsToCartButton From df6b9a34adbe114272343868dc025cad567fd0f7 Mon Sep 17 00:00:00 2001 From: Carlo Carels Date: Tue, 22 Aug 2023 14:42:29 +0200 Subject: [PATCH 03/28] chore(GCOM-1194): create a StickyAddToCart component --- .../StickyAddToCart/StickyAddToCart.tsx | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 packages/magento-product/components/StickyAddToCart/StickyAddToCart.tsx diff --git a/packages/magento-product/components/StickyAddToCart/StickyAddToCart.tsx b/packages/magento-product/components/StickyAddToCart/StickyAddToCart.tsx new file mode 100644 index 0000000000..8d0974e53f --- /dev/null +++ b/packages/magento-product/components/StickyAddToCart/StickyAddToCart.tsx @@ -0,0 +1,112 @@ +import { Image } from '@graphcommerce/image' +import { Container, Typography, useMediaQuery, useTheme } from '@mui/material' +import { useRef } from 'react' + +import { + AddProductsToCartButton, + AddProductsToCartButtonProps, + AddProductsToCartError, +} from '../AddProductsToCart' +import { + AddProductsToCartForm, + AddProductsToCartFormProps, +} from '../AddProductsToCart/AddProductsToCartForm' +import { ProductListPrice } from '../ProductListPrice' +import { ProductPageAddToCartQuantityRow } from '../ProductPage/ProductPageAddToCartRow' +import { ProductPageGalleryProps } from '../ProductPageGallery/ProductPageGallery' +import { ProductPageName } from '../ProductPageName' + +type Props = Pick & { + product: Pick & { + media_gallery: ProductPageGalleryProps['product']['media_gallery'] + sku: string + url_key: string + uid: string + } +} + +export function StickyAddToCart(props: Props) { + const { product, defaultValues } = props + + const mainImage = product?.media_gallery?.[0] + + const theme = useTheme() + const isMobile = useMediaQuery(theme.breakpoints.down('md')) + + const showStickyAddToCart = true + + if (!product?.sku || !product.url_key) return null + + return ( + + + {mainImage?.url && ( + {mainImage.label + )} + {!isMobile && ( + + + + )} + + + + + + + + + ) +} From 2baa32a0316735a943d9dac286e1976d2affcd28 Mon Sep 17 00:00:00 2001 From: Carlo Carels Date: Tue, 22 Aug 2023 15:07:13 +0200 Subject: [PATCH 04/28] refactor: rename props --- .../components/StickyAddToCart/StickyAddToCart.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/magento-product/components/StickyAddToCart/StickyAddToCart.tsx b/packages/magento-product/components/StickyAddToCart/StickyAddToCart.tsx index 8d0974e53f..0a499539c8 100644 --- a/packages/magento-product/components/StickyAddToCart/StickyAddToCart.tsx +++ b/packages/magento-product/components/StickyAddToCart/StickyAddToCart.tsx @@ -16,18 +16,17 @@ import { ProductPageAddToCartQuantityRow } from '../ProductPage/ProductPageAddTo import { ProductPageGalleryProps } from '../ProductPageGallery/ProductPageGallery' import { ProductPageName } from '../ProductPageName' -type Props = Pick & { product: Pick & { media_gallery: ProductPageGalleryProps['product']['media_gallery'] +type StickyAddToCartProps = Pick & { sku: string url_key: string uid: string } } -export function StickyAddToCart(props: Props) { +export function StickyAddToCart(props: StickyAddToCartProps) { const { product, defaultValues } = props - const mainImage = product?.media_gallery?.[0] const theme = useTheme() From 6a5be77a82d0fbdd22ffa7107692681e5ad2e893 Mon Sep 17 00:00:00 2001 From: Carlo Carels Date: Thu, 24 Aug 2023 15:17:39 +0200 Subject: [PATCH 05/28] =?UTF-8?q?chore(GCOM-1194):=20Add=20'forceScrolled'?= =?UTF-8?q?=20option=20to=20show=20the=20=E2=80=98scrolled'=20state=20with?= =?UTF-8?q?out=20using=20numbers=20for=20switchPoint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Layout/components/LayoutHeader.tsx | 4 +++ .../Layout/components/LayoutHeaderContent.tsx | 28 +++++++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/packages/next-ui/Layout/components/LayoutHeader.tsx b/packages/next-ui/Layout/components/LayoutHeader.tsx index 008aef3429..72a4d6df1a 100644 --- a/packages/next-ui/Layout/components/LayoutHeader.tsx +++ b/packages/next-ui/Layout/components/LayoutHeader.tsx @@ -28,6 +28,8 @@ export type LayoutHeaderProps = FloatingProps & sx?: SxProps hideBackButton?: boolean + + forceScrolled?: boolean } type ComponentStyleProps = { @@ -53,6 +55,7 @@ export function LayoutHeader(props: LayoutHeaderProps) { secondary, noAlign = false, switchPoint, + forceScrolled, size = 'responsive', sx = [], bgColor, @@ -153,6 +156,7 @@ export function LayoutHeader(props: LayoutHeaderProps) { floatingMd={floatingMd} floatingSm={floatingSm} switchPoint={switchPoint} + forceScrolled={forceScrolled} bgColor={bgColor} > {children} diff --git a/packages/next-ui/Layout/components/LayoutHeaderContent.tsx b/packages/next-ui/Layout/components/LayoutHeaderContent.tsx index 27fa518c3a..648c3129c9 100644 --- a/packages/next-ui/Layout/components/LayoutHeaderContent.tsx +++ b/packages/next-ui/Layout/components/LayoutHeaderContent.tsx @@ -14,6 +14,7 @@ export type LayoutHeaderContentProps = FloatingProps & { right?: React.ReactNode divider?: React.ReactNode switchPoint?: number + forceScrolled: boolean sx?: SxProps sxBg?: SxProps layout?: LayoutProps['layout'] @@ -25,6 +26,7 @@ type OwnerState = { floatingSm: boolean floatingMd: boolean scrolled: boolean + forcedScrolled: boolean divider: boolean size: 'small' | 'responsive' bgColor?: 'paper' | 'default' @@ -45,6 +47,7 @@ export function LayoutHeaderContent(props: LayoutHeaderContentProps) { floatingMd = false, floatingSm = false, switchPoint = 50, + forceScrolled, sx = [], sxBg = [], layout, @@ -56,7 +59,17 @@ export function LayoutHeaderContent(props: LayoutHeaderContentProps) { const scroll = useScrollY() const scrolled = useMotionValueValue(scroll, (y) => y >= switchPoint) - const classes = withState({ floatingSm, floatingMd, scrolled, divider: !!divider, size, bgColor }) + const forcedScrolled = forceScrolled + + const classes = withState({ + floatingSm, + floatingMd, + scrolled: !forceScrolled, + forcedScrolled, + divider: !!divider, + size, + bgColor, + }) return ( <> @@ -93,7 +106,11 @@ export function LayoutHeaderContent(props: LayoutHeaderContentProps) { opacity: 0, transition: `opacity 150ms`, - '&.scrolled': { + '&.scrolled:not(.forcedScrolled)': { + opacity: 1, + }, + + '&.forceScrolled': { opacity: 1, }, @@ -179,7 +196,12 @@ export function LayoutHeaderContent(props: LayoutHeaderContentProps) { transition: `opacity 150ms`, opacity: 0, - '&.scrolled': { + '&.scrolled:not(.forcedScrolled)': { + opacity: 1, + '& > *': { pointerEvents: 'all' }, + }, + + '&.forceScrolled': { opacity: 1, '& > *': { pointerEvents: 'all' }, }, From 2bbeb6a63c0616024f4da8aefb47bf194c962066 Mon Sep 17 00:00:00 2001 From: Carlo Carels Date: Thu, 24 Aug 2023 15:24:20 +0200 Subject: [PATCH 06/28] chore: add url to the ProductPageGallery fragment --- .../components/ProductPageGallery/ProductPageGallery.graphql | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/magento-product/components/ProductPageGallery/ProductPageGallery.graphql b/packages/magento-product/components/ProductPageGallery/ProductPageGallery.graphql index 6d5ccc105e..f3f24b56e3 100644 --- a/packages/magento-product/components/ProductPageGallery/ProductPageGallery.graphql +++ b/packages/magento-product/components/ProductPageGallery/ProductPageGallery.graphql @@ -4,6 +4,7 @@ fragment ProductPageGallery on ProductInterface { label position disabled + url ...ProductImage ...ProductVideo } From 2a2b5f466a51de47fbaa37375732e4988903f1ed Mon Sep 17 00:00:00 2001 From: Carlo Carels Date: Thu, 24 Aug 2023 15:30:20 +0200 Subject: [PATCH 07/28] chore(GCOM-1194): Pass a reference from the AddProductToCartButton to its parent component to trigger the visibility of StickAddToCart --- examples/magento-graphcms/pages/p/[url].tsx | 12 +- .../AddProductsToCartButton.tsx | 17 +- .../StickyAddToCart/StickyAddToCart.tsx | 165 ++++++++++-------- 3 files changed, 117 insertions(+), 77 deletions(-) diff --git a/examples/magento-graphcms/pages/p/[url].tsx b/examples/magento-graphcms/pages/p/[url].tsx index 9fc0715817..20e6d13c57 100644 --- a/examples/magento-graphcms/pages/p/[url].tsx +++ b/examples/magento-graphcms/pages/p/[url].tsx @@ -23,7 +23,7 @@ import { ProductPagePriceTiers, ProductShortDescription, ProductSidebarDelivery, - ProductPageWrapper, + StickyAddToCart, } from '@graphcommerce/magento-product' import { BundleProductOptions } from '@graphcommerce/magento-product-bundle' import { @@ -38,6 +38,7 @@ import { GetStaticProps, LayoutHeader, LayoutTitle, isTypename } from '@graphcom import { Trans } from '@lingui/react' import { Divider, Link, Typography } from '@mui/material' import { GetStaticPaths } from 'next' +import { useRef } from 'react' import { LayoutDocument, LayoutNavigation, @@ -67,10 +68,13 @@ function ProductPage(props: Props) { relatedUpsells?.items?.find((item) => item?.uid === products?.items?.[0]?.uid), ) + const cartButtonRef = useRef(null) + if (!product?.sku || !product.url_key) return null return ( - + <> + @@ -157,7 +161,7 @@ function ProductPage(props: Props) { - + @@ -186,7 +190,7 @@ function ProductPage(props: Props) { }} /> )} - + ) } diff --git a/packages/magento-product/components/AddProductsToCart/AddProductsToCartButton.tsx b/packages/magento-product/components/AddProductsToCart/AddProductsToCartButton.tsx index 0af4411ea8..3b8db187d7 100644 --- a/packages/magento-product/components/AddProductsToCart/AddProductsToCartButton.tsx +++ b/packages/magento-product/components/AddProductsToCart/AddProductsToCartButton.tsx @@ -17,14 +17,25 @@ export type AddProductsToCartButtonProps = UseAddProductsToCartActionProps & | 'onClick' | 'sx' | 'children' - > + > & { + forwardedRef?: React.Ref + } export function AddProductsToCartButton(props: AddProductsToCartButtonProps) { - const { children, product, ...rest } = props + const { children, product, forwardedRef, ...rest } = props const { showSuccess, ...action } = useAddProductsToCartAction(props) + const action = useAddProductsToCartAction(props) return ( - ) diff --git a/packages/magento-product/components/StickyAddToCart/StickyAddToCart.tsx b/packages/magento-product/components/StickyAddToCart/StickyAddToCart.tsx index 0a499539c8..94ed3c7733 100644 --- a/packages/magento-product/components/StickyAddToCart/StickyAddToCart.tsx +++ b/packages/magento-product/components/StickyAddToCart/StickyAddToCart.tsx @@ -1,111 +1,136 @@ import { Image } from '@graphcommerce/image' -import { Container, Typography, useMediaQuery, useTheme } from '@mui/material' -import { useRef } from 'react' +import { LayoutHeader } from '@graphcommerce/next-ui' +import { Box, Typography } from '@mui/material' -import { - AddProductsToCartButton, - AddProductsToCartButtonProps, - AddProductsToCartError, -} from '../AddProductsToCart' +import { useEffect, useState } from 'react' +import { ProductListItemFragment } from '../../Api/ProductListItem.gql' +import { AddProductsToCartButton, AddProductsToCartError } from '../AddProductsToCart' import { AddProductsToCartForm, AddProductsToCartFormProps, } from '../AddProductsToCart/AddProductsToCartForm' -import { ProductListPrice } from '../ProductListPrice' +import { UseAddProductsToCartActionFragment } from '../AddProductsToCart/UseAddProductsToCartAction.gql' import { ProductPageAddToCartQuantityRow } from '../ProductPage/ProductPageAddToCartRow' -import { ProductPageGalleryProps } from '../ProductPageGallery/ProductPageGallery' +import { ProductPageGalleryFragment } from '../ProductPageGallery/ProductPageGallery.gql' import { ProductPageName } from '../ProductPageName' +import { ProductPagePrice } from '../ProductPagePrice' - product: Pick & { - media_gallery: ProductPageGalleryProps['product']['media_gallery'] type StickyAddToCartProps = Pick & { - sku: string - url_key: string - uid: string - } + product?: UseAddProductsToCartActionFragment & + ProductPageGalleryFragment & + ProductListItemFragment + cartButtonRef?: React.RefObject } export function StickyAddToCart(props: StickyAddToCartProps) { - const { product, defaultValues } = props + const { product, defaultValues, cartButtonRef } = props const mainImage = product?.media_gallery?.[0] - const theme = useTheme() - const isMobile = useMediaQuery(theme.breakpoints.down('md')) + const [isButtonInViewport, setIsButtonInViewport] = useState(false) + + useEffect(() => { + if (!cartButtonRef || !cartButtonRef.current) return + + const options = { + root: null, + rootMargin: '0px', + threshold: 0, + } + + const observer = new IntersectionObserver((entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + setIsButtonInViewport(true) + } else { + setIsButtonInViewport(false) + } + }) + }, options) - const showStickyAddToCart = true + observer.observe(cartButtonRef.current) + + // eslint-disable-next-line consistent-return + return () => { + observer.disconnect() + } + }, [cartButtonRef, isButtonInViewport]) if (!product?.sku || !product.url_key) return null return ( - {mainImage?.url && ( {mainImage.label )} - {!isMobile && ( - - - - )} - - - - - - + + + + + + + + + + + + + + + - + ) } From 56ae512d2a745966eed53973dba0bcf82ed0f43b Mon Sep 17 00:00:00 2001 From: Carlo Carels Date: Fri, 25 Aug 2023 10:19:08 +0200 Subject: [PATCH 08/28] chore: make the forceScrolled optional --- packages/next-ui/Layout/components/LayoutHeaderContent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next-ui/Layout/components/LayoutHeaderContent.tsx b/packages/next-ui/Layout/components/LayoutHeaderContent.tsx index 648c3129c9..ea3ceacdea 100644 --- a/packages/next-ui/Layout/components/LayoutHeaderContent.tsx +++ b/packages/next-ui/Layout/components/LayoutHeaderContent.tsx @@ -14,7 +14,7 @@ export type LayoutHeaderContentProps = FloatingProps & { right?: React.ReactNode divider?: React.ReactNode switchPoint?: number - forceScrolled: boolean + forceScrolled?: boolean sx?: SxProps sxBg?: SxProps layout?: LayoutProps['layout'] From 4439bd8f2f0b6d93f1a3235323ea38a0c0e302ac Mon Sep 17 00:00:00 2001 From: Carlo Carels Date: Fri, 25 Aug 2023 10:32:32 +0200 Subject: [PATCH 09/28] chore(GCOM-1194): create a enableStickyAddToCart config --- docs/framework/config.md | 6 ++++++ packages/magento-product/Config.graphqls | 7 +++++++ packagesDev/next-config/dist/generated/config.js | 1 + packagesDev/next-config/src/generated/config.ts | 7 +++++++ 4 files changed, 21 insertions(+) diff --git a/docs/framework/config.md b/docs/framework/config.md index 319ff83c0f..5c2289f2a3 100644 --- a/docs/framework/config.md +++ b/docs/framework/config.md @@ -171,6 +171,12 @@ Enables some demo specific code that is probably not useful for a project: - Adds "dominant_color" attribute swatches to the product list items. - Creates a big list items in the product list. +#### `enableStickyAddToCart: Boolean` + +Enable this option to activate the sticky add-to-cart bar on the product page. +When enabled, a sticky bar will appear if the default add-to-cart button goes out of the visible viewport. +This allows users to easily access the add-to-cart functionality even as they scroll through the page. + #### `googleAnalyticsId: String` See https://support.google.com/analytics/answer/9539598?hl=en diff --git a/packages/magento-product/Config.graphqls b/packages/magento-product/Config.graphqls index ba68dc59b0..7c263d3afc 100644 --- a/packages/magento-product/Config.graphqls +++ b/packages/magento-product/Config.graphqls @@ -45,4 +45,11 @@ extend input GraphCommerceConfig { Default: 'false' """ crossSellsHideCartItems: Boolean = false + + """ + Enable this option to activate the sticky add-to-cart bar on the product page. + When enabled, a sticky bar will appear if the default add-to-cart button goes out of the visible viewport. + This allows users to easily access the add-to-cart functionality even as they scroll through the page. + """ + enableStickyAddToCart: Boolean } diff --git a/packagesDev/next-config/dist/generated/config.js b/packagesDev/next-config/dist/generated/config.js index ba826db8c4..46c6b8b030 100644 --- a/packagesDev/next-config/dist/generated/config.js +++ b/packagesDev/next-config/dist/generated/config.js @@ -68,6 +68,7 @@ function GraphCommerceConfigSchema() { customerRequireEmailConfirmation: _zod.z.boolean().nullish(), debug: GraphCommerceDebugConfigSchema().nullish(), demoMode: _zod.z.boolean().nullish(), + enableStickyAddToCart: _zod.z.boolean().nullish(), googleAnalyticsId: _zod.z.string().nullish(), googleRecaptchaKey: _zod.z.string().nullish(), googleTagmanagerId: _zod.z.string().nullish(), diff --git a/packagesDev/next-config/src/generated/config.ts b/packagesDev/next-config/src/generated/config.ts index d64733b6ae..089e6d5a6f 100644 --- a/packagesDev/next-config/src/generated/config.ts +++ b/packagesDev/next-config/src/generated/config.ts @@ -169,6 +169,12 @@ export type GraphCommerceConfig = { * - Creates a big list items in the product list. */ demoMode?: InputMaybe; + /** + * Enable this option to activate the sticky add-to-cart bar on the product page. + * When enabled, a sticky bar will appear if the default add-to-cart button goes out of the visible viewport. + * This allows users to easily access the add-to-cart functionality even as they scroll through the page. + */ + enableStickyAddToCart?: InputMaybe; /** * See https://support.google.com/analytics/answer/9539598?hl=en * @@ -427,6 +433,7 @@ export function GraphCommerceConfigSchema(): z.ZodObject Date: Fri, 25 Aug 2023 11:06:58 +0200 Subject: [PATCH 10/28] chore: add default value for the forceScrolled option --- packages/next-ui/Layout/components/LayoutHeaderContent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next-ui/Layout/components/LayoutHeaderContent.tsx b/packages/next-ui/Layout/components/LayoutHeaderContent.tsx index ea3ceacdea..7dec683abb 100644 --- a/packages/next-ui/Layout/components/LayoutHeaderContent.tsx +++ b/packages/next-ui/Layout/components/LayoutHeaderContent.tsx @@ -47,7 +47,7 @@ export function LayoutHeaderContent(props: LayoutHeaderContentProps) { floatingMd = false, floatingSm = false, switchPoint = 50, - forceScrolled, + forceScrolled = false, sx = [], sxBg = [], layout, From 32161a01c74167c20a477acec5f89493ca8ed0f9 Mon Sep 17 00:00:00 2001 From: Carlo Carels Date: Fri, 25 Aug 2023 11:32:34 +0200 Subject: [PATCH 11/28] chore(GCOM-1194): create ConfigurableStickyAddToCart plugin for enhanced compatibility with configurable products. --- .../ConfigurableStickyAddToCart.tsx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 packages/magento-product-configurable/plugins/ConfigurableProductPage/ConfigurableStickyAddToCart.tsx diff --git a/packages/magento-product-configurable/plugins/ConfigurableProductPage/ConfigurableStickyAddToCart.tsx b/packages/magento-product-configurable/plugins/ConfigurableProductPage/ConfigurableStickyAddToCart.tsx new file mode 100644 index 0000000000..8d3cb7866a --- /dev/null +++ b/packages/magento-product-configurable/plugins/ConfigurableProductPage/ConfigurableStickyAddToCart.tsx @@ -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 + +const ConfigurableStickyAddToCart: PluginType = (props) => { + const { Prev, product, ...rest } = props + const variant = useConfigurableSelectedVariant({ url_key: product.url_key, index: 0 }) + + return +} + +export const Plugin = ConfigurableStickyAddToCart From 3a82c319180ff90eba8f9978ad03ab5cbefde8dd Mon Sep 17 00:00:00 2001 From: Carlo Carels Date: Fri, 25 Aug 2023 11:33:40 +0200 Subject: [PATCH 12/28] chore(GCOM-1194): move StickyAddToCart to the AddProductsToCartForm so we can add products to cart within the StickyAddToCart --- examples/magento-graphcms/pages/p/[url].tsx | 176 ++++++++++---------- 1 file changed, 87 insertions(+), 89 deletions(-) diff --git a/examples/magento-graphcms/pages/p/[url].tsx b/examples/magento-graphcms/pages/p/[url].tsx index 20e6d13c57..394aee9b9e 100644 --- a/examples/magento-graphcms/pages/p/[url].tsx +++ b/examples/magento-graphcms/pages/p/[url].tsx @@ -73,107 +73,105 @@ function ProductPage(props: Props) { if (!product?.sku || !product.url_key) return null return ( - <> - - + + {import.meta.graphCommerce.enableStickyAddToCart ? ( + + ) : ( + )} - ({ - '@context': 'https://schema.org', - ...jsonLdProduct(p), - ...jsonLdProductOffer(p), - ...jsonLdProductReview(p), - })} - /> - - - - ({ - '& .SidebarGallery-sidebar': { display: 'grid', rowGap: theme.spacings.sm }, - })} - > -
- {isTypename(product, ['ConfigurableProduct', 'BundleProduct']) && ( - - }} - /> - - )} - - + ({ + '@context': 'https://schema.org', + ...jsonLdProduct(p), + ...jsonLdProductOffer(p), + ...jsonLdProductReview(p), + })} + /> + + + + ({ + '& .SidebarGallery-sidebar': { display: 'grid', rowGap: theme.spacings.sm }, + })} + > +
+ {isTypename(product, ['ConfigurableProduct', 'BundleProduct']) && ( + + }} + /> - ({ mb: theme.spacings.xs })} - product={product} - /> - -
- - {isTypename(product, ['ConfigurableProduct']) && ( - - - - ), - }} - /> - )} - {isTypename(product, ['BundleProduct']) && ( - - )} - {isTypename(product, ['DownloadableProduct']) && ( - )} - {!isTypename(product, ['GroupedProduct']) && } - - - - - - - - - - - - + + + + ({ mb: theme.spacings.xs })} product={product} /> + +
+ + {isTypename(product, ['ConfigurableProduct']) && ( + + + + ), + }} + /> + )} + {isTypename(product, ['BundleProduct']) && ( + + )} + {isTypename(product, ['DownloadableProduct']) && ( + + )} + {!isTypename(product, ['GroupedProduct']) && } + + + + + + + + + + + + - + - + - - - - + + + + - -
+ + - } - fontSize='responsive' - /> -
+ } + fontSize='responsive' + /> {pages?.[0] && ( )} - +
) } From 0d2c0db42efe121484baee7fe096690a4c8c4eba Mon Sep 17 00:00:00 2001 From: Carlo Carels Date: Fri, 25 Aug 2023 11:49:51 +0200 Subject: [PATCH 13/28] chore(GCOM-1194): add styling for the stick add to cart + remove the AddProductsToCartForm to prevent issues with the addtocart --- .../StickyAddToCart/StickyAddToCart.tsx | 125 +++++++++++------- 1 file changed, 76 insertions(+), 49 deletions(-) diff --git a/packages/magento-product/components/StickyAddToCart/StickyAddToCart.tsx b/packages/magento-product/components/StickyAddToCart/StickyAddToCart.tsx index 94ed3c7733..d50f9eb0fc 100644 --- a/packages/magento-product/components/StickyAddToCart/StickyAddToCart.tsx +++ b/packages/magento-product/components/StickyAddToCart/StickyAddToCart.tsx @@ -1,31 +1,23 @@ import { Image } from '@graphcommerce/image' import { LayoutHeader } from '@graphcommerce/next-ui' import { Box, Typography } from '@mui/material' - import { useEffect, useState } from 'react' import { ProductListItemFragment } from '../../Api/ProductListItem.gql' import { AddProductsToCartButton, AddProductsToCartError } from '../AddProductsToCart' -import { - AddProductsToCartForm, - AddProductsToCartFormProps, -} from '../AddProductsToCart/AddProductsToCartForm' import { UseAddProductsToCartActionFragment } from '../AddProductsToCart/UseAddProductsToCartAction.gql' import { ProductPageAddToCartQuantityRow } from '../ProductPage/ProductPageAddToCartRow' import { ProductPageGalleryFragment } from '../ProductPageGallery/ProductPageGallery.gql' import { ProductPageName } from '../ProductPageName' import { ProductPagePrice } from '../ProductPagePrice' -type StickyAddToCartProps = Pick & { - product?: UseAddProductsToCartActionFragment & - ProductPageGalleryFragment & - ProductListItemFragment +type StickyAddToCartProps = { + product: UseAddProductsToCartActionFragment & ProductPageGalleryFragment & ProductListItemFragment cartButtonRef?: React.RefObject } export function StickyAddToCart(props: StickyAddToCartProps) { - const { product, defaultValues, cartButtonRef } = props + const { product, cartButtonRef } = props const mainImage = product?.media_gallery?.[0] - const [isButtonInViewport, setIsButtonInViewport] = useState(false) useEffect(() => { @@ -62,25 +54,56 @@ export function StickyAddToCart(props: StickyAddToCartProps) { hideBackButton forceScrolled={isButtonInViewport} size='responsive' - divider - bgColor='paper' - sx={{ - width: '70vw', + sx={(theme) => ({ + width: { md: '60vw', lg: '65vw' }, inset: 0, margin: 'auto', + transform: { + xs: 'none', + md: `translateY(calc(${theme.appShell.appBarHeightMd} * .28))`, + }, + + [theme.breakpoints.down('md')]: { + height: `calc(${theme.appShell.headerHeightSm} + 15px)`, + mt: 0, + }, + '& .LayoutHeaderContent-content': { - width: '70vw', + display: 'flex', inset: 0, margin: 'auto', + px: '12px', + }, + + '& .LayoutHeaderContent-center': { + display: 'flex', + width: '100%', + }, + + '& .LayoutHeaderContent-center > div': { + width: '100%', + }, + + '& .LayoutHeaderContent-bg': { + borderRadius: { xs: 0, md: theme.shape.borderRadius }, + boxShadow: theme.shadows[6], + + [theme.breakpoints.down('md')]: { + height: `calc(${theme.appShell.headerHeightSm} + 15px)`, + }, + }, + + '& .LayoutHeaderContent-right': { + display: 'none', }, - }} + })} > - theme.spacings.sm, }} > {mainImage?.url && ( @@ -90,47 +113,51 @@ export function StickyAddToCart(props: StickyAddToCartProps) { width={60} height={60} src={mainImage.url} - sx={{ verticalAlign: 'middle' }} + sx={(theme) => ({ + verticalAlign: 'middle', + [theme.breakpoints.down('md')]: { + width: 46, + height: 46, + }, + })} /> )} ({ + fontSize: { xs: theme.typography.h5.fontSize, md: theme.typography.h4.fontSize }, + fontWeight: 'bold', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', - }} + })} > - - - - - - - - - - - + + + + + + + + + ) } From 2f22eba09d895ec1ca87b9f7b73227603f8fe901 Mon Sep 17 00:00:00 2001 From: Carlo Carels Date: Fri, 25 Aug 2023 11:50:17 +0200 Subject: [PATCH 14/28] refactor: remove unused files --- .../ProductPageWrapper/ProductPageWrapper.tsx | 5 ++-- .../StickyAddProductsToCartButton.tsx | 24 ------------------- 2 files changed, 2 insertions(+), 27 deletions(-) delete mode 100644 packages/magento-product/plugins/ProductPageWrapper/StickyAddProductsToCartButton.tsx diff --git a/packages/magento-product/components/ProductPageWrapper/ProductPageWrapper.tsx b/packages/magento-product/components/ProductPageWrapper/ProductPageWrapper.tsx index c81b8aac0f..33b52e5793 100644 --- a/packages/magento-product/components/ProductPageWrapper/ProductPageWrapper.tsx +++ b/packages/magento-product/components/ProductPageWrapper/ProductPageWrapper.tsx @@ -2,12 +2,11 @@ import { Box } from '@mui/material' import { AddProductsToCartButtonProps } from '../AddProductsToCart' export type ProductPageWrapperProps = { - product?: Pick children?: React.ReactNode } export function ProductPageWrapper(props: ProductPageWrapperProps) { - const { product, children } = props + const { children } = props - return {children} + return {children} } diff --git a/packages/magento-product/plugins/ProductPageWrapper/StickyAddProductsToCartButton.tsx b/packages/magento-product/plugins/ProductPageWrapper/StickyAddProductsToCartButton.tsx deleted file mode 100644 index 0aec414215..0000000000 --- a/packages/magento-product/plugins/ProductPageWrapper/StickyAddProductsToCartButton.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import type { IfConfig, ReactPlugin } from '@graphcommerce/next-config' -import { AddProductsToCartButton, AddToCartItemSelector, StickyAddToCart } from '../../components' - -export const component = 'ProductPageWrapper' -export const exported = - '@graphcommerce/magento-product/components/ProductPageWrapper/ProductPageWrapper' -// export const ifConfig: IfConfig = 'configurableVariantValues.content' - -type PluginType = ReactPlugin - -const StickyAddProductsToCartButton: PluginType = (props) => { - const { Prev, product, children, ...rest } = props - - return ( - <> - - {children} - - - - ) -} - -export const Plugin = StickyAddProductsToCartButton From 58011ed70f27e08f3afaf0cfb06d18d9f0671d2a Mon Sep 17 00:00:00 2001 From: Carlo Carels Date: Fri, 25 Aug 2023 22:32:42 +0200 Subject: [PATCH 15/28] refactor: move changes to a plugin --- .../AddProductsToCartButton.tsx | 14 ++------ .../StickyAddProductsToCartButton.tsx | 32 +++++++++++++++++++ 2 files changed, 34 insertions(+), 12 deletions(-) create mode 100644 packages/magento-product/plugins/StickyAddToCart/StickyAddProductsToCartButton.tsx diff --git a/packages/magento-product/components/AddProductsToCart/AddProductsToCartButton.tsx b/packages/magento-product/components/AddProductsToCart/AddProductsToCartButton.tsx index 3b8db187d7..8ada32e0be 100644 --- a/packages/magento-product/components/AddProductsToCart/AddProductsToCartButton.tsx +++ b/packages/magento-product/components/AddProductsToCart/AddProductsToCartButton.tsx @@ -17,9 +17,7 @@ export type AddProductsToCartButtonProps = UseAddProductsToCartActionProps & | 'onClick' | 'sx' | 'children' - > & { - forwardedRef?: React.Ref - } + > export function AddProductsToCartButton(props: AddProductsToCartButtonProps) { const { children, product, forwardedRef, ...rest } = props @@ -27,15 +25,7 @@ export function AddProductsToCartButton(props: AddProductsToCartButtonProps) { const action = useAddProductsToCartAction(props) return ( - ) diff --git a/packages/magento-product/plugins/StickyAddToCart/StickyAddProductsToCartButton.tsx b/packages/magento-product/plugins/StickyAddToCart/StickyAddProductsToCartButton.tsx new file mode 100644 index 0000000000..71ce1b7fcd --- /dev/null +++ b/packages/magento-product/plugins/StickyAddToCart/StickyAddProductsToCartButton.tsx @@ -0,0 +1,32 @@ +import type { IfConfig, ReactPlugin } from '@graphcommerce/next-config' +import { Button } from '@graphcommerce/next-ui' +import { Trans } from '@lingui/react' +import { type AddProductsToCartButton, useAddProductsToCartAction } from '../../components' + +export const component = 'AddProductsToCartButton' +export const exported = + '@graphcommerce/magento-product/components/AddProductsToCart/AddProductsToCartButton' +export const ifConfig: IfConfig = 'enableStickyAddToCart' + +type PluginType = ReactPlugin + +const StickyAddProductsToCartButton: PluginType = (props) => { + const { children, product, forwardedRef, ...rest } = props + const action = useAddProductsToCartAction(props) + + return ( + + ) +} + +export const Plugin = StickyAddProductsToCartButton From c49077147c9ed34f94627ee0f54c770430f62f78 Mon Sep 17 00:00:00 2001 From: Carlo Carels Date: Mon, 28 Aug 2023 14:02:04 +0200 Subject: [PATCH 16/28] refactor: remove unused files --- .../ProductPageWrapper/ProductPageWrapper.tsx | 12 ------------ packages/magento-product/components/index.ts | 1 - 2 files changed, 13 deletions(-) delete mode 100644 packages/magento-product/components/ProductPageWrapper/ProductPageWrapper.tsx diff --git a/packages/magento-product/components/ProductPageWrapper/ProductPageWrapper.tsx b/packages/magento-product/components/ProductPageWrapper/ProductPageWrapper.tsx deleted file mode 100644 index 33b52e5793..0000000000 --- a/packages/magento-product/components/ProductPageWrapper/ProductPageWrapper.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { Box } from '@mui/material' -import { AddProductsToCartButtonProps } from '../AddProductsToCart' - -export type ProductPageWrapperProps = { - children?: React.ReactNode -} - -export function ProductPageWrapper(props: ProductPageWrapperProps) { - const { children } = props - - return {children} -} diff --git a/packages/magento-product/components/index.ts b/packages/magento-product/components/index.ts index 99ae0d790a..5f0af7f8df 100644 --- a/packages/magento-product/components/index.ts +++ b/packages/magento-product/components/index.ts @@ -38,5 +38,4 @@ export * from './ProductStaticPaths/getSitemapPaths' export * from './ProductUpsells/UpsellProducts.gql' export * from './ProductWeight/ProductWeight' export * from './ProductListPrice' -export * from './ProductPageWrapper/ProductPageWrapper' export * from './StickyAddToCart/StickyAddToCart' From e9cf329fc533e48e0b20fc66d2cd23153b6fb8bf Mon Sep 17 00:00:00 2001 From: Carlo Carels Date: Mon, 28 Aug 2023 14:28:16 +0200 Subject: [PATCH 17/28] refactor: undo changes for the product page. --- examples/magento-graphcms/pages/p/[url].tsx | 179 ++++++++++---------- 1 file changed, 88 insertions(+), 91 deletions(-) diff --git a/examples/magento-graphcms/pages/p/[url].tsx b/examples/magento-graphcms/pages/p/[url].tsx index 394aee9b9e..1a3612f469 100644 --- a/examples/magento-graphcms/pages/p/[url].tsx +++ b/examples/magento-graphcms/pages/p/[url].tsx @@ -23,7 +23,6 @@ import { ProductPagePriceTiers, ProductShortDescription, ProductSidebarDelivery, - StickyAddToCart, } from '@graphcommerce/magento-product' import { BundleProductOptions } from '@graphcommerce/magento-product-bundle' import { @@ -38,7 +37,6 @@ import { GetStaticProps, LayoutHeader, LayoutTitle, isTypename } from '@graphcom import { Trans } from '@lingui/react' import { Divider, Link, Typography } from '@mui/material' import { GetStaticPaths } from 'next' -import { useRef } from 'react' import { LayoutDocument, LayoutNavigation, @@ -68,110 +66,109 @@ function ProductPage(props: Props) { relatedUpsells?.items?.find((item) => item?.uid === products?.items?.[0]?.uid), ) - const cartButtonRef = useRef(null) - if (!product?.sku || !product.url_key) return null return ( - - {import.meta.graphCommerce.enableStickyAddToCart ? ( - - ) : ( + <> + - )} - ({ - '@context': 'https://schema.org', - ...jsonLdProduct(p), - ...jsonLdProductOffer(p), - ...jsonLdProductReview(p), - })} - /> - - - - ({ - '& .SidebarGallery-sidebar': { display: 'grid', rowGap: theme.spacings.sm }, - })} - > -
- {isTypename(product, ['ConfigurableProduct', 'BundleProduct']) && ( - - }} - /> + ({ + '@context': 'https://schema.org', + ...jsonLdProduct(p), + ...jsonLdProductOffer(p), + ...jsonLdProductReview(p), + })} + /> + + + + ({ + '& .SidebarGallery-sidebar': { display: 'grid', rowGap: theme.spacings.sm }, + })} + > +
+ {isTypename(product, ['ConfigurableProduct', 'BundleProduct']) && ( + + }} + /> + + )} + + + ({ mb: theme.spacings.xs })} + product={product} + /> + +
+ + {isTypename(product, ['ConfigurableProduct']) && ( + + + + ), + }} + /> )} - - - - ({ mb: theme.spacings.xs })} product={product} /> - -
- - {isTypename(product, ['ConfigurableProduct']) && ( - - - - ), - }} - /> - )} - {isTypename(product, ['BundleProduct']) && ( - - )} - {isTypename(product, ['DownloadableProduct']) && ( - - )} - {!isTypename(product, ['GroupedProduct']) && } - - - - - - - - - - - - + {isTypename(product, ['BundleProduct']) && ( + + )} + {isTypename(product, ['DownloadableProduct']) && ( + + )} + {!isTypename(product, ['GroupedProduct']) && } + + - + + - + + + + + + - - - - + - -
+ - } - fontSize='responsive' - /> + + + + + + + + + } + fontSize='responsive' + /> +
{pages?.[0] && ( )} -
+ ) } From 2ff9d4e4b7ee7984abdfd3473faa2d23e945c2fe Mon Sep 17 00:00:00 2001 From: Carlo Carels Date: Thu, 31 Aug 2023 15:51:46 +0200 Subject: [PATCH 18/28] refactor: remove forceScrolled option --- .../Layout/components/LayoutHeader.tsx | 4 ---- .../Layout/components/LayoutHeaderContent.tsx | 21 +++---------------- 2 files changed, 3 insertions(+), 22 deletions(-) diff --git a/packages/next-ui/Layout/components/LayoutHeader.tsx b/packages/next-ui/Layout/components/LayoutHeader.tsx index 72a4d6df1a..008aef3429 100644 --- a/packages/next-ui/Layout/components/LayoutHeader.tsx +++ b/packages/next-ui/Layout/components/LayoutHeader.tsx @@ -28,8 +28,6 @@ export type LayoutHeaderProps = FloatingProps & sx?: SxProps hideBackButton?: boolean - - forceScrolled?: boolean } type ComponentStyleProps = { @@ -55,7 +53,6 @@ export function LayoutHeader(props: LayoutHeaderProps) { secondary, noAlign = false, switchPoint, - forceScrolled, size = 'responsive', sx = [], bgColor, @@ -156,7 +153,6 @@ export function LayoutHeader(props: LayoutHeaderProps) { floatingMd={floatingMd} floatingSm={floatingSm} switchPoint={switchPoint} - forceScrolled={forceScrolled} bgColor={bgColor} > {children} diff --git a/packages/next-ui/Layout/components/LayoutHeaderContent.tsx b/packages/next-ui/Layout/components/LayoutHeaderContent.tsx index 7dec683abb..475e9320de 100644 --- a/packages/next-ui/Layout/components/LayoutHeaderContent.tsx +++ b/packages/next-ui/Layout/components/LayoutHeaderContent.tsx @@ -14,7 +14,6 @@ export type LayoutHeaderContentProps = FloatingProps & { right?: React.ReactNode divider?: React.ReactNode switchPoint?: number - forceScrolled?: boolean sx?: SxProps sxBg?: SxProps layout?: LayoutProps['layout'] @@ -26,7 +25,6 @@ type OwnerState = { floatingSm: boolean floatingMd: boolean scrolled: boolean - forcedScrolled: boolean divider: boolean size: 'small' | 'responsive' bgColor?: 'paper' | 'default' @@ -47,7 +45,6 @@ export function LayoutHeaderContent(props: LayoutHeaderContentProps) { floatingMd = false, floatingSm = false, switchPoint = 50, - forceScrolled = false, sx = [], sxBg = [], layout, @@ -59,13 +56,10 @@ export function LayoutHeaderContent(props: LayoutHeaderContentProps) { const scroll = useScrollY() const scrolled = useMotionValueValue(scroll, (y) => y >= switchPoint) - const forcedScrolled = forceScrolled - const classes = withState({ floatingSm, floatingMd, - scrolled: !forceScrolled, - forcedScrolled, + scrolled, divider: !!divider, size, bgColor, @@ -106,11 +100,7 @@ export function LayoutHeaderContent(props: LayoutHeaderContentProps) { opacity: 0, transition: `opacity 150ms`, - '&.scrolled:not(.forcedScrolled)': { - opacity: 1, - }, - - '&.forceScrolled': { + '&.scrolled': { opacity: 1, }, @@ -196,12 +186,7 @@ export function LayoutHeaderContent(props: LayoutHeaderContentProps) { transition: `opacity 150ms`, opacity: 0, - '&.scrolled:not(.forcedScrolled)': { - opacity: 1, - '& > *': { pointerEvents: 'all' }, - }, - - '&.forceScrolled': { + '&.scrolled': { opacity: 1, '& > *': { pointerEvents: 'all' }, }, From c8b86a7e475476bb3b68086de3583f6617bca3c3 Mon Sep 17 00:00:00 2001 From: Carlo Carels Date: Thu, 31 Aug 2023 15:56:09 +0200 Subject: [PATCH 19/28] chore(GCOM-1194): calculate the cartButtonRef offset to fill the switchPoint value --- .../StickyAddToCart/StickyAddToCart.tsx | 251 +++++++++--------- 1 file changed, 123 insertions(+), 128 deletions(-) diff --git a/packages/magento-product/components/StickyAddToCart/StickyAddToCart.tsx b/packages/magento-product/components/StickyAddToCart/StickyAddToCart.tsx index d50f9eb0fc..42671bc54b 100644 --- a/packages/magento-product/components/StickyAddToCart/StickyAddToCart.tsx +++ b/packages/magento-product/components/StickyAddToCart/StickyAddToCart.tsx @@ -1,163 +1,158 @@ import { Image } from '@graphcommerce/image' -import { LayoutHeader } from '@graphcommerce/next-ui' +import { LayoutHeader, responsiveVal } from '@graphcommerce/next-ui' import { Box, Typography } from '@mui/material' import { useEffect, useState } from 'react' -import { ProductListItemFragment } from '../../Api/ProductListItem.gql' import { AddProductsToCartButton, AddProductsToCartError } from '../AddProductsToCart' -import { UseAddProductsToCartActionFragment } from '../AddProductsToCart/UseAddProductsToCartAction.gql' -import { ProductPageAddToCartQuantityRow } from '../ProductPage/ProductPageAddToCartRow' -import { ProductPageGalleryFragment } from '../ProductPageGallery/ProductPageGallery.gql' +import { + ProductPageAddToCartQuantityRow, + ProductPageAddToCartRowProps, +} from '../ProductPage/ProductPageAddToCartRow' import { ProductPageName } from '../ProductPageName' import { ProductPagePrice } from '../ProductPagePrice' -type StickyAddToCartProps = { - product: UseAddProductsToCartActionFragment & ProductPageGalleryFragment & ProductListItemFragment - cartButtonRef?: React.RefObject +export type StickyAddToCartProps = Partial & { + cartButtonRef?: React.RefObject } export function StickyAddToCart(props: StickyAddToCartProps) { const { product, cartButtonRef } = props + const mainImage = product?.media_gallery?.[0] - const [isButtonInViewport, setIsButtonInViewport] = useState(false) + const [switchPointPosition, setSwitchPointPosition] = useState() useEffect(() => { - if (!cartButtonRef || !cartButtonRef.current) return + if (!cartButtonRef?.current) return () => {} - const options = { - root: null, - rootMargin: '0px', - threshold: 0, - } + 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 - const observer = new IntersectionObserver((entries) => { - entries.forEach((entry) => { - if (entry.isIntersecting) { - setIsButtonInViewport(true) - } else { - setIsButtonInViewport(false) - } - }) - }, options) + // Set the switchPoint value to the calculated absolute position + setSwitchPointPosition(cartButtonAbsolutePosition) + } + } - observer.observe(cartButtonRef.current) + window.addEventListener('resize', handleResize) + handleResize() - // eslint-disable-next-line consistent-return return () => { - observer.disconnect() + window.removeEventListener('resize', handleResize) } - }, [cartButtonRef, isButtonInViewport]) - - if (!product?.sku || !product.url_key) return null + }, [cartButtonRef]) return ( - ({ - width: { md: '60vw', lg: '65vw' }, - inset: 0, - margin: 'auto', - transform: { - xs: 'none', - md: `translateY(calc(${theme.appShell.appBarHeightMd} * .28))`, - }, - - [theme.breakpoints.down('md')]: { - height: `calc(${theme.appShell.headerHeightSm} + 15px)`, - mt: 0, - }, - - '& .LayoutHeaderContent-content': { - display: 'flex', + product && + switchPointPosition && ( + ({ + width: { md: '60vw', lg: '65vw' }, inset: 0, margin: 'auto', - px: '12px', - }, + transform: { + xs: 'none', + md: `translateY(${theme.appShell.appBarHeightMd})`, + }, + + [theme.breakpoints.down('md')]: { + height: `calc(${theme.appShell.headerHeightSm} + 15px)`, + mt: 0, + }, + + '& .LayoutHeaderContent-content': { + display: 'flex', + inset: 0, + margin: 'auto', + px: '12px', + }, - '& .LayoutHeaderContent-center': { - display: 'flex', - width: '100%', - }, + '& .LayoutHeaderContent-center': { + display: 'flex', + width: '100%', + }, - '& .LayoutHeaderContent-center > div': { - width: '100%', - }, + '& .LayoutHeaderContent-center > div': { + width: '100%', + }, - '& .LayoutHeaderContent-bg': { - borderRadius: { xs: 0, md: theme.shape.borderRadius }, - boxShadow: theme.shadows[6], + '& .LayoutHeaderContent-bg': { + borderRadius: { xs: 0, md: theme.shape.borderRadius }, + boxShadow: theme.shadows[6], - [theme.breakpoints.down('md')]: { - height: `calc(${theme.appShell.headerHeightSm} + 15px)`, + [theme.breakpoints.down('md')]: { + height: `calc(${theme.appShell.headerHeightSm} + 15px)`, + }, }, - }, - - '& .LayoutHeaderContent-right': { - display: 'none', - }, - })} - > - theme.spacings.sm, - }} + + '& .LayoutHeaderContent-right': { + display: 'none', + }, + })} > - {mainImage?.url && ( - {mainImage.label ({ - verticalAlign: 'middle', - [theme.breakpoints.down('md')]: { - width: 46, - height: 46, - }, - })} - /> - )} - - ({ - fontSize: { xs: theme.typography.h5.fontSize, md: theme.typography.h4.fontSize }, - fontWeight: 'bold', - overflow: 'hidden', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', - })} + theme.spacings.sm, + }} > - - - - - - - - - - - - - + /> + )} + ({ + fontSize: { xs: theme.typography.h5.fontSize, md: theme.typography.h4.fontSize }, + fontWeight: 'bold', + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + })} + > + + + + + + + + + + + + + ) ) } From 5a2f1d774a3b57d9ca9085fffadd51ddfb12928d Mon Sep 17 00:00:00 2001 From: Carlo Carels Date: Thu, 31 Aug 2023 17:09:25 +0200 Subject: [PATCH 20/28] chore(GCOM-1194): add StickyAddToCartDestination when the enableStickyAddToCart option is enabled --- .../AddProductsToCart/AddProductsToCartForm.tsx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/magento-product/components/AddProductsToCart/AddProductsToCartForm.tsx b/packages/magento-product/components/AddProductsToCart/AddProductsToCartForm.tsx index b05004b4e5..1e901080f5 100644 --- a/packages/magento-product/components/AddProductsToCart/AddProductsToCartForm.tsx +++ b/packages/magento-product/components/AddProductsToCart/AddProductsToCartForm.tsx @@ -143,6 +143,22 @@ export function AddProductsToCartForm(props: AddProductsToCartFormProps) { value={useMemo(() => ({ ...form, redirect }), [form, redirect])} > + {import.meta.graphCommerce.enableStickyAddToCart && ( + ({ + position: { xs: 'sticky', md: 'absolute' }, + height: '100%', + width: '100%', + margin: 'auto', + inset: 0, + top: { xs: theme.appShell.headerHeightSm, md: 0 }, + zIndex: '1', + pointerEvents: 'none', + '& > *': { pointerEvents: 'all' }, + })} + id='StickyAddToCartDestination' + /> + )} {children} {disableSuccessSnackbar ? null : ( From 4284880daa580e243dd5ce48263c1732a78a6e2f Mon Sep 17 00:00:00 2001 From: Carlo Carels Date: Thu, 31 Aug 2023 17:09:45 +0200 Subject: [PATCH 21/28] chore(GCOM-1194): add transform animation --- .../StickyAddToCart/StickyAddToCart.tsx | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/packages/magento-product/components/StickyAddToCart/StickyAddToCart.tsx b/packages/magento-product/components/StickyAddToCart/StickyAddToCart.tsx index 42671bc54b..203a754c6d 100644 --- a/packages/magento-product/components/StickyAddToCart/StickyAddToCart.tsx +++ b/packages/magento-product/components/StickyAddToCart/StickyAddToCart.tsx @@ -58,7 +58,7 @@ export function StickyAddToCart(props: StickyAddToCartProps) { margin: 'auto', transform: { xs: 'none', - md: `translateY(${theme.appShell.appBarHeightMd})`, + md: `translateY(calc(${theme.appShell.appBarHeightMd} - ${theme.appShell.headerHeightSm} - 10px))`, }, [theme.breakpoints.down('md')]: { @@ -73,11 +73,6 @@ export function StickyAddToCart(props: StickyAddToCartProps) { px: '12px', }, - '& .LayoutHeaderContent-center': { - display: 'flex', - width: '100%', - }, - '& .LayoutHeaderContent-center > div': { width: '100%', }, @@ -85,12 +80,31 @@ export function StickyAddToCart(props: StickyAddToCartProps) { '& .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', }, From 1fbe69ffc89496a9cba4ac022441c8e39eeb9fe2 Mon Sep 17 00:00:00 2001 From: Carlo Carels Date: Thu, 31 Aug 2023 17:10:22 +0200 Subject: [PATCH 22/28] chore(GCOM-1194): inject missing StickyAddToCart data to the UseAddProductsToCartAction fragment --- packages/magento-product/graphql/StickyAddToCart.graphql | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 packages/magento-product/graphql/StickyAddToCart.graphql diff --git a/packages/magento-product/graphql/StickyAddToCart.graphql b/packages/magento-product/graphql/StickyAddToCart.graphql new file mode 100644 index 0000000000..18e526e1d9 --- /dev/null +++ b/packages/magento-product/graphql/StickyAddToCart.graphql @@ -0,0 +1,4 @@ +fragment StickyAddToCart on ProductInterface @inject(into: ["UseAddProductsToCartAction"]) { + ...ProductPageGallery + ...ProductPagePrice +} From 623f91140a974786a01430304fec89457ba4f396 Mon Sep 17 00:00:00 2001 From: Carlo Carels Date: Thu, 31 Aug 2023 17:10:35 +0200 Subject: [PATCH 23/28] refactor: remove unused plugin --- .../StickyAddProductsToCartButton.tsx | 32 ------------------- 1 file changed, 32 deletions(-) delete mode 100644 packages/magento-product/plugins/StickyAddToCart/StickyAddProductsToCartButton.tsx diff --git a/packages/magento-product/plugins/StickyAddToCart/StickyAddProductsToCartButton.tsx b/packages/magento-product/plugins/StickyAddToCart/StickyAddProductsToCartButton.tsx deleted file mode 100644 index 71ce1b7fcd..0000000000 --- a/packages/magento-product/plugins/StickyAddToCart/StickyAddProductsToCartButton.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import type { IfConfig, ReactPlugin } from '@graphcommerce/next-config' -import { Button } from '@graphcommerce/next-ui' -import { Trans } from '@lingui/react' -import { type AddProductsToCartButton, useAddProductsToCartAction } from '../../components' - -export const component = 'AddProductsToCartButton' -export const exported = - '@graphcommerce/magento-product/components/AddProductsToCart/AddProductsToCartButton' -export const ifConfig: IfConfig = 'enableStickyAddToCart' - -type PluginType = ReactPlugin - -const StickyAddProductsToCartButton: PluginType = (props) => { - const { children, product, forwardedRef, ...rest } = props - const action = useAddProductsToCartAction(props) - - return ( - - ) -} - -export const Plugin = StickyAddProductsToCartButton From f84716cf43c8fe913281e7b2c4eda4667016d782 Mon Sep 17 00:00:00 2001 From: Carlo Carels Date: Thu, 31 Aug 2023 17:11:46 +0200 Subject: [PATCH 24/28] chore(GCOM-1194): create plugin sends the cartButtonRef within the createPortal to calculate if the button is in viewport --- .../StickyProductPageAddToCartRow.tsx | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 packages/magento-product/plugins/StickyAddToCart/StickyProductPageAddToCartRow.tsx diff --git a/packages/magento-product/plugins/StickyAddToCart/StickyProductPageAddToCartRow.tsx b/packages/magento-product/plugins/StickyAddToCart/StickyProductPageAddToCartRow.tsx new file mode 100644 index 0000000000..e5aba70310 --- /dev/null +++ b/packages/magento-product/plugins/StickyAddToCart/StickyProductPageAddToCartRow.tsx @@ -0,0 +1,39 @@ +import type { IfConfig, ReactPlugin } from '@graphcommerce/next-config' +import { Box } from '@mui/material' +import { useLayoutEffect, 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 + +const StickyProductPageAddToCartRow: PluginType = (props) => { + const { Prev, children, product, ...rest } = props + + const cartButtonRef = useRef(null) + const [target, setTarget] = useState(null) + + const stickyAddToCart = + + useLayoutEffect(() => { + const stickyTarget = globalThis?.document?.getElementById('StickyAddToCartDestination') + if (stickyTarget) { + setTarget(stickyTarget) + } + }, []) + + return ( + + {target && createPortal(stickyAddToCart, target)} + + {children} + + + ) +} + +export const Plugin = StickyProductPageAddToCartRow From 15a5b9659b48aa6bba85090f1d642903d023cebc Mon Sep 17 00:00:00 2001 From: Carlo Carels Date: Thu, 31 Aug 2023 17:18:28 +0200 Subject: [PATCH 25/28] =?UTF-8?q?chore:=20hide=20product=20name=20on=20mob?= =?UTF-8?q?ile,=20since=20it=E2=80=99s=20duplicated?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/StickyAddToCart/StickyAddToCart.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/magento-product/components/StickyAddToCart/StickyAddToCart.tsx b/packages/magento-product/components/StickyAddToCart/StickyAddToCart.tsx index 203a754c6d..2b1ec51add 100644 --- a/packages/magento-product/components/StickyAddToCart/StickyAddToCart.tsx +++ b/packages/magento-product/components/StickyAddToCart/StickyAddToCart.tsx @@ -135,6 +135,7 @@ export function StickyAddToCart(props: StickyAddToCartProps) { ({ + display: { xs: 'none', md: 'block' }, fontSize: { xs: theme.typography.h5.fontSize, md: theme.typography.h4.fontSize }, fontWeight: 'bold', overflow: 'hidden', From fd5714a3d0ca59977fa47b89c4dc1b9bdc2814c0 Mon Sep 17 00:00:00 2001 From: Carlo Carels Date: Fri, 1 Sep 2023 09:43:38 +0200 Subject: [PATCH 26/28] refactor: replace useLayoutEffect for useEffect + move createPortal outside the Box component. --- .../StickyProductPageAddToCartRow.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/magento-product/plugins/StickyAddToCart/StickyProductPageAddToCartRow.tsx b/packages/magento-product/plugins/StickyAddToCart/StickyProductPageAddToCartRow.tsx index e5aba70310..6f390dd2c2 100644 --- a/packages/magento-product/plugins/StickyAddToCart/StickyProductPageAddToCartRow.tsx +++ b/packages/magento-product/plugins/StickyAddToCart/StickyProductPageAddToCartRow.tsx @@ -1,6 +1,6 @@ import type { IfConfig, ReactPlugin } from '@graphcommerce/next-config' import { Box } from '@mui/material' -import { useLayoutEffect, useRef, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import { createPortal } from 'react-dom' import { ProductPageAddToCartRow, StickyAddToCart } from '../../components' @@ -19,7 +19,7 @@ const StickyProductPageAddToCartRow: PluginType = (props) => { const stickyAddToCart = - useLayoutEffect(() => { + useEffect(() => { const stickyTarget = globalThis?.document?.getElementById('StickyAddToCartDestination') if (stickyTarget) { setTarget(stickyTarget) @@ -27,12 +27,14 @@ const StickyProductPageAddToCartRow: PluginType = (props) => { }, []) return ( - + <> {target && createPortal(stickyAddToCart, target)} - - {children} - - + + + {children} + + + ) } From 4c8001e3c30e3fd9b8f5d9cb0bf9c62ecb39faf3 Mon Sep 17 00:00:00 2001 From: Carlo Carels Date: Fri, 1 Sep 2023 10:23:05 +0200 Subject: [PATCH 27/28] chore: add changeset --- .changeset/famous-queens-give.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .changeset/famous-queens-give.md diff --git a/.changeset/famous-queens-give.md b/.changeset/famous-queens-give.md new file mode 100644 index 0000000000..1a576d1882 --- /dev/null +++ b/.changeset/famous-queens-give.md @@ -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. From e81ffd578cf8bd4dcf23cf1efb1c09310b1e6001 Mon Sep 17 00:00:00 2001 From: Carlo Carels Date: Thu, 9 Nov 2023 20:56:16 +0100 Subject: [PATCH 28/28] refactor: remove forwardedRef and duplicated hook --- .../components/AddProductsToCart/AddProductsToCartButton.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/magento-product/components/AddProductsToCart/AddProductsToCartButton.tsx b/packages/magento-product/components/AddProductsToCart/AddProductsToCartButton.tsx index 8ada32e0be..0af4411ea8 100644 --- a/packages/magento-product/components/AddProductsToCart/AddProductsToCartButton.tsx +++ b/packages/magento-product/components/AddProductsToCart/AddProductsToCartButton.tsx @@ -20,9 +20,8 @@ export type AddProductsToCartButtonProps = UseAddProductsToCartActionProps & > export function AddProductsToCartButton(props: AddProductsToCartButtonProps) { - const { children, product, forwardedRef, ...rest } = props + const { children, product, ...rest } = props const { showSuccess, ...action } = useAddProductsToCartAction(props) - const action = useAddProductsToCartAction(props) return (