Skip to content

Commit

Permalink
Feat/#492 가수 검색 기능을 구현한다. (#503)
Browse files Browse the repository at this point in the history
* chore: 돋보기 아이콘 추가

* design: 돋보기 아이콘 투명도 제

* feat: 검색 결과 시트 컴포넌트 마크업 및 스타일링 완료

* feat: 헤더에 서치바 컴포넌트 추가

* feat: 서치 바 컴포넌트 기본 동작, 마크업 및 스타일링 완료

* feat: 검색 쿼리 입력 전체삭제 기능 구현

* design: 결과 시트 디자인 수정

* refactor: Flex 컴포넌트 반응형 추가 스타일링을 위한 속성 추가

* design: 테블릿 이하 사이즈부터 결과시트가 화면을 가득 채우도록 변경

* design: 테블릿 이하 사이즈부터 검색 시 헤더를 덮어쓰도록 변경

* design: 검색창 관련 변경된 정책 적용

PC에서는 기본적으로 검색창이 열려있도록 청책이 변경되었음.
해당 정책을 위해 해야할 분기처리는 다음과 같다.

1. Input
pc -> 노검색: 길게 / 검색: 길게
모바일 -> 노검색: 짧게 / 검색: 길게

2. SearchButton(검색에 쓰이는 버튼)
pc -> 노검색: 표시 / 검색: 표시
모바일 -> 노검색: X / 검색: 표시

3. SearchBarExpendButton(검색 창 확장에 쓰이는 버튼)
pc -> 노검색: X / 검색: X
모바일 -> 노검색: 표시 / 검색: X

검색 상태 + 디바이스 크기에 따라 분기 처리하였음

* refactor: 상태명 및 핸들러명 변경으로 의미 개선

검색중 or 검색중 X 의 의미를 가지도록 상태명 개선
검색 상태를 변경하는 의미를 가지도록 핸들러 명  개선

* chore: 뒤로가기 아이콘 추가

* feat: 테블릿 이하 사이즈에서 검색창을 찾는 뒤로가기 버튼 제공

* design: 쿼리 리셋 버튼 관련 디자인 수정

테블릿 이하 사이즈에서 검색중이 아니라면, 쿼리 리셋 버튼이 표시되지 않도록 함

* design: 검색창 열고 닫힘 트렌지션 구현

* refactor: 웹 접근성 개선

웹 접근성을 위해 기존 div 태그를 form 태그로, 검색중이 아닐때 input disable 처리를 하였음.

* refactor: 불필요한 css함수 제거

* fix: disable 속성 제거 및 visibility 대체

디바이스 별로 검색중이 아닐때 보여줄 ui가 달라 disable 속성을 사용할 수 없었음.
트랜지션을 위해 input의 width를 0px로 변경하는 전략을 유지하면서
tab을 불가능하게 하여 접근성을 챙길 수 있는 visibility 속성을 적용하였음.

* refactor: jsx를 올바른 DOM 순서로 조정하여 tab 순서를 정렬함

* chore: 디렉터리 명 변경

artistSearch -> search 로 변경하여 추후 검색 기능 확장을 용이하게 하였음.

* feat: isSearching true 상태일 때 input focus 하는 effect 추가

비동기적으로 동작하는 setState 특성상 핸들러 함수 내부에서 focus() 하는 방식을 사용할 수 없었음.
때문에 상태와 DOM의 동기화를 위해 effect를 사용함.

또한 visibility의 transition은 애니메이션이 다 적용된 후에 변경하려던 속성을 가지므로
transition 속성을 width에만 적용하도록 변경하였음.

* refactor: 로직 커스텀 훅 분리

* refactor: DOM 순서에 맞게 스타일 코드 재배치

* feat: 검색 기능 관련 remote 함수 및 type 정의

* feat: 검색 기능 관련 msw 핸들러 및 fixture 구현

* feat: 검색 함수 적용 및 디바운스 처리

* refactor: msw fixture 데이터 추가

* refactor: 썸네일 컴포넌트 사이즈 추가

* refactor: 미리보기 가수 type에 id 필드 추가

* feat: 미리보기 시트 디자인 및 데이터 바인딩

* feat: useValidSearchParams 훅 구현

