mirror of
https://github.com/vercel/commerce.git
synced 2025-07-29 05:01:22 +00:00
init: ProducrCard
This commit is contained in:
@@ -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'
|
||||
|
Reference in New Issue
Block a user