-
Notifications
You must be signed in to change notification settings - Fork 3
댓글 내역 컴포넌트 구현 #30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
댓글 내역 컴포넌트 구현 #30
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,137 @@ | ||
| import type { Meta, StoryObj } from '@storybook/nextjs-vite'; | ||
|
|
||
| import { fn } from 'storybook/test'; | ||
|
|
||
| import CommentCard from './CommentCard'; | ||
|
|
||
| const meta = { | ||
| title: 'Components/CommentCard', | ||
| component: CommentCard, | ||
| parameters: { | ||
| layout: 'centered', | ||
| }, | ||
| tags: ['autodocs'], | ||
| args: { | ||
| name: '안해나', | ||
| content: '오늘 할 일 목록을 정리했습니다.', | ||
| date: '2025.01.15', | ||
| }, | ||
| decorators: [ | ||
| (Story) => ( | ||
| <div style={{ width: 460 }}> | ||
| <Story /> | ||
| </div> | ||
| ), | ||
| ], | ||
| } satisfies Meta<typeof CommentCard>; | ||
|
|
||
| export default meta; | ||
| type Story = StoryObj<typeof meta>; | ||
|
|
||
| export const Default: Story = {}; | ||
|
|
||
| export const WithProfileImage: Story = { | ||
| args: { | ||
| profileImage: ( | ||
| <div style={{ width: 32, height: 32, borderRadius: 12, background: '#cbd5e1' }} /> | ||
| ), | ||
| }, | ||
| }; | ||
|
|
||
| export const WithIcon: Story = { | ||
| args: { | ||
| profileImage: ( | ||
| <div style={{ width: 32, height: 32, borderRadius: 12, background: '#cbd5e1' }} /> | ||
| ), | ||
| icon: ( | ||
| <button | ||
| type="button" | ||
| onClick={fn()} | ||
| style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 0, fontSize: 16 }} | ||
| > | ||
| ⋮ | ||
| </button> | ||
| ), | ||
| }, | ||
| }; | ||
|
|
||
| export const WithActions: Story = { | ||
| args: { | ||
| profileImage: ( | ||
| <div style={{ width: 32, height: 32, borderRadius: 12, background: '#cbd5e1' }} /> | ||
| ), | ||
| icon: ( | ||
| <button | ||
| type="button" | ||
| onClick={fn()} | ||
| style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 0, fontSize: 16 }} | ||
| > | ||
| ⋮ | ||
| </button> | ||
| ), | ||
| actions: ( | ||
| <div style={{ display: 'flex', gap: 8 }}> | ||
| <button | ||
| type="button" | ||
| onClick={fn()} | ||
| style={{ | ||
| background: 'none', | ||
| border: 'none', | ||
| cursor: 'pointer', | ||
| fontSize: 12, | ||
| color: '#64748b', | ||
| }} | ||
| > | ||
| 취소 | ||
| </button> | ||
| <button | ||
| type="button" | ||
| onClick={fn()} | ||
| style={{ | ||
| background: 'none', | ||
| border: 'none', | ||
| cursor: 'pointer', | ||
| fontSize: 12, | ||
| color: '#3b82f6', | ||
| }} | ||
| > | ||
| 수정하기 | ||
| </button> | ||
| </div> | ||
| ), | ||
| }, | ||
| }; | ||
|
|
||
| export const Overview: Story = { | ||
| render: () => ( | ||
| <div style={{ display: 'flex', flexDirection: 'column', gap: 16, width: 460 }}> | ||
| <CommentCard name="안해나" content="기본 댓글입니다." date="2025.01.15" /> | ||
| <CommentCard | ||
| name="안해나" | ||
| content="프로필 이미지가 있는 댓글입니다." | ||
| date="2025.01.15" | ||
| profileImage={ | ||
| <div style={{ width: 32, height: 32, borderRadius: 12, background: '#cbd5e1' }} /> | ||
| } | ||
| /> | ||
| <CommentCard | ||
| name="안해나" | ||
| content="모든 슬롯이 채워진 댓글입니다." | ||
| date="2025.01.15" | ||
| profileImage={ | ||
| <div style={{ width: 32, height: 32, borderRadius: 12, background: '#cbd5e1' }} /> | ||
| } | ||
| icon={<span style={{ fontSize: 16, cursor: 'pointer' }}>⋮</span>} | ||
| actions={ | ||
| <div style={{ display: 'flex', gap: 8 }}> | ||
| <span style={{ fontSize: 12, color: '#64748b', cursor: 'pointer' }}>취소</span> | ||
| <span style={{ fontSize: 12, color: '#3b82f6', cursor: 'pointer' }}>수정하기</span> | ||
|
Comment on lines
+127
to
+128
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Overview 스토리의
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. span으로 자리만 잡아둔 것이라 버튼 주입하여 사용하면 됩니다. |
||
| </div> | ||
| } | ||
| /> | ||
| </div> | ||
| ), | ||
| parameters: { | ||
| controls: { disable: true }, | ||
| }, | ||
| }; | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,40 @@ | ||||||
| import styles from './styles/CommentCard.module.css'; | ||||||
| import type { CommentCardProps } from './types/types'; | ||||||
|
|
||||||
| /** | ||||||
| * 댓글 카드 컴포넌트. | ||||||
| * 프로필 이미지, 이름, 내용, 날짜를 표시합니다. | ||||||
| * icon 슬롯에 케밥 메뉴를, actions 슬롯에 수정/취소 버튼을 주입할 수 있습니다. | ||||||
| */ | ||||||
| export default function CommentCard({ | ||||||
| profileImage, | ||||||
| name, | ||||||
| content, | ||||||
| date, | ||||||
| dateTime, | ||||||
| icon, | ||||||
| actions, | ||||||
| }: CommentCardProps) { | ||||||
| return ( | ||||||
| <article className={styles.card}> | ||||||
| {profileImage && ( | ||||||
| <div className={styles.avatar} aria-hidden="true"> | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| {profileImage} | ||||||
| </div> | ||||||
| )} | ||||||
| <div className={styles.body}> | ||||||
| <div className={styles.header}> | ||||||
| <span className={styles.name}>{name}</span> | ||||||
| {icon} | ||||||
| </div> | ||||||
| <p className={styles.content}>{content}</p> | ||||||
| <div className={styles.footer}> | ||||||
| <time className={styles.date} dateTime={dateTime}> | ||||||
| {date} | ||||||
| </time> | ||||||
| {actions} | ||||||
| </div> | ||||||
| </div> | ||||||
| </article> | ||||||
| ); | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| export { default as CommentCard } from './CommentCard'; | ||
| export type { CommentCardProps } from './types/types'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| .card { | ||
| display: flex; | ||
| gap: 12px; | ||
| } | ||
|
|
||
| .avatar { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
.avatar {
width: 32px;
height: 32px; |
||
| flex-shrink: 0; | ||
| } | ||
|
|
||
| .body { | ||
| flex: 1; | ||
| min-width: 0; | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: 4px; | ||
| } | ||
|
|
||
| .header { | ||
| display: flex; | ||
| justify-content: space-between; | ||
| align-items: center; | ||
| } | ||
|
|
||
| .name { | ||
| font-weight: 600; | ||
| font-size: 14px; | ||
| color: var(--color-text-tertiary); | ||
| } | ||
|
|
||
| .content { | ||
| font-weight: 500; | ||
| font-size: 14px; | ||
| color: var(--color-text-secondary); | ||
| margin: 0; | ||
| } | ||
|
|
||
| .footer { | ||
| display: flex; | ||
| justify-content: space-between; | ||
| align-items: center; | ||
| } | ||
|
|
||
| .date { | ||
| font-weight: 400; | ||
| font-size: 12px; | ||
| color: var(--color-text-disabled); | ||
| } | ||
|
|
||
| @media (max-width: 375px) { | ||
| .card { | ||
| gap: 8px; | ||
| } | ||
|
|
||
| .avatar { | ||
| width: 24px; | ||
| height: 24px; | ||
| } | ||
|
|
||
| .name { | ||
| font-size: 12px; | ||
| } | ||
|
|
||
| .content { | ||
| font-size: 13px; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,18 @@ | ||||||
| import type { ReactNode } from 'react'; | ||||||
|
|
||||||
| export type CommentCardProps = { | ||||||
| /** 프로필 이미지 (ReactNode로 자유롭게 주입, 예: `<Image />`) */ | ||||||
| profileImage?: ReactNode; | ||||||
| /** 댓글 작성자 이름 */ | ||||||
| name: string; | ||||||
| /** 댓글 본문 내용 */ | ||||||
| content: string; | ||||||
| /** 화면에 표시할 날짜 텍스트 (예: "2024년 7월 29일") */ | ||||||
| date: string; | ||||||
| /** `<time>` 태그의 datetime 속성값 (예: "2024-07-29") */ | ||||||
| dateTime?: string; | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| /** 우측 상단 아이콘 슬롯 (예: 케밥 메뉴 버튼) */ | ||||||
| icon?: ReactNode; | ||||||
| /** 하단 우측 액션 슬롯 (예: 취소/수정하기 버튼) */ | ||||||
| actions?: ReactNode; | ||||||
| }; | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overview 스토리에서
icon슬롯에<span>태그를 사용하고cursor: 'pointer'스타일을 적용했습니다. 이는 시각적으로는 클릭 가능한 요소처럼 보이지만, 스크린 리더 사용자에게는 버튼으로 인식되지 않아 접근성 문제가 발생할 수 있습니다. 상호작용이 필요한 요소는<button>태그를 사용하는 것이 좋습니다.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
span으로 자리만 잡아둔 것이라 아이콘 주입하여 사용하면 됩니다.