From 05ba6c4f0641b56979015db49ed78fcfb4810330 Mon Sep 17 00:00:00 2001 From: river <130737187+0jenn0@users.noreply.github.com> Date: Tue, 22 Oct 2024 20:08:05 +0900 Subject: [PATCH 01/14] =?UTF-8?q?style(SingleSelectionTagModalBottomSheet)?= =?UTF-8?q?:=20=EB=B0=94=ED=85=80=20=EC=8B=9C=ED=8A=B8=20css=20=EA=B9=A8?= =?UTF-8?q?=EC=A7=80=EB=8D=98=EA=B2=83=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SingleSelectionTagModalBottomSheet.styled.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/components/common/Modal/SingleSelectionTagModalBottomSheet/SingleSelectionTagModalBottomSheet.styled.ts b/frontend/src/components/common/Modal/SingleSelectionTagModalBottomSheet/SingleSelectionTagModalBottomSheet.styled.ts index cb5768d52..e982403f6 100644 --- a/frontend/src/components/common/Modal/SingleSelectionTagModalBottomSheet/SingleSelectionTagModalBottomSheet.styled.ts +++ b/frontend/src/components/common/Modal/SingleSelectionTagModalBottomSheet/SingleSelectionTagModalBottomSheet.styled.ts @@ -16,4 +16,5 @@ export const HandleBar = styled.div` export const modalBodyStyle = css` align-items: flex-start; gap: ${theme.spacing.l}; + padding-bottom: ${theme.spacing.l}; `; From 11af618c390e6253f88f57b4a72ed73037e40729 Mon Sep 17 00:00:00 2001 From: river <130737187+0jenn0@users.noreply.github.com> Date: Wed, 23 Oct 2024 01:11:23 +0900 Subject: [PATCH 02/14] =?UTF-8?q?feat(useUnmountAnimation):=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=96=B8=EB=A7=88=EC=9A=B4?= =?UTF-8?q?=ED=8A=B8=EC=8B=9C=20=EC=95=A0=EB=8B=88=EB=A9=94=EC=9D=B4?= =?UTF-8?q?=EC=85=98=20=EC=9E=AC=EC=83=9D=EB=90=98=EB=8A=94=20=ED=9B=85=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/hooks/useUnmountAnimation.ts | 26 +++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 frontend/src/hooks/useUnmountAnimation.ts diff --git a/frontend/src/hooks/useUnmountAnimation.ts b/frontend/src/hooks/useUnmountAnimation.ts new file mode 100644 index 000000000..1c6ab08c3 --- /dev/null +++ b/frontend/src/hooks/useUnmountAnimation.ts @@ -0,0 +1,26 @@ +import { useEffect, useState } from "react"; + +interface useUnmountAnimationProps { + isOpen: boolean; + animationDuration?: number; +} + +const useUnmountAnimation = ({ isOpen, animationDuration = 300 }: useUnmountAnimationProps) => { + const [shouldRender, setShouldRender] = useState(false); + + useEffect(() => { + if (isOpen) { + setShouldRender(true); + } else { + const timer = setTimeout(() => { + setShouldRender(false); + }, animationDuration); + + return () => clearTimeout(timer); + } + }, [isOpen, animationDuration]); + + return { shouldRender }; +}; + +export default useUnmountAnimation; From c0cba7d9542166e6a34bc4a7b10955c231c7cb0f Mon Sep 17 00:00:00 2001 From: river <130737187+0jenn0@users.noreply.github.com> Date: Wed, 23 Oct 2024 01:15:24 +0900 Subject: [PATCH 03/14] =?UTF-8?q?feat(MainPage):=20=EC=A0=95=EB=A0=AC,?= =?UTF-8?q?=ED=95=84=ED=84=B0=20=EB=AA=A8=EB=8B=AC=EC=97=90=20useUnmountAn?= =?UTF-8?q?imation=20=ED=9B=85=20=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/pages/main/MainPage.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/pages/main/MainPage.tsx b/frontend/src/components/pages/main/MainPage.tsx index df648abca..017bea932 100644 --- a/frontend/src/components/pages/main/MainPage.tsx +++ b/frontend/src/components/pages/main/MainPage.tsx @@ -19,6 +19,7 @@ import useKeyDown from "@hooks/useKeyDown/useKeyDown"; import useMultiSelectionTag from "@hooks/useMultiSelectionTag"; import useSingleSelectionTag from "@hooks/useSingleSelectionTag"; import useTravelogueCardFocus from "@hooks/useTravelogueCardFocus"; +import useUnmountAnimation from "@hooks/useUnmountAnimation"; import { ERROR_MESSAGE_MAP } from "@constants/errorMessage"; import { FORM_VALIDATIONS_MAP } from "@constants/formValidation"; @@ -114,6 +115,14 @@ const MainPage = () => { const cardRefs = useTravelogueCardFocus(isFetchingNextPage); + const { shouldRender: shouldSortingRender } = useUnmountAnimation({ + isOpen: sorting.isModalOpen, + }); + + const { shouldRender: shouldFilterRender } = useUnmountAnimation({ + isOpen: travelPeriod.isModalOpen, + }); + if (isPaused) { alert(ERROR_MESSAGE_MAP.network); } @@ -271,7 +280,7 @@ const MainPage = () => { ? "여행기 정렬 메뉴가 열렸습니다." : "여행기 정렬 메뉴가 닫혔습니다."} - {sorting.isModalOpen && ( + {shouldSortingRender && ( { ? "여행기 필터 메뉴가 열렸습니다." : "여행기 필터 메뉴가 닫혔습니다."} - {travelPeriod.isModalOpen && ( + {shouldFilterRender && ( Date: Wed, 23 Oct 2024 01:15:57 +0900 Subject: [PATCH 04/14] =?UTF-8?q?feat(Drawer):=20Drawer=EC=97=90=20useUnmo?= =?UTF-8?q?untAnimation=20=ED=9B=85=20=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/common/Drawer/Drawer.styled.ts | 49 +++++++++++++------ .../src/components/common/Drawer/Drawer.tsx | 8 ++- 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/frontend/src/components/common/Drawer/Drawer.styled.ts b/frontend/src/components/common/Drawer/Drawer.styled.ts index de7021e90..ba4b2d49a 100644 --- a/frontend/src/components/common/Drawer/Drawer.styled.ts +++ b/frontend/src/components/common/Drawer/Drawer.styled.ts @@ -1,19 +1,7 @@ +import { keyframes } from "@emotion/react"; import styled from "@emotion/styled"; -export const DrawerContainer = styled.div<{ isOpen: boolean }>` - display: flex; - flex-direction: column; - position: fixed; - top: 0; - right: ${({ isOpen }) => (isOpen ? "0" : "-210px")}; - z-index: ${({ theme }) => theme.zIndex.drawer}; - width: 210px; - height: 100%; - - background-color: #fff; - - transition: right 0.3s ease-in-out; -`; +import { PRIMITIVE_COLORS } from "@styles/tokens"; export const Overlay = styled.div<{ isOpen: boolean }>` visibility: ${({ isOpen }) => (isOpen ? "visible" : "hidden")}; @@ -31,6 +19,21 @@ export const Overlay = styled.div<{ isOpen: boolean }>` visibility 0.3s ease-in-out; `; +export const DrawerContainer = styled.div<{ isOpen: boolean }>` + display: flex; + flex-direction: column; + position: fixed; + top: 0; + right: 0; + z-index: ${({ theme }) => theme.zIndex.drawer}; + width: 210px; + height: 100%; + + background-color: ${PRIMITIVE_COLORS.white}; + + animation: ${({ isOpen }) => (isOpen ? slideIn : slideOut)} 0.3s ease-in-out; +`; + export const DrawerHeader = styled.div` display: flex; align-items: center; @@ -52,3 +55,21 @@ export const TriggerButton = styled.button` font-size: 1.5rem; cursor: pointer; `; + +const slideIn = keyframes` + from { + transform: translateX(100%); + } + to { + transform: translateX(0); + } +`; + +const slideOut = keyframes` + from { + transform: translateX(0); + } + to { + transform: translateX(100%); + } +`; diff --git a/frontend/src/components/common/Drawer/Drawer.tsx b/frontend/src/components/common/Drawer/Drawer.tsx index bc23750a1..75555aebc 100644 --- a/frontend/src/components/common/Drawer/Drawer.tsx +++ b/frontend/src/components/common/Drawer/Drawer.tsx @@ -6,16 +6,19 @@ import DrawerProvider, { useDrawerContext } from "@contexts/DrawerProvider"; import useModalControl from "@hooks/useModalControl"; import usePressESC from "@hooks/usePressESC"; import useToggle from "@hooks/useToggle"; +import useUnmountAnimation from "@hooks/useUnmountAnimation"; import VisuallyHidden from "../VisuallyHidden/VisuallyHidden"; import * as S from "./Drawer.styled"; const Drawer = ({ children }: React.PropsWithChildren) => { const [isOpen, , , toggle] = useToggle(); - usePressESC(isOpen, toggle); + usePressESC(isOpen, toggle); useModalControl(isOpen, toggle); + const { shouldRender } = useUnmountAnimation({ isOpen }); + let headerContent: React.ReactNode | null = null; let drawerContent: React.ReactNode | null = null; const otherContent: React.ReactNode[] = []; @@ -39,12 +42,13 @@ const Drawer = ({ children }: React.PropsWithChildren) => { {otherContent} - {isOpen && + {shouldRender && ReactDOM.createPortal( {headerContent} {drawerContent} , + document.body, )} From d8244a254eae5abf50547fbd2597c26b03e677d9 Mon Sep 17 00:00:00 2001 From: river <130737187+0jenn0@users.noreply.github.com> Date: Wed, 23 Oct 2024 10:59:17 +0900 Subject: [PATCH 05/14] =?UTF-8?q?refactor(useUnmountAnimation):=20shouldRe?= =?UTF-8?q?nder=EB=A5=BC=20=20isRendered=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/common/Drawer/Drawer.tsx | 4 +- .../common/FloatingButton/FloatingButton.tsx | 52 +++++++++++++------ .../src/components/pages/main/MainPage.tsx | 4 +- frontend/src/hooks/useUnmountAnimation.ts | 8 +-- 4 files changed, 44 insertions(+), 24 deletions(-) diff --git a/frontend/src/components/common/Drawer/Drawer.tsx b/frontend/src/components/common/Drawer/Drawer.tsx index 75555aebc..f9fbd69b4 100644 --- a/frontend/src/components/common/Drawer/Drawer.tsx +++ b/frontend/src/components/common/Drawer/Drawer.tsx @@ -17,7 +17,7 @@ const Drawer = ({ children }: React.PropsWithChildren) => { usePressESC(isOpen, toggle); useModalControl(isOpen, toggle); - const { shouldRender } = useUnmountAnimation({ isOpen }); + const { isRendered } = useUnmountAnimation({ isOpen }); let headerContent: React.ReactNode | null = null; let drawerContent: React.ReactNode | null = null; @@ -42,7 +42,7 @@ const Drawer = ({ children }: React.PropsWithChildren) => { {otherContent} - {shouldRender && + {isRendered && ReactDOM.createPortal( {headerContent} diff --git a/frontend/src/components/common/FloatingButton/FloatingButton.tsx b/frontend/src/components/common/FloatingButton/FloatingButton.tsx index 7cf2a6eec..d621e896e 100644 --- a/frontend/src/components/common/FloatingButton/FloatingButton.tsx +++ b/frontend/src/components/common/FloatingButton/FloatingButton.tsx @@ -31,35 +31,55 @@ const FloatingButton = () => { ? "여행기 및 여행 계획 작성 메뉴가 열렸습니다. 닫으려면 esc버튼을 눌러주세요." : "여행기 및 여행 계획 작성 메뉴가 닫혔습니다."} - {isOpen && ( + {isOpen ? ( <> - - {SUB_BUTTONS.map(({ text, route }) => ( - handleClickSubButton(route)} - aria-label={removeEmoji(text)} - > - - {text} - - - ))} - + <> + + {SUB_BUTTONS.map(({ text, route }) => ( + handleClickSubButton(route)} + aria-label={removeEmoji(text)} + > + + {text} + + + ))} + + + + + + + ) : ( + + + )} - + {/* - + */} ); }; diff --git a/frontend/src/components/pages/main/MainPage.tsx b/frontend/src/components/pages/main/MainPage.tsx index 017bea932..5b3629cd6 100644 --- a/frontend/src/components/pages/main/MainPage.tsx +++ b/frontend/src/components/pages/main/MainPage.tsx @@ -115,11 +115,11 @@ const MainPage = () => { const cardRefs = useTravelogueCardFocus(isFetchingNextPage); - const { shouldRender: shouldSortingRender } = useUnmountAnimation({ + const { isRendered: shouldSortingRender } = useUnmountAnimation({ isOpen: sorting.isModalOpen, }); - const { shouldRender: shouldFilterRender } = useUnmountAnimation({ + const { isRendered: shouldFilterRender } = useUnmountAnimation({ isOpen: travelPeriod.isModalOpen, }); diff --git a/frontend/src/hooks/useUnmountAnimation.ts b/frontend/src/hooks/useUnmountAnimation.ts index 1c6ab08c3..3b6f0a479 100644 --- a/frontend/src/hooks/useUnmountAnimation.ts +++ b/frontend/src/hooks/useUnmountAnimation.ts @@ -6,21 +6,21 @@ interface useUnmountAnimationProps { } const useUnmountAnimation = ({ isOpen, animationDuration = 300 }: useUnmountAnimationProps) => { - const [shouldRender, setShouldRender] = useState(false); + const [isRendered, setIsRendered] = useState(false); useEffect(() => { if (isOpen) { - setShouldRender(true); + setIsRendered(true); } else { const timer = setTimeout(() => { - setShouldRender(false); + setIsRendered(false); }, animationDuration); return () => clearTimeout(timer); } }, [isOpen, animationDuration]); - return { shouldRender }; + return { isRendered }; }; export default useUnmountAnimation; From da402d4526765026968f02aab233e471a45e3cd7 Mon Sep 17 00:00:00 2001 From: river <130737187+0jenn0@users.noreply.github.com> Date: Wed, 23 Oct 2024 13:45:39 +0900 Subject: [PATCH 06/14] =?UTF-8?q?fix:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=82=AC=EC=A7=84=20=EC=88=98=EC=A0=95,=20=EC=97=AC=ED=96=89?= =?UTF-8?q?=EA=B8=B0=20=EB=B0=8F=20=EC=97=AC=ED=96=89=EA=B3=84=ED=9A=8D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20bottomSheet=EC=97=90=20useUnmountAnimation?= =?UTF-8?q?=20=ED=9B=85=20=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../EditRegisterModalBottomSheet.tsx | 6 +++++- frontend/src/components/pages/my/MyPage.tsx | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/common/Modal/EditRegisterModalBottomSheet/EditRegisterModalBottomSheet.tsx b/frontend/src/components/common/Modal/EditRegisterModalBottomSheet/EditRegisterModalBottomSheet.tsx index d298065af..6f0045249 100644 --- a/frontend/src/components/common/Modal/EditRegisterModalBottomSheet/EditRegisterModalBottomSheet.tsx +++ b/frontend/src/components/common/Modal/EditRegisterModalBottomSheet/EditRegisterModalBottomSheet.tsx @@ -1,5 +1,7 @@ import { Button, Modal, Spinner, Text } from "@components/common"; +import useUnmountAnimation from "@hooks/useUnmountAnimation"; + import { CYPRESS_DATA_MAP } from "@constants/cypress"; import { Tturi } from "@assets/svg"; @@ -23,8 +25,10 @@ const EditRegisterModalBottomSheet = ({ onClose, onConfirm, }: EditRegisterModalBottomSheetProps) => { + const { isRendered } = useUnmountAnimation({ isOpen }); + return ( - isOpen && ( + isRendered && ( diff --git a/frontend/src/components/pages/my/MyPage.tsx b/frontend/src/components/pages/my/MyPage.tsx index bea7fe2ce..900bdbade 100644 --- a/frontend/src/components/pages/my/MyPage.tsx +++ b/frontend/src/components/pages/my/MyPage.tsx @@ -9,6 +9,8 @@ import { } from "@components/common"; import MyPageSkeleton from "@components/pages/my/MyPageSkeleton/MyPageSkeleton"; +import useUnmountAnimation from "@hooks/useUnmountAnimation"; + import { FORM_VALIDATIONS_MAP } from "@constants/formValidation"; import { STORAGE_KEYS_MAP } from "@constants/storage"; @@ -19,6 +21,7 @@ import useMyPage from "./hooks/useMyPage"; const MyPage = () => { const { editModal, profileImage, profileNickname, profileEdit, userProfile } = useMyPage(); + const { isRendered } = useUnmountAnimation({ isOpen: editModal.isEditModalOpen }); const showErrorAlert = (error: Error | null) => { if (error && !IGNORED_ERROR_MESSAGES.includes(error.message)) alert(error.message); @@ -126,7 +129,7 @@ const MyPage = () => { css={S.listStyle} /> - {editModal.isEditModalOpen && ( + {isRendered && ( Date: Wed, 23 Oct 2024 14:05:13 +0900 Subject: [PATCH 07/14] =?UTF-8?q?fix(EditRegisterModalBottomSheet):=20?= =?UTF-8?q?=EC=95=A0=EB=8B=88=EB=A9=94=EC=9D=B4=EC=85=98=20=EC=8B=A4?= =?UTF-8?q?=EC=A2=85=20=EC=9D=B4=EC=8A=88=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../EditRegisterModalBottomSheet.tsx | 70 +++++++++---------- .../travelogueEdit/TravelogueEditPage.tsx | 20 +++--- 2 files changed, 44 insertions(+), 46 deletions(-) diff --git a/frontend/src/components/common/Modal/EditRegisterModalBottomSheet/EditRegisterModalBottomSheet.tsx b/frontend/src/components/common/Modal/EditRegisterModalBottomSheet/EditRegisterModalBottomSheet.tsx index 6f0045249..3bd41aa7a 100644 --- a/frontend/src/components/common/Modal/EditRegisterModalBottomSheet/EditRegisterModalBottomSheet.tsx +++ b/frontend/src/components/common/Modal/EditRegisterModalBottomSheet/EditRegisterModalBottomSheet.tsx @@ -1,7 +1,5 @@ import { Button, Modal, Spinner, Text } from "@components/common"; -import useUnmountAnimation from "@hooks/useUnmountAnimation"; - import { CYPRESS_DATA_MAP } from "@constants/cypress"; import { Tturi } from "@assets/svg"; @@ -25,43 +23,39 @@ const EditRegisterModalBottomSheet = ({ onClose, onConfirm, }: EditRegisterModalBottomSheetProps) => { - const { isRendered } = useUnmountAnimation({ isOpen }); - return ( - isRendered && ( - - - - - - - - {mainText} - - {subText} - - - - - - - - - - ) + + + + + + + + {mainText} + + {subText} + + + + + + + + + ); }; diff --git a/frontend/src/components/pages/travelogueEdit/TravelogueEditPage.tsx b/frontend/src/components/pages/travelogueEdit/TravelogueEditPage.tsx index d0daa3efb..716b2ec62 100644 --- a/frontend/src/components/pages/travelogueEdit/TravelogueEditPage.tsx +++ b/frontend/src/components/pages/travelogueEdit/TravelogueEditPage.tsx @@ -19,6 +19,7 @@ import TravelogueDayAccordion from "@components/pages/travelogueRegister/Travelo import useTravelogueFormState from "@hooks/pages/useTravelogueFormState/useTravelogueFormState"; import { useDragScroll } from "@hooks/useDragScroll"; import useToggle from "@hooks/useToggle"; +import useUnmountAnimation from "@hooks/useUnmountAnimation"; import { FORM_VALIDATIONS_MAP } from "@constants/formValidation"; @@ -26,6 +27,7 @@ import * as S from "./TravelogueEditPage.styled"; const TravelogueEditPage = () => { const [isOpen, handleOpenBottomSheet, handleCloseBottomSheet] = useToggle(); + const { isRendered } = useUnmountAnimation({ isOpen }); const { state: { @@ -197,14 +199,16 @@ const TravelogueEditPage = () => { - + {isRendered && ( + + )} ); }; From 665fd78c9e23f4d3786ee6c2515b08a1e71f9859 Mon Sep 17 00:00:00 2001 From: river <130737187+0jenn0@users.noreply.github.com> Date: Wed, 23 Oct 2024 14:35:41 +0900 Subject: [PATCH 08/14] =?UTF-8?q?style(FloatingButton):=20animation=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20useUnmountAnimation=20?= =?UTF-8?q?=ED=9B=85=20=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FloatingButton/FloatingButton.styled.ts | 69 ++++++++++++++++--- .../common/FloatingButton/FloatingButton.tsx | 56 +++++---------- 2 files changed, 77 insertions(+), 48 deletions(-) diff --git a/frontend/src/components/common/FloatingButton/FloatingButton.styled.ts b/frontend/src/components/common/FloatingButton/FloatingButton.styled.ts index a5dda5445..cffb0ceda 100644 --- a/frontend/src/components/common/FloatingButton/FloatingButton.styled.ts +++ b/frontend/src/components/common/FloatingButton/FloatingButton.styled.ts @@ -1,4 +1,4 @@ -import { css } from "@emotion/react"; +import { css, keyframes } from "@emotion/react"; import styled from "@emotion/styled"; import { PRIMITIVE_COLORS } from "@styles/tokens"; @@ -13,22 +13,73 @@ export const FloatingButtonContainer = styled.div` z-index: ${({ theme }) => theme.zIndex.floating}; `; -export const BackdropLayout = styled.div` +const fadeIn = keyframes` +from { + opacity:0; + visibility: hidden; +} +to { + opacity:1; + visibility: visible; +} +`; + +const fadeOut = keyframes` +from { + opacity:1; + visibility: visible; +} +to { + opacity:0; + visibility: hidden; +} +`; + +export const BackdropLayout = styled.div<{ $isOpen: boolean }>` + visibility: ${({ $isOpen }) => ($isOpen ? "visible" : "hidden")}; position: fixed; width: 100%; height: 100%; - inset: 0; background-color: ${({ theme }) => theme.colors.dimmed}; + + animation: ${({ $isOpen }) => ($isOpen ? fadeIn : fadeOut)} 0.3s ease-in-out; + inset: 0; cursor: pointer; `; +const slideUp = keyframes` + from { + opacity: 0; + transform: translateY(2rem); + visibility: hidden; + } + to { + opacity: 1; + transform: translateY(-0.8rem); + visibility: visible; + } +`; + +const slideDown = keyframes` + from { + opacity: 1; + transform: translateY(-0.8rem); + visibility: visible; + } + to { + opacity: 0; + transform: translateY(2rem); + visibility: hidden; + } +`; + export const SubButtonContainer = styled.div<{ $isOpen: boolean }>` display: flex; flex-direction: column; + visibility: ${({ $isOpen }) => ($isOpen ? "visible" : "hidden")}; position: absolute; bottom: 100%; - gap: ${({ theme }) => theme.spacing.l}; width: 16rem; padding: ${({ theme }) => theme.spacing.l} ${({ theme }) => theme.spacing.m}; @@ -36,13 +87,9 @@ export const SubButtonContainer = styled.div<{ $isOpen: boolean }>` background-color: ${PRIMITIVE_COLORS.gray[700]}; - transition: all 0.3s ease-out; - - ${({ $isOpen }) => css` - opacity: ${$isOpen ? 1 : 0}; - visibility: ${$isOpen ? "visible" : "hidden"}; - transform: translateY(${$isOpen ? -0.8 : 2}rem); - `} + animation: ${({ $isOpen }) => ($isOpen ? slideUp : slideDown)} 0.3s ease-in-out; + gap: ${({ theme }) => theme.spacing.l}; + animation-fill-mode: forwards; `; export const SubButton = styled.button` diff --git a/frontend/src/components/common/FloatingButton/FloatingButton.tsx b/frontend/src/components/common/FloatingButton/FloatingButton.tsx index d621e896e..e50e2b174 100644 --- a/frontend/src/components/common/FloatingButton/FloatingButton.tsx +++ b/frontend/src/components/common/FloatingButton/FloatingButton.tsx @@ -2,6 +2,7 @@ import { useNavigate } from "react-router-dom"; import useModalControl from "@hooks/useModalControl"; import useToggle from "@hooks/useToggle"; +import useUnmountAnimation from "@hooks/useUnmountAnimation"; import { removeEmoji } from "@utils/removeEmojis"; @@ -23,6 +24,7 @@ const FloatingButton = () => { }; useModalControl(isOpen, handleToggleButton); + const { isRendered } = useUnmountAnimation({ isOpen }); return ( @@ -31,55 +33,35 @@ const FloatingButton = () => { ? "여행기 및 여행 계획 작성 메뉴가 열렸습니다. 닫으려면 esc버튼을 눌러주세요." : "여행기 및 여행 계획 작성 메뉴가 닫혔습니다."} - {isOpen ? ( + {isRendered && ( <> - + - <> - - {SUB_BUTTONS.map(({ text, route }) => ( - handleClickSubButton(route)} - aria-label={removeEmoji(text)} - > - - {text} - - - ))} - - - - - - + + {SUB_BUTTONS.map(({ text, route }) => ( + handleClickSubButton(route)} + aria-label={removeEmoji(text)} + > + + {text} + + + ))} + - ) : ( - - - )} - {/* + - */} + ); }; From 0ef0851fcd2f285dc9321b5e915e2e4b98b2627e Mon Sep 17 00:00:00 2001 From: river <130737187+0jenn0@users.noreply.github.com> Date: Wed, 23 Oct 2024 14:59:54 +0900 Subject: [PATCH 09/14] =?UTF-8?q?style(Icon):=20=EC=84=B8=EB=A1=9C=20?= =?UTF-8?q?=EA=B0=80=EC=9A=B4=EB=8D=B0=20=EC=A0=95=EB=A0=AC=EC=9D=84=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20wrapper=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/common/Icon/Icon.styled.ts | 7 ++++ frontend/src/components/common/Icon/Icon.tsx | 41 +++++++++++-------- 2 files changed, 30 insertions(+), 18 deletions(-) create mode 100644 frontend/src/components/common/Icon/Icon.styled.ts diff --git a/frontend/src/components/common/Icon/Icon.styled.ts b/frontend/src/components/common/Icon/Icon.styled.ts new file mode 100644 index 000000000..d81b48623 --- /dev/null +++ b/frontend/src/components/common/Icon/Icon.styled.ts @@ -0,0 +1,7 @@ +import styled from "@emotion/styled"; + +export const Wrapper = styled.div<{ size: string }>` + display: flex; + align-items: center; + height: ${({ size }) => size}px; +`; diff --git a/frontend/src/components/common/Icon/Icon.tsx b/frontend/src/components/common/Icon/Icon.tsx index 20264f387..a6f1fef59 100644 --- a/frontend/src/components/common/Icon/Icon.tsx +++ b/frontend/src/components/common/Icon/Icon.tsx @@ -1,5 +1,6 @@ import { StrokeLineCap, StrokeLineJoin } from "@components/common/Icon/Icon.type"; +import * as S from "./Icon.styled"; import SVG_ICONS_MAP from "./svg-icons.json"; interface IconProps extends React.ComponentPropsWithoutRef<"svg"> { @@ -10,24 +11,28 @@ interface IconProps extends React.ComponentPropsWithoutRef<"svg"> { const Icon = ({ iconType, color, size, ...attributes }: IconProps) => { return ( - - - + + + + + ); }; From 0820734ea7b5b97f9bc08562f2801d4dbbcfe44f Mon Sep 17 00:00:00 2001 From: river <130737187+0jenn0@users.noreply.github.com> Date: Wed, 23 Oct 2024 15:40:10 +0900 Subject: [PATCH 10/14] =?UTF-8?q?feat(animation):=20animation=20=ED=86=A0?= =?UTF-8?q?=ED=81=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/styles/theme.ts | 3 +- frontend/src/styles/tokens/animation.ts | 162 ++++++++++++++++++++++++ frontend/src/styles/tokens/index.ts | 1 + frontend/src/types/style/emotion.d.ts | 3 +- 4 files changed, 167 insertions(+), 2 deletions(-) create mode 100644 frontend/src/styles/tokens/animation.ts diff --git a/frontend/src/styles/theme.ts b/frontend/src/styles/theme.ts index 07b8e04df..4b053a916 100644 --- a/frontend/src/styles/theme.ts +++ b/frontend/src/styles/theme.ts @@ -1,12 +1,13 @@ import { Theme } from "@emotion/react"; -import { SEMANTIC_COLORS, SPACING, TYPOGRAPHY, Z_INDEX } from "@styles/tokens"; +import { ANIMATION, SEMANTIC_COLORS, SPACING, TYPOGRAPHY, Z_INDEX } from "@styles/tokens"; const theme: Theme = { typography: TYPOGRAPHY, colors: SEMANTIC_COLORS, spacing: SPACING, zIndex: Z_INDEX, + animation: ANIMATION, }; export default theme; diff --git a/frontend/src/styles/tokens/animation.ts b/frontend/src/styles/tokens/animation.ts new file mode 100644 index 000000000..eb206e399 --- /dev/null +++ b/frontend/src/styles/tokens/animation.ts @@ -0,0 +1,162 @@ +// import { keyframes } from "@emotion/react"; +// interface SlideAnimationProps { +// from: string | number; +// to: string | number; +// } +// type CreateSlideAnimation = (props: SlideAnimationProps) => ReturnType; +// const formatValue = (value: string | number) => (typeof value === "number" ? `${value}rem` : value); +// const createSlideUp: CreateSlideAnimation = ({ from, to }) => keyframes` +// from { +// opacity: 0; +// transform: translateY(${formatValue(from)}); +// visibility: hidden; +// } +// to { +// opacity: 1; +// transform: translateY(${formatValue(to)}); +// visibility: visible; +// } +// `; +// const createSlideDown: CreateSlideAnimation = ({ from, to }) => keyframes` +// from { +// opacity: 1; +// transform: translateY(${formatValue(from)}); +// visibility: visible; +// } +// to { +// opacity: 0; +// transform: translateY(${formatValue(to)}); +// visibility: hidden; +// } +// `; +// const createSlideIn: CreateSlideAnimation = ({ from, to }) => keyframes` +// from { +// transform: translateX(${formatValue(from)}); +// visibility: visible; +// } +// to { +// transform: translateX(${formatValue(to)}); +// visibility: hidden; +// } +// `; +// const createSlideOut: CreateSlideAnimation = ({ from, to }) => keyframes` +// from { +// transform: translateX(${formatValue(from)}); +// visibility: hidden; +// } +// to { +// transform: translateX(${formatValue(to)}); +// visibility: visible; +// } +// `; +// export const ANIMATION = { +// duration: { +// default: "0.3s", +// }, +// fadeIn: keyframes` +// from { +// opacity:0; +// visibility: hidden; +// } +// to { +// opacity:1; +// visibility: visible; +// } +// `, +// fadeOut: keyframes` +// from { +// opacity:1; +// visibility: visible; +// } +// to { +// opacity:0; +// visibility: hidden; +// } +// `, +// createSlideUp, +// createSlideDown, +// createSlideIn, +// createSlideOut, +// }; +import { keyframes } from "@emotion/react"; + +interface SlideAnimationProps { + from: string | number; + to: string | number; +} + +const formatValue = (value: string | number) => (typeof value === "number" ? `${value}rem` : value); + +export const ANIMATION = { + duration: { + default: "0.3s", + }, + fade: { + in: keyframes` + from { + opacity: 0; + visibility: hidden; + } + to { + opacity: 1; + visibility: visible; + } + `, + out: keyframes` + from { + opacity: 1; + visibility: visible; + } + to { + opacity: 0; + visibility: hidden; + } + `, + }, + slide: { + up: ({ from, to }: SlideAnimationProps) => keyframes` + from { + opacity: 0; + transform: translateY(${formatValue(from)}); + visibility: hidden; + } + to { + opacity: 1; + transform: translateY(${formatValue(to)}); + visibility: visible; + } + `, + down: ({ from, to }: SlideAnimationProps) => keyframes` + from { + opacity: 1; + transform: translateY(${formatValue(from)}); + visibility: visible; + } + to { + opacity: 0; + transform: translateY(${formatValue(to)}); + visibility: hidden; + } + `, + in: ({ from, to }: SlideAnimationProps) => keyframes` + from { + transform: translateX(${formatValue(from)}); + visibility: visible; + } + to { + transform: translateX(${formatValue(to)}); + visibility: hidden; + } + `, + out: ({ from, to }: SlideAnimationProps) => keyframes` + from { + transform: translateX(${formatValue(from)}); + visibility: hidden; + } + to { + transform: translateX(${formatValue(to)}); + visibility: visible; + } + `, + }, +}; diff --git a/frontend/src/styles/tokens/index.ts b/frontend/src/styles/tokens/index.ts index 6819db706..9394ec341 100644 --- a/frontend/src/styles/tokens/index.ts +++ b/frontend/src/styles/tokens/index.ts @@ -2,3 +2,4 @@ export * from "./colors"; export * from "./typography"; export * from "./spacing"; export * from "./zIndex"; +export * from "./animation"; diff --git a/frontend/src/types/style/emotion.d.ts b/frontend/src/types/style/emotion.d.ts index f88c79a91..2d2e50ccf 100644 --- a/frontend/src/types/style/emotion.d.ts +++ b/frontend/src/types/style/emotion.d.ts @@ -1,6 +1,6 @@ import "@emotion/react"; -import { SEMANTIC_COLORS, SPACING, TYPOGRAPHY, Z_INDEX } from "@styles/tokens"; +import { ANIMATION, SEMANTIC_COLORS, SPACING, TYPOGRAPHY, Z_INDEX } from "@styles/tokens"; declare module "@emotion/react" { export interface Theme { @@ -8,5 +8,6 @@ declare module "@emotion/react" { colors: typeof SEMANTIC_COLORS; spacing: typeof SPACING; zIndex: typeof Z_INDEX; + animation: typeof ANIMATION; } } From ccf071141ab0337f35bbdee442b117cfe51af9f5 Mon Sep 17 00:00:00 2001 From: river <130737187+0jenn0@users.noreply.github.com> Date: Wed, 23 Oct 2024 15:40:45 +0900 Subject: [PATCH 11/14] =?UTF-8?q?refactor:=20Drawer,FloatingButton?= =?UTF-8?q?=EC=97=90=EC=84=9C=20animation=20=ED=86=A0=ED=81=B0=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/common/Drawer/Drawer.styled.ts | 40 +++++------- .../FloatingButton/FloatingButton.styled.ts | 61 +++---------------- 2 files changed, 24 insertions(+), 77 deletions(-) diff --git a/frontend/src/components/common/Drawer/Drawer.styled.ts b/frontend/src/components/common/Drawer/Drawer.styled.ts index ba4b2d49a..93433e435 100644 --- a/frontend/src/components/common/Drawer/Drawer.styled.ts +++ b/frontend/src/components/common/Drawer/Drawer.styled.ts @@ -1,27 +1,27 @@ -import { keyframes } from "@emotion/react"; import styled from "@emotion/styled"; +import theme from "@styles/theme"; import { PRIMITIVE_COLORS } from "@styles/tokens"; export const Overlay = styled.div<{ isOpen: boolean }>` visibility: ${({ isOpen }) => (isOpen ? "visible" : "hidden")}; position: fixed; - top: 0; - left: 0; z-index: ${({ theme }) => theme.zIndex.drawerOverlay}; width: 100%; height: 100%; + inset: 0; background-color: rgb(0 0 0 / 30%); opacity: ${({ isOpen }) => (isOpen ? 1 : 0)}; - transition: - opacity 0.3s ease-in-out, - visibility 0.3s ease-in-out; + + animation: ${({ isOpen, theme }) => (isOpen ? theme.animation.fade.in : theme.animation.fade.out)} + ${theme.animation.duration.default} ease-in-out; `; export const DrawerContainer = styled.div<{ isOpen: boolean }>` display: flex; flex-direction: column; + visibility: ${({ isOpen }) => (isOpen ? "visible" : "hidden")}; position: fixed; top: 0; right: 0; @@ -31,7 +31,11 @@ export const DrawerContainer = styled.div<{ isOpen: boolean }>` background-color: ${PRIMITIVE_COLORS.white}; - animation: ${({ isOpen }) => (isOpen ? slideIn : slideOut)} 0.3s ease-in-out; + animation: ${({ isOpen, theme }) => + isOpen + ? theme.animation.slide.in({ from: "100%", to: 0 }) + : theme.animation.slide.out({ from: 0, to: "100%" })} + ${theme.animation.duration.default} ease-in-out; `; export const DrawerHeader = styled.div` @@ -49,27 +53,11 @@ export const DrawerContent = styled.div` `; export const TriggerButton = styled.button` - background: none; border: none; - font-size: 1.5rem; - cursor: pointer; -`; + background-color: none; -const slideIn = keyframes` - from { - transform: translateX(100%); - } - to { - transform: translateX(0); - } -`; + font-size: 1.5rem; -const slideOut = keyframes` - from { - transform: translateX(0); - } - to { - transform: translateX(100%); - } + cursor: pointer; `; diff --git a/frontend/src/components/common/FloatingButton/FloatingButton.styled.ts b/frontend/src/components/common/FloatingButton/FloatingButton.styled.ts index cffb0ceda..189315700 100644 --- a/frontend/src/components/common/FloatingButton/FloatingButton.styled.ts +++ b/frontend/src/components/common/FloatingButton/FloatingButton.styled.ts @@ -1,6 +1,7 @@ -import { css, keyframes } from "@emotion/react"; +import { css } from "@emotion/react"; import styled from "@emotion/styled"; +import theme from "@styles/theme"; import { PRIMITIVE_COLORS } from "@styles/tokens"; export const FloatingButtonContainer = styled.div` @@ -13,28 +14,6 @@ export const FloatingButtonContainer = styled.div` z-index: ${({ theme }) => theme.zIndex.floating}; `; -const fadeIn = keyframes` -from { - opacity:0; - visibility: hidden; -} -to { - opacity:1; - visibility: visible; -} -`; - -const fadeOut = keyframes` -from { - opacity:1; - visibility: visible; -} -to { - opacity:0; - visibility: hidden; -} -`; - export const BackdropLayout = styled.div<{ $isOpen: boolean }>` visibility: ${({ $isOpen }) => ($isOpen ? "visible" : "hidden")}; position: fixed; @@ -43,37 +22,13 @@ export const BackdropLayout = styled.div<{ $isOpen: boolean }>` background-color: ${({ theme }) => theme.colors.dimmed}; - animation: ${({ $isOpen }) => ($isOpen ? fadeIn : fadeOut)} 0.3s ease-in-out; + animation: ${({ $isOpen, theme }) => + $isOpen ? theme.animation.fade.in : theme.animation.fade.out} + ${theme.animation.duration.default} ease-in-out; inset: 0; cursor: pointer; `; -const slideUp = keyframes` - from { - opacity: 0; - transform: translateY(2rem); - visibility: hidden; - } - to { - opacity: 1; - transform: translateY(-0.8rem); - visibility: visible; - } -`; - -const slideDown = keyframes` - from { - opacity: 1; - transform: translateY(-0.8rem); - visibility: visible; - } - to { - opacity: 0; - transform: translateY(2rem); - visibility: hidden; - } -`; - export const SubButtonContainer = styled.div<{ $isOpen: boolean }>` display: flex; flex-direction: column; @@ -87,7 +42,11 @@ export const SubButtonContainer = styled.div<{ $isOpen: boolean }>` background-color: ${PRIMITIVE_COLORS.gray[700]}; - animation: ${({ $isOpen }) => ($isOpen ? slideUp : slideDown)} 0.3s ease-in-out; + animation: ${({ $isOpen, theme }) => + $isOpen + ? theme.animation.slide.up({ from: 2, to: -0.8 }) + : theme.animation.slide.down({ from: -0.8, to: 2 })} + ${theme.animation.duration.default} ease-in-out; gap: ${({ theme }) => theme.spacing.l}; animation-fill-mode: forwards; `; From f44cfe7b49d3e221119001e1a387f2d006e4338f Mon Sep 17 00:00:00 2001 From: river <130737187+0jenn0@users.noreply.github.com> Date: Wed, 23 Oct 2024 15:52:22 +0900 Subject: [PATCH 12/14] =?UTF-8?q?chore:=20=ED=95=84=EC=9A=94=EC=97=86?= =?UTF-8?q?=EB=8A=94=20=EC=A3=BC=EC=84=9D=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/styles/tokens/animation.ts | 80 ------------------------- 1 file changed, 80 deletions(-) diff --git a/frontend/src/styles/tokens/animation.ts b/frontend/src/styles/tokens/animation.ts index eb206e399..88714ffee 100644 --- a/frontend/src/styles/tokens/animation.ts +++ b/frontend/src/styles/tokens/animation.ts @@ -1,83 +1,3 @@ -// import { keyframes } from "@emotion/react"; -// interface SlideAnimationProps { -// from: string | number; -// to: string | number; -// } -// type CreateSlideAnimation = (props: SlideAnimationProps) => ReturnType; -// const formatValue = (value: string | number) => (typeof value === "number" ? `${value}rem` : value); -// const createSlideUp: CreateSlideAnimation = ({ from, to }) => keyframes` -// from { -// opacity: 0; -// transform: translateY(${formatValue(from)}); -// visibility: hidden; -// } -// to { -// opacity: 1; -// transform: translateY(${formatValue(to)}); -// visibility: visible; -// } -// `; -// const createSlideDown: CreateSlideAnimation = ({ from, to }) => keyframes` -// from { -// opacity: 1; -// transform: translateY(${formatValue(from)}); -// visibility: visible; -// } -// to { -// opacity: 0; -// transform: translateY(${formatValue(to)}); -// visibility: hidden; -// } -// `; -// const createSlideIn: CreateSlideAnimation = ({ from, to }) => keyframes` -// from { -// transform: translateX(${formatValue(from)}); -// visibility: visible; -// } -// to { -// transform: translateX(${formatValue(to)}); -// visibility: hidden; -// } -// `; -// const createSlideOut: CreateSlideAnimation = ({ from, to }) => keyframes` -// from { -// transform: translateX(${formatValue(from)}); -// visibility: hidden; -// } -// to { -// transform: translateX(${formatValue(to)}); -// visibility: visible; -// } -// `; -// export const ANIMATION = { -// duration: { -// default: "0.3s", -// }, -// fadeIn: keyframes` -// from { -// opacity:0; -// visibility: hidden; -// } -// to { -// opacity:1; -// visibility: visible; -// } -// `, -// fadeOut: keyframes` -// from { -// opacity:1; -// visibility: visible; -// } -// to { -// opacity:0; -// visibility: hidden; -// } -// `, -// createSlideUp, -// createSlideDown, -// createSlideIn, -// createSlideOut, -// }; import { keyframes } from "@emotion/react"; interface SlideAnimationProps { From c9a4c3114bf6ef54d28320662789d7e996c70739 Mon Sep 17 00:00:00 2001 From: river <130737187+0jenn0@users.noreply.github.com> Date: Wed, 23 Oct 2024 15:58:45 +0900 Subject: [PATCH 13/14] =?UTF-8?q?fix(route):=20=EC=95=9E=EC=97=90=20/=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/constants/route.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/constants/route.ts b/frontend/src/constants/route.ts index f31a17cf3..ec9dbeabc 100644 --- a/frontend/src/constants/route.ts +++ b/frontend/src/constants/route.ts @@ -1,7 +1,7 @@ export const ROUTE_PATHS_MAP = { back: -1, root: "/", - main: "main", + main: "/main", travelogue: (id?: number | string) => (id ? `/travelogue/${id}` : "/travelogue/:id"), travelPlan: (id?: number | string) => (id ? `/travel-plan/${id}` : "/travel-plan/:id"), travelogueRegister: "/travelogue/register", From 4d130079194fa0173b9bc1046bfff130b06130db Mon Sep 17 00:00:00 2001 From: river <130737187+0jenn0@users.noreply.github.com> Date: Wed, 23 Oct 2024 16:06:18 +0900 Subject: [PATCH 14/14] =?UTF-8?q?fix(Drawer):=20overlay=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 페이지 이동할 때 마다 ovelay가 보였다가 안보이는 이슈가 있어서 수정 --- frontend/src/components/common/Drawer/Drawer.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/common/Drawer/Drawer.tsx b/frontend/src/components/common/Drawer/Drawer.tsx index f9fbd69b4..c1958792e 100644 --- a/frontend/src/components/common/Drawer/Drawer.tsx +++ b/frontend/src/components/common/Drawer/Drawer.tsx @@ -41,14 +41,16 @@ const Drawer = ({ children }: React.PropsWithChildren) => { {isOpen ? "사용자 메뉴가 열렸습니다." : "사용자 메뉴가 닫혔습니다."} {otherContent} - + {isRendered && ReactDOM.createPortal( - - {headerContent} - {drawerContent} - , - + <> + + + {headerContent} + {drawerContent} + + , document.body, )}