Skip to content

Conversation

@ujinsim
Copy link
Collaborator

@ujinsim ujinsim commented Oct 6, 2025

🔥 연관 이슈

🚀 작업 내용

스크린샷 2025-10-13 오후 6 17 55

🤔 고민했던 내용

  • 테이블 형태가 현재 조회를 위해서만 사용되어서 별도 기능 없이 조회용으로 제작했습니다
  • Table, TableBody, TableCell, TableHead, TableHeader, TableRow를 조합하여 사용할 수 있도록 하였습니다

💬 리뷰 중점사항

Summary by CodeRabbit

  • 신기능
    • 테이블 UI 컴포넌트 세트 추가: Table, TableHeader, TableBody, TableRow, TableHead, TableCell. 반응형 스크롤 컨테이너와 고정 레이아웃, 기본 타이포그래피/간격 스타일로 재사용성과 조합성 향상.
  • 문서
    • 스토리북에 기본 테이블 예제 추가: 날짜/카테고리/점수 컬럼 표시 및 점수에 소수점 셋째 자리까지 표기 후 “점” 접미사 데모.

@ujinsim ujinsim self-assigned this Oct 6, 2025
@github-actions github-actions bot added the feat label Oct 6, 2025
@coderabbitai
Copy link

coderabbitai bot commented Oct 6, 2025

Walkthrough

새로운 Table UI 컴포넌트 세트(Table, TableHeader, TableBody, TableRow, TableHead, TableCell)와 해당 스토리북 Basic 스토리가 추가되었고, 관련 재내보내기(export)가 인덱스 파일들에 반영되었습니다.

Changes

Cohort / File(s) Summary
Table 컴포넌트
src/shared/ui/Table/Table.tsx
반응형 스크롤 컨테이너 내부에 고정 레이아웃 <table>을 렌더링하는 Table 컴포넌트 추가. 기본/전달된 className 병합 및 children 렌더링.
헤더/바디/행/셀 서브컴포넌트
src/shared/ui/Table/TableHeader.tsx, src/shared/ui/Table/TableBody.tsx, src/shared/ui/Table/TableRow.tsx, src/shared/ui/Table/TableHead.tsx, src/shared/ui/Table/TableCell.tsx
thead/tbody/tr/th/td 래퍼 컴포넌트 추가. 표준 HTML 속성 전달, 클래스 병합, 기본 스타일과 내부 타이포그래피(Body2) 적용.
스토리북 스토리
src/shared/ui/Table/Table.stories.tsx
PrimitiveMeta 기본 내보내기와 Basic 스토리 추가. 샘플 데이터로 헤더와 행을 렌더링하며 Amount는 소수 3자리 포맷 후 "점" 접미사 적용.
모듈 재노출 (barrel)
src/shared/ui/Table/index.ts, src/shared/index.ts
Table 관련 컴포넌트들을 각각의 인덱스 파일에서 재내보내기 추가 (바렐 업데이트).

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor Dev as 스토리북 사용자
    participant SB as Storybook
    participant Story as Basic Story
    participant UI as Table 컴포넌트군

    Dev->>SB: Basic 스토리 선택
    SB->>Story: render()
    Story->>UI: Compose: <Table> -> <TableHeader/> + <TableBody/>
    UI->>UI: TableHeader -> TableRow -> TableHead (헤더 렌더)
    UI->>UI: TableBody -> TableRow* -> TableCell* (데이터 렌더)
    UI-->>SB: DOM 반환
    SB-->>Dev: 미리보기 표시
    note right of UI: 각 컴포넌트는 props 전달 및 클래스 병합만 수행
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

추가로 주의할 파일/영역:

  • Table.stories.tsx — 데이터 포맷(소수 처리, 타임스탬프 표시) 로직 검증.
  • TableCell/TableHead — 타이포그래피(Body2) 사용 일관성 및 접근성(semantic th/td) 확인.
  • index.ts 바렐 — 기존 내보내기와 충돌 여부 확인.

