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: 검색창 애니메이션 적용 #9

Open
wants to merge 17 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"@tanstack/react-query": "^5.59.8",
"@tanstack/react-query-devtools": "^5.59.8",
"axios": "^1.7.7",
"es-hangul": "^2.2.1",
"jotai": "^2.10.0",
"overlay-kit": "^1.4.1",
"react": "^18.3.1",
Expand Down
65 changes: 65 additions & 0 deletions src/features/search/data/professors.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
[
{
"name": "권순일",
"labLocation": "대양AI 624호",
"phoneNumber": "02-3408-3847",
"email": "[email protected]",
"department": "소프트웨어학과"
},
{
"name": "백성욱",
"labLocation": "대양AI 622호",
"phoneNumber": "02-3408-3797",
"email": "[email protected]",
"department": "소프트웨어학과"
},
{
"name": "이종원",
"labLocation": "대양AI 619호",
"phoneNumber": "02-3408-3798",
"email": "[email protected]",
"department": "소프트웨어학과"
},
{
"name": "송오영",
"labLocation": "대양AI 625호",
"phoneNumber": "02-3408-3830",
"email": "[email protected]",
"department": "소프트웨어학과"
},
{
"name": "최준연",
"labLocation": "대양AI 620호",
"phoneNumber": "02-3408-3887",
"email": "[email protected]",
"department": "소프트웨어학과"
},
{
"name": "박상일",
"labLocation": "대양AI 626호",
"phoneNumber": "02-3408-3832",
"email": "[email protected]",
"department": "소프트웨어학과"
},
{
"name": "변재욱",
"labLocation": "대양AI 604호",
"phoneNumber": "02-3408-1847",
"email": "[email protected]",
"department": "소프트웨어학과"
},
{
"name": "이은상",
"labLocation": "대양AI 621호",
"phoneNumber": "02-3408-2975",
"email": "[email protected]",
"department": "소프트웨어학과"
},
{
"name": "정승화",
"labLocation": "대양AI 623호",
"phoneNumber": "02-3408-3795",
"email": "[email protected]",
"department": "소프트웨어학과"
}
]
7 changes: 7 additions & 0 deletions src/features/search/models/Professor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface Professor {
name: string;
labLocation: string;
phoneNumber: string;
email: string;
department: string;
}
37 changes: 37 additions & 0 deletions src/features/search/ui/components/ModalSearch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { overlay } from 'overlay-kit';

Check warning on line 1 in src/features/search/ui/components/ModalSearch.tsx

View workflow job for this annotation

GitHub Actions / lint 체크

'overlay' is defined but never used

Check warning on line 1 in src/features/search/ui/components/ModalSearch.tsx

View workflow job for this annotation

GitHub Actions / lint 체크

'overlay' is defined but never used
import React from 'react';
import styled from 'styled-components';

interface ModalSearchProps {
open: boolean;
onClose: () => void;
onExit: () => void;
Anhye0n marked this conversation as resolved.
Show resolved Hide resolved
}

const ModalSearch: React.FC<ModalSearchProps> = ({ open, onClose, onExit }) => {

Check warning on line 11 in src/features/search/ui/components/ModalSearch.tsx

View workflow job for this annotation

GitHub Actions / lint 체크

'open' is defined but never used

Check warning on line 11 in src/features/search/ui/components/ModalSearch.tsx

View workflow job for this annotation

GitHub Actions / lint 체크

'open' is defined but never used

Check warning on line 11 in src/features/search/ui/components/ModalSearch.tsx

View workflow job for this annotation

GitHub Actions / lint 체크

'onClose' is defined but never used

Check warning on line 11 in src/features/search/ui/components/ModalSearch.tsx

View workflow job for this annotation

GitHub Actions / lint 체크

'onClose' is defined but never used
Anhye0n marked this conversation as resolved.
Show resolved Hide resolved
return (
<>
<ModalBackground>
<button
type='button'
onClick={() => {
onExit();
}}
>
asdf
</button>
</ModalBackground>
</>
);
};

export default ModalSearch;

const ModalBackground = styled.div`
background: rgba(0, 0, 0, 0.5);
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
`;
160 changes: 150 additions & 10 deletions src/features/search/ui/components/SearchInput.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,169 @@
import { useEffect, useState } from 'react';
import styled from 'styled-components';
import { Professor } from '@semo-client/features/search/models/Professor.ts';
import { SearchResults } from '@semo-client/features/search/ui/components/SearchResults.tsx';
import SearchIcon from '@semo-client/ui/assets/icons/Icon';
import searchPalette from '@semo-client/ui/styles/pallete/searchPalette.ts';
Anhye0n marked this conversation as resolved.
Show resolved Hide resolved

export const SearchInput = () => {
return <StyledInput type='text' placeholder='무엇이든 검색해 보세요...' />;
interface SearchInputProps {
query: string;
setQuery: (query: string) => void;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
onSubmit: () => void;
results: Professor[];
Copy link
Contributor

@GHooN99 GHooN99 Nov 11, 2024

Choose a reason for hiding this comment

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

results 는 input 만의 역할에 벗어난 데이터,
query는 input element의 일반적인 역할의 이름인 value 로
setQuery는 input 을 초기화 하는 용도로만 사용되는데 이걸 덜어낼 방향을 생각해보시는 것도.. 굳이 필요한 props 인가? 구현(동작)을 위해 껴넣어진 props 인가?
onSubmit 은 input레벨이 아니라 상위에서 처리해야 할 관심인 것 같아요

인풋의 역할은 사용자의 입력을 받아, 그 값을 상태로 관리해주고, 추가적으로 인풋이 오버레이 형태니 그것의 열고 닫힘을 관리해주는 것까지로 한정지을 수도 있겠네요

검색어를 submit 하고 결과를 찾아 result 를 보여주는 건 input 의 역할에서 벗어난 듯해요
이것을 생각하면서 props 디자인을 해보는 것도 괜찮을듯함다

}

export const SearchInput: React.FC<SearchInputProps> = ({
query,
setQuery,
onChange,
onSubmit,
results,
}) => {
const [isExpanded, setIsExpanded] = useState(false);
const [isOverlayVisible, setIsOverlayVisible] = useState(false);

const handleExpand = () => {
setIsExpanded(true);
setIsOverlayVisible(true);
};

const handleCollapse = () => {
setIsExpanded(false);
};

const handleKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
onSubmit();
handleCollapse();
}
};
Anhye0n marked this conversation as resolved.
Show resolved Hide resolved

