feat: product caroucel

:%s
This commit is contained in:
unknown
2021-08-25 13:41:45 +07:00
20 changed files with 320 additions and 136 deletions

View File

@@ -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);
}
}
}

View File

@@ -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 ;
}
}
}

View File

@@ -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>
)
}

View File

@@ -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 ;
}
}
}

View File

@@ -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>

View File

@@ -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");
}
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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;

View File

@@ -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>
)
}

View File

@@ -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);
}
}
}

View 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

View File

@@ -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");
}
}
}

View File

@@ -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>
)