[250929] feat: DetailPanelSkeleton 추가
This commit is contained in:
@@ -14,6 +14,7 @@ import {
|
||||
} from 'react-redux';
|
||||
|
||||
import Spinner from '@enact/sandstone/Spinner';
|
||||
import DetailPanelSkeleton from './DetailPanelSkeleton/DetailPanelSkeleton';
|
||||
import Spotlight from '@enact/spotlight';
|
||||
import { setContainerLastFocusedElement } from '@enact/spotlight/src/container';
|
||||
|
||||
@@ -795,11 +796,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
}
|
||||
|
||||
if (renderStates.showLoading) {
|
||||
return (
|
||||
<div className={css.loadingContainer}>
|
||||
<Spinner />
|
||||
</div>
|
||||
);
|
||||
return <DetailPanelSkeleton />;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
// src/views/DetailPanel/DetailPanelSkeleton/DetailPanelSkeleton.jsx
|
||||
import React from 'react';
|
||||
|
||||
import css from './DetailPanelSkeleton.module.less';
|
||||
|
||||
export default function DetailPanelSkeleton() {
|
||||
return (
|
||||
<div className={css.detailArea}>
|
||||
{/* 1. Left Margin Section - 60px */}
|
||||
<div className={css.leftMarginSection}></div>
|
||||
|
||||
{/* 2. Info Section - 650px (왼쪽 영역 스켈레톤) */}
|
||||
<div className={css.infoSection}>
|
||||
<div className={css.leftInfoContainer}>
|
||||
<div className={css.leftInfoWrapper}>
|
||||
{/* 제품 태그 스켈레톤 */}
|
||||
<div className={css.skeletonProductTag}></div>
|
||||
|
||||
{/* 제품 정보 스켈레톤 */}
|
||||
<div className={css.skeletonProductInfo}>
|
||||
<div className={css.skeletonTitle}></div>
|
||||
<div className={css.skeletonSubtitle}></div>
|
||||
<div className={css.skeletonPrice}></div>
|
||||
</div>
|
||||
|
||||
{/* QR 코드 스켈레톤 */}
|
||||
<div className={css.skeletonQrCode}></div>
|
||||
|
||||
{/* 버튼들 스켈레톤 */}
|
||||
<div className={css.skeletonButtons}>
|
||||
<div className={css.skeletonShopButton}></div>
|
||||
<div className={css.skeletonFavoriteButton}></div>
|
||||
</div>
|
||||
|
||||
{/* 주문 전화 섹션 스켈레톤 */}
|
||||
<div className={css.skeletonCallToOrder}></div>
|
||||
|
||||
{/* 액션 버튼들 스켈레톤 */}
|
||||
<div className={css.skeletonActionButtons}>
|
||||
<div className={css.skeletonActionButton}></div>
|
||||
<div className={css.skeletonActionButton}></div>
|
||||
<div className={css.skeletonActionButton}></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 3. Content Section - 1210px (오른쪽 영역 스켈레톤) */}
|
||||
<div className={css.contentSection}>
|
||||
<div className={css.rightContentContainer}>
|
||||
<div className={css.scrollerWrapper}>
|
||||
<div className={css.scrollerOverride}>
|
||||
{/* 제품 이미지/비디오 스켈레톤 */}
|
||||
<div className={css.skeletonProductImages}>
|
||||
<div className={css.skeletonMainImage}></div>
|
||||
</div>
|
||||
|
||||
{/* 제품 설명 스켈레톤 */}
|
||||
<div className={css.skeletonDescription}>
|
||||
<div className={css.skeletonDescTitle}></div>
|
||||
<div className={css.skeletonDescLine}></div>
|
||||
<div className={css.skeletonDescLine}></div>
|
||||
<div className={css.skeletonDescLine}></div>
|
||||
<div className={css.skeletonDescLineShort}></div>
|
||||
</div>
|
||||
|
||||
{/* 리뷰 섹션 스켈레톤 */}
|
||||
<div className={css.skeletonReviews}>
|
||||
<div className={css.skeletonReviewTitle}></div>
|
||||
<div className={css.skeletonReviewItem}>
|
||||
<div className={css.skeletonReviewAvatar}></div>
|
||||
<div className={css.skeletonReviewContent}>
|
||||
<div className={css.skeletonReviewHeader}></div>
|
||||
<div className={css.skeletonReviewText}></div>
|
||||
<div className={css.skeletonReviewTextShort}></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={css.skeletonReviewItem}>
|
||||
<div className={css.skeletonReviewAvatar}></div>
|
||||
<div className={css.skeletonReviewContent}>
|
||||
<div className={css.skeletonReviewHeader}></div>
|
||||
<div className={css.skeletonReviewText}></div>
|
||||
<div className={css.skeletonReviewTextShort}></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 추천 상품 스켈레톤 */}
|
||||
<div className={css.skeletonYouMayLike}>
|
||||
<div className={css.skeletonYouMayLikeTitle}></div>
|
||||
<div className={css.skeletonProductGrid}>
|
||||
<div className={css.skeletonProductCard}>
|
||||
<div className={css.skeletonProductImage}></div>
|
||||
<div className={css.skeletonProductName}></div>
|
||||
<div className={css.skeletonProductPrice}></div>
|
||||
</div>
|
||||
<div className={css.skeletonProductCard}>
|
||||
<div className={css.skeletonProductImage}></div>
|
||||
<div className={css.skeletonProductName}></div>
|
||||
<div className={css.skeletonProductPrice}></div>
|
||||
</div>
|
||||
<div className={css.skeletonProductCard}>
|
||||
<div className={css.skeletonProductImage}></div>
|
||||
<div className={css.skeletonProductName}></div>
|
||||
<div className={css.skeletonProductPrice}></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,396 @@
|
||||
@import "../../../style/CommonStyle.module.less";
|
||||
@import "../../../style/utils.module.less";
|
||||
|
||||
// Shimmer 애니메이션 정의
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
background-position: -200px 0;
|
||||
}
|
||||
100% {
|
||||
background-position: calc(200px + 100%) 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 기본 스켈레톤 스타일
|
||||
.skeletonBase {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(100, 100, 100, 0.3) 25%,
|
||||
rgba(140, 140, 140, 0.4) 50%,
|
||||
rgba(100, 100, 100, 0.3) 75%
|
||||
);
|
||||
background-size: 200px 100%;
|
||||
animation: shimmer 1.5s infinite;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
// 1920px 화면 기준 전체 구조 (ProductAllSection과 동일)
|
||||
.detailArea {
|
||||
width: 1920px;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
position: relative;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
// 1. Left Margin Section - 60px
|
||||
.leftMarginSection {
|
||||
width: 60px;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
// 2. Info Section - 650px (왼쪽 영역)
|
||||
.infoSection {
|
||||
width: 650px;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
// 왼쪽 영역 컨테이너
|
||||
.leftInfoContainer {
|
||||
width: 635px;
|
||||
margin-right: 10px;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
// 왼쪽 영역 내부 래퍼
|
||||
.leftInfoWrapper {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
|
||||
> * {
|
||||
margin-bottom: 20px;
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Content Section - 1210px (오른쪽 영역)
|
||||
.contentSection {
|
||||
width: 1210px;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
// 오른쪽 영역 컨테이너
|
||||
.rightContentContainer {
|
||||
width: 1210px;
|
||||
height: @globalHeight - 136px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
// 스크롤러 래퍼
|
||||
.scrollerWrapper {
|
||||
width: 1210px;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: visible;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
// 스크롤러 오버라이드
|
||||
.scrollerOverride {
|
||||
width: 1210px;
|
||||
height: 100%;
|
||||
padding: 0 15px 0 30px;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
> * {
|
||||
margin-bottom: 40px;
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 왼쪽 영역 스켈레톤 요소들 =====
|
||||
|
||||
// 제품 태그 스켈레톤
|
||||
.skeletonProductTag {
|
||||
.skeletonBase();
|
||||
width: 120px;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
// 제품 정보 스켈레톤
|
||||
.skeletonProductInfo {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.skeletonTitle {
|
||||
.skeletonBase();
|
||||
width: 80%;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.skeletonSubtitle {
|
||||
.skeletonBase();
|
||||
width: 60%;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.skeletonPrice {
|
||||
.skeletonBase();
|
||||
width: 200px;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
// QR 코드 스켈레톤
|
||||
.skeletonQrCode {
|
||||
.skeletonBase();
|
||||
width: 240px;
|
||||
height: 180px;
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
// 버튼들 스켈레톤
|
||||
.skeletonButtons {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.skeletonShopButton {
|
||||
.skeletonBase();
|
||||
flex: 1;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.skeletonFavoriteButton {
|
||||
.skeletonBase();
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
// 주문 전화 섹션 스켈레톤
|
||||
.skeletonCallToOrder {
|
||||
.skeletonBase();
|
||||
width: 100%;
|
||||
height: 74px;
|
||||
}
|
||||
|
||||
// 액션 버튼들 스켈레톤
|
||||
.skeletonActionButtons {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.skeletonActionButton {
|
||||
.skeletonBase();
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
// ===== 오른쪽 영역 스켈레톤 요소들 =====
|
||||
|
||||
// 제품 이미지 스켈레톤
|
||||
.skeletonProductImages {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.skeletonMainImage {
|
||||
.skeletonBase();
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
// 제품 설명 스켈레톤
|
||||
.skeletonDescription {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.skeletonDescTitle {
|
||||
.skeletonBase();
|
||||
width: 300px;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
.skeletonDescLine {
|
||||
.skeletonBase();
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.skeletonDescLineShort {
|
||||
.skeletonBase();
|
||||
width: 60%;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
// 리뷰 섹션 스켈레톤
|
||||
.skeletonReviews {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.skeletonReviewTitle {
|
||||
.skeletonBase();
|
||||
width: 250px;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
.skeletonReviewItem {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.skeletonReviewAvatar {
|
||||
.skeletonBase();
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.skeletonReviewContent {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.skeletonReviewHeader {
|
||||
.skeletonBase();
|
||||
width: 200px;
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.skeletonReviewText {
|
||||
.skeletonBase();
|
||||
width: 100%;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.skeletonReviewTextShort {
|
||||
.skeletonBase();
|
||||
width: 70%;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
// 추천 상품 스켈레톤
|
||||
.skeletonYouMayLike {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 25px;
|
||||
}
|
||||
|
||||
.skeletonYouMayLikeTitle {
|
||||
.skeletonBase();
|
||||
width: 300px;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
.skeletonProductGrid {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.skeletonProductCard {
|
||||
width: 300px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.skeletonProductImage {
|
||||
.skeletonBase();
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.skeletonProductName {
|
||||
.skeletonBase();
|
||||
width: 80%;
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.skeletonProductPrice {
|
||||
.skeletonBase();
|
||||
width: 60%;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
// 반응형 스켈레톤 요소들 (다양한 크기 제공)
|
||||
.skeletonSmall {
|
||||
.skeletonBase();
|
||||
width: 80px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.skeletonMedium {
|
||||
.skeletonBase();
|
||||
width: 150px;
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.skeletonLarge {
|
||||
.skeletonBase();
|
||||
width: 300px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.skeletonExtraLarge {
|
||||
.skeletonBase();
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
}
|
||||
Reference in New Issue
Block a user