-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
- Loading branch information
Showing
5 changed files
with
233 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,13 @@ | ||
import styled from "@emotion/styled"; | ||
import { TextButtonWrapper } from "components/atoms/Button/TextButton/styled"; | ||
|
||
export const KebabMenuWrapper: ReturnType<typeof styled.div> = styled.div` | ||
display: flex; | ||
flex-direction: column; | ||
justifiy-content: center; | ||
justify-content: center; | ||
align-items: center; | ||
${TextButtonWrapper} { | ||
width: 100%; | ||
} | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import { IconButton, Image, Text } from "components/atoms"; | ||
import { CommentItemWrapper } from "./styled"; | ||
import { KebabIcon } from "components/atoms/Icon"; | ||
import { getRelativeTime } from "utils"; | ||
import { useEffect, useRef, useState } from "react"; | ||
import { useKebabMenuManager } from "hooks/useKebabMenuManager"; | ||
import { KebabMenu } from "components/molecules"; | ||
|
||
interface ICommentItemProps { | ||
/** 댓글 아이디 */ | ||
commentId: number; | ||
/** 프로필 img Url */ | ||
imgUrl: string; | ||
/** 작성자 닉네임 */ | ||
nickname: string; | ||
/** 작성 날짜*/ | ||
createdAt: string; | ||
/** 내용 */ | ||
content: string; | ||
/** 해당 댓글이 내가 작성한 댓글인지 여부 */ | ||
isMyComment: boolean; | ||
} | ||
|
||
export const CommentItem = ({ | ||
commentId, | ||
imgUrl, | ||
nickname, | ||
createdAt, | ||
content, | ||
isMyComment, | ||
}: ICommentItemProps) => { | ||
const [isMenuOpen, setIsMenuOpen] = useState(false); | ||
const menuRef = useRef<HTMLDivElement | null>(null); | ||
const time = getRelativeTime(createdAt); | ||
const { getMenus } = useKebabMenuManager(); | ||
const menus = getMenus(isMyComment ? "myComment" : "notMyComment"); | ||
|
||
useEffect(() => { | ||
/** | ||
* 메뉴 영역 외 클릭 감지해서 메뉴 닫는 함수 | ||
* @param event : MouseEvent | ||
* @return void | ||
*/ | ||
const handleOutsideClick = (event: MouseEvent) => { | ||
if ( | ||
isMenuOpen && | ||
menuRef.current && | ||
!menuRef.current.contains(event.target as Node) | ||
) { | ||
setIsMenuOpen(false); | ||
} | ||
}; | ||
|
||
document.addEventListener("mousedown", handleOutsideClick); | ||
return () => { | ||
document.removeEventListener("mousedown", handleOutsideClick); | ||
}; | ||
}, []); | ||
|
||
/** | ||
* 케밥 메뉴 여는 함수 | ||
* @param void | ||
* @return void | ||
*/ | ||
const handleMenuClick = () => { | ||
setIsMenuOpen(true); | ||
}; | ||
|
||
return ( | ||
<CommentItemWrapper key={commentId}> | ||
<Image url={imgUrl} type="round"></Image> | ||
<div className="content-con"> | ||
<div className="writer-create-con"> | ||
<Text content={nickname}></Text> | ||
<span className="separator">|</span> | ||
<Text content={time}></Text> | ||
</div> | ||
<Text content={content}></Text> | ||
</div> | ||
<IconButton | ||
backgroundColor="transparent" | ||
icon={KebabIcon} | ||
onClick={handleMenuClick} | ||
></IconButton> | ||
<div ref={menuRef}>{isMenuOpen && <KebabMenu menus={menus} />}</div> | ||
</CommentItemWrapper> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import type { Meta, StoryObj } from "@storybook/react"; | ||
import { CommentItem } from "."; | ||
import { DEFAULT_IMG_PATH } from "constants/imgPath"; | ||
|
||
const meta: Meta<typeof CommentItem> = { | ||
title: "Organisms/CommentItem", | ||
component: CommentItem, | ||
tags: ["autodocs"], | ||
}; | ||
|
||
type Story = StoryObj<typeof meta>; | ||
|
||
export const Default: Story = { | ||
args: { | ||
commentId: 1, | ||
imgUrl: DEFAULT_IMG_PATH, | ||
nickname: "작성자", | ||
createdAt: new Date().toString(), | ||
content: "내용", | ||
isMyComment: false, | ||
}, | ||
}; | ||
|
||
export const LongContent: Story = { | ||
args: { | ||
commentId: 1, | ||
imgUrl: DEFAULT_IMG_PATH, | ||
nickname: "작성자", | ||
createdAt: new Date().toString(), | ||
content: | ||
"내용내용내용내용내용내용내용내용내용내용내용내용내용내용내용내용용내용내용내용내용내용내용내용내용내용내용내용내용내용내용내용내용용", | ||
isMyComment: false, | ||
}, | ||
}; | ||
|
||
export const MyComment: Story = { | ||
args: { | ||
commentId: 1, | ||
imgUrl: DEFAULT_IMG_PATH, | ||
nickname: "작성자", | ||
createdAt: new Date().toString(), | ||
content: | ||
"내용내용내용내용내용내용내용내용내용내용내용내용내용내용내용내용용내용내용내용내용내용내용내용내용내용내용내용내용내용내용내용내용용", | ||
isMyComment: true, | ||
}, | ||
}; | ||
export default meta; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import styled from "@emotion/styled"; | ||
import { IconButtonWrapper } from "components/atoms/Button/IconButton/styled"; | ||
|
||
import { ImageWrapper } from "components/atoms/Image/styled"; | ||
import { KebabMenuWrapper } from "components/molecules/KebabMenu/styled"; | ||
|
||
export const CommentItemWrapper: ReturnType<typeof styled.div> = styled.div` | ||
display: flex; | ||
gap: 8px; | ||
position: relative; | ||
.content-con { | ||
display: flex; | ||
flex-direction: column; | ||
} | ||
${ImageWrapper} { | ||
width: 55px; | ||
} | ||
.content-con { | ||
display: flex; | ||
justify-content: center; | ||
flex: 1; | ||
} | ||
.writer-create-con { | ||
display: flex; | ||
flex-direction: row; | ||
white-space: pre-wrap; | ||
} | ||
.separator { | ||
padding: 4px; | ||
} | ||
${KebabMenuWrapper} { | ||
position: absolute; | ||
right: 0; | ||
transform: translate(-20%, 10%); | ||
} | ||
${IconButtonWrapper} { | ||
height: max-content; | ||
} | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import { IMenu } from "components/molecules/KebabMenu"; | ||
import { useCallback } from "react"; | ||
|
||
export const useKebabMenuManager = () => { | ||
const onReply = useCallback(() => { | ||
console.log("답글 클릭"); | ||
}, []); | ||
|
||
const onEdit = useCallback(() => { | ||
console.log("수정하기 클릭"); | ||
}, []); | ||
|
||
const onDelete = useCallback(() => { | ||
console.log("삭제하기 클릭"); | ||
}, []); | ||
|
||
const onBlock = useCallback(() => { | ||
console.log("차단하기 클릭"); | ||
}, []); | ||
const onRepot = useCallback(() => { | ||
console.log("신고하기 클릭"); | ||
}, []); | ||
const getMenus = useCallback( | ||
(scenario: string): IMenu[] => { | ||
switch (scenario) { | ||
/** CommentItem에서 내 코멘트인 경우 */ | ||
case "myComment": | ||
return [ | ||
{ content: "답글", onClick: onReply }, | ||
{ content: "수정하기", onClick: onEdit }, | ||
{ content: "삭제하기", onClick: onDelete }, | ||
]; | ||
/** CommentItem에서 내 코멘트 아닌 경우 */ | ||
case "notMyComment": | ||
return [ | ||
{ content: "답글", onClick: onReply }, | ||
{ content: "차단하기", onClick: onBlock }, | ||
{ content: "신고하기", onClick: onRepot }, | ||
]; | ||
default: | ||
return []; | ||
} | ||
}, | ||
[onBlock, onDelete, onEdit, onReply, onRepot] | ||
); | ||
|
||
return { getMenus }; | ||
}; |