# 고정 헤더 테이블 표준 가이드

## 개요

스크롤 가능한 테이블에서 헤더를 상단에 고정하는 표준 구조입니다.
플로우 위젯의 스텝 데이터 리스트 테이블을 참조 기준으로 합니다.

## 필수 구조

### 1. 기본 HTML 구조

```tsx
<div className="relative overflow-auto" style={{ height: "450px" }}>
  <Table noWrapper>
    <TableHeader>
      <TableRow>
        <TableHead className="bg-background sticky top-0 z-10 border-b shadow-[0_1px_0_0_rgb(0,0,0,0.1)]">
          헤더 1
        </TableHead>
        <TableHead className="bg-background sticky top-0 z-10 border-b shadow-[0_1px_0_0_rgb(0,0,0,0.1)]">
          헤더 2
        </TableHead>
      </TableRow>
    </TableHeader>
    <TableBody>{/* 데이터 행들 */}</TableBody>
  </Table>
</div>
```

### 2. 필수 클래스 설명

#### 스크롤 컨테이너 (외부 div)

```tsx
className="relative overflow-auto"
style={{ height: "450px" }}
```

**필수 요소:**

- `relative`: sticky positioning의 기준점
- `overflow-auto`: 스크롤 활성화
- `height`: 고정 높이 (인라인 스타일 또는 Tailwind 클래스)

#### Table 컴포넌트

```tsx
<Table noWrapper>
```

**필수 props:**

- `noWrapper`: Table 컴포넌트의 내부 wrapper 제거 (매우 중요!)
  - 이것이 없으면 sticky header가 작동하지 않음

#### TableHead (헤더 셀)

```tsx
className =
  "bg-background sticky top-0 z-10 border-b shadow-[0_1px_0_0_rgb(0,0,0,0.1)]";
```

**필수 클래스:**

- `bg-background`: 배경색 (스크롤 시 데이터가 보이지 않도록)
- `sticky top-0`: 상단 고정
- `z-10`: 다른 요소 위에 표시
- `border-b`: 하단 테두리
- `shadow-[0_1px_0_0_rgb(0,0,0,0.1)]`: 얇은 그림자 (헤더와 본문 구분)

### 3. 왼쪽 열 고정 (체크박스 등)

첫 번째 열도 고정하려면:

```tsx
<TableHead className="bg-background sticky top-0 left-0 z-20 border-b shadow-[0_1px_0_0_rgb(0,0,0,0.1)]">
  <Checkbox />
</TableHead>
```

**z-index 규칙:**

- 왼쪽+상단 고정: `z-20`
- 상단만 고정: `z-10`
- 왼쪽만 고정: `z-10`
- 일반 셀: z-index 없음

### 4. 완전한 예제 (체크박스 포함)

```tsx
<div className="relative overflow-auto" style={{ height: "450px" }}>
  <Table noWrapper>
    <TableHeader>
      <TableRow>
        {/* 왼쪽 고정 체크박스 열 */}
        <TableHead className="bg-background sticky top-0 left-0 z-20 w-12 border-b px-3 py-2 text-center shadow-[0_1px_0_0_rgb(0,0,0,0.1)]">
          <Checkbox checked={allSelected} onCheckedChange={handleSelectAll} />
        </TableHead>

        {/* 일반 헤더 열들 */}
        {columns.map((col) => (
          <TableHead
            key={col}
            className="bg-background sticky top-0 z-10 border-b px-3 py-2 text-xs font-semibold whitespace-nowrap shadow-[0_1px_0_0_rgb(0,0,0,0.1)] sm:text-sm"
          >
            {col}
          </TableHead>
        ))}
      </TableRow>
    </TableHeader>

    <TableBody>
      {data.map((row, index) => (
        <TableRow key={index}>
          {/* 왼쪽 고정 체크박스 */}
          <TableCell className="bg-background sticky left-0 z-10 border-b px-3 py-2 text-center">
            <Checkbox
              checked={selectedRows.has(index)}
              onCheckedChange={() => toggleRow(index)}
            />
          </TableCell>

          {/* 데이터 셀들 */}
          {columns.map((col) => (
            <TableCell key={col} className="border-b px-3 py-2">
              {row[col]}
            </TableCell>
          ))}
        </TableRow>
      ))}
    </TableBody>
  </Table>
</div>
```

## 반응형 대응

### 모바일: 카드 뷰

```tsx
{
  /* 모바일: 카드 뷰 */
}
<div className="overflow-y-auto sm:hidden" style={{ height: "450px" }}>
  <div className="space-y-2 p-3">
    {data.map((item, index) => (
      <div key={index} className="bg-card rounded-md border p-3">
        {/* 카드 내용 */}
      </div>
    ))}
  </div>
</div>;

{
  /* 데스크톱: 테이블 뷰 */
}
<div
  className="relative hidden overflow-auto sm:block"
  style={{ height: "450px" }}
>
  <Table noWrapper>{/* 위의 테이블 구조 */}</Table>
</div>;
```

