Skip to content

Commit

Permalink
[FE] [Fix, Feature] 최근검색어 삭제기능, hover 개선 (#128)
Browse files Browse the repository at this point in the history
yeynii authored Dec 13, 2022
1 parent b969cda commit 6febd6b
Showing 5 changed files with 55 additions and 15 deletions.
5 changes: 3 additions & 2 deletions frontend/src/components/IconButton.tsx
Original file line number Diff line number Diff line change
@@ -3,12 +3,13 @@ import styled from 'styled-components';
interface IProps {
icon: JSX.Element;
onClick?: React.MouseEventHandler;
onMouseDown?: React.MouseEventHandler;
'aria-label': string;
}

const IconButton = ({ icon, onClick, ...rest }: IProps) => {
const IconButton = ({ icon, ...rest }: IProps) => {
return (
<Button type="button" onClick={onClick} {...rest}>
<Button type="button" {...rest}>
{icon}
</Button>
);
31 changes: 30 additions & 1 deletion frontend/src/components/search/RecentKeywordsList.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { ClockIcon } from '@/icons';
import { IconButton } from '@/components';
import { ClockIcon, XIcon } from '@/icons';
import { setLocalStorage } from '@/utils/storage';
import { isEmpty } from 'lodash-es';
import { Dispatch, SetStateAction, useEffect } from 'react';
import styled from 'styled-components';
@@ -8,14 +10,23 @@ interface RecentKeywordsListProps {
hoverdIndex: number;
handleMouseDown: (prop: string) => void;
setHoveredIndex: Dispatch<SetStateAction<number>>;
initializeRecentKeywords: () => void;
}

const RecentKeywordsList = ({
recentKeywords,
hoverdIndex,
handleMouseDown,
setHoveredIndex,
initializeRecentKeywords,
}: RecentKeywordsListProps) => {
const handleRecentKeywordRemove = (e: React.MouseEvent, keyword: string) => {
e.preventDefault();
e.stopPropagation();
setLocalStorage('recentKeywords', Array.from([...recentKeywords.filter((v) => v !== keyword)]));
initializeRecentKeywords();
};

useEffect(() => {
setHoveredIndex(-1);
}, [setHoveredIndex]);
@@ -32,6 +43,11 @@ const RecentKeywordsList = ({
>
<ClockIcon />
{keyword}
<DeleteButton
icon={<XIcon />}
onMouseDown={(e) => handleRecentKeywordRemove(e, keyword)}
aria-label="삭제"
/>
</Keyword>
))
) : (
@@ -61,4 +77,17 @@ const NoResult = styled.div`
overflow: hidden;
`;

const DeleteButton = styled(IconButton)`
margin-left: auto;
width: 20px;
height: 20px;
display: flex;
justify-content: center;
align-items: center;
:hover {
background-color: ${({ theme }) => theme.COLOR.offWhite};
border-radius: 50%;
}
`;

export default RecentKeywordsList;
29 changes: 18 additions & 11 deletions frontend/src/components/search/Search.tsx
Original file line number Diff line number Diff line change
@@ -65,13 +65,13 @@ const Search = ({ initialKeyword = '' }: SearchProps) => {
[navigate],
);

const getRecentKeywordsFromLocalStorage = useCallback(() => {
const getRecentKeywords = () => {
const result = getLocalStorage('recentKeywords');
if (!Array.isArray(result)) {
return [];
}
return result;
}, []);
};

// 논문 상세정보 페이지로 이동
const goToDetailPage = (doi: string, state?: { initialData: IPaperDetail }) => {
@@ -85,24 +85,26 @@ const Search = ({ initialKeyword = '' }: SearchProps) => {

// localStorage에서 가져온 recent keywords를 최근에 검색한 순서대로 set
const handleInputFocus = () => {
const recentKeywords = getRecentKeywordsFromLocalStorage();
setRecentKeywords(recentKeywords.reverse());
setIsFocused(true);
};

const handleInputBlur = () => {
setIsFocused(false);
};

const initializeRecentKeywords = useCallback(() => {
const recentKeywords = getRecentKeywords();
setRecentKeywords(recentKeywords);
}, []);

// localStorage에 최근 검색어를 중복없이 최대 5개까지 저장 후 search-list로 이동
const handleSearchButtonClick = async (newKeyword: string) => {
if (!newKeyword) return;
setKeyword(newKeyword);
const recentKeywords = getRecentKeywordsFromLocalStorage();
const recentSet = new Set(recentKeywords);
recentSet.delete(newKeyword);
recentSet.add(newKeyword);
setLocalStorage('recentKeywords', Array.from(recentSet).slice(-5));
setLocalStorage(
'recentKeywords',
Array.from([newKeyword, ...recentKeywords.filter((keyword) => keyword !== newKeyword)]).slice(0, 5),
);

// DOI 형식의 input이 들어온 경우
if (isDoiFormat(newKeyword)) {
@@ -139,10 +141,10 @@ const Search = ({ initialKeyword = '' }: SearchProps) => {

switch (e.code) {
case 'ArrowDown':
setHoveredIndex((prev) => (prev + 1) % length);
setHoveredIndex((prev) => (prev + 1 > length - 1 ? -1 : prev + 1));
break;
case 'ArrowUp':
setHoveredIndex((prev) => (prev - 1 < 0 ? length - 1 : (prev - 1) % length));
setHoveredIndex((prev) => (prev - 1 < -1 ? length - 1 : prev - 1));
break;
case 'Enter':
handleEnterKeyDown();
@@ -167,6 +169,7 @@ const Search = ({ initialKeyword = '' }: SearchProps) => {
hoverdIndex={hoverdIndex}
handleMouseDown={handleSearchButtonClick}
setHoveredIndex={setHoveredIndex}
initializeRecentKeywords={initializeRecentKeywords}
/>
),
[DROPDOWN_TYPE.LOADING]: <MoonLoader />,
@@ -178,6 +181,10 @@ const Search = ({ initialKeyword = '' }: SearchProps) => {
setKeyword(initialKeyword);
}, [initialKeyword]);

useEffect(() => {
initializeRecentKeywords();
}, [initializeRecentKeywords]);

return (
<Container>
<SearchBox>
4 changes: 3 additions & 1 deletion frontend/src/icons/XIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import theme from '@/style/theme';

const XIcon = () => {
return (
<svg xmlns="http://www.w3.org/2000/svg" fill="#000000" width="15" height="15" viewBox="0 0 320 512">
<svg xmlns="http://www.w3.org/2000/svg" fill={theme.COLOR.gray3} width="15" height="15" viewBox="0 0 320 512">
<path d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z" />
</svg>
);
1 change: 1 addition & 0 deletions frontend/src/icons/index.ts
Original file line number Diff line number Diff line change
@@ -5,3 +5,4 @@ export { default as GithubLogoIcon } from './GithubLogoIcon';
export { default as LogoIcon } from './LogoIcon';
export { default as MagnifyingGlassIcon } from './MagnifyingGlassIcon';
export { default as PreviousButtonIcon } from './PreviousButtonIcon';
export { default as XIcon } from './XIcon';

0 comments on commit 6febd6b

Please sign in to comment.