Skip to content

Commit ad30670

Browse files
authored
Merge pull request #94 from cskime/feature/#84
[#78, #84] Modal 개선 / 롤링 페이퍼 및 메시지 삭제 전에 확인 Dialog 표시
2 parents ebfcb67 + c15879f commit ad30670

File tree

9 files changed

+258
-87
lines changed

9 files changed

+258
-87
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import styled from "styled-components";
2+
import { PrimaryButton } from "../button/button";
3+
import BUTTON_SIZE from "../button/button-size";
4+
import Colors from "../color/colors";
5+
6+
const Title = styled.h2`
7+
margin: 0;
8+
color: ${Colors.gray(600)};
9+
`;
10+
11+
const Content = styled.p`
12+
margin: 0;
13+
color: ${Colors.gray(600)};
14+
`;
15+
16+
const Action = styled.div`
17+
display: flex;
18+
justify-content: flex-end;
19+
gap: 16px;
20+
`;
21+
22+
const StyledAlertDialog = styled.div`
23+
display: flex;
24+
flex-direction: column;
25+
gap: 24px;
26+
`;
27+
28+
function ModalDialog({ title, content, action }) {
29+
return (
30+
<StyledAlertDialog>
31+
<Title>{title}</Title>
32+
{content && <Content>{content}</Content>}
33+
<Action>
34+
{action ?? <PrimaryButton size={BUTTON_SIZE.medium} title="확인" />}
35+
</Action>
36+
</StyledAlertDialog>
37+
);
38+
}
39+
40+
export default ModalDialog;

src/components/modal/modal.jsx

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
11
import styled from "styled-components";
2-
import { useModal } from "../../hooks/use-modal";
3-
import { PrimaryButton } from "../button/button";
4-
import BUTTON_SIZE from "../button/button-size";
52
import Portal from "../portal/portal";
63

74
const Content = styled.div`
@@ -34,34 +31,14 @@ const ModalContainer = styled.div`
3431
align-items: center;
3532
`;
3633

37-
const ActionButton = styled.div`
38-
cursor: pointer;
39-
`;
40-
41-
function Modal({ id, action, children }) {
42-
const { showsModal, setShowsModal } = useModal({
43-
id: id,
44-
type: "modal",
45-
});
46-
47-
const handleClick = () => setShowsModal(true);
48-
const handleConfirmClick = () => setShowsModal(false);
49-
34+
function Modal({ shows, children }) {
5035
return (
5136
<>
52-
<ActionButton onClick={handleClick}>{action}</ActionButton>
53-
{showsModal && (
37+
{shows && (
5438
<Portal id="modal">
5539
<ModalContainer>
5640
<StyledModal>
57-
<Content>
58-
{children}
59-
<PrimaryButton
60-
size={BUTTON_SIZE.medium}
61-
title="확인"
62-
onClick={handleConfirmClick}
63-
/>
64-
</Content>
41+
<Content>{children}</Content>
6542
</StyledModal>
6643
</ModalContainer>
6744
</Portal>

src/features/message/components/message-card-detail.jsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import styled from "styled-components";
2+
import { PrimaryButton } from "../../../components/button/button";
3+
import BUTTON_SIZE from "../../../components/button/button-size";
24
import Colors from "../../../components/color/colors";
35
import { formatDate } from "../../../utils/formatter";
46
import MessageSender from "./message-sender";
57

68
const Header = styled.header`
9+
width: 100%;
710
display: flex;
811
justify-content: space-between;
912
align-items: center;
@@ -12,6 +15,7 @@ const Header = styled.header`
1215
`;
1316

1417
const Content = styled.div`
18+
width: 100%;
1519
margin-top: 16px;
1620
font-size: 18px;
1721
font-weight: 400;
@@ -34,16 +38,24 @@ const Content = styled.div`
3438
}
3539
`;
3640

41+
const Action = styled.div`
42+
padding-top: 24px;
43+
`;
44+
3745
const CreatedDate = styled.span`
3846
font-size: 14px;
3947
font-weight: 400;
4048
line-height: 20px;
4149
color: ${Colors.gray(400)};
4250
`;
4351

44-
const StyledMessageCardDetail = styled.div``;
52+
const StyledMessageCardDetail = styled.div`
53+
display: flex;
54+
flex-direction: column;
55+
align-items: center;
56+
`;
4557

46-
function MessageCardDetail({ message }) {
58+
function MessageCardDetail({ message, onConfirm }) {
4759
return (
4860
<StyledMessageCardDetail>
4961
<Header>
@@ -55,6 +67,13 @@ function MessageCardDetail({ message }) {
5567
<CreatedDate>{formatDate(message.createdAt, ".")}</CreatedDate>
5668
</Header>
5769
<Content>{message.content}</Content>
70+
<Action>
71+
<PrimaryButton
72+
size={BUTTON_SIZE.medium}
73+
title="확인"
74+
onClick={onConfirm}
75+
/>
76+
</Action>
5877
</StyledMessageCardDetail>
5978
);
6079
}

src/features/message/components/message-card.jsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,22 @@ const StyledMessageCard = styled.article`
5050
border-radius: 16px;
5151
background-color: white;
5252
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.08);
53+
cursor: ${({ $isEditing }) => ($isEditing ? "default" : "pointer")};
5354
`;
5455

55-
function MessageCard({ isEditing, message, onDelete }) {
56+
function MessageCard({ isEditing, message, onClick, onDelete }) {
57+
const handleClick = () => {
58+
if (isEditing) return;
59+
onClick(message);
60+
};
61+
62+
const handleDeleteClick = (event) => {
63+
event.stopPropagation();
64+
onDelete(message);
65+
};
66+
5667
return (
57-
<StyledMessageCard>
68+
<StyledMessageCard $isEditing={isEditing} onClick={handleClick}>
5869
<Header>
5970
<MessageSender
6071
profileImageUrl={message.profileImageURL}
@@ -65,7 +76,7 @@ function MessageCard({ isEditing, message, onDelete }) {
6576
<OutlinedButton
6677
size={BUTTON_SIZE.medium}
6778
icon={deleteImage}
68-
onClick={onDelete}
79+
onClick={handleDeleteClick}
6980
/>
7081
)}
7182
</Header>

src/features/message/components/messages-grid.jsx

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { useRef } from "react";
1+
import { useRef, useState } from "react";
22
import { useNavigate, useParams } from "react-router";
33
import styled from "styled-components";
44
import Modal from "../../../components/modal/modal.jsx";
55
import { useIntersectionObserver } from "../../../hooks/use-intersection-observer.jsx";
6+
import { useModal } from "../../../hooks/use-modal.jsx";
67
import { media } from "../../../utils/media.js";
78
import MessageCardAdd from "./message-card-add.jsx";
89
import MessageCardDetail from "./message-card-detail.jsx";
@@ -28,6 +29,10 @@ function MessagesGrid({ isEditing, messages, onDelete, onInfiniteScroll }) {
2829
const navigate = useNavigate();
2930
const { id } = useParams();
3031
const infiniteScrollTargetRef = useRef();
32+
const { showsModal, setShowsModal } = useModal({
33+
key: "message-modal",
34+
});
35+
const [modalMessage, setModalMessage] = useState(null);
3136

3237
const observerCallback = (entry) => {
3338
if (!entry.isIntersecting) return;
@@ -39,33 +44,42 @@ function MessagesGrid({ isEditing, messages, onDelete, onInfiniteScroll }) {
3944
navigate(`/post/${id}/message`);
4045
};
4146

42-
const handleDeleteClick = (messageId) => {
43-
onDelete(messageId);
47+
const handleMessageClick = (message) => {
48+
setShowsModal(true);
49+
setModalMessage(message);
4450
};
4551

46-
const messageCard = (message) => (
47-
<MessageCard
48-
key={message.id}
49-
isEditing={isEditing}
50-
message={message}
51-
onDelete={() => handleDeleteClick(message.id)}
52-
/>
53-
);
52+
const handleDeleteClick = (message) => {
53+
onDelete(message);
54+
};
55+
56+
const handleModalConfirm = () => {
57+
setShowsModal(false);
58+
setModalMessage(null);
59+
};
5460

5561
return (
56-
<StyledRollingPaperMessagesGrid>
57-
<MessageCardAdd onClick={handleAddClick} />
58-
{messages.map((message) =>
59-
isEditing ? (
60-
messageCard(message)
61-
) : (
62-
<Modal key={message.id} id={message.id} action={messageCard(message)}>
63-
<MessageCardDetail message={message} />
64-
</Modal>
65-
)
66-
)}
67-
<div ref={infiniteScrollTargetRef}></div>
68-
</StyledRollingPaperMessagesGrid>
62+
<>
63+
<StyledRollingPaperMessagesGrid>
64+
<MessageCardAdd onClick={handleAddClick} />
65+
{messages.map((message) => (
66+
<MessageCard
67+
key={message.id}
68+
isEditing={isEditing}
69+
message={message}
70+
onClick={handleMessageClick}
71+
onDelete={handleDeleteClick}
72+
/>
73+
))}
74+
<div ref={infiniteScrollTargetRef}></div>
75+
</StyledRollingPaperMessagesGrid>
76+
<Modal shows={showsModal && modalMessage != null}>
77+
<MessageCardDetail
78+
message={modalMessage}
79+
onConfirm={handleModalConfirm}
80+
/>
81+
</Modal>
82+
</>
6983
);
7084
}
7185

src/hooks/use-modal-dialog.jsx

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { useState } from "react";
2+
import { useModal } from "./use-modal";
3+
4+
function useModalDialog() {
5+
const { showsModal, setShowsModal } = useModal({
6+
key: "delete-modal",
7+
});
8+
const [title, setTitle] = useState("");
9+
const [content, setContent] = useState("");
10+
const [primaryAction, setPrimaryAction] = useState(null);
11+
12+
const openDialog = ({ title, content, primaryAction }) => {
13+
setShowsModal(true);
14+
setTitle(title);
15+
setContent(content);
16+
setPrimaryAction(() => primaryAction);
17+
};
18+
19+
const closeDialog = () => {
20+
setShowsModal(false);
21+
setTitle("");
22+
setContent("");
23+
setPrimaryAction(null);
24+
};
25+
26+
const onPrimaryAction = () => {
27+
primaryAction();
28+
closeDialog();
29+
};
30+
31+
return {
32+
showsDialog: showsModal,
33+
dialogTitle: title,
34+
dialogContent: content,
35+
openDialog,
36+
closeDialog,
37+
onPrimaryAction,
38+
};
39+
}
40+
41+
export { useModalDialog };

src/hooks/use-modal.jsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { usePortal } from "./use-portal";
22

3-
function useModal({ id, type }) {
4-
const key = `${type}_${id}`;
3+
function useModal({ key }) {
54
const { isOpen, setIsOpen } = usePortal({ key });
65
return { showsModal: isOpen, setShowsModal: setIsOpen };
76
}

0 commit comments

Comments
 (0)