feat: checkboxCommon

This commit is contained in:
quangnhankie
2021-08-25 17:06:48 +07:00
parent bfec1e8a09
commit 188232e5e3
40 changed files with 2849 additions and 1518 deletions

View File

@@ -1,5 +0,0 @@
/* style demo here */
.buttonCommon {
color: red;
}

View File

@@ -0,0 +1,96 @@
@import "../../../styles/utilities";
.buttonCommon {
@apply custom-border-radius bg-primary transition-all duration-200 text-white font-bold;
display: flex;
justify-content: center;
align-items: center;
padding: 1.6rem 3.2rem;
&:disabled {
filter: brightness(0.9);
cursor: not-allowed;
color: var(--disabled);
}
&.loading {
&::before {
content: "";
border-radius: 50%;
width: 1.6rem;
height: 1.6rem;
border: 3px solid rgba(170, 170, 170, 0.5);
border-top: 3px solid var(--white);
-webkit-animation: spin 2s linear infinite;
animation: spin 2s linear infinite;
margin-right: 0.8rem;
}
}
&:hover {
@apply shadow-md;
&:not(:disabled) {
filter: brightness(1.05);
}
}
&:focus {
outline: none;
filter: brightness(1.05);
}
&:focus-visible {
outline: 2px solid var(--text-active);
}
&.light {
@apply text-base bg-white;
border: 1px solid var(--text-active);
&.loading {
&::before {
border-top-color: var(--primary);
}
}
}
&.ghost {
@apply bg-white;
color: var(--primary);
border: 1px solid var(--primary);
&.loading {
&::before {
border-top-color: var(--primary);
}
}
}
&.large {
padding: 3.2rem 4.8rem;
&.loading {
&::before {
width: 2.4rem;
height: 2.4rem;
}
}
}
&.preserve {
flex-direction: row-reverse;
.icon {
margin: 0 0 0 1.6rem;
}
}
.icon {
margin: 0 1.6rem 0 0;
svg path {
fill: currentColor;
}
}
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

View File

@@ -1,15 +1,37 @@
import { FC, useRef, useEffect } from 'react'
import s from './ButtonCommon.module.css'
import classNames from 'classnames'
import React, { memo } from 'react'
import s from './ButtonCommon.module.scss'
interface Props {
className?: string
children?: any
children?: React.ReactNode,
type?: 'primary' | 'light' | 'ghost',
size?: 'default' | 'large',
icon?: any,
isIconSuffix?: boolean,
loading?: boolean,
disabled?: boolean,
onClick?: () => void,
}
const ButtonCommon: FC<Props> = ({ }) => {
const ButtonCommon = memo(({ type = 'primary', size = 'default',
icon, loading, disabled, isIconSuffix, children, onClick }: Props) => {
return (
<div className={s.buttonCommon}>This is button common</div>
<button className={classNames({
[s.buttonCommon]: true,
[s[type]]: !!type,
[s[size]]: !!size,
[s.loading]: loading,
[s.preserve]: isIconSuffix,
})}
disabled={disabled}
onClick={onClick}
>
{
icon && <span className={s.icon}>{icon}</span>
}
<span className={s.label}>{children}</span>
</button>
)
}
})
export default ButtonCommon

View File

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

View File

@@ -0,0 +1,57 @@
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
}
const CarouselCommon = ({ data, Component,itemKey }: CarouselCommonProps) => {
const [currentSlide, setCurrentSlide] = React.useState(0)
const [sliderRef, slider] = useKeenSlider<HTMLDivElement>({
slidesPerView: 1,
initial: 0,
slideChanged(s) {
setCurrentSlide(s.details().relativeSlide)
},
})
const handleRightArrowClick = () => {
slider.next()
}
const handleLeftArrowClick = () => {
slider.prev()
}
return (
<div className={s.navigation_wrapper}>
<div ref={sliderRef} className="keen-slider">
{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}
/>
</>
)}
</div>
)
}
export default CarouselCommon

View File

@@ -0,0 +1,17 @@
.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 ;
}
}

View File

@@ -0,0 +1,24 @@
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"
interface CustomCarouselArrowProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
side: 'left' | 'right'
isDisabled:Boolean
}
export const CustomCarouselArrow = ({
side,isDisabled,
...props
}: CustomCarouselArrowProps) => {
return (
<button
{...props}
className={classNames(`${s.custom_arrow}`, { [`${s[side]}`]: side,[`${s.isDisabled}`]:isDisabled })}
>
{side==='left'?(<ArrowLeft/>):(<ArrowRight/>)}
</button>
)
}

View File

