Skip to content

Commit 30c2043

Browse files
authored
Merge branch 'main' into changeset-release/main
2 parents 21fc7e3 + de681a8 commit 30c2043

File tree

9 files changed

+154
-37
lines changed

9 files changed

+154
-37
lines changed

.changeset/rotten-numbers-clean.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@clab-platforms/member": minor
3+
"@clab-platforms/icon": minor
4+
---
5+
6+
feat(member): 댓글 이모지 기능 추가, 게시물 반응 디자인 수정

apps/member/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"@tanstack/react-query-devtools": "^5.66.0",
2424
"dayjs": "^1.11.10",
2525
"framer-motion": "^10.18.0",
26+
"frimousse": "^0.1.0",
2627
"jotai": "^2.11.3",
2728
"react": "^19.0.0",
2829
"react-dom": "^19.0.0",

apps/member/src/components/common/Comment/Comment.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { MODAL_ACCEPT, MODAL_CONTENT, MODAL_TITLE } from '@constants/modal';
55
import { useModal } from '@hooks/common/useModal';
66
import { useAccusesMutation, useCommentDeleteMutation } from '@hooks/queries';
77
import { useCommentLikesMutation } from '@hooks/queries/comment/useCommentLikesMutation';
8-
import { formattedDate } from '@utils/date';
8+
import { formattedDate, toKoreaISOString } from '@utils/date';
99
import { formatMemberName } from '@utils/string';
1010

1111
import type { CommentListItem } from '@type/comment';
@@ -96,7 +96,9 @@ const Comment = ({
9696
</p>
9797
)}
9898
<div className="flex justify-end gap-4">
99-
<p className="text-gray-500">{formattedDate(createdAt)}</p>
99+
<p className="text-gray-500">
100+
{formattedDate(toKoreaISOString(createdAt))}
101+
</p>
100102