useEffect(() => {
if (!isExpanded) {
// 오버레이를 애니메이션 후에 숨김
const timeoutId = setTimeout(() => {
setIsOverlayVisible(false);
}, 300); // 트랜지션 시간과 동일하게 설정

// 검색창 초기화
setQuery('');

return () => clearTimeout(timeoutId);
} else {
setIsOverlayVisible(true);
}
Anhye0n marked this conversation as resolved.
Show resolved Hide resolved
}, [isExpanded]);

Check warning on line 56 in src/features/search/ui/components/SearchInput.tsx

View workflow job for this annotation

GitHub Actions / lint 체크

React Hook useEffect has a missing dependency: 'setQuery'. Either include it or remove the dependency array. If 'setQuery' changes too often, find the parent component that defines it and wrap that definition in useCallback

return (
<>
<SearchInputWrapper isExpanded={isExpanded}>
<InputContainer isExpanded={isExpanded}>
<SearchIconWrapper>
<SearchIcon
width={24}
height={24}
fill={searchPalette.blackGray[50]}
/>
</SearchIconWrapper>
<StyledInput
type='text'
placeholder='무엇이든 검색해 보세요...'
value={query}
onChange={onChange}
onKeyPress={handleKeyPress}
onClick={e => {
e.stopPropagation();
handleExpand();
}}
isExpanded={isExpanded}
autoFocus={isExpanded}
/>
</InputContainer>
{results.length != 0 && (
<SearchResults value={query} results={results} />
)}
</SearchInputWrapper>
Copy link
Contributor

@GHooN99 GHooN99 Nov 11, 2024

Choose a reason for hiding this comment

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

찾는 Input UI 와 Input 에 따른 결과 Result UI 는 Input 내에 있기보단 동일 레벨에 있어야 할 것 같아요
Input UI 가 결과 UI 를 알고 있는 구조가 자연스럽진 않아서요

Result UI 의 변경이 Input 에 영향을 끼치게 됨

to-be

<Search>
  // 동일 뎁스 
  <SearchInput /> 
  <SerachResult />
</Search> 


{isOverlayVisible && (
<Overlay isExpanded={isExpanded} onClick={handleCollapse} />
)}
</>
);
};

const StyledInput = styled.input`
const SearchInputWrapper = styled.div<{ isExpanded: boolean }>`
Copy link
Contributor

Choose a reason for hiding this comment

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

interface Expandable {
  isExpaned : boolean
}

interface SearchInputStyledProps extends ExpandableProps { 
 // additional styled props
}
styled.div<SearchInputStyledProps>

이 파일 내부에 만들고 재사용하는 방법은.. 선택

position: absolute;
top: ${({ isExpanded }) => (isExpanded ? '40%' : '65%')};
left: 50%;
transform: translate(-50%, -50%);
z-index: 1000;
width: ${({ isExpanded }) => (isExpanded ? '50%' : '500px')};
transition: 0.3s ease-in-out;
`;

const InputContainer = styled.div<{ isExpanded: boolean }>`
display: flex;
align-items: center;
width: 100%;
height: 54px;
border-radius: 8px;
border-radius: ${({ isExpanded }) =>
isExpanded ? '8px 8px 0 0' : '8px'}; /* 조건부 border-radius */
background-color: ${({ isExpanded }) =>
isExpanded ? searchPalette.blackGray[90] : searchPalette.blackGray[70]};
border: 1px solid ${searchPalette.whiteGray[30]};
Anhye0n marked this conversation as resolved.
Show resolved Hide resolved
padding: 0 14px;
gap: 10px;
font-size: 20px;
color: #fff;

&:focus-within {
outline: none;
border-color: ${searchPalette.whiteGray[50]};
background: ${({ isExpanded }) =>
isExpanded ? searchPalette.whiteGray[70] : '#000000'};
}

/* 애니메이션 효과 */
transition: all 0.3s ease-in-out;
`;

background-color: rgba(0, 0, 0, 0.7);
border: 1px solid rgba(255, 255, 255, 0.3);
padding: 10px 14px;
const SearchIconWrapper = styled.div`
display: flex;
gap: 10px;
align-items: center;
justify-content: center;
transition: all 0.3s ease-in-out;
`;

const StyledInput = styled.input<{ isExpanded: boolean }>`
flex: 1;
height: 100%;
border: none;
background: transparent;
color: ${({ isExpanded }) =>
isExpanded ? searchPalette.black : searchPalette.white};
font-size: 20px;
color: #fff;
position: relative;
z-index: 2;

&::placeholder {
color: rgba(255, 255, 255, 0.5);
color: ${({ isExpanded }) =>
isExpanded ? '#000' : searchPalette.whiteGray[50]};
}

&:focus {
outline: none;
}
`;

const Overlay = styled.div<{ isExpanded: boolean }>`
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: ${searchPalette.blackGray[90]};
z-index: 999;
opacity: ${({ isExpanded }) => (isExpanded ? 1 : 0)};
transition: opacity 0.3s ease-in-out;
`;
Loading
Loading