From e35ccc5f55f2c366fe17af0824b326a3d20af7ae Mon Sep 17 00:00:00 2001 From: Chamsol Kim Date: Sun, 24 Aug 2025 16:14:18 +0900 Subject: [PATCH 01/13] =?UTF-8?q?refactor=20[]=20Toast=20=ED=91=9C?= =?UTF-8?q?=EC=8B=9C=20=EC=8B=9C=EA=B0=84=EC=9D=84=205=EC=B4=88=EB=A1=9C?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rolling-paper/components/header/rolling-paper-header.jsx | 2 +- src/hooks/use-toast.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/features/rolling-paper/components/header/rolling-paper-header.jsx b/src/features/rolling-paper/components/header/rolling-paper-header.jsx index 5127f4b..6ae573a 100644 --- a/src/features/rolling-paper/components/header/rolling-paper-header.jsx +++ b/src/features/rolling-paper/components/header/rolling-paper-header.jsx @@ -69,7 +69,7 @@ function RollingPaperHeader({ recipientName, messages, }) { - const { showsToast, setShowsToast } = useToast(); + const { showsToast, setShowsToast } = useToast({ timeout: 5000 }); const { isDesktop, isMobile } = useMedia(); const [reactions, setReactions] = useState([]); diff --git a/src/hooks/use-toast.jsx b/src/hooks/use-toast.jsx index 8b461b6..3bb98d4 100644 --- a/src/hooks/use-toast.jsx +++ b/src/hooks/use-toast.jsx @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; -function useToast(timeout = 2500) { +function useToast({ timeout = 2500 } = {}) { const [showsToast, setShowsToast] = useState(false); useEffect(() => { From 1db2481efd11b5f0e3b613484ecd974ff46c04f0 Mon Sep 17 00:00:00 2001 From: Chamsol Kim Date: Sun, 24 Aug 2025 17:25:33 +0900 Subject: [PATCH 02/13] =?UTF-8?q?feat=20[#49]=20Button=20component?= =?UTF-8?q?=EC=97=90=20hover=20transition=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/button/button.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/button/button.jsx b/src/components/button/button.jsx index 51a75dd..0d3ab9a 100644 --- a/src/components/button/button.jsx +++ b/src/components/button/button.jsx @@ -50,6 +50,7 @@ const BaseButton = styled.button` line-height: ${({ $size }) => styles.lineHeight[$size]}; border-radius: ${({ $size }) => styles.borderRadius[$size]}; height: ${({ $size, $icon }) => styles.height($icon)[$size]}; + transition: background-color 0.3s; span { display: block; From 596339ae3a3776554793d7bb9c05ed456ef87521 Mon Sep 17 00:00:00 2001 From: Chamsol Kim Date: Sun, 24 Aug 2025 17:27:57 +0900 Subject: [PATCH 03/13] =?UTF-8?q?feat=20[#49]=20ToggleButton=20component?= =?UTF-8?q?=EC=97=90=20hover=20transition=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/button/toggle-button.jsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/button/toggle-button.jsx b/src/components/button/toggle-button.jsx index cf805d2..e80fd69 100644 --- a/src/components/button/toggle-button.jsx +++ b/src/components/button/toggle-button.jsx @@ -18,6 +18,12 @@ const ToggleItem = styled.button` padding: 0 16px; height: 40px; cursor: pointer; + transition: background-color 0.3s; + + &:hover { + background-color: ${({ $selected }) => + $selected ? Colors.purple(100) : Colors.gray(200)}; + } span { display: block; From 5e9b4df59ce055467c275d876fc10c5bc396d7e3 Mon Sep 17 00:00:00 2001 From: Chamsol Kim Date: Sun, 24 Aug 2025 17:32:10 +0900 Subject: [PATCH 04/13] =?UTF-8?q?feat=20[#49]=20TextField=20component?= =?UTF-8?q?=EC=97=90=20border=20transition=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/text-field/dropdown-input/dropdown-input.jsx | 1 + src/components/text-field/text-input/text-input.jsx | 1 + 2 files changed, 2 insertions(+) diff --git a/src/components/text-field/dropdown-input/dropdown-input.jsx b/src/components/text-field/dropdown-input/dropdown-input.jsx index 775710c..831d1ad 100644 --- a/src/components/text-field/dropdown-input/dropdown-input.jsx +++ b/src/components/text-field/dropdown-input/dropdown-input.jsx @@ -51,6 +51,7 @@ const StyledDropdownInput = styled.button` gap: 8px; cursor: pointer; position: relative; + transition: box-shadow 0.3s; &:hover { box-shadow: 0 0 0 1px diff --git a/src/components/text-field/text-input/text-input.jsx b/src/components/text-field/text-input/text-input.jsx index 5687c6e..3c6ff8a 100644 --- a/src/components/text-field/text-input/text-input.jsx +++ b/src/components/text-field/text-input/text-input.jsx @@ -12,6 +12,7 @@ const StyledTextInput = styled.input` ${INPUT_STYLES.font} color: ${INPUT_STYLES.textColor.normal}; min-width: 320px; + transition: box-shadow 0.3s; &::placeholder { ${INPUT_STYLES.font} From 30fe7f3959f1ef12afe5287133fff9937dae0435 Mon Sep 17 00:00:00 2001 From: Chamsol Kim Date: Sun, 24 Aug 2025 17:32:45 +0900 Subject: [PATCH 05/13] =?UTF-8?q?feat=20[]=20TextField=EA=B0=80=20error=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=EC=9D=BC=20=EB=95=8C=20=EC=83=89=EC=83=81=20?= =?UTF-8?q?Layer=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/text-field/input-styles.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/text-field/input-styles.js b/src/components/text-field/input-styles.js index 24b7b4c..8da878e 100644 --- a/src/components/text-field/input-styles.js +++ b/src/components/text-field/input-styles.js @@ -8,10 +8,10 @@ const INPUT_STYLES = Object.freeze({ line-height: 26px; `, borderColor: { - normal: (error) => (error ? Colors.error : Colors.gray(300)), - hover: (error) => (error ? Colors.error : Colors.gray(500)), - active: (error) => (error ? Colors.error : Colors.gray(700)), - focus: (error) => (error ? Colors.error : Colors.gray(500)), + normal: (error) => (error ? Colors.red(400) : Colors.gray(300)), + hover: (error) => (error ? Colors.red(500) : Colors.gray(500)), + active: (error) => (error ? Colors.red(700) : Colors.gray(700)), + focus: (error) => (error ? Colors.red(500) : Colors.gray(500)), disabled: Colors.gray(300), }, textColor: { From 246bd3438129cd38549ad76cfab3fc3977198da0 Mon Sep 17 00:00:00 2001 From: Chamsol Kim Date: Sun, 24 Aug 2025 17:52:35 +0900 Subject: [PATCH 06/13] =?UTF-8?q?feat=20[#49]=20MessageCard=EA=B0=80=20?= =?UTF-8?q?=EC=88=9C=EC=B0=A8=EC=A0=81=EC=9C=BC=EB=A1=9C=20=EC=98=AC?= =?UTF-8?q?=EB=9D=BC=EC=98=A4=EB=8A=94=20animation=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../message/components/message-card-add.jsx | 19 +++++---- .../message/components/message-card-base.jsx | 33 +++++++++++++++ .../message/components/message-card.jsx | 42 +++++++++---------- .../message/components/messages-grid.jsx | 3 +- 4 files changed, 66 insertions(+), 31 deletions(-) create mode 100644 src/features/message/components/message-card-base.jsx diff --git a/src/features/message/components/message-card-add.jsx b/src/features/message/components/message-card-add.jsx index 9fbeb23..0f36b61 100644 --- a/src/features/message/components/message-card-add.jsx +++ b/src/features/message/components/message-card-add.jsx @@ -2,6 +2,7 @@ import styled from "styled-components"; import plusImage from "../../../assets/ic-plus.svg"; import Colors from "../../../components/color/colors"; import { media } from "../../../utils/media"; +import MessageCardBase from "./message-card-base"; const AddCircle = styled.div` width: 56px; @@ -14,15 +15,13 @@ const AddCircle = styled.div` `; const StyledMessageCardAdd = styled.button` + width: 100%; + background: none; border: none; display: flex; justify-content: center; align-items: center; - border-radius: 16px; - background-color: white; - box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.08); min-height: 280px; - cursor: pointer; ${media.tablet} { min-height: 284px; @@ -35,11 +34,13 @@ const StyledMessageCardAdd = styled.button` function MessageCardAdd({ onClick }) { return ( - - - Message 추가 - - + + + + Message 추가 + + + ); } diff --git a/src/features/message/components/message-card-base.jsx b/src/features/message/components/message-card-base.jsx new file mode 100644 index 0000000..e431d4b --- /dev/null +++ b/src/features/message/components/message-card-base.jsx @@ -0,0 +1,33 @@ +import styled, { keyframes } from "styled-components"; + +const mountAnimation = keyframes` + from { + transform: translateY(36px); + } + + to { + transform: initial; + } +`; + +function animationDelay({ $index = 0 }) { + const delay = 150; + return `${delay * $index}ms`; +} + +const StyledMessageCardBase = styled.div` + background-color: white; + border-radius: 16px; + box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.08); + cursor: pointer; + overflow: hidden; + animation: ${mountAnimation} 900ms ${animationDelay} backwards; +`; + +function MessageCardBase({ index, children }) { + return ( + {children} + ); +} + +export default MessageCardBase; diff --git a/src/features/message/components/message-card.jsx b/src/features/message/components/message-card.jsx index 1400b3c..331b695 100644 --- a/src/features/message/components/message-card.jsx +++ b/src/features/message/components/message-card.jsx @@ -5,6 +5,7 @@ import BUTTON_SIZE from "../../../components/button/button-size"; import Colors from "../../../components/color/colors"; import { formatDate } from "../../../utils/formatter"; import { media } from "../../../utils/media"; +import MessageCardBase from "./message-card-base"; import MessageSender from "./message-sender"; const Header = styled.header` @@ -47,13 +48,10 @@ const StyledMessageCard = styled.article` display: flex; flex-direction: column; padding: 24px; - border-radius: 16px; - background-color: white; - box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.08); cursor: ${({ $isEditing }) => ($isEditing ? "default" : "pointer")}; `; -function MessageCard({ isEditing, message, onClick, onDelete }) { +function MessageCard({ isEditing, index, message, onClick, onDelete }) { const handleClick = () => { if (isEditing) return; onClick(message); @@ -65,24 +63,26 @@ function MessageCard({ isEditing, message, onClick, onDelete }) { }; return ( - -
- - {isEditing && ( - + +
+ - )} -
- {message.content} - {formatDate(message.createdAt, ".")} -
+ {isEditing && ( + + )} +
+ {message.content} + {formatDate(message.createdAt, ".")} +
+ ); } diff --git a/src/features/message/components/messages-grid.jsx b/src/features/message/components/messages-grid.jsx index 7aae610..2c5618d 100644 --- a/src/features/message/components/messages-grid.jsx +++ b/src/features/message/components/messages-grid.jsx @@ -62,9 +62,10 @@ function MessagesGrid({ isEditing, messages, onDelete, onInfiniteScroll }) { <> - {messages.map((message) => ( + {messages.map((message, index) => ( Date: Sun, 24 Aug 2025 17:55:34 +0900 Subject: [PATCH 07/13] =?UTF-8?q?feat=20[#49]=20MessagedCard=EC=97=90=20ho?= =?UTF-8?q?ver=20=ED=95=A0=20=EB=95=8C=20scale=20transition=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/message/components/message-card-add.jsx | 1 + src/features/message/components/message-card-base.jsx | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/features/message/components/message-card-add.jsx b/src/features/message/components/message-card-add.jsx index 0f36b61..be1d730 100644 --- a/src/features/message/components/message-card-add.jsx +++ b/src/features/message/components/message-card-add.jsx @@ -22,6 +22,7 @@ const StyledMessageCardAdd = styled.button` justify-content: center; align-items: center; min-height: 280px; + cursor: pointer; ${media.tablet} { min-height: 284px; diff --git a/src/features/message/components/message-card-base.jsx b/src/features/message/components/message-card-base.jsx index e431d4b..7fe7386 100644 --- a/src/features/message/components/message-card-base.jsx +++ b/src/features/message/components/message-card-base.jsx @@ -19,9 +19,13 @@ const StyledMessageCardBase = styled.div` background-color: white; border-radius: 16px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.08); - cursor: pointer; overflow: hidden; animation: ${mountAnimation} 900ms ${animationDelay} backwards; + transition: transform 300ms; + + &:hover { + transform: scale(1.02); + } `; function MessageCardBase({ index, children }) { From 82d9c59040d0a3b01b72df41c90edc7f8e845fa1 Mon Sep 17 00:00:00 2001 From: Chamsol Kim Date: Sun, 24 Aug 2025 18:01:17 +0900 Subject: [PATCH 08/13] =?UTF-8?q?feat=20[#49]=20Editing=20mode=EC=97=90?= =?UTF-8?q?=EC=84=9C=EB=8A=94=20hover=20transition=20=EC=A0=81=EC=9A=A9=20?= =?UTF-8?q?=EC=95=88=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../message/components/message-card-base.jsx | 23 ++++++++++++++----- .../message/components/message-card.jsx | 2 +- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/features/message/components/message-card-base.jsx b/src/features/message/components/message-card-base.jsx index 7fe7386..9ec43a6 100644 --- a/src/features/message/components/message-card-base.jsx +++ b/src/features/message/components/message-card-base.jsx @@ -21,16 +21,27 @@ const StyledMessageCardBase = styled.div` box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.08); overflow: hidden; animation: ${mountAnimation} 900ms ${animationDelay} backwards; - transition: transform 300ms; - &:hover { - transform: scale(1.02); - } + ${({ $useScaleTransform }) => + $useScaleTransform + ? ` + transition: transform 300ms; + + &:hover { + transform: scale(1.02); + } + ` + : ""}; `; -function MessageCardBase({ index, children }) { +function MessageCardBase({ useScaleTransform, index, children }) { return ( - {children} + + {children} + ); } diff --git a/src/features/message/components/message-card.jsx b/src/features/message/components/message-card.jsx index 331b695..6a355da 100644 --- a/src/features/message/components/message-card.jsx +++ b/src/features/message/components/message-card.jsx @@ -63,7 +63,7 @@ function MessageCard({ isEditing, index, message, onClick, onDelete }) { }; return ( - +
Date: Sun, 24 Aug 2025 22:52:24 +0900 Subject: [PATCH 09/13] =?UTF-8?q?feat=20[#49]=20Component=EC=97=90=20mount?= =?UTF-8?q?=20=EB=B0=8F=20unmount=20=ED=95=A0=20=EB=96=84=20animation?= =?UTF-8?q?=EC=9D=84=20=EC=A0=81=EC=9A=A9=ED=95=98=EB=8A=94=20custom=20hoo?= =?UTF-8?q?k=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/use-animated-mount.jsx | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/hooks/use-animated-mount.jsx diff --git a/src/hooks/use-animated-mount.jsx b/src/hooks/use-animated-mount.jsx new file mode 100644 index 0000000..630432a --- /dev/null +++ b/src/hooks/use-animated-mount.jsx @@ -0,0 +1,24 @@ +import { useState } from "react"; + +function useAnimatedMount() { + const [isMount, setMount] = useState(false); + const [isOpen, setOpen] = useState(false); + + const setShows = (shows) => { + if (shows) { + setMount(true); + setOpen(true); + } else { + setOpen(false); + } + }; + + const onAnimationEnd = () => { + if (isOpen) return; + setMount(false); + }; + + return { isMount, isOpen, setShows, onAnimationEnd }; +} + +export { useAnimatedMount }; From 60c2cfe748d8ce94f1394f9eb2d103839c04fcc7 Mon Sep 17 00:00:00 2001 From: Chamsol Kim Date: Sun, 24 Aug 2025 22:51:27 +0900 Subject: [PATCH 10/13] =?UTF-8?q?feat=20[#49]=20Toast=20component=EC=97=90?= =?UTF-8?q?=20animated=20mount=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../animated-mount/mount-animation.js | 9 +++++++ src/components/toast/toast.jsx | 25 +++++++++++++++--- .../header/rolling-paper-header.jsx | 10 +++++-- src/hooks/use-toast.jsx | 26 +++++++++++-------- src/tests/test-components-page.jsx | 10 ++++--- 5 files changed, 60 insertions(+), 20 deletions(-) create mode 100644 src/components/animated-mount/mount-animation.js diff --git a/src/components/animated-mount/mount-animation.js b/src/components/animated-mount/mount-animation.js new file mode 100644 index 0000000..40d516a --- /dev/null +++ b/src/components/animated-mount/mount-animation.js @@ -0,0 +1,9 @@ +import { css } from "styled-components"; + +function mountAnimation({ isOpen, open, close }) { + return css` + animation: ${isOpen ? open : close} 300ms both; + `; +} + +export { mountAnimation }; diff --git a/src/components/toast/toast.jsx b/src/components/toast/toast.jsx index 9a276ea..465d4c5 100644 --- a/src/components/toast/toast.jsx +++ b/src/components/toast/toast.jsx @@ -1,6 +1,17 @@ -import styled from "styled-components"; +import styled, { keyframes } from "styled-components"; import checkImage from "../../assets/ic-check-circle-green.svg"; import closeImage from "../../assets/ic-xmark.svg"; +import { mountAnimation } from "../animated-mount/mount-animation"; + +const openAnimation = keyframes` + from { opacity: 0 } + to { opacity: 1 } +`; + +const closeAnimation = keyframes` + from { opacity: 1 } + to { opacity: 0 } +`; const StyledToast = styled.div` background-color: rgba(0, 0, 0, 0.8); @@ -20,6 +31,12 @@ const StyledToast = styled.div` left: 50%; bottom: 70px; transform: translateX(-50%); + ${({ $isOpen }) => + mountAnimation({ + isOpen: $isOpen, + open: openAnimation, + close: closeAnimation, + })}; p { margin: 0; @@ -56,14 +73,14 @@ const IconButton = styled(Icon)` cursor: pointer; `; -function Toast({ message, onDismiss }) { +function Toast({ isOpen, message, onClose, onDismiss }) { return ( - + 확인

{message}

- + 닫기
diff --git a/src/features/rolling-paper/components/header/rolling-paper-header.jsx b/src/features/rolling-paper/components/header/rolling-paper-header.jsx index 6ae573a..3bb87df 100644 --- a/src/features/rolling-paper/components/header/rolling-paper-header.jsx +++ b/src/features/rolling-paper/components/header/rolling-paper-header.jsx @@ -69,7 +69,9 @@ function RollingPaperHeader({ recipientName, messages, }) { - const { showsToast, setShowsToast } = useToast({ timeout: 5000 }); + const { showsToast, isOpen, setShowsToast, onDismiss } = useToast({ + timeout: 5000, + }); const { isDesktop, isMobile } = useMedia(); const [reactions, setReactions] = useState([]); @@ -104,6 +106,8 @@ function RollingPaperHeader({ setShowsToast(true); }; + const handleToastClose = () => setShowsToast(false); + useEffect(() => { updateReactions(); }, [updateReactions]); @@ -137,8 +141,10 @@ function RollingPaperHeader({ {showsToast && ( setShowsToast(false)} + onClose={handleToastClose} + onDismiss={onDismiss} /> )} diff --git a/src/hooks/use-toast.jsx b/src/hooks/use-toast.jsx index 3bb98d4..5ea7a2f 100644 --- a/src/hooks/use-toast.jsx +++ b/src/hooks/use-toast.jsx @@ -1,21 +1,25 @@ -import { useEffect, useState } from "react"; +import { useEffect } from "react"; +import { useAnimatedMount } from "./use-animated-mount"; -function useToast({ timeout = 2500 } = {}) { - const [showsToast, setShowsToast] = useState(false); +function useToast({ timeout = 1000 } = {}) { + const { isMount, isOpen, setShows, onAnimationEnd } = useAnimatedMount(); useEffect(() => { - if (!showsToast) return; + if (!isOpen) return; - const id = setTimeout(() => { - setShowsToast(false); + const timer = setTimeout(() => { + setShows(false); }, timeout); - return () => { - clearTimeout(id); - }; - }, [showsToast, setShowsToast, timeout]); + return () => clearTimeout(timer); + }, [timeout, isOpen, setShows]); - return { showsToast, setShowsToast }; + return { + showsToast: isMount, + isOpen, + setShowsToast: setShows, + onDismiss: onAnimationEnd, + }; } export { useToast }; diff --git a/src/tests/test-components-page.jsx b/src/tests/test-components-page.jsx index 1268df5..d979b15 100644 --- a/src/tests/test-components-page.jsx +++ b/src/tests/test-components-page.jsx @@ -50,10 +50,12 @@ function TestComponentsPage() { }; /* Toast */ - const { showsToast, setShowsToast } = useToast(); + const { showsToast, isOpen, setShowsToast, onDismiss } = useToast({ + timeout: 5000, + }); const handleToastClick = () => setShowsToast(true); - const handleToastDismiss = () => setShowsToast(false); + const handleToastClose = () => setShowsToast(false); /* Modal */ const { showsModal, setShowsModal } = useModal({ @@ -221,8 +223,10 @@ function TestComponentsPage() { /> {showsToast && ( )} From 95f9a108b680d1b579e24d16bc8c586b20a91362 Mon Sep 17 00:00:00 2001 From: Chamsol Kim Date: Sun, 24 Aug 2025 22:53:25 +0900 Subject: [PATCH 11/13] =?UTF-8?q?feat=20[#49]=20Portal=EC=97=90=20mount=20?= =?UTF-8?q?=EB=B0=8F=20unmount=20=ED=95=A0=20=EB=96=84=20animation?= =?UTF-8?q?=EC=9D=84=20=EC=A0=81=EC=9A=A9=ED=95=98=EB=8A=94=20custom=20hoo?= =?UTF-8?q?k=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/use-animated-portal.jsx | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/hooks/use-animated-portal.jsx diff --git a/src/hooks/use-animated-portal.jsx b/src/hooks/use-animated-portal.jsx new file mode 100644 index 0000000..8854368 --- /dev/null +++ b/src/hooks/use-animated-portal.jsx @@ -0,0 +1,27 @@ +import { useState } from "react"; +import { usePortal } from "./use-portal"; + +function useAnimatedPortal({ key }) { + const { isOpen: isPortalOpen, setIsOpen: setIsPortalOpen } = usePortal({ + key, + }); + const [isOpen, setOpen] = useState(false); + + const setShows = (shows) => { + if (shows) { + setIsPortalOpen(true); + setOpen(true); + } else { + setOpen(false); + } + }; + + const onAnimationEnd = () => { + if (isOpen) return; + setIsPortalOpen(false); + }; + + return { isMount: isPortalOpen, isOpen, setShows, onAnimationEnd }; +} + +export { useAnimatedPortal }; From def970ff7c6109f653e63e6fef7d87972829833e Mon Sep 17 00:00:00 2001 From: Chamsol Kim Date: Sun, 24 Aug 2025 23:01:08 +0900 Subject: [PATCH 12/13] =?UTF-8?q?feat=20[#49]=20Modal=20component=EC=97=90?= =?UTF-8?q?=20animated=20mount=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/modal/modal.jsx | 23 ++++++++++++++++--- .../message/components/messages-grid.jsx | 8 +++++-- src/hooks/use-modal.jsx | 14 ++++++++--- src/tests/test-components-page.jsx | 8 +++++-- 4 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/components/modal/modal.jsx b/src/components/modal/modal.jsx index 631f61a..898e122 100644 --- a/src/components/modal/modal.jsx +++ b/src/components/modal/modal.jsx @@ -1,6 +1,17 @@ -import styled from "styled-components"; +import styled, { keyframes } from "styled-components"; +import { mountAnimation } from "../animated-mount/mount-animation"; import Portal from "../portal/portal"; +const openAnimation = keyframes` + from { opacity: 0 } + to { opacity: 1 } +`; + +const closeAnimation = keyframes` + from { opacity: 1 } + to { opacity: 0 } +`; + const Content = styled.div` width: 100%; display: flex; @@ -29,14 +40,20 @@ const ModalContainer = styled.div` display: flex; justify-content: center; align-items: center; + ${({ $isOpen }) => + mountAnimation({ + isOpen: $isOpen, + open: openAnimation, + close: closeAnimation, + })}; `; -function Modal({ shows, children }) { +function Modal({ shows, isOpen, onDismiss, children }) { return ( <> {shows && ( - + {children} diff --git a/src/features/message/components/messages-grid.jsx b/src/features/message/components/messages-grid.jsx index 2c5618d..a77aac6 100644 --- a/src/features/message/components/messages-grid.jsx +++ b/src/features/message/components/messages-grid.jsx @@ -29,7 +29,7 @@ function MessagesGrid({ isEditing, messages, onDelete, onInfiniteScroll }) { const navigate = useNavigate(); const { id } = useParams(); const infiniteScrollTargetRef = useRef(); - const { showsModal, setShowsModal } = useModal({ + const { showsModal, isModalOpen, setShowsModal, onDismissModal } = useModal({ key: "message-modal", }); const [modalMessage, setModalMessage] = useState(null); @@ -74,7 +74,11 @@ function MessagesGrid({ isEditing, messages, onDelete, onInfiniteScroll }) { ))}
- + setShowsToast(false); /* Modal */ - const { showsModal, setShowsModal } = useModal({ + const { showsModal, isModalOpen, setShowsModal, onDismissModal } = useModal({ key: "test-modal", }); const handleModalOpen = () => setShowsModal(true); @@ -236,7 +236,11 @@ function TestComponentsPage() { title="Show Modal" onClick={handleModalOpen} /> - +

This is Modal.

Date: Sun, 24 Aug 2025 23:01:27 +0900 Subject: [PATCH 13/13] =?UTF-8?q?feat=20[#49]=20Modal=20dialog=20component?= =?UTF-8?q?=EC=97=90=20animated=20mount=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/use-modal-dialog.jsx | 4 +++- src/pages/messages-page.jsx | 8 +++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/hooks/use-modal-dialog.jsx b/src/hooks/use-modal-dialog.jsx index 5bde450..056e133 100644 --- a/src/hooks/use-modal-dialog.jsx +++ b/src/hooks/use-modal-dialog.jsx @@ -2,7 +2,7 @@ import { useState } from "react"; import { useModal } from "./use-modal"; function useModalDialog() { - const { showsModal, setShowsModal } = useModal({ + const { showsModal, isModalOpen, setShowsModal, onDismissModal } = useModal({ key: "delete-modal", }); const [title, setTitle] = useState(""); @@ -30,11 +30,13 @@ function useModalDialog() { return { showsDialog: showsModal, + isDialogOpen: isModalOpen, dialogTitle: title, dialogContent: content, openDialog, closeDialog, onPrimaryAction, + onDismissDialog: onDismissModal, }; } diff --git a/src/pages/messages-page.jsx b/src/pages/messages-page.jsx index 707ad50..e97fcd8 100644 --- a/src/pages/messages-page.jsx +++ b/src/pages/messages-page.jsx @@ -102,11 +102,13 @@ function MessagesPage() { const { id } = useParams(); const { showsDialog, + isDialogOpen, dialogTitle, dialogContent, openDialog, closeDialog, onPrimaryAction, + onDismissDialog, } = useModalDialog(); const isEditing = useMemo( @@ -243,7 +245,11 @@ function MessagesPage() { ) : ( {content} - +