-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Сверстан виджет Карточка товара на странице товара, фича Карусель фот…
…ографий товара. Пока без API
- Loading branch information
1 parent
f225fc6
commit e803a09
Showing
20 changed files
with
1,121 additions
and
18 deletions.
There are no files selected for viewing
38 changes: 38 additions & 0 deletions
38
src/features/ProductImgCarousel/ProductImgCarousel.module.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
@use '@/shared/styles/utils/variables' as var; | ||
@use '@/shared/styles/utils/mixins' as media; | ||
|
||
.wrapper { | ||
height: 670px; | ||
display: flex; | ||
background-color: var.$white; | ||
|
||
@include media.respond-to('middle') { | ||
flex-direction: column-reverse; | ||
align-items: center; | ||
} | ||
|
||
.carouselwrapper { | ||
width: 500px; | ||
height: 100%; | ||
|
||
@include media.respond-to('middle') { | ||
width: 300px; | ||
} | ||
|
||
.maincontainer { | ||
width: 100%; | ||
height: 100%; | ||
background-color: var.$white; | ||
display: flex; | ||
flex-direction: row; | ||
align-items: center; | ||
border-top: 2px solid var.$bg-subscribe; | ||
border-bottom: 2px solid var.$bg-subscribe; | ||
|
||
@include media.respond-to('middle') { | ||
border-top: none; | ||
border-bottom: none; | ||
} | ||
} | ||
} | ||
} |
53 changes: 53 additions & 0 deletions
53
src/features/ProductImgCarousel/ProductImgCarousel.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import { Meta, StoryObj } from '@storybook/react' | ||
|
||
import { ProductImgCarousel } from './ProductImgCarousel' | ||
|
||
export default { component: ProductImgCarousel } | ||
|
||
const Metadata = { | ||
title: 'features/ProductImgCarousel', | ||
component: ProductImgCarousel, | ||
parameters: { | ||
product: 'данные о товаре' | ||
} | ||
} as Meta<typeof ProductImgCarousel> | ||
|
||
type Story = StoryObj<typeof Metadata> | ||
|
||
export const Default: Story = { | ||
args: { | ||
imgList: [ | ||
{ | ||
image: 'https://ir.ozone.ru/s3/multimedia-b/wc1000/6661991531.jpg' | ||
}, | ||
{ | ||
image: 'https://ir.ozone.ru/s3/multimedia-9/wc1000/6661991493.jpg' | ||
}, | ||
{ | ||
image: 'https://ir.ozone.ru/s3/multimedia-j/wc1000/6661991575.jpg' | ||
}, | ||
{ | ||
image: | ||
'https://maxboom.ru/image/cachewebp/catalog/vol862/part86256/86256350/images/big/1-1000x1000.webp' | ||
}, | ||
{ | ||
image: 'https://ir.ozone.ru/s3/multimedia-y/wc1000/6661991482.jpg' | ||
}, | ||
{ | ||
image: 'https://ir.ozone.ru/s3/multimedia-v/wc1000/6661991551.jpg' | ||
}, | ||
{ | ||
image: 'https://ir.ozone.ru/s3/multimedia-1-x/wc1000/6928616361.jpg' | ||
}, | ||
{ | ||
image: 'https://ir.ozone.ru/s3/multimedia-n/wc1000/6767151887.jpg' | ||
}, | ||
{ | ||
image: 'https://ir.ozone.ru/s3/multimedia-d/wc1000/6767151877.jpg' | ||
} | ||
], | ||
setShowPopup: () => { | ||
alert('Должен всплывать попап') | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { useState, type FC } from 'react' | ||
|
||
import { TProductImgCarouselProps } from './model/types/productImgCarouselType' | ||
import styles from './ProductImgCarousel.module.scss' | ||
import { ImgCarousel } from './ui/ImgCarousel/ImgCarousel' | ||
import { PreviewCarousel } from './ui/PreviewCarousel/PreviewCarousel' | ||
|
||
/** | ||
* Компонент переключения фотографий товара на странице товара | ||
* @param imgList (TImgList) - список фотографий товаров | ||
* @param setShowPopup (f(boolean)) - функция управления видимостью попапа | ||
*/ | ||
export const ProductImgCarousel: FC<TProductImgCarouselProps> = ({ imgList, setShowPopup }) => { | ||
const [curImg, setCurImg] = useState<number>(0) | ||
|
||
return ( | ||
<section className={styles.wrapper}> | ||
<PreviewCarousel imgList={imgList} curImg={curImg} setCurImg={setCurImg} /> | ||
<div className={styles.carouselwrapper}> | ||
<div className={styles.maincontainer}> | ||
{<ImgCarousel isPopup={false} imgList={imgList} setShowPopup={setShowPopup} curImg={curImg} />} | ||
</div> | ||
</div> | ||
</section> | ||
) | ||
} |
22 changes: 22 additions & 0 deletions
22
src/features/ProductImgCarousel/model/constants/constants.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { TImgSizes } from '../types/productImgCarouselType' | ||
|
||
export const IMG_SIZE_PAGE: TImgSizes = { | ||
Xl: { | ||
width: 500, | ||
height: 500 | ||
}, | ||
Md: { | ||
width: 300, | ||
height: 300 | ||
} | ||
} | ||
export const IMG_SIZE_POPUP: TImgSizes = { | ||
Xl: { | ||
width: 720, | ||
height: 720 | ||
}, | ||
Md: { | ||
width: 470, | ||
height: 470 | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
src/features/ProductImgCarousel/model/functions/functions.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { TImgList } from '../types/productImgCarouselType' | ||
|
||
export const slicedImgList = (imgList: TImgList, maxCount: number, curIndex: number): TImgList => { | ||
if (imgList.length > maxCount) { | ||
let end = maxCount | ||
let start = 0 | ||
if (curIndex > maxCount - 1) { | ||
end = curIndex + 1 | ||
start = curIndex - (maxCount - 1) | ||
} | ||
return imgList.slice(start, end) | ||
} | ||
return imgList.slice(0) | ||
} | ||
|
||
export const getDisplayedIndex = (maxCount: number, curIndex: number): number => { | ||
return curIndex > maxCount - 1 ? maxCount - 1 : curIndex | ||
} |
47 changes: 47 additions & 0 deletions
47
src/features/ProductImgCarousel/model/types/productImgCarouselType.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { Dispatch, SetStateAction } from 'react' | ||
|
||
export type TBoxProps = { | ||
children: React.ReactNode | ||
} | ||
|
||
interface IObjectWithImage { | ||
image: string | ||
index?: number | ||
} | ||
|
||
export type TImgList = Array<IObjectWithImage> | ||
|
||
export type TImgCarouselProps = { | ||
imgList: TImgList | ||
isPopup: boolean | ||
setShowPopup: Dispatch<SetStateAction<boolean>> | ||
curImg: number | ||
} | ||
|
||
export type TProductImgCarouselProps = { | ||
imgList: TImgList | ||
setShowPopup: Dispatch<SetStateAction<boolean>> | ||
} | ||
|
||
export type TImgSize = { | ||
width: number | ||
height: number | ||
} | ||
|
||
export type TImgSizes = { | ||
Xl: TImgSize | ||
Md: TImgSize | ||
} | ||
|
||
export type TPreviewCarouselProps = { | ||
imgList: TImgList | ||
curImg: number | ||
setCurImg: Dispatch<SetStateAction<number>> | ||
} | ||
|
||
export type TDisplayedImgList = { | ||
displayedIndex: number | ||
displayedImages: TImgList | ||
} | ||
|
||
export type TchangeImgArgs = 'next' | 'prev' | 'current' |
31 changes: 31 additions & 0 deletions
31
src/features/ProductImgCarousel/ui/ImgCarousel/ImgCarousel.module.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
@use '@/shared/styles/utils/variables' as var; | ||
@use '@/shared/styles/utils/mixins' as media; | ||
|
||
.imgcarousel { | ||
height: 100%; | ||
width: 100%; | ||
overflow: hidden; | ||
|
||
&__allimg { | ||
height: 100%; | ||
display: flex; | ||
align-items: center; | ||
transition: translate; | ||
transition-property: transform; | ||
transition-duration: 300ms; | ||
transition-timing-function: ease-in-out; | ||
} | ||
|
||
&__imageframe { | ||
min-width: '100%'; | ||
height: '100%'; | ||
background-color: var.$white; | ||
display: flex; | ||
justify-content: center; | ||
align-items: center; | ||
background-size: contain; | ||
background-repeat: no-repeat; | ||
background-position: center center; | ||
cursor: pointer; | ||
} | ||
} |
128 changes: 128 additions & 0 deletions
128
src/features/ProductImgCarousel/ui/ImgCarousel/ImgCarousel.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
import React, { FC, useEffect, useMemo, useState } from 'react' | ||
|
||
import { useResize } from '@/shared/libs/hooks/useResize' | ||
|
||
import { IMG_SIZE_PAGE, IMG_SIZE_POPUP } from '../../model/constants/constants' | ||
import { TImgCarouselProps } from '../../model/types/productImgCarouselType' | ||
|
||
import styles from './ImgCarousel.module.scss' | ||
|
||
/** | ||
* Компонент фотографии товара на странице товара. | ||
* @param isPopup (boolean) - значение true, если компонент отображается в рамках попапа | ||
* @param imgList (TImgList) - список изображений | ||
* @param setShowPopup (f(boolean)) - функция управления видимостью попапа | ||
* @param curImg (number) - индекс текущего изображения | ||
*/ | ||
export const ImgCarousel: FC<TImgCarouselProps> = ({ imgList, isPopup, setShowPopup, curImg }) => { | ||
const [offset, setOffset] = useState(() => ({ | ||
imgOffset: '0', | ||
offsetX: 'center', | ||
offsetY: 'center' | ||
})) | ||
const [scale, setScale] = useState('contain') | ||
|
||
const resize = useResize() | ||
|
||
const getImgSise = () => { | ||
if (!resize.isScreenMd) { | ||
return isPopup ? IMG_SIZE_POPUP.Md : IMG_SIZE_PAGE.Md | ||
} else { | ||
return isPopup ? IMG_SIZE_POPUP.Xl : IMG_SIZE_PAGE.Xl | ||
} | ||
} | ||
|
||
const [imgSize, setImgSize] = useState(getImgSise()) | ||
|
||
useEffect(() => { | ||
setImgSize(getImgSise()) | ||
}, [resize.isScreenMd]) | ||
|
||
useEffect(() => { | ||
setOffset({ | ||
imgOffset: (-imgSize.width * curImg).toString(), | ||
offsetX: 'center', | ||
offsetY: 'center' | ||
}) | ||
setScale('contain') | ||
}, [curImg]) | ||
|
||
const resetScale = (): void => { | ||
setScale('contain') | ||
setOffset(prevOffset => ({ | ||
imgOffset: prevOffset.imgOffset, | ||
offsetX: 'center', | ||
offsetY: 'center' | ||
})) | ||
} | ||
|
||
//Ф-я определяет, что курсор покидает картинку. Нужна т.к. события mouseOut/mouseLeave зачастую не срабатывают | ||
const isMouseOut = (x: number, y: number): boolean => { | ||
return x > imgSize.width - 10 || x < 10 || y < 10 || y > imgSize.height - 10 | ||
} | ||
|
||
const moseMoveHandle = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => { | ||
if (isMouseOut(e.nativeEvent.offsetX, e.nativeEvent.offsetY)) { | ||
resetScale() | ||
} else { | ||
setOffset(prevOffset => ({ | ||
imgOffset: prevOffset.imgOffset, | ||
offsetX: `${Math.max(-e.nativeEvent.offsetX, -imgSize.width / 2)}px`, | ||
offsetY: `${-e.nativeEvent.offsetY}px` | ||
})) | ||
if (scale !== '150%') { | ||
setScale('150%') | ||
} | ||
} | ||
} | ||
|
||
const mouseLeaveHanle = () => { | ||
resetScale() | ||
} | ||
|
||
const mouseClickHandle = () => { | ||
setShowPopup(true) | ||
//фиксация body, для отключения прокрутки при откытом попапе | ||
//убираю скрол | ||
const body = document.body | ||
body.style.position = 'fixed' | ||
} | ||
|
||
const photoList = useMemo(() => { | ||
return imgList.map(i => ( | ||
<div | ||
className={`${styles.imgcarousel__imageframe}`} | ||
key={i.index} | ||
style={{ | ||
backgroundImage: `url(${i.image})`, | ||
backgroundPositionX: `${offset.offsetX}`, | ||
backgroundPositionY: `${offset.offsetY}`, | ||
backgroundSize: `${scale}`, | ||
minWidth: `100%`, | ||
height: `100%`, | ||
cursor: `${isPopup ? 'all-scroll' : 'pointer'}` | ||
}} | ||
onMouseMove={moseMoveHandle} | ||
onMouseOut={mouseLeaveHanle} | ||
onClick={!isPopup ? mouseClickHandle : undefined}></div> | ||
)) | ||
}, [imgList, offset, scale]) | ||
|
||
return ( | ||
<div | ||
className={styles.imgcarousel} | ||
style={{ | ||
height: `${imgSize.height}px` | ||
}}> | ||
<div className={styles.popular}></div> | ||
|
||
<div | ||
className={styles.imgcarousel__allimg} | ||
style={{ | ||
transform: `translateX(${offset.imgOffset}px)` | ||
}}> | ||
{photoList} | ||
</div> | ||
</div> | ||
) | ||
} |
Oops, something went wrong.