101103
{isOwner ? (
102104
<ActionButton onClick={() => handleDeleteClick(id)}>

apps/member/src/components/common/CommentInput/CommentInput.tsx

Lines changed: 73 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { useState } from 'react';
22

33
import { Button, Checkbox } from '@clab-platforms/design-system';
4+
import EmojiAdd from '@clab-platforms/icon/src/outline/react/EmojiAdd';
45
import { cn } from '@clab-platforms/utils';
56

67
import { useCommentCreateMutation } from '@hooks/queries';
8+
import { EmojiPicker } from 'frimousse';
79

810
import Textarea from '../Textarea/Textarea';
911

@@ -25,6 +27,7 @@ const CommentInput = ({
2527
const { commentWriteInfo } = useCommentCreateMutation();
2628

2729
const [anonymous, setAnonymous] = useState(false);
30+
const [isShowEmoji, setIsShowEmoji] = useState(false);
2831

2932
const handleAnonymousToggle = () => {
3033
setAnonymous((prev) => !prev);
@@ -42,28 +45,87 @@ const CommentInput = ({
4245
} as React.ChangeEvent<HTMLTextAreaElement>);
4346
};
4447

48+
const handleEmojiButtonClick = (emoji: string) => {
49+
onChange({
50+
target: { value: value + emoji },
51+
} as React.ChangeEvent<HTMLTextAreaElement>);
52+
};
53+
4554
return (
46-
<div className={cn('flex items-center gap-2', className)}>
47-
<Textarea
48-
className="size-full resize-none border p-2"
49-
placeholder="댓글을 입력해주세요."
50-
maxLength={1000}
51-
value={value}
52-
onChange={onChange}
53-
/>
54-
<div className="flex flex-col justify-between text-nowrap">
55+
<>
56+
<div className={cn('flex items-center gap-2', className)}>
57+
<Textarea
58+
className="size-full resize-none border p-2"
59+
placeholder="댓글을 입력해주세요."
60+
maxLength={1000}
61+
value={value}
62+
onChange={onChange}
63+
/>
64+
<div className="relative flex flex-col justify-between gap-2 text-nowrap">
65+
<button
66+
type="button"
67+
className="flex justify-center"
68+
onClick={() => setIsShowEmoji(!isShowEmoji)}
69+
>
70+
<EmojiAdd />
71+
</button>
72+
{isShowEmoji && (
73+
<EmojiPicker.Root
74+
className="absolute right-0 isolate mt-12 flex h-[368px] w-fit flex-col rounded-md bg-white"
75+
onEmojiSelect={(emoji) => handleEmojiButtonClick(emoji.emoji)}
76+
>
77+
<EmojiPicker.Search className="z-10 mx-2 mt-2 appearance-none rounded-md bg-neutral-100 px-2.5 py-2 text-sm" />
78+
<EmojiPicker.Viewport className="outline-hidden relative flex-1">
79+
<EmojiPicker.Loading className="absolute inset-0 flex items-center justify-center text-sm text-neutral-400">
80+
로딩중이예요
81+
</EmojiPicker.Loading>
82+
<EmojiPicker.Empty className="absolute inset-0 flex items-center justify-center text-sm text-neutral-400">
83+
이모티콘이 없어요.
84+
</EmojiPicker.Empty>
85+
<EmojiPicker.List
86+
className="select-none pb-1.5"
87+
components={{
88+
CategoryHeader: ({ category, ...props }) => (
89+
<div
90+
className="bg-white px-3 pb-1.5 pt-3 text-xs font-medium text-neutral-600"
91+
{...props}
92+
>
93+
{category.label}
94+
</div>
95+
),
96+
Row: ({ children, ...props }) => (
97+
<div className="scroll-my-1.5 px-1.5" {...props}>
98+
{children}
99+
</div>
100+
),
101+
Emoji: ({ emoji, ...props }) => (
102+
<button
103+
className="flex size-8 items-center justify-center rounded-md text-lg data-[active]:bg-neutral-100"
104+
{...props}
105+
>
106+
{emoji.emoji}
107+
</button>
108+
),
109+
}}
110+
/>
111+
</EmojiPicker.Viewport>
112+
</EmojiPicker.Root>
113+
)}
114+
</div>
115+
</div>
116+
<div className="mt-2 flex items-center justify-end gap-3">
55117
<Checkbox
56118
id="wantAnonymous"
57119
name="wantAnonymous"
58120
label="익명"
59121
checked={anonymous}
60122
onChange={handleAnonymousToggle}
61123
/>
62-
<Button className="whitespace-nowrap" onClick={handleSubmit}>
124+
<Button className="whitespace-nowrap py-1" onClick={handleSubmit}>
63125
등록
64126
</Button>
65127
</div>
66-
</div>
128+
</>
67129
);
68130
};
69131

apps/member/src/components/common/ReactionButton/ReactionButton.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,15 @@ const ReactionButton = ({
1313
...props
1414
}: ReactionButtonProps) => {
1515
return (
16-
<button className={cn('p-1', className)} {...props}>
16+
<button
17+
className={cn(
18+
'flex items-center gap-2 rounded-full border px-3 hover:bg-blue-200 hover:bg-opacity-20',
19+
className,
20+
)}
21+
{...props}
22+
>
1723
{children}
18-
<p className="font-semibold text-gray-600">{countNumber ?? 0}</p>
24+
<p className="font-medium">{countNumber ?? 0}</p>
1925
</button>
2026
);
2127
};

apps/member/src/components/community/CommunityBoardPost/CommunityBoardPost.tsx

Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useState } from 'react';
22

33
import { Button, Grid } from '@clab-platforms/design-system';
4+
import EmojiAdd from '@clab-platforms/icon/src/outline/react/EmojiAdd';
45
import { toDecodeHTMLEntities } from '@clab-platforms/utils/src/string';
56

67
import Image from '@components/common/Image/Image';
@@ -43,6 +44,7 @@ const EMOJI = [
4344

4445
const CommunityBoardPost = ({ data }: CommunityBoardPostProps) => {
4546
const [isEdit, setIsEdit] = useState(false);
47+
const [isShowReactionModal, setIsShowReactionModal] = useState(false);
4648

4749
const { boardEmojiMutate, isPending } = useBoardEmojiMutation();
4850

@@ -77,39 +79,63 @@ const CommunityBoardPost = ({ data }: CommunityBoardPostProps) => {
7779
<Post.Body className="flex min-h-60 flex-col justify-between">
7880
{data.content}
7981
{data.category === 'development_qna' && data.boardHashtagInfos && (
80-
<div className="mt-8 flex space-x-2">
82+
<div className="mt-8 flex justify-start space-x-2">
8183
{data.boardHashtagInfos?.map(({ id, name }) => (
8284
<div
8385
key={id}
84-
className="rounded-full bg-gray-100 px-4 py-1 font-semibold text-gray-500"
86+
className="rounded-full border border-blue-400 bg-blue-50 px-3 font-semibold text-blue-400"
8587
>
8688
{name}
8789
</div>
8890
))}
8991
</div>
9092
)}
9193
</Post.Body>
92-
<Post.Footer className="flex flex-col items-end">
93-
<Grid gap="lg" col="4" className="mx-auto">
94-
{EMOJI.map(({ name, value }) => {
95-
const countNumber =
96-
data.emojiInfos?.find(
97-
(emoji) => toDecodeHTMLEntities(emoji.emoji) === name,
98-
)?.count ?? 0;
94+
<Post.Footer className="flex justify-between">
95+
<div className="flex gap-2">
96+
<button
97+
type="button"
98+
className="flex size-fit justify-center rounded-full border p-1 hover:bg-gray-100"
99+
onClick={() => setIsShowReactionModal(!isShowReactionModal)}
100+
>
101+
<EmojiAdd />
102+
</button>
103+
{isShowReactionModal && (
104+
<div className="absolute mt-11 flex gap-6 rounded-full border bg-white px-4 py-1 shadow-lg transition">
105+
{EMOJI.map(({ name }) => (
106+
<button
107+
onClick={() => handleReactionButtonClick(data.id, name)}
108+
className="text-xl hover:cursor-pointer"
109+
key={name}
110+
>
111+
{name}
112+
</button>
113+
))}
114+
</div>
115+
)}
116+
<Grid gap="sm" col="4">
117+
{EMOJI.map(({ name, value }) => {
118+
const countNumber =
119+
data.emojiInfos?.find(
120+
(emoji) => toDecodeHTMLEntities(emoji.emoji) === name,
121+
)?.count ?? 0;
99122

100-
return (
101-
<ReactionButton
102-
key={value}
103-
onClick={() => handleReactionButtonClick(data.id, name)}
104-
disabled={isPending}
105-
countNumber={countNumber}
106-
>
107-
<p className="text-3xl">{name}</p>
108-
</ReactionButton>
109-
);
110-
})}
111-
</Grid>
112-
<div>
123+
if (countNumber)
124+
return (
125+
<ReactionButton
126+
key={value}
127+
onClick={() => handleReactionButtonClick(data.id, name)}
128+
disabled={isPending}
129+
countNumber={countNumber}
130+
className="transition"
131+
>
132+
<p className="text-xl">{name}</p>
133+
</ReactionButton>
134+
);
135+
})}
136+
</Grid>
137+
</div>
138+
<div className="flex gap-2">
113139
{data.isOwner ? (
114140
// 작성자인 경우 삭제 버튼을 보여준다.
115141
<CommunityDeleteButton id={data.id} />

packages/icon/src/outline/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export { default as ChevronRightOutline } from './react/ChevronRight';
44
export { default as CloseOutline } from './react/Close';
55
export { default as CommentSolidOutline } from './react/CommentSolid';
66
export { default as DateRangeOutline } from './react/DateRange';
7+
export { default as EmojiAddOutline } from './react/EmojiAdd';
78
export { default as FileEarmarkArrowUpOutline } from './react/FileEarmarkArrowUp';
89
export { default as FileEarmarkDiffOutline } from './react/FileEarmarkDiff';
910
export { default as GridOutline } from './react/Grid';
Lines changed: 1 addition & 0 deletions
Loading

pnpm-lock.yaml

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)