mirror of
https://github.com/vercel/commerce.git
synced 2025-07-27 04:01:23 +00:00
🔀 merge: Merge branch 'common' of https://github.com/KieIO/grocery-vercel-commerce into m3-lytran
:%s
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 1.2rem 3.2rem;
|
||||
padding: 0.8rem 3.2rem;
|
||||
&:disabled {
|
||||
filter: brightness(0.9);
|
||||
cursor: not-allowed;
|
||||
@@ -63,7 +63,7 @@
|
||||
border: 1px solid var(--primary);
|
||||
&.loading {
|
||||
&::before {
|
||||
border-top-color: var(--primary);
|
||||
border-top-color: var(--text-active);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +0,0 @@
|
||||
.navigation_wrapper{
|
||||
@apply relative;
|
||||
min-height: theme("caroucel.arrow-height") ;
|
||||
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
@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;
|
||||
}
|
||||
}
|
||||
:global {
|
||||
.dots {
|
||||
display: flex;
|
||||
padding: 1rem 0;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.dot {
|
||||
border: none;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
background: #c5c5c5;
|
||||
border-radius: 50%;
|
||||
margin: 0 0.5rem;
|
||||
padding: 0.5rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dot:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.dot.active {
|
||||
background: #000;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,25 +1,65 @@
|
||||
import { useKeenSlider } from 'keen-slider/react'
|
||||
import React from 'react'
|
||||
import React, { useEffect } 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 './CarouselCommon.module.scss'
|
||||
import { TOptionsEvents } from 'keen-slider'
|
||||
import classNames from 'classnames'
|
||||
import CustomDot from './CustomDot/CustomDot'
|
||||
export interface CarouselCommonProps<T> {
|
||||
data: T[]
|
||||
Component: React.ComponentType<T>
|
||||
isArrow?: Boolean
|
||||
isDot?: Boolean
|
||||
itemKey: String
|
||||
option: TOptionsEvents
|
||||
keenClassname?: string
|
||||
isPadding?: boolean
|
||||
}
|
||||
|
||||
const CarouselCommon = ({ data, Component,itemKey }: CarouselCommonProps) => {
|
||||
const CarouselCommon = <T,>({
|
||||
data,
|
||||
Component,
|
||||
itemKey,
|
||||
keenClassname,
|
||||
isPadding = false,
|
||||
isArrow = true,
|
||||
isDot = false,
|
||||
option: { slideChanged,slidesPerView, ...sliderOption },
|
||||
}: CarouselCommonProps<T>) => {
|
||||
const [currentSlide, setCurrentSlide] = React.useState(0)
|
||||
const [dotActive, setDotActive] = React.useState<number>(0)
|
||||
const [dotArr, setDotArr] = React.useState<number[]>([])
|
||||
const [sliderRef, slider] = useKeenSlider<HTMLDivElement>({
|
||||
slidesPerView: 1,
|
||||
initial: 0,
|
||||
...sliderOption,
|
||||
slidesPerView,
|
||||
slideChanged(s) {
|
||||
setCurrentSlide(s.details().relativeSlide)
|
||||
|
||||
},
|
||||
afterChange(s) {
|
||||
let dot = 0
|
||||
dotArr.forEach((index)=>{
|
||||
if(s.details().relativeSlide >= index){
|
||||
dot = index
|
||||
}
|
||||
})
|
||||
console.log(dot)
|
||||
setDotActive(dot)
|
||||
}
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if(isDot && slider){
|
||||
let array:number[]
|
||||
array = [...Array(Math.ceil(data.length/(Number(slider.details().slidesPerView)||1))).keys()].map((i)=>{
|
||||
return (Number(slider.details().slidesPerView)||1)*i
|
||||
})
|
||||
console.log(array)
|
||||
setDotArr(array)
|
||||
}
|
||||
}, [isDot,slider])
|
||||
|
||||
const handleRightArrowClick = () => {
|
||||
slider.next()
|
||||
}
|
||||
@@ -27,29 +67,46 @@ const CarouselCommon = ({ data, Component,itemKey }: CarouselCommonProps) => {
|
||||
const handleLeftArrowClick = () => {
|
||||
slider.prev()
|
||||
}
|
||||
|
||||
const onDotClick = (index:number) => {
|
||||
slider.moveToSlideRelative(index)
|
||||
setDotActive(index)
|
||||
}
|
||||
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}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{slider && isArrow && (
|
||||
<>
|
||||
<CustomCarouselArrow
|
||||
side="right"
|
||||
onClick={handleRightArrowClick}
|
||||
/>
|
||||
<CustomCarouselArrow
|
||||
side="left"
|
||||
onClick={handleLeftArrowClick}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{slider && isDot && (
|
||||
<div className="dots">
|
||||
{dotArr.map((index) => {
|
||||
return (
|
||||
<CustomDot key={`dot-${index}`} index={index} dotActive={dotActive} onClick={onDotClick}/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@@ -1,17 +1,20 @@
|
||||
.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 ;
|
||||
.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,12 @@ 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"
|
||||
|
||||
interface CustomCarouselArrowProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
side: 'left' | 'right'
|
||||
isDisabled:Boolean
|
||||
isDisabled?:Boolean
|
||||
}
|
||||
|
||||
export const CustomCarouselArrow = ({
|
||||
@@ -16,7 +17,7 @@ export const CustomCarouselArrow = ({
|
||||
return (
|
||||
<button
|
||||
{...props}
|
||||
className={classNames(`${s.custom_arrow}`, { [`${s[side]}`]: side,[`${s.isDisabled}`]:isDisabled })}
|
||||
className={classNames("customArrow", { [`${side}Arrow`]: side,"isDisabledArrow":isDisabled})}
|
||||
>
|
||||
{side==='left'?(<ArrowLeft/>):(<ArrowRight/>)}
|
||||
</button>
|
||||
|
21
src/components/common/CarouselCommon/CustomDot/CustomDot.tsx
Normal file
21
src/components/common/CarouselCommon/CustomDot/CustomDot.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import React from 'react'
|
||||
|
||||
interface Props {
|
||||
index: number
|
||||
dotActive:number
|
||||
onClick: (index: number) => void
|
||||
}
|
||||
|
||||
const CustomDot = ({ index, onClick, dotActive }: Props) => {
|
||||
const handleOnClick = () => {
|
||||
onClick && onClick(index)
|
||||
}
|
||||
return (
|
||||
<button
|
||||
onClick={handleOnClick}
|
||||
className={'dot' + (dotActive === index ? ' active' : '')}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default CustomDot
|
@@ -1,3 +1,5 @@
|
||||
.subtitle {
|
||||
font-size: var(--font-size);
|
||||
line-height: var(--line-height);
|
||||
margin-top: .4rem;
|
||||
}
|
@@ -2,7 +2,7 @@ import React from 'react'
|
||||
import s from './CollectionHeading.module.scss'
|
||||
import HeadingCommon from '../HeadingCommon/HeadingCommon'
|
||||
|
||||
interface CollectionHeadingProps {
|
||||
export interface CollectionHeadingProps {
|
||||
type?: 'default' | 'highlight' | 'light';
|
||||
title: string;
|
||||
subtitle: string;
|
||||
|
@@ -0,0 +1,61 @@
|
||||
@import '../../../styles/utilities';
|
||||
.featuredProductCardWarpper{
|
||||
width: 59.8rem;
|
||||
height: 28.8rem;
|
||||
padding: 2.4rem;
|
||||
@apply bg-primary-light inline-flex justify-start items-center custom-border-radius ;
|
||||
.left{
|
||||
width: 24rem;
|
||||
height: 24rem;
|
||||
}
|
||||
.right{
|
||||
padding-left: 2.4rem;
|
||||
min-width: 27rem;
|
||||
max-width: 28.6rem;
|
||||
min-height: 16.8rem;
|
||||
@apply flex justify-between flex-col;
|
||||
.rightTop{
|
||||
min-height: 9.6rem;
|
||||
@apply flex justify-between flex-col;
|
||||
.title{
|
||||
@apply font-bold;
|
||||
font-size: 2rem;
|
||||
line-height: 2.8rem;
|
||||
letter-spacing: -0.01em;
|
||||
color: var(--text-active);
|
||||
}
|
||||
.subTitle{
|
||||
color: var(--text-base);
|
||||
font-size: 1.6rem;
|
||||
line-height: 2.4rem;
|
||||
}
|
||||
.priceWrapper{
|
||||
@apply flex justify-start;
|
||||
.price{
|
||||
@apply font-bold;
|
||||
font-size: 2rem;
|
||||
line-height: 2.8rem;
|
||||
letter-spacing: -0.01em;
|
||||
color: var(--text-active);
|
||||
}
|
||||
.originPrice{
|
||||
margin-left: 0.8rem;
|
||||
font-size: 2rem;
|
||||
line-height: 2.8rem;
|
||||
color: var(--text-label);
|
||||
text-decoration-line: line-through;
|
||||
}
|
||||
}
|
||||
}
|
||||
.buttonWarpper{
|
||||
@apply flex;
|
||||
.icon{
|
||||
width: 5.6rem;
|
||||
}
|
||||
.button{
|
||||
margin-left: 0.8rem;
|
||||
width: 20.6rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
import React from 'react'
|
||||
import { FeaturedProductProps } from 'src/utils/types.utils'
|
||||
import s from './FeaturedProductCard.module.scss'
|
||||
import { LANGUAGE } from '../../../utils/language.utils'
|
||||
import ButtonIconBuy from '../ButtonIconBuy/ButtonIconBuy'
|
||||
import ButtonCommon from '../ButtonCommon/ButtonCommon'
|
||||
interface FeaturedProductCardProps extends FeaturedProductProps {
|
||||
buttonText?: string
|
||||
}
|
||||
|
||||
const FeaturedProductCard = ({
|
||||
imageSrc,
|
||||
title,
|
||||
subTitle,
|
||||
price,
|
||||
originPrice,
|
||||
buttonText = LANGUAGE.BUTTON_LABEL.BUY_NOW,
|
||||
}: FeaturedProductCardProps) => {
|
||||
return (
|
||||
<div className={s.featuredProductCardWarpper}>
|
||||
<div className={s.left}>
|
||||
<img src={imageSrc} alt="image" />
|
||||
</div>
|
||||
<div className={s.right}>
|
||||
<div className={s.rightTop}>
|
||||
<div className={s.title}>{title}</div>
|
||||
<div className={s.subTitle}>{subTitle}</div>
|
||||
<div className={s.priceWrapper}>
|
||||
<div className={s.price}>{price} </div>
|
||||
<div className={s.originPrice}>{originPrice} </div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={s.buttonWarpper}>
|
||||
<div className={s.icon}>
|
||||
<ButtonIconBuy />
|
||||
</div>
|
||||
<div className={s.button}>
|
||||
<ButtonCommon>{buttonText}</ButtonCommon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default FeaturedProductCard
|
@@ -11,5 +11,7 @@
|
||||
}
|
||||
&.center {
|
||||
@apply text-center;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
28
src/components/common/ModalCommon/ModalCommon.module.scss
Normal file
28
src/components/common/ModalCommon/ModalCommon.module.scss
Normal file
@@ -0,0 +1,28 @@
|
||||
.background{
|
||||
@apply fixed inset-0 overflow-y-auto;
|
||||
background: rgba(20, 20, 20, 0.65);
|
||||
z-index: 10000;
|
||||
.warpper{
|
||||
@apply flex justify-center items-center min-h-screen;
|
||||
.modal{
|
||||
@apply inline-block align-bottom bg-white relative;
|
||||
max-width: 60rem;
|
||||
padding: 3.2rem;
|
||||
box-shadow: 0px 8px 16px rgba(0, 0, 0, 0.24);
|
||||
border-radius: 1.2rem;
|
||||
.title{
|
||||
padding: 0 0.8rem 0 0.8rem;
|
||||
font-size: 3.2rem;
|
||||
line-height: 4rem;
|
||||
}
|
||||
.close{
|
||||
@apply absolute;
|
||||
&:hover{
|
||||
cursor: pointer;
|
||||
}
|
||||
top:4.4rem;
|
||||
right: 4.4rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
40
src/components/common/ModalCommon/ModalCommon.tsx
Normal file
40
src/components/common/ModalCommon/ModalCommon.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import React, { useRef } from 'react'
|
||||
import { Close } from 'src/components/icons'
|
||||
import { useOnClickOutside } from 'src/utils/useClickOutSide'
|
||||
import s from './ModalCommon.module.scss'
|
||||
interface Props {
|
||||
onClose: () => void
|
||||
visible: boolean
|
||||
children: React.ReactNode
|
||||
title?: string
|
||||
maxWidth?:string
|
||||
}
|
||||
|
||||
const ModalCommon = ({ onClose, visible, children, title="Modal",maxWidth }: Props) => {
|
||||
const modalRef = useRef<HTMLDivElement>(null)
|
||||
const clickOutSide = () => {
|
||||
onClose && onClose()
|
||||
}
|
||||
useOnClickOutside(modalRef, clickOutSide)
|
||||
return (
|
||||
<>
|
||||
{visible && (
|
||||
<div className={s.background}>
|
||||
<div className={s.warpper}>
|
||||
<div className={s.modal} ref={modalRef} style={{maxWidth}}>
|
||||
<div className={s.top}>
|
||||
<div className={s.title}>{title}</div>
|
||||
<div className={s.close} onClick={clickOutSide}>
|
||||
<Close />
|
||||
</div>
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ModalCommon
|
63
src/components/common/ProductCard/ProductCard.module.scss
Normal file
63
src/components/common/ProductCard/ProductCard.module.scss
Normal file
@@ -0,0 +1,63 @@
|
||||
.productCardWarpper{
|
||||
max-width: 20.8rem;
|
||||
min-height: 31.8rem;
|
||||
padding: 1.2rem 1.2rem 0 1.2rem;
|
||||
margin-bottom: 1px;
|
||||
@apply flex flex-col justify-between;
|
||||
.cardTop{
|
||||
@apply relative;
|
||||
height: 13.8rem;
|
||||
width: 100%;
|
||||
.productImage{
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
@apply flex justify-center items-center;
|
||||
img{
|
||||
@apply inline;
|
||||
}
|
||||
&:hover{
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.productLabel{
|
||||
@apply absolute left-0 bottom-0;
|
||||
}
|
||||
}
|
||||
.cardMid{
|
||||
min-height: 10.4rem;
|
||||
@apply flex flex-col justify-between;
|
||||
.cardMidTop{
|
||||
.productname{
|
||||
font-weight: bold;
|
||||
line-height: 2.4rem;
|
||||
font-size: 1.6rem;
|
||||
color: var(--text-active);
|
||||
&:hover{
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.productWeight{
|
||||
font-size: 1.2rem;
|
||||
line-height: 2rem;
|
||||
letter-spacing: 0.01em;
|
||||
color: var(--text-base);
|
||||
}
|
||||
}
|
||||
.cardMidBot{
|
||||
padding-top: 0.8rem;
|
||||
@apply flex justify-between items-center border-t border-solid border-line;
|
||||
.productPrice{
|
||||
@apply font-bold;
|
||||
font-size: 2rem;
|
||||
line-height: 2.8rem;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
}
|
||||
}
|
||||
.cardBot{
|
||||
min-height: 4rem;
|
||||
@apply flex justify-between items-center;
|
||||
.cardButton{
|
||||
}
|
||||
}
|
||||
}
|
60
src/components/common/ProductCard/ProductCard.tsx
Normal file
60
src/components/common/ProductCard/ProductCard.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import Link from 'next/link'
|
||||
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'
|
||||
|
||||
export interface ProductCardProps extends ProductProps {
|
||||
buttonText?: string
|
||||
}
|
||||
|
||||
const ProductCard = ({
|
||||
category,
|
||||
name,
|
||||
weight,
|
||||
price,
|
||||
buttonText = 'Buy Now',
|
||||
imageSrc,
|
||||
}: ProductCardProps) => {
|
||||
return (
|
||||
<div className={s.productCardWarpper}>
|
||||
<div className={s.cardTop}>
|
||||
<Link href="#">
|
||||
<div className={s.productImage}>
|
||||
<img src={imageSrc} alt="image" />
|
||||
</div>
|
||||
</Link>
|
||||
<div className={s.productLabel}>
|
||||
<LabelCommon shape="half">{category}</LabelCommon>
|
||||
</div>
|
||||
</div>
|
||||
<div className={s.cardMid}>
|
||||
<div className={s.cardMidTop}>
|
||||
<Link href="#">
|
||||
<div className={s.productname}>{name} </div>
|
||||
</Link>
|
||||
<div className={s.productWeight}>{weight}</div>
|
||||
</div>
|
||||
<div className={s.cardMidBot}>
|
||||
<div className={s.productPrice}>{price}</div>
|
||||
<div className={s.wishList}>
|
||||
<ItemWishList />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={s.cardBot}>
|
||||
<div className={s.cardIcon}>
|
||||
<ButtonIconBuy />
|
||||
</div>
|
||||
<div className={s.cardButton}>
|
||||
<ButtonCommon type="light">{buttonText}</ButtonCommon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ProductCard
|
@@ -0,0 +1,16 @@
|
||||
@import '../../../styles/utilities';
|
||||
.productCardWarpper {
|
||||
@apply spacing-horizontal;
|
||||
@screen xl {
|
||||
:global(.customArrow) {
|
||||
@screen lg {
|
||||
&:global(.leftArrow) {
|
||||
left: calc(-6.4rem - 2rem);
|
||||
}
|
||||
&:global(.rightArrow) {
|
||||
right: calc(-6.4rem - 2rem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
44
src/components/common/ProductCarousel/ProductCarousel.tsx
Normal file
44
src/components/common/ProductCarousel/ProductCarousel.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import { TOptionsEvents } from 'keen-slider'
|
||||
import React from 'react'
|
||||
import CarouselCommon, {
|
||||
CarouselCommonProps,
|
||||
} from '../CarouselCommon/CarouselCommon'
|
||||
import ProductCard, { ProductCardProps } from '../ProductCard/ProductCard'
|
||||
import s from "./ProductCarousel.module.scss"
|
||||
|
||||
interface ProductCarouselProps
|
||||
extends Omit<CarouselCommonProps<ProductCardProps>, 'Component'|"option"> {
|
||||
option?:TOptionsEvents
|
||||
}
|
||||
|
||||
const OPTION_DEFAULT: TOptionsEvents = {
|
||||
slidesPerView: 2,
|
||||
mode: 'free',
|
||||
breakpoints: {
|
||||
'(min-width: 640px)': {
|
||||
slidesPerView: 3,
|
||||
},
|
||||
'(min-width: 768px)': {
|
||||
slidesPerView: 4,
|
||||
},
|
||||
'(min-width: 1024px)': {
|
||||
slidesPerView: 4.5,
|
||||
},'(min-width: 1280px)': {
|
||||
slidesPerView: 5.5,
|
||||
},
|
||||
},
|
||||
}
|
||||
const ProductCarousel = ({ option, data, ...props }: ProductCarouselProps) => {
|
||||
return (
|
||||
<div className={s.productCardWarpper}>
|
||||
<CarouselCommon<ProductCardProps>
|
||||
data={data}
|
||||
Component={ProductCard}
|
||||
{...props}
|
||||
option={{ ...OPTION_DEFAULT, ...option }}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ProductCarousel
|
31
src/components/common/RecipeCard/RecipeCard.module.scss
Normal file
31
src/components/common/RecipeCard/RecipeCard.module.scss
Normal file
@@ -0,0 +1,31 @@
|
||||
.recipeCardWarpper{
|
||||
max-width: 39.2rem;
|
||||
min-height: 34rem;
|
||||
@apply inline-flex flex-col justify-start;
|
||||
.image{
|
||||
width: 100%;
|
||||
max-height: 22rem;
|
||||
border-radius: 2.4rem;
|
||||
&:hover{
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.title{
|
||||
padding: 1.6rem 0.8rem 0.4rem 0.8rem;
|
||||
@apply font-bold;
|
||||
font-size: 2rem;
|
||||
line-height: 2.8rem;
|
||||
letter-spacing: -0.01em;
|
||||
color: var(--text-active);
|
||||
&:hover{
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.description{
|
||||
padding: 0 0.8rem;
|
||||
@apply overflow-hidden overflow-ellipsis ;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3; /* number of lines to show */
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
}
|
23
src/components/common/RecipeCard/RecipeCard.tsx
Normal file
23
src/components/common/RecipeCard/RecipeCard.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import Link from 'next/link'
|
||||
import React from 'react'
|
||||
import { RecipeProps } from 'src/utils/types.utils'
|
||||
import s from './RecipeCard.module.scss'
|
||||
export interface RecipeCardProps extends RecipeProps {}
|
||||
|
||||
const RecipeCard = ({ imageSrc, title, description }: RecipeCardProps) => {
|
||||
return (
|
||||
<div className={s.recipeCardWarpper}>
|
||||
<Link href="#">
|
||||
<div className={s.image}>
|
||||
<img src={imageSrc} alt="image recipe" />
|
||||
</div>
|
||||
</Link>
|
||||
<Link href="#">
|
||||
<div className={s.title}>{title}</div>
|
||||
</Link>
|
||||
<div className={s.description}>{description}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default RecipeCard
|
@@ -0,0 +1,16 @@
|
||||
@import '../../../styles/utilities';
|
||||
.recipeCardWarpper {
|
||||
@apply spacing-horizontal;
|
||||
@screen xl {
|
||||
:global(.customArrow) {
|
||||
@screen lg {
|
||||
&:global(.leftArrow) {
|
||||
left: calc(-6.4rem - 2rem);
|
||||
}
|
||||
&:global(.rightArrow) {
|
||||
right: calc(-6.4rem - 2rem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
46
src/components/common/RecipeCarousel/RecipeCarousel.tsx
Normal file
46
src/components/common/RecipeCarousel/RecipeCarousel.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { TOptionsEvents } from 'keen-slider'
|
||||
import React from 'react'
|
||||
import CarouselCommon, {
|
||||
CarouselCommonProps,
|
||||
} from '../CarouselCommon/CarouselCommon'
|
||||
import RecipeCard, { RecipeCardProps } from '../RecipeCard/RecipeCard'
|
||||
import s from "./RecipeCarousel.module.scss"
|
||||
|
||||
interface RecipeCarouselProps
|
||||
extends Omit<CarouselCommonProps<RecipeCardProps>, 'Component'|"option"> {
|
||||
option?:TOptionsEvents
|
||||
}
|
||||
|
||||
const OPTION_DEFAULT: TOptionsEvents = {
|
||||
slidesPerView: 1.25,
|
||||
mode: 'free',
|
||||
spacing:24,
|
||||
breakpoints: {
|
||||
'(min-width: 640px)': {
|
||||
slidesPerView: 2,
|
||||
},
|
||||
'(min-width: 1024px)': {
|
||||
slidesPerView: 2.5,
|
||||
},
|
||||
'(min-width: 1440px)': {
|
||||
slidesPerView: 3,
|
||||
},
|
||||
'(min-width: 1536px)': {
|
||||
slidesPerView: 3.5,
|
||||
},
|
||||
},
|
||||
}
|
||||
const RecipeCarousel = ({ option, data, ...props }: RecipeCarouselProps) => {
|
||||
return (
|
||||
<div className={s.recipeCardWarpper}>
|
||||
<CarouselCommon<RecipeCardProps>
|
||||
data={data}
|
||||
Component={RecipeCard}
|
||||
{...props}
|
||||
option={{ ...OPTION_DEFAULT, ...option }}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default RecipeCarousel
|
@@ -3,6 +3,10 @@ export { default as Layout } from './Layout/Layout'
|
||||
export { default as CarouselCommon } from './CarouselCommon/CarouselCommon'
|
||||
export { default as QuanittyInput } from './QuanittyInput/QuanittyInput'
|
||||
export { default as LabelCommon } from './LabelCommon/LabelCommon'
|
||||
export { default as ProductCard } from './ProductCard/ProductCard'
|
||||
export { default as ProductCarousel } from './ProductCarousel/ProductCarousel'
|
||||
export { default as FeaturedProductCard } from './FeaturedProductCard/FeaturedProductCard'
|
||||
export { default as RecipeCard } from './RecipeCard/RecipeCard'
|
||||
export { default as Head } from './Head/Head'
|
||||
export { default as ViewAllItem} from './ViewAllItem/ViewAllItem'
|
||||
export { default as ItemWishList} from './ItemWishList/ItemWishList'
|
||||
@@ -23,4 +27,4 @@ export { default as MenuDropdown} from './MenuDropdown/MenuDropdown'
|
||||
export { default as NotiMessage} from './NotiMessage/NotiMessage'
|
||||
export { default as VideoPlayer} from './VideoPlayer/VideoPlayer'
|
||||
export { default as SelectCommon} from './SelectCommon/SelectCommon'
|
||||
export { default as ModalLogin} from './ModalAuthenticate/components/FormLogin/FormLogin'
|
||||
export { default as ModalCommon} from './ModalCommon/ModalCommon'
|
||||
|
Reference in New Issue
Block a user