Skip to content

Commit

Permalink
Added a zoomable gallery for product images
Browse files Browse the repository at this point in the history
  • Loading branch information
tedraykov committed Jun 15, 2021
1 parent 2728b90 commit ceb77d7
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 66 deletions.
Empty file.
50 changes: 50 additions & 0 deletions components/product/ProductGallery/Lightbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React, { FC, MutableRefObject, useEffect, useState } from 'react'

import { ProductImage } from '@commerce/types'
import 'photoswipe/dist/photoswipe.css'
import 'photoswipe/dist/default-skin/default-skin.css'

import { Gallery, Item } from 'react-photoswipe-gallery'
import PhotoSwipe, { Options } from 'photoswipe'
import Portal from '@reach/portal'

interface Props {
images: ProductImage[]
selectedImageIndex?: number
isOpen: boolean
onClose: () => void
}

export const Lightbox: FC<Props> = ({ images, selectedImageIndex = 0, isOpen, onClose }) => {
const [activeImage, setActiveImage] = useState<MutableRefObject<HTMLElement>>();

useEffect(() => {
if (!!activeImage) {
activeImage.current.click()
}
}, [activeImage])

function addCloseListeners(photoswipe: PhotoSwipe<Options>) {
console.log('Opening onOpen hook')
photoswipe.listen('destroy', onClose)
photoswipe.listen('close', () => onClose)
}

return (
<>{isOpen && (
<Portal>
<Gallery onOpen={addCloseListeners}>
{images.map(({ url }, index) => (
<Item original={url} width={1500} height={1500} key={url}>
{({ref, open}) => {
if (index === selectedImageIndex) setActiveImage(ref)
return <span ref={ref} onClick={open}/>
}}
</Item>
))
}
</Gallery>
</Portal>
)}</>
)
}
21 changes: 17 additions & 4 deletions components/product/ProductSlider/ProductSlider.module.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.root {
@apply relative w-full h-full;
overflow-y: hidden;
cursor: pointer;
}

.leftControl,
Expand Down Expand Up @@ -37,20 +38,19 @@
}

.control {
@apply opacity-0 transition duration-150;
@apply opacity-50 transition duration-150;
}

.root:hover .control {
@apply opacity-100;
}

.positionIndicatorsContainer {
@apply hidden;
/*@apply hidden;*/

@screen sm {
@apply block absolute bottom-6 left-1/2;
transform: translateX(-50%);
}

}

.positionIndicator {
Expand Down Expand Up @@ -80,3 +80,16 @@
.positionIndicatorActive:hover .dot {
@apply bg-white;
}

.imageContainer {
& > div {
@apply h-full;
& > div {
@apply h-full;
}
}
}

.img {
@apply w-full h-auto max-h-full object-cover;
}
13 changes: 0 additions & 13 deletions components/product/ProductView/ProductView.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -64,19 +64,6 @@
@apply absolute z-10 inset-0 flex items-center justify-center overflow-x-hidden;
}

.imageContainer {
& > div {
@apply h-full;
& > div {
@apply h-full;
}
}
}

.img {
@apply w-full h-auto max-h-full object-cover;
}

.button {
text-align: center;
width: 100%;
Expand Down
57 changes: 10 additions & 47 deletions components/product/ProductView/ProductView.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import cn from 'classnames'
import Image from 'next/image'
import { NextSeo } from 'next-seo'
import React, { FC, useEffect, useState } from 'react'
import s from './ProductView.module.css'
Expand All @@ -10,7 +9,6 @@ import usePrice from '@framework/product/use-price'
import { useAddItem } from '@framework/cart'
import { getVariant, SelectedOptions } from '../helpers'
import WishlistButton from '@components/wishlist/WishlistButton'
import { useRouter } from 'next/router'

interface Props {
children?: any
Expand All @@ -20,56 +18,23 @@ interface Props {

const ProductView: FC<Props> = ({ product }) => {
const addItem = useAddItem()
const router = useRouter()

const { price } = usePrice({
amount: product.price.value,
baseAmount: product.price.retailPrice,
currencyCode: product.price.currencyCode!,
currencyCode: product.price.currencyCode!
})
const { openSidebar } = useUI()
const [loading, setLoading] = useState(false)
const [choices, setChoices] = useState<SelectedOptions>({})

useEffect(() => {
const optionsFromUrl = getOptionsFromQueryParams()
if (
!!Object.keys(optionsFromUrl).length &&
!!getVariant(product, optionsFromUrl as SelectedOptions)
) {
setChoices(() => optionsFromUrl as SelectedOptions)
return
}

useEffect(() =>
product.variants[0].options?.forEach((v) => {
setChoices((choices) => ({
...choices,
[v.displayName.toLowerCase()]: v.values[0].label.toLowerCase(),
[v.displayName.toLowerCase()]: v.values[0].label.toLowerCase()
}))
})
}, [])

function getOptionsFromQueryParams(): {
[name: string]: string | string[] | undefined
} {
const queryParams = new URLSearchParams(
router.asPath.substring(router.asPath.indexOf('?') + 1)
)

let optionEntries: [string, any][] = []
queryParams.forEach((value, key) => optionEntries.push([key, value]))

optionEntries = optionEntries.filter(([key]) =>
product?.options.find(
(opt) => opt.displayName.toLowerCase() === key.toLowerCase()
)
)
return Object.fromEntries(optionEntries)
}

useEffect(() => {
router.push({ query: { ...choices } }, undefined, { shallow: true }).then()
}, [choices])
}), [])

const variant = getVariant(product, choices)

Expand All @@ -78,15 +43,13 @@ const ProductView: FC<Props> = ({ product }) => {
try {
const selectedVariant = variant ? variant : product.variants[0]

console.log('selected variant', selectedVariant)

await addItem({
productId: String(product.id),
variantId: String(selectedVariant.id),
pricing: {
amount: selectedVariant.price,
currencyCode: product.price.currencyCode ?? 'USD',
},
currencyCode: product.price.currencyCode ?? 'USD'
}
})
openSidebar()
setLoading(false)
Expand All @@ -96,7 +59,7 @@ const ProductView: FC<Props> = ({ product }) => {
}

return (
<Container className="max-w-none w-full" clean>
<Container className='max-w-none w-full' clean>
<NextSeo
title={product.name}
description={product.description}
Expand All @@ -109,9 +72,9 @@ const ProductView: FC<Props> = ({ product }) => {
url: product.images[0]?.url!,
width: 800,
height: 600,
alt: product.name,
},
],
alt: product.name
}
]
}}
/>
<div className={cn(s.root, 'fit')}>
Expand Down
2 changes: 1 addition & 1 deletion framework/commerce/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ export interface ProductOptionValues {
hexColors?: string[]
}

