🔀 merge: Merge branch 'common' of https://github.com/KieIO/grocery-vercel-commerce into m3-lytran

:%s
This commit is contained in:
lytrankieio123
2021-08-27 16:07:27 +07:00
58 changed files with 4894 additions and 3686 deletions

View File

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

View File

@@ -1,5 +0,0 @@
.navigation_wrapper{
@apply relative;
min-height: theme("caroucel.arrow-height") ;
}

View File

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

View File

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

View File

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

View File

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

View 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

View File

@@ -1,3 +1,5 @@
.subtitle {
font-size: var(--font-size);
line-height: var(--line-height);
margin-top: .4rem;
}

View File

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

View File

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

View File

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

View File

@@ -11,5 +11,7 @@
}
&.center {
@apply text-center;
}
}
}

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

View 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

View 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{
}
}
}

View 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

View File

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

View 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

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

View 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

View File

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

View 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

View File

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