# 고정 헤더 테이블 표준 가이드 ## 개요 스크롤 가능한 테이블에서 헤더를 상단에 고정하는 표준 구조입니다. 플로우 위젯의 스텝 데이터 리스트 테이블을 참조 기준으로 합니다. ## 필수 구조 ### 1. 기본 HTML 구조 ```tsx
헤더 1 헤더 2 {/* 데이터 행들 */}
``` ### 2. 필수 클래스 설명 #### 스크롤 컨테이너 (외부 div) ```tsx className="relative overflow-auto" style={{ height: "450px" }} ``` **필수 요소:** - `relative`: sticky positioning의 기준점 - `overflow-auto`: 스크롤 활성화 - `height`: 고정 높이 (인라인 스타일 또는 Tailwind 클래스) #### Table 컴포넌트 ```tsx ``` **필수 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 ``` **z-index 규칙:** - 왼쪽+상단 고정: `z-20` - 상단만 고정: `z-10` - 왼쪽만 고정: `z-10` - 일반 셀: z-index 없음 ### 4. 완전한 예제 (체크박스 포함) ```tsx
{/* 왼쪽 고정 체크박스 열 */} {/* 일반 헤더 열들 */} {columns.map((col) => ( {col} ))} {data.map((row, index) => ( {/* 왼쪽 고정 체크박스 */} toggleRow(index)} /> {/* 데이터 셀들 */} {columns.map((col) => ( {row[col]} ))} ))}
``` ## 반응형 대응 ### 모바일: 카드 뷰 ```tsx { /* 모바일: 카드 뷰 */ }
{data.map((item, index) => (
{/* 카드 내용 */}
))}
; { /* 데스크톱: 테이블 뷰 */ }
{/* 위의 테이블 구조 */}
; ``` ## 자주하는 실수 ### ❌ 잘못된 예시 ```tsx { /* 1. noWrapper 없음 - sticky 작동 안함 */ } ...
; { /* 2. 배경색 없음 - 스크롤 시 데이터가 보임 */ } 헤더; { /* 3. relative 없음 - sticky 기준점 없음 */ }
...
; { /* 4. 고정 높이 없음 - 스크롤 발생 안함 */ }
...
; ``` ### ✅ 올바른 예시 ```tsx { /* 모든 필수 요소 포함 */ }
헤더 ...
; ``` ## 높이 설정 가이드 ### 권장 높이값 - **소형 리스트**: `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(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
{/* 테이블 내용 */}
``` ### 키보드 네비게이션 ```tsx { if (e.key === "Enter" || e.key === " ") { handleRowClick(); } }} > {/* 행 내용 */} ``` ## 다크 모드 대응 ### 배경색 ```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에 고정 높이 설정 - [ ] `` 사용 - [ ] TableHead에 `bg-background sticky top-0 z-10` 적용 - [ ] TableHead에 `border-b shadow-[...]` 적용 - [ ] 왼쪽 고정 열은 `z-20` 사용 - [ ] 모바일 반응형 대응 (카드 뷰) - [ ] 다크 모드 호환 색상 사용