Skip to content

Commit 167ecfd

Browse files
[feat] 댓글 컴포넌트 제작
1 parent 4f1a108 commit 167ecfd

File tree

14 files changed

+254
-0
lines changed

14 files changed

+254
-0
lines changed
14.4 KB
Loading
3.82 KB
Loading
2.29 KB
Loading

src/assets/three_dots.svg

Lines changed: 5 additions & 0 deletions
Loading
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
'use client';
2+
3+
import ThreeDot from '@/assets/three_dots.svg';
4+
import { detailDate } from '@/utils/time';
5+
import { CommentDropdown } from '@/features/comment/components';
6+
import { useDropdown } from '@/hooks/useDropdown';
7+
8+
interface CommentCardProps {
9+
name: string;
10+
text: string;
11+
time: Date;
12+
}
13+
14+
export const CommentCard = ({ name, text, time }: CommentCardProps) => {
15+
const { isDropdownOpen, handleToggleDropdown, dropdownRef, triggerRef } =
16+
useDropdown();
17+
18+
const handleClick = (e: React.MouseEvent) => {
19+
handleToggleDropdown();
20+
e.stopPropagation();
21+
};
22+
return (
23+
<div className='flex flex-col pb-3'>
24+
<div className='relative'>
25+
<div className='flex justify-between pb-2 text-[12px]'>
26+
<div className='flex items-center gap-2'>
27+
<span className='text-sm text-gray-800'>{name}</span>
28+
<span className='text-xs text-gray-600'>{detailDate(time)}</span>
29+
</div>
30+
<div ref={triggerRef} onClick={handleClick}>
31+
<ThreeDot />
32+
</div>
33+
</div>
34+
{isDropdownOpen && (
35+
<div ref={dropdownRef} className='absolute right-0 top-6 z-10'>
36+
<CommentDropdown />
37+
</div>
38+
)}
39+
</div>
40+
<span className='text-[16px] text-white'>{text}</span>
41+
</div>
42+
);
43+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { CommentCard } from './CommentCard';
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
'use client';
2+
3+
import DownArrow from '@/assets/down_arrow.svg';
4+
import TopArrow from '@/assets/top_arrow.svg';
5+
import { StaticImageData } from 'next/image';
6+
import { useState } from 'react';
7+
import Image from 'next/image';
8+
9+
interface FoldableImageProps {
10+
spanText: string;
11+
images: StaticImageData[];
12+
}
13+
14+
export const FoldableImage = ({ spanText, images }: FoldableImageProps) => {
15+
const [isOpen, setIsOpen] = useState(false);
16+
17+
if (images.length <= 3) return null;
18+
19+
if (isOpen)
20+
return (
21+
<div className='flex flex-col'>
22+
<div className='grid grid-cols-3 gap-[3px]'>
23+
{images?.map((image) => (
24+
<Image src={image} width={90} height={90} alt='이미지' />
25+
))}
26+
</div>
27+
<div
28+
className='flex cursor-pointer items-center justify-end gap-2'
29+
onClick={() => setIsOpen(false)}
30+
>
31+
접기 <TopArrow />
32+
</div>
33+
</div>
34+
);
35+
36+
return (
37+
<div className='flex flex-col'>
38+
<div className='grid grid-cols-3 gap-[3px] pb-2'>
39+
{images
40+
?.slice(0, 3)
41+
.map((image) => (
42+
<Image src={image} width={90} height={90} alt='이미지' />
43+
))}
44+
</div>
45+
<div
46+
className='flex cursor-pointer items-center justify-end gap-2'
47+
onClick={() => setIsOpen(true)}
48+
>
49+
<span>{spanText}</span>
50+
<DownArrow />
51+
</div>
52+
</div>
53+
);
54+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { FoldableImage } from './FoldableImage';
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import type { Meta, StoryObj } from '@storybook/react';
2+
import CommentImage from '@/assets/images/comment_image.png';
3+
import DefaultProfile from '@/assets/images/default_profile.png';
4+
import { Comment } from './';
5+
6+
const meta: Meta<typeof Comment> = {
7+
title: 'Components/Comment',
8+
component: Comment,
9+
};
10+
11+
export default meta;
12+
type Story = StoryObj<typeof Comment>;
13+
14+
// 기본 카드
15+
export const Default: Story = {
16+
args: {
17+
name: '예신',
18+
text: '여기 완전 좋다 그치? 여기로 가볼까?',
19+
time: new Date(),
20+
profile: DefaultProfile,
21+
images: [
22+
CommentImage,
23+
CommentImage,
24+
CommentImage,
25+
CommentImage,
26+
CommentImage,
27+
CommentImage,
28+
CommentImage,
29+
CommentImage,
30+
CommentImage,
31+
],
32+
},
33+
decorators: [
34+
(Story) => (
35+
<div className='w-[350px]'>
36+
<Story />
37+
</div>
38+
),
39+
],
40+
};
41+
42+
Default.parameters = {
43+
design: {
44+
type: 'figma',
45+
url: 'https://www.figma.com/design/cr2DuY0vceiMI5LlqWdKR2/Wedvice_%EB%94%94%EC%9E%90%EC%9D%B8?node-id=1493-6711&t=C1C6AY6noGiQkf3h-4',
46+
},
47+
};
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import Image, { StaticImageData } from 'next/image';
2+
import { FoldableImage } from '../../../components/molecules/foldableSpan';
3+
import { CommentCard } from '@/components/molecules/commentCard';
4+
5+
interface CommentProps {
6+
name: string;
7+
profile: StaticImageData;
8+
text: string;
9+
time: Date;
10+
images?: StaticImageData[];
11+
}
12+
13+
export const Comment = ({
14+
name,
15+
profile,
16+
text,
17+
time,
18+
images = [],
19+
}: CommentProps) => {
20+
return (
21+
<div className='relative w-full p-[20px]'>
22+
<div className='flex w-full gap-[12px]'>
23+
<div className='w-[44px]'>
24+
<Image src={profile} width={44} height={44} alt='프로필' />
25+
</div>
26+
<div className='flex-1 text-white'>
27+
<CommentCard name={name} text={text} time={time} />
28+
29+
<FoldableImage
30+
images={images}
31+
spanText={`${images.length - 3}개 더보기`}
32+
/>
33+
</div>
34+
</div>
35+
</div>
36+
);
37+
};

0 commit comments

Comments
 (0)