* feat: 검색 완료 페이지 추가 및 경로 추가

* feat: 검색 함수 구현 및 바인딩

* style: 함수 순서 변경

* feat: 가수 상세정보 페이지 추가 및 경로 추가

* refactor: useValidSearchParams return 값 객체로 개선

사용처에서 순서에 상관없이 사용할 수 있도록 개선

* refactor: 검색 완료 시 input blur 하도록 개선

* refactor: 변경된 api 명세 반영

* refactor: Artist -> singer로 변경

* refactor: Artist -> singer로 변경

* refactor: 핸들러 함수 useCallback 적용

* feat: 검색 미리보기 결과 클릭시 가수 상세페이지로 이동하는 기능 구현

input의 onBlur 이벤트에 우선순위가 있기 때문에 onMouseDown 이벤트 사용

* feat: 가수 상세페이지 remote 함수 구현

* refactor: 검색 결과 미리보기 컴포넌트 웹 접근성 개선
create portal 제거
검색 결과 item을 button으로 래핑하여 tabable 하도록 개선
잘못된 disabled 속성 hidden 으로 변경

* refactor: 구조분해 할당 적용

* feat: 스크롤 방지 effect 추가

* design: Header left 속성 명시

* config: storybook-addon-react-router-v6 설치

* fix: 스토리 정상 표시를 위한 provider, router 적용 및 viewport 정의

useFetch, useMutaion내에서 사용하는 context로 인해 스토리북 표시되지 않는 오류 해결

* refactor: 팝업 로그인 컨텍스트 내에  잘못 표시된 error 메세지 수정 및 prop 타입 변경

AuthProvider 하위에 단일 children을 받지 못하는 type 수정

* test: 서치 바 스토리 추가

단일 컴포넌트로 동작할 수 없는 특성 상 Header 컴포넌트로 대체

* refactor: handler 관리 방식 변경

스토리북에 핸들러를 가져오는 과정에서, 여러개의 핸들러를 개별로 가져와야 하는 불편함을 느낌
이에 모든 핸들러를 하나의 배열로 묶어 관리하도록 개선하였음

* config: 스토리북 msw 적용을 위한 패키지 설치

* feat: 스토리북에 msw 적용

service worker 파일 경로 설정 및 handler 적용

* fix: ci unused error fix

* refactor: 검색어 입력 없이 검색할 수 없도록 개선

 search 함수 실행되지 않도록 변경

* fix: 모바일 환경에서 검색버튼을 통한 검색이 되도록 수정

* chore: 오타 수정

* design: px 단위 제거

* chore: 컴포넌트, 스타일 컴포넌트 명 변경

Flex prefix 추가, 미리보기의 의미를 담아 컴포넌트 명 변경

* refactor: 아티스트 아이템 외의 빈곳 클릭시 검색 종료되는 문제 해결

1. 미리보기 시트에 tabIndex={-1} 부여
2. onBlur 핸들러의 검색 종료 제외 요소로 추가
3. FlexPreviewItem mouseDown 핸들러에 검색 종료 함수 추가

* feat: 백드롭 추가

* feat: esc 검색종료 함수 추가

* design: 검색창 expand 시 로고 완전히 덮히도록 수정

* feat: 백드롭 클릭 시 검색종료

* refactor: api url 변경 반영

* refactor: msw 핸들러 고도화 및 픽스쳐 데이터 추가

검색 완료 페이지에서 쓰이는 api 요청을 처리할 수 있도록 개선

* refactor: msw 핸들러 고도화 및 픽스쳐 데이터 수정

가수 상세 페이지에서 쓰이는 api 요청을 처리할 수 있도록 개선
  • Loading branch information
