[251030] feat: implement microphone button focus management with debouncing

마이크 버튼의 포커스 관리 및 음성 오버레이 로직 개선

🕐 커밋 시간: 2025. 10. 30. $(date +%H:%M:%S)

📊 변경 통계:
  • 총 파일: 1개
  • 추가: +48줄
  • 삭제: -5줄

📝 수정된 파일:
  ~ src/views/SearchPanel/SearchPanel.new.v2.jsx

🔧 주요 변경 내용:
  • isMicFocusable 상태 추가로 마이크 버튼 포커스 가능 여부 관리
  • onFocusMic 콜백 핸들러 구현 (500ms 디바운싱 로직 포함)
  • 음성 오버레이 열기 로직을 재사용 가능한 openVoiceOverlay 함수로 추출
  • SpottableMicButton 컴포넌트의 onFocus 이벤트 핸들러 활성화 (이전에는 주석 처리됨)
  • 언마운트 시 마이크 포커스 타이머 정리 로직 추가
This commit is contained in:
2025-10-30 23:37:53 +09:00
parent da6d10a8c6
commit 8d79a68782

View File

@@ -221,6 +221,9 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
// 🎯 HowAboutThese 포커스 관리 - 검색 입력 영역 포커스 감지용 상태
const [searchInputFocused, setSearchInputFocused] = useState(false);
// 마이크 버튼 포커스 가능 여부 상태
const [isMicFocusable, setIsMicFocusable] = useState(true);
// 🐛 [DEBUG] shopperHouseData 상태 변경 추적 (DEBUG_MODE가 true일 경우에만)
useEffect(() => {
if (DEBUG_MODE) {
@@ -362,6 +365,8 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
setInputFocus(true);
// 🎯 HowAboutThese 포커스 관리 - 검색 입력 영역 포커스 상태 설정
setSearchInputFocused(true);
// 마이크 버튼 포커스 가능하도록 설정
setIsMicFocusable(true);
}, []);
/**
@@ -382,6 +387,34 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
setSearchInputFocused(false);
}, []);
// 마이크 버튼 포커스 핸들러
const micFocusTimerRef = useRef(null);
const onFocusMic = useCallback(() => {
// 이전 타이머 정리
if (micFocusTimerRef.current) {
clearTimeout(micFocusTimerRef.current);
micFocusTimerRef.current = null;
}
// isMicFocusable이 true인 경우에만 음성 오버레이 열기
if (isMicFocusable) {
openVoiceOverlay();
// 500ms 후에 isMicFocusable을 false로 설정
micFocusTimerRef.current = setTimeout(() => {
setIsMicFocusable(false);
micFocusTimerRef.current = null;
}, 500);
} else {
// isMicFocusable이 false인 경우 250ms 후에 TInputSimple으로 포커스 이동
micFocusTimerRef.current = setTimeout(() => {
Spotlight.focus(SPOTLIGHT_IDS.SEARCH_INPUT_BOX);
micFocusTimerRef.current = null;
}, 250);
}
}, [isMicFocusable, openVoiceOverlay]);
// 0hun: ✨ [Phase 3] showVirtualKeyboard 제거 (주석 처리된 VirtualKeyboardContainer와 함께 비활성화)
const handleKeydown = useCallback(
(e) => {
@@ -547,16 +580,16 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
}, []);
/**
* 0hun: Mic 아이콘 클릭 시 발생하는 이벤트, `isVoiceOverlayVisible`의 상태값을 `true`로 변경하는 함수
* 0hun: 음성 입력 오버레이를 여는 공통 함수
*/
const onClickMic = useCallback(() => {
const openVoiceOverlay = useCallback(() => {
if (!isOnTopRef.current) {
return;
}
if (DEBUG_MODE) {
console.log(
'🖱️ [DEBUG][SearchPanel] onClickMic called, current isVoiceOverlayVisible:',
'🎤 [DEBUG][SearchPanel] openVoiceOverlay called, current isVoiceOverlayVisible:',
isVoiceOverlayVisible
);
}
@@ -564,9 +597,15 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
setVoiceOverlayResponseText('');
setIsVoiceOverlayBubbleSearch(false);
setIsVoiceOverlayVisible(true);
// setIsVoiceOverlayVisible((prev) => !prev);
}, [isVoiceOverlayVisible]);
/**
* 0hun: Mic 아이콘 클릭 시 발생하는 이벤트
*/
const onClickMic = useCallback(() => {
openVoiceOverlay();
}, [openVoiceOverlay]);
/**
* 0hun: panel 뒤로가기 이벤트
*/
@@ -1748,6 +1787,10 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
clearTimeout(spotlightResumeTimerRef.current);
spotlightResumeTimerRef.current = null;
}
if (micFocusTimerRef.current) {
clearTimeout(micFocusTimerRef.current);
micFocusTimerRef.current = null;
}
};
}, [isOnTopRef]);
@@ -1855,7 +1898,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
<SpottableMicButton
className={css.microphoneButton}
onClick={onClickMic}
// onFocus={onFocusMic}
onFocus={onFocusMic}
onKeyDown={handleMicKeyDown}
spotlightId={SPOTLIGHT_IDS.MICROPHONE_BUTTON}
// 🎯 [포커스 중첩 해결] SearchResultsContainer로 포커스 전달