mirror of
https://github.com/vercel/commerce.git
synced 2025-07-29 05:01:22 +00:00
✨ feat: product caroucel
:%s
This commit is contained in:
@@ -53,11 +53,11 @@
|
||||
|
||||
&.ghost {
|
||||
@apply bg-white;
|
||||
color: var(--primary);
|
||||
border: 1px solid var(--primary);
|
||||
color: var(--text-active);
|
||||
border: 1px solid var(--text-active);
|
||||
&.loading {
|
||||
&::before {
|
||||
border-top-color: var(--primary);
|
||||
border-top-color: var(--text-active);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,25 @@
|
||||
.navigation_wrapper{
|
||||
@import '../../../styles/utilities';
|
||||
.navigationWrapper{
|
||||
@apply relative;
|
||||
min-height: theme("caroucel.arrow-height") ;
|
||||
|
||||
.isPadding{
|
||||
@apply spacing-horizontal;
|
||||
}
|
||||
:global(.customArrow) {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
&:focus{
|
||||
outline: none;
|
||||
}
|
||||
@apply absolute top-1/2 bg-background-arrow transform -translate-y-1/2 flex justify-center items-center transition duration-100;
|
||||
&:global(.leftArrow){
|
||||
@apply left-0;
|
||||
}
|
||||
&:global(.rightArrow){
|
||||
@apply right-0;
|
||||
}
|
||||
&:global(.isDisabledArrow){
|
||||
@apply hidden ;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,21 +1,30 @@
|
||||
import { useKeenSlider } from 'keen-slider/react'
|
||||
import React from 'react'
|
||||
import 'keen-slider/keen-slider.min.css'
|
||||
import { CustomCarouselArrow } from './CustomArrow/CustomCarouselArrow';
|
||||
import s from "./CaroucelCommon.module.scss"
|
||||
interface CarouselCommonProps {
|
||||
children?: React.ReactNode
|
||||
data?: any[]
|
||||
Component: React.ComponentType<any>
|
||||
isArrow?:Boolean
|
||||
itemKey:String
|
||||
import { CustomCarouselArrow } from './CustomArrow/CustomCarouselArrow'
|
||||
import s from './CaroucelCommon.module.scss'
|
||||
import { TOptionsEvents } from 'keen-slider'
|
||||
import classNames from 'classnames'
|
||||
export interface CarouselCommonProps<T> {
|
||||
data: T[]
|
||||
Component: React.ComponentType<T>
|
||||
isArrow?: Boolean
|
||||
itemKey: String
|
||||
option: TOptionsEvents
|
||||
keenClassname?: string
|
||||
isPadding?: boolean
|
||||
}
|
||||
|
||||
const CarouselCommon = ({ data, Component,itemKey }: CarouselCommonProps) => {
|
||||
const CarouselCommon = <T,>({
|
||||
data,
|
||||
Component,
|
||||
itemKey,
|
||||
keenClassname,isPadding=false,
|
||||
option: { slideChanged, ...sliderOption },
|
||||
}: CarouselCommonProps<T>) => {
|
||||
const [currentSlide, setCurrentSlide] = React.useState(0)
|
||||
const [sliderRef, slider] = useKeenSlider<HTMLDivElement>({
|
||||
slidesPerView: 1,
|
||||
initial: 0,
|
||||
...sliderOption,
|
||||
slideChanged(s) {
|
||||
setCurrentSlide(s.details().relativeSlide)
|
||||
},
|
||||
@@ -28,28 +37,28 @@ const CarouselCommon = ({ data, Component,itemKey }: CarouselCommonProps) => {
|
||||
slider.prev()
|
||||
}
|
||||
return (
|
||||
<div className={s.navigation_wrapper}>
|
||||
<div ref={sliderRef} className="keen-slider">
|
||||
{data?.map((props,index) => (
|
||||
<div className={s.navigationWrapper}>
|
||||
<div ref={sliderRef} className={classNames('keen-slider', keenClassname,{[s.isPadding]:isPadding})}>
|
||||
{data?.map((props, index) => (
|
||||
<div className="keen-slider__slide" key={`${itemKey}-${index}`}>
|
||||
<Component {...props} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{slider && (
|
||||
<>
|
||||
<CustomCarouselArrow
|
||||
side="right"
|
||||
onClick={handleRightArrowClick}
|
||||
isDisabled={currentSlide === slider.details().size - 1}
|
||||
/>
|
||||
<CustomCarouselArrow
|
||||
side="left"
|
||||
onClick={handleLeftArrowClick}
|
||||
isDisabled={currentSlide === 0}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<>
|
||||
<CustomCarouselArrow
|
||||
side="right"
|
||||
onClick={handleRightArrowClick}
|
||||
// isDisabled={currentSlide === slider.details().size - 1}
|
||||
/>
|
||||
<CustomCarouselArrow
|
||||
side="left"
|
||||
onClick={handleLeftArrowClick}
|
||||
// isDisabled={currentSlide === 0}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@@ -1,17 +1,37 @@
|
||||
.custom_arrow{
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
&:focus{
|
||||
outline: none;
|
||||
}
|
||||
@apply absolute top-1/2 bg-background-arrow transform -translate-y-1/2 flex justify-center items-center transition duration-100;
|
||||
&.left{
|
||||
@apply left-0;
|
||||
}
|
||||
&.right{
|
||||
@apply right-0;
|
||||
}
|
||||
&.isDisabled{
|
||||
@apply hidden ;
|
||||
// .customArrow{
|
||||
// width: 64px;
|
||||
// height: 64px;
|
||||
// &:focus{
|
||||
// outline: none;
|
||||
// }
|
||||
// @apply absolute top-1/2 bg-background-arrow transform -translate-y-1/2 flex justify-center items-center transition duration-100;
|
||||
// &.leftArrow{
|
||||
// @apply left-0;
|
||||
// }
|
||||
// &.rightArrow{
|
||||
// @apply right-0;
|
||||
// }
|
||||
// &.isDisabled{
|
||||
// @apply hidden ;
|
||||
// }
|
||||
// }
|
||||
.navigationWrapper{
|
||||
:global(.customArrow) {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
&:focus{
|
||||
outline: none;
|
||||
}
|
||||
@apply absolute top-1/2 bg-background-arrow transform -translate-y-1/2 flex justify-center items-center transition duration-100;
|
||||
&.leftArrow{
|
||||
@apply left-0;
|
||||
}
|
||||
&.rightArrow{
|
||||
@apply right-0;
|
||||
}
|
||||
&.isDisabled{
|
||||
@apply hidden ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -2,11 +2,13 @@ import classNames from 'classnames'
|
||||
import React from 'react'
|
||||
import ArrowLeft from 'src/components/icons/ArrowLeft'
|
||||
import ArrowRight from 'src/components/icons/ArrowRight'
|
||||
import s from "./CustomCarouselArrow.module.scss"
|
||||
import "./CustomCarouselArrow.module.scss"
|
||||
// import s from "../CaroucelCommon.module.scss"
|
||||
|
||||
interface CustomCarouselArrowProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
side: 'left' | 'right'
|
||||
isDisabled:Boolean
|
||||
isDisabled?:Boolean
|
||||
}
|
||||
|
||||
export const CustomCarouselArrow = ({
|
||||
@@ -16,7 +18,8 @@ export const CustomCarouselArrow = ({
|
||||
return (
|
||||
<button
|
||||
{...props}
|
||||
className={classNames(`${s.custom_arrow}`, { [`${s[side]}`]: side,[`${s.isDisabled}`]:isDisabled })}
|
||||
// className={classNames(`${s.customArrow}`, { [`${s[`${side}Arrow`]}`]: side,[`${s.isDisabled}`]:isDisabled })}
|
||||
className={classNames("customArrow", { [`${side}Arrow`]: side,"isDisabledArrow":isDisabled})}
|
||||
>
|
||||
{side==='left'?(<ArrowLeft/>):(<ArrowRight/>)}
|
||||
</button>
|
||||
|
@@ -2,9 +2,14 @@
|
||||
|
||||
.heartToggle{
|
||||
cursor: pointer;
|
||||
width: 4.8rem;
|
||||
height: 4.8rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 2.4rem;
|
||||
height: 2.4rem;
|
||||
path{
|
||||
stroke: theme("colors.primary");
|
||||
}
|
||||
}
|
||||
.isToggleOn{
|
||||
svg path{
|
||||
stroke: theme("colors.primary");
|
||||
}
|
||||
}
|
@@ -1,19 +1,27 @@
|
||||
import { Heart } from '@components/icons'
|
||||
import React, { useState } from 'react'
|
||||
import classNames from 'classnames'
|
||||
import IconHeart from 'src/components/icons/IconHeart'
|
||||
import React, { memo } from 'react'
|
||||
import s from './ItemWishList.module.scss'
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
children?: any
|
||||
isActive?: boolean,
|
||||
onClick?: () => void
|
||||
onChange?: () => void
|
||||
}
|
||||
|
||||
const ItemWishList = ({}:Props) => {
|
||||
const [isClick,setClick] = useState(false)
|
||||
const ItemWishList = memo(({isActive, onClick, onChange}:Props) => {
|
||||
const handleClick = () => {
|
||||
isActive = !isActive
|
||||
}
|
||||
return(
|
||||
<div className={s.heartToggle} onClick={() => setClick(!isClick)}>
|
||||
<Heart color={isClick ? "#D1644D" : "#5B9A74"}/>
|
||||
<div className={classNames({
|
||||
[s.heartToggle]:true,
|
||||
[s.isToggleOn]:isActive
|
||||
})}
|
||||
onClick={handleClick}>
|
||||
<IconHeart />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
export default ItemWishList
|
@@ -1,6 +1,4 @@
|
||||
@import '../../../styles/utilities';
|
||||
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@700&display=swap');
|
||||
|
||||
|
||||
.logo{
|
||||
display: flex;
|
||||
@@ -12,7 +10,7 @@
|
||||
margin-right: 1.2rem;
|
||||
}
|
||||
.conTent{
|
||||
font-family: 'Poppins', sans-serif;
|
||||
@apply font-logo;
|
||||
text-transform: uppercase;
|
||||
line-height: 3.2rem;
|
||||
letter-spacing: -0.02rem;
|
||||
|
@@ -1,15 +1,13 @@
|
||||
import s from './Logo.module.scss'
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
children?: any
|
||||
|
||||
}
|
||||
|
||||
const Logo = ({}: Props) => {
|
||||
return(
|
||||
<div className={s.logo}>
|
||||
<div className={s.eclipse}>
|
||||
|
||||
</div>
|
||||
<div className={s.conTent}>
|
||||
ONLINE GROCERY
|
||||
|
@@ -1,24 +1,30 @@
|
||||
.productCardWarpper{
|
||||
max-width: 20.8rem;
|
||||
max-height: 31.8rem;
|
||||
width: 20.8rem;
|
||||
height: 31.8rem;
|
||||
padding: 1.2rem 1.2rem 0 1.2rem;
|
||||
@apply border border-solid border-black;
|
||||
margin-bottom: 1px;
|
||||
@apply inline-flex flex-col justify-between;
|
||||
.cardTop{
|
||||
@apply border border-solid border-yellow-300 relative;
|
||||
max-height: 13.8rem;
|
||||
@apply relative;
|
||||
height: 13.8rem;
|
||||
width: 100%;
|
||||
.productImage{
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
@apply flex justify-center items-center;
|
||||
img{
|
||||
@apply inline;
|
||||
}
|
||||
.productLabel{
|
||||
@apply absolute left-0 bottom-0;
|
||||
}
|
||||
}
|
||||
.productLabel{
|
||||
@apply absolute left-0 bottom-0;
|
||||
}
|
||||
}
|
||||
.cardMid{
|
||||
padding: 1.6rem 0;
|
||||
.cardMid{
|
||||
// padding: 1.6rem 0;
|
||||
min-height: 10.4rem;
|
||||
@apply flex flex-col justify-between;
|
||||
.cardMidTop{
|
||||
.productname{
|
||||
font-weight: bold;
|
||||
line-height: 2.4rem;
|
||||
@@ -33,7 +39,6 @@
|
||||
}
|
||||
}
|
||||
.cardMidBot{
|
||||
margin-top: 2.8rem;
|
||||
@apply flex justify-between items-center border-t border-solid border-line;
|
||||
.productPrice{
|
||||
@apply font-bold;
|
||||
@@ -44,6 +49,7 @@
|
||||
}
|
||||
}
|
||||
.cardBot{
|
||||
max-height: 4rem;
|
||||
@apply flex justify-between items-center;
|
||||
.cardButton{
|
||||
width: 13.6rem;
|
||||
|
@@ -1,17 +1,13 @@
|
||||
import React from 'react'
|
||||
import { ProductProps } from 'src/utils/types.utils'
|
||||
import ButtonCommon from '../ButtonCommon/ButtonCommon'
|
||||
import ButtonIconBuy from '../ButtonIconBuy/ButtonIconBuy'
|
||||
import ItemWishList from '../ItemWishList/ItemWishList'
|
||||
import LabelCommon from '../LabelCommon/LabelCommon'
|
||||
import s from './ProductCard.module.scss'
|
||||
|
||||
interface ProductCardProps {
|
||||
category: string
|
||||
name: string
|
||||
weight: string
|
||||
price: string
|
||||
export interface ProductCardProps extends ProductProps {
|
||||
buttonText?: string
|
||||
imageSrc: string
|
||||
}
|
||||
|
||||
const ProductCard = ({
|
||||
@@ -23,16 +19,16 @@ const ProductCard = ({
|
||||
imageSrc,
|
||||
}: ProductCardProps) => {
|
||||
return (
|
||||
<section className={s.productCardWarpper}>
|
||||
<section className={s.cardTop}>
|
||||
<div className={s.productCardWarpper}>
|
||||
<div className={s.cardTop}>
|
||||
<div className={s.productImage}>
|
||||
<img src={imageSrc} alt="image" />
|
||||
</div>
|
||||
<div className={s.productLabel}>
|
||||
<LabelCommon shape="half">{category}</LabelCommon>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section className={s.cardMid}>
|
||||
</div>
|
||||
<div className={s.cardMid}>
|
||||
<div className={s.cardMidTop}>
|
||||
<div className={s.productname}>{name} </div>
|
||||
<div className={s.productWeight}>{weight}</div>
|
||||
@@ -43,16 +39,16 @@ const ProductCard = ({
|
||||
<ItemWishList />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section className={s.cardBot}>
|
||||
</div>
|
||||
<div className={s.cardBot}>
|
||||
<div className={s.cardIcon}>
|
||||
<ButtonIconBuy/>
|
||||
</div>
|
||||
<div className={s.cardButton}>
|
||||
<ButtonCommon type="ghost">{buttonText}</ButtonCommon>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,12 @@
|
||||
@import "../../../styles/utilities";
|
||||
.productCardWarpper{
|
||||
@apply spacing-horizontal;
|
||||
:global(.customArrow) {
|
||||
&:global(.leftArrow){
|
||||
left: calc(-6.4rem - 2rem);
|
||||
}
|
||||
&:global(.rightArrow){
|
||||
right: calc(-6.4rem - 2rem);
|
||||
}
|
||||
}
|
||||
}
|
39
src/components/common/ProductCaroucel/ProductCaroucel.tsx
Normal file
39
src/components/common/ProductCaroucel/ProductCaroucel.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import { TOptionsEvents } from 'keen-slider'
|
||||
import React from 'react'
|
||||
import CarouselCommon, {
|
||||
CarouselCommonProps,
|
||||
} from '../CarouselCommon/CarouselCommon'
|
||||
import ProductCard, { ProductCardProps } from '../ProductCard/ProductCard'
|
||||
import s from "./ProductCaroucel.module.scss"
|
||||
|
||||
interface ProductCaroucelProps
|
||||
extends Omit<CarouselCommonProps<ProductCardProps>, 'Component'|"option"> {
|
||||
option?:TOptionsEvents
|
||||
}
|
||||
|
||||
const OPTION_DEFAULT: TOptionsEvents = {
|
||||
slidesPerView: 2,
|
||||
mode: 'free',
|
||||
breakpoints: {
|
||||
'(min-width: 768px)': {
|
||||
slidesPerView: 3,
|
||||
},
|
||||
'(min-width: 1024px)': {
|
||||
slidesPerView: 5.5,
|
||||
},
|
||||
},
|
||||
}
|
||||
const ProductCaroucel = ({ option, data, ...props }: ProductCaroucelProps) => {
|
||||
return (
|
||||
<div className={s.productCardWarpper}>
|
||||
<CarouselCommon<ProductCardProps>
|
||||
data={data}
|
||||
Component={ProductCard}
|
||||
{...props}
|
||||
option={{ ...OPTION_DEFAULT, ...option }}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ProductCaroucel
|
@@ -1,16 +1,18 @@
|
||||
@import '../../../styles/utilities';
|
||||
@import url('https://fonts.googleapis.com/css2?family=Nunito&display=swap');
|
||||
|
||||
.viewAll{
|
||||
display: flex;
|
||||
color: theme("colors.primary");
|
||||
.conTent{
|
||||
margin: 0.8rem 0.8rem 0.8rem 1.6rem;
|
||||
font-family: 'Nunito', sans-serif;
|
||||
font-family: var(--font-sans);
|
||||
font-weight: bold;
|
||||
}
|
||||
.vecTor{
|
||||
margin: 0.8rem 0rem 0.8rem 0rem;
|
||||
svg path{
|
||||
fill: theme("colors.primary");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,10 +1,8 @@
|
||||
import Vector from 'src/components/icons/Vector'
|
||||
import IconVector from 'src/components/icons/IconVector'
|
||||
import s from './ViewAllItem.module.scss'
|
||||
import Link from 'next/link'
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
children?: any
|
||||
link?: string
|
||||
}
|
||||
|
||||
@@ -17,7 +15,7 @@ const ViewAllItem = ({ link }: Props) => {
|
||||
</a>
|
||||
</Link>
|
||||
<div className={s.vecTor}>
|
||||
<Vector />
|
||||
<IconVector />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
Reference in New Issue
Block a user