[장바구니] 스타일 작업#1

- 데이터없을때 디자인 작업 및 베스트 상품 노출 (스크롤 부분 처리 해야함)
 - 장바구니 목업 디자인 작업
This commit is contained in:
junghoon86.park
2025-09-24 18:40:17 +09:00
parent cd7438248e
commit 33ea465bcb
15 changed files with 668 additions and 110 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

@@ -43,6 +43,17 @@
max-width: 650px;
letter-spacing: -0.75px;
}
&.cartEa {
min-width: 90px;
max-width: 90px;
}
&.cartTrash {
min-width: 60px;
max-width: 60px;
}
&.extra {
min-width: 350px;
max-width: 900px;
@@ -145,7 +156,9 @@
border-radius: 10px;
box-sizing: border-box;
.flex();
box-shadow: 0 5px 5px #003, 0 6px 7px #0000001a;
box-shadow:
0 5px 5px #003,
0 6px 7px #0000001a;
line-height: normal;
&:focus {
@@ -165,7 +178,9 @@
border-radius: 10px;
box-sizing: border-box;
.flex();
box-shadow: 0 5px 5px #003, 0 6px 7px #0000001a;
box-shadow:
0 5px 5px #003,
0 6px 7px #0000001a;
line-height: normal;
&:focus {

View File

@@ -1,13 +1,17 @@
import React, { useCallback, useMemo } from "react";
import React, {
useCallback,
useMemo,
} from 'react';
import classNames from "classnames";
import classNames from 'classnames';
import { Marquee } from "@enact/sandstone/Marquee";
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
import Spottable from "@enact/spotlight/Spottable";
import { Marquee } from '@enact/sandstone/Marquee';
import SpotlightContainerDecorator
from '@enact/spotlight/SpotlightContainerDecorator';
import Spottable from '@enact/spotlight/Spottable';
import { $L } from "../../utils/helperMethods";
import css from "./THeader.module.less";
import { $L } from '../../utils/helperMethods';
import css from './THeader.module.less';
const Container = SpotlightContainerDecorator(
{ enterTo: "last-focused" },
@@ -25,6 +29,7 @@ export default function THeader({
onClick,
ariaLabel,
children,
kind,
...rest
}) {
const convertedTitle = useMemo(() => {
@@ -68,9 +73,10 @@ export default function THeader({
role="button"
/>
)}
<Marquee
marqueeOn="render"
className={css.title}
className={kind === "cartPanel" ? css.cartTitle : css.title}
marqueeDisabled={marqueeDisabled}
aria-label={ariaLabel}
>
@@ -78,6 +84,7 @@ export default function THeader({
<span dangerouslySetInnerHTML={{ __html: convertedTitle }} />
)}
</Marquee>
{children}
</Container>
);

View File

@@ -19,6 +19,14 @@
letter-spacing: 1px;
text-transform: uppercase;
}
.cartTitle {
width: 1788px;
font-size: 42px;
padding-left: 12px;
font-weight: 600;
letter-spacing: 1px;
text-transform: uppercase;
}
}
.button {

View File

@@ -0,0 +1,29 @@
import React from 'react';
import SpotlightContainerDecorator
from '@enact/spotlight/SpotlightContainerDecorator';
import emptyImg from '../../../assets/images/img-cart-empty@3x.png';
import CustomImage from '../../components/CustomImage/CustomImage';
import BestSeller from '../HomePanel/BestSeller/BestSeller';
import css from './CartEmpty.module.less';
const Container = SpotlightContainerDecorator("div");
const CartEmpty = () => {
return (
<Container>
<div className={css.emptyBox}>
<CustomImage className={css.emptyImg} src={emptyImg} />
<span className={css.emptyTitle}>YOUR CART IS EMPTY</span>
<span className={css.emptySubTitle}>
Add some items to make cart Full
</span>
</div>
<div className={css.bestSeller}>
<BestSeller />
</div>
</Container>
);
};
export default CartEmpty;

View File

@@ -0,0 +1,38 @@
@import "../../style/CommonStyle.module.less";
@import "../../style/utils.module.less";
.emptyBox {
width: 1320px;
height: 288px;
text-align: center;
display: flex;
flex-wrap: wrap;
justify-content: center;
align-content: center;
padding: 18px 300px;
.emptyImg {
width: 360px;
height: 168px;
}
.emptyTitle {
margin-top: 6px;
width: 377px;
height: 26px;
font-size: 36px;
font-weight: bold;
text-align: center;
color: #a3a3a3;
}
.emptySubTitle {
margin-top: 6px;
width: 350px;
height: 18px;
font-size: 23.5px;
font-weight: normal;
text-align: center;
color: #b5b5b5;
}
}
.bestSeller {
margin-top: 70px;
}

View File

@@ -1,30 +1,67 @@
import React, { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import React, {
useCallback,
useEffect,
} from 'react';
import TPanel from '../../components/TPanel/TPanel';
import {
useDispatch,
useSelector,
} from 'react-redux';
import { popPanel } from '../../actions/panelActions';
import TBody from '../../components/TBody/TBody';
import THeader from '../../components/THeader/THeader';
import { popPanel } from '../../actions/panelActions';
import TPanel from '../../components/TPanel/TPanel';
import TScroller from '../../components/TScroller/TScroller';
import useScrollTo from '../../hooks/useScrollTo';
import CartEmpty from './CartEmpty';
import css from './CartPanel.module.less';
import CartProductBar from './CartProductBar';
import CartSidebar from './CartSidebar';
import css from './CartPanel.module.less';
export default function CartPanel({ spotlightId }) {
export default function CartPanel({ spotlightId, scrollOptions = [] }) {
const cartData = useSelector((state) => state.cart.getMyinfoCartSearch);
const dispatch = useDispatch();
const onBackClick = useCallback(() => {
dispatch(popPanel());
}, [dispatch]);
useEffect(() => {
console.log("###cartData", cartData);
}, [cartData]);
const {
getScrollTo,
getScrollTo: getScrollToBody,
scrollTop: scrollTopBody,
} = useScrollTo();
return (
<TPanel spotlightId={spotlightId} isTabActivated={false}>
<TBody className={css.tbody}>
<THeader className={css.theader} title="CART" onBackButton onClick={onBackClick} />
<THeader
className={css.theader}
kind={"cartPanel"}
title="CART"
onBackButton
onClick={onBackClick}
/>
<div className={css.Wrap}>
<div className={css.leftSection}>
<CartSidebar />
</div>
<div className={css.rightSection}>{/* 오른쪽 상품 영역 */}</div>
<div className={css.rightSection} cbScrollTo={getScrollToBody}>
{/* 오른쪽 상품 영역 */}
{/* <TScroller
className={css.tScroller}
scrollTopBody={scrollTopBody}
{...scrollOptions}
>
<CartProductBar />
</TScroller> */}
<CartEmpty />
</div>
</div>
</TBody>
</TPanel>

View File

@@ -26,4 +26,7 @@
.rightSection {
flex: 1;
background-color: #ffffff;
.tScroller {
height: 100%;
}
}

View File

@@ -0,0 +1,134 @@
import React, {
useCallback,
useEffect,
useMemo,
useState,
} from 'react';
import classNames from 'classnames';
import Spotlight from '@enact/spotlight';
import SpotlightContainerDecorator
from '@enact/spotlight/SpotlightContainerDecorator';
import logoImage from '../../../assets/images/ic-partners-qvc@3x.png';
import defaultImage from '../../../assets/images/img-thumb-empty-144@3x.png';
import CustomImage from '../../components/CustomImage/CustomImage';
import TButton from '../../components/TButton/TButton';
import TCheckBoxSquare from '../../components/TCheckBox/TCheckBoxSquare';
import css from './CartProduct.module.less';
const Container = SpotlightContainerDecorator("div");
const CartProduct = () => {
const randomCount = useMemo(() => Math.floor(Math.random() * 3) + 1, []);
const [pdEa, setPdEa] = useState(1);
const handleDecreseClick = useCallback(() => {
if (pdEa > 1) {
setPdEa(pdEa - 1);
}
}, [pdEa]);
const handleIncreseClick = useCallback(() => {
setPdEa(pdEa + 1);
}, [pdEa]);
useEffect(() => {
if (pdEa === 1) {
Spotlight.focus("pd_ea_increse");
}
}, [pdEa]);
return (
<Container className={css.productBox}>
<div className={css.productCompany}>
{/* 장바구니에 담긴 상품의 회사정보 */}
<div className={css.logo}>
<CustomImage
className={css.img}
src={logoImage}
fallbackSrc={defaultImage}
/>
</div>
<span className={css.company}>
Juvelirochka
<span className={css.productEa}>(1 ITEM)</span>
</span>
</div>
<div className={css.productPrice}>
{/* 그 회사에 장바구니에 담긴 상품의 총합 */}
<div className={css.leftSection}>
Product Total $ 99,999.99 + Option $ 99,999.99 + S&H $ 99,999.9
</div>
<div className={css.rightSection}>
Total
<span className={css.total}>
$<span className={css.totalAcc}>999,999,999</span>
</span>
</div>
</div>
{/* 상품 정보 반복가능 */}
{Array.from({ length: randomCount }, (_, index) => (
<div className={css.product}>
<div className={css.leftBox}>
<div className={css.checkBox}>
<TCheckBoxSquare
className={css.customeCheckbox}
spotlightId="productCheckbox"
/>
<span className={css.productId}>ID : A565145</span>
</div>
<div className={css.productInfo}>
<div className={css.leftSection}>
<CustomImage
className={css.productImage}
src={defaultImage}
fallbackSrc={defaultImage}
/>
</div>
<div className={css.rightSection}>
<div className={css.productNm}>
Farmer Jon`s 25-ct Mini Bags Of Microwave Popcorn in Flavor
Choice Area
</div>
<div className={css.optionNm}>Sliver Metallic / XL</div>
<div className={css.accountBox}>
<span className={css.account}>$38.09</span>
<div className={css.accountInfo}>
<span className={css.originalAcc}>$41.99</span>
<span className={css.optionAcc}>OPTION : $5.50</span>
<span className={css.shippingAcc}>S&H: $5.50</span>
</div>
</div>
<div className={css.eaBox}>
<TButton
className={classNames(
css.minusBox,
pdEa === 1 ? css.dimm : ""
)}
size="cartEa"
onClick={handleDecreseClick}
spotlightId={"pd_ea_decrese"}
spotlightDisabled={pdEa === 1 ? true : false}
/>
<div className={css.ea}>{pdEa}</div>
<TButton
onClick={handleIncreseClick}
className={css.plusBox}
spotlightId={"pd_ea_increse"}
size="cartEa"
/>
</div>
</div>
</div>
</div>
<div className={css.rightBox}>
{/* 휴지통 */}
<TButton className={css.trashImg} size="cartTrash" />
</div>
</div>
))}
</Container>
);
};
export default CartProduct;

View File

@@ -0,0 +1,214 @@
@import "../../style/CommonStyle.module.less";
@import "../../style/utils.module.less";
.productBox {
background: #fff;
width: 100%;
border: 1px solid #dadada;
border-radius: 12px;
box-sizing: border-box;
margin-bottom: 60px;
.productCompany {
height: 92px;
display: flex;
align-items: center;
padding: 20px 30px;
.logo {
width: 42px;
height: 42px;
border: 1px solid #dadada;
border-radius: 50%;
.img {
width: 100%;
}
}
.company {
margin-left: 6px;
font-size: 30px;
font-weight: bold;
color: #222;
.productEa {
margin-left: 6px;
color: #c70850;
}
}
}
.productPrice {
width: 1198px;
height: 81px;
padding: 20px 29px 20px 28px;
background-color: #f2f2f2;
display: flex;
justify-content: space-between;
border-top: 1px solid #dadada;
border-bottom: 1px solid #dadada;
box-sizing: border-box;
.leftSection {
font-size: 23.5px;
color: #555;
font-weight: normal;
}
.rightSection {
font-size: 30px;
color: #222;
font-weight: bold;
.total {
margin-left: 10px;
color: #c70850;
font-weight: bold;
.totalAcc {
margin-left: 10px;
}
}
}
}
.product {
display: flex;
flex-wrap: nowrap;
border-bottom: 1px solid #dadada;
box-sizing: border-box;
height: 389px;
&:last-child {
border-bottom: none;
box-sizing: border-box;
}
.leftBox {
width: 940px;
padding: 30px;
.checkBox {
display: flex;
align-items: center;
margin-bottom: 20px;
.productId {
font-size: 30px;
font-weight: 600;
text-align: left;
color: #767676;
margin-left: 12px;
}
}
.productInfo {
display: flex;
.leftSection {
.productImage {
width: 200px;
height: 200px;
border: 1px solid #dadada;
box-sizing: border-box;
}
}
.rightSection {
margin-left: 20px;
.productNm {
width: 600px;
font-size: 24px;
font-weight: bold;
line-height: 1.33;
color: #333;
.elip(2);
}
.optionNm {
margin-top: 10px;
width: 600px;
height: 30px;
font-size: 24px;
font-weight: normal;
line-height: 1.17;
letter-spacing: normal;
color: #808080;
.elip(1);
}
.accountBox {
display: flex;
align-items: center;
margin-top: 15px;
.account {
font-size: 30px;
font-weight: bold;
line-height: 0.93;
color: #c70850;
}
.accountInfo {
> span {
font-size: 24px;
font-weight: normal;
color: #767676;
border-left: 2px solid #808080;
padding-left: 11px;
padding-right: 9px;
&:first-child {
border-left: none;
padding-left: 6px;
}
}
// 동일 코드로 인한 상위에서 공통 처리.
// .originalAcc {
// }
// .optionAcc {
// }
// .shippingAcc {
// }
}
}
.eaBox {
margin-top: 30px;
display: flex;
.minusBox {
width: 90px;
height: 90px;
background-size: contain;
background-image: url("../../../assets/images/btn/btn-quantity-decrease-nor@3x.png");
&.dimm {
background-color: #fff;
background-image: url("../../../assets/images/btn/btn-quantity-decrease-dim_new@3x.png");
}
&:focus {
background-image: url("../../../assets/images/btn/btn-quantity-decrease-foc@3x.png");
}
}
.ea {
width: 210px;
height: 90px;
display: flex;
justify-content: center;
align-items: center;
font-size: 24px;
color: #808080;
font-weight: normal;
}
.plusBox {
width: 90px;
height: 90px;
background-size: contain;
background-image: url("../../../assets/images/btn/btn-quantity-increase-nor@3x.png");
&.dimm {
background-image: url("../../../assets/images/btn/btn-quantity-increase-dim@3x.png");
}
&:focus {
background-image: url("../../../assets/images/btn/btn-quantity-increase-foc@3x.png");
}
}
}
}
}
}
.rightBox {
width: 260px;
display: flex;
justify-content: center;
align-items: center;
border-left: 1px solid #dadada;
box-sizing: border-box;
.trashImg {
width: 60px;
height: 60px;
background-color: #fff;
background-image: url("../../../assets/images/btn/btn-60-delete-nor.svg");
&:focus {
background-image: url("../../../assets/images/btn/btn-60-delete-foc.svg");
}
}
}
}
}

View File

@@ -0,0 +1,23 @@
import React, { useMemo } from 'react';
import SpotlightContainerDecorator
from '@enact/spotlight/SpotlightContainerDecorator';
import CartProduct from './CartProduct';
import css from './CartProductBar.module.less';
const CartProductBar = ({ scrollOptions = {} }) => {
const Container = SpotlightContainerDecorator("div");
const randomCount = useMemo(() => Math.floor(Math.random() * 3) + 1, []);
return (
<Container className={css.productContainer}>
{Array.from({ length: randomCount }, (_, index) => (
<CartProduct key={index} />
))}
</Container>
);
};
export default CartProductBar;

View File

@@ -0,0 +1,13 @@
@import "../../style/CommonStyle.module.less";
@import "../../style/utils.module.less";
.productContainer {
background-color: #f8f8f8;
width: 1320px;
height: 100%;
padding: 60px;
.tScroller {
width: 100%;
height: 100%;
}
}

View File

@@ -23,7 +23,7 @@ const CartSidebar = () => {
<div className={css.title}>Subtotal</div>
<span className={css.itemCount}>{mockData.itemCount} Items</span>
</div>
<div className={css.borderLine} />
<div className={css.priceList}>
<div className={css.priceItem}>
<span className={css.label}>Subtotal</span>
@@ -31,27 +31,38 @@ const CartSidebar = () => {
</div>
<div className={css.priceItem}>
<span className={css.label}>Option</span>
<span className={css.value}>{formatPrice(mockData.optionTotal)}</span>
<span className={css.value}>
{formatPrice(mockData.optionTotal)}
</span>
</div>
<div className={css.priceItem}>
<span className={css.label}>S&H</span>
<span className={css.value}>{formatPrice(mockData.shippingHandling)}</span>
<span className={css.value}>
{formatPrice(mockData.shippingHandling)}
</span>
</div>
</div>
<div className={css.totalRow}>
<span className={css.totalLabel}>Order Total (Before Tax)</span>
<span className={css.totalValue}>{formatPrice(mockData.orderTotalBeforeTax)}</span>
<span className={css.totalLabel}>
Order Total <br />
<span className={css.totalLabelSub}>(Before Tax)</span>
</span>
<span className={css.totalValue}>
{formatPrice(mockData.orderTotalBeforeTax)}
</span>
</div>
</div>
<div className={css.notesSection}>
<p className={css.note}>
Final costs will be available on your Order Review before you place order
Final costs will be available on your Order Review before you place
order
</p>
<p className={css.note}>
By proceeding, you agree to ShopTime's General Terms of Use and acknowledge the Privacy
Statement
By proceeding, you agree to ShopTime's
<a href="#none">General Terms of Use</a> and acknowledge the
<a href="#none">Privacy Statement</a>
</p>
</div>
@@ -59,7 +70,7 @@ const CartSidebar = () => {
<TButton
className={css.checkoutButton}
spotlightId="cart-checkout-button"
onClick={() => console.log('Checkout clicked')}
onClick={() => console.log("Checkout clicked")}
>
Checkout
</TButton>

View File

@@ -5,26 +5,25 @@
flex-direction: column;
width: 100%;
height: 100%;
padding: 60px;
// padding: 60px;
padding-top: 60px;
color: #ffffff;
}
.summarySection {
margin-bottom: 40px;
}
.header {
width: 480px;
height: 40px;
margin: 0 60px 20px;
padding: 9px 0;
// padding: 9px 0;
// background-color: #000;
}
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
text-transform: uppercase;
.title {
width: 148px;
height: 22px;
margin: 0 218px 0 0;
font-family: LGSmartUI;
font-size: 30px;
font-weight: bold;
@@ -37,14 +36,31 @@
}
.itemCount {
font-size: 14px;
color: #cccccc;
width: 114px;
height: 22px;
font-family: LGSmartUI;
font-size: 30.5px;
font-weight: bold;
font-stretch: normal;
font-style: normal;
line-height: normal;
letter-spacing: normal;
text-align: right;
color: #e50459;
}
}
.borderLine {
width: 480px;
height: 1px;
margin: 20px 60px;
opacity: 0.1;
background-color: #fff;
}
.priceList {
margin-bottom: 20px;
}
margin-bottom: 40px;
padding: 0 60px;
.priceItem {
display: flex;
justify-content: space-between;
@@ -57,63 +73,73 @@
}
.label {
font-size: 14px;
color: #cccccc;
font-size: 24px;
color: #ccc;
}
.value {
font-size: 14px;
color: #ffffff;
font-size: 24px;
color: #ccc;
font-weight: 500;
}
}
.totalRow {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 20px;
border-top: 1px solid #4a5568;
}
padding: 20px 60px;
background-color: rgba(255, 255, 255, 0.1);
.totalLabel {
font-size: 16px;
font-weight: 600;
font-size: 30px;
font-weight: bold;
color: #ffffff;
text-transform: uppercase;
.totalLabelSub {
font-size: 24px;
color: #ccc;
font-weight: normal;
text-transform: none;
}
}
.totalValue {
font-size: 18px;
font-weight: 700;
font-size: 42px;
font-weight: bold;
color: #ffffff;
}
}
.notesSection {
margin-bottom: 40px;
}
margin-top: 306px;
padding: 0 60px;
.note {
font-size: 12px;
color: #999999;
line-height: 1.4;
font-size: 18px;
color: #ccc;
line-height: 1.22;
margin: 0;
margin-bottom: 12px;
> a {
margin-left: 4px;
}
}
.note:last-child {
margin-bottom: 0;
}
.buttonSection {
margin-top: auto;
}
.buttonSection {
margin-top: 32px;
text-align: center;
.checkoutButton {
width: 100%;
height: 50px;
background-color: #007bff;
width: 480px;
height: 78px;
background-color: #c70850;
color: #ffffff;
border: none;
border-radius: 6px;
font-size: 16px;
font-weight: 600;
font-size: 30px;
font-weight: bold;
}
}