interface ProductImage {
export interface ProductImage {
url: string
alt?: string
}
Expand Down
2 changes: 1 addition & 1 deletion framework/reactioncommerce/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ const commerce = require('./commerce.config.json')
module.exports = {
commerce,
images: {
domains: ['localhost'],
domains: ['94.155.79.88'],
},
}
57 changes: 57 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1144,6 +1144,11 @@
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==

"@types/photoswipe@^4.1.1":
version "4.1.1"
resolved "https://registry.yarnpkg.com/@types/photoswipe/-/photoswipe-4.1.1.tgz#3d76ed9698775ea17a1191578e4d0eeb331024a2"
integrity sha512-5ffGZEWjDqARuX/04uH/913zwkz/z9fuCVgMAnToeDBwOcN7P0xJE5LCrX50EiuyfpWN2AlkAIFwRfhaWYs6mw==

"@types/prop-types@*":
version "15.7.3"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
Expand Down Expand Up @@ -3128,6 +3133,21 @@ get-stream@^5.0.0, get-stream@^5.1.0:
dependencies:
pump "^3.0.0"

glob-base@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4"
integrity sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=
dependencies:
glob-parent "^2.0.0"
is-glob "^2.0.0"

glob-parent@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28"
integrity sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=
dependencies:
is-glob "^2.0.0"

glob-parent@^5.1.0, glob-parent@~5.1.0:
version "5.1.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
Expand Down Expand Up @@ -3581,6 +3601,16 @@ is-date-object@^1.0.1:
resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.4.tgz#550cfcc03afada05eea3dd30981c7b09551f73e5"
integrity sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A==

is-dotfile@^1.0.0:
version "1.0.3"
resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1"
integrity sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=

is-extglob@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0"
integrity sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=

is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
Expand Down Expand Up @@ -3615,6 +3645,13 @@ [email protected], is-glob@^4.0.1, is-glob@~4.0.1:
dependencies:
is-extglob "^2.1.1"

is-glob@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863"
integrity sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=
dependencies:
is-extglob "^1.0.0"

is-interactive@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e"
Expand Down Expand Up @@ -4939,6 +4976,16 @@ parse-filepath@^1.0.2:
map-cache "^0.2.0"
path-root "^0.1.1"

parse-glob@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c"
integrity sha1-ssN2z7EfNVE7rdFz7wu246OIORw=
dependencies:
glob-base "^0.3.0"
is-dotfile "^1.0.0"
is-extglob "^1.0.0"
is-glob "^2.0.0"

parse-json@^5.0.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
Expand Down Expand Up @@ -5028,6 +5075,11 @@ pbkdf2@^3.0.3:
safe-buffer "^5.0.1"
sha.js "^2.4.8"

photoswipe@^4.1.3:
version "4.1.3"
resolved "https://registry.yarnpkg.com/photoswipe/-/photoswipe-4.1.3.tgz#59f49494eeb9ddab5888d03392926a19bc197550"
integrity sha512-89Z43IRUyw7ycTolo+AaiDn3W1EEIfox54hERmm9bI12IB9cvRfHSHez3XhAyU8XW2EAFrC+2sKMhh7SJwn0bA==

picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3:
version "2.3.0"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972"
Expand Down Expand Up @@ -5666,6 +5718,11 @@ react-merge-refs@^1.1.0:
resolved "https://registry.yarnpkg.com/react-merge-refs/-/react-merge-refs-1.1.0.tgz#73d88b892c6c68cbb7a66e0800faa374f4c38b06"
integrity sha512-alTKsjEL0dKH/ru1Iyn7vliS2QRcBp9zZPGoWxUOvRGWPUYgjo+V01is7p04It6KhgrzhJGnIj9GgX8W4bZoCQ==

react-photoswipe-gallery@^1.3.6:
version "1.3.6"
resolved "https://registry.yarnpkg.com/react-photoswipe-gallery/-/react-photoswipe-gallery-1.3.6.tgz#c6e7f72393e78a09d7de312b12a89ecfe8d19d8c"
integrity sha512-HUaNu4FWNcHbZeBtYMZ+wX2+DbaW0LCtbkQuHLxM+VJWvYhPSpiVTmkR0Wn5qeN8t2L5/ca1rV66G8e15AVcOA==

[email protected]:
version "0.8.3"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"
Expand Down

1 comment on commit ceb77d7

@vercel
Copy link

@vercel vercel bot commented on ceb77d7 Jun 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.