-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat/#492 가수 검색 기능을 구현한다. #503
Conversation
PC에서는 기본적으로 검색창이 열려있도록 청책이 변경되었음. 해당 정책을 위해 해야할 분기처리는 다음과 같다. 1. Input pc -> 노검색: 길게 / 검색: 길게 모바일 -> 노검색: 짧게 / 검색: 길게 2. SearchButton(검색에 쓰이는 버튼) pc -> 노검색: 표시 / 검색: 표시 모바일 -> 노검색: X / 검색: 표시 3. SearchBarExpendButton(검색 창 확장에 쓰이는 버튼) pc -> 노검색: X / 검색: X 모바일 -> 노검색: 표시 / 검색: X 검색 상태 + 디바이스 크기에 따라 분기 처리하였음
검색중 or 검색중 X 의 의미를 가지도록 상태명 개선 검색 상태를 변경하는 의미를 가지도록 핸들러 명 개선
테블릿 이하 사이즈에서 검색중이 아니라면, 쿼리 리셋 버튼이 표시되지 않도록 함
웹 접근성을 위해 기존 div 태그를 form 태그로, 검색중이 아닐때 input disable 처리를 하였음.
디바이스 별로 검색중이 아닐때 보여줄 ui가 달라 disable 속성을 사용할 수 없었음. 트랜지션을 위해 input의 width를 0px로 변경하는 전략을 유지하면서 tab을 불가능하게 하여 접근성을 챙길 수 있는 visibility 속성을 적용하였음.
artistSearch -> search 로 변경하여 추후 검색 기능 확장을 용이하게 하였음.
비동기적으로 동작하는 setState 특성상 핸들러 함수 내부에서 focus() 하는 방식을 사용할 수 없었음. 때문에 상태와 DOM의 동기화를 위해 effect를 사용함. 또한 visibility의 transition은 애니메이션이 다 적용된 후에 변경하려던 속성을 가지므로 transition 속성을 width에만 적용하도록 변경하였음.
} | ||
`; | ||
|
||
const SheetTitle = styled(Flex)` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
검색결과가 많을 경우에도 상단 타이틀은 고정되는 사용성을 위해 추가했습니다.
|
||
export default SearchBar; | ||
|
||
const searchButtonStyles = css` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
61번 라인부터 css 코드를 보실때는 이 commit을 참고해 주세요.
디바이스에 따른 분기를 다 적어 놓았습니다.
inputRef.current?.focus(); | ||
}, []); | ||
|
||
useEffect(() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@media (max-width: ${({ theme }) => theme.breakPoints.md}) { | ||
width: ${({ $isSearching }) => !$isSearching && '0px'}; | ||
padding: ${({ $isSearching }) => ($isSearching ? '0 40px 0 28px' : 0)}; | ||
visibility: ${({ $isSearching }) => !$isSearching && 'hidden'}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
import type { SingerSearchPreview, SingerSearchResult } from '../types/search'; | ||
|
||
export const getSingerSearchPreview = async (query: string): Promise<SingerSearchPreview[]> => { | ||
const encodedQuery = encodeURIComponent(query); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
검색 쿼리문에 &, = 등이 포함되면 올바르게 전달되지 않을 수 있다고 해요.
가수 이름에 &
포함되는 경우도 충분히 있기에 별도로 적용했습니다.
// ex
https://javascript.com?name=co&ding
파라메터로 들어가는 name의 value에 &이 들어가므로
encodeURIComponent를 사용하여 인코딩 해줘야 한다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
작업 인계를 위한 구현1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
작업 인계를 위한 구현2
@@ -0,0 +1,18 @@ | |||
import { useSearchParams } from 'react-router-dom'; | |||
|
|||
const useValidSearchParams = (...params: string[]) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
기존에 설명드린 useValidParams와 비슷한 맥락으로 구현한 hook이에요
이번엔 searchParams를 편하게 사용하기 위해 구현했습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
와우~ 첨엔 이해가 잘 안되었는데 null 을 조기에 검사한 부분 너무 좋은것 같아요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
검색 기능 구현하느라 고생하셨습니다 도밥!
스토리북에 자잘한 것까지 신경 써주셔서 한결 나아졌네요! 👍
검색창이 expanded 되었을때 로고가 살짝 튀어나오는건 어쩔 수 없는 건가용? 😭
import LoginPopupProvider from '@/features/auth/hooks/LoginPopUpContext'; | ||
import handlers from '@/mocks/handlers'; | ||
|
||
const customViewport = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍👍 와우 커스텀 뷰포트 추가하는거 좋네요!
|
||
export const getSingerSearch = async (query: string): Promise<SingerSearchResult[]> => { | ||
const encodedQuery = encodeURIComponent(query); | ||
return await fetcher(`/singers?name=${encodedQuery}&search=singer,song`, 'GET'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💬 singer,song
은 뭔가요?!?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저도 궁금합니뎅~
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해당 api는 쿼리에 해당하는 가수의 정보 + 노래 3개를 반환합니다!
여기를 보시면 더 이해가 빠를듯 합니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💬 singer,song
이런 형태도 가능한건지 궁금해요 ,
를 %2C
로 안바꿔도 되는가용?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
바로 물어봐야겠군요.
"postcss-styled-syntax": "^0.4.0", | ||
"prettier": "^3.0.0", | ||
"react-refresh": "^0.14.0", | ||
"storybook": "^7.0.27", | ||
"storybook-addon-react-router-v6": "^2.0.7", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💬 storybook에서 react router가 필요한 이유가 있나요? 🤔
페이지를 넘나드는 단위의 스토리북은 작성하지 않기로 했었던 것 같은데,
필요하다고 생각드신다면 의견 이야기 해주세요! 😊
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
맞아요. '넘나들기 위해서' 라기 보다는 컴포넌트를 보여주기 위해서 사용했습니다.
searchBar 컴포넌트 자체가 라우터를 사용하고 있는 컴포넌트기 때문에, 사용해야 했습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
확인이 끝나고 추후에 삭제...를 해야할까요 ? ㅋㅋㅋㅋㅋ
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
음 일단 제 작업에서 스토리북 수정하면서 useNavigate를 사용한다고 에러가 떴었는데,
BrowserRouter만 감싸줘도 동작이 되었는데요.
해당 라이브러리가 필요한지는 검토해봐야 알 것 같아요
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아하.. 알겠습니당
search 함수 실행되지 않도록 변경
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
안녕하세요 도밥! 코난이 이미 언급한 부분 제외하고 의견 남겼습니다! 여러모로 복잡할 로직이었을 텐데 대체로 잘 해주신 것 같아요! 특히 훅 분리, 스토리북이나 msw 신경써주신 점 보면서 많이 배웠습니다! 항상 도밥의 꼼꼼함에 놀라요 ㅎㅎ
이번 PR 핵심에 대해선 세 개의 의견이 있습니다.
-
onBlur 이벤트로 모달을 제거하는 방식
저는 input에 포커스를 잃었을 때 모달을 제거하는 건 사용자 경험상 좋지 않다고 생각해요. 만약 사용자가 모달의 빈 부분을 클릭한다면 모달이 닫힐 거예요. 아마 아티스트 목록 사이에 빈곳을 눌렀다면, 사용자는 오작동났다고 느낄 수도 있을 것 같네요. -
데스크탑 환경에서 작은 모달로 띄워줄 필요가 있을까요?
사용하던 페이지 컨텍스트와 검색 컨텍스트는 별개라고 생각해요. 즉, 검색 도중에 뒷편에 있는 내용을 참고할 일이 없을 것 같아요. (도밥이 구현한 것도 뒷편이 스크롤되지 않는 걸로 보아 비슷한 생각이지 않을까 감히 생각해봅니다.) 만약 데스크탑 환경에서 모달로 띄워주신 이유가 있다면 의견 알려주세요! -
검색 절차
현재 "검색 -> 가수 목록 -> 가수와 가수의 노래 목록 -> 가수별 노래 페이지" 플로우인 것 같아요. 중간 절차인 "가수 목록"이 필요했던 이유에 대해서 궁금해요. 사실 우리 서비스에서 가수에 대해 가지고 있는 정보도 없어서 가수 목록만 보여주기엔 ui가 좀 휑한 것 같아요...
이번에 검색 스쿼드에서 피드백을 진행하지 않아서 2번하고 3번에 대해 미리 의견을 공유하지 못한 점이 아쉽네요! 2번과 3번 같은 경우는 기획적인 부분도 포함되어 있는 사항이고 이미 구현을 해주셨기 때문에 당장은 무시하셔도 괜찮....을수도??? 그래도 제 생각을 이야기 해야할 것 같아서 남겼습니다.
width: ${({ $isSearching }) => !$isSearching && '0px'}; | ||
padding: ${({ $isSearching }) => ($isSearching ? '0 40px 0 28px' : 0)}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
0 "px" 빼면 좋을 것 같아요
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
반영 완료했습니다.
|
||
transition: width 0.3s ease; | ||
|
||
@media (max-width: ${({ theme }) => theme.breakPoints.md}) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💬 저희 서비스가 mobile-first 라 기본 속성을 "모바일" 기준으로 보는 게 더 편리할 것 같아요. max-width 보다는 min-width가 좋아보이는데 어떻게 생각하시나요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
프로젝트 전반적으로 모든 반응형 사이즈가 max-width로 되어있어서, 지금 모두 되돌리기에는 다소 리소스가 들 것 같아요.
우코 말씀대로 사실 처음부터 모바일만 pc로 지원범위를 늘려갈때는 min-width가 좋은 선택이였을거에요 ㅋㅋㅋ
(첫 반응형 코드를 넣은게 저일텐데... min으로 시작했어야 하는데 ㅠㅠ죄송합니다 익숙한대로 해버렸어요.)
다만, 이제 pc 모바일 관련없이 모두 지원하고 있으니 그대로 두어도 괜찮다고 생각하는데 우코 생각은어떤가요?
|
||
export const getSingerSearch = async (query: string): Promise<SingerSearchResult[]> => { | ||
const encodedQuery = encodeURIComponent(query); | ||
return await fetcher(`/singers?name=${encodedQuery}&search=singer,song`, 'GET'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저도 궁금합니뎅~
<SheetTitle $align="center" aria-hidden> | ||
아티스트 | ||
</SheetTitle> | ||
<PreviewItemList as="ul" $direction="column" $gap={16}> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💬 Flex 공용컴포넌트를 사용하면서부터 flex props들이 JSX 표기로 이동된 김에 suffix로 "Flex"붙이는 거 어떠신가요? JSX단 읽을 때 더 쉽게 해당 element가 flex임을 알 수 있을 것 같네요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
좋은 생각이네요! 안그래도 flex 관련 props를 맨앞으로 빼서 식별하게 해줘야하나 생각하고 있었어요
|
||
background-color: #121212; | ||
border-radius: 8px; | ||
box-shadow: 0px 0px 10px #ffffff49; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
여기도 'px' 빼주세용~
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💬 추가로 빛 번지듯이 흰새긍로 shadow 생기는 게 다소 어색하다고 느껴져요. 도밥은 어떻게 생각하시나요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
height: 50px; | ||
|
||
font-weight: 700; | ||
letter-spacing: 2px; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
letter-spacing 보다는 weight와 size로만 소제목 구성하는 게 좋을 것 같아요
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
제목이 너무 다닥다닥 붙어있는것 같아서 letter-spacing을 넣었는데, 지금보니깐 또 괜찮은것도 같네요 ㅋㅋㅋ
반영하겠습니다~
@@ -0,0 +1,122 @@ | |||
import { useNavigate } from 'react-router-dom'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해당 파일 이름도 Search라는 단어가 들어가면 더 파악하기 쉬울 것 같아요
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오우 감사합니다.
곧 생길 결과 페이지와 혼동을 고려하고, 내부적으로 이름을 통일하려고 SearchPreviewSheet
로 변경했습니다~
@@ -0,0 +1,18 @@ | |||
import { useSearchParams } from 'react-router-dom'; | |||
|
|||
const useValidSearchParams = (...params: string[]) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
와우~ 첨엔 이해가 잘 안되었는데 null 을 조기에 검사한 부분 너무 좋은것 같아요!
Flex prefix 추가, 미리보기의 의미를 담아 컴포넌트 명 변경
1. 미리보기 시트에 tabIndex={-1} 부여 2. onBlur 핸들러의 검색 종료 제외 요소로 추가 3. FlexPreviewItem mouseDown 핸들러에 검색 종료 함수 추가
코멘트 답변 및 반영 완료했습니다! 코난 피드백 답변로고 빠져나오는거 border-radius를 살짝 줄여서 덮는 방법으로 해결해 봤는데, 둥근게 이쁘긴해서 ㅠ 아쉽네요 우코 피드백 답변
넵 420px(xs) 이하의 사이즈부터 왼쪽 끝에 달라붙어 길어지도록 의도했어요.
저는 이 피드백을 그렇다면 저도 공감했던 부분이고 해결책이 없을까 더 찾아보았는데요!! 덕분에 해결책을 발견했습니다!!!! 사용성 부분 개선
if (relatedTarget?.id === 'search-preview-sheet') return;
의미적인 부분 개선
우코 피드백을 반영하면서 백드롭을 추가했고, esc로 검색 종료하는 핸들러도 추가하게 되었어요. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
고생많았어요 도밥! 😄 데스크탑 환경에서 검색창 클릭 -> 모달 클릭 -> 백드랍 클릭 시에 모달이 닫히지 않아요! 해당 문제만 해결해주시면 될 것 같아요! 다른 부분은 별다른 피드백은 없습니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
검색 완료 페이지에서 쓰이는 api 요청을 처리할 수 있도록 개선
가수 상세 페이지에서 쓰이는 api 요청을 처리할 수 있도록 개선
📝작업 내용
서치 바 구현
기존 회의에서 정한 정책대로, Header에 고정적으로 표시될 서치 바를 구현했습니다.
완성도를 위해 반응형 디자인, 애니메이션을 구현했어요.
또한 정책 내에서 사용자 편의성을 위한 몇가지 기능을 구현하였습니다.
디바이스에 따른 세부 동작에 대한 설명은 분기가 많아 commit msg로 정리해 두었습니다.
이 Commit과 스토리북을 참고해 주세요.
msw에서 fixture 데이터를 응답하는 검색 키워드는 '악동', '악동뮤지션', '뮤지션' 입니다.
스토리북 확인하실 때 참고해 주세요
원할한 작업 인계를 위해 미리 remote 함수, type, Page컴포넌트 및 router 경로까지 정의해 두었습니다.
구현에 도움 됐으면 좋겠네요.👶
💬리뷰 참고사항
또한 Router를 사용하는 컴포넌트에서는 추가 설정도 필요했습니다.
근본적인 해결은 로직을 변경해야 하는 부분이라 그대로 두고, 관련 설정만 다시 해 두었습니다.
빠른 시일 내에 개선해봐야겠네요.
설정을 위해
storybook-addon-react-router-v6
,msw-storybook-addon
를 설치했으니,본 PR 머지 후 작업하실 때
npm ci
해주시길 바랍니다.#️⃣연관된 이슈
close #492