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

[FE] 사이드바 및 모달 사용성 개선 #139

Merged
merged 9 commits into from
Jul 31, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const App = () => {
return (
<PageLayout>
{isSidebarModalOpen && (
<SideModal isSidebarHidden={isSidebarHidden}>
<SideModal isSidebarHidden={isSidebarHidden} closeModal={closeSidebar}>
<Sidebar closeSidebar={closeSidebar} />
</SideModal>
)}
Expand Down
15 changes: 12 additions & 3 deletions frontend/src/components/common/modals/ModalBackground/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import React from 'react';
import React, { PropsWithChildren, useRef } from 'react';

import { useModalClose } from '@/hooks';

import * as S from './styles';

const ModalBackground = ({ children }: React.PropsWithChildren) => {
return <S.ModalBackground>{children}</S.ModalBackground>;
interface ModalBackgroundProps {
closeModal: () => void;
}

const ModalBackground: React.FC<PropsWithChildren<ModalBackgroundProps>> = ({ children, closeModal }) => {
const modalBackgroundRef = useRef<HTMLDivElement>(null);
useModalClose(closeModal, modalBackgroundRef);

return <S.ModalBackground ref={modalBackgroundRef}>{children}</S.ModalBackground>;
};

export default ModalBackground;
5 changes: 3 additions & 2 deletions frontend/src/components/common/modals/SideModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ import * as S from './styles';

interface SideModalProps {
isSidebarHidden: boolean;
closeModal: () => void;
}

const SideModal: React.FC<PropsWithChildren<SideModalProps>> = ({ children: Sidebar, isSidebarHidden }) => {
const SideModal: React.FC<PropsWithChildren<SideModalProps>> = ({ children: Sidebar, isSidebarHidden, closeModal }) => {
return (
<ModalPortal id="sidebarModal-portal">
<ModalBackground>
<ModalBackground closeModal={closeModal}>
<S.SidebarWrapper $isSidebarHidden={isSidebarHidden}>{Sidebar}</S.SidebarWrapper>
</ModalBackground>
</ModalPortal>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/common/modals/SideModal/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ export const SidebarWrapper = styled.div<SidebarWrapperProps>`
position: absolute;
top: 0;
left: ${(props) => (props.$isSidebarHidden ? '-100%' : 0)};
transition: left 1s ease-in-out;
transition: left 0.2s ease-in-out;
`;
2 changes: 1 addition & 1 deletion frontend/src/components/layouts/Sidebar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const Sidebar = ({ closeSidebar }: SidebarProps) => {
<S.MenuList>
{menuItems.map((item) => (
<S.MenuItem key={item.path} selected={location.pathname === item.path}>
<Link to={item.path}>{item.label}</Link>
<Link to={item.path} onClick={closeSidebar}>{item.label}</Link>
</S.MenuItem>
))}
</S.MenuList>
Expand Down
1 change: 1 addition & 0 deletions frontend/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { default as useSidebar } from './useSidebar';
export { default as useModalClose } from './useModalClose';
52 changes: 52 additions & 0 deletions frontend/src/hooks/useModalClose.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { useEffect, RefObject } from 'react';

const useModalClose = (closeModal: () => void, modalBackgroundRef: RefObject<HTMLElement>) => {
const isNodeElement = (element: EventTarget | null): element is Node => {
return element instanceof Node;
};

const isModalBackground = (targetElement: Node | null) => {
return modalBackgroundRef.current ? modalBackgroundRef.current === targetElement : false;
};

const isHTMLElement = (element: Element | null): element is HTMLElement => {
return element instanceof HTMLElement;
};

// NOTE: esc 키를 눌렀을 때 햄버거 버튼이 포커싱되는 문제 해결을 위한 함수
const blurFocusing = () => {
const activeElement = document.activeElement;

if (!isHTMLElement(activeElement)) return;
if (typeof activeElement.blur === 'function') activeElement.blur();
};

const handleBackgroundClick = (event: MouseEvent) => {
if (isNodeElement(event.target) && isModalBackground(event.target)) {
closeModal();
}
};

const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
event.preventDefault();

blurFocusing();
closeModal();
}
};

useEffect(() => {
const modalBackgroundElement = modalBackgroundRef.current;

modalBackgroundElement?.addEventListener('click', handleBackgroundClick);
document.addEventListener('keydown', handleKeyDown);

return () => {
modalBackgroundElement?.removeEventListener('click', handleBackgroundClick);
Comment on lines +40 to +46
Copy link
Contributor

Choose a reason for hiding this comment

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

추후 JS element 오류 처리 함수가 완성되면 적용 가능할 것 같은데, 반복되는 optional chaining을 줄이기 위해, modalBackgroundElement가 없는 경우 early return을 적용해보는 것은 어떨까요? 사소한 부분입니다 :)

const modalBackgroundElement = modalBackgroundRef.current;
if(!modalBackgroundElement) return;

modalBackgroundElement.addEventListener( ... )
// ...

return () => {
  modalBackgroundElement.removeEventListener( ... )
// ...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

요 부분은 해당 유틸 함수가 만들어지면 적용할게요 ㅎㅎ 꼼꼼하게 봐 줘서 고마워요!

document.removeEventListener('keydown', handleKeyDown);
};
}, [closeModal, modalBackgroundRef]);
};

export default useModalClose;
8 changes: 3 additions & 5 deletions frontend/src/hooks/useSidebar.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
import { useState } from 'react';

const useSidebar = () => {
const CLOSE_TIME = 1000;
const OPEN_TIME = 0.5;
const OPEN_TIME = 0.2;
Copy link
Contributor

Choose a reason for hiding this comment

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

사이드 바 모달이 열릴 때, 백그라운드가 먼저 나오고 그 이후에 모달이 나오는데 그 텀이 길다 보니까 답답하다는 느낌을 받았어요. 센스있게 setTimeout 시간을 줄여주셨군요!! 👍👍👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

useSidebar는 제가 작업한 게 아니랍니다...ㅋㅋㅋㅋㅋ 전 추가적으로 닫는 기능을 훅으로 분리했어요~


const [isSidebarModalOpen, setIsSidebarModalOpen] = useState(false);
const [isSidebarHidden, setIsSidebarHidden] = useState(true);

const closeSidebar = () => {
setIsSidebarModalOpen(false);
setIsSidebarHidden(true);
setTimeout(() => {
setIsSidebarModalOpen(false);
}, CLOSE_TIME);
};

const openSidebar = () => {
setIsSidebarModalOpen(true);

setTimeout(() => {
setIsSidebarHidden(false);
}, OPEN_TIME);
Expand Down