fix: [공통] HeaderFavoritesBar 즐겨찾기 변경 시 깜빡임 수정
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user