1- import React , { useEffect , useMemo , useRef , useState } from 'react' ;
1+ import React , { useCallback , useEffect , useMemo , useRef , useState } from 'react' ;
22import StackImage from '@components/StackImage.tsx' ;
33import CloseIcon from '@components/svgs/CloseIcon.tsx' ;
44import Search from '@components/svgs/Search.tsx' ;
@@ -7,6 +7,7 @@ import TechStacks from '@constant/stackList.ts';
77import Alert from '@constant/Alert.ts' ;
88import '@styles/components/TechStackSelector.scss' ;
99import { DefaultStack } from '@constant/initData.ts' ;
10+ import { ITechStack } from '@constant/interfaces.ts' ;
1011
1112interface ITechStackSelector {
1213 value : string [ ] ;
@@ -47,6 +48,53 @@ function TechStackSelector({value, placeholder='스택 입력', max=Infinity, al
4748 return 80 ;
4849 } , [ search ] ) ;
4950
51+ const addStack = useCallback ( ( stack : string ) => {
52+ if ( max !== 1 && value . length >= max ) {
53+ Alert . show ( `최대 ${ max } 개까지 선택 가능합니다.` ) ;
54+ return ;
55+ }
56+
57+ if ( value . includes ( stack ) ) {
58+ Alert . show ( '이미 선택된 스택입니다.' ) ;
59+ return ;
60+ }
61+
62+ if ( ! allowCustomInput && ! TechStacks . some ( s => s . tagName === stack ) ) {
63+ Alert . show ( '존재하지 않는 스택입니다.' ) ;
64+ return ;
65+ }
66+
67+ // max = 1 일 때, 스택만 변경되도록 설정
68+ if ( max === 1 ) {
69+ onChange ?.( [ stack ] ) ;
70+ saveSelectedTechStack ( stack ) ;
71+ setSearch ( hideSelectedOptions ? '' : stack ) ;
72+ console . log ( 'search' , search , stack ) ;
73+ return ;
74+ }
75+
76+ onChange ?.( [ ...value , stack ] ) ;
77+ saveSelectedTechStack ( stack ) ;
78+ setSearch ( '' ) ;
79+ searchRef . current ?. focus ( ) ;
80+ } , [ max , value , allowCustomInput , onChange , hideSelectedOptions , search ] ) ;
81+
82+ function cancelSearch ( ) {
83+ setIsShow ( false ) ;
84+ searchRef . current ?. focus ( ) ;
85+ popupRef . current ?. blur ( ) ;
86+ }
87+
88+ const deleteStack = useCallback ( ( stack : string ) => {
89+ onChange ?.( value . filter ( s => s !== stack ) ) ;
90+ } , [ value , onChange ] ) ;
91+
92+ function deleteAllStacks ( ) {
93+ onChange ?.( [ ] ) ;
94+ setSearch ( '' ) ;
95+ setIsShow ( true ) ;
96+ searchRef . current ?. focus ( ) ;
97+ }
5098
5199 // 검색창 키 이벤트
52100 function searchKeyEvent ( e : React . KeyboardEvent < HTMLInputElement > ) {
@@ -129,7 +177,7 @@ function TechStackSelector({value, placeholder='스택 입력', max=Infinity, al
129177 block : 'nearest' ,
130178 } )
131179 }
132- } , [ focusedIndex ] ) ;
180+ } , [ focusedIndex , isShow ] ) ;
133181
134182 // max = 1 이고, 유효한 search 가 아닐 때 초기화
135183 useEffect ( ( ) => {
@@ -138,54 +186,6 @@ function TechStackSelector({value, placeholder='스택 입력', max=Infinity, al
138186 }
139187 } , [ isShow , value ] ) ;
140188
141- function addStack ( stack : string ) {
142- if ( max !== 1 && value . length >= max ) {
143- Alert . show ( `최대 ${ max } 개까지 선택 가능합니다.` ) ;
144- return ;
145- }
146-
147- if ( value . includes ( stack ) ) {
148- Alert . show ( '이미 선택된 스택입니다.' ) ;
149- return ;
150- }
151-
152- if ( ! allowCustomInput && ! TechStacks . some ( s => s . tagName === stack ) ) {
153- Alert . show ( '존재하지 않는 스택입니다.' ) ;
154- return ;
155- }
156-
157- // max = 1 일 때, 스택만 변경되도록 설정
158- if ( max === 1 ) {
159- onChange ?.( [ stack ] ) ;
160- saveSelectedTechStack ( stack ) ;
161- setSearch ( hideSelectedOptions ? '' : stack ) ;
162- console . log ( 'search' , search , stack ) ;
163- return ;
164- }
165-
166- onChange ?.( [ ...value , stack ] ) ;
167- saveSelectedTechStack ( stack ) ;
168- setSearch ( '' ) ;
169- searchRef . current ?. focus ( ) ;
170- }
171-
172- function cancelSearch ( ) {
173- setIsShow ( false ) ;
174- searchRef . current ?. focus ( ) ;
175- popupRef . current ?. blur ( ) ;
176- }
177-
178- function deleteStack ( stack : string ) {
179- onChange ?.( value . filter ( s => s !== stack ) ) ;
180- }
181-
182- function deleteAllStacks ( ) {
183- onChange ?.( [ ] ) ;
184- setSearch ( '' ) ;
185- setIsShow ( true ) ;
186- searchRef . current ?. focus ( ) ;
187- }
188-
189189 return (
190190 < div className = { hideSelectedOptions ? 'tech_stack_selector search_style' : 'tech_stack_selector' }
191191 ref = { popupRef } >
@@ -244,13 +244,12 @@ function TechStackSelector({value, placeholder='스택 입력', max=Infinity, al
244244 ) }
245245 < ul ref = { ulRef } onMouseLeave = { ( ) => setFocusedIndex ( - 1 ) } >
246246 { searchedStacks . length > 0 ? searchedStacks . map ( ( stack , index ) => (
247- < li className = { 'option_view ' + ( focusedIndex === index ? 'selected' : '' ) }
248- key = { stack . tagName } tabIndex = { 0 }
249- onMouseOver = { ( ) => setFocusedIndex ( index ) }
250- onClick = { ( ) => addStack ( stack . tagName ) } >
251- < StackImage stack = { stack } hasTooltip = { false } />
252- < span > { stack . tagName } </ span >
253- </ li >
247+ < SearchedStackLi key = { stack . tagName }
248+ index = { index }
249+ stack = { stack }
250+ selected = { focusedIndex === index }
251+ addStack = { addStack }
252+ setFocusedIndex = { setFocusedIndex } />
254253 ) ) : (
255254 < li className = 'option_view not_searched' >
256255 < span > 검색 결과가 없습니다</ span >
@@ -263,4 +262,24 @@ function TechStackSelector({value, placeholder='스택 입력', max=Infinity, al
263262 ) ;
264263}
265264
265+ interface ISearchedStackLi {
266+ stack : ITechStack ;
267+ selected : boolean ;
268+ index : number ;
269+ setFocusedIndex : ( index : number ) => void ;
270+ addStack : ( stack : string ) => void ;
271+ }
272+
273+ function SearchedStackLi ( { stack, selected, index, setFocusedIndex, addStack } : ISearchedStackLi ) {
274+ return (
275+ < li className = { 'option_view ' + ( selected ? 'selected' : '' ) }
276+ tabIndex = { 0 }
277+ onMouseOver = { ( ) => setFocusedIndex ( index ) }
278+ onClick = { ( ) => addStack ( stack . tagName ) } >
279+ < StackImage stack = { stack } hasTooltip = { false } />
280+ < span > { stack . tagName } </ span >
281+ </ li >
282+ ) ;
283+ }
284+
266285export default TechStackSelector ;
0 commit comments