Suggested reviewers

  • yougyung
  • keemsebin

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목 '[FEAT] Table 컴포넌트 제작'은 변경 사항의 주요 내용을 명확하게 요약하고 있습니다.
Description check ✅ Passed PR 설명이 템플릿의 주요 섹션(연관 이슈, 작업 내용, 고민했던 내용)을 모두 포함하고 있습니다.
Linked Issues check ✅ Passed PR의 모든 변경 사항이 이슈 #68의 요구사항을 충족합니다. 테이블 컴포넌트(Table, TableBody, TableCell, TableHead, TableHeader, TableRow)와 스토리북이 모두 구현되었습니다.
Out of Scope Changes check ✅ Passed 모든 변경 사항이 테이블 컴포넌트 개발과 스토리북 작성이라는 연관 이슈의 범위 내에 있습니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#68-table

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 099944d and 27a99fe.

📒 Files selected for processing (1)
  • src/shared/index.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: preview-storybook
🔇 Additional comments (1)
src/shared/index.ts (1)

30-30: LGTM! Table 컴포넌트 export가 올바르게 추가되었습니다.

모든 필요한 Table 하위 컴포넌트들(Table, TableBody, TableCell, TableHead, TableHeader, TableRow)이 올바르게 export되었으며, 기존 코드베이스의 패턴(Accordion, Carousel, Header 등의 다중 컴포넌트 export 방식)과 일관성 있게 작성되었습니다.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ujinsim ujinsim changed the title feat: Table 컴포넌트 제작 [FEAT] Table 컴포넌트 제작 Oct 6, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Oct 6, 2025

Update: 2025년 11월 17일 14시 36분 50초
Storybook 미리보기: https://677a9a76c58a295e9421d3ef-easyfvycjy.chromatic.com/

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2fd31df and 653a29f.

📒 Files selected for processing (6)
  • src/shared/ui/Table/Table.stories.tsx (1 hunks)
  • src/shared/ui/Table/Table.tsx (1 hunks)
  • src/shared/ui/Table/TableBody.tsx (1 hunks)
  • src/shared/ui/Table/TableHead.tsx (1 hunks)
  • src/shared/ui/Table/index.ts (1 hunks)
  • src/shared/ui/Table/types.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
src/shared/ui/Table/TableBody.tsx (1)
src/shared/ui/Table/types.ts (1)
  • Column (3-7)
src/shared/ui/Table/TableHead.tsx (1)
src/shared/ui/Table/types.ts (1)
  • Column (3-7)
src/shared/ui/Table/Table.tsx (4)
src/shared/ui/Table/types.ts (1)
  • Column (3-7)
src/shared/ui/Table/index.ts (1)
  • Table (1-1)
src/shared/ui/Table/TableHead.tsx (1)
  • TableHead (10-20)
src/shared/ui/Table/TableBody.tsx (1)
  • TableBody (11-37)
src/shared/ui/Table/Table.stories.tsx (2)
src/shared/ui/Table/Table.tsx (1)
  • Table (14-31)
src/shared/ui/Table/types.ts (1)
  • Column (3-7)
🔇 Additional comments (4)
src/shared/ui/Table/index.ts (1)

1-1: 깔끔한 배럴 익스포트 구현입니다.

표준적인 모듈 재export 패턴을 따르고 있습니다.

src/shared/ui/Table/types.ts (1)

3-7: 타입 정의가 올바르게 구현되었습니다.

제네릭 타입 제약과 optional render 함수가 적절하게 정의되어 있습니다.

src/shared/ui/Table/TableHead.tsx (1)

10-20: 헤더 컴포넌트가 올바르게 구현되었습니다.

sticky 포지셔닝과 key 생성 로직이 적절합니다.

src/shared/ui/Table/Table.tsx (1)

