[251017] fix: SearchPanel TInput
🕐 커밋 시간: 2025. 10. 17. 12:33:29 📊 변경 통계: • 총 파일: 6개 • 추가: +75줄 • 삭제: -48줄 📁 추가된 파일: + com.twin.app.shoptime/src/views/SearchPanel/TInput/TInput.jsx + com.twin.app.shoptime/src/views/SearchPanel/TInput/TInput.module.less 📝 수정된 파일: ~ com.twin.app.shoptime/src/views/SearchPanel/SearchPanel.new.jsx ~ com.twin.app.shoptime/src/views/SearchPanel/SearchPanel.new.module.less ~ com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.jsx ~ com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/modes/VoiceNotRecognized.module.less 🔧 함수 변경 내용: 📄 com.twin.app.shoptime/src/views/SearchPanel/TInput/TInput.module.less (unknown): ✅ Added: translateY()
This commit is contained in:
@@ -17,16 +17,16 @@ import { sendLogGNB, sendLogTotalRecommend } from '../../actions/logActions';
|
||||
import { getMyRecommandedKeyword } from '../../actions/myPageActions';
|
||||
import { popPanel, updatePanel } from '../../actions/panelActions';
|
||||
import { getSearch, resetSearch, searchMain } from '../../actions/searchActions';
|
||||
import {
|
||||
showErrorToast,
|
||||
showInfoToast,
|
||||
showSearchErrorToast,
|
||||
showSearchSuccessToast,
|
||||
showSuccessToast,
|
||||
showWarningToast,
|
||||
} from '../../actions/toastActions';
|
||||
// import {
|
||||
// showErrorToast,
|
||||
// showInfoToast,
|
||||
// showSearchErrorToast,
|
||||
// showSearchSuccessToast,
|
||||
// showSuccessToast,
|
||||
// showWarningToast,
|
||||
// } from '../../actions/toastActions';
|
||||
import TBody from '../../components/TBody/TBody';
|
||||
import TInput, { ICONS, KINDS } from '../../components/TInput/TInput';
|
||||
import TInput, { ICONS, KINDS } from './TInput/TInput';
|
||||
import TPanel from '../../components/TPanel/TPanel';
|
||||
import TScroller from '../../components/TScroller/TScroller';
|
||||
import TVerticalPagenator from '../../components/TVerticalPagenator/TVerticalPagenator';
|
||||
@@ -101,6 +101,16 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId, scrollOpt
|
||||
setInputFocus(false);
|
||||
}, [inputFocus]);
|
||||
|
||||
// TInput의 입력 모드 상태 (webOS 키보드가 뜨는지 여부)
|
||||
const [isInputModeActive, setIsInputModeActive] = useState(false);
|
||||
const handleInputModeChange = useCallback((isActive) => {
|
||||
console.log(
|
||||
'[SearchPanel] TInput 입력 모드:',
|
||||
isActive ? '활성화 (키보드 표시)' : '비활성화 (키보드 숨김)'
|
||||
);
|
||||
setIsInputModeActive(isActive);
|
||||
}, []);
|
||||
|
||||
let searchQueryRef = usePrevious(searchQuery);
|
||||
let isOnTopRef = usePrevious(isOnTop);
|
||||
|
||||
@@ -281,7 +291,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId, scrollOpt
|
||||
);
|
||||
|
||||
// 검색 시작 알림 (선택사항)
|
||||
dispatch(showSuccessToast(`"${query}" 검색 중...`, { duration: 2000 }));
|
||||
// dispatch(showSuccessToast(`"${query}" 검색 중...`, { duration: 2000 }));
|
||||
} else {
|
||||
dispatch(resetSearch());
|
||||
}
|
||||
@@ -473,10 +483,10 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId, scrollOpt
|
||||
}, []);
|
||||
|
||||
// 테스트용 Toast 핸들러들
|
||||
const handleTestToasts = useCallback(() => {
|
||||
// 간단한 Toast 테스트
|
||||
dispatch(showSuccessToast('테스트 메시지입니다', { duration: 3000 }));
|
||||
}, [dispatch]);
|
||||
// const handleTestToasts = useCallback(() => {
|
||||
// // 간단한 Toast 테스트
|
||||
// dispatch(showSuccessToast('테스트 메시지입니다', { duration: 3000 }));
|
||||
// }, [dispatch]);
|
||||
|
||||
// ProductCard 컴포넌트
|
||||
const renderItem = useCallback(
|
||||
@@ -565,6 +575,12 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId, scrollOpt
|
||||
spotlightId={SPOTLIGHT_IDS.SEARCH_INPUT_LAYER}
|
||||
>
|
||||
<div className={css.searchInputWrapper}>
|
||||
<div
|
||||
className={classNames(
|
||||
css.inputWrapper,
|
||||
isInputModeActive && css.inputWrapperActive
|
||||
)}
|
||||
>
|
||||
<TInput
|
||||
className={css.inputBox}
|
||||
kind={KINDS.withIcon}
|
||||
@@ -586,7 +602,9 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId, scrollOpt
|
||||
spotlightBoxDisabled={true}
|
||||
onFocus={_onFocus}
|
||||
onBlur={_onBlur}
|
||||
onInputModeChange={handleInputModeChange}
|
||||
/>
|
||||
</div>
|
||||
<SpottableMicButton
|
||||
className={css.microphoneButton}
|
||||
onClick={onClickMic}
|
||||
|
||||
@@ -70,14 +70,36 @@
|
||||
}
|
||||
}
|
||||
|
||||
.inputBox {
|
||||
// TInput을 감싸는 wrapper
|
||||
.inputWrapper {
|
||||
width: 880px;
|
||||
height: 100px !important;
|
||||
height: 100px;
|
||||
border-radius: 1000px;
|
||||
border: 5px solid #ccc; // 기본 회색 테두리
|
||||
box-sizing: border-box;
|
||||
transition: border 0.2s ease, box-shadow 0.2s ease;
|
||||
|
||||
&:focus-within {
|
||||
border: 5px solid @PRIMARY_COLOR_RED;
|
||||
box-shadow: 0 0 22px 0 rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
// 입력 모드 활성화 시 (webOS 키보드 표시됨)
|
||||
.inputWrapperActive {
|
||||
outline: 2px solid @PRIMARY_COLOR_RED; // 기존 5px 테두리 위에 2px 추가
|
||||
outline-offset: 0px;
|
||||
box-shadow: 0 0 30px 0 rgba(229, 9, 20, 0.6) !important;
|
||||
}
|
||||
|
||||
.inputBox {
|
||||
width: 100%;
|
||||
height: 100% !important;
|
||||
padding-left: 50px;
|
||||
padding-right: 40px;
|
||||
background: white;
|
||||
border-radius: 1000px;
|
||||
border: 5px solid #ccc;
|
||||
border: none; // wrapper에서 테두리 처리
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -126,12 +148,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
&:focus-within,
|
||||
&:focus {
|
||||
border: 5px solid @PRIMARY_COLOR_RED;
|
||||
box-shadow: 0 0 22px 0 rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
// TInput 컴포넌트 자체의 내부 포커스 스타일 제거
|
||||
> div {
|
||||
&:focus,
|
||||
|
||||
151
com.twin.app.shoptime/src/views/SearchPanel/TInput/TInput.jsx
Normal file
151
com.twin.app.shoptime/src/views/SearchPanel/TInput/TInput.jsx
Normal file
@@ -0,0 +1,151 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { InputField } from '@enact/sandstone/Input';
|
||||
import Spotlight from '@enact/spotlight';
|
||||
import Spottable from '@enact/spotlight/Spottable';
|
||||
|
||||
import useScrollReset from '../../../hooks/useScrollReset';
|
||||
import TIconButton from '../../../components/TIconButton/TIconButton';
|
||||
import css from './TInput.module.less';
|
||||
|
||||
const KINDS = { withIcon: 'withIcon' };
|
||||
const ICONS = { search: 'search' };
|
||||
const BORDER = { none: 'none' };
|
||||
const COLOR = { transparent: 'transparent' };
|
||||
|
||||
const Container = Spottable('div');
|
||||
export default function TInput({
|
||||
kind,
|
||||
icon,
|
||||
border,
|
||||
color,
|
||||
className,
|
||||
spotlightDisabled,
|
||||
spotlightBoxDisabled,
|
||||
spotlightId,
|
||||
disabled,
|
||||
onKeyDown,
|
||||
scrollTop,
|
||||
onIconClick,
|
||||
forcedSpotlight,
|
||||
onFocus,
|
||||
onBlur,
|
||||
onInputModeChange, // 입력 모드 변경 콜백
|
||||
...rest
|
||||
}) {
|
||||
const { handleScrollReset, handleStopScrolling } = useScrollReset(scrollTop);
|
||||
|
||||
// 입력 가능 모드 상태 (webOS 키보드가 뜨는 상태)
|
||||
const [isInputActive, setIsInputActive] = useState(false);
|
||||
|
||||
const handleContainerClick = useCallback((e) => {
|
||||
if (e.key === 'ArrrowLeft' || e.key === 'Left') {
|
||||
Spotlight.focus('spotlight_search');
|
||||
} else {
|
||||
Spotlight.focus('input-field-box');
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleButtonKeyDown = useCallback((e) => {
|
||||
if (e.key === 'ArrowDown' || e.key === 'Down') {
|
||||
Spotlight.move('down');
|
||||
}
|
||||
|
||||
if (e.key === 'ArrowLeft' || e.key === 'Left') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
Spotlight.focus('input-field-box');
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleBoxKeyDown = useCallback((e) => {
|
||||
if (e.key === 'ArrowLeft' || e.key === 'Left') {
|
||||
const currentElement = e.currentTarget;
|
||||
const inputElement = currentElement.querySelector('input');
|
||||
|
||||
if (inputElement.value.length === 0) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
Spotlight.focus('spotlight_search');
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
//focus,blur 추가
|
||||
const _onFocus = useCallback(() => {
|
||||
if (onFocus) {
|
||||
onFocus();
|
||||
}
|
||||
handleScrollReset();
|
||||
}, [onFocus]);
|
||||
const _onBlur = useCallback(() => {
|
||||
if (onBlur) {
|
||||
onBlur();
|
||||
}
|
||||
handleStopScrolling();
|
||||
}, [onBlur]);
|
||||
|
||||
// onActivate: 내부 input에 실제로 포커스가 가서 입력 가능한 상태 (webOS 키보드가 뜨는 시점)
|
||||
const _onActivate = useCallback(() => {
|
||||
console.log('[TInput] Input 모드 활성화 - webOS 키보드 표시됨, isInputActive:', true);
|
||||
setIsInputActive(true);
|
||||
// 부모 컴포넌트에 모드 변경 알림
|
||||
if (onInputModeChange) {
|
||||
onInputModeChange(true);
|
||||
}
|
||||
}, [onInputModeChange]);
|
||||
|
||||
// onDeactivate: 내부 input에서 포커스가 빠져나감 (webOS 키보드가 사라지는 시점)
|
||||
const _onDeactivate = useCallback(() => {
|
||||
console.log('[TInput] Input 모드 비활성화 - webOS 키보드 숨김, isInputActive:', false);
|
||||
setIsInputActive(false);
|
||||
// 부모 컴포넌트에 모드 변경 알림
|
||||
if (onInputModeChange) {
|
||||
onInputModeChange(false);
|
||||
}
|
||||
}, [onInputModeChange]);
|
||||
|
||||
console.log('[TInput Render] isInputActive:', isInputActive);
|
||||
|
||||
return (
|
||||
<Container
|
||||
className={classNames(
|
||||
css.container,
|
||||
className ? className : null,
|
||||
isInputActive && css.containerActive
|
||||
)}
|
||||
spotlightId={spotlightId}
|
||||
onClick={handleContainerClick}
|
||||
onKeyDown={handleBoxKeyDown}
|
||||
>
|
||||
<InputField
|
||||
{...rest}
|
||||
spotlightDisabled={spotlightDisabled}
|
||||
className={classNames(css.input, isInputActive && css.inputActive)}
|
||||
autoFocus
|
||||
onFocus={_onFocus}
|
||||
onBlur={_onBlur}
|
||||
onActivate={_onActivate}
|
||||
onDeactivate={_onDeactivate}
|
||||
onKeyDown={onKeyDown}
|
||||
spotlightId={'input-field-box'}
|
||||
aria-label="Keyword edit box"
|
||||
dismissOnEnter
|
||||
/>
|
||||
|
||||
{kind === 'withIcon' && (
|
||||
<TIconButton
|
||||
className={classNames(icon && css[icon])}
|
||||
onClick={onIconClick}
|
||||
forcedSpotlight={forcedSpotlight}
|
||||
ariaLabel="Search, button"
|
||||
onKeyDown={handleButtonKeyDown}
|
||||
/>
|
||||
)}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export { BORDER, COLOR, ICONS, KINDS };
|
||||
@@ -0,0 +1,74 @@
|
||||
@import "../../../style/CommonStyle.module.less";
|
||||
@import "../../../style/utils.module.less";
|
||||
|
||||
.container {
|
||||
width: fit-content;
|
||||
height: fit-content;
|
||||
position: relative;
|
||||
|
||||
// 입력 가능 모드일 때 Container 스타일
|
||||
&.containerActive {
|
||||
.input {
|
||||
background-color: #ffff00 !important; // 노란색 배경
|
||||
}
|
||||
}
|
||||
|
||||
.input {
|
||||
margin-right: 0;
|
||||
margin-left: 0;
|
||||
height: 100px !important;
|
||||
padding: 20px 40px 20px 50px !important;
|
||||
border-radius: 50px;
|
||||
border: none !important; // SearchPanel의 wrapper에서 테두리 처리
|
||||
background-color: #fff !important;
|
||||
width: 100% !important;
|
||||
|
||||
&:focus {
|
||||
box-shadow: none !important; // SearchPanel의 wrapper에서 포커스 처리
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
> div {
|
||||
display: none;
|
||||
}
|
||||
|
||||
> input {
|
||||
font-family: @baseFont;
|
||||
color: @COLOR_GRAY07 !important;
|
||||
height: inherit !important;
|
||||
line-height: inherit !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 입력 가능 모드 (webOS 키보드가 뜨는 상태)
|
||||
// .input과 함께 사용되므로 더 구체적인 선택자 필요
|
||||
.input.inputActive {
|
||||
background-color: #ffff00 !important; // 노란색 배경
|
||||
}
|
||||
|
||||
.withIcon {
|
||||
> div {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.search {
|
||||
position: absolute;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center top;
|
||||
background-size: cover;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
top: 50%;
|
||||
right: 30px;
|
||||
transform: translateY(-50%);
|
||||
background-image: url("../../../../assets/images/searchpanel/ico_search_submit.png");
|
||||
|
||||
&:focus {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
transform: translateY(-50%) scale(1.1);
|
||||
background-position: center bottom;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import Spottable from '@enact/spotlight/Spottable';
|
||||
import micIcon from '../../../../assets/images/searchpanel/image-mic.png';
|
||||
import { getShopperHouseSearch } from '../../../actions/searchActions';
|
||||
import TFullPopup from '../../../components/TFullPopup/TFullPopup';
|
||||
import TInput, { ICONS, KINDS } from '../../../components/TInput/TInput';
|
||||
import TInput, { ICONS, KINDS } from '../TInput/TInput';
|
||||
import { useWebSpeech } from '../../../hooks/useWebSpeech';
|
||||
import VoiceListening from './modes/VoiceListening';
|
||||
import VoiceNotRecognized from './modes/VoiceNotRecognized';
|
||||
@@ -334,9 +334,9 @@ const VoiceInputOverlay = ({
|
||||
setCurrentMode(mode);
|
||||
setVoiceInputMode(null);
|
||||
|
||||
// Overlay 내부로 포커스 이동
|
||||
// 마이크 버튼으로 포커스 이동
|
||||
setTimeout(() => {
|
||||
Spotlight.focus(OVERLAY_SPOTLIGHT_ID);
|
||||
Spotlight.focus(MIC_SPOTLIGHT_ID);
|
||||
}, 100);
|
||||
} else {
|
||||
// Overlay가 닫힐 때 원래 포커스 복원 및 상태 초기화
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
font-weight: 700;
|
||||
letter-spacing: -1px;
|
||||
color: #fff;
|
||||
line-height: 1.2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user