Skip to content
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

Merged
merged 69 commits into from
Oct 17, 2023
Merged

Conversation

Creative-Lee
Copy link
Collaborator

@Creative-Lee Creative-Lee commented Oct 12, 2023

📝작업 내용

서치 바 구현

  • 기존 회의에서 정한 정책대로, Header에 고정적으로 표시될 서치 바를 구현했습니다.
    완성도를 위해 반응형 디자인, 애니메이션을 구현했어요.
    또한 정책 내에서 사용자 편의성을 위한 몇가지 기능을 구현하였습니다.

  • 디바이스에 따른 세부 동작에 대한 설명은 분기가 많아 commit msg로 정리해 두었습니다.
    Commit과 스토리북을 참고해 주세요.
    msw에서 fixture 데이터를 응답하는 검색 키워드는 '악동', '악동뮤지션', '뮤지션' 입니다.
    스토리북 확인하실 때 참고해 주세요

  • 원할한 작업 인계를 위해 미리 remote 함수, type, Page컴포넌트 및 router 경로까지 정의해 두었습니다.
    구현에 도움 됐으면 좋겠네요.👶

💬리뷰 참고사항

  • 전역적으로 사용하는 useFetch, Mutation에서 여러 Context를 사용하면서, 거의 모든 스토리가 동작하지 않고 있었어요.
    또한 Router를 사용하는 컴포넌트에서는 추가 설정도 필요했습니다.
    근본적인 해결은 로직을 변경해야 하는 부분이라 그대로 두고, 관련 설정만 다시 해 두었습니다.
    빠른 시일 내에 개선해봐야겠네요.

설정을 위해 storybook-addon-react-router-v6, msw-storybook-addon 를 설치했으니,
본 PR 머지 후 작업하실 때 npm ci 해주시길 바랍니다.

  • 아래 코드 코멘트에 자세한 설명을 해 놓았으니, 꼭 읽어주시면 감사하겠습니다.

#️⃣연관된 이슈

