init: ProducrCard

This commit is contained in:
unknown 2021-08-24 16:25:08 +07:00
commit de5a77fd4e
22 changed files with 362 additions and 62 deletions

View File

@ -74,11 +74,7 @@
"prettier": "^2.3.0",
"typescript": "4.3.4"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"**/*.{js,jsx,ts,tsx}": [
"prettier --write",

View File

@ -1,8 +1,7 @@
import { ButtonCommon, Layout } from 'src/components/common'
import { IconBuy } from 'src/components/icons'
import { ButonType, ButtonSize } from 'src/utils/constanst.utils'
import {CarouselCommon, LabelCommon, QuanittyInput } from 'src/components/common'
import { Layout, ProductCard } from 'src/components/common'
import {CarouselCommon } from 'src/components/common'
import image1 from "../public/assets/images/image5.png"
const dataTest = [{
text:1
},{
@ -21,20 +20,8 @@ export default function Home() {
return (
<>
<CarouselCommon data={dataTest} Component={test} itemKey="test"/>
<QuanittyInput size ="default" min={5} max={10} initValue={3}/>
<QuanittyInput size ="small" min={3} step={10}/>
<LabelCommon type="default" shape="half" >SEEFOOT</LabelCommon>
<LabelCommon type="discount" shape="round">-15%</LabelCommon>
<LabelCommon type="waiting">Waitting</LabelCommon>
<LabelCommon type="delivering" >Delivering</LabelCommon>
<LabelCommon type="delivered">Delivered</LabelCommon>
<ButtonCommon loading={true}>Button default</ButtonCommon>
<ButtonCommon type={ButonType.light} >{ButonType.light} - Button light</ButtonCommon>
<ButtonCommon type={ButonType.light} disabled>{ButonType.light} - Button light</ButtonCommon>
<ButtonCommon type={ButonType.light} loading = {true}>{ButonType.light} - Button light</ButtonCommon>
<ButtonCommon size={ButtonSize.large} loading={true}>{ButtonSize.large} - Button default large</ButtonCommon>
<ButtonCommon icon={<IconBuy/>} disabled>Button with icon disabled</ButtonCommon>
<ButtonCommon icon={<IconBuy/>} type={ButonType.light}>Button with icon</ButtonCommon>
<ProductCard name="tomato" weight = "250g" category ="VEGGIE" price="Rp 27.500" imageSrc={image1.src}/>
</>
)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

@ -5,12 +5,28 @@
display: flex;
justify-content: center;
align-items: center;
padding: 1.6rem 3.2rem;
// padding: 1.6rem 3.2rem;
padding: 0.8rem 1.6rem;
width: 100%;
&:disabled {
filter: brightness(0.9);
cursor: not-allowed;
color: var(--disabled);
}
&: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);
}
&.loading {
&::before {
content: "";
@ -24,20 +40,6 @@
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;
@ -48,8 +50,30 @@
}
}
}
&.ghost {
@apply bg-white;
color: var(--primary);
border: 1px solid var(--primary);
&.loading {
&::before {
border-top-color: var(--primary);
}
}
}
&.onlyIcon {
padding: 0.8rem;
.icon {
margin: 0;
}
}
&.large {
padding: 3.2rem 4.8rem;
&.onlyIcon {
padding: 1.6rem;
}
&.loading {
&::before {
width: 2.4rem;
@ -58,8 +82,21 @@
}
}
&.preserve {
flex-direction: row-reverse;
.icon {
margin: 0 0 0 1.6rem;
}
}
.icon {
margin: 0 1.6rem 0 0;
}
.label,
.icon {
margin-right: 0.8rem;
svg path {
fill: currentColor;
}

View File

@ -1,26 +1,28 @@
import classNames from 'classnames'
import React from 'react'
import { ButonType, ButtonSize } from 'src/utils/constanst.utils'
import React, { memo } from 'react'
import s from './ButtonCommon.module.scss'
interface Props {
children?: any,
type?: ButonType,
size?: ButtonSize,
icon?: any,
children?: React.ReactNode,
type?: 'primary' | 'light' | 'ghost',
size?: 'default' | 'large',
icon?: React.ReactNode,
isIconSuffix?: boolean,
loading?: boolean,
disabled?: boolean,
onClick?: () => void,
}
const ButtonCommon = ({ type = ButonType.primary, size = ButtonSize.default,
icon, loading, disabled, children, onClick }: Props) => {
const ButtonCommon = memo(({ type = 'primary', size = 'default', loading = false, isIconSuffix = false,
icon, disabled, children, onClick }: Props) => {
return (
<button className={classNames({
[s.buttonCommon]: true,
[s[type]]: !!type,
[s[size]]: !!size,
[s.loading]: loading,
[s.preserve]: isIconSuffix,
[s.onlyIcon]: icon && !children,
})}
disabled={disabled}
onClick={onClick}
@ -31,6 +33,6 @@ const ButtonCommon = ({ type = ButonType.primary, size = ButtonSize.default,
<span className={s.label}>{children}</span>
</button>
)
}
})
export default ButtonCommon

View File

@ -0,0 +1,26 @@
import React, { memo } from 'react'
import { IconBuy } from 'src/components/icons'
import ButtonCommon from '../ButtonCommon/ButtonCommon'
interface Props {
type?: 'primary' | 'light' | 'ghost',
size?: 'default' | 'large',
loading?: boolean,
disabled?: boolean,
onClick?: () => void,
}
const ButtonIconBuy = memo(({ type = 'light', size = 'default', loading = false, disabled, onClick }: Props) => {
return (
<ButtonCommon
type={type}
size={size}
loading={loading}
disabled={disabled}
onClick={onClick}
icon={<IconBuy />}
/>
)
})
export default ButtonIconBuy

View File

@ -0,0 +1,43 @@
@import "../../../styles/utilities";
.inputWrap {
@apply flex items-center relative;
.icon {
@apply absolute;
content: "";
left: 1.6rem;
margin-right: 1.6rem;
svg path {
fill: currentColor;
}
}
.icon + .inputCommon {
padding-left: 4.8rem;
}
.inputCommon {
@apply block w-full transition-all duration-200 rounded;
padding: 1.6rem;
border: 1px solid var(--border-line);
&:hover,
&:focus,
&:active {
outline: none;
border: 1px solid var(--primary);
@apply shadow-md;
}
&::placeholder {
@apply text-label;
}
&.custom {
@apply custom-border-radius border-none;
background: var(--gray);
&:hover,
&:focus,
&:active {
border: 1px solid var(--primary);
}
}
}
}

View File

@ -0,0 +1,59 @@
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',
styleType?: 'default' | 'custom',
icon?: React.ReactNode,
onChange?: (value: string | number) => void,
onEnter?: (value: string | number) => void,
}
const InputCommon = forwardRef<Ref, Props>(({ value, placeholder, type, styleType = 'default', icon,
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 (
<div className={s.inputWrap}>
{
icon && <span className={s.icon}>{icon}</span>
}
<input
ref={inputElementRef}
value={value}
type={type}
placeholder={placeholder}
onChange={handleChange}
onKeyDown={handleKeyDown}
className={`${s.inputCommon} ${s[styleType]}`}
/>
</div>
)
})
export default InputCommon

View File

@ -0,0 +1,22 @@
import React from 'react';
import { IconSearch } from 'src/components/icons';
import { LANGUAGE } from 'src/utils/language.utils';
import { Inputcommon } from '..';
interface Props {
onChange?: (value: string | number) => void,
onEnter?: (value: string | number) => void,
}
const InputSearch = ({ onChange, onEnter }: Props) => {
return (
<Inputcommon placeholder={LANGUAGE.PLACE_HOLDER.SEARCH}
styleType='custom'
icon={<IconSearch />}
onChange={onChange}
onEnter={onEnter}
/>
)
}
export default InputSearch

View File

@ -0,0 +1,52 @@
.productCardWarpper{
max-width: 20.8rem;
max-height: 31.8rem;
padding: 1.2rem 1.2rem 0 1.2rem;
@apply border border-solid border-black;
.cardTop{
@apply border border-solid border-yellow-300 relative;
max-height: 13.8rem;
.productImage{
@apply flex justify-center items-center;
img{
@apply inline;
}
.productLabel{
@apply absolute left-0 bottom-0;
}
}
}
.cardMid{
padding: 1.6rem 0;
.cardMid{
.productname{
font-weight: bold;
line-height: 2.4rem;
font-size: 1.6rem;
color: var(--text-active);
}
.productWeight{
font-size: 1.2rem;
line-height: 2rem;
letter-spacing: 0.01em;
color: var(--text-base);
}
}
.cardMidBot{
margin-top: 2.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{
@apply flex justify-between items-center;
.cardButton{
width: 13.6rem;
}
}
}

View File

@ -0,0 +1,59 @@
import React from 'react'
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
buttonText?: string
imageSrc: string
}
const ProductCard = ({
category,
name,
weight,
price,
buttonText = 'Buy Now',
imageSrc,
}: ProductCardProps) => {
return (
<section className={s.productCardWarpper}>
<section className={s.cardTop}>
<div className={s.productImage}>
<img src={imageSrc} alt="image" />
<div className={s.productLabel}>
<LabelCommon shape="half">{category}</LabelCommon>
</div>
</div>
</section>
<section className={s.cardMid}>
<div className={s.cardMidTop}>
<div className={s.productname}>{name} </div>
<div className={s.productWeight}>{weight}</div>
</div>
<div className={s.cardMidBot}>
<div className={s.productPrice}>{price}</div>
<div className={s.wishList}>
<ItemWishList />
</div>
</div>
</section>
<section className={s.cardBot}>
<div className={s.cardIcon}>
<ButtonIconBuy/>
</div>
<div className={s.cardButton}>
<ButtonCommon type="ghost">{buttonText}</ButtonCommon>
</div>
</section>
</section>
)
}
export default ProductCard

View File

@ -3,7 +3,11 @@ 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 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 Logo} from './Logo/Logo'
export { default as Inputcommon} from './InputCommon/InputCommon'
export { default as InputSearch} from './InputSearch/InputSearch'
export { default as ButtonIconBuy} from './ButtonIconBuy/ButtonIconBuy'

View File

@ -0,0 +1,11 @@
import React from 'react'
const IconSearch = () => {
return (
<svg width="21" height="21" viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20.7104 19.2899L17.0004 15.6099C18.4405 13.8143 19.1379 11.5352 18.9492 9.2412C18.7605 6.94721 17.7001 4.81269 15.9859 3.27655C14.2718 1.74041 12.0342 0.919414 9.73332 0.982375C7.43243 1.04534 5.24311 1.98747 3.61553 3.61505C1.98795 5.24263 1.04582 7.43194 0.982863 9.73283C0.919903 12.0337 1.7409 14.2713 3.27704 15.9854C4.81318 17.6996 6.94769 18.76 9.24169 18.9487C11.5357 19.1374 13.8148 18.44 15.6104 16.9999L19.2904 20.6799C19.3834 20.7736 19.494 20.848 19.6158 20.8988C19.7377 20.9496 19.8684 20.9757 20.0004 20.9757C20.1324 20.9757 20.2631 20.9496 20.385 20.8988C20.5068 20.848 20.6174 20.7736 20.7104 20.6799C20.8906 20.4934 20.9914 20.2442 20.9914 19.9849C20.9914 19.7256 20.8906 19.4764 20.7104 19.2899ZM10.0004 16.9999C8.61592 16.9999 7.26255 16.5894 6.1114 15.8202C4.96026 15.051 4.06305 13.9578 3.53324 12.6787C3.00342 11.3996 2.8648 9.99214 3.1349 8.63427C3.40499 7.27641 4.07168 6.02912 5.05065 5.05016C6.02961 4.07119 7.27689 3.4045 8.63476 3.13441C9.99263 2.86431 11.4001 3.00293 12.6792 3.53275C13.9583 4.06256 15.0515 4.95977 15.8207 6.11091C16.5899 7.26206 17.0004 8.61544 17.0004 9.9999C17.0004 11.8564 16.2629 13.6369 14.9501 14.9497C13.6374 16.2624 11.8569 16.9999 10.0004 16.9999Z" fill="#3D3D3D" />
</svg>
)
}
export default IconSearch

View File

@ -1 +1,3 @@
export { default as IconBuy } from './IconBuy'
export { default as IconSearch } from './IconSearch'

View File

@ -32,7 +32,8 @@
--disabled: #cccccc;
--border-line: #ebebeb;
--background: #f8f8f8;
--background: #fff;
--gray: #f8f8f8;
--white: #fbfbfb;
--background-arrow:rgba(20, 20, 20, 0.05);
--font-size: 1.6rem;

View File

@ -89,7 +89,7 @@
}
}
.custom-border-radius {
border-radius: 60% 10% 60% 2%/ 10% 40% 10% 50%;
border-radius: 60% 10% 60% 2%/ 10% 20% 10% 50%;
}
.font-heading {

View File

@ -1,12 +1,3 @@
export enum ButonType {
primary = 'primary',
light = 'light',
export const KEY = {
ENTER: 'Enter',
}
export enum ButtonSize {
default = 'default',
large = 'large',
}

View File

@ -3,4 +3,7 @@ export const LANGUAGE = {
BUY_NOW: 'Buy now',
SHOP_NOW: 'Shop now',
},
PLACE_HOLDER: {
SEARCH: 'Search',
}
}

View File

@ -52,6 +52,11 @@ module.exports = {
'background-arrow':'var(--background-arrow)',
'disabled': 'var(--text-disabled)',
line: 'var(--border-line)',
background: 'var(--background)',
white: 'var(--white)',
gray: 'var(--gray)',
disabled: 'var(--text-disabled)',
// @deprecated (NOT use these variables)
'primary-2': 'var(--primary-2)',