[components] TItemCard, type 및 props 추가

Detail Notes :

1. vertical(thumbnail) type, horisontal type 추가
2. soldoutFlag props 추가 (“Y” 일경우 rendering)
3. rank 및 isBestSeller props 추가
4. onCardClick() return productId, props 추가
This commit is contained in:
younghoon100.park
2024-01-29 16:38:06 +09:00
parent e4e70cab6b
commit 34a5122a58
2 changed files with 229 additions and 105 deletions

View File

@@ -1,68 +1,96 @@
import React from "react"; import React, { memo, useCallback } from "react";
import classNames from "classnames";
import Spottable from "@enact/spotlight/Spottable"; import Spottable from "@enact/spotlight/Spottable";
import { $L } from "../../utils/helperMethods";
import css from "./TItemCard.module.less"; import css from "./TItemCard.module.less";
const SpottableItemList = Spottable("li"); const SpottableComponent = Spottable("li");
export default function TItemCard({ const TYPE_VERTICAL = "vertical";
const TYPE_HORIZONTAL = "horizontal";
const SOLD_OUT_STRING = "SOLD OUT";
const TOP_STRING = "TOP";
export default memo(function ProductCard({
children, children,
imageSource,
imageAlt, imageAlt,
productName, imageSource,
isBestSeller = false,
onCardClick,
priceInfo, priceInfo,
rankOrd, productId,
productName,
rank,
soldoutFlag,
type = TYPE_HORIZONTAL,
...rest ...rest
}) { }) {
const parsePriceInfo = (priceInfo) => { const handleClick = useCallback(
const priceParts = priceInfo (productId) => {
.split("|") onCardClick && onCardClick(productId);
.filter((part) => part !== "N") },
.map((item) => item.trim()); [onCardClick, productId]
);
let originalPrice, discountedPrice, discountRate; const parsePriceInfo = useCallback(
(priceInfo) => {
const priceParts = priceInfo
.split("|")
.filter((part) => part !== "N")
.map((item) => item.trim());
if (priceParts.length === 4) { let originalPrice, discountedPrice, discountRate;
[originalPrice, discountedPrice, , discountRate] = priceParts;
} else if (priceParts.length === 2) {
[originalPrice, discountedPrice] = priceParts;
discountRate = null;
} else {
originalPrice = null;
discountedPrice = null;
discountRate = null;
}
return { originalPrice, discountedPrice, discountRate }; if (priceParts.length === 4) {
}; [originalPrice, discountedPrice, , discountRate] = priceParts;
} else if (priceParts.length === 2) {
[originalPrice, discountedPrice] = priceParts;
discountRate = null;
} else {
originalPrice = null;
discountedPrice = null;
discountRate = null;
}
return { originalPrice, discountedPrice, discountRate };
},
[priceInfo]
);
const { originalPrice, discountedPrice, discountRate } = const { originalPrice, discountedPrice, discountRate } =
parsePriceInfo(priceInfo); parsePriceInfo(priceInfo);
return ( return (
<SpottableItemList {...rest} className={css.container}> <SpottableComponent
{rankOrd && ( {...rest}
<div className={css.rankOrd}> className={classNames(
<span>TOP</span> type === TYPE_HORIZONTAL && css.horizontal,
<strong>{rankOrd}</strong> type === TYPE_VERTICAL && css.vertical
</div>
)} )}
<div className={css.imageContainer}> onClick={() => handleClick(productId)}
>
<div>
<img src={imageSource} alt={imageAlt} /> <img src={imageSource} alt={imageAlt} />
{discountRate && ( {discountRate && <span>{discountRate}</span>}
<div className={css.discountBanner}>{discountRate}</div> {soldoutFlag && soldoutFlag === "N" && <div>{$L(SOLD_OUT_STRING)}</div>}
)}
</div> </div>
<div className={css.descContainer}> <div>
<h1 className={css.productName}>{productName}</h1> <h3>{productName}</h3>
<p className={css.priceInfo}> <p>
{discountedPrice} {discountRate ? discountedPrice : originalPrice}
{originalPrice !== discountedPrice && ( {discountRate && <span>{discountRate}</span>}
<span className={css.originalPrice}>{originalPrice}</span>
)}
</p> </p>
</div> </div>
</SpottableItemList> {isBestSeller && rank && (
<div>
{$L(TOP_STRING)}
<span>{rank}</span>
</div>
)}
</SpottableComponent>
); );
} });

