fix: [공통] HeaderFavoritesBar 즐겨찾기 변경 시 깜빡임 수정

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
유병철
2026-03-23 12:29:51 +09:00
parent 4beef110df
commit 5688fd9d9f

View File

@@ -20,10 +20,7 @@ import { useFavoritesStore } from '@/stores/favoritesStore';
import { iconMap } from '@/lib/utils/menuTransform';
import type { FavoriteItem } from '@/stores/favoritesStore';
// "시스템 대시보드" 기준 텍스트 폭 (7글자 ≈ 80px)
const TEXT_DEFAULT_MAX = 80;
const TEXT_EXPANDED_MAX = 200;
const TEXT_SHRUNK_MAX = 28;
const OVERFLOW_BTN_WIDTH = 56;
const GAP = 6;
@@ -94,11 +91,12 @@ export default function HeaderFavoritesBar({ isMobile }: HeaderFavoritesBarProps
return () => window.removeEventListener('resize', check);
}, []);
// 즐겨찾기 변경 시 측정 리셋
// 즐겨찾기 변경 시 측정 리셋 (visibleCount는 유지 → 깜빡임 방지)
useEffect(() => {
measuredRef.current = false;
chipWidthsRef.current = [];
setVisibleCount(favorites.length);
// 삭제 시에만 count 보정 (현재 count가 전체보다 크면 맞춤)
setVisibleCount((prev) => Math.min(prev, favorites.length));
}, [favorites.length]);
// 모바일/태블릿 ↔ 데스크탑 전환 시 측정 리셋
@@ -106,22 +104,31 @@ export default function HeaderFavoritesBar({ isMobile }: HeaderFavoritesBarProps
if (!isMobile && !isTablet) {
measuredRef.current = false;
chipWidthsRef.current = [];
setVisibleCount(favorites.length);
setVisibleCount((prev) => Math.min(prev, favorites.length));
}
}, [isMobile, isTablet, favorites.length]);
// 데스크탑 동적 오버플로: 전체 chip 폭 측정 → 저장 → resize 시 재계산
// 데스크탑 동적 오버플로: chip 폭 측정 → resize 시 재계산
useEffect(() => {
if (isMobile || isTablet) return;
const container = containerRef.current;
if (!container) return;
const calculate = () => {
// 최초: 전체 chip 렌더 상태에서 폭 저장
// 미측정 칩이 있으면 렌더된 칩에서 폭 수집
if (!measuredRef.current) {
const chips = container.querySelectorAll<HTMLElement>('[data-chip]');
if (chips.length === favorites.length && chips.length > 0) {
chipWidthsRef.current = Array.from(chips).map((c) => c.offsetWidth);
if (chips.length > 0) {
// 렌더된 칩들의 실제 폭 저장
const measured = Array.from(chips).map((c) => c.offsetWidth);
// 아직 렌더 안 된 칩(overflow)은 평균 폭으로 추정
if (measured.length < favorites.length) {
const avgWidth = Math.round(measured.reduce((a, b) => a + b, 0) / measured.length);
while (measured.length < favorites.length) {
measured.push(avgWidth);
}
}
chipWidthsRef.current = measured;
measuredRef.current = true;
} else {
return;
@@ -207,14 +214,7 @@ export default function HeaderFavoritesBar({ isMobile }: HeaderFavoritesBarProps
<>
{visibleItems.map((item) => {
const Icon = getIcon(item.iconName);
const isHovered = hoveredId === item.id;
const isOtherHovered = hoveredId !== null && !isHovered;
const textMaxWidth = isHovered
? TEXT_EXPANDED_MAX
: isOtherHovered
? TEXT_SHRUNK_MAX
: TEXT_DEFAULT_MAX;
const isOtherHovered = hoveredId !== null && hoveredId !== item.id;
return (
<Tooltip key={item.id}>
@@ -225,21 +225,16 @@ export default function HeaderFavoritesBar({ isMobile }: HeaderFavoritesBarProps
size="sm"
onClick={() => handleClick(item)}
onMouseEnter={() => setHoveredId(item.id)}
className={`rounded-full text-white h-8 flex items-center overflow-hidden ${
isOtherHovered ? 'px-2 gap-1 bg-blue-400/70' : 'px-3 gap-1.5 bg-blue-600 hover:bg-blue-700'
className={`rounded-full h-8 flex items-center overflow-hidden px-3 gap-1.5 transition-colors ${
isOtherHovered
? 'bg-blue-500/30 text-blue-200'
: 'bg-blue-600 hover:bg-blue-700 text-white'
}`}
style={{
transition: 'all 500ms cubic-bezier(0.25, 0.8, 0.25, 1)',
}}
>
{Icon && <Icon className="h-3.5 w-3.5 shrink-0" />}
<span
className="text-xs whitespace-nowrap overflow-hidden text-ellipsis"
style={{
maxWidth: textMaxWidth,
transition: 'max-width 500ms cubic-bezier(0.25, 0.8, 0.25, 1), opacity 400ms ease',
opacity: isOtherHovered ? 0.7 : 1,
}}
style={{ maxWidth: TEXT_DEFAULT_MAX }}
>
{item.label}
</span>