mirror of
https://github.com/vercel/commerce.git
synced 2025-07-03 19:51:22 +00:00
init: ProducrCard
This commit is contained in:
commit
de5a77fd4e
@ -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",
|
||||
|
@ -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}/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
BIN
public/assets/images/image5.png
Normal file
BIN
public/assets/images/image5.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.8 KiB |
BIN
public/assets/images/image6.png
Normal file
BIN
public/assets/images/image6.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.3 KiB |
BIN
public/assets/images/image7.png
Normal file
BIN
public/assets/images/image7.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.1 KiB |
BIN
public/assets/images/image8.png
Normal file
BIN
public/assets/images/image8.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.7 KiB |
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
26
src/components/common/ButtonIconBuy/ButtonIconBuy.tsx
Normal file
26
src/components/common/ButtonIconBuy/ButtonIconBuy.tsx
Normal 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
|
43
src/components/common/InputCommon/InputCommon.module.scss
Normal file
43
src/components/common/InputCommon/InputCommon.module.scss
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
59
src/components/common/InputCommon/InputCommon.tsx
Normal file
59
src/components/common/InputCommon/InputCommon.tsx
Normal 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
|
22
src/components/common/InputSearch/InputSearch.tsx
Normal file
22
src/components/common/InputSearch/InputSearch.tsx
Normal 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
|
52
src/components/common/ProductCard/ProductCard.module.scss
Normal file
52
src/components/common/ProductCard/ProductCard.module.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
59
src/components/common/ProductCard/ProductCard.tsx
Normal file
59
src/components/common/ProductCard/ProductCard.tsx
Normal 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
|
@ -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'
|
||||
|
11
src/components/icons/IconSearch.tsx
Normal file
11
src/components/icons/IconSearch.tsx
Normal 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
|
@ -1 +1,3 @@
|
||||
export { default as IconBuy } from './IconBuy'
|
||||
export { default as IconSearch } from './IconSearch'
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -1,12 +1,3 @@
|
||||
|
||||
|
||||
export enum ButonType {
|
||||
primary = 'primary',
|
||||
light = 'light',
|
||||
export const KEY = {
|
||||
ENTER: 'Enter',
|
||||
}
|
||||
|
||||
export enum ButtonSize {
|
||||
default = 'default',
|
||||
large = 'large',
|
||||
}
|
||||
|
||||
|
@ -3,4 +3,7 @@ export const LANGUAGE = {
|
||||
BUY_NOW: 'Buy now',
|
||||
SHOP_NOW: 'Shop now',
|
||||
},
|
||||
PLACE_HOLDER: {
|
||||
SEARCH: 'Search',
|
||||
}
|
||||
}
|
@ -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)',
|
||||
|
Loading…
x
Reference in New Issue
Block a user