From 27e0120a102e04217ed766093277722a9665fd8f Mon Sep 17 00:00:00 2001 From: ErwinOtten Date: Tue, 14 Feb 2023 10:14:50 +0100 Subject: [PATCH 01/40] initial --- .../components/ScrollerThumbnail.tsx | 69 +++++++++++++++++++ .../components/ScrollerThumbnails.tsx | 54 +++++++++++++++ packages/framer-scroller/index.ts | 2 + .../next-ui/FramerScroller/SidebarGallery.tsx | 4 +- 4 files changed, 127 insertions(+), 2 deletions(-) create mode 100644 packages/framer-scroller/components/ScrollerThumbnail.tsx create mode 100644 packages/framer-scroller/components/ScrollerThumbnails.tsx diff --git a/packages/framer-scroller/components/ScrollerThumbnail.tsx b/packages/framer-scroller/components/ScrollerThumbnail.tsx new file mode 100644 index 0000000000..b0e0aea7ed --- /dev/null +++ b/packages/framer-scroller/components/ScrollerThumbnail.tsx @@ -0,0 +1,69 @@ +import { useMotionValueValue } from '@graphcommerce/framer-utils' +import { Image } from '@graphcommerce/image' +import { extendableComponent } from '@graphcommerce/next-ui/Styles' +import { i18n } from '@lingui/core' +import { Fab, ButtonProps, styled, Button } from '@mui/material' +import { m } from 'framer-motion' +import { useScrollTo } from '../hooks/useScrollTo' +import { useScrollerContext } from '../hooks/useScrollerContext' +import { ItemState } from '../types' + +const name = 'ScrollerThumbnail' +const parts = ['thumbnail'] as const +type OwnerProps = { active: boolean } + +const { withState } = extendableComponent(name, parts) + +type ScrollerThumbnailProps = Omit & + ItemState & { idx: number; image: any } + +const MotionBox = styled(m.div)({}) + +export function ScrollerThumbnail(props: ScrollerThumbnailProps) { + const { el, visibility, opacity, idx, image, ...buttonProps } = props + const scrollTo = useScrollTo() + const { getScrollSnapPositions } = useScrollerContext() + + const active = useMotionValueValue(visibility, (v) => v > 0.5) + const classes = withState({ active }) + + if (!image) return null + return ( + + ) +} diff --git a/packages/framer-scroller/components/ScrollerThumbnails.tsx b/packages/framer-scroller/components/ScrollerThumbnails.tsx new file mode 100644 index 0000000000..45d2252beb --- /dev/null +++ b/packages/framer-scroller/components/ScrollerThumbnails.tsx @@ -0,0 +1,54 @@ +import { useMotionValueValue } from '@graphcommerce/framer-utils' +import { Scroller, ScrollerButton, ScrollerProvider } from '@graphcommerce/framer-scroller' +import { extendableComponent } from '@graphcommerce/next-ui/Styles' +import { Box, ButtonProps, styled, SxProps, Theme } from '@mui/material' +import { m } from 'framer-motion' +import React from 'react' +import { useScrollerContext } from '../hooks/useScrollerContext' +import { ScrollerThumbnail } from './ScrollerThumbnail' + +const MotionBox = styled(m.div)({}) + +export type DotsProps = { + buttonProps?: Omit + images: any + sx?: SxProps +} + +const componentName = 'ScrollerThumbnails' +const { classes } = extendableComponent(componentName, ['root', 'dot', 'circle'] as const) + +export const ScrollerThumbnails = m( + React.forwardRef((props, ref) => { + const { buttonProps, images, sx = [], ...containerProps } = props + + const { items } = useScrollerContext() + const itemsArr = useMotionValueValue(items, (v) => v) + + if (itemsArr.length <= 1) return null + + return ( + + {itemsArr.map((item, idx) => ( + // eslint-disable-next-line react/no-array-index-key + + ))} + + ) + }), +) +ScrollerThumbnails.displayName = componentName diff --git a/packages/framer-scroller/index.ts b/packages/framer-scroller/index.ts index 9780ce6628..4f15f14924 100644 --- a/packages/framer-scroller/index.ts +++ b/packages/framer-scroller/index.ts @@ -7,6 +7,8 @@ export * from './components/ScrollerButton' export * from './components/ScrollerDots' export * from './components/ScrollerPageCounter' export * from './components/ScrollerProvider' +export * from './components/ScrollerThumbnails' +export * from './components/ScrollerThumbnail' export * from './hooks/useScrollerContext' // export * from './hooks/useScroller' diff --git a/packages/next-ui/FramerScroller/SidebarGallery.tsx b/packages/next-ui/FramerScroller/SidebarGallery.tsx index a2ee228667..e0df32d1ae 100644 --- a/packages/next-ui/FramerScroller/SidebarGallery.tsx +++ b/packages/next-ui/FramerScroller/SidebarGallery.tsx @@ -4,10 +4,10 @@ import { MotionImageAspectProps, Scroller, ScrollerButton, - ScrollerButtonProps, - ScrollerDots, ScrollerProvider, unstable_usePreventScroll as usePreventScroll, + ScrollerButtonProps, + ScrollerDots, } from '@graphcommerce/framer-scroller' import { dvh } from '@graphcommerce/framer-utils' import { From 426a83c184e71d42eb966763b8abd6e78a299c2e Mon Sep 17 00:00:00 2001 From: ErwinOtten Date: Wed, 15 Feb 2023 10:56:49 +0100 Subject: [PATCH 02/40] Image types --- .../framer-scroller/components/ScrollerThumbnail.tsx | 4 ++-- .../framer-scroller/components/ScrollerThumbnails.tsx | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/framer-scroller/components/ScrollerThumbnail.tsx b/packages/framer-scroller/components/ScrollerThumbnail.tsx index b0e0aea7ed..2a98b25ad8 100644 --- a/packages/framer-scroller/components/ScrollerThumbnail.tsx +++ b/packages/framer-scroller/components/ScrollerThumbnail.tsx @@ -1,5 +1,5 @@ import { useMotionValueValue } from '@graphcommerce/framer-utils' -import { Image } from '@graphcommerce/image' +import { Image, ImageProps } from '@graphcommerce/image' import { extendableComponent } from '@graphcommerce/next-ui/Styles' import { i18n } from '@lingui/core' import { Fab, ButtonProps, styled, Button } from '@mui/material' @@ -15,7 +15,7 @@ type OwnerProps = { active: boolean } const { withState } = extendableComponent(name, parts) type ScrollerThumbnailProps = Omit & - ItemState & { idx: number; image: any } + ItemState & { idx: number; image: Pick } const MotionBox = styled(m.div)({}) diff --git a/packages/framer-scroller/components/ScrollerThumbnails.tsx b/packages/framer-scroller/components/ScrollerThumbnails.tsx index 45d2252beb..a6f5543d86 100644 --- a/packages/framer-scroller/components/ScrollerThumbnails.tsx +++ b/packages/framer-scroller/components/ScrollerThumbnails.tsx @@ -1,5 +1,5 @@ import { useMotionValueValue } from '@graphcommerce/framer-utils' -import { Scroller, ScrollerButton, ScrollerProvider } from '@graphcommerce/framer-scroller' +import { ImageProps } from '@graphcommerce/image' import { extendableComponent } from '@graphcommerce/next-ui/Styles' import { Box, ButtonProps, styled, SxProps, Theme } from '@mui/material' import { m } from 'framer-motion' @@ -9,17 +9,17 @@ import { ScrollerThumbnail } from './ScrollerThumbnail' const MotionBox = styled(m.div)({}) -export type DotsProps = { +export type ThumbnailsProps = { buttonProps?: Omit - images: any sx?: SxProps + images: Pick[] } const componentName = 'ScrollerThumbnails' -const { classes } = extendableComponent(componentName, ['root', 'dot', 'circle'] as const) +const { classes } = extendableComponent(componentName, ['root'] as const) export const ScrollerThumbnails = m( - React.forwardRef((props, ref) => { + React.forwardRef((props, ref) => { const { buttonProps, images, sx = [], ...containerProps } = props const { items } = useScrollerContext() From e359b9b334d9f7d7f08b7ba43281ff0886e0b122 Mon Sep 17 00:00:00 2001 From: ErwinOtten Date: Wed, 15 Feb 2023 12:04:40 +0100 Subject: [PATCH 03/40] Pass galleryNavigation as prop --- .../ProductPageGallery/ProductPageGallery.tsx | 29 ++++++++++--------- .../next-ui/FramerScroller/SidebarGallery.tsx | 6 ++++ 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/packages/magento-product/components/ProductPageGallery/ProductPageGallery.tsx b/packages/magento-product/components/ProductPageGallery/ProductPageGallery.tsx index f488e30343..a826c68c79 100644 --- a/packages/magento-product/components/ProductPageGallery/ProductPageGallery.tsx +++ b/packages/magento-product/components/ProductPageGallery/ProductPageGallery.tsx @@ -1,3 +1,4 @@ +import { ScrollerDots } from '@graphcommerce/framer-scroller' import { nonNullable, SidebarGallery, @@ -19,24 +20,26 @@ export function ProductPageGallery(props: ProductPageGalleryProps) { const { product, children, aspectRatio: [width, height] = [1532, 1678], ...sidebarProps } = props const { media_gallery } = product + const images = + media_gallery + ?.filter(nonNullable) + .sort((a, b) => (a.position ?? 0) - (b.position ?? 0)) + .map((item) => { + if (item.__typename === 'ProductImage') + return { src: item.url ?? '', alt: item.label || undefined, width, height } + return { + src: '', + alt: `{${item.__typename} not yet supported}`, + } + }) ?? [] + return ( (a.position ?? 0) - (b.position ?? 0)) - .map((item) => { - if (item.__typename === 'ProductImage') - return { src: item.url ?? '', alt: item.label || undefined, width, height } - return { - src: '', - alt: `{${item.__typename} not yet supported}`, - } - }) ?? [] - } + images={images} + galleryNavigation={} /> ) } diff --git a/packages/next-ui/FramerScroller/SidebarGallery.tsx b/packages/next-ui/FramerScroller/SidebarGallery.tsx index e0df32d1ae..f6c77b2d63 100644 --- a/packages/next-ui/FramerScroller/SidebarGallery.tsx +++ b/packages/next-ui/FramerScroller/SidebarGallery.tsx @@ -55,6 +55,7 @@ const { withState, selectors } = extendableComponent @@ -65,6 +66,7 @@ export function SidebarGallery(props: SidebarGalleryProps) { const { sidebar, images, + galleryNavigation, aspectRatio: [width, height] = [1, 1], sx, routeHash = 'gallery', @@ -110,6 +112,10 @@ export function SidebarGallery(props: SidebarGalleryProps) { if (zoomed && (e as KeyboardEvent)?.key === 'Escape') toggle() } + const navigation = React.cloneElement(galleryNavigation, { + layoutDependency: zoomed, + }) + const dragStart = useMotionValue(0) const onMouseDownScroller: React.MouseEventHandler = (e) => { if (dragStart.get() === e.clientX) return From 154a295eb1a3f090e0eaed63661c643066312cfb Mon Sep 17 00:00:00 2001 From: ErwinOtten Date: Thu, 16 Feb 2023 11:55:57 +0100 Subject: [PATCH 04/40] Plugin --- packages/demo-magento-graphcommerce/package.json | 2 +- .../plugins/demo/DemoSidebarGallery.tsx | 15 +++++++++++++++ .../components/ScrollerThumbnails.tsx | 3 ++- .../next-ui/FramerScroller/SidebarGallery.tsx | 8 +------- 4 files changed, 19 insertions(+), 9 deletions(-) create mode 100644 packages/demo-magento-graphcommerce/plugins/demo/DemoSidebarGallery.tsx diff --git a/packages/demo-magento-graphcommerce/package.json b/packages/demo-magento-graphcommerce/package.json index 1dba0df189..48c577862b 100644 --- a/packages/demo-magento-graphcommerce/package.json +++ b/packages/demo-magento-graphcommerce/package.json @@ -29,4 +29,4 @@ "@graphcommerce/magento-product": "7.1.0-canary.24", "@graphcommerce/magento-product-configurable": "7.1.0-canary.24" } -} +} \ No newline at end of file diff --git a/packages/demo-magento-graphcommerce/plugins/demo/DemoSidebarGallery.tsx b/packages/demo-magento-graphcommerce/plugins/demo/DemoSidebarGallery.tsx new file mode 100644 index 0000000000..58c3e2f05e --- /dev/null +++ b/packages/demo-magento-graphcommerce/plugins/demo/DemoSidebarGallery.tsx @@ -0,0 +1,15 @@ +import { ScrollerThumbnails } from '@graphcommerce/framer-scroller' +import type { SidebarGalleryProps } from '@graphcommerce/next-ui/FramerScroller/SidebarGallery' +import type { PluginProps } from '@graphcommerce/next-config' +import { useCallback } from 'react' + +export const component = 'SidebarGallery' +export const exported = '@graphcommerce/next-ui' +export const ifEnv = 'DEMO_MAGENTO_GRAPHCOMMERCE' + +function DemoSidebarGallery(props: PluginProps) { + const { Prev, ...rest } = props + const navigation = useCallback(() => , [rest.images]) + return +} +export const Plugin = DemoSidebarGallery diff --git a/packages/framer-scroller/components/ScrollerThumbnails.tsx b/packages/framer-scroller/components/ScrollerThumbnails.tsx index a6f5543d86..114ae8c10a 100644 --- a/packages/framer-scroller/components/ScrollerThumbnails.tsx +++ b/packages/framer-scroller/components/ScrollerThumbnails.tsx @@ -5,6 +5,7 @@ import { Box, ButtonProps, styled, SxProps, Theme } from '@mui/material' import { m } from 'framer-motion' import React from 'react' import { useScrollerContext } from '../hooks/useScrollerContext' +import { Scroller } from './Scroller' import { ScrollerThumbnail } from './ScrollerThumbnail' const MotionBox = styled(m.div)({}) @@ -38,7 +39,7 @@ export const ScrollerThumbnails = m( maxWidth: '100%', py: 0, display: 'flex', - gap: { xs: '5px', sm: '10px', md: '15px' }, + gap: { xs: '10px', sm: '15px', md: '20px' }, }, ...(Array.isArray(sx) ? sx : [sx]), ]} diff --git a/packages/next-ui/FramerScroller/SidebarGallery.tsx b/packages/next-ui/FramerScroller/SidebarGallery.tsx index f6c77b2d63..53e968e75f 100644 --- a/packages/next-ui/FramerScroller/SidebarGallery.tsx +++ b/packages/next-ui/FramerScroller/SidebarGallery.tsx @@ -3,11 +3,11 @@ import { MotionImageAspect, MotionImageAspectProps, Scroller, + ScrollerDots, ScrollerButton, ScrollerProvider, unstable_usePreventScroll as usePreventScroll, ScrollerButtonProps, - ScrollerDots, } from '@graphcommerce/framer-scroller' import { dvh } from '@graphcommerce/framer-utils' import { @@ -55,7 +55,6 @@ const { withState, selectors } = extendableComponent @@ -66,7 +65,6 @@ export function SidebarGallery(props: SidebarGalleryProps) { const { sidebar, images, - galleryNavigation, aspectRatio: [width, height] = [1, 1], sx, routeHash = 'gallery', @@ -112,10 +110,6 @@ export function SidebarGallery(props: SidebarGalleryProps) { if (zoomed && (e as KeyboardEvent)?.key === 'Escape') toggle() } - const navigation = React.cloneElement(galleryNavigation, { - layoutDependency: zoomed, - }) - const dragStart = useMotionValue(0) const onMouseDownScroller: React.MouseEventHandler = (e) => { if (dragStart.get() === e.clientX) return From fbd34a3b3822eef6336572b62d9af0390b09fb60 Mon Sep 17 00:00:00 2001 From: ErwinOtten Date: Thu, 16 Feb 2023 12:04:14 +0100 Subject: [PATCH 05/40] relocate plugin --- packages/demo-magento-graphcommerce/package.json | 6 ++++-- .../plugins/GalleryThumbnailNavigation.tsx} | 10 +++++----- 2 files changed, 9 insertions(+), 7 deletions(-) rename packages/{demo-magento-graphcommerce/plugins/demo/DemoSidebarGallery.tsx => framer-scroller/plugins/GalleryThumbnailNavigation.tsx} (66%) diff --git a/packages/demo-magento-graphcommerce/package.json b/packages/demo-magento-graphcommerce/package.json index 48c577862b..dfb17cffa9 100644 --- a/packages/demo-magento-graphcommerce/package.json +++ b/packages/demo-magento-graphcommerce/package.json @@ -26,7 +26,9 @@ "react-dom": "^18.2.0" }, "dependencies": { - "@graphcommerce/magento-product": "7.1.0-canary.24", - "@graphcommerce/magento-product-configurable": "7.1.0-canary.24" + "@graphcommerce/next-ui": "7.0.2-canary.5", + "@graphcommerce/framer-scroller": "7.0.2-canary.5", + "@graphcommerce/magento-product": "7.0.2-canary.5", + "@graphcommerce/magento-product-configurable": "7.0.2-canary.5" } } \ No newline at end of file diff --git a/packages/demo-magento-graphcommerce/plugins/demo/DemoSidebarGallery.tsx b/packages/framer-scroller/plugins/GalleryThumbnailNavigation.tsx similarity index 66% rename from packages/demo-magento-graphcommerce/plugins/demo/DemoSidebarGallery.tsx rename to packages/framer-scroller/plugins/GalleryThumbnailNavigation.tsx index 58c3e2f05e..a743f38b13 100644 --- a/packages/demo-magento-graphcommerce/plugins/demo/DemoSidebarGallery.tsx +++ b/packages/framer-scroller/plugins/GalleryThumbnailNavigation.tsx @@ -1,15 +1,15 @@ -import { ScrollerThumbnails } from '@graphcommerce/framer-scroller' -import type { SidebarGalleryProps } from '@graphcommerce/next-ui/FramerScroller/SidebarGallery' import type { PluginProps } from '@graphcommerce/next-config' +import type { SidebarGalleryProps } from '@graphcommerce/next-ui/FramerScroller/SidebarGallery' import { useCallback } from 'react' +import { ScrollerThumbnails } from '../components/ScrollerThumbnails' export const component = 'SidebarGallery' export const exported = '@graphcommerce/next-ui' -export const ifEnv = 'DEMO_MAGENTO_GRAPHCOMMERCE' +export const ifEnv = 'GALLERY_THUMBNAILS' -function DemoSidebarGallery(props: PluginProps) { +function GalleryThumbnailNavigation(props: PluginProps) { const { Prev, ...rest } = props const navigation = useCallback(() => , [rest.images]) return } -export const Plugin = DemoSidebarGallery +export const Plugin = GalleryThumbnailNavigation From d80f85ed23b1439bf7ebfd9f297b3b76d9dedc9f Mon Sep 17 00:00:00 2001 From: ErwinOtten Date: Thu, 16 Feb 2023 12:12:36 +0100 Subject: [PATCH 06/40] GalleryNavigation optional --- .../components/ProductPageGallery/ProductPageGallery.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/magento-product/components/ProductPageGallery/ProductPageGallery.tsx b/packages/magento-product/components/ProductPageGallery/ProductPageGallery.tsx index a826c68c79..09d244a66c 100644 --- a/packages/magento-product/components/ProductPageGallery/ProductPageGallery.tsx +++ b/packages/magento-product/components/ProductPageGallery/ProductPageGallery.tsx @@ -39,7 +39,6 @@ export function ProductPageGallery(props: ProductPageGalleryProps) { sidebar={children} aspectRatio={[width, height]} images={images} - galleryNavigation={} /> ) } From 44c164a95657e34223095b6c13da092f3c61dada Mon Sep 17 00:00:00 2001 From: ErwinOtten Date: Thu, 16 Feb 2023 12:19:10 +0100 Subject: [PATCH 07/40] debug --- examples/magento-graphcms/public/manifest.webmanifest | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/magento-graphcms/public/manifest.webmanifest b/examples/magento-graphcms/public/manifest.webmanifest index 1ab2d2f0f3..29521ac190 100644 --- a/examples/magento-graphcms/public/manifest.webmanifest +++ b/examples/magento-graphcms/public/manifest.webmanifest @@ -1,6 +1,6 @@ { "name": "GraphCommerce", - "short_name": "GraphCommerce", + "short_name": "Graph Commerce", "display": "standalone", "theme_color": "#000000", "background_color": "#FFFFFF", From 8512d32d7c9e4d130ff7da8a458d326bb4b820db Mon Sep 17 00:00:00 2001 From: ErwinOtten Date: Thu, 16 Feb 2023 12:27:42 +0100 Subject: [PATCH 08/40] Revert "debug" This reverts commit 115ae2b5b0f0ec7af8c79cd1e4f27bf505119762. --- examples/magento-graphcms/public/manifest.webmanifest | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/magento-graphcms/public/manifest.webmanifest b/examples/magento-graphcms/public/manifest.webmanifest index 29521ac190..1ab2d2f0f3 100644 --- a/examples/magento-graphcms/public/manifest.webmanifest +++ b/examples/magento-graphcms/public/manifest.webmanifest @@ -1,6 +1,6 @@ { "name": "GraphCommerce", - "short_name": "Graph Commerce", + "short_name": "GraphCommerce", "display": "standalone", "theme_color": "#000000", "background_color": "#FFFFFF", From 122eb36a30683c7924a80be90eeb138ffbcc8dcc Mon Sep 17 00:00:00 2001 From: Mike Keehnen Date: Wed, 31 May 2023 15:22:19 +0200 Subject: [PATCH 09/40] Hover concept --- .../components/ScrollerThumbnail.tsx | 80 ++++++++++--------- .../components/ScrollerThumbnails.tsx | 17 +--- 2 files changed, 45 insertions(+), 52 deletions(-) diff --git a/packages/framer-scroller/components/ScrollerThumbnail.tsx b/packages/framer-scroller/components/ScrollerThumbnail.tsx index 2a98b25ad8..19bb04e5f9 100644 --- a/packages/framer-scroller/components/ScrollerThumbnail.tsx +++ b/packages/framer-scroller/components/ScrollerThumbnail.tsx @@ -1,8 +1,8 @@ import { useMotionValueValue } from '@graphcommerce/framer-utils' import { Image, ImageProps } from '@graphcommerce/image' +// eslint-disable-next-line import/no-extraneous-dependencies import { extendableComponent } from '@graphcommerce/next-ui/Styles' -import { i18n } from '@lingui/core' -import { Fab, ButtonProps, styled, Button } from '@mui/material' +import { ButtonProps, styled } from '@mui/material' import { m } from 'framer-motion' import { useScrollTo } from '../hooks/useScrollTo' import { useScrollerContext } from '../hooks/useScrollerContext' @@ -19,6 +19,8 @@ type ScrollerThumbnailProps = Omit & const MotionBox = styled(m.div)({}) +const imageDimensions = 120 + export function ScrollerThumbnail(props: ScrollerThumbnailProps) { const { el, visibility, opacity, idx, image, ...buttonProps } = props const scrollTo = useScrollTo() @@ -27,43 +29,47 @@ export function ScrollerThumbnail(props: ScrollerThumbnailProps) { const active = useMotionValueValue(visibility, (v) => v > 0.5) const classes = withState({ active }) + const imageAnimation = { + rest: { width: imageDimensions / 2 }, + hover: { + width: imageDimensions, + }, + } + + const spacingAnimation = { + rest: { width: imageDimensions / 2 }, + hover: { + width: imageDimensions + 20, + }, + } + if (!image) return null return ( - + ) } diff --git a/packages/framer-scroller/components/ScrollerThumbnails.tsx b/packages/framer-scroller/components/ScrollerThumbnails.tsx index 114ae8c10a..40cb079df1 100644 --- a/packages/framer-scroller/components/ScrollerThumbnails.tsx +++ b/packages/framer-scroller/components/ScrollerThumbnails.tsx @@ -6,6 +6,7 @@ import { m } from 'framer-motion' import React from 'react' import { useScrollerContext } from '../hooks/useScrollerContext' import { Scroller } from './Scroller' +import { ScrollerProvider } from './ScrollerProvider' import { ScrollerThumbnail } from './ScrollerThumbnail' const MotionBox = styled(m.div)({}) @@ -29,21 +30,7 @@ export const ScrollerThumbnails = m( if (itemsArr.length <= 1) return null return ( - + {itemsArr.map((item, idx) => ( // eslint-disable-next-line react/no-array-index-key From 942318934fe54aee1e023390ce495836cf8d4d03 Mon Sep 17 00:00:00 2001 From: Mike Keehnen Date: Tue, 13 Jun 2023 11:14:23 +0200 Subject: [PATCH 10/40] Porgress --- docs/framework/config.md | 182 +++++++++++------- .../components/ScrollerThumbnail.tsx | 87 ++++++--- .../components/ScrollerThumbnails.tsx | 2 +- 3 files changed, 169 insertions(+), 102 deletions(-) diff --git a/docs/framework/config.md b/docs/framework/config.md index f2a0c86f4c..2d461922a2 100644 --- a/docs/framework/config.md +++ b/docs/framework/config.md @@ -1,19 +1,28 @@ + # GraphCommerce configuration system -Global GraphCommerce configuration can be configured in your `graphcommerce.config.js` file -in the root of your project and are automatically validated on startup. +Global GraphCommerce configuration can be configured in your +`graphcommerce.config.js` file in the root of your project and are automatically +validated on startup. ## Configuring with the configuration file. -The configuration file is a javascript file that exports a `GraphCommerceConfig` object. See graphcommerce.config.js.example for an example. +The configuration file is a javascript file that exports a `GraphCommerceConfig` +object. See graphcommerce.config.js.example for an example. ## Using configuration -Configuration can be accessed in your project with the `import.meta.graphCommerce` object. +Configuration can be accessed in your project with the +`import.meta.graphCommerce` object. ```tsx -import { storefrontAll, storefrontConfig, storefrontConfigDefault, useStorefrontConfig } from '@graphcommerce/next-ui' +import { + storefrontAll, + storefrontConfig, + storefrontConfigDefault, + useStorefrontConfig, +} from '@graphcommerce/next-ui' // Accessing a global value const globalConf = import.meta.graphCommerce.cartDisplayPricesInclTax @@ -27,7 +36,8 @@ function MyComponent() { // Or as single line const scopedConfigWithFallback2 = - useStorefrontConfig().cartDisplayPricesInclTax ?? import.meta.graphCommerce.cartDisplayPricesInclTax + useStorefrontConfig().cartDisplayPricesInclTax ?? + import.meta.graphCommerce.cartDisplayPricesInclTax return
{googleRecaptchaKey}
} @@ -42,25 +52,28 @@ endpoint: '{graphCommerce.magentoEndpoint}' ## Environment variables to override configuration -Configuration values can be overwriten by environment variables, with the following rules: +Configuration values can be overwriten by environment variables, with the +following rules: + - Convert from camelCase to `SCREAMING_SNAKE_CASE` - Prefix with `GC_` - Arrays can be indexed with `_0`, `_1`, `_2`, etc. - Objects can be accessed with `_`. Examples: + - `limitSsg` -> `GC_LIMIT_SSG="1"` - `storefront[0].locale` -> `GC_STOREFRONT_0_LOCALE="en"` - `debug.pluginStatus` -> `GC_DEBUG_PLUGIN_STATUS="1"` - ## Exporting current configuration to environment variables You can export configuration by running `yarn graphcommerce export-config` -## Extending the configuration in your project +## Extending the configuration in your project -Create a graphql/Config.graphqls file in your project and extend the GraphCommerceConfig, GraphCommerceStorefrontConfig inputs to add configuration. +Create a graphql/Config.graphqls file in your project and extend the +GraphCommerceConfig, GraphCommerceStorefrontConfig inputs to add configuration. ```graphql extend input GraphCommerceConfig { @@ -82,6 +95,7 @@ Below is a list of all possible configurations that can be set by GraphCommerce. The canonical base URL is used for SEO purposes. Examples: + - https://example.com - https://example.com/en - https://example.com/en-US @@ -90,7 +104,8 @@ Examples: The HyGraph endpoint. -> Read-only endpoint that allows low latency and high read-throughput content delivery. +> Read-only endpoint that allows low latency and high read-throughput content +> delivery. Project settings -> API Access -> High Performance Read-only Content API @@ -99,6 +114,7 @@ Project settings -> API Access -> High Performance Read-only Content API GraphQL Magento endpoint. Examples: + - https://magento2.test/graphql #### `storefront: [[GraphCommerceStorefrontConfig](#GraphCommerceStorefrontConfig)!]!` @@ -107,7 +123,8 @@ All storefront configuration for the project #### `cartDisplayPricesInclTax: Boolean` -Due to a limitation of the GraphQL API it is not possible to determine if a cart should be displayed including or excluding tax. +Due to a limitation of the GraphQL API it is not possible to determine if a cart +should be displayed including or excluding tax. When Magento's StoreConfig adds this value, this can be replaced. @@ -117,25 +134,30 @@ Use compare functionality #### `compareVariant: [CompareVariant](#CompareVariant) (default: ICON)` -By default the compare feature is denoted with a 'compare ICON' (2 arrows facing one another). -This may be fine for experienced users, but for more clarity it's also possible to present the compare feature as a CHECKBOX accompanied by the 'Compare' label +By default the compare feature is denoted with a 'compare ICON' (2 arrows facing +one another). This may be fine for experienced users, but for more clarity it's +also possible to present the compare feature as a CHECKBOX accompanied by the +'Compare' label #### `configurableVariantForSimple: Boolean (default: [object Object])` -If a simple product is part of a Configurable product page, should the simple product be -rendered as a configured option of the configurable product page? +If a simple product is part of a Configurable product page, should the simple +product be rendered as a configured option of the configurable product page? How does this work: -When the `products(filters: { url_key: { eq: 'simple-product' } }) { ... }` query is ran, -Magento also returns the Simple product and the Configurable product the simple belongs to. +When the `products(filters: { url_key: { eq: 'simple-product' } }) { ... }` +query is ran, Magento also returns the Simple product and the Configurable +product the simple belongs to. -If that is the case we render the configurable product page instead of the simple product page but -the options to select the simple product are pre-selected. +If that is the case we render the configurable product page instead of the +simple product page but the options to select the simple product are +pre-selected. #### `configurableVariantValues: [MagentoConfigurableVariantValues](#MagentoConfigurableVariantValues) (default: [object Object])` -When a user selects a variant, it will switch the values on the configurable page with the values of the configured variant. +When a user selects a variant, it will switch the values on the configurable +page with the values of the configured variant. Enabling options here will allow switching of those variants. @@ -181,19 +203,17 @@ To override the value for a specific locale, configure in i18n config. #### `googleRecaptchaKey: String` -Google reCAPTCHA site key. -When using reCAPTCHA, this value is required, even if you are configuring different values for each locale. +Google reCAPTCHA key, get from https://developers.google.com/recaptcha/docs/v3 -Get a site key and a secret key from https://developers.google.com/recaptcha/docs/v3 - -The secret key should be added in the Magento admin panel (Stores > Configuration > Security > Google ReCAPTCHA Storefront > reCAPTCHA v3 Invisible) -ReCAPTCHA can then be enabled/disabled for the different forms, separately (Stores > Configuration > Security > Google ReCAPTCHA Storefront > Storefront) +This value is required even if you are configuring different values for each +locale. #### `googleTagmanagerId: String` The Google Tagmanager ID to be used on the site. -This value is required even if you are configuring different values for each locale. +This value is required even if you are configuring different values for each +locale. #### `hygraphProjectId: String` @@ -203,7 +223,8 @@ Hygraph Project ID. **Only used for migrations.** Content API. **Only used for migrations.** -> Regular read & write endpoint that allows querying and mutating data in your project. +> Regular read & write endpoint that allows querying and mutating data in your +> project. Project settings -> API Access -> Content API @@ -213,25 +234,22 @@ Hygraph Management SDK Authorization Token. **Only used for migrations.** Project settings -> API Access -> Permanent Auth Tokens -1. Click 'Add token' and give it a name, something like 'GraphCommerce Write Access Token' and keep stage on 'Published'. +1. Click 'Add token' and give it a name, something like 'GraphCommerce Write + Access Token' and keep stage on 'Published'. 2. Under 'Management API', click 'Yes, Initialize defaults' -3. Click 'Edit Permissions' and enable: 'Update' and 'Delete' permissions for 'models', 'enumerations', 'fields', 'components' and 'sources' - - Update existing models - - Delete existing models - - Update existing fields - - Delete existing fields - - Update existing enumerations - - Delete existing enumerations - - Update existing components - - Delete existing components - - Update remote sources - - Delete remote sources - - Read existing environments - - Read public content views - - Create public content views - - Update public content views - - Delete public content views - - Can see schema view +3. Click 'Edit Permissions' and enable: 'Update' and 'Delete' permissions for + 'models', 'enumerations', 'fields', 'components' and 'sources' + +- Update existing models +- Delete existing models +- Update existing fields +- Delete existing fields +- Update existing enumerations +- Delete existing enumerations +- Update existing components +- Delete existing components +- Update remote sources +- Delete remote sources ``` GC_HYGRAPH_WRITE_ACCESS_ENDPOINT="https://...hygraph.com/v2/..." @@ -241,11 +259,14 @@ yarn graphcommerce hygraph-migrate #### `legacyProductRoute: Boolean` -On older versions of GraphCommerce products would use a product type specific route. +On older versions of GraphCommerce products would use a product type specific +route. -This should only be set to true if you use the /product/[url] AND /product/configurable/[url] routes. +This should only be set to true if you use the /product/[url] AND +/product/configurable/[url] routes. -@deprecated Will be removed in a future version. [migration](../upgrading/graphcommerce-5-to-6.md#product-routing-changes) +@deprecated Will be removed in a future version. +[migration](../upgrading/graphcommerce-5-to-6.md#product-routing-changes) #### `limitSsg: Boolean` @@ -257,9 +278,9 @@ To enable next.js' preview mode, configure the secret you'd like to use. #### `productFiltersLayout: [ProductFiltersLayout](#ProductFiltersLayout) (default: DEFAULT)` -Layout how the filters are rendered. -DEFAULT: Will be rendered as horzontal chips on desktop and mobile -SIDEBAR: Will be rendered as a sidebar on desktop and horizontal chips on mobile +Layout how the filters are rendered. DEFAULT: Will be rendered as horzontal +chips on desktop and mobile SIDEBAR: Will be rendered as a sidebar on desktop +and horizontal chips on mobile #### `productFiltersPro: Boolean` @@ -267,15 +288,15 @@ Product filters with better UI for mobile and desktop. #### `productRoute: String` -By default we route products to /p/[url] but you can change this to /product/[url] if you wish. +By default we route products to /p/[url] but you can change this to +/product/[url] if you wish. -Default: '/p/' -Example: '/product/' +Default: '/p/' Example: '/product/' #### `robotsAllow: Boolean` -Allow the site to be indexed by search engines. -If false, the robots.txt file will be set to disallow all. +Allow the site to be indexed by search engines. If false, the robots.txt file +will be set to disallow all. #### `wishlistHideForGuests: Boolean` @@ -283,7 +304,8 @@ Hide the wishlist functionality for guests. #### `wishlistIgnoreProductWishlistStatus: Boolean` -Ignores whether a product is already in the wishlist, makes the toggle an add only. +Ignores whether a product is already in the wishlist, makes the toggle an add +only. #### `wishlistShowFeedbackMessage: Boolean` @@ -299,18 +321,22 @@ Reports which plugins are enabled or disabled. #### `webpackCircularDependencyPlugin: Boolean` -Cyclic dependencies can cause memory issues and other strange bugs. -This plugin will warn you when it detects a cyclic dependency. +Cyclic dependencies can cause memory issues and other strange bugs. This plugin +will warn you when it detects a cyclic dependency. When running into memory issues, it can be useful to enable this plugin. #### `webpackDuplicatesPlugin: Boolean` -When updating packages it can happen that the same package is included with different versions in the same project. +When updating packages it can happen that the same package is included with +different versions in the same project. Issues that this can cause are: -- The same package is included multiple times in the bundle, increasing the bundle size. -- The Typescript types of the package are not compatible with each other, causing Typescript errors. + +- The same package is included multiple times in the bundle, increasing the + bundle size. +- The Typescript types of the package are not compatible with each other, + causing Typescript errors. ### GraphCommerceStorefrontConfig @@ -318,7 +344,8 @@ All storefront configuration for the project #### `locale: String!` -Must be a locale string https://www.unicode.org/reports/tr35/tr35-59/tr35.html#Identifiers +Must be a locale string +https://www.unicode.org/reports/tr35/tr35-59/tr35.html#Identifiers #### `magentoStoreCode: String!` @@ -327,6 +354,7 @@ Magento store code. Stores => All Stores => [Store View] => Store View Code Examples: + - default - en-us - b2b-us @@ -336,17 +364,20 @@ Examples: The canonical base URL is used for SEO purposes. Examples: + - https://example.com - https://example.com/en - https://example.com/en-US #### `cartDisplayPricesInclTax: Boolean` -Due to a limitation of the GraphQL API it is not possible to determine if a cart should be displayed including or excluding tax. +Due to a limitation of the GraphQL API it is not possible to determine if a cart +should be displayed including or excluding tax. #### `defaultLocale: Boolean` There can only be one entry with defaultLocale set to true. + - If there are more, the first one is used. - If there is none, the first entry is used. @@ -370,7 +401,8 @@ The Google Tagmanager ID to be used per locale. #### `hygraphLocales: [String!]` -Add a gcms-locales header to make sure queries return in a certain language, can be an array to define fallbacks. +Add a gcms-locales header to make sure queries return in a certain language, can +be an array to define fallbacks. #### `linguiLocale: String` @@ -378,19 +410,23 @@ Specify a custom locale for to load translations. ### MagentoConfigurableVariantValues -Options to configure which values will be replaced when a variant is selected on the product page. +Options to configure which values will be replaced when a variant is selected on +the product page. #### `content: Boolean` -Use the name, description, short description and meta data from the configured variant +Use the name, description, short description and meta data from the configured +variant #### `gallery: Boolean` -This option enables the automatic update of product gallery images on the product page when a variant is selected, -provided that the gallery images for the selected variant differ from the currently displayed images. +This option enables the automatic update of product gallery images on the +product page when a variant is selected, provided that the gallery images for +the selected variant differ from the currently displayed images. #### `url: Boolean` -When a variant is selected the URL of the product will be changed in the address bar. +When a variant is selected the URL of the product will be changed in the address +bar. -This only happens when the actual variant is can be accessed by the URL. \ No newline at end of file +This only happens when the actual variant is can be accessed by the URL. diff --git a/packages/framer-scroller/components/ScrollerThumbnail.tsx b/packages/framer-scroller/components/ScrollerThumbnail.tsx index 19bb04e5f9..087e299616 100644 --- a/packages/framer-scroller/components/ScrollerThumbnail.tsx +++ b/packages/framer-scroller/components/ScrollerThumbnail.tsx @@ -3,7 +3,7 @@ import { Image, ImageProps } from '@graphcommerce/image' // eslint-disable-next-line import/no-extraneous-dependencies import { extendableComponent } from '@graphcommerce/next-ui/Styles' import { ButtonProps, styled } from '@mui/material' -import { m } from 'framer-motion' +import { m, Variant, useAnimation, useMotionValueEvent } from 'framer-motion' import { useScrollTo } from '../hooks/useScrollTo' import { useScrollerContext } from '../hooks/useScrollerContext' import { ItemState } from '../types' @@ -17,57 +17,88 @@ const { withState } = extendableComponent type ScrollerThumbnailProps = Omit & ItemState & { idx: number; image: Pick } -const MotionBox = styled(m.div)({}) +type AnimationType = { + default: Variant + moving: Variant + active: Variant +} -const imageDimensions = 120 +const MotionBox = styled(m.div)({}) export function ScrollerThumbnail(props: ScrollerThumbnailProps) { const { el, visibility, opacity, idx, image, ...buttonProps } = props const scrollTo = useScrollTo() - const { getScrollSnapPositions } = useScrollerContext() + const { getScrollSnapPositions, scroll } = useScrollerContext() + const active = useMotionValueValue(visibility, (v) => v >= 0.5) + const imageController = useAnimation() - const active = useMotionValueValue(visibility, (v) => v > 0.5) const classes = withState({ active }) - const imageAnimation = { - rest: { width: imageDimensions / 2 }, - hover: { - width: imageDimensions, + const positions = getScrollSnapPositions() + const currentPosition = positions.x[idx] + + useMotionValueEvent(visibility, 'change', (v) => { + if (v >= 0.9) { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + imageController.start('active') + } else { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + imageController.start('default') + } + }) + + if (!image) return null + + const imageAnimation: AnimationType = { + default: { + scaleX: 1, + }, + moving: { + scaleX: 1.5, }, + active: { + scaleX: 1.5, + }, + } + + const isBeforeScroll = scroll.x.get() < getScrollSnapPositions().x[idx] + + const placement = () => { + if (active) return 0 + if (isBeforeScroll) return 60 + return -60 } - const spacingAnimation = { - rest: { width: imageDimensions / 2 }, - hover: { - width: imageDimensions + 20, + const backgroundAnimation: AnimationType = { + default: { + scaleX: 2, + x: placement(), + }, + moving: { + scaleX: 2, + }, + active: { + scaleX: 1.5, }, } - if (!image) return null return ( - - + + { - const positions = getScrollSnapPositions() // eslint-disable-next-line @typescript-eslint/no-floating-promises - scrollTo({ x: positions.x[idx] ?? 0, y: positions.y[idx] ?? 0 }) + scrollTo({ x: currentPosition, y: positions.y[idx] ?? 0 }) }} sx={{ - width: imageDimensions, - height: imageDimensions, - aspectRatio: '1/1', - '& img': { - display: 'block', - height: '100%', - objectFit: 'cover', - borderRadius: { xs: 1, md: 2 }, - }, + height: 120, + bgcolor: 'green', + overflow: 'hidden', }} > - + diff --git a/packages/framer-scroller/components/ScrollerThumbnails.tsx b/packages/framer-scroller/components/ScrollerThumbnails.tsx index 40cb079df1..322d9b7e3c 100644 --- a/packages/framer-scroller/components/ScrollerThumbnails.tsx +++ b/packages/framer-scroller/components/ScrollerThumbnails.tsx @@ -30,7 +30,7 @@ export const ScrollerThumbnails = m( if (itemsArr.length <= 1) return null return ( - + {itemsArr.map((item, idx) => ( // eslint-disable-next-line react/no-array-index-key From 785bf5e9a01e080ccf8b8e73a41cc52a6f9a9754 Mon Sep 17 00:00:00 2001 From: Mike Keehnen Date: Fri, 7 Jul 2023 15:24:00 +0200 Subject: [PATCH 11/40] WIP --- .../components/ScrollerThumbnail.tsx | 82 ++++--------- .../components/ScrollerThumbnails.tsx | 109 +++++++++++++----- 2 files changed, 105 insertions(+), 86 deletions(-) diff --git a/packages/framer-scroller/components/ScrollerThumbnail.tsx b/packages/framer-scroller/components/ScrollerThumbnail.tsx index 087e299616..137db5ff6f 100644 --- a/packages/framer-scroller/components/ScrollerThumbnail.tsx +++ b/packages/framer-scroller/components/ScrollerThumbnail.tsx @@ -3,7 +3,7 @@ import { Image, ImageProps } from '@graphcommerce/image' // eslint-disable-next-line import/no-extraneous-dependencies import { extendableComponent } from '@graphcommerce/next-ui/Styles' import { ButtonProps, styled } from '@mui/material' -import { m, Variant, useAnimation, useMotionValueEvent } from 'framer-motion' +import { m, Variant, useAnimation, useMotionValueEvent, useTransform } from 'framer-motion' import { useScrollTo } from '../hooks/useScrollTo' import { useScrollerContext } from '../hooks/useScrollerContext' import { ItemState } from '../types' @@ -19,7 +19,6 @@ type ScrollerThumbnailProps = Omit & type AnimationType = { default: Variant - moving: Variant active: Variant } @@ -28,7 +27,7 @@ const MotionBox = styled(m.div)({}) export function ScrollerThumbnail(props: ScrollerThumbnailProps) { const { el, visibility, opacity, idx, image, ...buttonProps } = props const scrollTo = useScrollTo() - const { getScrollSnapPositions, scroll } = useScrollerContext() + const { getScrollSnapPositions } = useScrollerContext() const active = useMotionValueValue(visibility, (v) => v >= 0.5) const imageController = useAnimation() @@ -37,69 +36,32 @@ export function ScrollerThumbnail(props: ScrollerThumbnailProps) { const positions = getScrollSnapPositions() const currentPosition = positions.x[idx] - useMotionValueEvent(visibility, 'change', (v) => { - if (v >= 0.9) { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - imageController.start('active') - } else { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - imageController.start('default') - } - }) + const scale = useMotionValueValue(visibility, (v) => v + 1) if (!image) return null - const imageAnimation: AnimationType = { - default: { - scaleX: 1, - }, - moving: { - scaleX: 1.5, - }, - active: { - scaleX: 1.5, - }, - } - - const isBeforeScroll = scroll.x.get() < getScrollSnapPositions().x[idx] - - const placement = () => { - if (active) return 0 - if (isBeforeScroll) return 60 - return -60 - } - - const backgroundAnimation: AnimationType = { - default: { - scaleX: 2, - x: placement(), - }, - moving: { - scaleX: 2, - }, - active: { - scaleX: 1.5, - }, - } + const activeWidth = (120 / (image.height ?? 120)) * (image.width ?? 120) return ( - - { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - scrollTo({ x: currentPosition, y: positions.y[idx] ?? 0 }) - }} - sx={{ - height: 120, - bgcolor: 'green', - overflow: 'hidden', - }} - > - - + { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + scrollTo({ x: currentPosition, y: positions.y[idx] ?? 0 }) + }} + sx={{ + width: '100%', + overflow: 'hidden', + height: 120, + }} + animate={{ + width: active ? activeWidth : 'auto', + margin: active ? '0 8px' : '0', + scale, + }} + > + ) diff --git a/packages/framer-scroller/components/ScrollerThumbnails.tsx b/packages/framer-scroller/components/ScrollerThumbnails.tsx index 322d9b7e3c..303de4895c 100644 --- a/packages/framer-scroller/components/ScrollerThumbnails.tsx +++ b/packages/framer-scroller/components/ScrollerThumbnails.tsx @@ -1,12 +1,12 @@ import { useMotionValueValue } from '@graphcommerce/framer-utils' import { ImageProps } from '@graphcommerce/image' import { extendableComponent } from '@graphcommerce/next-ui/Styles' -import { Box, ButtonProps, styled, SxProps, Theme } from '@mui/material' -import { m } from 'framer-motion' -import React from 'react' +import { ButtonProps, styled, SxProps, Theme } from '@mui/material' +import { m, motionValue, PanInfo, useMotionValueEvent } from 'framer-motion' +import React, { use, useRef } from 'react' import { useScrollerContext } from '../hooks/useScrollerContext' -import { Scroller } from './Scroller' -import { ScrollerProvider } from './ScrollerProvider' +import { useScrollTo } from '../hooks/useScrollTo' +import { ItemState } from '../types' import { ScrollerThumbnail } from './ScrollerThumbnail' const MotionBox = styled(m.div)({}) @@ -18,25 +18,82 @@ export type ThumbnailsProps = { } const componentName = 'ScrollerThumbnails' -const { classes } = extendableComponent(componentName, ['root'] as const) - -export const ScrollerThumbnails = m( - React.forwardRef((props, ref) => { - const { buttonProps, images, sx = [], ...containerProps } = props - - const { items } = useScrollerContext() - const itemsArr = useMotionValueValue(items, (v) => v) - - if (itemsArr.length <= 1) return null - - return ( - - {itemsArr.map((item, idx) => ( - // eslint-disable-next-line react/no-array-index-key - - ))} - - ) - }), -) + +export const ScrollerThumbnails = m((props: ThumbnailsProps) => { + const { images, sx = [] } = props + const motionBoxRef = useRef(null) + const scrollTo = useScrollTo() + const { items, getScrollSnapPositions } = useScrollerContext() + const itemsArr = useMotionValueValue(items, (i) => i) + const snapPositions = getScrollSnapPositions() + const offsetWidth = motionBoxRef.current?.offsetWidth ?? 0 + + useMotionValueEvent(items, 'change', (i) => { + console.log(i) + }) + + if (itemsArr.length <= 1) return null + + const onPan = (_event: PointerEvent, info: PanInfo) => { + const raw = info.point.x / (offsetWidth / itemsArr.length) + const idx = Math.max(0, Math.min(Math.round(raw), itemsArr.length - 1)) + // This should be the code that triggers the scroll + const progressRight = raw - idx // -0.5 to 0.5 + const progressLeft = 1 - progressRight // 1.5 to 0.5 + + if (progressLeft > -0.5 && progressLeft < 0.5) { + console.log('in left', (progressLeft / 100) * -20) + items.set( + itemsArr.map((item, i) => { + if (idx === i) { + return { + ...item, + visibility: motionValue((progressLeft / 100) * -20), + } + } + return item + }), + ) + } + + if (progressRight > 0.5 && progressRight < 1.5) { + console.log('in right', (progressLeft / 100) * -20) + + items.set( + itemsArr.map((item, i) => { + if (idx === i) { + return { + ...item, + visibility: motionValue((progressRight / 100) * -20), + } + } + return item + }), + ) + } + + // // This line will only be triggered when the user stops paning the thumbnails + // const x = snapPositions.x[idx] + // const y = snapPositions.y[idx] ?? 0 + // // eslint-disable-next-line @typescript-eslint/no-floating-promises + // scrollTo({ x, y }) + } + + return ( + + {itemsArr.map((item, idx) => ( + // eslint-disable-next-line react/no-array-index-key + + ))} + + ) +}) + ScrollerThumbnails.displayName = componentName From 2b8cbd92739f1f6385a792ffad01a35ededdddaa Mon Sep 17 00:00:00 2001 From: Mike Keehnen Date: Wed, 2 Aug 2023 13:38:23 +0200 Subject: [PATCH 12/40] Working version for p/subtle-sympathy-gc-1087-sock, but broken on pages with less images --- .../components/ImageGalleryContext.tsx | 25 +++++ .../components/ScrollerThumbnail.tsx | 60 ++++++++---- .../components/ScrollerThumbnails.tsx | 96 ++++--------------- .../components/ThumbnailContainer.tsx | 81 ++++++++++++++++ .../framer-scroller/hooks/useScrollOrScrub.ts | 45 +++++++++ 5 files changed, 209 insertions(+), 98 deletions(-) create mode 100644 packages/framer-scroller/components/ImageGalleryContext.tsx create mode 100644 packages/framer-scroller/components/ThumbnailContainer.tsx create mode 100644 packages/framer-scroller/hooks/useScrollOrScrub.ts diff --git a/packages/framer-scroller/components/ImageGalleryContext.tsx b/packages/framer-scroller/components/ImageGalleryContext.tsx new file mode 100644 index 0000000000..6c5d72a3d0 --- /dev/null +++ b/packages/framer-scroller/components/ImageGalleryContext.tsx @@ -0,0 +1,25 @@ +import { MotionValue, useMotionValue } from 'framer-motion' +import { createContext, useContext } from 'react' +import { ItemState } from '../types' + +export type ImageGallaryContextValues = { + container: { + width: number + pan: { coordinates: MotionValue<{ x: number; y: number }> } + } + items: ItemState[] + animation: { active: MotionValue; mode: MotionValue<'scroll' | 'drag'> } +} + +export const ImageGallaryContext = createContext(undefined) + +export function useImageGalleryContext() { + const context = useContext(ImageGallaryContext) + if (typeof context === 'undefined') { + throw Error( + 'usePaymentMethodContext must be used within a PaymentMethodContextProvider or provide the optional=true argument', + ) + } + + return context +} diff --git a/packages/framer-scroller/components/ScrollerThumbnail.tsx b/packages/framer-scroller/components/ScrollerThumbnail.tsx index 137db5ff6f..b20fb5302f 100644 --- a/packages/framer-scroller/components/ScrollerThumbnail.tsx +++ b/packages/framer-scroller/components/ScrollerThumbnail.tsx @@ -2,11 +2,14 @@ import { useMotionValueValue } from '@graphcommerce/framer-utils' import { Image, ImageProps } from '@graphcommerce/image' // eslint-disable-next-line import/no-extraneous-dependencies import { extendableComponent } from '@graphcommerce/next-ui/Styles' -import { ButtonProps, styled } from '@mui/material' -import { m, Variant, useAnimation, useMotionValueEvent, useTransform } from 'framer-motion' +import { styled } from '@mui/material' +import { m, useAnimation, Variants } from 'framer-motion' +import { useEffect } from 'react' +import { useScrollOrScrub } from '../hooks/useScrollOrScrub' import { useScrollTo } from '../hooks/useScrollTo' import { useScrollerContext } from '../hooks/useScrollerContext' import { ItemState } from '../types' +import { useImageGalleryContext } from './ImageGalleryContext' const name = 'ScrollerThumbnail' const parts = ['thumbnail'] as const @@ -14,21 +17,19 @@ type OwnerProps = { active: boolean } const { withState } = extendableComponent(name, parts) -type ScrollerThumbnailProps = Omit & - ItemState & { idx: number; image: Pick } - -type AnimationType = { - default: Variant - active: Variant +type ScrollerThumbnailProps = ItemState & { + idx: number + image: Pick } const MotionBox = styled(m.div)({}) export function ScrollerThumbnail(props: ScrollerThumbnailProps) { - const { el, visibility, opacity, idx, image, ...buttonProps } = props + const { visibility, idx, image } = props const scrollTo = useScrollTo() - const { getScrollSnapPositions } = useScrollerContext() + const { getScrollSnapPositions, scroll } = useScrollerContext() const active = useMotionValueValue(visibility, (v) => v >= 0.5) + const scrollIsAnimating = useMotionValueValue(scroll.animating, (v) => v) const imageController = useAnimation() const classes = withState({ active }) @@ -36,17 +37,44 @@ export function ScrollerThumbnail(props: ScrollerThumbnailProps) { const positions = getScrollSnapPositions() const currentPosition = positions.x[idx] - const scale = useMotionValueValue(visibility, (v) => v + 1) + const activeWidth = (120 / (image.height ?? 120)) * (image.width ?? 120) + + const width = useScrollOrScrub({ + activeWidth, + idx, + }) + + const { animation } = useImageGalleryContext() + + useEffect(() => { + if (active && !scrollIsAnimating && animation.mode.get()) { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + imageController.start('active') + animation.active.set(false) + } + // eslint-disable-next-line @typescript-eslint/no-floating-promises + else imageController.start('default') + }, [active, animation.active, animation.mode, imageController, scrollIsAnimating]) if (!image) return null - const activeWidth = (120 / (image.height ?? 120)) * (image.width ?? 120) + const marginAnimation: Variants = { + default: { + margin: '0', + }, + active: { + margin: '0 8px', + }, + } return ( - + { + animation.mode.set('scroll') + animation.active.set(true) // eslint-disable-next-line @typescript-eslint/no-floating-promises scrollTo({ x: currentPosition, y: positions.y[idx] ?? 0 }) }} @@ -55,11 +83,7 @@ export function ScrollerThumbnail(props: ScrollerThumbnailProps) { overflow: 'hidden', height: 120, }} - animate={{ - width: active ? activeWidth : 'auto', - margin: active ? '0 8px' : '0', - scale, - }} + style={{ width }} > diff --git a/packages/framer-scroller/components/ScrollerThumbnails.tsx b/packages/framer-scroller/components/ScrollerThumbnails.tsx index 303de4895c..c4d132b35d 100644 --- a/packages/framer-scroller/components/ScrollerThumbnails.tsx +++ b/packages/framer-scroller/components/ScrollerThumbnails.tsx @@ -1,15 +1,8 @@ -import { useMotionValueValue } from '@graphcommerce/framer-utils' import { ImageProps } from '@graphcommerce/image' -import { extendableComponent } from '@graphcommerce/next-ui/Styles' -import { ButtonProps, styled, SxProps, Theme } from '@mui/material' -import { m, motionValue, PanInfo, useMotionValueEvent } from 'framer-motion' -import React, { use, useRef } from 'react' -import { useScrollerContext } from '../hooks/useScrollerContext' -import { useScrollTo } from '../hooks/useScrollTo' -import { ItemState } from '../types' +import { ButtonProps, SxProps, Theme } from '@mui/material' +import { m } from 'framer-motion' import { ScrollerThumbnail } from './ScrollerThumbnail' - -const MotionBox = styled(m.div)({}) +import { ThumbnailContainer } from './ThumbnailContainer' export type ThumbnailsProps = { buttonProps?: Omit @@ -21,78 +14,21 @@ const componentName = 'ScrollerThumbnails' export const ScrollerThumbnails = m((props: ThumbnailsProps) => { const { images, sx = [] } = props - const motionBoxRef = useRef(null) - const scrollTo = useScrollTo() - const { items, getScrollSnapPositions } = useScrollerContext() - const itemsArr = useMotionValueValue(items, (i) => i) - const snapPositions = getScrollSnapPositions() - const offsetWidth = motionBoxRef.current?.offsetWidth ?? 0 - - useMotionValueEvent(items, 'change', (i) => { - console.log(i) - }) - - if (itemsArr.length <= 1) return null - - const onPan = (_event: PointerEvent, info: PanInfo) => { - const raw = info.point.x / (offsetWidth / itemsArr.length) - const idx = Math.max(0, Math.min(Math.round(raw), itemsArr.length - 1)) - // This should be the code that triggers the scroll - const progressRight = raw - idx // -0.5 to 0.5 - const progressLeft = 1 - progressRight // 1.5 to 0.5 - - if (progressLeft > -0.5 && progressLeft < 0.5) { - console.log('in left', (progressLeft / 100) * -20) - items.set( - itemsArr.map((item, i) => { - if (idx === i) { - return { - ...item, - visibility: motionValue((progressLeft / 100) * -20), - } - } - return item - }), - ) - } - - if (progressRight > 0.5 && progressRight < 1.5) { - console.log('in right', (progressLeft / 100) * -20) - - items.set( - itemsArr.map((item, i) => { - if (idx === i) { - return { - ...item, - visibility: motionValue((progressRight / 100) * -20), - } - } - return item - }), - ) - } - - // // This line will only be triggered when the user stops paning the thumbnails - // const x = snapPositions.x[idx] - // const y = snapPositions.y[idx] ?? 0 - // // eslint-disable-next-line @typescript-eslint/no-floating-promises - // scrollTo({ x, y }) - } return ( - - {itemsArr.map((item, idx) => ( - // eslint-disable-next-line react/no-array-index-key - - ))} - + + {(items) => + items.map((item, i) => ( + + )) + } + ) }) diff --git a/packages/framer-scroller/components/ThumbnailContainer.tsx b/packages/framer-scroller/components/ThumbnailContainer.tsx new file mode 100644 index 0000000000..44a67eecc2 --- /dev/null +++ b/packages/framer-scroller/components/ThumbnailContainer.tsx @@ -0,0 +1,81 @@ +import { styled, SxProps, Theme } from '@mui/material' +import { m, PanInfo, useMotionValue } from 'framer-motion' +import React, { useEffect, useMemo, useRef, useState } from 'react' +import { useScrollTo } from '../hooks/useScrollTo' +import { useScrollerContext } from '../hooks/useScrollerContext' +import { ImageGallaryContext, ImageGallaryContextValues } from './ImageGalleryContext' +import { useMotionValueValue } from '@graphcommerce/framer-utils' + +const MotionBox = styled(m.div)({}) + +type ThumbnailContainerProps = { + children: (items: ImageGallaryContextValues['items']) => React.ReactNode + sx?: SxProps +} + +export function ThumbnailContainer(props: ThumbnailContainerProps) { + const { children, sx } = props + const { getScrollSnapPositions, items } = useScrollerContext() + const itemsArr = useMotionValueValue(items, (i) => i) + const motionBoxRef = useRef(null) + const panValues = useMotionValue({ x: 0, y: 0 }) + const animationActive = useMotionValue(false) + const animationMode = useMotionValue<'scroll' | 'drag'>('scroll') + const snapPositions = getScrollSnapPositions() + + const [elementWidth, setElementWidth] = useState(0) + const scrollTo = useScrollTo() + + useEffect(() => { + setElementWidth((old) => motionBoxRef.current?.offsetWidth ?? old) + }, []) + + const memoizedContextValues = useMemo( + () => ({ + container: { + width: elementWidth, + pan: { coordinates: panValues }, + }, + items: itemsArr, + animation: { active: animationActive, mode: animationMode }, + }), + [elementWidth, panValues, itemsArr, animationActive, animationMode], + ) + + if (itemsArr.length <= 1) return null + + const onPanStart = () => { + animationActive.set(true) + animationMode.set('drag') + } + + const onPan = (_event: PointerEvent, info: PanInfo) => { + panValues.set({ x: info.point.x, y: info.point.y }) + } + + const onPanEnd = (_event: PointerEvent, info: PanInfo) => { + animationActive.set(false) + const raw = info.point.x / (elementWidth / itemsArr.length) + const idx = Math.max(0, Math.min(Math.round(raw), itemsArr.length - 1)) + + const x = snapPositions.x[idx] + const y = snapPositions.y[idx] ?? 0 + // eslint-disable-next-line @typescript-eslint/no-floating-promises + scrollTo({ x, y }) + animationMode.set('scroll') + } + + return ( + + + {children(itemsArr)} + + + ) +} diff --git a/packages/framer-scroller/hooks/useScrollOrScrub.ts b/packages/framer-scroller/hooks/useScrollOrScrub.ts new file mode 100644 index 0000000000..53cc4987f7 --- /dev/null +++ b/packages/framer-scroller/hooks/useScrollOrScrub.ts @@ -0,0 +1,45 @@ +import { useMotionValueValue } from '@graphcommerce/framer-utils' +import { useTransform } from 'framer-motion' +import { useImageGalleryContext } from '../components/ImageGalleryContext' +import { useScrollerContext } from './useScrollerContext' + +export type ScrollOrScrubProps = { + activeWidth: number + idx: number +} + +export function useScrollOrScrub(props: ScrollOrScrubProps) { + const { activeWidth, idx } = props + const { scroll } = useScrollerContext() + const { items, container, animation } = useImageGalleryContext() + const active = useMotionValueValue(animation.active, (v) => v) + const mode = useMotionValueValue(animation.mode, (v) => v) + const calculateWidth = (raw: number) => { + const scrubIndex = Math.max(0, Math.min(Math.round(raw), items.length - 1)) + const progress = raw - scrubIndex + 0.5 + + if (scrubIndex === idx - 1) return Math.max(10, Math.min(activeWidth * progress)) + if (scrubIndex === idx + 1) return Math.max(10, Math.min(activeWidth * (1 - progress))) + if (scrubIndex === idx) return activeWidth + return 'auto' + } + + const widthOnScrub = useTransform(container.pan.coordinates, (v) => { + if (!active) return undefined + const raw = v.x / (container.width / items.length) + return calculateWidth(raw) + }) + + const widthOnScroll = useTransform(scroll.xProgress, (v) => { + if (!active) return undefined + const raw = v * (items.length - 1) + return calculateWidth(raw) + }) + + console.log('mode', mode, 'active', active) + + if (mode === 'scroll' && active) return widthOnScroll + if (mode === 'drag' && active) return widthOnScrub + + return undefined +} From 8c8de02ffa7bf1280832297a8ba4a744997f75b0 Mon Sep 17 00:00:00 2001 From: Mike Keehnen Date: Wed, 2 Aug 2023 15:44:54 +0200 Subject: [PATCH 13/40] New object observer --- .../components/ImageGalleryContext.tsx | 4 ++-- .../components/ScrollerThumbnail.tsx | 1 - .../components/ThumbnailContainer.tsx | 20 +++++++++++++------ .../hooks/useResizeObserver.ts | 19 ++++++++++++++++++ .../framer-scroller/hooks/useScrollOrScrub.ts | 17 +++++++++------- 5 files changed, 45 insertions(+), 16 deletions(-) create mode 100644 packages/framer-scroller/hooks/useResizeObserver.ts diff --git a/packages/framer-scroller/components/ImageGalleryContext.tsx b/packages/framer-scroller/components/ImageGalleryContext.tsx index 6c5d72a3d0..cacf2d09da 100644 --- a/packages/framer-scroller/components/ImageGalleryContext.tsx +++ b/packages/framer-scroller/components/ImageGalleryContext.tsx @@ -1,4 +1,4 @@ -import { MotionValue, useMotionValue } from 'framer-motion' +import { MotionValue } from 'framer-motion' import { createContext, useContext } from 'react' import { ItemState } from '../types' @@ -17,7 +17,7 @@ export function useImageGalleryContext() { const context = useContext(ImageGallaryContext) if (typeof context === 'undefined') { throw Error( - 'usePaymentMethodContext must be used within a PaymentMethodContextProvider or provide the optional=true argument', + 'useImageGalleryContext must be used within a ImageGallaryContext.Provider component', ) } diff --git a/packages/framer-scroller/components/ScrollerThumbnail.tsx b/packages/framer-scroller/components/ScrollerThumbnail.tsx index b20fb5302f..ef59efbcb7 100644 --- a/packages/framer-scroller/components/ScrollerThumbnail.tsx +++ b/packages/framer-scroller/components/ScrollerThumbnail.tsx @@ -79,7 +79,6 @@ export function ScrollerThumbnail(props: ScrollerThumbnailProps) { scrollTo({ x: currentPosition, y: positions.y[idx] ?? 0 }) }} sx={{ - width: '100%', overflow: 'hidden', height: 120, }} diff --git a/packages/framer-scroller/components/ThumbnailContainer.tsx b/packages/framer-scroller/components/ThumbnailContainer.tsx index 44a67eecc2..d259a57149 100644 --- a/packages/framer-scroller/components/ThumbnailContainer.tsx +++ b/packages/framer-scroller/components/ThumbnailContainer.tsx @@ -1,10 +1,11 @@ +import { useMotionValueValue } from '@graphcommerce/framer-utils' import { styled, SxProps, Theme } from '@mui/material' import { m, PanInfo, useMotionValue } from 'framer-motion' import React, { useEffect, useMemo, useRef, useState } from 'react' import { useScrollTo } from '../hooks/useScrollTo' import { useScrollerContext } from '../hooks/useScrollerContext' import { ImageGallaryContext, ImageGallaryContextValues } from './ImageGalleryContext' -import { useMotionValueValue } from '@graphcommerce/framer-utils' +import { useResizeObserver } from '../hooks/useResizeObserver' const MotionBox = styled(m.div)({}) @@ -23,12 +24,11 @@ export function ThumbnailContainer(props: ThumbnailContainerProps) { const animationMode = useMotionValue<'scroll' | 'drag'>('scroll') const snapPositions = getScrollSnapPositions() - const [elementWidth, setElementWidth] = useState(0) const scrollTo = useScrollTo() - useEffect(() => { - setElementWidth((old) => motionBoxRef.current?.offsetWidth ?? old) - }, []) + const objectDimensions = useResizeObserver(motionBoxRef) + + const elementWidth = objectDimensions?.contentRect.width ?? 0 const memoizedContextValues = useMemo( () => ({ @@ -72,7 +72,15 @@ export function ThumbnailContainer(props: ThumbnailContainerProps) { onPanStart={onPanStart} onPan={onPan} onPanEnd={onPanEnd} - sx={[{ display: 'flex', flexDirection: 'row' }, ...(Array.isArray(sx) ? sx : [sx])]} + sx={[ + { + display: 'flex', + flexDirection: 'row', + touchAction: 'none', + maxWidth: '100%', + }, + ...(Array.isArray(sx) ? sx : [sx]), + ]} > {children(itemsArr)} diff --git a/packages/framer-scroller/hooks/useResizeObserver.ts b/packages/framer-scroller/hooks/useResizeObserver.ts new file mode 100644 index 0000000000..2093b835e5 --- /dev/null +++ b/packages/framer-scroller/hooks/useResizeObserver.ts @@ -0,0 +1,19 @@ +import { useEffect, useState } from 'react' + +export function useResizeObserver(ref: React.RefObject) { + const [resizeObserverObject, setResizeObserverObject] = useState< + ResizeObserverEntry | undefined + >() + + useEffect(() => { + if (!ref.current || ref.current === null) return () => {} + const ro = new ResizeObserver(([entry]) => { + console.log('entry', entry) + setResizeObserverObject(entry) + }) + ro.observe(ref.current) + return () => ro.disconnect() + }, [ref]) + + return resizeObserverObject +} diff --git a/packages/framer-scroller/hooks/useScrollOrScrub.ts b/packages/framer-scroller/hooks/useScrollOrScrub.ts index 53cc4987f7..0d03e3f1e9 100644 --- a/packages/framer-scroller/hooks/useScrollOrScrub.ts +++ b/packages/framer-scroller/hooks/useScrollOrScrub.ts @@ -18,8 +18,10 @@ export function useScrollOrScrub(props: ScrollOrScrubProps) { const scrubIndex = Math.max(0, Math.min(Math.round(raw), items.length - 1)) const progress = raw - scrubIndex + 0.5 - if (scrubIndex === idx - 1) return Math.max(10, Math.min(activeWidth * progress)) - if (scrubIndex === idx + 1) return Math.max(10, Math.min(activeWidth * (1 - progress))) + if (scrubIndex === idx - 1) + return Math.max(container.width / items.length, Math.min(activeWidth * progress)) + if (scrubIndex === idx + 1) + return Math.max(container.width / items.length, Math.min(activeWidth * (1 - progress))) if (scrubIndex === idx) return activeWidth return 'auto' } @@ -35,11 +37,12 @@ export function useScrollOrScrub(props: ScrollOrScrubProps) { const raw = v * (items.length - 1) return calculateWidth(raw) }) - - console.log('mode', mode, 'active', active) - - if (mode === 'scroll' && active) return widthOnScroll - if (mode === 'drag' && active) return widthOnScrub + if (mode === 'scroll' && active) { + return widthOnScroll + } + if (mode === 'drag' && active) { + return widthOnScrub + } return undefined } From b0a7268fbd27e7003e9fca2ad87aa213d4da8e26 Mon Sep 17 00:00:00 2001 From: Mike Keehnen Date: Thu, 3 Aug 2023 15:46:47 +0200 Subject: [PATCH 14/40] Removed state with motion value to prevent rerenders --- .../components/ImageGalleryContext.tsx | 34 +++++- .../components/ScrollerThumbnail.tsx | 31 ++---- .../components/ScrollerThumbnails.tsx | 31 +++--- .../components/ThumbnailContainer.tsx | 104 ++++++++---------- .../hooks/useResizeObserver.ts | 19 ---- .../framer-scroller/hooks/useScrollOrScrub.ts | 48 -------- 6 files changed, 103 insertions(+), 164 deletions(-) delete mode 100644 packages/framer-scroller/hooks/useResizeObserver.ts delete mode 100644 packages/framer-scroller/hooks/useScrollOrScrub.ts diff --git a/packages/framer-scroller/components/ImageGalleryContext.tsx b/packages/framer-scroller/components/ImageGalleryContext.tsx index cacf2d09da..027701b8db 100644 --- a/packages/framer-scroller/components/ImageGalleryContext.tsx +++ b/packages/framer-scroller/components/ImageGalleryContext.tsx @@ -1,14 +1,15 @@ -import { MotionValue } from 'framer-motion' -import { createContext, useContext } from 'react' +import { useMotionValueValue } from '@graphcommerce/framer-utils' +import { MotionValue, useMotionValue } from 'framer-motion' +import React, { createContext, PropsWithChildren, useContext, useMemo } from 'react' +import { useScrollerContext } from '../hooks/useScrollerContext' import { ItemState } from '../types' export type ImageGallaryContextValues = { container: { - width: number - pan: { coordinates: MotionValue<{ x: number; y: number }> } + width?: number + pan: { active: MotionValue } } items: ItemState[] - animation: { active: MotionValue; mode: MotionValue<'scroll' | 'drag'> } } export const ImageGallaryContext = createContext(undefined) @@ -23,3 +24,26 @@ export function useImageGalleryContext() { return context } + +export function ImageGalleryContextProvider(props: { children: React.ReactNode }) { + const { children } = props + const { items } = useScrollerContext() + const itemsArr = useMotionValueValue(items, (i) => i) + const panActive = useMotionValue(false) + + const memoizedContextValues = useMemo( + () => ({ + container: { + pan: { active: panActive }, + }, + items: itemsArr, + }), + [panActive, itemsArr], + ) + + return ( + + {children} + + ) +} diff --git a/packages/framer-scroller/components/ScrollerThumbnail.tsx b/packages/framer-scroller/components/ScrollerThumbnail.tsx index ef59efbcb7..3e10907ecf 100644 --- a/packages/framer-scroller/components/ScrollerThumbnail.tsx +++ b/packages/framer-scroller/components/ScrollerThumbnail.tsx @@ -5,7 +5,6 @@ import { extendableComponent } from '@graphcommerce/next-ui/Styles' import { styled } from '@mui/material' import { m, useAnimation, Variants } from 'framer-motion' import { useEffect } from 'react' -import { useScrollOrScrub } from '../hooks/useScrollOrScrub' import { useScrollTo } from '../hooks/useScrollTo' import { useScrollerContext } from '../hooks/useScrollerContext' import { ItemState } from '../types' @@ -28,7 +27,9 @@ export function ScrollerThumbnail(props: ScrollerThumbnailProps) { const { visibility, idx, image } = props const scrollTo = useScrollTo() const { getScrollSnapPositions, scroll } = useScrollerContext() - const active = useMotionValueValue(visibility, (v) => v >= 0.5) + const active = useMotionValueValue(visibility, (v) => v >= 0.7) + const { container } = useImageGalleryContext() + const panActive = useMotionValueValue(container.pan.active, (v) => v) const scrollIsAnimating = useMotionValueValue(scroll.animating, (v) => v) const imageController = useAnimation() @@ -37,52 +38,44 @@ export function ScrollerThumbnail(props: ScrollerThumbnailProps) { const positions = getScrollSnapPositions() const currentPosition = positions.x[idx] - const activeWidth = (120 / (image.height ?? 120)) * (image.width ?? 120) - - const width = useScrollOrScrub({ - activeWidth, - idx, - }) - - const { animation } = useImageGalleryContext() + const activeWidth = 120 useEffect(() => { - if (active && !scrollIsAnimating && animation.mode.get()) { + if (active && !scrollIsAnimating && !panActive) { // eslint-disable-next-line @typescript-eslint/no-floating-promises imageController.start('active') - animation.active.set(false) } // eslint-disable-next-line @typescript-eslint/no-floating-promises else imageController.start('default') - }, [active, animation.active, animation.mode, imageController, scrollIsAnimating]) + }, [active, imageController, panActive, scrollIsAnimating]) if (!image) return null - const marginAnimation: Variants = { + const widthAnimation: Variants = { default: { margin: '0', + width: 'auto', }, active: { margin: '0 8px', + width: activeWidth, }, } return ( - + { - animation.mode.set('scroll') - animation.active.set(true) // eslint-disable-next-line @typescript-eslint/no-floating-promises scrollTo({ x: currentPosition, y: positions.y[idx] ?? 0 }) }} sx={{ - overflow: 'hidden', height: 120, }} - style={{ width }} + animate={imageController} + variants={widthAnimation} > diff --git a/packages/framer-scroller/components/ScrollerThumbnails.tsx b/packages/framer-scroller/components/ScrollerThumbnails.tsx index c4d132b35d..08ce841237 100644 --- a/packages/framer-scroller/components/ScrollerThumbnails.tsx +++ b/packages/framer-scroller/components/ScrollerThumbnails.tsx @@ -1,6 +1,7 @@ import { ImageProps } from '@graphcommerce/image' import { ButtonProps, SxProps, Theme } from '@mui/material' import { m } from 'framer-motion' +import { ImageGalleryContextProvider } from './ImageGalleryContext' import { ScrollerThumbnail } from './ScrollerThumbnail' import { ThumbnailContainer } from './ThumbnailContainer' @@ -15,20 +16,24 @@ const componentName = 'ScrollerThumbnails' export const ScrollerThumbnails = m((props: ThumbnailsProps) => { const { images, sx = [] } = props + console.log('rerendering thumbnails') + return ( - - {(items) => - items.map((item, i) => ( - - )) - } - + + + {(items) => + items.map((item, i) => ( + + )) + } + + ) }) diff --git a/packages/framer-scroller/components/ThumbnailContainer.tsx b/packages/framer-scroller/components/ThumbnailContainer.tsx index d259a57149..63c08b2e90 100644 --- a/packages/framer-scroller/components/ThumbnailContainer.tsx +++ b/packages/framer-scroller/components/ThumbnailContainer.tsx @@ -1,11 +1,14 @@ import { useMotionValueValue } from '@graphcommerce/framer-utils' import { styled, SxProps, Theme } from '@mui/material' -import { m, PanInfo, useMotionValue } from 'framer-motion' -import React, { useEffect, useMemo, useRef, useState } from 'react' +import { m, PanInfo, useMotionValue, useMotionValueEvent } from 'framer-motion' +import React, { useCallback, useMemo } from 'react' import { useScrollTo } from '../hooks/useScrollTo' import { useScrollerContext } from '../hooks/useScrollerContext' -import { ImageGallaryContext, ImageGallaryContextValues } from './ImageGalleryContext' -import { useResizeObserver } from '../hooks/useResizeObserver' +import { + ImageGallaryContext, + ImageGallaryContextValues, + useImageGalleryContext, +} from './ImageGalleryContext' const MotionBox = styled(m.div)({}) @@ -16,74 +19,55 @@ type ThumbnailContainerProps = { export function ThumbnailContainer(props: ThumbnailContainerProps) { const { children, sx } = props - const { getScrollSnapPositions, items } = useScrollerContext() - const itemsArr = useMotionValueValue(items, (i) => i) - const motionBoxRef = useRef(null) - const panValues = useMotionValue({ x: 0, y: 0 }) - const animationActive = useMotionValue(false) - const animationMode = useMotionValue<'scroll' | 'drag'>('scroll') + const { getScrollSnapPositions } = useScrollerContext() + const { items, container } = useImageGalleryContext() + const scrollIndex = useMotionValue(0) const snapPositions = getScrollSnapPositions() + const dimensions = useMotionValue({ width: 0, height: 0 }) - const scrollTo = useScrollTo() - - const objectDimensions = useResizeObserver(motionBoxRef) + const measuredRef = useCallback( + (node: HTMLDivElement) => { + if (node) { + dimensions.set(node.getBoundingClientRect()) + } + }, + [dimensions], + ) - const elementWidth = objectDimensions?.contentRect.width ?? 0 + const scrollTo = useScrollTo() - const memoizedContextValues = useMemo( - () => ({ - container: { - width: elementWidth, - pan: { coordinates: panValues }, - }, - items: itemsArr, - animation: { active: animationActive, mode: animationMode }, - }), - [elementWidth, panValues, itemsArr, animationActive, animationMode], + useMotionValueEvent(scrollIndex, 'change', (v) => + scrollTo({ x: snapPositions.x[v], y: snapPositions.y[v] }), ) - if (itemsArr.length <= 1) return null + if (items.length <= 1) return null - const onPanStart = () => { - animationActive.set(true) - animationMode.set('drag') - } + const onPanStart = () => container.pan.active.set(true) const onPan = (_event: PointerEvent, info: PanInfo) => { - panValues.set({ x: info.point.x, y: info.point.y }) + const raw = info.point.x / (dimensions.get().width / (items.length - 1)) + const idx = Math.max(0, Math.min(Math.round(raw), items.length)) + scrollIndex.set(idx) } - const onPanEnd = (_event: PointerEvent, info: PanInfo) => { - animationActive.set(false) - const raw = info.point.x / (elementWidth / itemsArr.length) - const idx = Math.max(0, Math.min(Math.round(raw), itemsArr.length - 1)) - - const x = snapPositions.x[idx] - const y = snapPositions.y[idx] ?? 0 - // eslint-disable-next-line @typescript-eslint/no-floating-promises - scrollTo({ x, y }) - animationMode.set('scroll') - } + const onPanEnd = () => container.pan.active.set(false) return ( - - - {children(itemsArr)} - - + + {children(items)} + ) } diff --git a/packages/framer-scroller/hooks/useResizeObserver.ts b/packages/framer-scroller/hooks/useResizeObserver.ts deleted file mode 100644 index 2093b835e5..0000000000 --- a/packages/framer-scroller/hooks/useResizeObserver.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { useEffect, useState } from 'react' - -export function useResizeObserver(ref: React.RefObject) { - const [resizeObserverObject, setResizeObserverObject] = useState< - ResizeObserverEntry | undefined - >() - - useEffect(() => { - if (!ref.current || ref.current === null) return () => {} - const ro = new ResizeObserver(([entry]) => { - console.log('entry', entry) - setResizeObserverObject(entry) - }) - ro.observe(ref.current) - return () => ro.disconnect() - }, [ref]) - - return resizeObserverObject -} diff --git a/packages/framer-scroller/hooks/useScrollOrScrub.ts b/packages/framer-scroller/hooks/useScrollOrScrub.ts deleted file mode 100644 index 0d03e3f1e9..0000000000 --- a/packages/framer-scroller/hooks/useScrollOrScrub.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { useMotionValueValue } from '@graphcommerce/framer-utils' -import { useTransform } from 'framer-motion' -import { useImageGalleryContext } from '../components/ImageGalleryContext' -import { useScrollerContext } from './useScrollerContext' - -export type ScrollOrScrubProps = { - activeWidth: number - idx: number -} - -export function useScrollOrScrub(props: ScrollOrScrubProps) { - const { activeWidth, idx } = props - const { scroll } = useScrollerContext() - const { items, container, animation } = useImageGalleryContext() - const active = useMotionValueValue(animation.active, (v) => v) - const mode = useMotionValueValue(animation.mode, (v) => v) - const calculateWidth = (raw: number) => { - const scrubIndex = Math.max(0, Math.min(Math.round(raw), items.length - 1)) - const progress = raw - scrubIndex + 0.5 - - if (scrubIndex === idx - 1) - return Math.max(container.width / items.length, Math.min(activeWidth * progress)) - if (scrubIndex === idx + 1) - return Math.max(container.width / items.length, Math.min(activeWidth * (1 - progress))) - if (scrubIndex === idx) return activeWidth - return 'auto' - } - - const widthOnScrub = useTransform(container.pan.coordinates, (v) => { - if (!active) return undefined - const raw = v.x / (container.width / items.length) - return calculateWidth(raw) - }) - - const widthOnScroll = useTransform(scroll.xProgress, (v) => { - if (!active) return undefined - const raw = v * (items.length - 1) - return calculateWidth(raw) - }) - if (mode === 'scroll' && active) { - return widthOnScroll - } - if (mode === 'drag' && active) { - return widthOnScrub - } - - return undefined -} From c5502d78dc4530e71574abddc8dea6aa14f568dd Mon Sep 17 00:00:00 2001 From: Mike Keehnen Date: Thu, 3 Aug 2023 15:47:31 +0200 Subject: [PATCH 15/40] Remove console.log --- packages/framer-scroller/components/ScrollerThumbnails.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/framer-scroller/components/ScrollerThumbnails.tsx b/packages/framer-scroller/components/ScrollerThumbnails.tsx index 08ce841237..97ee969675 100644 --- a/packages/framer-scroller/components/ScrollerThumbnails.tsx +++ b/packages/framer-scroller/components/ScrollerThumbnails.tsx @@ -16,8 +16,6 @@ const componentName = 'ScrollerThumbnails' export const ScrollerThumbnails = m((props: ThumbnailsProps) => { const { images, sx = [] } = props - console.log('rerendering thumbnails') - return ( From da821af185bf6419fa2aeca31e5985bf99b7bbb9 Mon Sep 17 00:00:00 2001 From: Mike Keehnen Date: Thu, 3 Aug 2023 15:51:09 +0200 Subject: [PATCH 16/40] Changeset --- .changeset/sharp-apes-burn.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/sharp-apes-burn.md diff --git a/.changeset/sharp-apes-burn.md b/.changeset/sharp-apes-burn.md new file mode 100644 index 0000000000..f181e9471b --- /dev/null +++ b/.changeset/sharp-apes-burn.md @@ -0,0 +1,5 @@ +--- +'@graphcommerce/framer-scroller': patch +--- + +Added a new Image Gallery as a plugin From bfe3ed154ba7b7fab1157133b47ee1039858b041 Mon Sep 17 00:00:00 2001 From: Mike Keehnen Date: Fri, 4 Aug 2023 11:44:37 +0200 Subject: [PATCH 17/40] Add 100% width for container width calculation --- docs/framework/config.md | 12 +++++++++--- .../components/ThumbnailContainer.tsx | 13 +++++-------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/docs/framework/config.md b/docs/framework/config.md index 2d461922a2..fda6c79248 100644 --- a/docs/framework/config.md +++ b/docs/framework/config.md @@ -203,10 +203,16 @@ To override the value for a specific locale, configure in i18n config. #### `googleRecaptchaKey: String` -Google reCAPTCHA key, get from https://developers.google.com/recaptcha/docs/v3 +Google reCAPTCHA site key. When using reCAPTCHA, this value is required, even if +you are configuring different values for each locale. -This value is required even if you are configuring different values for each -locale. +Get a site key and a secret key from +https://developers.google.com/recaptcha/docs/v3 + +The secret key should be added in the Magento admin panel (Stores > +Configuration > Security > Google ReCAPTCHA Storefront > reCAPTCHA v3 Invisible) +ReCAPTCHA can then be enabled/disabled for the different forms, separately +(Stores > Configuration > Security > Google ReCAPTCHA Storefront > Storefront) #### `googleTagmanagerId: String` diff --git a/packages/framer-scroller/components/ThumbnailContainer.tsx b/packages/framer-scroller/components/ThumbnailContainer.tsx index 63c08b2e90..0daf0bdd6c 100644 --- a/packages/framer-scroller/components/ThumbnailContainer.tsx +++ b/packages/framer-scroller/components/ThumbnailContainer.tsx @@ -1,14 +1,9 @@ -import { useMotionValueValue } from '@graphcommerce/framer-utils' import { styled, SxProps, Theme } from '@mui/material' import { m, PanInfo, useMotionValue, useMotionValueEvent } from 'framer-motion' -import React, { useCallback, useMemo } from 'react' +import React, { useCallback } from 'react' import { useScrollTo } from '../hooks/useScrollTo' import { useScrollerContext } from '../hooks/useScrollerContext' -import { - ImageGallaryContext, - ImageGallaryContextValues, - useImageGalleryContext, -} from './ImageGalleryContext' +import { ImageGallaryContextValues, useImageGalleryContext } from './ImageGalleryContext' const MotionBox = styled(m.div)({}) @@ -28,6 +23,7 @@ export function ThumbnailContainer(props: ThumbnailContainerProps) { const measuredRef = useCallback( (node: HTMLDivElement) => { if (node) { + console.log(node.getBoundingClientRect()) dimensions.set(node.getBoundingClientRect()) } }, @@ -45,7 +41,7 @@ export function ThumbnailContainer(props: ThumbnailContainerProps) { const onPanStart = () => container.pan.active.set(true) const onPan = (_event: PointerEvent, info: PanInfo) => { - const raw = info.point.x / (dimensions.get().width / (items.length - 1)) + const raw = info.point.x / (dimensions.get().width / (items.length - 1)) - 0.5 const idx = Math.max(0, Math.min(Math.round(raw), items.length)) scrollIndex.set(idx) } @@ -60,6 +56,7 @@ export function ThumbnailContainer(props: ThumbnailContainerProps) { onPanEnd={onPanEnd} sx={[ { + width: '100%', display: 'flex', flexDirection: 'row', touchAction: 'none', From 45e347437b84fc13939934fc29e3697f72374a9f Mon Sep 17 00:00:00 2001 From: Mike Keehnen Date: Tue, 8 Aug 2023 15:12:45 +0200 Subject: [PATCH 18/40] Simplified Image Gallery --- .../components/ImageGalleryContext.tsx | 2 +- .../components/ScrollerThumbnail.tsx | 97 ++++++++++--------- .../components/ScrollerThumbnails.tsx | 5 +- .../components/ThumbnailContainer.tsx | 56 +++++------ 4 files changed, 78 insertions(+), 82 deletions(-) diff --git a/packages/framer-scroller/components/ImageGalleryContext.tsx b/packages/framer-scroller/components/ImageGalleryContext.tsx index 027701b8db..fc6bad17b3 100644 --- a/packages/framer-scroller/components/ImageGalleryContext.tsx +++ b/packages/framer-scroller/components/ImageGalleryContext.tsx @@ -1,6 +1,6 @@ import { useMotionValueValue } from '@graphcommerce/framer-utils' import { MotionValue, useMotionValue } from 'framer-motion' -import React, { createContext, PropsWithChildren, useContext, useMemo } from 'react' +import React, { createContext, useContext, useMemo } from 'react' import { useScrollerContext } from '../hooks/useScrollerContext' import { ItemState } from '../types' diff --git a/packages/framer-scroller/components/ScrollerThumbnail.tsx b/packages/framer-scroller/components/ScrollerThumbnail.tsx index 3e10907ecf..1dcfb67de5 100644 --- a/packages/framer-scroller/components/ScrollerThumbnail.tsx +++ b/packages/framer-scroller/components/ScrollerThumbnail.tsx @@ -2,9 +2,8 @@ import { useMotionValueValue } from '@graphcommerce/framer-utils' import { Image, ImageProps } from '@graphcommerce/image' // eslint-disable-next-line import/no-extraneous-dependencies import { extendableComponent } from '@graphcommerce/next-ui/Styles' -import { styled } from '@mui/material' -import { m, useAnimation, Variants } from 'framer-motion' -import { useEffect } from 'react' +import { alpha, styled, useTheme } from '@mui/material' +import { m, useTransform } from 'framer-motion' import { useScrollTo } from '../hooks/useScrollTo' import { useScrollerContext } from '../hooks/useScrollerContext' import { ItemState } from '../types' @@ -26,59 +25,67 @@ const MotionBox = styled(m.div)({}) export function ScrollerThumbnail(props: ScrollerThumbnailProps) { const { visibility, idx, image } = props const scrollTo = useScrollTo() - const { getScrollSnapPositions, scroll } = useScrollerContext() - const active = useMotionValueValue(visibility, (v) => v >= 0.7) + const { getScrollSnapPositions } = useScrollerContext() const { container } = useImageGalleryContext() - const panActive = useMotionValueValue(container.pan.active, (v) => v) - const scrollIsAnimating = useMotionValueValue(scroll.animating, (v) => v) - const imageController = useAnimation() + const active = useMotionValueValue(visibility, (v) => v >= 0.5) + const theme = useTheme() + const boxShadow = useTransform( + visibility, + [1, 0], + [ + `inset 0 0 0 2px ${theme.palette.primary.main}, 0 0 0 4px ${alpha( + theme.palette.primary.main, + theme.palette.action.hoverOpacity, + )}`, + `inset 0 0 0 2px #ffffff00, 0 0 0 4px #ffffff00`, + ], + ) const classes = withState({ active }) - const positions = getScrollSnapPositions() - const currentPosition = positions.x[idx] - - const activeWidth = 120 + const itemX = positions.x[idx] - useEffect(() => { - if (active && !scrollIsAnimating && !panActive) { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - imageController.start('active') - } - // eslint-disable-next-line @typescript-eslint/no-floating-promises - else imageController.start('default') - }, [active, imageController, panActive, scrollIsAnimating]) + if (!image || !image?.width || !image?.height) return null - if (!image) return null - - const widthAnimation: Variants = { - default: { - margin: '0', - width: 'auto', - }, - active: { - margin: '0 8px', - width: activeWidth, - }, - } + const imageHeight = 120 + const minWidth = (image.width / image.height) * imageHeight return ( - - { + { + if (container.pan.active.get() === false) // eslint-disable-next-line @typescript-eslint/no-floating-promises - scrollTo({ x: currentPosition, y: positions.y[idx] ?? 0 }) - }} + scrollTo({ x: itemX, y: positions.y[idx] ?? 0 }) + }} + layout + style={{ + minWidth, + padding: '2px', + height: imageHeight, + boxShadow, + }} + sx={{ + mx: `calc(${theme.spacing(1)} / 2)`, + borderRadius: theme.shape.borderRadius, + }} + > + - - + /> ) } diff --git a/packages/framer-scroller/components/ScrollerThumbnails.tsx b/packages/framer-scroller/components/ScrollerThumbnails.tsx index 97ee969675..317312de21 100644 --- a/packages/framer-scroller/components/ScrollerThumbnails.tsx +++ b/packages/framer-scroller/components/ScrollerThumbnails.tsx @@ -1,6 +1,5 @@ import { ImageProps } from '@graphcommerce/image' import { ButtonProps, SxProps, Theme } from '@mui/material' -import { m } from 'framer-motion' import { ImageGalleryContextProvider } from './ImageGalleryContext' import { ScrollerThumbnail } from './ScrollerThumbnail' import { ThumbnailContainer } from './ThumbnailContainer' @@ -13,7 +12,7 @@ export type ThumbnailsProps = { const componentName = 'ScrollerThumbnails' -export const ScrollerThumbnails = m((props: ThumbnailsProps) => { +export function ScrollerThumbnails(props: ThumbnailsProps) { const { images, sx = [] } = props return ( @@ -33,6 +32,6 @@ export const ScrollerThumbnails = m((props: ThumbnailsProps) => { ) -}) +} ScrollerThumbnails.displayName = componentName diff --git a/packages/framer-scroller/components/ThumbnailContainer.tsx b/packages/framer-scroller/components/ThumbnailContainer.tsx index 0daf0bdd6c..b65b3281cd 100644 --- a/packages/framer-scroller/components/ThumbnailContainer.tsx +++ b/packages/framer-scroller/components/ThumbnailContainer.tsx @@ -1,6 +1,6 @@ import { styled, SxProps, Theme } from '@mui/material' -import { m, PanInfo, useMotionValue, useMotionValueEvent } from 'framer-motion' -import React, { useCallback } from 'react' +import { m, useMotionValue, useMotionValueEvent } from 'framer-motion' +import React, { useCallback, useState } from 'react' import { useScrollTo } from '../hooks/useScrollTo' import { useScrollerContext } from '../hooks/useScrollerContext' import { ImageGallaryContextValues, useImageGalleryContext } from './ImageGalleryContext' @@ -18,53 +18,43 @@ export function ThumbnailContainer(props: ThumbnailContainerProps) { const { items, container } = useImageGalleryContext() const scrollIndex = useMotionValue(0) const snapPositions = getScrollSnapPositions() - const dimensions = useMotionValue({ width: 0, height: 0 }) + const [width, setWidth] = useState(0) - const measuredRef = useCallback( - (node: HTMLDivElement) => { - if (node) { - console.log(node.getBoundingClientRect()) - dimensions.set(node.getBoundingClientRect()) - } - }, - [dimensions], - ) + const measuredRef = useCallback((node: HTMLDivElement) => { + if (node) { + setWidth(node.scrollWidth - node.offsetWidth) + } + }, []) const scrollTo = useScrollTo() useMotionValueEvent(scrollIndex, 'change', (v) => + // Scrolls when different index is selected in ThumbnailContainer scrollTo({ x: snapPositions.x[v], y: snapPositions.y[v] }), ) if (items.length <= 1) return null const onPanStart = () => container.pan.active.set(true) - - const onPan = (_event: PointerEvent, info: PanInfo) => { - const raw = info.point.x / (dimensions.get().width / (items.length - 1)) - 0.5 - const idx = Math.max(0, Math.min(Math.round(raw), items.length)) - scrollIndex.set(idx) - } - const onPanEnd = () => container.pan.active.set(false) return ( - {children(items)} + + {children(items)} + ) } From eb35c68bdf5446efd16c016b474fe9af0e614716 Mon Sep 17 00:00:00 2001 From: Mike Keehnen Date: Thu, 10 Aug 2023 10:12:11 +0200 Subject: [PATCH 19/40] Removed unused context values, removed animation when clicked from gallery and removed scrollbar --- .../components/ImageGalleryContext.tsx | 23 +++++++--- .../components/ScrollerThumbnail.tsx | 2 +- .../components/ThumbnailContainer.tsx | 46 ++++++++++--------- packages/framer-scroller/hooks/useScrollTo.ts | 10 ++-- .../framer-utils/hooks/useElementScroll.ts | 1 + 5 files changed, 49 insertions(+), 33 deletions(-) diff --git a/packages/framer-scroller/components/ImageGalleryContext.tsx b/packages/framer-scroller/components/ImageGalleryContext.tsx index fc6bad17b3..1b49cb7173 100644 --- a/packages/framer-scroller/components/ImageGalleryContext.tsx +++ b/packages/framer-scroller/components/ImageGalleryContext.tsx @@ -1,13 +1,20 @@ -import { useMotionValueValue } from '@graphcommerce/framer-utils' +import { + ScrollMotionValues, + useElementScroll, + useMotionValueValue, +} from '@graphcommerce/framer-utils' import { MotionValue, useMotionValue } from 'framer-motion' -import React, { createContext, useContext, useMemo } from 'react' +import React, { createContext, useContext, useMemo, useRef } from 'react' import { useScrollerContext } from '../hooks/useScrollerContext' import { ItemState } from '../types' export type ImageGallaryContextValues = { container: { - width?: number - pan: { active: MotionValue } + ref: React.RefObject + pan: { + active: MotionValue + value: ScrollMotionValues + } } items: ItemState[] } @@ -27,18 +34,20 @@ export function useImageGalleryContext() { export function ImageGalleryContextProvider(props: { children: React.ReactNode }) { const { children } = props + const ref = useRef(null) const { items } = useScrollerContext() const itemsArr = useMotionValueValue(items, (i) => i) const panActive = useMotionValue(false) - + const value = useElementScroll(ref) const memoizedContextValues = useMemo( () => ({ container: { - pan: { active: panActive }, + ref, + pan: { active: panActive, value }, }, items: itemsArr, }), - [panActive, itemsArr], + [value, panActive, itemsArr], ) return ( diff --git a/packages/framer-scroller/components/ScrollerThumbnail.tsx b/packages/framer-scroller/components/ScrollerThumbnail.tsx index 1dcfb67de5..23f7b11ec2 100644 --- a/packages/framer-scroller/components/ScrollerThumbnail.tsx +++ b/packages/framer-scroller/components/ScrollerThumbnail.tsx @@ -57,7 +57,7 @@ export function ScrollerThumbnail(props: ScrollerThumbnailProps) { onClick={() => { if (container.pan.active.get() === false) // eslint-disable-next-line @typescript-eslint/no-floating-promises - scrollTo({ x: itemX, y: positions.y[idx] ?? 0 }) + scrollTo({ x: itemX, y: positions.y[idx] ?? 0 }, { from: itemX }) }} layout style={{ diff --git a/packages/framer-scroller/components/ThumbnailContainer.tsx b/packages/framer-scroller/components/ThumbnailContainer.tsx index b65b3281cd..ffcd8ed3ec 100644 --- a/packages/framer-scroller/components/ThumbnailContainer.tsx +++ b/packages/framer-scroller/components/ThumbnailContainer.tsx @@ -1,6 +1,6 @@ import { styled, SxProps, Theme } from '@mui/material' -import { m, useMotionValue, useMotionValueEvent } from 'framer-motion' -import React, { useCallback, useState } from 'react' +import { m, PanHandlers, useMotionValue, useMotionValueEvent } from 'framer-motion' +import React from 'react' import { useScrollTo } from '../hooks/useScrollTo' import { useScrollerContext } from '../hooks/useScrollerContext' import { ImageGallaryContextValues, useImageGalleryContext } from './ImageGalleryContext' @@ -18,13 +18,6 @@ export function ThumbnailContainer(props: ThumbnailContainerProps) { const { items, container } = useImageGalleryContext() const scrollIndex = useMotionValue(0) const snapPositions = getScrollSnapPositions() - const [width, setWidth] = useState(0) - - const measuredRef = useCallback((node: HTMLDivElement) => { - if (node) { - setWidth(node.scrollWidth - node.offsetWidth) - } - }, []) const scrollTo = useScrollTo() @@ -38,21 +31,32 @@ export function ThumbnailContainer(props: ThumbnailContainerProps) { const onPanStart = () => container.pan.active.set(true) const onPanEnd = () => container.pan.active.set(false) + const onPan: PanHandlers['onPan'] = (_, info) => { + container.ref.current?.scrollBy({ left: -info.delta.x }) + } + return ( - + {children(items)} diff --git a/packages/framer-scroller/hooks/useScrollTo.ts b/packages/framer-scroller/hooks/useScrollTo.ts index 5286a5b5b8..68f882d4f3 100644 --- a/packages/framer-scroller/hooks/useScrollTo.ts +++ b/packages/framer-scroller/hooks/useScrollTo.ts @@ -1,10 +1,10 @@ import { MotionConfigContext, Point, Tween } from 'framer-motion' -import { animate } from 'popmotion' +import { AnimationOptions, animate } from 'popmotion' import { useCallback, useContext } from 'react' import { distanceAnimationDuration } from '../utils/distanceAnimationDuration' import { useScrollerContext } from './useScrollerContext' -type Options = { +type Options = AnimationOptions & { stopAnimationOnScroll?: boolean } @@ -17,7 +17,7 @@ export function useScrollTo() { const scrollTo = useCallback( async ( incoming: Point | [number, number], - options: Options = { stopAnimationOnScroll: true }, + options: Options = { stopAnimationOnScroll: true }, __retrigger = 0, ) => { const ref = scrollerRef.current @@ -71,6 +71,7 @@ export function useScrollTo() { onComplete, onStop: onComplete, duration: duration * 1000 || distanceAnimationDuration(ref.scrollLeft, to.x), + ...options, }), ) } else onComplete() @@ -93,7 +94,8 @@ export function useScrollTo() { }, onComplete, onStop: onComplete, - duration: duration * 1000 || distanceAnimationDuration(ref.scrollTop, to.y), + duration: 0, + ...options, }), ) } else { diff --git a/packages/framer-utils/hooks/useElementScroll.ts b/packages/framer-utils/hooks/useElementScroll.ts index 563483d98e..9eb72b0b76 100644 --- a/packages/framer-utils/hooks/useElementScroll.ts +++ b/packages/framer-utils/hooks/useElementScroll.ts @@ -35,6 +35,7 @@ export function useElementScroll(ref?: RefObject): Scro useIsomorphicLayoutEffect(() => { const element = ref?.current + console.log('element', element?.id) if (!element) return () => {} const updater = () => { From c7561d82589233fcb6656157b817fbc8df213e3e Mon Sep 17 00:00:00 2001 From: Mike Keehnen Date: Thu, 10 Aug 2023 11:45:22 +0200 Subject: [PATCH 20/40] Remove motion event on container --- .../framer-scroller/components/ThumbnailContainer.tsx | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/packages/framer-scroller/components/ThumbnailContainer.tsx b/packages/framer-scroller/components/ThumbnailContainer.tsx index ffcd8ed3ec..09ad8e4ff8 100644 --- a/packages/framer-scroller/components/ThumbnailContainer.tsx +++ b/packages/framer-scroller/components/ThumbnailContainer.tsx @@ -14,17 +14,7 @@ type ThumbnailContainerProps = { export function ThumbnailContainer(props: ThumbnailContainerProps) { const { children, sx } = props - const { getScrollSnapPositions } = useScrollerContext() const { items, container } = useImageGalleryContext() - const scrollIndex = useMotionValue(0) - const snapPositions = getScrollSnapPositions() - - const scrollTo = useScrollTo() - - useMotionValueEvent(scrollIndex, 'change', (v) => - // Scrolls when different index is selected in ThumbnailContainer - scrollTo({ x: snapPositions.x[v], y: snapPositions.y[v] }), - ) if (items.length <= 1) return null From acb1549bdb0e1a23e02fa80aa5f291f261497d93 Mon Sep 17 00:00:00 2001 From: Mike Keehnen Date: Thu, 10 Aug 2023 12:31:08 +0200 Subject: [PATCH 21/40] Feedback fixes --- .../components/ScrollerThumbnail.tsx | 42 ++++++++----------- .../components/ThumbnailContainer.tsx | 4 +- packages/framer-scroller/hooks/useScrollTo.ts | 6 +-- 3 files changed, 20 insertions(+), 32 deletions(-) diff --git a/packages/framer-scroller/components/ScrollerThumbnail.tsx b/packages/framer-scroller/components/ScrollerThumbnail.tsx index 23f7b11ec2..422fd3e9ae 100644 --- a/packages/framer-scroller/components/ScrollerThumbnail.tsx +++ b/packages/framer-scroller/components/ScrollerThumbnail.tsx @@ -1,7 +1,7 @@ import { useMotionValueValue } from '@graphcommerce/framer-utils' import { Image, ImageProps } from '@graphcommerce/image' // eslint-disable-next-line import/no-extraneous-dependencies -import { extendableComponent } from '@graphcommerce/next-ui/Styles' +import { extendableComponent, responsiveVal } from '@graphcommerce/next-ui/Styles' import { alpha, styled, useTheme } from '@mui/material' import { m, useTransform } from 'framer-motion' import { useScrollTo } from '../hooks/useScrollTo' @@ -25,10 +25,10 @@ const MotionBox = styled(m.div)({}) export function ScrollerThumbnail(props: ScrollerThumbnailProps) { const { visibility, idx, image } = props const scrollTo = useScrollTo() - const { getScrollSnapPositions } = useScrollerContext() const { container } = useImageGalleryContext() const active = useMotionValueValue(visibility, (v) => v >= 0.5) const theme = useTheme() + const { scrollerRef, scroll, getScrollSnapPositions } = useScrollerContext() const boxShadow = useTransform( visibility, @@ -42,49 +42,41 @@ export function ScrollerThumbnail(props: ScrollerThumbnailProps) { ], ) const classes = withState({ active }) - const positions = getScrollSnapPositions() - const itemX = positions.x[idx] if (!image || !image?.width || !image?.height) return null - const imageHeight = 120 - const minWidth = (image.width / image.height) * imageHeight - return ( { - if (container.pan.active.get() === false) - // eslint-disable-next-line @typescript-eslint/no-floating-promises - scrollTo({ x: itemX, y: positions.y[idx] ?? 0 }, { from: itemX }) + if (container.pan.active.get() === false) { + if (!scrollerRef.current) return + const { x } = getScrollSnapPositions() + scrollerRef.current.scrollLeft = x[idx] + scroll.x.set(x[idx]) + } }} layout - style={{ - minWidth, - padding: '2px', - height: imageHeight, - boxShadow, - }} + style={{ boxShadow }} sx={{ + padding: '2px', mx: `calc(${theme.spacing(1)} / 2)`, borderRadius: theme.shape.borderRadius, }} > ) diff --git a/packages/framer-scroller/components/ThumbnailContainer.tsx b/packages/framer-scroller/components/ThumbnailContainer.tsx index 09ad8e4ff8..fa23f5f7e2 100644 --- a/packages/framer-scroller/components/ThumbnailContainer.tsx +++ b/packages/framer-scroller/components/ThumbnailContainer.tsx @@ -46,9 +46,7 @@ export function ThumbnailContainer(props: ThumbnailContainerProps) { ...(Array.isArray(sx) ? sx : [sx]), ]} > - - {children(items)} - + {children(items)} ) } diff --git a/packages/framer-scroller/hooks/useScrollTo.ts b/packages/framer-scroller/hooks/useScrollTo.ts index 68f882d4f3..798b4d543f 100644 --- a/packages/framer-scroller/hooks/useScrollTo.ts +++ b/packages/framer-scroller/hooks/useScrollTo.ts @@ -1,10 +1,10 @@ import { MotionConfigContext, Point, Tween } from 'framer-motion' -import { AnimationOptions, animate } from 'popmotion' +import { animate } from 'popmotion' import { useCallback, useContext } from 'react' import { distanceAnimationDuration } from '../utils/distanceAnimationDuration' import { useScrollerContext } from './useScrollerContext' -type Options = AnimationOptions & { +type Options = { stopAnimationOnScroll?: boolean } @@ -71,7 +71,6 @@ export function useScrollTo() { onComplete, onStop: onComplete, duration: duration * 1000 || distanceAnimationDuration(ref.scrollLeft, to.x), - ...options, }), ) } else onComplete() @@ -95,7 +94,6 @@ export function useScrollTo() { onComplete, onStop: onComplete, duration: 0, - ...options, }), ) } else { From f34017eeeb539fb712865632a292acae7f8dc479 Mon Sep 17 00:00:00 2001 From: Mike Keehnen Date: Mon, 14 Aug 2023 09:32:48 +0200 Subject: [PATCH 22/40] item.visibility error --- .../components/ScrollerThumbnail.tsx | 28 +++++++---------- .../components/ScrollerThumbnails.tsx | 30 ++++++++----------- .../components/ThumbnailContainer.tsx | 24 +++++---------- 3 files changed, 30 insertions(+), 52 deletions(-) diff --git a/packages/framer-scroller/components/ScrollerThumbnail.tsx b/packages/framer-scroller/components/ScrollerThumbnail.tsx index 422fd3e9ae..e865e23262 100644 --- a/packages/framer-scroller/components/ScrollerThumbnail.tsx +++ b/packages/framer-scroller/components/ScrollerThumbnail.tsx @@ -4,10 +4,7 @@ import { Image, ImageProps } from '@graphcommerce/image' import { extendableComponent, responsiveVal } from '@graphcommerce/next-ui/Styles' import { alpha, styled, useTheme } from '@mui/material' import { m, useTransform } from 'framer-motion' -import { useScrollTo } from '../hooks/useScrollTo' import { useScrollerContext } from '../hooks/useScrollerContext' -import { ItemState } from '../types' -import { useImageGalleryContext } from './ImageGalleryContext' const name = 'ScrollerThumbnail' const parts = ['thumbnail'] as const @@ -15,7 +12,7 @@ type OwnerProps = { active: boolean } const { withState } = extendableComponent(name, parts) -type ScrollerThumbnailProps = ItemState & { +type ScrollerThumbnailProps = { idx: number image: Pick } @@ -23,15 +20,14 @@ type ScrollerThumbnailProps = ItemState & { const MotionBox = styled(m.div)({}) export function ScrollerThumbnail(props: ScrollerThumbnailProps) { - const { visibility, idx, image } = props - const scrollTo = useScrollTo() - const { container } = useImageGalleryContext() - const active = useMotionValueValue(visibility, (v) => v >= 0.5) + const { idx, image } = props + const { scrollerRef, scroll, getScrollSnapPositions, items } = useScrollerContext() + const item = items.get()[idx] + const active = useMotionValueValue(item.visibility, (v) => v >= 0.5) const theme = useTheme() - const { scrollerRef, scroll, getScrollSnapPositions } = useScrollerContext() const boxShadow = useTransform( - visibility, + item.visibility, [1, 0], [ `inset 0 0 0 2px ${theme.palette.primary.main}, 0 0 0 4px ${alpha( @@ -43,19 +39,17 @@ export function ScrollerThumbnail(props: ScrollerThumbnailProps) { ) const classes = withState({ active }) - if (!image || !image?.width || !image?.height) return null + if (!image) return null return ( { - if (container.pan.active.get() === false) { - if (!scrollerRef.current) return - const { x } = getScrollSnapPositions() - scrollerRef.current.scrollLeft = x[idx] - scroll.x.set(x[idx]) - } + if (!scrollerRef.current) return + const { x } = getScrollSnapPositions() + scrollerRef.current.scrollLeft = x[idx] + scroll.x.set(x[idx]) }} layout style={{ boxShadow }} diff --git a/packages/framer-scroller/components/ScrollerThumbnails.tsx b/packages/framer-scroller/components/ScrollerThumbnails.tsx index 317312de21..79fabd47c1 100644 --- a/packages/framer-scroller/components/ScrollerThumbnails.tsx +++ b/packages/framer-scroller/components/ScrollerThumbnails.tsx @@ -1,6 +1,5 @@ import { ImageProps } from '@graphcommerce/image' import { ButtonProps, SxProps, Theme } from '@mui/material' -import { ImageGalleryContextProvider } from './ImageGalleryContext' import { ScrollerThumbnail } from './ScrollerThumbnail' import { ThumbnailContainer } from './ThumbnailContainer' @@ -13,24 +12,19 @@ export type ThumbnailsProps = { const componentName = 'ScrollerThumbnails' export function ScrollerThumbnails(props: ThumbnailsProps) { - const { images, sx = [] } = props - + const { images, sx = [], ...buttonProps } = props return ( - - - {(items) => - items.map((item, i) => ( - - )) - } - - + + {images.map((item, i) => ( + + ))} + ) } diff --git a/packages/framer-scroller/components/ThumbnailContainer.tsx b/packages/framer-scroller/components/ThumbnailContainer.tsx index fa23f5f7e2..fdd95b7943 100644 --- a/packages/framer-scroller/components/ThumbnailContainer.tsx +++ b/packages/framer-scroller/components/ThumbnailContainer.tsx @@ -1,35 +1,25 @@ import { styled, SxProps, Theme } from '@mui/material' -import { m, PanHandlers, useMotionValue, useMotionValueEvent } from 'framer-motion' -import React from 'react' -import { useScrollTo } from '../hooks/useScrollTo' -import { useScrollerContext } from '../hooks/useScrollerContext' -import { ImageGallaryContextValues, useImageGalleryContext } from './ImageGalleryContext' +import { m, PanHandlers } from 'framer-motion' +import React, { useRef } from 'react' const MotionBox = styled(m.div)({}) type ThumbnailContainerProps = { - children: (items: ImageGallaryContextValues['items']) => React.ReactNode + children: React.ReactNode sx?: SxProps } export function ThumbnailContainer(props: ThumbnailContainerProps) { const { children, sx } = props - const { items, container } = useImageGalleryContext() - - if (items.length <= 1) return null - - const onPanStart = () => container.pan.active.set(true) - const onPanEnd = () => container.pan.active.set(false) + const containerRef = useRef(null) const onPan: PanHandlers['onPan'] = (_, info) => { - container.ref.current?.scrollBy({ left: -info.delta.x }) + containerRef.current?.scrollBy({ left: -info.delta.x }) } return ( - {children(items)} + {children} ) } From 27de6bfd7de7579b61b54026260a9e8db2057310 Mon Sep 17 00:00:00 2001 From: Mike Keehnen Date: Mon, 14 Aug 2023 12:08:37 +0200 Subject: [PATCH 23/40] Remove context, improved render time and added styling to image container --- .../components/ImageGalleryContext.tsx | 58 ------------------- .../components/ScrollerThumbnail.tsx | 10 +++- .../components/ThumbnailContainer.tsx | 1 + 3 files changed, 9 insertions(+), 60 deletions(-) delete mode 100644 packages/framer-scroller/components/ImageGalleryContext.tsx diff --git a/packages/framer-scroller/components/ImageGalleryContext.tsx b/packages/framer-scroller/components/ImageGalleryContext.tsx deleted file mode 100644 index 1b49cb7173..0000000000 --- a/packages/framer-scroller/components/ImageGalleryContext.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { - ScrollMotionValues, - useElementScroll, - useMotionValueValue, -} from '@graphcommerce/framer-utils' -import { MotionValue, useMotionValue } from 'framer-motion' -import React, { createContext, useContext, useMemo, useRef } from 'react' -import { useScrollerContext } from '../hooks/useScrollerContext' -import { ItemState } from '../types' - -export type ImageGallaryContextValues = { - container: { - ref: React.RefObject - pan: { - active: MotionValue - value: ScrollMotionValues - } - } - items: ItemState[] -} - -export const ImageGallaryContext = createContext(undefined) - -export function useImageGalleryContext() { - const context = useContext(ImageGallaryContext) - if (typeof context === 'undefined') { - throw Error( - 'useImageGalleryContext must be used within a ImageGallaryContext.Provider component', - ) - } - - return context -} - -export function ImageGalleryContextProvider(props: { children: React.ReactNode }) { - const { children } = props - const ref = useRef(null) - const { items } = useScrollerContext() - const itemsArr = useMotionValueValue(items, (i) => i) - const panActive = useMotionValue(false) - const value = useElementScroll(ref) - const memoizedContextValues = useMemo( - () => ({ - container: { - ref, - pan: { active: panActive, value }, - }, - items: itemsArr, - }), - [value, panActive, itemsArr], - ) - - return ( - - {children} - - ) -} diff --git a/packages/framer-scroller/components/ScrollerThumbnail.tsx b/packages/framer-scroller/components/ScrollerThumbnail.tsx index e865e23262..8d92661c31 100644 --- a/packages/framer-scroller/components/ScrollerThumbnail.tsx +++ b/packages/framer-scroller/components/ScrollerThumbnail.tsx @@ -3,7 +3,7 @@ import { Image, ImageProps } from '@graphcommerce/image' // eslint-disable-next-line import/no-extraneous-dependencies import { extendableComponent, responsiveVal } from '@graphcommerce/next-ui/Styles' import { alpha, styled, useTheme } from '@mui/material' -import { m, useTransform } from 'framer-motion' +import { m, motionValue, useTransform } from 'framer-motion' import { useScrollerContext } from '../hooks/useScrollerContext' const name = 'ScrollerThumbnail' @@ -22,7 +22,13 @@ const MotionBox = styled(m.div)({}) export function ScrollerThumbnail(props: ScrollerThumbnailProps) { const { idx, image } = props const { scrollerRef, scroll, getScrollSnapPositions, items } = useScrollerContext() - const item = items.get()[idx] + const found = useMotionValueValue(items, (v) => v.find((_, index) => index === idx)) + // This ensures that the first item in the scroller is selected by default. + // The opacity property is set to 0 by default. + const item = found ?? { + visibility: idx === 0 ? motionValue(1) : motionValue(0), + opacity: motionValue(0), + } const active = useMotionValueValue(item.visibility, (v) => v >= 0.5) const theme = useTheme() diff --git a/packages/framer-scroller/components/ThumbnailContainer.tsx b/packages/framer-scroller/components/ThumbnailContainer.tsx index fdd95b7943..17dabaa4df 100644 --- a/packages/framer-scroller/components/ThumbnailContainer.tsx +++ b/packages/framer-scroller/components/ThumbnailContainer.tsx @@ -23,6 +23,7 @@ export function ThumbnailContainer(props: ThumbnailContainerProps) { onPan={onPan} sx={[ { + padding: '4px', userSelect: 'none', cursor: 'grab', overflow: 'none', From 0c23cf130ab990e403f22e3a81b563d16f6ab423 Mon Sep 17 00:00:00 2001 From: Mike Keehnen Date: Mon, 14 Aug 2023 12:10:06 +0200 Subject: [PATCH 24/40] Revert config.md --- docs/framework/config.md | 175 ++++++++++++++------------------------- 1 file changed, 64 insertions(+), 111 deletions(-) diff --git a/docs/framework/config.md b/docs/framework/config.md index fda6c79248..578a5fc0ba 100644 --- a/docs/framework/config.md +++ b/docs/framework/config.md @@ -1,28 +1,19 @@ - # GraphCommerce configuration system -Global GraphCommerce configuration can be configured in your -`graphcommerce.config.js` file in the root of your project and are automatically -validated on startup. +Global GraphCommerce configuration can be configured in your `graphcommerce.config.js` file +in the root of your project and are automatically validated on startup. ## Configuring with the configuration file. -The configuration file is a javascript file that exports a `GraphCommerceConfig` -object. See graphcommerce.config.js.example for an example. +The configuration file is a javascript file that exports a `GraphCommerceConfig` object. See graphcommerce.config.js.example for an example. ## Using configuration -Configuration can be accessed in your project with the -`import.meta.graphCommerce` object. +Configuration can be accessed in your project with the `import.meta.graphCommerce` object. ```tsx -import { - storefrontAll, - storefrontConfig, - storefrontConfigDefault, - useStorefrontConfig, -} from '@graphcommerce/next-ui' +import { storefrontAll, storefrontConfig, storefrontConfigDefault, useStorefrontConfig } from '@graphcommerce/next-ui' // Accessing a global value const globalConf = import.meta.graphCommerce.cartDisplayPricesInclTax @@ -36,8 +27,7 @@ function MyComponent() { // Or as single line const scopedConfigWithFallback2 = - useStorefrontConfig().cartDisplayPricesInclTax ?? - import.meta.graphCommerce.cartDisplayPricesInclTax + useStorefrontConfig().cartDisplayPricesInclTax ?? import.meta.graphCommerce.cartDisplayPricesInclTax return
{googleRecaptchaKey}
} @@ -52,28 +42,25 @@ endpoint: '{graphCommerce.magentoEndpoint}' ## Environment variables to override configuration -Configuration values can be overwriten by environment variables, with the -following rules: - +Configuration values can be overwriten by environment variables, with the following rules: - Convert from camelCase to `SCREAMING_SNAKE_CASE` - Prefix with `GC_` - Arrays can be indexed with `_0`, `_1`, `_2`, etc. - Objects can be accessed with `_`. Examples: - - `limitSsg` -> `GC_LIMIT_SSG="1"` - `storefront[0].locale` -> `GC_STOREFRONT_0_LOCALE="en"` - `debug.pluginStatus` -> `GC_DEBUG_PLUGIN_STATUS="1"` + ## Exporting current configuration to environment variables You can export configuration by running `yarn graphcommerce export-config` -## Extending the configuration in your project +## Extending the configuration in your project -Create a graphql/Config.graphqls file in your project and extend the -GraphCommerceConfig, GraphCommerceStorefrontConfig inputs to add configuration. +Create a graphql/Config.graphqls file in your project and extend the GraphCommerceConfig, GraphCommerceStorefrontConfig inputs to add configuration. ```graphql extend input GraphCommerceConfig { @@ -95,7 +82,6 @@ Below is a list of all possible configurations that can be set by GraphCommerce. The canonical base URL is used for SEO purposes. Examples: - - https://example.com - https://example.com/en - https://example.com/en-US @@ -104,8 +90,7 @@ Examples: The HyGraph endpoint. -> Read-only endpoint that allows low latency and high read-throughput content -> delivery. +> Read-only endpoint that allows low latency and high read-throughput content delivery. Project settings -> API Access -> High Performance Read-only Content API @@ -114,7 +99,6 @@ Project settings -> API Access -> High Performance Read-only Content API GraphQL Magento endpoint. Examples: - - https://magento2.test/graphql #### `storefront: [[GraphCommerceStorefrontConfig](#GraphCommerceStorefrontConfig)!]!` @@ -123,8 +107,7 @@ All storefront configuration for the project #### `cartDisplayPricesInclTax: Boolean` -Due to a limitation of the GraphQL API it is not possible to determine if a cart -should be displayed including or excluding tax. +Due to a limitation of the GraphQL API it is not possible to determine if a cart should be displayed including or excluding tax. When Magento's StoreConfig adds this value, this can be replaced. @@ -134,30 +117,25 @@ Use compare functionality #### `compareVariant: [CompareVariant](#CompareVariant) (default: ICON)` -By default the compare feature is denoted with a 'compare ICON' (2 arrows facing -one another). This may be fine for experienced users, but for more clarity it's -also possible to present the compare feature as a CHECKBOX accompanied by the -'Compare' label +By default the compare feature is denoted with a 'compare ICON' (2 arrows facing one another). +This may be fine for experienced users, but for more clarity it's also possible to present the compare feature as a CHECKBOX accompanied by the 'Compare' label #### `configurableVariantForSimple: Boolean (default: [object Object])` -If a simple product is part of a Configurable product page, should the simple -product be rendered as a configured option of the configurable product page? +If a simple product is part of a Configurable product page, should the simple product be +rendered as a configured option of the configurable product page? How does this work: -When the `products(filters: { url_key: { eq: 'simple-product' } }) { ... }` -query is ran, Magento also returns the Simple product and the Configurable -product the simple belongs to. +When the `products(filters: { url_key: { eq: 'simple-product' } }) { ... }` query is ran, +Magento also returns the Simple product and the Configurable product the simple belongs to. -If that is the case we render the configurable product page instead of the -simple product page but the options to select the simple product are -pre-selected. +If that is the case we render the configurable product page instead of the simple product page but +the options to select the simple product are pre-selected. #### `configurableVariantValues: [MagentoConfigurableVariantValues](#MagentoConfigurableVariantValues) (default: [object Object])` -When a user selects a variant, it will switch the values on the configurable -page with the values of the configured variant. +When a user selects a variant, it will switch the values on the configurable page with the values of the configured variant. Enabling options here will allow switching of those variants. @@ -203,23 +181,19 @@ To override the value for a specific locale, configure in i18n config. #### `googleRecaptchaKey: String` -Google reCAPTCHA site key. When using reCAPTCHA, this value is required, even if -you are configuring different values for each locale. +Google reCAPTCHA site key. +When using reCAPTCHA, this value is required, even if you are configuring different values for each locale. -Get a site key and a secret key from -https://developers.google.com/recaptcha/docs/v3 +Get a site key and a secret key from https://developers.google.com/recaptcha/docs/v3 -The secret key should be added in the Magento admin panel (Stores > -Configuration > Security > Google ReCAPTCHA Storefront > reCAPTCHA v3 Invisible) -ReCAPTCHA can then be enabled/disabled for the different forms, separately -(Stores > Configuration > Security > Google ReCAPTCHA Storefront > Storefront) +The secret key should be added in the Magento admin panel (Stores > Configuration > Security > Google ReCAPTCHA Storefront > reCAPTCHA v3 Invisible) +ReCAPTCHA can then be enabled/disabled for the different forms, separately (Stores > Configuration > Security > Google ReCAPTCHA Storefront > Storefront) #### `googleTagmanagerId: String` The Google Tagmanager ID to be used on the site. -This value is required even if you are configuring different values for each -locale. +This value is required even if you are configuring different values for each locale. #### `hygraphProjectId: String` @@ -229,8 +203,7 @@ Hygraph Project ID. **Only used for migrations.** Content API. **Only used for migrations.** -> Regular read & write endpoint that allows querying and mutating data in your -> project. +> Regular read & write endpoint that allows querying and mutating data in your project. Project settings -> API Access -> Content API @@ -240,22 +213,19 @@ Hygraph Management SDK Authorization Token. **Only used for migrations.** Project settings -> API Access -> Permanent Auth Tokens -1. Click 'Add token' and give it a name, something like 'GraphCommerce Write - Access Token' and keep stage on 'Published'. +1. Click 'Add token' and give it a name, something like 'GraphCommerce Write Access Token' and keep stage on 'Published'. 2. Under 'Management API', click 'Yes, Initialize defaults' -3. Click 'Edit Permissions' and enable: 'Update' and 'Delete' permissions for - 'models', 'enumerations', 'fields', 'components' and 'sources' - -- Update existing models -- Delete existing models -- Update existing fields -- Delete existing fields -- Update existing enumerations -- Delete existing enumerations -- Update existing components -- Delete existing components -- Update remote sources -- Delete remote sources +3. Click 'Edit Permissions' and enable: 'Update' and 'Delete' permissions for 'models', 'enumerations', 'fields', 'components' and 'sources' + - Update existing models + - Delete existing models + - Update existing fields + - Delete existing fields + - Update existing enumerations + - Delete existing enumerations + - Update existing components + - Delete existing components + - Update remote sources + - Delete remote sources ``` GC_HYGRAPH_WRITE_ACCESS_ENDPOINT="https://...hygraph.com/v2/..." @@ -265,14 +235,11 @@ yarn graphcommerce hygraph-migrate #### `legacyProductRoute: Boolean` -On older versions of GraphCommerce products would use a product type specific -route. +On older versions of GraphCommerce products would use a product type specific route. -This should only be set to true if you use the /product/[url] AND -/product/configurable/[url] routes. +This should only be set to true if you use the /product/[url] AND /product/configurable/[url] routes. -@deprecated Will be removed in a future version. -[migration](../upgrading/graphcommerce-5-to-6.md#product-routing-changes) +@deprecated Will be removed in a future version. [migration](../upgrading/graphcommerce-5-to-6.md#product-routing-changes) #### `limitSsg: Boolean` @@ -284,9 +251,9 @@ To enable next.js' preview mode, configure the secret you'd like to use. #### `productFiltersLayout: [ProductFiltersLayout](#ProductFiltersLayout) (default: DEFAULT)` -Layout how the filters are rendered. DEFAULT: Will be rendered as horzontal -chips on desktop and mobile SIDEBAR: Will be rendered as a sidebar on desktop -and horizontal chips on mobile +Layout how the filters are rendered. +DEFAULT: Will be rendered as horzontal chips on desktop and mobile +SIDEBAR: Will be rendered as a sidebar on desktop and horizontal chips on mobile #### `productFiltersPro: Boolean` @@ -294,15 +261,15 @@ Product filters with better UI for mobile and desktop. #### `productRoute: String` -By default we route products to /p/[url] but you can change this to -/product/[url] if you wish. +By default we route products to /p/[url] but you can change this to /product/[url] if you wish. -Default: '/p/' Example: '/product/' +Default: '/p/' +Example: '/product/' #### `robotsAllow: Boolean` -Allow the site to be indexed by search engines. If false, the robots.txt file -will be set to disallow all. +Allow the site to be indexed by search engines. +If false, the robots.txt file will be set to disallow all. #### `wishlistHideForGuests: Boolean` @@ -310,8 +277,7 @@ Hide the wishlist functionality for guests. #### `wishlistIgnoreProductWishlistStatus: Boolean` -Ignores whether a product is already in the wishlist, makes the toggle an add -only. +Ignores whether a product is already in the wishlist, makes the toggle an add only. #### `wishlistShowFeedbackMessage: Boolean` @@ -327,22 +293,18 @@ Reports which plugins are enabled or disabled. #### `webpackCircularDependencyPlugin: Boolean` -Cyclic dependencies can cause memory issues and other strange bugs. This plugin -will warn you when it detects a cyclic dependency. +Cyclic dependencies can cause memory issues and other strange bugs. +This plugin will warn you when it detects a cyclic dependency. When running into memory issues, it can be useful to enable this plugin. #### `webpackDuplicatesPlugin: Boolean` -When updating packages it can happen that the same package is included with -different versions in the same project. +When updating packages it can happen that the same package is included with different versions in the same project. Issues that this can cause are: - -- The same package is included multiple times in the bundle, increasing the - bundle size. -- The Typescript types of the package are not compatible with each other, - causing Typescript errors. +- The same package is included multiple times in the bundle, increasing the bundle size. +- The Typescript types of the package are not compatible with each other, causing Typescript errors. ### GraphCommerceStorefrontConfig @@ -350,8 +312,7 @@ All storefront configuration for the project #### `locale: String!` -Must be a locale string -https://www.unicode.org/reports/tr35/tr35-59/tr35.html#Identifiers +Must be a locale string https://www.unicode.org/reports/tr35/tr35-59/tr35.html#Identifiers #### `magentoStoreCode: String!` @@ -360,7 +321,6 @@ Magento store code. Stores => All Stores => [Store View] => Store View Code Examples: - - default - en-us - b2b-us @@ -370,20 +330,17 @@ Examples: The canonical base URL is used for SEO purposes. Examples: - - https://example.com - https://example.com/en - https://example.com/en-US #### `cartDisplayPricesInclTax: Boolean` -Due to a limitation of the GraphQL API it is not possible to determine if a cart -should be displayed including or excluding tax. +Due to a limitation of the GraphQL API it is not possible to determine if a cart should be displayed including or excluding tax. #### `defaultLocale: Boolean` There can only be one entry with defaultLocale set to true. - - If there are more, the first one is used. - If there is none, the first entry is used. @@ -407,8 +364,7 @@ The Google Tagmanager ID to be used per locale. #### `hygraphLocales: [String!]` -Add a gcms-locales header to make sure queries return in a certain language, can -be an array to define fallbacks. +Add a gcms-locales header to make sure queries return in a certain language, can be an array to define fallbacks. #### `linguiLocale: String` @@ -416,13 +372,11 @@ Specify a custom locale for to load translations. ### MagentoConfigurableVariantValues -Options to configure which values will be replaced when a variant is selected on -the product page. +Options to configure which values will be replaced when a variant is selected on the product page. #### `content: Boolean` -Use the name, description, short description and meta data from the configured -variant +Use the name, description, short description and meta data from the configured variant #### `gallery: Boolean` @@ -432,7 +386,6 @@ the selected variant differ from the currently displayed images. #### `url: Boolean` -When a variant is selected the URL of the product will be changed in the address -bar. +When a variant is selected the URL of the product will be changed in the address bar. -This only happens when the actual variant is can be accessed by the URL. +This only happens when the actual variant is can be accessed by the URL. \ No newline at end of file From bf24ae9c42f39fc24cf2e32037c1b5b03ad4ea34 Mon Sep 17 00:00:00 2001 From: Mike Keehnen Date: Mon, 14 Aug 2023 12:11:40 +0200 Subject: [PATCH 25/40] revert useScrollTo --- packages/framer-scroller/hooks/useScrollTo.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/framer-scroller/hooks/useScrollTo.ts b/packages/framer-scroller/hooks/useScrollTo.ts index 798b4d543f..5286a5b5b8 100644 --- a/packages/framer-scroller/hooks/useScrollTo.ts +++ b/packages/framer-scroller/hooks/useScrollTo.ts @@ -4,7 +4,7 @@ import { useCallback, useContext } from 'react' import { distanceAnimationDuration } from '../utils/distanceAnimationDuration' import { useScrollerContext } from './useScrollerContext' -type Options = { +type Options = { stopAnimationOnScroll?: boolean } @@ -17,7 +17,7 @@ export function useScrollTo() { const scrollTo = useCallback( async ( incoming: Point | [number, number], - options: Options = { stopAnimationOnScroll: true }, + options: Options = { stopAnimationOnScroll: true }, __retrigger = 0, ) => { const ref = scrollerRef.current @@ -93,7 +93,7 @@ export function useScrollTo() { }, onComplete, onStop: onComplete, - duration: 0, + duration: duration * 1000 || distanceAnimationDuration(ref.scrollTop, to.y), }), ) } else { From d49e73bd358d20b9e9344505a12aebd96724e360 Mon Sep 17 00:00:00 2001 From: Mike Keehnen Date: Tue, 15 Aug 2023 10:09:50 +0200 Subject: [PATCH 26/40] Fix smooth scoll animation --- .../components/ScrollerThumbnail.tsx | 20 ++++++++++++++++--- .../components/ThumbnailContainer.tsx | 5 ++--- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/packages/framer-scroller/components/ScrollerThumbnail.tsx b/packages/framer-scroller/components/ScrollerThumbnail.tsx index 8d92661c31..99b1761882 100644 --- a/packages/framer-scroller/components/ScrollerThumbnail.tsx +++ b/packages/framer-scroller/components/ScrollerThumbnail.tsx @@ -3,7 +3,8 @@ import { Image, ImageProps } from '@graphcommerce/image' // eslint-disable-next-line import/no-extraneous-dependencies import { extendableComponent, responsiveVal } from '@graphcommerce/next-ui/Styles' import { alpha, styled, useTheme } from '@mui/material' -import { m, motionValue, useTransform } from 'framer-motion' +import { m, motionValue, useMotionValueEvent, useTransform } from 'framer-motion' +import { useEffect, useRef } from 'react' import { useScrollerContext } from '../hooks/useScrollerContext' const name = 'ScrollerThumbnail' @@ -32,6 +33,8 @@ export function ScrollerThumbnail(props: ScrollerThumbnailProps) { const active = useMotionValueValue(item.visibility, (v) => v >= 0.5) const theme = useTheme() + const ref = useRef(null) + const boxShadow = useTransform( item.visibility, [1, 0], @@ -43,21 +46,32 @@ export function ScrollerThumbnail(props: ScrollerThumbnailProps) { `inset 0 0 0 2px #ffffff00, 0 0 0 4px #ffffff00`, ], ) + const classes = withState({ active }) + const scrollIntoView = () => ref.current?.scrollIntoView({ block: 'nearest', behavior: 'smooth' }) + + useMotionValueEvent(scroll.animating, 'change', (v) => { + if (!v && active && ref.current) { + // This is a hack to ensure that the scroll animation is finished. + setTimeout(() => scrollIntoView(), 1) + } + }) + if (!image) return null return ( { if (!scrollerRef.current) return const { x } = getScrollSnapPositions() scrollerRef.current.scrollLeft = x[idx] scroll.x.set(x[idx]) + scrollIntoView() }} - layout + // layout style={{ boxShadow }} sx={{ padding: '2px', diff --git a/packages/framer-scroller/components/ThumbnailContainer.tsx b/packages/framer-scroller/components/ThumbnailContainer.tsx index 17dabaa4df..ba250b1d1c 100644 --- a/packages/framer-scroller/components/ThumbnailContainer.tsx +++ b/packages/framer-scroller/components/ThumbnailContainer.tsx @@ -12,7 +12,6 @@ type ThumbnailContainerProps = { export function ThumbnailContainer(props: ThumbnailContainerProps) { const { children, sx } = props const containerRef = useRef(null) - const onPan: PanHandlers['onPan'] = (_, info) => { containerRef.current?.scrollBy({ left: -info.delta.x }) } @@ -28,7 +27,7 @@ export function ThumbnailContainer(props: ThumbnailContainerProps) { cursor: 'grab', overflow: 'none', overflowX: 'auto', - display: 'block', + display: 'flex', scrollbarWidth: 'none', '&::-webkit-scrollbar': { display: 'none', @@ -37,7 +36,7 @@ export function ThumbnailContainer(props: ThumbnailContainerProps) { ...(Array.isArray(sx) ? sx : [sx]), ]} > - {children} + {children} ) } From bb511feccd17e41cfd0cc1053657c00bf9382f34 Mon Sep 17 00:00:00 2001 From: Mike Keehnen Date: Tue, 15 Aug 2023 14:19:53 +0200 Subject: [PATCH 27/40] Test for closing of zoomed image --- .../components/ProductPageGallery/ProductPageGallery.tsx | 1 - packages/next-ui/FramerScroller/SidebarGallery.tsx | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/magento-product/components/ProductPageGallery/ProductPageGallery.tsx b/packages/magento-product/components/ProductPageGallery/ProductPageGallery.tsx index 09d244a66c..d36aca7734 100644 --- a/packages/magento-product/components/ProductPageGallery/ProductPageGallery.tsx +++ b/packages/magento-product/components/ProductPageGallery/ProductPageGallery.tsx @@ -1,4 +1,3 @@ -import { ScrollerDots } from '@graphcommerce/framer-scroller' import { nonNullable, SidebarGallery, diff --git a/packages/next-ui/FramerScroller/SidebarGallery.tsx b/packages/next-ui/FramerScroller/SidebarGallery.tsx index 53e968e75f..9095d28262 100644 --- a/packages/next-ui/FramerScroller/SidebarGallery.tsx +++ b/packages/next-ui/FramerScroller/SidebarGallery.tsx @@ -30,7 +30,7 @@ import { iconChevronLeft, iconChevronRight, iconFullscreen, iconFullscreenExit } const MotionBox = styled(m.div)({}) -type OwnerState = { zoomed: boolean } +type OwnerState = { zoomed: boolean; disableZoom: boolean } const name = 'SidebarGallery' as const const parts = [ 'row', @@ -68,8 +68,8 @@ export function SidebarGallery(props: SidebarGalleryProps) { aspectRatio: [width, height] = [1, 1], sx, routeHash = 'gallery', - disableZoom, showButtons, + disableZoom = false, } = props const router = useRouter() @@ -102,7 +102,7 @@ export function SidebarGallery(props: SidebarGalleryProps) { } } - const classes = withState({ zoomed }) + const classes = withState({ zoomed, disableZoom }) const theme = useTheme() const windowRef = useRef(typeof window !== 'undefined' ? window : null) From ada63303c006dd03ae55e72908d080ac12dad3ad Mon Sep 17 00:00:00 2001 From: Mike Keehnen Date: Tue, 15 Aug 2023 15:04:48 +0200 Subject: [PATCH 28/40] Test log for toggle --- packages/next-ui/FramerScroller/SidebarGallery.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/next-ui/FramerScroller/SidebarGallery.tsx b/packages/next-ui/FramerScroller/SidebarGallery.tsx index 9095d28262..1858b6fa40 100644 --- a/packages/next-ui/FramerScroller/SidebarGallery.tsx +++ b/packages/next-ui/FramerScroller/SidebarGallery.tsx @@ -90,6 +90,7 @@ export function SidebarGallery(props: SidebarGalleryProps) { }, [prevRoute?.pathname, route, router, zoomed]) const toggle = () => { + console.log('toggle') if (disableZoom) { return } From aeeb6d676a078c494ca6bd0ee48ddd24083a7341 Mon Sep 17 00:00:00 2001 From: Mike Keehnen Date: Tue, 15 Aug 2023 15:42:04 +0200 Subject: [PATCH 29/40] Test for redirect --- .../ConfigurableProductPage/ConfigurableProductPageMeta.tsx | 2 +- packages/next-ui/FramerScroller/SidebarGallery.tsx | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/magento-product-configurable/plugins/ConfigurableProductPage/ConfigurableProductPageMeta.tsx b/packages/magento-product-configurable/plugins/ConfigurableProductPage/ConfigurableProductPageMeta.tsx index efa2366fd1..16388e40fb 100644 --- a/packages/magento-product-configurable/plugins/ConfigurableProductPage/ConfigurableProductPageMeta.tsx +++ b/packages/magento-product-configurable/plugins/ConfigurableProductPage/ConfigurableProductPageMeta.tsx @@ -31,7 +31,7 @@ const ConfigurableProductPageMetaUrls: PluginType = (props) => { // navigation action on the product page. if (targetUrl !== asPath.split('#')[0]) { // eslint-disable-next-line @typescript-eslint/no-floating-promises - replace(targetUrl, undefined, { scroll: false, shallow: true }) + // replace(targetUrl, undefined, { scroll: false, shallow: true }) } }, [asPath, replace, targetUrl]) diff --git a/packages/next-ui/FramerScroller/SidebarGallery.tsx b/packages/next-ui/FramerScroller/SidebarGallery.tsx index 1858b6fa40..9095d28262 100644 --- a/packages/next-ui/FramerScroller/SidebarGallery.tsx +++ b/packages/next-ui/FramerScroller/SidebarGallery.tsx @@ -90,7 +90,6 @@ export function SidebarGallery(props: SidebarGalleryProps) { }, [prevRoute?.pathname, route, router, zoomed]) const toggle = () => { - console.log('toggle') if (disableZoom) { return } From 1dc2f3bebcb509255a6ffcaa461495f5ae1fb735 Mon Sep 17 00:00:00 2001 From: Mike Keehnen Date: Tue, 15 Aug 2023 16:09:46 +0200 Subject: [PATCH 30/40] Filter asPath with #, for zoomed gallery --- .../ConfigurableProductPage/ConfigurableProductPageMeta.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/magento-product-configurable/plugins/ConfigurableProductPage/ConfigurableProductPageMeta.tsx b/packages/magento-product-configurable/plugins/ConfigurableProductPage/ConfigurableProductPageMeta.tsx index 16388e40fb..efa2366fd1 100644 --- a/packages/magento-product-configurable/plugins/ConfigurableProductPage/ConfigurableProductPageMeta.tsx +++ b/packages/magento-product-configurable/plugins/ConfigurableProductPage/ConfigurableProductPageMeta.tsx @@ -31,7 +31,7 @@ const ConfigurableProductPageMetaUrls: PluginType = (props) => { // navigation action on the product page. if (targetUrl !== asPath.split('#')[0]) { // eslint-disable-next-line @typescript-eslint/no-floating-promises - // replace(targetUrl, undefined, { scroll: false, shallow: true }) + replace(targetUrl, undefined, { scroll: false, shallow: true }) } }, [asPath, replace, targetUrl]) From e3bf8f95ae443ae1dde12c33611117273631049c Mon Sep 17 00:00:00 2001 From: Mike Keehnen Date: Wed, 23 Aug 2023 17:00:30 +0200 Subject: [PATCH 31/40] Add onScroll event to useScroller hook --- .../framer-scroller/components/ScrollerThumbnail.tsx | 7 ++----- packages/framer-scroller/hooks/useScroller.ts | 12 ++++++++++++ packages/framer-utils/hooks/useElementScroll.ts | 1 - 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/framer-scroller/components/ScrollerThumbnail.tsx b/packages/framer-scroller/components/ScrollerThumbnail.tsx index 99b1761882..5235464a47 100644 --- a/packages/framer-scroller/components/ScrollerThumbnail.tsx +++ b/packages/framer-scroller/components/ScrollerThumbnail.tsx @@ -4,7 +4,7 @@ import { Image, ImageProps } from '@graphcommerce/image' import { extendableComponent, responsiveVal } from '@graphcommerce/next-ui/Styles' import { alpha, styled, useTheme } from '@mui/material' import { m, motionValue, useMotionValueEvent, useTransform } from 'framer-motion' -import { useEffect, useRef } from 'react' +import { useRef } from 'react' import { useScrollerContext } from '../hooks/useScrollerContext' const name = 'ScrollerThumbnail' @@ -49,12 +49,10 @@ export function ScrollerThumbnail(props: ScrollerThumbnailProps) { const classes = withState({ active }) - const scrollIntoView = () => ref.current?.scrollIntoView({ block: 'nearest', behavior: 'smooth' }) - useMotionValueEvent(scroll.animating, 'change', (v) => { if (!v && active && ref.current) { // This is a hack to ensure that the scroll animation is finished. - setTimeout(() => scrollIntoView(), 1) + setTimeout(() => ref.current?.scrollIntoView({ block: 'nearest', behavior: 'smooth' }), 1) } }) @@ -69,7 +67,6 @@ export function ScrollerThumbnail(props: ScrollerThumbnailProps) { const { x } = getScrollSnapPositions() scrollerRef.current.scrollLeft = x[idx] scroll.x.set(x[idx]) - scrollIntoView() }} // layout style={{ boxShadow }} diff --git a/packages/framer-scroller/hooks/useScroller.ts b/packages/framer-scroller/hooks/useScroller.ts index be583791b4..6cc010f930 100644 --- a/packages/framer-scroller/hooks/useScroller.ts +++ b/packages/framer-scroller/hooks/useScroller.ts @@ -120,6 +120,17 @@ export function useScroller< snapToVelocity(info) } + let isScrolling: NodeJS.Timeout | undefined + + const onScroll: MouseEventHandler = () => { + const isScrollable = !scroll.animating.get() + if (isScrolling) scroll.animating.set(true) + window.clearTimeout(isScrolling) + isScrolling = setTimeout(() => { + if (!isScrollable) scroll.animating.set(false) + }, 100) + } + const ref: React.RefCallback = (el) => { // @ts-expect-error current is assignable scrollerRef.current = el ?? undefined @@ -285,6 +296,7 @@ export function useScroller< onPan, onPanEnd, onMouseDown, + onScroll, children, className: `${classes.root} ${props.className}`, sx, diff --git a/packages/framer-utils/hooks/useElementScroll.ts b/packages/framer-utils/hooks/useElementScroll.ts index 9eb72b0b76..563483d98e 100644 --- a/packages/framer-utils/hooks/useElementScroll.ts +++ b/packages/framer-utils/hooks/useElementScroll.ts @@ -35,7 +35,6 @@ export function useElementScroll(ref?: RefObject): Scro useIsomorphicLayoutEffect(() => { const element = ref?.current - console.log('element', element?.id) if (!element) return () => {} const updater = () => { From dd09bc96fa1d7624c75b69f36b9044533af01e7f Mon Sep 17 00:00:00 2001 From: Mike Keehnen Date: Thu, 24 Aug 2023 10:37:06 +0200 Subject: [PATCH 32/40] Add animating state to click action --- packages/framer-scroller/components/ScrollerThumbnail.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/framer-scroller/components/ScrollerThumbnail.tsx b/packages/framer-scroller/components/ScrollerThumbnail.tsx index 5235464a47..b432ef7297 100644 --- a/packages/framer-scroller/components/ScrollerThumbnail.tsx +++ b/packages/framer-scroller/components/ScrollerThumbnail.tsx @@ -49,10 +49,12 @@ export function ScrollerThumbnail(props: ScrollerThumbnailProps) { const classes = withState({ active }) + const scrollIntoView = () => ref.current?.scrollIntoView({ block: 'nearest', behavior: 'smooth' }) + useMotionValueEvent(scroll.animating, 'change', (v) => { if (!v && active && ref.current) { // This is a hack to ensure that the scroll animation is finished. - setTimeout(() => ref.current?.scrollIntoView({ block: 'nearest', behavior: 'smooth' }), 1) + setTimeout(() => scrollIntoView(), 1) } }) @@ -64,9 +66,11 @@ export function ScrollerThumbnail(props: ScrollerThumbnailProps) { className={classes.thumbnail} onClick={() => { if (!scrollerRef.current) return + scroll.animating.set(true) const { x } = getScrollSnapPositions() scrollerRef.current.scrollLeft = x[idx] scroll.x.set(x[idx]) + scroll.animating.set(false) }} // layout style={{ boxShadow }} From aab9a976194a5acef13871a8014b365f644f2166 Mon Sep 17 00:00:00 2001 From: Mike Keehnen Date: Mon, 2 Oct 2023 16:11:19 +0200 Subject: [PATCH 33/40] Added config values and improved zoom animation gallery --- docs/framework/config.md | 16 ++++++++++++- .../components/ScrollerThumbnail.tsx | 15 ++++++------ .../components/ThumbnailContainer.tsx | 1 + packages/next-ui/Config.graphqls | 24 +++++++++++++++++++ .../next-config/dist/generated/config.js | 16 +++++++++++++ .../next-config/src/generated/config.ts | 23 ++++++++++++++++++ 6 files changed, 87 insertions(+), 8 deletions(-) create mode 100644 packages/next-ui/Config.graphqls diff --git a/docs/framework/config.md b/docs/framework/config.md index 578a5fc0ba..6780a50662 100644 --- a/docs/framework/config.md +++ b/docs/framework/config.md @@ -271,6 +271,8 @@ Example: '/product/' Allow the site to be indexed by search engines. If false, the robots.txt file will be set to disallow all. +#### `sidebarGallery: [SidebarGalleryConfig](#SidebarGalleryConfig)` + #### `wishlistHideForGuests: Boolean` Hide the wishlist functionality for guests. @@ -388,4 +390,16 @@ the selected variant differ from the currently displayed images. When a variant is selected the URL of the product will be changed in the address bar. -This only happens when the actual variant is can be accessed by the URL. \ No newline at end of file +This only happens when the actual variant is can be accessed by the URL. + +### SidebarGalleryConfig + +SidebarGalleryConfig will contain all configuration values for the Sidebar Gallery component. + +#### `thumbnails: Boolean` + +This variable will enable or disable the sidebar gallery thumbnails. + +#### `thumbnailsPosition: [SidebarGalleryThumbnailPosition](#SidebarGalleryThumbnailPosition)` + +This variable will allocate the position of the sidebar gallery thumbnails. \ No newline at end of file diff --git a/packages/framer-scroller/components/ScrollerThumbnail.tsx b/packages/framer-scroller/components/ScrollerThumbnail.tsx index b432ef7297..87e00c2251 100644 --- a/packages/framer-scroller/components/ScrollerThumbnail.tsx +++ b/packages/framer-scroller/components/ScrollerThumbnail.tsx @@ -3,8 +3,8 @@ import { Image, ImageProps } from '@graphcommerce/image' // eslint-disable-next-line import/no-extraneous-dependencies import { extendableComponent, responsiveVal } from '@graphcommerce/next-ui/Styles' import { alpha, styled, useTheme } from '@mui/material' -import { m, motionValue, useMotionValueEvent, useTransform } from 'framer-motion' -import { useRef } from 'react' +import { m, motionValue, useTransform } from 'framer-motion' +import { useEffect, useRef } from 'react' import { useScrollerContext } from '../hooks/useScrollerContext' const name = 'ScrollerThumbnail' @@ -49,14 +49,15 @@ export function ScrollerThumbnail(props: ScrollerThumbnailProps) { const classes = withState({ active }) - const scrollIntoView = () => ref.current?.scrollIntoView({ block: 'nearest', behavior: 'smooth' }) + const scrollIntoView = () => ref.current?.scrollIntoView({ block: 'nearest', inline: 'nearest', behavior: 'instant' }) - useMotionValueEvent(scroll.animating, 'change', (v) => { - if (!v && active && ref.current) { + useEffect(() => { + if (active && ref.current) { // This is a hack to ensure that the scroll animation is finished. setTimeout(() => scrollIntoView(), 1) } - }) + }, [active]) + if (!image) return null @@ -72,7 +73,7 @@ export function ScrollerThumbnail(props: ScrollerThumbnailProps) { scroll.x.set(x[idx]) scroll.animating.set(false) }} - // layout + layout='position' style={{ boxShadow }} sx={{ padding: '2px', diff --git a/packages/framer-scroller/components/ThumbnailContainer.tsx b/packages/framer-scroller/components/ThumbnailContainer.tsx index ba250b1d1c..434fc06f3e 100644 --- a/packages/framer-scroller/components/ThumbnailContainer.tsx +++ b/packages/framer-scroller/components/ThumbnailContainer.tsx @@ -20,6 +20,7 @@ export function ThumbnailContainer(props: ThumbnailContainerProps) { ; + sidebarGallery?: InputMaybe; /** All storefront configuration for the project */ storefront: Array; /** Hide the wishlist functionality for guests. */ @@ -384,6 +385,18 @@ export type ProductFiltersLayout = | 'DEFAULT' | 'SIDEBAR'; +/** SidebarGalleryConfig will contain all configuration values for the Sidebar Gallery component. */ +export type SidebarGalleryConfig = { + /** This variable will enable or disable the sidebar gallery thumbnails. */ + thumbnails?: InputMaybe; + /** This variable will allocate the position of the sidebar gallery thumbnails. */ + thumbnailsPosition?: InputMaybe; +}; + +/** Enumeration of all possible positions for the sidebar gallery thumbnails. */ +export type SidebarGalleryThumbnailPosition = + | 'BOTTOM'; + type Properties = Required<{ [K in keyof T]: z.ZodType; @@ -399,6 +412,8 @@ export const CompareVariantSchema = z.enum(['CHECKBOX', 'ICON']); export const ProductFiltersLayoutSchema = z.enum(['DEFAULT', 'SIDEBAR']); +export const SidebarGalleryThumbnailPositionSchema = z.enum(['BOTTOM']); + export function GraphCommerceConfigSchema(): z.ZodObject> { return z.object({ canonicalBaseUrl: z.string().min(1), @@ -427,6 +442,7 @@ export function GraphCommerceConfigSchema(): z.ZodObject> { + return z.object({ + thumbnails: z.boolean().nullish(), + thumbnailsPosition: SidebarGalleryThumbnailPositionSchema.nullish() + }) +} From b5c17532c80c73c94727728f2b43d6491859d656 Mon Sep 17 00:00:00 2001 From: Mike Keehnen Date: Mon, 2 Oct 2023 16:12:24 +0200 Subject: [PATCH 34/40] Whitespace package.json --- packages/demo-magento-graphcommerce/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/demo-magento-graphcommerce/package.json b/packages/demo-magento-graphcommerce/package.json index dfb17cffa9..c3781bb7a6 100644 --- a/packages/demo-magento-graphcommerce/package.json +++ b/packages/demo-magento-graphcommerce/package.json @@ -31,4 +31,4 @@ "@graphcommerce/magento-product": "7.0.2-canary.5", "@graphcommerce/magento-product-configurable": "7.0.2-canary.5" } -} \ No newline at end of file +} From e9e0aea399bb56c9142678290b34f9dbe84b3474 Mon Sep 17 00:00:00 2001 From: Mike Keehnen Date: Mon, 2 Oct 2023 16:17:54 +0200 Subject: [PATCH 35/40] Remove onScroll from useScroller hook --- packages/framer-scroller/hooks/useScroller.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/packages/framer-scroller/hooks/useScroller.ts b/packages/framer-scroller/hooks/useScroller.ts index 6cc010f930..be583791b4 100644 --- a/packages/framer-scroller/hooks/useScroller.ts +++ b/packages/framer-scroller/hooks/useScroller.ts @@ -120,17 +120,6 @@ export function useScroller< snapToVelocity(info) } - let isScrolling: NodeJS.Timeout | undefined - - const onScroll: MouseEventHandler = () => { - const isScrollable = !scroll.animating.get() - if (isScrolling) scroll.animating.set(true) - window.clearTimeout(isScrolling) - isScrolling = setTimeout(() => { - if (!isScrollable) scroll.animating.set(false) - }, 100) - } - const ref: React.RefCallback = (el) => { // @ts-expect-error current is assignable scrollerRef.current = el ?? undefined @@ -296,7 +285,6 @@ export function useScroller< onPan, onPanEnd, onMouseDown, - onScroll, children, className: `${classes.root} ${props.className}`, sx, From de8e73064476cb02be59a2d95319722b95b2e57c Mon Sep 17 00:00:00 2001 From: Mike Keehnen Date: Thu, 5 Oct 2023 11:40:13 +0200 Subject: [PATCH 36/40] Add gallery to example config --- examples/magento-graphcms/graphcommerce.config.js.example | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/magento-graphcms/graphcommerce.config.js.example b/examples/magento-graphcms/graphcommerce.config.js.example index f58536feeb..8bc7913a16 100644 --- a/examples/magento-graphcms/graphcommerce.config.js.example +++ b/examples/magento-graphcms/graphcommerce.config.js.example @@ -10,6 +10,10 @@ const config = { magentoEndpoint: 'https://backend.reachdigital.dev/graphql', canonicalBaseUrl: 'https://graphcommerce.vercel.app', storefront: [{ locale: 'en', magentoStoreCode: 'en_US' }], + sidebarGallery: { + thumbnails: true, + thumbnailsPosition: 'BOTTOM', + }, } module.exports = config From 84d26bab0c3d3b315659a13708b35839566b96e0 Mon Sep 17 00:00:00 2001 From: Mike Keehnen Date: Thu, 5 Oct 2023 11:44:58 +0200 Subject: [PATCH 37/40] Fix dependency version --- packages/demo-magento-graphcommerce/package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/demo-magento-graphcommerce/package.json b/packages/demo-magento-graphcommerce/package.json index c3781bb7a6..22a09ec84f 100644 --- a/packages/demo-magento-graphcommerce/package.json +++ b/packages/demo-magento-graphcommerce/package.json @@ -26,9 +26,9 @@ "react-dom": "^18.2.0" }, "dependencies": { - "@graphcommerce/next-ui": "7.0.2-canary.5", - "@graphcommerce/framer-scroller": "7.0.2-canary.5", - "@graphcommerce/magento-product": "7.0.2-canary.5", - "@graphcommerce/magento-product-configurable": "7.0.2-canary.5" + "@graphcommerce/next-ui": "7.1.0-canary.8", + "@graphcommerce/framer-scroller": "7.1.0-canary.8", + "@graphcommerce/magento-product": "7.1.0-canary.8", + "@graphcommerce/magento-product-configurable": "7.1.0-canary.8" } -} +} \ No newline at end of file From a8a5205d2780ef1dea62240b764fcc81adc5504d Mon Sep 17 00:00:00 2001 From: Mike Keehnen Date: Mon, 23 Oct 2023 16:45:52 +0200 Subject: [PATCH 38/40] Feedback PR --- docs/framework/config.md | 21 +++++++++++-------- .../graphcommerce.config.js.example | 3 +-- .../components/ScrollerThumbnail.tsx | 4 ++-- .../plugins/GalleryThumbnailNavigation.tsx | 15 ------------- packages/next-ui/Config.graphqls | 16 +++++++------- .../next-ui/FramerScroller/SidebarGallery.tsx | 10 ++++++--- .../dist/config/utils/configToImportMeta.js | 2 +- .../next-config/dist/generated/config.js | 12 +++++------ .../src/config/utils/configToImportMeta.ts | 2 +- .../next-config/src/generated/config.ts | 17 +++++++-------- 10 files changed, 46 insertions(+), 56 deletions(-) delete mode 100644 packages/framer-scroller/plugins/GalleryThumbnailNavigation.tsx diff --git a/docs/framework/config.md b/docs/framework/config.md index 6780a50662..319ff83c0f 100644 --- a/docs/framework/config.md +++ b/docs/framework/config.md @@ -226,6 +226,12 @@ Project settings -> API Access -> Permanent Auth Tokens - Delete existing components - Update remote sources - Delete remote sources + - Read existing environments + - Read public content views + - Create public content views + - Update public content views + - Delete public content views + - Can see schema view ``` GC_HYGRAPH_WRITE_ACCESS_ENDPOINT="https://...hygraph.com/v2/..." @@ -273,6 +279,8 @@ If false, the robots.txt file will be set to disallow all. #### `sidebarGallery: [SidebarGalleryConfig](#SidebarGalleryConfig)` +Configuration for the SidebarGallery component + #### `wishlistHideForGuests: Boolean` Hide the wishlist functionality for guests. @@ -382,9 +390,8 @@ Use the name, description, short description and meta data from the configured v #### `gallery: Boolean` -This option enables the automatic update of product gallery images on the -product page when a variant is selected, provided that the gallery images for -the selected variant differ from the currently displayed images. +This option enables the automatic update of product gallery images on the product page when a variant is selected, +provided that the gallery images for the selected variant differ from the currently displayed images. #### `url: Boolean` @@ -396,10 +403,6 @@ This only happens when the actual variant is can be accessed by the URL. SidebarGalleryConfig will contain all configuration values for the Sidebar Gallery component. -#### `thumbnails: Boolean` - -This variable will enable or disable the sidebar gallery thumbnails. - -#### `thumbnailsPosition: [SidebarGalleryThumbnailPosition](#SidebarGalleryThumbnailPosition)` +#### `paginationVariant: [SidebarGalleryPaginationVariant](#SidebarGalleryPaginationVariant)` -This variable will allocate the position of the sidebar gallery thumbnails. \ No newline at end of file +Variant used for the pagination \ No newline at end of file diff --git a/examples/magento-graphcms/graphcommerce.config.js.example b/examples/magento-graphcms/graphcommerce.config.js.example index 8bc7913a16..070c532d67 100644 --- a/examples/magento-graphcms/graphcommerce.config.js.example +++ b/examples/magento-graphcms/graphcommerce.config.js.example @@ -11,8 +11,7 @@ const config = { canonicalBaseUrl: 'https://graphcommerce.vercel.app', storefront: [{ locale: 'en', magentoStoreCode: 'en_US' }], sidebarGallery: { - thumbnails: true, - thumbnailsPosition: 'BOTTOM', + paginationVariant: 'THUMBNAILS_BOTTOM', }, } diff --git a/packages/framer-scroller/components/ScrollerThumbnail.tsx b/packages/framer-scroller/components/ScrollerThumbnail.tsx index 87e00c2251..11b68b5d45 100644 --- a/packages/framer-scroller/components/ScrollerThumbnail.tsx +++ b/packages/framer-scroller/components/ScrollerThumbnail.tsx @@ -49,7 +49,8 @@ export function ScrollerThumbnail(props: ScrollerThumbnailProps) { const classes = withState({ active }) - const scrollIntoView = () => ref.current?.scrollIntoView({ block: 'nearest', inline: 'nearest', behavior: 'instant' }) + const scrollIntoView = () => + ref.current?.scrollIntoView({ block: 'nearest', inline: 'center', behavior: 'auto' }) useEffect(() => { if (active && ref.current) { @@ -57,7 +58,6 @@ export function ScrollerThumbnail(props: ScrollerThumbnailProps) { setTimeout(() => scrollIntoView(), 1) } }, [active]) - if (!image) return null diff --git a/packages/framer-scroller/plugins/GalleryThumbnailNavigation.tsx b/packages/framer-scroller/plugins/GalleryThumbnailNavigation.tsx deleted file mode 100644 index a743f38b13..0000000000 --- a/packages/framer-scroller/plugins/GalleryThumbnailNavigation.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import type { PluginProps } from '@graphcommerce/next-config' -import type { SidebarGalleryProps } from '@graphcommerce/next-ui/FramerScroller/SidebarGallery' -import { useCallback } from 'react' -import { ScrollerThumbnails } from '../components/ScrollerThumbnails' - -export const component = 'SidebarGallery' -export const exported = '@graphcommerce/next-ui' -export const ifEnv = 'GALLERY_THUMBNAILS' - -function GalleryThumbnailNavigation(props: PluginProps) { - const { Prev, ...rest } = props - const navigation = useCallback(() => , [rest.images]) - return -} -export const Plugin = GalleryThumbnailNavigation diff --git a/packages/next-ui/Config.graphqls b/packages/next-ui/Config.graphqls index fafb03012c..2bef3570cd 100644 --- a/packages/next-ui/Config.graphqls +++ b/packages/next-ui/Config.graphqls @@ -1,12 +1,16 @@ extend input GraphCommerceConfig { + """ + Configuration for the SidebarGallery component + """ sidebarGallery: SidebarGalleryConfig } """ Enumeration of all possible positions for the sidebar gallery thumbnails. """ -enum SidebarGalleryThumbnailPosition { - BOTTOM +enum SidebarGalleryPaginationVariant { + DOTS + THUMBNAILS_BOTTOM } """ @@ -14,11 +18,7 @@ SidebarGalleryConfig will contain all configuration values for the Sidebar Galle """ input SidebarGalleryConfig { """ - This variable will enable or disable the sidebar gallery thumbnails. - """ - thumbnails: Boolean - """ - This variable will allocate the position of the sidebar gallery thumbnails. + Variant used for the pagination """ - thumbnailsPosition: SidebarGalleryThumbnailPosition + paginationVariant: SidebarGalleryPaginationVariant } diff --git a/packages/next-ui/FramerScroller/SidebarGallery.tsx b/packages/next-ui/FramerScroller/SidebarGallery.tsx index 9095d28262..590b302a92 100644 --- a/packages/next-ui/FramerScroller/SidebarGallery.tsx +++ b/packages/next-ui/FramerScroller/SidebarGallery.tsx @@ -8,6 +8,7 @@ import { ScrollerProvider, unstable_usePreventScroll as usePreventScroll, ScrollerButtonProps, + ScrollerThumbnails, } from '@graphcommerce/framer-scroller' import { dvh } from '@graphcommerce/framer-utils' import { @@ -317,7 +318,6 @@ export function SidebarGallery(props: SidebarGalleryProps) { className={classes.bottomCenter} sx={{ display: 'flex', - px: theme.page.horizontal, gap: theme.spacings.xxs, position: 'absolute', bottom: theme.spacings.xxs, @@ -329,11 +329,15 @@ export function SidebarGallery(props: SidebarGalleryProps) { }, }} > - + {import.meta.graphCommerce.sidebarGallery?.paginationVariant === + 'THUMBNAILS_BOTTOM' ? ( + + ) : ( + + )}
- ({ ...acc, ...path })), + .reduce((acc, path) => ({ ...acc, ...path }), {}), }; } throw Error(`Unexpected value: ${value}`); diff --git a/packagesDev/next-config/dist/generated/config.js b/packagesDev/next-config/dist/generated/config.js index b0220e443d..ba826db8c4 100644 --- a/packagesDev/next-config/dist/generated/config.js +++ b/packagesDev/next-config/dist/generated/config.js @@ -21,8 +21,8 @@ _export(exports, { ProductFiltersLayoutSchema: function() { return ProductFiltersLayoutSchema; }, - SidebarGalleryThumbnailPositionSchema: function() { - return SidebarGalleryThumbnailPositionSchema; + SidebarGalleryPaginationVariantSchema: function() { + return SidebarGalleryPaginationVariantSchema; }, GraphCommerceConfigSchema: function() { return GraphCommerceConfigSchema; @@ -51,8 +51,9 @@ const ProductFiltersLayoutSchema = _zod.z.enum([ "DEFAULT", "SIDEBAR" ]); -const SidebarGalleryThumbnailPositionSchema = _zod.z.enum([ - "BOTTOM" +const SidebarGalleryPaginationVariantSchema = _zod.z.enum([ + "DOTS", + "THUMBNAILS_BOTTOM" ]); function GraphCommerceConfigSchema() { return _zod.z.object({ @@ -120,7 +121,6 @@ function MagentoConfigurableVariantValuesSchema() { } function SidebarGalleryConfigSchema() { return _zod.z.object({ - thumbnails: _zod.z.boolean().nullish(), - thumbnailsPosition: SidebarGalleryThumbnailPositionSchema.nullish() + paginationVariant: SidebarGalleryPaginationVariantSchema.nullish() }); } diff --git a/packagesDev/next-config/src/config/utils/configToImportMeta.ts b/packagesDev/next-config/src/config/utils/configToImportMeta.ts index a626e42961..c3968e0db4 100644 --- a/packagesDev/next-config/src/config/utils/configToImportMeta.ts +++ b/packagesDev/next-config/src/config/utils/configToImportMeta.ts @@ -34,7 +34,7 @@ function flattenKeys( const deep = (value as Record)[key] return flattenKeys(deep, `${initialPathPrefix}.${key}`, stringify) }) - .reduce((acc, path) => ({ ...acc, ...path })), + .reduce((acc, path) => ({ ...acc, ...path }), {}), } } diff --git a/packagesDev/next-config/src/generated/config.ts b/packagesDev/next-config/src/generated/config.ts index c71fd6c666..d64733b6ae 100644 --- a/packagesDev/next-config/src/generated/config.ts +++ b/packagesDev/next-config/src/generated/config.ts @@ -282,6 +282,7 @@ export type GraphCommerceConfig = { * If false, the robots.txt file will be set to disallow all. */ robotsAllow?: InputMaybe; + /** Configuration for the SidebarGallery component */ sidebarGallery?: InputMaybe; /** All storefront configuration for the project */ storefront: Array; @@ -387,15 +388,14 @@ export type ProductFiltersLayout = /** SidebarGalleryConfig will contain all configuration values for the Sidebar Gallery component. */ export type SidebarGalleryConfig = { - /** This variable will enable or disable the sidebar gallery thumbnails. */ - thumbnails?: InputMaybe; - /** This variable will allocate the position of the sidebar gallery thumbnails. */ - thumbnailsPosition?: InputMaybe; + /** Variant used for the pagination */ + paginationVariant?: InputMaybe; }; /** Enumeration of all possible positions for the sidebar gallery thumbnails. */ -export type SidebarGalleryThumbnailPosition = - | 'BOTTOM'; +export type SidebarGalleryPaginationVariant = + | 'DOTS' + | 'THUMBNAILS_BOTTOM'; type Properties = Required<{ @@ -412,7 +412,7 @@ export const CompareVariantSchema = z.enum(['CHECKBOX', 'ICON']); export const ProductFiltersLayoutSchema = z.enum(['DEFAULT', 'SIDEBAR']); -export const SidebarGalleryThumbnailPositionSchema = z.enum(['BOTTOM']); +export const SidebarGalleryPaginationVariantSchema = z.enum(['DOTS', 'THUMBNAILS_BOTTOM']); export function GraphCommerceConfigSchema(): z.ZodObject> { return z.object({ @@ -484,7 +484,6 @@ export function MagentoConfigurableVariantValuesSchema(): z.ZodObject> { return z.object({ - thumbnails: z.boolean().nullish(), - thumbnailsPosition: SidebarGalleryThumbnailPositionSchema.nullish() + paginationVariant: SidebarGalleryPaginationVariantSchema.nullish() }) } From 2d4bda27a3047c07d6fb7553d6b5e07d22ef3330 Mon Sep 17 00:00:00 2001 From: Mike Keehnen Date: Mon, 30 Oct 2023 11:14:14 +0100 Subject: [PATCH 39/40] Add sidebarGallery to demoConfig --- packagesDev/next-config/src/config/demoConfig.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packagesDev/next-config/src/config/demoConfig.ts b/packagesDev/next-config/src/config/demoConfig.ts index 973c4a455f..8a809eed75 100644 --- a/packagesDev/next-config/src/config/demoConfig.ts +++ b/packagesDev/next-config/src/config/demoConfig.ts @@ -27,7 +27,7 @@ export const demoConfig: PartialDeep Date: Thu, 2 Nov 2023 13:37:04 +0100 Subject: [PATCH 40/40] Add dots to demo config --- examples/magento-graphcms/graphcommerce.config.js.example | 3 --- packagesDev/next-config/src/config/demoConfig.ts | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/examples/magento-graphcms/graphcommerce.config.js.example b/examples/magento-graphcms/graphcommerce.config.js.example index 070c532d67..f58536feeb 100644 --- a/examples/magento-graphcms/graphcommerce.config.js.example +++ b/examples/magento-graphcms/graphcommerce.config.js.example @@ -10,9 +10,6 @@ const config = { magentoEndpoint: 'https://backend.reachdigital.dev/graphql', canonicalBaseUrl: 'https://graphcommerce.vercel.app', storefront: [{ locale: 'en', magentoStoreCode: 'en_US' }], - sidebarGallery: { - paginationVariant: 'THUMBNAILS_BOTTOM', - }, } module.exports = config diff --git a/packagesDev/next-config/src/config/demoConfig.ts b/packagesDev/next-config/src/config/demoConfig.ts index 8a809eed75..5fdda3e357 100644 --- a/packagesDev/next-config/src/config/demoConfig.ts +++ b/packagesDev/next-config/src/config/demoConfig.ts @@ -27,7 +27,7 @@ export const demoConfig: PartialDeep