close #492

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)`
Copy link
Collaborator Author

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`
Copy link
Collaborator Author

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(() => {
Copy link
Collaborator Author

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'};
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

visivilty 속성에 대한 설명은 이 commit이 commit참고해 주세요

import type { SingerSearchPreview, SingerSearchResult } from '../types/search';

export const getSingerSearchPreview = async (query: string): Promise<SingerSearchPreview[]> => {
const encodedQuery = encodeURIComponent(query);
Copy link
Collaborator Author

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를 사용하여 인코딩 해줘야 한다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

작업 인계를 위한 구현1

Copy link
Collaborator Author

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[]) => {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

기존에 설명드린 useValidParams와 비슷한 맥락으로 구현한 hook이에요

이번엔 searchParams를 편하게 사용하기 위해 구현했습니다.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

와우~ 첨엔 이해가 잘 안되었는데 null 을 조기에 검사한 부분 너무 좋은것 같아요!

Copy link
Collaborator

@cruelladevil cruelladevil left a 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 = {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍👍 와우 커스텀 뷰포트 추가하는거 좋네요!

frontend/src/mocks/handlers/index.ts Show resolved Hide resolved

export const getSingerSearch = async (query: string): Promise<SingerSearchResult[]> => {
const encodedQuery = encodeURIComponent(query);
return await fetcher(`/singers?name=${encodedQuery}&search=singer,song`, 'GET');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 singer,song은 뭔가요?!?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 궁금합니뎅~

Copy link
Collaborator Author

@Creative-Lee Creative-Lee Oct 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 api는 쿼리에 해당하는 가수의 정보 + 노래 3개를 반환합니다!
여기를 보시면 더 이해가 빠를듯 합니다!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 singer,song 이런 형태도 가능한건지 궁금해요 ,%2C로 안바꿔도 되는가용?

Copy link
Collaborator Author

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",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 storybook에서 react router가 필요한 이유가 있나요? 🤔
페이지를 넘나드는 단위의 스토리북은 작성하지 않기로 했었던 것 같은데,
필요하다고 생각드신다면 의견 이야기 해주세요! 😊

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

맞아요. '넘나들기 위해서' 라기 보다는 컴포넌트를 보여주기 위해서 사용했습니다.
searchBar 컴포넌트 자체가 라우터를 사용하고 있는 컴포넌트기 때문에, 사용해야 했습니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인이 끝나고 추후에 삭제...를 해야할까요 ? ㅋㅋㅋㅋㅋ

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

음 일단 제 작업에서 스토리북 수정하면서 useNavigate를 사용한다고 에러가 떴었는데,
BrowserRouter만 감싸줘도 동작이 되었는데요.
해당 라이브러리가 필요한지는 검토해봐야 알 것 같아요

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아하.. 알겠습니당

frontend/src/features/search/components/SearchBar.tsx Outdated Show resolved Hide resolved
frontend/src/features/search/components/SearchBar.tsx Outdated Show resolved Hide resolved
 search 함수 실행되지 않도록 변경
Copy link
Collaborator

@ukkodeveloper ukkodeveloper left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안녕하세요 도밥! 코난이 이미 언급한 부분 제외하고 의견 남겼습니다! 여러모로 복잡할 로직이었을 텐데 대체로 잘 해주신 것 같아요! 특히 훅 분리, 스토리북이나 msw 신경써주신 점 보면서 많이 배웠습니다! 항상 도밥의 꼼꼼함에 놀라요 ㅎㅎ

이번 PR 핵심에 대해선 세 개의 의견이 있습니다.

  1. onBlur 이벤트로 모달을 제거하는 방식
    저는 input에 포커스를 잃었을 때 모달을 제거하는 건 사용자 경험상 좋지 않다고 생각해요. 만약 사용자가 모달의 빈 부분을 클릭한다면 모달이 닫힐 거예요. 아마 아티스트 목록 사이에 빈곳을 눌렀다면, 사용자는 오작동났다고 느낄 수도 있을 것 같네요.

  2. 데스크탑 환경에서 작은 모달로 띄워줄 필요가 있을까요?
    사용하던 페이지 컨텍스트와 검색 컨텍스트는 별개라고 생각해요. 즉, 검색 도중에 뒷편에 있는 내용을 참고할 일이 없을 것 같아요. (도밥이 구현한 것도 뒷편이 스크롤되지 않는 걸로 보아 비슷한 생각이지 않을까 감히 생각해봅니다.) 만약 데스크탑 환경에서 모달로 띄워주신 이유가 있다면 의견 알려주세요!

  3. 검색 절차
    현재 "검색 -> 가수 목록 -> 가수와 가수의 노래 목록 -> 가수별 노래 페이지" 플로우인 것 같아요. 중간 절차인 "가수 목록"이 필요했던 이유에 대해서 궁금해요. 사실 우리 서비스에서 가수에 대해 가지고 있는 정보도 없어서 가수 목록만 보여주기엔 ui가 좀 휑한 것 같아요...

이번에 검색 스쿼드에서 피드백을 진행하지 않아서 2번하고 3번에 대해 미리 의견을 공유하지 못한 점이 아쉽네요! 2번과 3번 같은 경우는 기획적인 부분도 포함되어 있는 사항이고 이미 구현을 해주셨기 때문에 당장은 무시하셔도 괜찮....을수도??? 그래도 제 생각을 이야기 해야할 것 같아서 남겼습니다.

추가로
애매한 뷰포트의 width에서 해당 부분이 의도된 것인지 확인해주세요!
image

Comment on lines 116 to 117
width: ${({ $isSearching }) => !$isSearching && '0px'};
padding: ${({ $isSearching }) => ($isSearching ? '0 40px 0 28px' : 0)};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 "px" 빼면 좋을 것 같아요

Copy link
Collaborator Author

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}) {
Copy link
Collaborator

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가 좋아보이는데 어떻게 생각하시나요?

Copy link
Collaborator Author

@Creative-Lee Creative-Lee Oct 15, 2023

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');
Copy link
Collaborator

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}>
Copy link
Collaborator

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임을 알 수 있을 것 같네요.

Copy link
Collaborator Author

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;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기도 'px' 빼주세용~

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 추가로 빛 번지듯이 흰새긍로 shadow 생기는 게 다소 어색하다고 느껴져요. 도밥은 어떻게 생각하시나요?

Copy link
Collaborator Author

@Creative-Lee Creative-Lee Oct 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

초기에 뒷 배경와 검색창의 색상 차이가 거의 없어서 조금 강조시키기 위해 적용했었어요 ㅋㅋㅋ
흐음 이미 바깥 배경 스크롤이 안되도록 구현한 김에 백드랍을 넣는것도 괜찮겠네요.
반영하겠습니다ㅎ

💬 본문 PR을 참고해주세요!
백드랍을 넣으니 배경색이 더 어두워 져서 경계가 더욱더 명확하지 않아졌어요.
고민이네요..ㅠ 혹시 다른 해결책이 있을까요?

image
image

💬 백드랍이 생기고 나서의 쉐도우 느낌은 어떤가요?

frontend/src/features/search/components/ResultSheet.tsx Outdated Show resolved Hide resolved
height: 50px;

font-weight: 700;
letter-spacing: 2px;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

letter-spacing 보다는 weight와 size로만 소제목 구성하는 게 좋을 것 같아요

Copy link
Collaborator Author

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';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 파일 이름도 Search라는 단어가 들어가면 더 파악하기 쉬울 것 같아요

Copy link
Collaborator Author

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[]) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

와우~ 첨엔 이해가 잘 안되었는데 null 을 조기에 검사한 부분 너무 좋은것 같아요!

@Creative-Lee
Copy link
Collaborator Author

코멘트 답변 및 반영 완료했습니다!

코난 피드백 답변

로고 빠져나오는거 border-radius를 살짝 줄여서 덮는 방법으로 해결해 봤는데, 둥근게 이쁘긴해서 ㅠ 아쉽네요
다른 해결책 있을까요?
image
image

우코 피드백 답변

  1. 애매한 뷰포트의 width에서 해당 부분이 의도된 것인지 확인해주세요!

넵 420px(xs) 이하의 사이즈부터 왼쪽 끝에 달라붙어 길어지도록 의도했어요.
말씀해 주신대로 애매한 뷰포트라서 반응형 break point를 추가하기가 애매했어요.
테블렛 사이즈부터 검색창이 헤더 맨 좌측까지 길어지도록 하기에도 애매했고요
무엇보다 웬만한 모바일 디바이스의 width값이 저희 반응형 사이즈에 잘 맞아서
저 view가 보여질 일이 많이 없을것 같다고 판단했습니다! (pc에서 줄여놓거나 했을때는 감수했습니다...😅)

  1. onBlur 이벤트로 모달을 제거하는 방식

저는 이 피드백을 onBlur 자체가 별로다. 라기 보다
FlexPreviewItem 외의 빈곳 클릭시 꺼지는 문제가 있어, 사용자 경험이 좋지않다~ 라고 해석했는데 맞을까요?

그렇다면 저도 공감했던 부분이고 해결책이 없을까 더 찾아보았는데요!! 덕분에 해결책을 발견했습니다!!!!
다음과 같은 방식으로 해결해 보았습니다.

사용성 부분 개선

  1. 미리보기 시트에 tabIndex={-1} 부여 (blur 이벤트의 e.relatedTarget은 대화형 요소에만 반응함)
  2. onBlur 핸들러의 endSearch 제외 요소로 추가
if (relatedTarget?.id === 'search-preview-sheet') return;
  1. FlexPreviewItem 의 mouseDown 핸들러에 검색 종료 함수 추가
  2. 사용성 개선!

의미적인 부분 개선

  1. 이미 스크롤 방지 effect가 들어간 이상, 백드랍을 추가한다. (검색에만 집중시키려는 의도를 담음)
  2. 검색창 외의 영역 클릭시 === 백드랍을 클릭한 것이므로 검색을 종료한다는 의미더 명확하게 줄 수 있다.
  3. 이로써 사용자 입장에서의 의미개선!

우코 피드백을 반영하면서 백드롭을 추가했고, esc로 검색 종료하는 핸들러도 추가하게 되었어요.
다시 리뷰 해주세요!

Copy link
Collaborator

@ukkodeveloper ukkodeveloper left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생많았어요 도밥! 😄 데스크탑 환경에서 검색창 클릭 -> 모달 클릭 -> 백드랍 클릭 시에 모달이 닫히지 않아요! 해당 문제만 해결해주시면 될 것 같아요! 다른 부분은 별다른 피드백은 없습니다!

Copy link
Collaborator

@cruelladevil cruelladevil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

반영사항 모두 확인했습니다 👍👍

빈공간으로 길게 채웠을 때를 허용할지(naver) 허용하지 않을지(melon) 정하면 얼추 마무리 될 것 같네요!

image

검색 완료 페이지에서 쓰이는 api 요청을 처리할 수 있도록 개선
가수 상세 페이지에서 쓰이는 api 요청을 처리할 수 있도록 개선
@Creative-Lee Creative-Lee merged commit 58dbb38 into main Oct 17, 2023
4 checks passed
@Creative-Lee Creative-Lee deleted the feat/#492 branch October 17, 2023 07:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[ 🌞 FE ] 프론트엔드 크루들의 빛나는 개발 이야기 하나둘셋 호! ✨ Feat 꼼꼼한 기능 구현 중요하죠
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[FEAT] 가수 검색 기능을 구현한다.
3 participants