Skip to content

Commit

Permalink
Feat: 검색바 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
Nahyun-Kang committed Dec 15, 2024
1 parent d8fb58f commit 00d7fcc
Show file tree
Hide file tree
Showing 9 changed files with 246 additions and 20 deletions.
2 changes: 1 addition & 1 deletion src/app/(home)/_components/Header.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export const headerWrapper = style({
width: '100%',
height: '40px',
padding: '10px 16px',
margin: '0 0 12px',
margin: '0 0 16px',
});

export const entireWrapper = style({
Expand Down
4 changes: 2 additions & 2 deletions src/app/(home)/_components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Link from 'next/link';
import Image from 'next/image';
import { useQuery } from '@tanstack/react-query';

import SearchBarComponent from '@/components/SearchBar';
import SearchBarArea from './SearchBarArea';
import Modal from '@/components/Modal/Modal';
import LoginModal from '@/components/login/LoginModal';

Expand Down Expand Up @@ -59,7 +59,7 @@ function Header() {

return (
<header className={styles.headerWrapper}>
{isSearchBarOpened && <SearchBarComponent handleCancel={handleInactivateSearchBar} />}
{isSearchBarOpened && <SearchBarArea handleCancel={handleInactivateSearchBar} />}
{!isSearchBarOpened && (
<div className={styles.entireWrapper}>
<div className={styles.homeTitleContainer}>
Expand Down
35 changes: 35 additions & 0 deletions src/app/(home)/_components/SearchBarArea.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { style } from '@vanilla-extract/css';

export const wrapper = style({
width: '100%',

display: 'flex',
alignItems: 'center',
});

export const inputWrapper = style({
width: '100%',

display: 'flex',
alignItems: 'center',
});

export const input = style({
width: '100%',

fontSize: '1.6rem',
overflow: 'hidden',
'::placeholder': {
color: '#637587',
fontWeight: '400',
fontSize: '1.6rem',
},
});

export const cancelButton = style({
flexShrink: 0,
fontWeight: '400',
fontSize: '1.6rem',
letterSpacing: '-3%',
color: '#213752',
});
21 changes: 21 additions & 0 deletions src/app/(home)/_components/SearchBarArea.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import * as styles from './SearchBarArea.css';
import SearchBar from '@/components/SearchBar';

interface SearchBarProps {
handleCancel?: () => void;
}

function SearchBarArea({ handleCancel }: SearchBarProps) {
return (
<div className={styles.wrapper}>
<div className={styles.inputWrapper}>
<SearchBar />
</div>
<button className={styles.cancelButton} onClick={handleCancel}>
취소
</button>
</div>
);
}

export default SearchBarArea;
2 changes: 1 addition & 1 deletion src/app/search/_components/SearchBar.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { style } from '@vanilla-extract/css';

export const searchWrapper = style({
width: '100%',
padding: '0 16px',
padding: '0 16px 0 0',

display: 'flex',
flexDirection: 'column',
Expand Down
86 changes: 86 additions & 0 deletions src/components/SearchBar/KeywordArea.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { style, keyframes } from '@vanilla-extract/css';
import { vars } from '@/styles/__theme.css';

export const keywordWrapper = style({
width: '100%',

position: 'relative',

display: 'flex',
flexDirection: 'column',
gap: '1.6rem',

backgroundColor: vars.color.white,
borderRadius: '50px',
});

const moveInputRight = keyframes({
'0%': { transform: 'translateX(1%)' },
'100%': { transform: 'translateX(0)' },
});

const moveInputLeft = keyframes({
'0%': { transform: 'translateX(-1%)' },
'100%': { transform: 'translateX(0)' },
});

const moveIconRight = keyframes({
'0%': { transform: 'translateX(-100%)' },
'100%': { transform: 'translateX(0)' },
});

const moveIconLeft = keyframes({
'0%': { transform: 'translateX(100%)' },
'100%': { transform: 'translateX(0)' },
});

export const keywordInput = style({
padding: '0.5rem 1.5rem 0.5rem 4rem',

backgroundColor: 'transparent',

fontSize: '1.5rem',

zIndex: 2,
});

export const basicKeywordInput = style([
keywordInput,
{
padding: '1.1rem 4rem 1.1rem 4rem',
animation: `${moveInputLeft} 0.5s ease`,
},
]);

export const typedKeywordInput = style([
keywordInput,
{
padding: '1.1rem 4rem 1.1rem 1.5rem',
animation: `${moveInputRight} 0.5s ease`,
},
]);

export const searchIcon = style({
position: 'absolute',
top: '1.2rem',
zIndex: 2,
});

export const basicSearchIcon = style([
searchIcon,
{
left: '1.5rem',

animation: `${moveIconLeft} 0.2s ease-in-out`,
},
]);

export const typedSearchIcon = style([
searchIcon,
{
right: '1.5rem',

animation: `${moveIconRight} 0.2s ease-in-out`,
cursor: 'pointer',
},
]);
44 changes: 44 additions & 0 deletions src/components/SearchBar/KeywordArea.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { ChangeEventHandler, KeyboardEventHandler, MouseEventHandler, useEffect, useRef, useState } from 'react';
import { useSearchParams } from 'next/navigation';

import * as styles from './KeywordArea.css';

import SearchIcon from '/public/icons/search.svg';
import { useLanguage } from '@/store/useLanguage';
import { searchPlaceholer } from '@/lib/constants/placeholder';

interface searchKeywordAreaProps {
onKeyDown?: KeyboardEventHandler;
onInput: ChangeEventHandler<HTMLInputElement>;
onClick?: MouseEventHandler;
}

function KeywordArea({ onKeyDown, onInput, onClick }: searchKeywordAreaProps) {
const { language } = useLanguage();
const searchParams = useSearchParams();
const defaultKeyword: string = searchParams?.get('keyword') ?? '';

const inputRef = useRef<HTMLInputElement>(null);
const [inputHasValue, setInputHasValue] = useState<boolean>(false);

useEffect(() => {
setInputHasValue(!!inputRef.current?.value);
}, [inputRef.current?.value]);

return (
<div className={styles.keywordWrapper}>
<input
ref={inputRef}
className={`${inputHasValue ? styles.typedKeywordInput : styles.basicKeywordInput}`}
type="text"
placeholder={searchPlaceholer[language].keyword}
onKeyDown={onKeyDown}
onInput={onInput}
defaultValue={defaultKeyword}
/>
<SearchIcon onClick={onClick} className={`${inputHasValue ? styles.typedSearchIcon : styles.basicSearchIcon}`} />
</div>
);
}

export default KeywordArea;
70 changes: 55 additions & 15 deletions src/components/SearchBar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,62 @@
import * as styles from './SearchBar.css';
import SearchIcon from '/public/icons/ver3/search.svg';
'use client';

interface SearchBarProps {
handleCancel?: () => void;
}
import { ChangeEvent, useState, KeyboardEvent, MouseEvent } from 'react';
import { useRouter } from 'next/navigation';
import KeywordArea from './KeywordArea';

import * as styles from '@/app/search/_components/SearchBar.css';

function SearchBar() {
const router = useRouter();
const [keyword, setKeyword] = useState('');

const handleSearchClick = (e: MouseEvent) => {
router.push(`/search?keyword=${keyword}`);
};

const handleEnterKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
// keyword가 없는 경우, 초기 category값을 '전체' 로 설정
router.push(`/search?keyword=${keyword}&category=entire`);
}
};

const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
setKeyword(e.target.value);
};

const handelCategoryClick = (e: MouseEvent<HTMLDivElement>) => {
router.push(`/search?category=${e.currentTarget?.dataset?.value}`);
};

function SearchBarComponent({ handleCancel }: SearchBarProps) {
return (
<div className={styles.wrapper}>
<div className={styles.inputWrapper}>
<SearchIcon />
<input placeholder="리스트 또는 리스터를 검색해 보세요" className={styles.input} />
</div>
<button className={styles.cancelButton} onClick={handleCancel}>
취소
</button>
<div className={styles.searchWrapper}>
<KeywordArea onClick={handleSearchClick} onKeyDown={handleEnterKeyDown} onInput={handleInputChange} />
</div>
);
}

export default SearchBarComponent;
export default SearchBar;

// import * as styles from './SearchBar.css';
// import SearchIcon from '/public/icons/ver3/search.svg';

// interface SearchBarProps {
// handleCancel?: () => void;
// }

// function SearchBarComponent({ handleCancel }: SearchBarProps) {
// return (
// <div className={styles.wrapper}>
// <div className={styles.inputWrapper}>
// <SearchIcon />
// <input placeholder="리스트 또는 리스터를 검색해 보세요" className={styles.input} />
// </div>
// <button className={styles.cancelButton} onClick={handleCancel}>
// 취소
// </button>
// </div>
// );
// }

// export default SearchBarComponent;
2 changes: 1 addition & 1 deletion src/lib/constants/placeholder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const profilePlaceholder = {

export const searchPlaceholer = {
ko: {
keyword: '검색어를 입력해주세요.',
keyword: '리스트 혹은 리스터를 검색해 보세요',
},
en: {
keyword: 'Please enter a search term.',
Expand Down

0 comments on commit 00d7fcc

Please sign in to comment.