@@ -0,0 +1,66 @@
@import '../../../styles/utilities';
.checkboxCommonWarper{
@apply flex flex-col;
margin-left:2rem;
.checkboxItem{
display: block;
position: relative;
cursor: pointer;
margin-top:2rem;
border-radius: 0.4rem;
width:50%;
&:hover .checkboxInput ~ .checkMark {
background-color: #ccc;
}
.checkboxInput{
position: absolute;
opacity: 0;
cursor: pointer;
height: 0;
width: 0;
&:checked ~ .checkMark:after {
display: block;
}
}
.checkMark {
border-radius: 0.4rem;
position: absolute;
top: 0;
left: 0;
height: 2rem;
width: 2rem;
background-color:var(--positive);
&:after {
left: 25%;
top: 27%;
width: 1rem;
height: 1rem;
color:white;
content: "";
background-image:url('/assets/svg/checkmark.svg');
background-repeat: no-repeat;
position: absolute;
display: none;
}
}
&~ .checkMark{
background-color: #ccc;
}
&:checked ~ .checkMark {
background-color: #2196F3;
}
&:checked ~ .checkMark:after {
display: block;
}
&:hover .checkboxInput ~ .checkMark {
background-color: #ccc;
}
}
.checkboxText{
margin-left:3rem;
}
}

View File

@@ -0,0 +1,40 @@
import React,{useState,useEffect} from 'react';
import s from './CheckboxCommon.module.scss';
import classNames from 'classnames';
interface CheckboxProps extends Omit<
React.InputHTMLAttributes<HTMLInputElement>,
'onChange'
>{
onChange?: (value: boolean) => void,
defaultChecked?: boolean
}
const CheckboxCommon = ({onChange,defaultChecked = true,...props}: CheckboxProps) =>{
const [value, setValue] = useState<boolean>();
useEffect(()=>{
onChange && onChange(value)
},[value])
const onValueChange = (e: ChangeEvent<HTMLInputElement>)=>{
let value =e.target.checked;
setValue(value);
}
return (
<div className={classNames(s.checkboxCommonWarper)}>
<label className={classNames(s.checkboxItem)}>
<input id="check" defaultChecked={defaultChecked} className={s.checkboxInput} type="checkbox" onChange={onValueChange}/>
<span className={s.checkMark}></span>
</label>
<div className={classNames(s.checkboxText)}>
<label for="check"> Billing address is same as shipping </label>
</div>
</div>
)
}
export default CheckboxCommon;

View File

@@ -1,21 +1,22 @@
@import '../../../styles/utilities';
@import "../../../styles/utilities";
.header {
.btn {
@apply font-bold py-2 px-4 rounded;
// @apply font-bold py-2 px-4 rounded;
}
.btnBlue {
@apply bg-primary hover:bg-warning text-label font-bold py-2 px-4 custom-border-radius;
}
.link {
color: theme("colors.warning");
}
.heading {
@apply text-base;
@apply text-base font-heading;
}
.paragraph {
@apply topline;
}
.logo {
@apply font-logo;
}
}

View File

@@ -10,27 +10,8 @@ const Header: FC<Props> = ({ }: Props) => {
return (
<div className={s.header}>
This is Header
<button className={s.btnBlue}>
Button
</button>
<button className={s.btn}>
Button
</button>
<div className={s.link}>
Test link style
</div>
<h1 className="heading-1">
HEADING 1
</h1>
<p className="spacing-horizontal">
Lorem ipsum, dolor sit amet consectetur adipisicing elit. Sunt delectus, atque aliquid repudiandae debitis dolor facere impedit alias nemo dolores voluptatum? Commodi, delectus. Dignissimos aspernatur nobis, distinctio delectus eligendi nisi illo tempore non nostrum, molestias excepturi dolor culpa fugiat rem perspiciatis. Repellendus numquam quisquam possimus natus vero recusandae, ipsam earum ratione quos ex consectetur cum nostrum modi amet odit fugiat fugit. Facere cum enim dignissimos molestias facilis error dicta exercitationem, delectus voluptates fuga laboriosam esse sunt odio, impedit modi veritatis, nisi nam? Voluptatum voluptas similique aspernatur. Soluta, accusamus! Mollitia praesentium adipisci perspiciatis iusto dolorum sint sit placeat, nesciunt id repellendus.
</p>
<p className="spacing-horizontal-left">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Itaque doloremque quos excepturi laborum maiores laudantium hic iusto natus? Ipsa deleniti quas odit! Labore esse enim ipsam tempora tenetur beatae maxime officiis est, a illo! Soluta suscipit maxime odit eveniet laudantium, iure atque doloribus quaerat. Obcaecati tempore molestiae aliquid amet maiores suscipit, beatae repellat illum ipsam tenetur. Porro officiis omnis quam, iure quia necessitatibus consectetur culpa itaque, in tempora rem ex ad et iusto, hic commodi fuga quibusdam. Dolores exercitationem natus dolor pariatur voluptates non corporis, minus repellat! Quis distinctio esse, animi suscipit ducimus sequi obcaecati facere, perferendis ea omnis soluta.
</p>
<p className={s.paragraph}>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Tempore, natus?
</p>
<h1 className={s.heading}>This is heading</h1>
<div className={s.logo}>This is logo text</div>
</div>
)
}

