Skip to content

Commit

Permalink
홈 > 검색창 및 최근 검색어 UI 구현 (#223)
Browse files Browse the repository at this point in the history
* 💄 #219 - SearchIcon 너비, 높이, 컬러 변경할 수 있도록 수정 및 적용

* ✨ #219 - 검색 헤더 UI 구현

* 🍱 #219 - search empty 이미지 추가

* ✨ #219 - NoSearchResults 컴포넌트 생성 및 적용

* ✨ #219 - 최근 검색어 UI 구현

* ♻️ #219 - 최근 검색어 컨테이너 분리

* ✨ #219 - react-hook-form 적용하여 검색어가 비어있는 경우 최근 검색어 컨테이너 노출

* 💄 #219 - CloseIcon 사용하는 곳에서 stroke 컬러 변경할 수 있도록 수정

* 💚 #219 - build 에러 해결

* ✨ #219 - 검색창 DeleteButton 클릭 시 입력된 문자열 삭제

* 🔥 #219 - 중복된 stroke 속성 제거
  • Loading branch information
Bori-github authored Apr 20, 2024
1 parent 82cc116 commit 5b29dbe
Show file tree
Hide file tree
Showing 12 changed files with 268 additions and 4 deletions.
Binary file added public/images/search/search_empty.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/assets/icons/close.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 src/assets/icons/search.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/components/layouts/header/HeaderLeft.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const HeaderLeft = ({ type, onClick }: HeaderLeftProps) => {
)}
{type === '닫기' && (
<Button type="button" onClick={onClick}>
<CloseIcon />
<CloseIcon width={24} height={24} />
</Button>
)}
</>
Expand Down
2 changes: 1 addition & 1 deletion src/components/layouts/header/HeaderRight.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const HeaderRight = ({
)}
{type === '검색' && (
<SearchLink href="/search">
<SearchIcon />
<SearchIcon width={24} height={24} />
</SearchLink>
)}
{(type === '등록' || type === '수정' || type === '저장') && (
Expand Down
34 changes: 34 additions & 0 deletions src/components/search/NoSearchResults.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import styled from '@emotion/styled';
import Image from 'next/image';

interface NoSearchResultsProps {
description: string;
}

export const NoSearchResults = ({ description }: NoSearchResultsProps) => {
return (
<Container>
<Image
src={'/images/search/search_empty.png'}
alt={description}
width={100}
height={100}
priority
/>
<Description>{description}</Description>
</Container>
);
};

const Container = styled.div`
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
padding: 44px 0;
`;

const Description = styled.p`
color: ${({ theme }) => theme.colors.gray_02};
${({ theme }) => theme.fonts.body_05}
`;
72 changes: 72 additions & 0 deletions src/components/search/RecentSearchContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import styled from '@emotion/styled';
import { NoSearchResults } from './NoSearchResults';
import { CloseIcon } from 'assets/icons';
import { theme } from 'styles';

// TODO: 목데이터 제거
const RECENT_SEARCHES_MOCKS = [
'hello',
'daily routine',
'dance',
'morning',
'movies',
'cakes',
'happy',
'computer',
];

export const RecentSearchContainer = () => {
// TODO: localStorage 최근 검색어 목록으로 변경
const isEmptyRecentSearches = RECENT_SEARCHES_MOCKS.length === 0;

return (
<Container>
<Title>최근 검색어</Title>
{isEmptyRecentSearches ? (
<NoSearchResults description="최근 검색어 내역이 없습니다." />
) : (
<RecentSearchList>
{RECENT_SEARCHES_MOCKS.map((recentSearch) => {
return (
<li key={recentSearch}>
<RecentSearchButton type="button">
{recentSearch}
<CloseIcon
width={16}
height={16}
stroke={theme.colors.gray_04}
/>
</RecentSearchButton>
</li>
);
})}
</RecentSearchList>
)}
</Container>
);
};

const Container = styled.div`
padding: 20px;
`;

const Title = styled.h2`
${({ theme }) => theme.fonts.headline_02}
`;

const RecentSearchList = styled.ul`
display: flex;
flex-direction: column;
gap: 10px;
padding-top: 18px;
`;

const RecentSearchButton = styled.button`
display: flex;
align-items: center;
gap: 12px;
padding: 14px 16px;
border-radius: 100px;
background-color: ${({ theme }) => theme.colors.bg_01};
${({ theme }) => theme.fonts.body_05}
`;
101 changes: 101 additions & 0 deletions src/components/search/SearchHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import styled from '@emotion/styled';
import Link from 'next/link';
import { useFormContext } from 'react-hook-form';
import type { SearchForm } from 'types/search';
import { DeleteIcon, SearchIcon } from 'assets/icons';
import { PAGE_PATH } from 'constants/common';
import { SVGVerticalAlignStyle, theme } from 'styles';

export const SearchHeader = () => {
const { register, setValue, watch } = useFormContext<SearchForm>();
const { searchKeyword } = watch();

const handleDeleteSearchKeyword = () => {
setValue('searchKeyword', '');
};

const handleChangeSearch = () => {
// TODO: 검색 기능 구현
console.log(searchKeyword);
};

return (
<HeaderLayout>
<SearchContainer>
<SearchLabel htmlFor="searchKeyword">
<SearchIcon width={20} height={20} stroke={theme.colors.primary_00} />
</SearchLabel>
<SearchInput
{...register('searchKeyword', {
required: true,
onChange: handleChangeSearch,
})}
type="search"
id="searchKeyword"
placeholder="검색어를 입력하세요."
/>
<DeleteButton
type="button"
isVisible={searchKeyword?.length > 0}
onClick={handleDeleteSearchKeyword}
>
<DeleteIcon />
</DeleteButton>
</SearchContainer>
<CancelLink href={PAGE_PATH.main}>취소</CancelLink>
</HeaderLayout>
);
};

const HeaderLayout = styled.header`
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
position: sticky;
top: 0;
right: 0;
left: 0;
height: 54px;
padding: 0 20px;
border-bottom: 1px solid ${({ theme }) => theme.colors.gray_06};
background: ${({ theme }) => theme.colors.white};
`;

const SearchContainer = styled.div`
flex: 1;
display: flex;
align-items: center;
gap: 12px;
height: 40px;
padding: 10px 12px;
border-radius: 10px;
background-color: ${({ theme }) => theme.colors.bg_02};
`;

const SearchLabel = styled.label`
${SVGVerticalAlignStyle}
`;

const SearchInput = styled.input`
flex: 1;
background-color: transparent;
color: ${({ theme }) => theme.colors.black};
${({ theme }) => theme.fonts.body_05}
&::placeholder {
color: ${({ theme }) => theme.colors.gray_02};
}
&:focus {
outline: 0;
}
`;

const DeleteButton = styled.button<{ isVisible: boolean }>`
opacity: ${({ isVisible }) => (isVisible ? 1 : 0)};
`;

const CancelLink = styled(Link)`
${({ theme }) => theme.fonts.body_05}
`;
3 changes: 3 additions & 0 deletions src/components/search/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './SearchHeader';
export * from './NoSearchResults';
export * from './RecentSearchContainer';
44 changes: 44 additions & 0 deletions src/pages/search.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import styled from '@emotion/styled';
import { FormProvider, useForm } from 'react-hook-form';
import type { NextPage } from 'next';
import type { SearchForm } from 'types/search';
import { Seo } from 'components/common';
import { RecentSearchContainer, SearchHeader } from 'components/search';

const SearchPage: NextPage = () => {
const methods = useForm<SearchForm>({ mode: 'onChange' });
const { watch } = methods;
const { searchKeyword } = watch();

const isShowRecentSearchResult =
searchKeyword === undefined || searchKeyword.trim().length === 0;

return (
<>
<Seo title={'검색 | a daily diary'} />
<Section>
<FormProvider {...methods}>
<SearchHeader />
{isShowRecentSearchResult ? (
<RecentSearchContainer />
) : (
<div>검색결과</div>
)}
</FormProvider>
</Section>
</>
);
};

export default SearchPage;

const Section = styled.section`
overflow-y: auto;
scrollbar-width: none;
-ms-overflow-style: none;
&::-webkit-scrollbar {
display: none;
}
`;
6 changes: 6 additions & 0 deletions src/styles/GlobalStyle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ const GlobalStyle = css`
-webkit-box-shadow: 0 0 0px 1000px #fff inset;
box-shadow: 0 0 0px 1000px #fff inset;
}
input::-webkit-search-decoration,
input::-webkit-search-cancel-button,
input::-webkit-search-results-button,
input::-webkit-search-results-decoration {
-webkit-appearance: none;
}
@media (prefers-color-scheme: dark) {
html {
Expand Down
3 changes: 3 additions & 0 deletions src/types/search.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface SearchForm {
searchKeyword: string;
}

0 comments on commit 5b29dbe

Please sign in to comment.