14-31: Table 컴포넌트가 깔끔하게 구현되었습니다.

TableHead와 TableBody를 적절하게 조합하고 있으며, hideHead prop을 통한 조건부 렌더링이 올바릅니다.

Comment on lines 18 to 19
{data.map((row, rIdx) => (
<Flex key={rIdx} className="items-center gap-4 border-b border-gray-200 px-4 py-2">
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

행 키로 배열 인덱스 사용 시 주의가 필요합니다.

현재 rIdx를 키로 사용하고 있는데, 데이터 배열의 순서가 변경되거나 항목이 추가/삭제될 경우 React의 렌더링 최적화가 제대로 동작하지 않을 수 있습니다. 가능하다면 각 행 데이터에서 고유 식별자(예: id)를 추출하여 키로 사용하는 것을 권장합니다.

다음과 같이 개선할 수 있습니다:

-      {data.map((row, rIdx) => (
-        <Flex key={rIdx} className="items-center gap-4 border-b border-gray-200 px-4 py-2">
+      {data.map((row, rIdx) => {
+        // row에 id 같은 고유 속성이 있다면 사용, 없으면 index 폴백
+        const rowKey = 'id' in row ? String(row.id) : rIdx;
+        return (
+        <Flex key={rowKey} className="items-center gap-4 border-b border-gray-200 px-4 py-2">

또는 Props에 getRowKey?: (row: T, index: number) => string | number 같은 prop을 추가하여 사용자가 키 생성 방식을 제어할 수 있도록 할 수 있습니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{data.map((row, rIdx) => (
<Flex key={rIdx} className="items-center gap-4 border-b border-gray-200 px-4 py-2">
{data.map((row, rIdx) => {
// row에 id 같은 고유 속성이 있다면 사용, 없으면 index 폴백
const rowKey = 'id' in row ? String(row.id) : rIdx;
return (
<Flex key={rowKey} className="items-center gap-4 border-b border-gray-200 px-4 py-2">
🤖 Prompt for AI Agents
In src/shared/ui/Table/TableBody.tsx around lines 18 to 19, the code uses the
array index (rIdx) as the React key which can break rendering when rows are
reordered/added/removed; change the component to derive a stable key from the
row (e.g., row.id or another unique field) and fallback only if absent, or add a
getRowKey?: (row: T, index: number) => string | number prop so callers can
supply a stable key generator; update the map to call getRowKey(row, rIdx) or
use row.id (or a configured unique property) instead of rIdx and ensure the prop
type and default fallback are added to the component props.

Comment on lines 20 to 25
{columns.map((col, cIdx) => {
const value = row[col.key];
const content = col.render ? col.render(value, row) : (value as React.ReactNode);
return (
<Body3
key={cIdx}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

열 키를 개선할 수 있습니다.

현재 cIdx를 열 키로 사용하고 있습니다. 열 순서는 일반적으로 변경되지 않으므로 큰 문제는 아니지만, col.key를 사용하면 더 명시적이고 안정적입니다.

-            key={cIdx}
+            key={String(col.key)}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{columns.map((col, cIdx) => {
const value = row[col.key];
const content = col.render ? col.render(value, row) : (value as React.ReactNode);
return (
<Body3
key={cIdx}
{columns.map((col, cIdx) => {
const value = row[col.key];
const content = col.render ? col.render(value, row) : (value as React.ReactNode);
return (
<Body3
key={String(col.key)}
🤖 Prompt for AI Agents
In src/shared/ui/Table/TableBody.tsx around lines 20 to 25, the column elements
use cIdx as the React key which is less explicit; change the key to use col.key
(or `${col.key}-${cIdx}` as a safe fallback) so keys are stable and meaningful;
ensure col.key is a string/unique identifier and fall back to the index only if
col.key is undefined to avoid warnings.

<Flex key={rIdx} className="items-center gap-4 border-b border-gray-200 px-4 py-2">
{columns.map((col, cIdx) => {
const value = row[col.key];
const content = col.render ? col.render(value, row) : (value as React.ReactNode);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

타입 단언의 안전성을 검토해주세요.

valueReact.ReactNode로 단언하고 있지만, 실제로 모든 타입의 값이 유효한 ReactNode는 아닙니다. 예를 들어 객체, 함수, Symbol 등은 렌더링 시 에러를 발생시킬 수 있습니다.

다음과 같이 개선할 수 있습니다:

-            const content = col.render ? col.render(value, row) : (value as React.ReactNode);
+            const content = col.render 
+              ? col.render(value, row) 
+              : (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' || value === null || value === undefined)
+                ? value
+                : String(value);

또는 Column 타입 정의에서 render가 없을 때는 value가 기본적으로 렌더링 가능한 타입이어야 한다는 제약을 추가할 수 있습니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const content = col.render ? col.render(value, row) : (value as React.ReactNode);
// Before:
// const content = col.render ? col.render(value, row) : (value as React.ReactNode);
// After:
const content = col.render
? col.render(value, row)
: (typeof value === 'string'
|| typeof value === 'number'
|| typeof value === 'boolean'
|| value === null
|| value === undefined)
? value
: String(value);
🤖 Prompt for AI Agents
In src/shared/ui/Table/TableBody.tsx around line 22, the code unsafely asserts
value as React.ReactNode when col.render is absent; validate and handle
non-renderable types instead of blind assertion. Update logic to detect
renderable primitives/React elements (string, number, boolean, null, undefined,
React.isValidElement) and return them directly; for objects/functions/symbols
either call a safe formatter (e.g., JSON.stringify with try/catch or a toString
fallback) or render a placeholder; alternatively tighten the Column type to
require a render function when the value type is not guaranteed to be renderable
so TypeScript enforces safe usage.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/shared/ui/Table/Table.stories.tsx (1)

44-117: 데이터 중복이 여전히 존재합니다.

  • Lines 81-86과 100-104가 동일합니다
  • Lines 87-92와 105-110이 동일합니다
  • Lines 93-98과 111-116이 동일합니다

스크롤 테스트를 위한 의도적인 중복이라면 문제없지만, 실수라면 중복 항목을 제거해 주세요.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 653a29f and 6976ae7.

📒 Files selected for processing (1)
  • src/shared/ui/Table/Table.stories.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/shared/ui/Table/Table.stories.tsx (2)
src/shared/ui/Table/Table.tsx (1)
  • Table (14-31)
src/shared/ui/Table/types.ts (1)
  • Column (3-7)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: preview-storybook
🔇 Additional comments (1)
src/shared/ui/Table/Table.stories.tsx (1)

133-139: 이전 리뷰의 hideHead 누락 이슈가 해결되었습니다.

NoHeader 스토리에 hideHead: true prop이 올바르게 추가되어 헤더 없는 테이블이 정상적으로 렌더링됩니다.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6976ae7 and 8456ee4.

📒 Files selected for processing (9)
  • src/shared/index.ts (1 hunks)
  • src/shared/ui/Table/Table.stories.tsx (1 hunks)
  • src/shared/ui/Table/Table.tsx (1 hunks)
  • src/shared/ui/Table/TableBody.tsx (1 hunks)
  • src/shared/ui/Table/TableCell.tsx (1 hunks)
  • src/shared/ui/Table/TableHead.tsx (1 hunks)
  • src/shared/ui/Table/TableHeader.tsx (1 hunks)
  • src/shared/ui/Table/TableRow.tsx (1 hunks)
  • src/shared/ui/Table/index.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (7)
src/shared/ui/Table/TableHeader.tsx (7)
src/shared/ui/Table/Table.tsx (1)
  • Props (5-15)
src/shared/ui/Table/TableBody.tsx (1)
  • Props (5-8)
src/shared/ui/Table/TableCell.tsx (1)
  • Props (7-10)
src/shared/ui/Table/TableHead.tsx (1)
  • Props (7-10)
src/shared/ui/Table/TableRow.tsx (1)
  • Props (5-8)
src/shared/index.ts (2)
  • TableHeader (25-25)
  • cn (30-30)
src/shared/ui/Table/index.ts (1)
  • TableHeader (2-2)
src/shared/ui/Table/TableRow.tsx (7)
src/shared/ui/Table/Table.tsx (1)
  • Props (5-15)
src/shared/ui/Table/TableBody.tsx (1)
  • Props (5-8)
src/shared/ui/Table/TableCell.tsx (1)
  • Props (7-10)
src/shared/ui/Table/TableHead.tsx (1)
  • Props (7-10)
src/shared/ui/Table/TableHeader.tsx (1)
  • Props (5-8)
src/shared/index.ts (2)
  • TableRow (25-25)
  • cn (30-30)
src/shared/ui/Table/index.ts (1)
  • TableRow (4-4)
src/shared/ui/Table/TableBody.tsx (7)
src/shared/ui/Table/Table.tsx (1)
  • Props (5-15)
src/shared/ui/Table/TableCell.tsx (1)
  • Props (7-10)
src/shared/ui/Table/TableHead.tsx (1)
  • Props (7-10)
src/shared/ui/Table/TableHeader.tsx (1)
  • Props (5-8)
src/shared/ui/Table/TableRow.tsx (1)
  • Props (5-8)
src/shared/index.ts (2)
  • TableBody (25-25)
  • cn (30-30)
src/shared/ui/Table/index.ts (1)
  • TableBody (3-3)
src/shared/ui/Table/TableHead.tsx (7)
src/shared/ui/Table/Table.tsx (1)
  • Props (5-15)
src/shared/ui/Table/TableBody.tsx (1)
  • Props (5-8)
src/shared/ui/Table/TableCell.tsx (1)
  • Props (7-10)
src/shared/ui/Table/TableHeader.tsx (1)
  • Props (5-8)
src/shared/ui/Table/TableRow.tsx (1)
  • Props (5-8)
src/shared/index.ts (3)
  • TableHead (25-25)
  • cn (30-30)
  • Body2 (24-24)
src/shared/ui/Table/index.ts (1)
  • TableHead (5-5)
src/shared/ui/Table/TableCell.tsx (7)
src/shared/ui/Table/Table.tsx (1)
  • Props (5-15)
src/shared/ui/Table/TableBody.tsx (1)
  • Props (5-8)
src/shared/ui/Table/TableHead.tsx (1)
  • Props (7-10)
src/shared/ui/Table/TableHeader.tsx (1)
  • Props (5-8)
src/shared/ui/Table/TableRow.tsx (1)
  • Props (5-8)
src/shared/index.ts (3)
  • TableCell (25-25)
  • cn (30-30)
  • Body2 (24-24)
src/shared/ui/Table/index.ts (1)
  • TableCell (6-6)
src/shared/ui/Table/Table.stories.tsx (8)
src/shared/index.ts (6)
  • Table (25-25)
  • TableHeader (25-25)
  • TableRow (25-25)
  • TableHead (25-25)
  • TableBody (25-25)
  • TableCell (25-25)
src/shared/ui/Table/Table.tsx (1)
  • Table (16-26)
src/shared/ui/Table/index.ts (6)
  • Table (1-1)
  • TableHeader (2-2)
  • TableRow (4-4)
  • TableHead (5-5)
  • TableBody (3-3)
  • TableCell (6-6)
src/shared/ui/Table/TableHeader.tsx (1)
  • TableHeader (10-16)
src/shared/ui/Table/TableRow.tsx (1)
  • TableRow (10-16)
src/shared/ui/Table/TableHead.tsx (1)
  • TableHead (12-21)
src/shared/ui/Table/TableBody.tsx (1)
  • TableBody (10-16)
src/shared/ui/Table/TableCell.tsx (1)
  • TableCell (12-20)
src/shared/ui/Table/Table.tsx (7)
src/shared/ui/Table/TableBody.tsx (1)
  • Props (5-8)
src/shared/ui/Table/TableCell.tsx (1)
  • Props (7-10)
src/shared/ui/Table/TableHead.tsx (1)
  • Props (7-10)
src/shared/ui/Table/TableHeader.tsx (1)
  • Props (5-8)
src/shared/ui/Table/TableRow.tsx (1)
  • Props (5-8)
src/shared/index.ts (2)
  • Table (25-25)
  • cn (30-30)
src/shared/ui/Table/index.ts (1)
  • Table (1-1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: preview-storybook
🔇 Additional comments (9)
src/shared/ui/Table/TableCell.tsx (1)

15-15: 텍스트 색상이 너무 연해 가독성이 떨어질 수 있습니다.

text-gray-400은 상당히 밝은 색상으로, 일반 텍스트 콘텐츠에 사용하기에는 대비가 부족할 수 있습니다. 접근성 가이드라인(WCAG)에서는 일반 텍스트에 대해 최소 4.5:1의 명암비를 권장합니다.

text-gray-600 이상의 더 어두운 색상을 사용하는 것을 고려해주세요:

-      <Body2 className="whitespace-pre-wrap text-gray-400" weight="normal">
+      <Body2 className="whitespace-pre-wrap text-gray-600" weight="normal">
src/shared/ui/Table/TableHead.tsx (1)

12-20: LGTM!

헤더 셀 구현이 적절합니다. text-gray-700을 사용하여 헤더와 데이터 셀을 시각적으로 구분하고, whitespace-nowrap으로 헤더 텍스트가 줄바꿈되지 않도록 처리한 것이 좋습니다.

src/shared/index.ts (1)

25-25: LGTM!

모든 Table 컴포넌트가 올바르게 export되었습니다.

src/shared/ui/Table/Table.tsx (2)

21-21: table-fixed 레이아웃으로 인한 콘텐츠 오버플로우를 검토해주세요.

table-fixed를 사용하면 모든 열이 동일한 너비를 가지게 되어, 콘텐츠 길이가 불균등할 경우 텍스트가 잘리거나 보기 불편할 수 있습니다. 현재 조회용으로만 사용된다고 하셨지만, 향후 다양한 데이터 형식에 대응하기 어려울 수 있습니다.

다음을 확인해주세요:

  • 실제 사용 시나리오에서 모든 열의 콘텐츠 길이가 비슷한지
  • 긴 텍스트가 있을 경우 whitespace-pre-wrap이나 말줄임 처리가 적절히 동작하는지

필요하다면 table-auto로 변경하거나, Props에 layout?: 'auto' | 'fixed'를 추가하여 상황에 맞게 선택할 수 있도록 하는 것을 고려해주세요.


19-19: no-scrollbar 유틸리티 클래스 정의 확인 완료
src/index.css에서 .no-scrollbar가 정의되어 있음을 확인했습니다.

src/shared/ui/Table/TableHeader.tsx (1)

10-15: LGTM!

테이블 헤더 영역을 깔끔하게 구분하는 간단하고 효과적인 구현입니다.

src/shared/ui/Table/TableRow.tsx (1)

10-15: LGTM!

행 사이의 구분선을 적절하게 처리하고, last:border-0으로 마지막 행의 불필요한 테두리를 제거한 깔끔한 구현입니다.

src/shared/ui/Table/index.ts (1)

1-6: LGTM!

모든 Table 컴포넌트가 올바르게 export되었으며, 논리적인 계층 구조 순서를 따르고 있습니다.

src/shared/ui/Table/TableBody.tsx (1)

10-15: LGTM!

테이블 본문 영역을 감싸는 간단하고 명확한 구현입니다. bg-white를 사용하여 헤더(bg-gray-50)와 시각적으로 구분되도록 했습니다.

참고: 이전 리뷰 코멘트들은 데이터 렌더링 로직이 포함된 다른 버전에 대한 것으로, 현재의 단순한 래퍼 컴포넌트 구현과는 관련이 없습니다.

Comment on lines +52 to +73
export const Basic: StoryObj<typeof PrimitiveMeta> = {
render: () => (
<Table>
<TableHeader>
<TableRow>
<TableHead>날짜</TableHead>
<TableHead>카테고리</TableHead>
<TableHead>점수</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data.map((row, i) => (
<TableRow key={i}>
<TableCell>{row.createdAt}</TableCell>
<TableCell>{row.scoreCategory}</TableCell>
<TableCell>{row.amount.toFixed(3)}점</TableCell>
</TableRow>
))}
</TableBody>
</Table>
),
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

스토리를 더 인터랙티브하게 만들 수 있습니다.

현재 Basic 스토리는 render 함수에 데이터가 하드코딩되어 있어 Storybook의 Controls 패널을 통해 수정할 수 없습니다. 데이터를 args로 받도록 하면 사용자가 다양한 데이터 조합을 테스트할 수 있습니다.

다음과 같이 개선할 수 있습니다:

+type StoryArgs = {
+  data: typeof data;
+};
+
-export const Basic: StoryObj<typeof PrimitiveMeta> = {
+export const Basic: StoryObj<StoryArgs> = {
+  args: {
+    data,
+  },
-  render: () => (
+  render: ({ data }) => (
     <Table>
       <TableHeader>
         <TableRow>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const Basic: StoryObj<typeof PrimitiveMeta> = {
render: () => (
<Table>
<TableHeader>
<TableRow>
<TableHead>날짜</TableHead>
<TableHead>카테고리</TableHead>
<TableHead>점수</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data.map((row, i) => (
<TableRow key={i}>
<TableCell>{row.createdAt}</TableCell>
<TableCell>{row.scoreCategory}</TableCell>
<TableCell>{row.amount.toFixed(3)}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
),
};
type StoryArgs = {
data: typeof data;
};
export const Basic: StoryObj<StoryArgs> = {
args: {
data,
},
render: ({ data }) => (
<Table>
<TableHeader>
<TableRow>
<TableHead>날짜</TableHead>
<TableHead>카테고리</TableHead>
<TableHead>점수</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data.map((row, i) => (
<TableRow key={i}>
<TableCell>{row.createdAt}</TableCell>
<TableCell>{row.scoreCategory}</TableCell>
<TableCell>{row.amount.toFixed(3)}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
),
};
🤖 Prompt for AI Agents
In src/shared/ui/Table/Table.stories.tsx around lines 52-73, the Basic story
uses hardcoded data inside render which prevents Storybook Controls from
modifying it; refactor the story to accept args (e.g., render: (args) => ...)
and use args.data (with a sensible default via Basic.args) for the rows so users
can edit the dataset from Controls; update the StoryObj generic if needed to
reflect the arg type and add Basic.args = { data: [...] } providing the original
sample so backwards compatibility is preserved.

</TableHeader>
<TableBody>
{data.map((row, i) => (
<TableRow key={i}>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

배열 인덱스를 key로 사용하고 있습니다.

정적 데모 데이터의 경우 허용되지만, 안정적인 고유 식별자를 key로 사용하는 것이 더 좋은 관행입니다. 예를 들어, createdAtscoreCategory를 조합하여 사용할 수 있습니다.

-        {data.map((row, i) => (
-          <TableRow key={i}>
+        {data.map((row) => (
+          <TableRow key={`${row.createdAt}-${row.scoreCategory}`}>
🤖 Prompt for AI Agents
In src/shared/ui/Table/Table.stories.tsx around line 64, the story currently
uses the array index as the React key which can cause unstable identity; replace
the index with a stable unique key built from the row data (e.g., combine
createdAt and scoreCategory into a single string) and ensure it’s deterministic
(stringify or format timestamps) and falls back to a safe unique value if either
field is missing.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8456ee4 and 099944d.

📒 Files selected for processing (1)
  • src/shared/ui/Table/TableCell.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/shared/ui/Table/TableCell.tsx (7)
src/shared/ui/Table/Table.tsx (1)
  • Props (5-15)
src/shared/ui/Table/TableBody.tsx (1)
  • Props (5-8)
src/shared/ui/Table/TableHead.tsx (1)
  • Props (7-10)
src/shared/ui/Table/TableHeader.tsx (1)
  • Props (5-8)
src/shared/ui/Table/TableRow.tsx (1)
  • Props (5-8)
src/shared/index.ts (3)
  • TableCell (25-25)
  • cn (30-30)
  • Body2 (24-24)
src/shared/ui/Table/index.ts (1)
  • TableCell (6-6)
🔇 Additional comments (1)
src/shared/ui/Table/TableCell.tsx (1)

1-10: Props 타입 정의와 import가 적절합니다.

타입 정의가 다른 Table 컴포넌트들과 일관성 있게 구현되어 있고, 필요한 import만 포함하고 있습니다.

Comment on lines +12 to +20
export function TableCell({ className, children, ...props }: Props) {
return (
<td className={cn('px-4 py-2 align-middle md:px-4', className)} {...props}>
<Body2 className="whitespace-pre-wrap text-gray-400" weight="normal">
{children}
</Body2>
</td>
);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

중복된 md:px-4 클래스를 제거하세요.

px-4는 모든 화면 크기에 이미 적용되므로, md:px-4는 중복됩니다. Tailwind의 mobile-first 접근 방식에서 기본 클래스는 모든 브레이크포인트에 적용되며, md: 접두사는 해당 브레이크포인트 이상에서 값을 재정의할 때만 의미가 있습니다.

참고: Body2에서 패딩을 제거하여 이전 리뷰의 중복 패딩 문제가 해결된 점은 좋습니다.

다음과 같이 수정하여 중복을 제거하세요:

-    <td className={cn('px-4 py-2 align-middle md:px-4', className)} {...props}>
+    <td className={cn('px-4 py-2 align-middle', className)} {...props}>

만약 모바일과 데스크톱에서 다른 패딩을 의도했다면, 다음과 같은 패턴을 사용하세요:

-    <td className={cn('px-4 py-2 align-middle md:px-4', className)} {...props}>
+    <td className={cn('px-2 py-2 align-middle md:px-4', className)} {...props}>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function TableCell({ className, children, ...props }: Props) {
return (
<td className={cn('px-4 py-2 align-middle md:px-4', className)} {...props}>
<Body2 className="whitespace-pre-wrap text-gray-400" weight="normal">
{children}
</Body2>
</td>
);
}
export function TableCell({ className, children, ...props }: Props) {
return (
<td className={cn('px-4 py-2 align-middle', className)} {...props}>
<Body2 className="whitespace-pre-wrap text-gray-400" weight="normal">
{children}
</Body2>
</td>
);
}
🤖 Prompt for AI Agents
In src/shared/ui/Table/TableCell.tsx around lines 12 to 20, remove the redundant
Tailwind class `md:px-4` from the td class list because `px-4` already applies
to all breakpoints; update the class string to only include `px-4 py-2
align-middle` (plus the incoming className) so you don't duplicate padding; if
you actually intended different padding on md and up, replace `md:px-4` with the
desired `md:px-{value}` instead of duplicating the same value.

@ujinsim ujinsim merged commit 26105dd into main Nov 17, 2025
7 checks passed
@ujinsim ujinsim deleted the feat/#68-table branch November 17, 2025 05:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] 테이블 컴포넌트, 스토리북

2 participants