[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:
2025-10-17 20:51:54 +09:00
parent 394faadbf3
commit 2b0d8d8b26
5 changed files with 142 additions and 283 deletions

View File

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

View File

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

View File

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

View File

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

View File

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