View File

@@ -0,0 +1,5 @@
@import "../../../styles/utilities";
.inputCommon {
@apply custom-border-radius transition-all duration-200;
}

View File

@@ -0,0 +1,52 @@
import React, { forwardRef, useImperativeHandle, useRef } from 'react';
import { KEY } from 'src/utils/constanst.utils';
import s from './InputCommon.module.scss';
type Ref = {
focus: () => void
} | null;
interface Props {
children?: React.ReactNode,
value?: string | number,
placeholder?: string,
type?: 'text' | 'number',
onChange?: (value: string | number) => void,
onEnter?: (value: string | number) => void,
}
const InputCommon = forwardRef<Ref, Props>(({ value, placeholder, type, onChange, onEnter }: Props, ref) => {
const inputElementRef = useRef<HTMLInputElement>(null);
useImperativeHandle(ref, () => ({
focus: () => {
inputElementRef.current?.focus();
},
}));
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
onChange && onChange(e.target.value)
}
const handleKeyDown = (e: any) => {
if (e.key === KEY.ENTER && onEnter) {
const value = inputElementRef.current?.value || ''
onEnter(value)
}
}
return (
<input
ref={inputElementRef}
value={value}
type={type}
placeholder={placeholder}
onChange={handleChange}
onKeyDown={handleKeyDown}
className={s.inputCommon}
/>
)
})
export default InputCommon

View File

@@ -0,0 +1,10 @@
@import '../../../styles/utilities';
.heartToggle{
cursor: pointer;
width: 4.8rem;
height: 4.8rem;
display: flex;
justify-content: center;
align-items: center;
}

View File

@@ -0,0 +1,19 @@
import { Heart } from '@components/icons'
import React, { useState } from 'react'
import s from './ItemWishList.module.scss'
interface Props {
className?: string
children?: any
}
const ItemWishList = ({}:Props) => {
const [isClick,setClick] = useState(false)
return(
<div className={s.heartToggle} onClick={() => setClick(!isClick)}>
<Heart color={isClick ? "#D1644D" : "#5B9A74"}/>
</div>
)
}
export default ItemWishList

View File

@@ -0,0 +1,43 @@
.labelCommonWarper{
display: inline-flex;
align-items: flex-start;
font-weight: bold;
letter-spacing: 0.01em;
@apply text-white text-right;
&.defaultSize{
min-height: 2rem;
line-height: 2rem;
font-size: 1.2rem;
padding: 0 0.8rem;
}
&.largeSize{
max-height: 2.4rem;
line-height: 2.4rem;
font-size: 1.6rem;
padding: 0 1.8rem;
}
&.defaultType{
@apply bg-positive-dark;
}
&.discountType{
@apply bg-negative;
}
&.waitingType{
@apply bg-warning;
}
&.deliveringType{
@apply bg-info;
}
&.deliveredType{
@apply bg-positive;
}
&.defaultShape{
border-radius: 0.4rem;
}
&.halfShape{
border-radius: 0px 1.4rem 1.4rem 0px;
}
&.roundShape{
border-radius: 1.4rem;
}
}

View File

@@ -0,0 +1,30 @@
import classNames from 'classnames'
import React from 'react'
import s from './LabelCommon.module.scss'
interface LabelCommonProps extends React.HTMLAttributes<HTMLDivElement> {
size?: 'default' | 'large'
shape?: 'half' | 'round' | 'default'
type?: 'default' | 'discount' | 'waiting' | 'delivering' | 'delivered'
color?: string
}
const LabelCommon = ({
size = 'default',
type = 'default',
shape = "default",
children,
}: LabelCommonProps) => {
return (
<div
className={classNames(s.labelCommonWarper, {
[s[`${size}Size`]]: size,
[s[`${type}Type`]]: type,
[s[`${shape}Shape`]]: shape,
})}
>
{children}
</div>
)
}
export default LabelCommon