## 자주하는 실수

### ❌ 잘못된 예시

```tsx
{
  /* 1. noWrapper 없음 - sticky 작동 안함 */
}
<Table>
  <TableHeader>...</TableHeader>
</Table>;

{
  /* 2. 배경색 없음 - 스크롤 시 데이터가 보임 */
}
<TableHead className="sticky top-0">헤더</TableHead>;

{
  /* 3. relative 없음 - sticky 기준점 없음 */
}
<div className="overflow-auto">
  <Table noWrapper>...</Table>
</div>;

{
  /* 4. 고정 높이 없음 - 스크롤 발생 안함 */
}
<div className="relative overflow-auto">
  <Table noWrapper>...</Table>
</div>;
```

### ✅ 올바른 예시

```tsx
{
  /* 모든 필수 요소 포함 */
}
<div className="relative overflow-auto" style={{ height: "450px" }}>
  <Table noWrapper>
    <TableHeader>
      <TableRow>
        <TableHead className="bg-background sticky top-0 z-10 border-b shadow-[0_1px_0_0_rgb(0,0,0,0.1)]">
          헤더
        </TableHead>
      </TableRow>
    </TableHeader>
    <TableBody>...</TableBody>
  </Table>
</div>;
```

## 높이 설정 가이드

### 권장 높이값

- **소형 리스트**: `300px` ~ `400px`
- **중형 리스트**: `450px` ~ `600px` (플로우 위젯 기준)
- **대형 리스트**: `calc(100vh - 200px)` (화면 높이 기준)

### 동적 높이 계산

```tsx
// 화면 높이의 60%
style={{ height: "60vh" }}

// 화면 높이 - 헤더/푸터 제외
style={{ height: "calc(100vh - 250px)" }}

// 부모 요소 기준
className="h-full overflow-auto"
```

## 성능 최적화

### 1. 가상 스크롤 (대량 데이터)

데이터가 1000건 이상인 경우 `react-virtual` 사용 권장:

```tsx
import { useVirtualizer } from "@tanstack/react-virtual";

const parentRef = useRef<HTMLDivElement>(null);

const rowVirtualizer = useVirtualizer({
  count: data.length,
  getScrollElement: () => parentRef.current,
  estimateSize: () => 50, // 행 높이
});
```

### 2. 페이지네이션

대량 데이터는 페이지 단위로 렌더링:

```tsx
const paginatedData = data.slice((page - 1) * pageSize, page * pageSize);
```

## 접근성

### ARIA 레이블

```tsx
<div
  className="relative overflow-auto"
  style={{ height: "450px" }}
  role="region"
  aria-label="스크롤 가능한 데이터 테이블"
  tabIndex={0}
>
  <Table noWrapper aria-label="데이터 목록">
    {/* 테이블 내용 */}
  </Table>
</div>
```

### 키보드 네비게이션

```tsx
<TableRow
  tabIndex={0}
  onKeyDown={(e) => {
    if (e.key === "Enter" || e.key === " ") {
      handleRowClick();
    }
  }}
>
  {/* 행 내용 */}
</TableRow>
```

## 다크 모드 대응

### 배경색

```tsx
{
  /* 라이트/다크 모드 모두 대응 */
}
className = "bg-background"; // ✅ 권장

{
  /* 고정 색상 - 다크 모드 문제 */
}
className = "bg-white"; // ❌ 비권장
```

### 그림자

```tsx
{
  /* 다크 모드에서도 보이는 그림자 */
}
className = "shadow-[0_1px_0_0_hsl(var(--border))]";

{
  /* 또는 */
}
className = "shadow-[0_1px_0_0_rgb(0,0,0,0.1)]";
```

## 참조 파일

- **구현 예시**: `frontend/components/screen/widgets/FlowWidget.tsx` (line 760-820)
- **Table 컴포넌트**: `frontend/components/ui/table.tsx`

## 체크리스트

테이블 구현 시 다음을 확인하세요:

- [ ] 외부 div에 `relative overflow-auto` 적용
- [ ] 외부 div에 고정 높이 설정
- [ ] `<Table noWrapper>` 사용
- [ ] TableHead에 `bg-background sticky top-0 z-10` 적용
- [ ] TableHead에 `border-b shadow-[...]` 적용
- [ ] 왼쪽 고정 열은 `z-20` 사용
- [ ] 모바일 반응형 대응 (카드 뷰)
- [ ] 다크 모드 호환 색상 사용
