[251017] feat: Code Optimization - 2
🕐 커밋 시간: 2025. 10. 17. 20:51:51 📊 변경 통계: • 총 파일: 5개 • 추가: +53줄 • 삭제: -71줄 📝 수정된 파일: ~ com.twin.app.shoptime/src/views/UserReview/UserReviewPanel.jsx ~ com.twin.app.shoptime/src/views/UserReview/components/UserReviewItem.jsx ~ com.twin.app.shoptime/src/views/UserReview/components/UserReviewsList.jsx ~ com.twin.app.shoptime/src/views/UserReview/components/UserReviewsScroller.jsx ~ com.twin.app.shoptime/src/views/UserReview/components/VirtualScrollBar.jsx 🔧 함수 변경 내용: 📄 com.twin.app.shoptime/src/views/UserReview/components/UserReviewItem.jsx (javascript): 🔄 Modified: Spottable() 🔧 주요 변경 내용: • UI 컴포넌트 아키텍처 개선 Performance: 코드 최적화로 성능 개선 기대
This commit is contained in:
@@ -1,14 +1,7 @@
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
useDispatch,
|
||||
useSelector,
|
||||
} from 'react-redux';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { popPanel } from '../../actions/panelActions';
|
||||
import TBody from '../../components/TBody/TBody';
|
||||
@@ -24,18 +17,9 @@ import css from './UserReviewPanel.module.less';
|
||||
const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const prdtId = fp.pipe(
|
||||
() => panelInfo,
|
||||
fp.get("prdtId"),
|
||||
fp.defaultTo(null)
|
||||
)();
|
||||
|
||||
const patnrId = fp.pipe(
|
||||
() => panelInfo,
|
||||
fp.get("patnrId"),
|
||||
fp.defaultTo(null)
|
||||
)();
|
||||
const prdtId = fp.pipe(() => panelInfo, fp.get('prdtId'), fp.defaultTo(null))();
|
||||
|
||||
const patnrId = fp.pipe(() => panelInfo, fp.get('patnrId'), fp.defaultTo(null))();
|
||||
|
||||
const {
|
||||
userReviewPanelReviews,
|
||||
@@ -57,37 +41,30 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
||||
|
||||
const productImage = fp.pipe(
|
||||
() => panelInfo,
|
||||
fp.get("productImage"),
|
||||
fp.defaultTo("https://placehold.co/150x150")
|
||||
fp.get('productImage'),
|
||||
fp.defaultTo('https://placehold.co/150x150')
|
||||
)();
|
||||
|
||||
const brandLogo = fp.pipe(
|
||||
() => panelInfo,
|
||||
fp.get("brandLogo"),
|
||||
fp.defaultTo("https://placehold.co/50x50")
|
||||
fp.get('brandLogo'),
|
||||
fp.defaultTo('https://placehold.co/50x50')
|
||||
)();
|
||||
|
||||
const productId = fp.pipe(
|
||||
() => panelInfo,
|
||||
fp.get("prdtId"),
|
||||
fp.defaultTo(null)
|
||||
)();
|
||||
const productId = fp.pipe(() => panelInfo, fp.get('prdtId'), fp.defaultTo(null))();
|
||||
|
||||
const productName = fp.pipe(
|
||||
() => panelInfo,
|
||||
fp.get("productName"),
|
||||
fp.defaultTo("상품명 정보가 없습니다")
|
||||
fp.get('productName'),
|
||||
fp.defaultTo('?<3F>품<EFBFBD>??<3F>보가 ?<3F>습?<3F>다')
|
||||
)();
|
||||
|
||||
const handleNextPage = useCallback(() => {
|
||||
if (userReviewPanelHasNext) {
|
||||
|
||||
setIsPaging(true);
|
||||
goToNextUserReviewPage();
|
||||
setTimeout(() => {
|
||||
const targetElement = document.querySelector(
|
||||
`[data-spotlight-id="user-review-2"]`
|
||||
);
|
||||
const targetElement = document.querySelector(`[data-spotlight-id="user-review-2"]`);
|
||||
if (targetElement && targetElement.focus) {
|
||||
targetElement.focus();
|
||||
setTimeout(() => setIsPaging(false), 200);
|
||||
@@ -98,13 +75,10 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
||||
|
||||
const handlePrevPage = useCallback(() => {
|
||||
if (userReviewPanelHasPrev) {
|
||||
|
||||
setIsPaging(true);
|
||||
goToPrevUserReviewPage();
|
||||
setTimeout(() => {
|
||||
const targetElement = document.querySelector(
|
||||
`[data-spotlight-id="user-review-0"]`
|
||||
);
|
||||
const targetElement = document.querySelector(`[data-spotlight-id="user-review-0"]`);
|
||||
if (targetElement && targetElement.focus) {
|
||||
targetElement.focus();
|
||||
setTimeout(() => setIsPaging(false), 200);
|
||||
@@ -113,12 +87,10 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
||||
}
|
||||
}, [userReviewPanelHasPrev, goToPrevUserReviewPage]);
|
||||
|
||||
|
||||
const reviewCount = stats.totalReviews || 0;
|
||||
const filteredCount = stats.filteredCount || 0;
|
||||
const avgRating = stats.averageRating || 5;
|
||||
|
||||
|
||||
const handleRatingFilter = useCallback(
|
||||
(rating) => {
|
||||
applyRatingFilter(rating);
|
||||
@@ -126,64 +98,35 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
||||
[applyRatingFilter]
|
||||
);
|
||||
|
||||
const handleAllStarsFilter = useCallback(
|
||||
() => handleRatingFilter("all"),
|
||||
[handleRatingFilter]
|
||||
);
|
||||
const handle5StarsFilter = useCallback(
|
||||
() => handleRatingFilter(5),
|
||||
[handleRatingFilter]
|
||||
);
|
||||
const handle4StarsFilter = useCallback(
|
||||
() => handleRatingFilter(4),
|
||||
[handleRatingFilter]
|
||||
);
|
||||
const handle3StarsFilter = useCallback(
|
||||
() => handleRatingFilter(3),
|
||||
[handleRatingFilter]
|
||||
);
|
||||
const handle2StarsFilter = useCallback(
|
||||
() => handleRatingFilter(2),
|
||||
[handleRatingFilter]
|
||||
);
|
||||
const handle1StarsFilter = useCallback(
|
||||
() => handleRatingFilter(1),
|
||||
[handleRatingFilter]
|
||||
);
|
||||
const handleAllStarsFilter = useCallback(() => handleRatingFilter('all'), [handleRatingFilter]);
|
||||
const handle5StarsFilter = useCallback(() => handleRatingFilter(5), [handleRatingFilter]);
|
||||
const handle4StarsFilter = useCallback(() => handleRatingFilter(4), [handleRatingFilter]);
|
||||
const handle3StarsFilter = useCallback(() => handleRatingFilter(3), [handleRatingFilter]);
|
||||
const handle2StarsFilter = useCallback(() => handleRatingFilter(2), [handleRatingFilter]);
|
||||
const handle1StarsFilter = useCallback(() => handleRatingFilter(1), [handleRatingFilter]);
|
||||
|
||||
const handleAromaClick = useCallback(() => console.log("Aroma clicked"), []);
|
||||
const handleVanillaClick = useCallback(
|
||||
() => console.log("Vanilla clicked"),
|
||||
[]
|
||||
);
|
||||
const handleCinnamonClick = useCallback(
|
||||
() => console.log("Cinnamon clicked"),
|
||||
[]
|
||||
);
|
||||
const handleQualityClick = useCallback(
|
||||
() => console.log("Quality clicked"),
|
||||
[]
|
||||
);
|
||||
const handleAromaClick = useCallback(() => console.log('Aroma clicked'), []);
|
||||
const handleVanillaClick = useCallback(() => console.log('Vanilla clicked'), []);
|
||||
const handleCinnamonClick = useCallback(() => console.log('Cinnamon clicked'), []);
|
||||
const handleQualityClick = useCallback(() => console.log('Quality clicked'), []);
|
||||
|
||||
// 감정 필터링 핸들러들 - 별점 필터와 동일한 방식
|
||||
// 감정 ?<3F>터<EFBFBD>??<3F>들?<3F>들 - 별점 ?<3F>터?<3F> ?<3F>일??방식
|
||||
const handleSentimentFilter = useCallback(
|
||||
(sentiment) => {
|
||||
applySentimentFilter(sentiment === "all" ? null : sentiment);
|
||||
applySentimentFilter(sentiment === 'all' ? null : sentiment);
|
||||
},
|
||||
[applySentimentFilter]
|
||||
);
|
||||
|
||||
const handlePositiveClick = useCallback(
|
||||
() => handleSentimentFilter("positive"),
|
||||
() => handleSentimentFilter('positive'),
|
||||
[handleSentimentFilter]
|
||||
);
|
||||
const handleNegativeClick = useCallback(
|
||||
() => handleSentimentFilter("negative"),
|
||||
() => handleSentimentFilter('negative'),
|
||||
[handleSentimentFilter]
|
||||
);
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
clearAllFilters();
|
||||
@@ -209,11 +152,7 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
||||
/>
|
||||
<TBody className={css.tbody} scrollable={false}>
|
||||
<div className={css.infoSection}>
|
||||
<img
|
||||
className={css.infoSection__productImage}
|
||||
src={productImage}
|
||||
alt="Product"
|
||||
/>
|
||||
<img className={css.infoSection__productImage} src={productImage} alt="Product" />
|
||||
<div className={css.infoSection__content}>
|
||||
<div className={css.infoSection__content__topRow}>
|
||||
<img
|
||||
@@ -222,15 +161,11 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
||||
alt="Brand"
|
||||
/>
|
||||
{productId && (
|
||||
<div className={css.infoSection__content__topRow__productId}>
|
||||
ID: {productId}
|
||||
</div>
|
||||
<div className={css.infoSection__content__topRow__productId}>ID: {productId}</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={css.infoSection__content__titleRow}>
|
||||
<div className={css.infoSection__content__titleRow__title}>
|
||||
{productName}
|
||||
</div>
|
||||
<div className={css.infoSection__content__titleRow__title}>{productName}</div>
|
||||
</div>
|
||||
<div className={css.infoSection__content__bottomRow}>
|
||||
<StarRating
|
||||
@@ -247,19 +182,13 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
||||
<div className={css.reviewsSection}>
|
||||
<div className={css.reviewsSection__filters}>
|
||||
<div className={css.reviewsSection__filters__title}>
|
||||
<div className={css.reviewsSection__filters__title__text}>
|
||||
Filter Reviews
|
||||
</div>
|
||||
<div className={css.reviewsSection__filters__title__text}>Filter Reviews</div>
|
||||
</div>
|
||||
|
||||
<div className={css.reviewsSection__filters__container}>
|
||||
<div className={css.reviewsSection__filters__section}>
|
||||
<div className={css.reviewsSection__filters__sectionTitle}>
|
||||
<div
|
||||
className={css.reviewsSection__filters__sectionTitle__text}
|
||||
>
|
||||
Rating
|
||||
</div>
|
||||
<div className={css.reviewsSection__filters__sectionTitle__text}>Rating</div>
|
||||
</div>
|
||||
<div className={css.reviewsSection__filters__group}>
|
||||
<FilterItemButton
|
||||
@@ -268,10 +197,7 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
||||
spotlightId="filter-all-stars"
|
||||
ariaLabel="Filter by all star ratings"
|
||||
dataSpotlightDown="filter-5-stars"
|
||||
isActive={
|
||||
currentFilter.type === "rating" &&
|
||||
currentFilter.value === "all"
|
||||
}
|
||||
isActive={currentFilter.type === 'rating' && currentFilter.value === 'all'}
|
||||
/>
|
||||
<FilterItemButton
|
||||
text={`5 star (${filterCounts?.rating?.[5] || 0})`}
|
||||
@@ -280,10 +206,7 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
||||
ariaLabel="Filter by 5 star ratings"
|
||||
dataSpotlightUp="filter-all-stars"
|
||||
dataSpotlightDown="filter-4-stars"
|
||||
isActive={
|
||||
currentFilter.type === "rating" &&
|
||||
currentFilter.value === 5
|
||||
}
|
||||
isActive={currentFilter.type === 'rating' && currentFilter.value === 5}
|
||||
/>
|
||||
<FilterItemButton
|
||||
text={`4 star (${filterCounts?.rating?.[4] || 0})`}
|
||||
@@ -292,10 +215,7 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
||||
ariaLabel="Filter by 4 star ratings"
|
||||
dataSpotlightUp="filter-5-stars"
|
||||
dataSpotlightDown="filter-3-stars"
|
||||
isActive={
|
||||
currentFilter.type === "rating" &&
|
||||
currentFilter.value === 4
|
||||
}
|
||||
isActive={currentFilter.type === 'rating' && currentFilter.value === 4}
|
||||
/>
|
||||
<FilterItemButton
|
||||
text={`3 star (${filterCounts?.rating?.[3] || 0})`}
|
||||
@@ -304,10 +224,7 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
||||
ariaLabel="Filter by 3 star ratings"
|
||||
dataSpotlightUp="filter-4-stars"
|
||||
dataSpotlightDown="filter-2-stars"
|
||||
isActive={
|
||||
currentFilter.type === "rating" &&
|
||||
currentFilter.value === 3
|
||||
}
|
||||
isActive={currentFilter.type === 'rating' && currentFilter.value === 3}
|
||||
/>
|
||||
<FilterItemButton
|
||||
text={`2 star (${filterCounts?.rating?.[2] || 0})`}
|
||||
@@ -316,10 +233,7 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
||||
ariaLabel="Filter by 2 star ratings"
|
||||
dataSpotlightUp="filter-3-stars"
|
||||
dataSpotlightDown="filter-1-stars"
|
||||
isActive={
|
||||
currentFilter.type === "rating" &&
|
||||
currentFilter.value === 2
|
||||
}
|
||||
isActive={currentFilter.type === 'rating' && currentFilter.value === 2}
|
||||
/>
|
||||
<FilterItemButton
|
||||
text={`1 star (${filterCounts?.rating?.[1] || 0})`}
|
||||
@@ -327,21 +241,14 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
||||
spotlightId="filter-1-stars"
|
||||
ariaLabel="Filter by 1 star ratings"
|
||||
dataSpotlightUp="filter-2-stars"
|
||||
isActive={
|
||||
currentFilter.type === "rating" &&
|
||||
currentFilter.value === 1
|
||||
}
|
||||
isActive={currentFilter.type === 'rating' && currentFilter.value === 1}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={css.reviewsSection__filters__section}>
|
||||
<div className={css.reviewsSection__filters__sectionTitle}>
|
||||
<div
|
||||
className={css.reviewsSection__filters__sectionTitle__text}
|
||||
>
|
||||
Keywords
|
||||
</div>
|
||||
<div className={css.reviewsSection__filters__sectionTitle__text}>Keywords</div>
|
||||
</div>
|
||||
<div className={css.reviewsSection__filters__group}>
|
||||
<FilterItemButton
|
||||
@@ -381,11 +288,7 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
||||
|
||||
<div className={css.reviewsSection__filters__section}>
|
||||
<div className={css.reviewsSection__filters__sectionTitle}>
|
||||
<div
|
||||
className={css.reviewsSection__filters__sectionTitle__text}
|
||||
>
|
||||
Sentiment
|
||||
</div>
|
||||
<div className={css.reviewsSection__filters__sectionTitle__text}>Sentiment</div>
|
||||
</div>
|
||||
<div className={css.reviewsSection__filters__group}>
|
||||
<FilterItemButton
|
||||
@@ -396,8 +299,7 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
||||
dataSpotlightUp="filter-quality"
|
||||
dataSpotlightDown="filter-negative"
|
||||
isActive={
|
||||
currentFilter.type === "sentiment" &&
|
||||
currentFilter.value === "positive"
|
||||
currentFilter.type === 'sentiment' && currentFilter.value === 'positive'
|
||||
}
|
||||
/>
|
||||
<FilterItemButton
|
||||
@@ -407,8 +309,7 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
||||
ariaLabel="Filter by negative sentiment"
|
||||
dataSpotlightUp="filter-positive"
|
||||
isActive={
|
||||
currentFilter.type === "sentiment" &&
|
||||
currentFilter.value === "negative"
|
||||
currentFilter.type === 'sentiment' && currentFilter.value === 'negative'
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import Spottable from '@enact/spotlight/Spottable';
|
||||
import classNames from 'classnames';
|
||||
@@ -23,19 +22,15 @@ const UserReviewItem = ({
|
||||
}
|
||||
}, [onClick, review, index]);
|
||||
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(event) => {
|
||||
|
||||
if (event.key === 'ArrowUp' && index === 0) {
|
||||
if (hasPrev && onPrevPage) {
|
||||
onPrevPage();
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
else if (event.key === 'ArrowDown' && index === 2) {
|
||||
} else if (event.key === 'ArrowDown' && index === 2) {
|
||||
if (hasNext && onNextPage) {
|
||||
onNextPage();
|
||||
}
|
||||
@@ -66,7 +61,6 @@ const UserReviewItem = ({
|
||||
spotlightId={`user-review-${index}`}
|
||||
data-spotlight-id={`user-review-${index}`}
|
||||
>
|
||||
|
||||
{reviewImageList && reviewImageList.length > 0 && (
|
||||
<img
|
||||
className={css.reviewThumbnail}
|
||||
@@ -75,7 +69,6 @@ const UserReviewItem = ({
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
<div className={css.reviewContent}>
|
||||
<div className={css.reviewMeta}>
|
||||
{rvwRtng && (
|
||||
@@ -87,7 +80,6 @@ const UserReviewItem = ({
|
||||
{rvwRgstDtt && <span className={css.reviewDate}>{formatToYYMMDD(rvwRgstDtt)}</span>}
|
||||
</div>
|
||||
|
||||
|
||||
{rvwCtnt && <div className={css.reviewText}>{rvwCtnt}</div>}
|
||||
</div>
|
||||
</SpottableComponent>
|
||||
|
||||
@@ -1,28 +1,24 @@
|
||||
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
|
||||
import UserReviewItem from "./UserReviewItem";
|
||||
import UserReviewsScroller from "./UserReviewsScroller";
|
||||
import VirtualScrollBar from "./VirtualScrollBar";
|
||||
import { $L } from "../../../utils/helperMethods";
|
||||
import css from "./UserReviewsList.module.less";
|
||||
import React, { useCallback } from 'react';
|
||||
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
import UserReviewItem from './UserReviewItem';
|
||||
import UserReviewsScroller from './UserReviewsScroller';
|
||||
import VirtualScrollBar from './VirtualScrollBar';
|
||||
import { $L } from '../../../utils/helperMethods';
|
||||
import css from './UserReviewsList.module.less';
|
||||
|
||||
const Container = SpotlightContainerDecorator(
|
||||
{
|
||||
enterTo: "default-element",
|
||||
enterTo: 'default-element',
|
||||
preserveId: true,
|
||||
leaveFor: {
|
||||
left: "filter-all-stars"
|
||||
left: 'filter-all-stars',
|
||||
},
|
||||
restrict: "none",
|
||||
spotlightDirection: "vertical"
|
||||
restrict: 'none',
|
||||
spotlightDirection: 'vertical',
|
||||
},
|
||||
"div"
|
||||
'div'
|
||||
);
|
||||
|
||||
|
||||
const UserReviewsList = ({
|
||||
prdtId,
|
||||
reviewsData = [],
|
||||
totalReviewCount = 0,
|
||||
filteredReviewCount = 0,
|
||||
@@ -32,15 +28,13 @@ const UserReviewsList = ({
|
||||
hasPrev = false,
|
||||
onNextPage,
|
||||
onPrevPage,
|
||||
isPaging = false
|
||||
isPaging = false,
|
||||
}) => {
|
||||
|
||||
|
||||
const handleReviewClick = useCallback((review, index) => {
|
||||
console.log("[UserReviewsList] Review item clicked:", {
|
||||
console.log('[UserReviewsList] Review item clicked:', {
|
||||
rvwId: review.rvwId,
|
||||
index: index,
|
||||
review: review
|
||||
review: review,
|
||||
});
|
||||
}, []);
|
||||
|
||||
@@ -50,7 +44,8 @@ const UserReviewsList = ({
|
||||
<div className={css.reviewsListHeaderText}>
|
||||
<span className={css.reviewsListHeaderCount}>
|
||||
{currentFilter.value !== 'all' ? filteredReviewCount : totalReviewCount}
|
||||
</span> Customer Reviews
|
||||
</span>{' '}
|
||||
Customer Reviews
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -59,9 +54,9 @@ const UserReviewsList = ({
|
||||
<UserReviewsScroller className={css.reviewsScroller}>
|
||||
<div className={css.showReviewsText}>
|
||||
{$L(
|
||||
currentFilter.value !== 'all' ?
|
||||
`Showing ${reviewsData ? reviewsData.length : 0} out of ${filteredReviewCount} filtered reviews` :
|
||||
`Showing ${reviewsData ? reviewsData.length : 0} out of ${totalReviewCount} reviews`
|
||||
currentFilter.value !== 'all'
|
||||
? `Showing ${reviewsData ? reviewsData.length : 0} out of ${filteredReviewCount} filtered reviews`
|
||||
: `Showing ${reviewsData ? reviewsData.length : 0} out of ${totalReviewCount} reviews`
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,27 +1,17 @@
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
|
||||
import classNames from "classnames";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
import { Job } from "@enact/core/util";
|
||||
import Scroller from "@enact/sandstone/Scroller";
|
||||
|
||||
import css from "./UserReviewsScroller.module.less";
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { Job } from '@enact/core/util';
|
||||
import Scroller from '@enact/sandstone/Scroller';
|
||||
import css from './UserReviewsScroller.module.less';
|
||||
|
||||
export default function UserReviewsScroller({
|
||||
className,
|
||||
children,
|
||||
verticalScrollbar = "visible",
|
||||
verticalScrollbar = 'visible',
|
||||
focusableScrollbar = true,
|
||||
direction = "vertical",
|
||||
horizontalScrollbar = "hidden",
|
||||
direction = 'vertical',
|
||||
horizontalScrollbar = 'hidden',
|
||||
scrollMode,
|
||||
onScrollStart,
|
||||
onScrollStop,
|
||||
@@ -31,9 +21,8 @@ export default function UserReviewsScroller({
|
||||
forceUpdate = false,
|
||||
...rest
|
||||
}) {
|
||||
|
||||
const isScrolling = useRef(false);
|
||||
const scrollPosition = useRef("top");
|
||||
const scrollPosition = useRef('top');
|
||||
|
||||
const scrollToRef = useRef(null);
|
||||
const scrollHorizontalPos = useRef(0);
|
||||
@@ -91,18 +80,18 @@ export default function UserReviewsScroller({
|
||||
|
||||
if (e.reachedEdgeInfo) {
|
||||
if (e.reachedEdgeInfo.top) {
|
||||
scrollPosition.current = "top";
|
||||
scrollPosition.current = 'top';
|
||||
} else if (e.reachedEdgeInfo.bottom) {
|
||||
scrollPosition.current = "bottom";
|
||||
scrollPosition.current = 'bottom';
|
||||
} else if (e.reachedEdgeInfo.left) {
|
||||
scrollPosition.current = "left";
|
||||
scrollPosition.current = 'left';
|
||||
} else if (e.reachedEdgeInfo.right) {
|
||||
scrollPosition.current = "right";
|
||||
scrollPosition.current = 'right';
|
||||
} else {
|
||||
scrollPosition.current = "middle";
|
||||
scrollPosition.current = 'middle';
|
||||
}
|
||||
} else {
|
||||
scrollPosition.current = "middle";
|
||||
scrollPosition.current = 'middle';
|
||||
}
|
||||
|
||||
scrollHorizontalPos.current = e.scrollLeft;
|
||||
@@ -131,12 +120,7 @@ export default function UserReviewsScroller({
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
className ? className : null,
|
||||
css.scrollerContainer
|
||||
)}
|
||||
>
|
||||
<div className={classNames(className ? className : null, css.scrollerContainer)}>
|
||||
<Scroller
|
||||
{...rest}
|
||||
ref={scrollerRef}
|
||||
@@ -144,12 +128,9 @@ export default function UserReviewsScroller({
|
||||
onScrollStart={_onScrollStart}
|
||||
onScrollStop={_onScrollStop}
|
||||
onScroll={_onScroll}
|
||||
scrollMode={scrollMode || "translate"}
|
||||
scrollMode={scrollMode || 'translate'}
|
||||
focusableScrollbar={focusableScrollbar}
|
||||
className={classNames(
|
||||
isMounted && css.tScroller,
|
||||
noScrollByWheel && css.preventScroll
|
||||
)}
|
||||
className={classNames(isMounted && css.tScroller, noScrollByWheel && css.preventScroll)}
|
||||
direction={direction}
|
||||
horizontalScrollbar={horizontalScrollbar}
|
||||
verticalScrollbar={verticalScrollbar}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import React, { useCallback, useMemo, useEffect, useRef } from 'react';
|
||||
import Spottable from '@enact/spotlight/Spottable';
|
||||
import classNames from 'classnames';
|
||||
import css from './VirtualScrollBar.module.less';
|
||||
|
||||
import React, { useCallback, useMemo, useEffect, useRef } from "react";
|
||||
import Spottable from "@enact/spotlight/Spottable";
|
||||
import classNames from "classnames";
|
||||
import css from "./VirtualScrollBar.module.less";
|
||||
|
||||
const SpottableDiv = Spottable("div");
|
||||
|
||||
const SpottableDiv = Spottable('div');
|
||||
|
||||
const VirtualScrollBar = ({
|
||||
currentPage = 0,
|
||||
@@ -15,52 +13,46 @@ const VirtualScrollBar = ({
|
||||
hasNext = false,
|
||||
hasPrev = false,
|
||||
className,
|
||||
spotlightId = "virtual-scrollbar"
|
||||
spotlightId = 'virtual-scrollbar',
|
||||
}) => {
|
||||
const thumbRef = useRef(null);
|
||||
|
||||
const VISIBLE_ITEMS = 3; // 화면에 표시되는 리뷰 개수 (고정)
|
||||
|
||||
// Thumb 높이 계산 - 3개가 보이므로 3/totalPages
|
||||
const VISIBLE_ITEMS = 3;
|
||||
const thumbHeightPercent = useMemo(() => {
|
||||
if (totalPages === 0) return 100;
|
||||
const percent = (VISIBLE_ITEMS / totalPages) * 100;
|
||||
// 최소 3%, 최대 100%
|
||||
return Math.max(3, Math.min(100, percent));
|
||||
}, [totalPages]);
|
||||
|
||||
// Thumb 위치 계산 - currentPage가 곧 현재 첫 번째 리뷰 인덱스
|
||||
const thumbTopPercent = useMemo(() => {
|
||||
if (totalPages === 0) return 0;
|
||||
|
||||
// 현재 위치의 비율
|
||||
const position = (currentPage / totalPages) * 100;
|
||||
|
||||
// Thumb가 track을 벗어나지 않도록 조정
|
||||
const maxPosition = 100 - thumbHeightPercent;
|
||||
return Math.min(position, maxPosition);
|
||||
}, [currentPage, totalPages, thumbHeightPercent]);
|
||||
|
||||
// 키보드 이벤트 핸들러
|
||||
const handleKeyDown = useCallback((event) => {
|
||||
if (event.key === 'ArrowDown') {
|
||||
if (hasNext && onNextPage) {
|
||||
console.log('[VirtualScrollBar] Arrow Down - Next page');
|
||||
onNextPage();
|
||||
const handleKeyDown = useCallback(
|
||||
(event) => {
|
||||
if (event.key === 'ArrowDown') {
|
||||
if (hasNext && onNextPage) {
|
||||
console.log('[VirtualScrollBar] Arrow Down - Next page');
|
||||
onNextPage();
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
} else if (event.key === 'ArrowUp') {
|
||||
if (hasPrev && onPrevPage) {
|
||||
console.log('[VirtualScrollBar] Arrow Up - Previous page');
|
||||
onPrevPage();
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
} else if (event.key === 'ArrowUp') {
|
||||
if (hasPrev && onPrevPage) {
|
||||
console.log('[VirtualScrollBar] Arrow Up - Previous page');
|
||||
onPrevPage();
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
}, [hasNext, hasPrev, onNextPage, onPrevPage]);
|
||||
},
|
||||
[hasNext, hasPrev, onNextPage, onPrevPage]
|
||||
);
|
||||
|
||||
// Thumb 높이와 위치 변경 시 즉시 업데이트
|
||||
useEffect(() => {
|
||||
if (thumbRef.current) {
|
||||
thumbRef.current.style.height = `${thumbHeightPercent}%`;
|
||||
@@ -69,12 +61,11 @@ const VirtualScrollBar = ({
|
||||
totalPages,
|
||||
thumbHeightPercent,
|
||||
thumbTopPercent,
|
||||
currentPage
|
||||
currentPage,
|
||||
});
|
||||
}
|
||||
}, [thumbHeightPercent, thumbTopPercent, totalPages, currentPage]);
|
||||
|
||||
// 스크롤바가 필요 없는 경우
|
||||
if (totalPages <= VISIBLE_ITEMS) {
|
||||
return null;
|
||||
}
|
||||
@@ -94,7 +85,7 @@ const VirtualScrollBar = ({
|
||||
className={css.thumb}
|
||||
style={{
|
||||
height: `${thumbHeightPercent}%`,
|
||||
top: `${thumbTopPercent}%`
|
||||
top: `${thumbTopPercent}%`,
|
||||
}}
|
||||
>
|
||||
<div className={css.thumbInner}>
|
||||
@@ -104,7 +95,6 @@ const VirtualScrollBar = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</SpottableDiv>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user