View File

@@ -0,0 +1,20 @@
@import '../../../styles/utilities';
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@700&display=swap');
.logo{
display: flex;
.eclipse{
width: 3.2rem;
height: 3.2rem;
background-color: theme("colors.primary");
border-radius: 50%;
margin-right: 1.2rem;
}
.conTent{
font-family: 'Poppins', sans-serif;
text-transform: uppercase;
line-height: 3.2rem;
letter-spacing: -0.02rem;
}
}

View File

@@ -0,0 +1,21 @@
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
</div>
</div>
)
}
export default Logo

View File

@@ -0,0 +1,39 @@
@import '../../../styles/utilities';
.quanittyInputWarper{
border-color: theme("textColor.active");
@apply border border-solid inline-flex justify-between items-center custom-border-radius;
.plusIcon, .minusIcon{
&:hover{
cursor: pointer;
}
}
&.default{
max-width: 18.4rem;
min-height: 4rem;
.plusIcon, .minusIcon{
margin: 0.8rem;
width: 2.4rem;
height: 2.4rem;
}
}
&.small{
max-width: 10rem;
min-height: 2.8rem;
.plusIcon, .minusIcon{
margin: 0 0.6rem;
// width: 1rem;
// height: 1rem;
}
}
.quanittyInput{
@apply bg-background outline-none w-1/2 text-center h-full font-bold;
font-size: 20px;
line-height: 28px;
color: theme("textColor.active");
&::-webkit-inner-spin-button, &::-webkit-inner-spin-button{
-webkit-appearance: none;
margin: 0;
}
}
}

View File

@@ -0,0 +1,83 @@
import React, { ChangeEvent, useEffect, useState } from 'react'
import s from './QuanittyInput.module.scss'
import classNames from 'classnames'
import { Minus, Plus } from '@components/icons'
interface QuanittyInputProps
extends Omit<
React.InputHTMLAttributes<HTMLInputElement>,
'onChange' | 'min' | 'max' | 'step' | "type" | "size"
> {
size?: 'default' | 'small'
onChange?: (value: number) => void
initValue?: number
min?: number
max?: number
step?: number
}
const QuanittyInput = ({
size = 'default',
onChange,
initValue = 0,
min,
max,
step = 1,
...props
}: QuanittyInputProps) => {
const [value, setValue] = useState<number>(0)
useEffect(() => {
onChange && onChange(value)
}, [value])
useEffect(() => {
initValue && setValue(initValue)
}, [initValue])
const onPlusClick = () => {
if (max && value + step > max) {
setValue(max)
} else {
setValue(value + step)
}
}
const onMinusClick = () => {
if (min && value - step < min) {
setValue(min)
} else {
setValue(value - step)
}
}
const onValueChange = (e: ChangeEvent<HTMLInputElement>) => {
let value = Number(e.target.value) || 0
if (min && value < min) {
setValue(min)
} else if (max && value > max) {
setValue(max)
} else {
setValue(value)
}
}
return (
<div className={classNames(s.quanittyInputWarper, { [s[size]]: size })}>
<div className={s.minusIcon} onClick={onMinusClick}>
<Minus />
</div>
<input
{...props}
type="number"
value={value}
onChange={onValueChange}
className={s.quanittyInput}
/>
<div className={s.plusIcon} onClick={onPlusClick}>
<Plus />
</div>
</div>
)
}
export default QuanittyInput

View File

@@ -0,0 +1,17 @@
@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-weight: bold;
}
.vecTor{
margin: 0.8rem 0rem 0.8rem 0rem;
}
}

View File

@@ -0,0 +1,26 @@
import Vector from 'src/components/icons/Vector'
import s from './ViewAllItem.module.scss'
import Link from 'next/link'
interface Props {
className?: string
children?: any
link?: string
}
const ViewAllItem = ({ link }: Props) => {
return(
<div className={s.viewAll}>
<Link href={"/all"}>
<a className={s.conTent}>
View All
</a>
</Link>
<div className={s.vecTor}>
<Vector />
</div>
</div>
)
}
export default ViewAllItem

View File

@@ -1,4 +1,11 @@
export { default as ButtonCommon } from './ButtonCommon/ButtonCommon'
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 Head } from './Head/Head'
export { default as ViewAllItem} from './ViewAllItem/ViewAllItem'
export { default as ItemWishList} from './ItemWishList/ItemWishList'
export { default as Logo} from './Logo/Logo'
export { default as Inputcommon} from './InputCommon/InputCommon'
export { default as CheckboxCommon} from './CheckboxCommon/CheckboxCommon'