Creative-Lee authored Oct 17, 2023
1 parent b03ace9 commit 58dbb38
Show file tree
Hide file tree
Showing 29 changed files with 848 additions and 22 deletions.
4 changes: 4 additions & 0 deletions frontend/.storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const config: StorybookConfig = {
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'storybook-addon-react-router-v6',
],
framework: {
name: '@storybook/react-webpack5',
Expand All @@ -15,6 +16,9 @@ const config: StorybookConfig = {
docs: {
autodocs: true,
},

staticDirs: ['../public'],

webpackFinal: async (config) => {
if (config.resolve) {
config.resolve.plugins = [
Expand Down
85 changes: 81 additions & 4 deletions frontend/.storybook/preview.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,101 @@
import { initialize, mswLoader } from 'msw-storybook-addon';
import type { Preview } from '@storybook/react';
import GlobalStyles from '../src/shared/styles/GlobalStyles';
import { ThemeProvider } from 'styled-components';
import theme from '../src/shared/styles/theme';
import { BrowserRouter } from 'react-router-dom';
import AuthProvider from '@/features/auth/components/AuthProvider';
import LoginPopupProvider from '@/features/auth/hooks/LoginPopUpContext';
import handlers from '@/mocks/handlers';

const customViewport = {
xxl: {
name: 'xxl',
styles: {
width: '1440px',
height: '1080px',
},
},

xl: {
name: 'xl',
styles: {
width: '1280px',
height: '720px',
},
},

lg: {
name: 'lg',
styles: {
width: '1024px',
height: '720px',
},
},

md: {
name: 'md',
styles: {
width: '768px',
height: '1024px',
},
},

sm: {
name: 'sm',
styles: {
width: '640px',
height: '768px',
},
},

xs: {
name: 'xs',
styles: {
width: '420px',
height: '768px',
},
},

xxs: {
name: 'xxs',
styles: {
width: '380px',
height: '768px',
},
},
};

initialize();

const preview: Preview = {
parameters: {
actions: { argTypesRegex: '^on[A-Z].*' },
viewport: { viewports: customViewport, defaultViewport: 'xs' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
msw: {
handlers: [...handlers],
},
},
loaders: [mswLoader],

decorators: [
(Story) => (
<ThemeProvider theme={theme}>
<GlobalStyles />
<Story />
</ThemeProvider>
<AuthProvider>
<LoginPopupProvider>
<ThemeProvider theme={theme}>
<BrowserRouter>
<GlobalStyles />
<Story />
</BrowserRouter>
</ThemeProvider>
</LoginPopupProvider>
</AuthProvider>
),
],
};
Expand Down
50 changes: 50 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,12 @@
"jest": "^29.6.1",
"jest-environment-jsdom": "^29.6.1",
"msw": "^1.2.3",
"msw-storybook-addon": "^1.9.0",
"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",
"stylelint": "^15.10.2",
"stylelint-config-clean-order": "^5.0.1",
"ts-jest": "^29.1.1",
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/assets/icon/left-arrow.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions frontend/src/assets/icon/search.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion frontend/src/features/auth/components/AuthProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createContext, useContext, useMemo, useState } from 'react';
import accessTokenStorage from '@/shared/utils/accessTokenStorage';
import parseJWT from '../utils/parseJWT';
import type { PropsWithChildren } from 'react';

interface User {
memberId: number;
Expand All @@ -23,7 +24,7 @@ export const useAuthContext = () => {

const AuthContext = createContext<AuthContextProps | null>(null);

const AuthProvider = ({ children }: { children: React.ReactElement[] }) => {
const AuthProvider = ({ children }: PropsWithChildren) => {
const [accessToken, setAccessToken] = useState(accessTokenStorage.getToken() || '');

const user: User | null = useMemo(() => {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/features/auth/hooks/LoginPopUpContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const LoginPopUpContext = createContext<LoginPopUpContextProps | null>(null);
export const useLoginPopup = () => {
const contextValue = useContext(LoginPopUpContext);

if (contextValue === null) throw new Error('AuthContext가 null입니다.');
if (contextValue === null) throw new Error('LoginPopUpContext에 값이 제공되지 않았습니다.');

return contextValue;
};
Expand Down
15 changes: 15 additions & 0 deletions frontend/src/features/search/components/SearchBar.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Header from '@/shared/components/Layout/Header';
import SearchBar from './SearchBar';
import type { Meta, StoryObj } from '@storybook/react';

const meta = {
component: SearchBar,
title: 'SearchBar',
} satisfies Meta<typeof SearchBar>;

export default meta;
type Story = StoryObj<typeof SearchBar>;

export const Default: Story = {
render: () => <Header />,
};
Loading

0 comments on commit 58dbb38

Please sign in to comment.