View File

@@ -1,90 +1,186 @@
@import "../../style/CommonStyle.module.less"; @import "../../style/CommonStyle.module.less";
@import "../../style/utils.module.less"; @import "../../style/utils.module.less";
.container { /* horizontal type */
.size(@w: 324px, @h: 438px); .horizontal {
/* normal */
display: flex;
gap: 20px;
.size(@w: 600px, @h: 236px);
padding: 18px; padding: 18px;
border-radius: 12px; border-radius: 12px;
border: 1px solid @COLOR_GRAY02; border: solid 1px @COLOR_GRAY02;
background-color: @COLOR_WHITE; background-color: @COLOR_WHITE;
position: relative;
&:focus-within { // left contents (image contetns)
border: 4px solid @PRIMARY_COLOR_RED; > div:nth-child(1) {
padding: 15px;
box-shadow: 0 0 50px 0 rgba(0, 0, 0, 0.5);
}
.rankOrd {
.size(@w:78px, @h:102px);
.position(@position: absolute, @top: 0, @left: 18px);
.flex(@justifyCenter: space-between, @direction: column);
padding: 12px 12px 18px;
background: @COLOR_GRAY07;
z-index: 2;
border-radius: 0 0 50px 50px;
font-weight: bold;
color: @COLOR_WHITE;
> span {
.font (@fontFamily:Roboto, @fontSize:24px);
}
> strong {
.font (@fontFamily:Arial, @fontSize:42px);
}
}
&:focus-within {
.rankOrd {
top: -3px;
left: 15px;
background: @PRIMARY_COLOR_RED;
}
}
.imageContainer {
.size(@w: 288px, @h: 288px);
overflow: hidden;
position: relative; position: relative;
color: @COLOR_WHITE;
img { img {
.size(@w: 100%, @h: 100%); .size(@w: 200px, @h: 200px);
object-fit: cover; object-fit: cover;
object-position: center; border: solid 1px #f0f0f0;
} }
.discountBanner { // discount rate
span {
.position(@position: absolute, @right: 12px, @bottom: 12px);
.size(@w: 60px, @h: 60px); .size(@w: 60px, @h: 60px);
.flex(); border-radius: 60px;
.position(@position: absolute, @bottom: 12px, @right: 12px);
background-color: @PRIMARY_COLOR_RED; background-color: @PRIMARY_COLOR_RED;
color: @COLOR_WHITE; .font(@fontFamily: "ArialBold", @fontSize:26px);
border-radius: 50%; text-align: center;
padding: 5px 10px; line-height: 60px;
font-size: 26px; }
// sold out
div {
.position(@position: absolute, @top: 0, @right: 0);
.flex();
.size(@w: 200px, @h: 200px);
background-color: rgba(26, 26, 26, 0.6);
.font(@fontFamily: @baseFontBold, @fontSize: 36px);
} }
} }
.descContainer { // right contents
.productName { > div:nth-child(2) {
.font (@fontFamily:@baseFontBold, @fontSize:24px); .flex(@direction: column, @justifyCenter: space-between, @alignCenter: flex-start);
.elip(@clamp:2); flex-grow: 1;
line-height: 1.33; padding: 12px 0;
h3 {
.font(@fontFamily: @baseFontBold, @fontSize: 24px);
color: @COLOR_GRAY06; color: @COLOR_GRAY06;
margin-top: 14px; .elip(@clamp:2);
word-break: break-all;
overflow: hidden;
} }
.priceInfo { p {
.font (@fontFamily:@baseFontBold, @fontSize:30px); .flex(@justifyCenter: flex-start);
margin-top: 9px; gap: 5px;
.font(@fontFamily: @baseFontBold, @fontSize: 30px);
color: @PRIMARY_COLOR_RED; color: @PRIMARY_COLOR_RED;
line-height: 0.93;
.originalPrice { span {
.font (@fontFamily:@baseFont, @fontSize:18px); .font(@fontFamily: @baseFont, @fontSize: 18px);
margin-left: 5px;
text-decoration: line-through;
color: @COLOR_GRAY04; color: @COLOR_GRAY04;
text-decoration: line-through;
} }
} }
} }
/* focused */
&:focus-within {
border: solid 1px @PRIMARY_COLOR_RED;
box-shadow: inset 0 0 0 4px @PRIMARY_COLOR_RED,
0 0 50px 0 rgba(0, 0, 0, 0.5);
}
}
/* vertical type (Thumbnail) */
.vertical {
/* normal */
position: relative;
display: flex;
flex-direction: column;
gap: 12px;
.size(@w: 324px, @h: 438px);
padding: 18px;
border-radius: 12px;
border: solid 1px @COLOR_GRAY02;
background-color: @COLOR_WHITE;
// top contents (image contetns)
> div:nth-child(1) {
position: relative;
.size(@w: 288px, @h: 288px);
color: @COLOR_WHITE;
img {
.size(@w: 288px, @h: 288px);
object-fit: cover;
border: solid 1px #f0f0f0;
}
// discount rate
span {
.position(@position: absolute, @right: 12px, @bottom: 12px);
.size(@w: 60px, @h: 60px);
border-radius: 60px;
background-color: @PRIMARY_COLOR_RED;
.font(@fontFamily: "ArialBold", @fontSize:26px);
text-align: center;
line-height: 60px;
}
// sold out
> div:nth-child(3) {
.position(@position: absolute, @top: 0, @right: 0);
.flex();
.size(@w: 288px, @h: 288px);
background-color: rgba(26, 26, 26, 0.6);
.font(@fontFamily: @baseFontBold, @fontSize: 36px);
}
}
// bottom contents
> div:nth-child(2) {
.flex(@direction: column, @alignCenter: flex-start);
gap: 6px;
flex-grow: 1;
h3 {
.font(@fontFamily: @baseFontBold, @fontSize: 24px);
color: @COLOR_GRAY06;
.elip(@clamp:2);
word-break: break-all;
overflow: hidden;
}
p {
.flex(@justifyCenter: flex-start);
gap: 5px;
.font(@fontFamily: @baseFontBold, @fontSize: 30px);
color: @PRIMARY_COLOR_RED;
span {
.font(@fontFamily: @baseFont, @fontSize: 18px);
color: @COLOR_GRAY04;
text-decoration: line-through;
}
}
}
// best sellet
> div:nth-child(3) {
.position(@position: absolute, @top: -1px, @left: 18px);
.flex(@direction: column, @justifyCenter: space-between);
.size(@w: 79px, @h: 102px);
padding: 12px 12px 18px;
background-color: @COLOR_GRAY07;
.font(@fontFamily: @robotoFontBold, @fontSize: 24px);
color: @COLOR_WHITE;
border-bottom-left-radius: 79px;
border-bottom-right-radius: 79px;
span {
.font(@fontFamily: @arialFontBold, @fontSize: 42px);
font-size: 42px;
}
}
/* focused */
&:focus-within {
border: solid 1px @PRIMARY_COLOR_RED;
box-shadow: inset 0 0 0 4px @PRIMARY_COLOR_RED,
0 0 50px 0 rgba(0, 0, 0, 0.5);
// best seller
div:nth-child(3) {
background-color: @PRIMARY_COLOR_RED;
}
}
} }