[250929] feat: DetailPanelSkeleton 추가

This commit is contained in:
2025-09-29 15:38:26 +09:00
parent cee5a2ccb0
commit 40d32339a1
3 changed files with 513 additions and 5 deletions

View File

@@ -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;

View File

@@ -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>
);
}

View File

@@ -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;
}