Grid
TanStack Table 기반의 선언적 데이터 그리드 컴포넌트입니다.
개요
Grid 컴포넌트는 <TableColumn> 자식 컴포넌트로 열을 선언적으로 정의하고, TanStack Table을 내부 엔진으로 사용하여 정렬, 페이지네이션, 행 선택 등을 지원합니다.
주요 특징
- ✅ 선언적 API:
<TableColumn>children으로 열 구성 - ✅ 페이지네이션: Foundation Pagination 기반, 페이지 크기 선택 및 직접 이동 지원
- ✅ 행 선택: 체크박스(다중) / 라디오(단일) 모드
- ✅ 정렬: 클라이언트/서버 사이드 정렬 지원
- ✅ 셀 유형: link, button, range(progress/rate), 조건부 서식
- ✅ 커스텀 렌더링: renderCell prop으로 자유로운 셀 커스터마이징
- ✅ 그룹 컬럼:
<TableColumn>중첩으로 멀티행 헤더 지원 - ✅ 컨럼 표시/숨김: columnVisibility로 컨럼별 표시 여부 제어
- ✅ 개인화 (Personalization): 컨럼 순서/크기/표시 상태 저장 및 복원 (비동기 저장소 지원)
- ✅ 합계 행 (showSummary): 컨럼별 sum/avg/count/max/min 집계 표시
- ✅ 컨럼 설정 (GridColumnSettings): 컨럼 표시/숨김 및 순서 변경 다이얼로그
- ✅ 디자인 토큰: Foundation 토큰 시스템 호환
기본 사용
<Grid>에 data와 getRowId를 전달하고, <TableColumn>으로 열을 정의합니다.
Preview
공통 —list-* 토큰 오버라이드
Grid의 최상위 컨테이너는 data-token="list"를 사용하며 List와 기존 32개 --list-* 토큰을 공유합니다. 컴포넌트 이름 Grid와 .grid-container 클래스는 그대로 유지됩니다. 테마는 별도의 --grid-*가 아닌 공통 --list-* 네임스페이스로 설정하세요.
Grid에는 style prop이 없습니다. 인라인 토큰을 적용하려면 아래처럼 data-token="list"인 HTML 래퍼의 style에 CSS 사용자 정의 속성을 지정하세요. 중첩된 Grid는 토큰 기본값을 다시 선언하지 않고 래퍼의 값을 상속합니다. List 내부에서도 같은 방식으로 List의 토큰을 상속합니다.
| 토큰 | 적용 대상 | 예제 값 |
|---|---|---|
--list-head-background | 헤더 배경 | #ede9fe |
--list-cell-foreground | 셀 글자색 | #4c1d95 |
--list-cell-padding-x | 셀 좌우 여백 | 18px |
--list-row-background-selected | 선택 행 배경 | #dcfce7 |
--list-footer-background | 합계 행 배경 | #dbeafe |
체크박스 선택과 상품명 검색·정렬을 조작해 토큰이 적용된 헤더, 셀, 선택 행, 합계 행을 확인하세요.
Preview
Foundation Table 내부 브리지
Grid는 내부 Foundation Table의 색상·글꼴·셀 여백을 --list-* 토큰에 연결합니다. --table-*는 내부 브리지로 사용되므로 Grid 테마를 변경하기 위해 직접 재정의할 필요가 없습니다. 높이와 모서리 반경 같은 구조적 Foundation Table 토큰은 그대로 유지합니다.
backgroundColor를 전달하면 --list-row-background와 자동 파생된 --list-row-background-hover, --list-row-background-selected 값이 내부 Grid에 우선 적용됩니다. 위 예제처럼 선택 행 색을 토큰으로 직접 제어하려면 이 prop을 생략하세요.
스타일시트에서는 화면 범위를 지정한 .orders [data-token="list"] 같은 선택자로도 동일한 토큰을 재정의할 수 있습니다.
행 선택
체크박스 (다중 선택)
selectable + selectionMode="checkbox"로 다중 행 선택을 활성화합니다.
Preview
라디오 (단일 선택)
selectionMode="radio"로 단일 행 선택을 활성화합니다.
Preview
페이지네이션
pageSize, pageSizeOptions, useGoToPage으로 페이지네이션을 구성합니다.
Preview
셀 유형 (uiType)
TableColumn의 uiType prop으로 셀 표시 유형을 지정합니다.
Preview
조건부 서식
conditionalFormatting으로 조건에 따라 셀 스타일을 적용합니다.
Preview
조건부 서식 — 확장 연산자
between(범위), in(목록 포함), isMatch(정규식) 연산자를 지원합니다.
여러 규칙이 동시에 매칭되면 스타일이 누적(병합)됩니다.
Preview
조건부 서식 — 행 스타일
applyTo: "row"를 지정하면 조건 통과 시 행 전체에 스타일이 적용됩니다.
Preview
그룹 컬럼 (멀티행 헤더)
<TableColumn> 안에 <TableColumn>을 중첩하면 그룹 컬럼(멀티행 헤더)을 구성할 수 있습니다.
그룹 컬럼의 field는 생략하고 title만 지정합니다.
Preview
합계 행 (showSummary)
showSummary와 TableColumn의 summary prop으로 컨럼별 집계를 표시합니다.
Preview
TableColumnSummary
| 속성 | 타입 | 설명 |
|---|---|---|
type | "sum" | "avg" | "count" | "max" | "min" | "custom" | 집계 유형 (생략 시 label만 표시) |
label | string | 레이블 텍스트 (집계 대신 표시) |
formatter | (value: number) => string | 결과 포맷터 |
커스텀 셀 렌더링
renderCell prop으로 셀을 자유롭게 커스터마이징합니다.
Preview
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
data | T[] | - | 테이블 데이터 배열 |
getRowId | (row: T) => string | - | 행 고유 ID 추출 함수 (필수) |
children | ReactNode | - | <TableColumn> 자식 컴포넌트들 |
loading | boolean | false | 로딩 상태 표시 |
height | number | string | - | 그리드 높이 |
rowHeight | number | - | 행 높이 (가상 스크롤 시) |
className | string | - | 그리드 컨테이너 CSS 클래스 |
selectable | boolean | false | 행 선택 활성화 |
selectionMode | "checkbox" | "radio" | "checkbox" | 선택 방식 |
selectedRows | string[] | - | 선택된 행 ID 배열 (controlled) |
pagination | boolean | true | 페이지네이션 표시 여부 |
pageIndex | number | - | 현재 페이지 번호 (1-based, controlled) |
pageSize | number | 20 | 페이지당 행 수 |
pageCount | number | - | 전체 페이지 수 (서버 페이징 시) |
rowCount | number | - | 전체 행 수 |
pageSizeOptions | number[] | [10, 20, 50, 100] | 페이지 크기 선택 목록 |
useGoToPage | boolean | false | 페이지 직접 이동 입력란 표시 |
paginationDisabled | boolean | false | 로딩 시 페이지 탐색 비활성화 |
manualPagination | boolean | false | 서버 사이드 페이징 여부 |
manualSorting | boolean | false | 서버 사이드 정렬 여부 |
showRowNumber | boolean | false | 행 번호 열 표시 |
striped | boolean | false | 줄무늬 행 표시 |
fullSize | boolean | false | 화면 가득 채움 |
enableColumnResize | boolean | true | 열 너비 조절 활성화 |
enableDragCopy | boolean | false | 행 텍스트 드래그 복사 활성화 |
virtualScroll | boolean | false | 가상 스크롤 활성화 |
columnSearch | GridColumnSearchState | - | 컬럼 헤더 검색 조건 (controlled) |
clientSearch | boolean | false | 클라이언트 사이드 검색 필터링 활성화 |
columnVisibility | Record<string, boolean> | - | 컨럼 표시/숨김 상태 (controlled) |
onColumnVisibilityChange | (visibility: Record<string, boolean>) => void | - | 컨럼 표시 변경 콜백 |
columnOrder | string[] | - | 컬럼 순서 (controlled, 필드명 배열) |
onColumnOrderChange | (order: string[]) => void | - | 컬럼 순서 변경 콜백 |
personalization | GridPersonalizationConfig | - | 개인화 저장/복원 설정 |
showSummary | boolean | false | 합계 행 표시 |
overlay | GridOverlayConfig[] | - | 오버레이 목록 |
backgroundColor | string | - | 그리드 배경색 |
Grid Events
| Event | Type | Description |
|---|---|---|
onRowClick | (row: T, rowIndex: number) => void | 행 클릭 |
onRowDoubleClick | (row: T, rowIndex: number) => void | 행 더블클릭 |
onRowContextMenu | (row: T, rowIndex: number, event: React.MouseEvent) => void | 행 우클릭 컨텍스트 메뉴 |
onLoad | (params: GridLoadParams) => void | 데이터 로드 요청 |
onSortChange | (sorting: SortingState) => void | 정렬 변경 |
onPageChange | (pageIndex: number) => void | 페이지 변경 |
onPageSizeChange | (pageSize: number) => void | 페이지 크기 변경 |
onSelectionChange | (selectedIds: string[], selectedRows: T[]) => void | 선택 변경 |
onColumnResize | (columnId: string, width: number) => void | 열 너비 변경 |
onColumnSearch | (field: string, value: GridColumnSearchValue | undefined, search: GridColumnSearchState) => void | 컬럼 검색 실행 |
onScrollEnd | () => void | 스크롤 하단 도달 (무한스크롤) |
TableColumn Props
주요 props만 요약합니다. 전체 명세는 TableColumn 문서를 참조하세요.
| Prop | Type | Default | Description |
|---|---|---|---|
field | string | - | 데이터 필드명 (그룹 컬럼은 생략 가능) |
title | string | - | 열 헤더 텍스트 (필수) |
width | number | - | 열 너비 (px) |
minWidth | number | - | 최소 열 너비 |
sortable | boolean | false | 정렬 가능 여부 |
searchable | boolean | false | 검색 가능 여부 (헤더 검색 영역 표시) |
searchType | "text" | "date" | "select" | "popup" | "text" | 검색 입력 유형 |
enabled | boolean | true | 열 활성화 여부 |
hidden | boolean | false | 시각적 숨김 |
fixed | boolean | "left" | "right" | - | 열 고정 방향 |
uiType | "text" | "link" | "button" | "range" | … | "text" | 셀 표시 유형 |
formatter | TableColumnFormatter | - | 값 포맷터 (천단위 구분 등) |
conditionalFormatting | TableColumnConditionalRule[] | - | 조건부 서식 규칙 |
children | ReactNode | - | 하위 <TableColumn> (그룹 컬럼용) |
renderCell | (value, row, rowIndex) => ReactNode | - | 커스텀 셀 렌더 함수 |
onLinkClick | (row: T) => void | - | uiType=“link” 클릭 콜백 |
onButtonClick | (row: T) => void | - | uiType=“button” 클릭 콜백 |
summary | TableColumnSummary | - | 합계 행 설정 (type, label, formatter) |
서버 사이드 페이징 / 정렬
manualPagination과 manualSorting을 활성화하면 Grid는 클라이언트에서 데이터를 자르지 않고, onLoad/onPageChange/onSortChange 콜백을 통해 서버에 요청합니다.
Preview
onLoadvsonPageChange:onLoad는 Grid 마운트 시 1회 호출됩니다. 이후 페이지 변경은onPageChange, 정렬 변경은onSortChange가 호출됩니다.
컬럼 검색
searchable TableColumn과 함께 사용합니다. clientSearch={true}이면 Grid가 직접 필터링하고, false(기본)이면 onColumnSearch 콜백으로 조건만 전달합니다.
Preview
인라인 편집 모드
inlineEditMode로 Grid의 편집 상태를 제어합니다. "edit" 모드에서는 editable로 지정한 셀이 편집 가능해집니다.
| 모드 | 설명 |
|---|---|
"none" | 읽기 전용 (기본) |
"add" | 새 행 추가 모드 |
"edit" | 기존 행 편집 모드 |
"delete" | 삭제 모드 |
Preview
Tip: List 컴포넌트를 사용하면 InlineEditToolbar와 함께 추가/수정/삭제/저장/취소 워크플로우를 쉽게 구성할 수 있습니다.
ScrollArea 내부 스크롤
Grid의 스크롤 영역은 Foundation ScrollArea / ScrollBar를 사용합니다. height, fullSize, virtualScroll, onScrollEnd 등 기존 공개 API는 그대로이며, Grid에 viewportRef나 viewportProps를 추가할 필요가 없습니다.
가상 행 계산, 무한 스크롤의 하단 감지, 열 리사이즈는 내부 Viewport의 실제 스크롤 위치와 크기를 기준으로 동작합니다. 가로·세로 스크롤바를 제공하며, 스크롤바 너비와 thumb 색상은 List의 스크롤바 스타일 예제처럼 중첩된 data-token="scroll-area" 루트에 Foundation 토큰을 지정해 변경합니다. 기존 --list-* 토큰과는 독립적입니다.
가상 스크롤
virtualScroll을 활성화하면 화면에 보이는 행만 렌더링하여 대용량 데이터(수천~수만 행)에서도 성능을 유지합니다. 이 예제는 기존 페이지 모델에서 전체 데이터를 한 페이지로 표시하도록 pageSize={bigData.length}를 지정합니다. pagination={false}는 페이지 탐색 UI를 숨기는 설정입니다.
Preview
주의:
virtualScroll사용 시height지정이 필수입니다.rowHeight는 각 행의 예상 높이로, 정확한 값을 지정할수록 스크롤이 부드럽습니다.
오버레이
overlay prop으로 Grid 위에 메시지/아이콘 오버레이를 표시합니다. 권한 잠금, 안내 메시지 등에 사용합니다.
Preview
드래그 복사
enableDragCopy를 활성화하면 셀 텍스트를 드래그로 선택·복사할 수 있습니다. 기본적으로 Grid는 텍스트 선택이 비활성화되어 있습니다.
Preview
컬럼 표시/숨김 (columnVisibility)
columnVisibility와 onColumnVisibilityChange로 컬럼별 표시 여부를 제어합니다.
import { useState } from "react";
import { Grid, TableColumn } from "@vortex/ui-cals";
function ColumnVisibilityExample() {
const [visibility, setVisibility] = useState<Record<string, boolean>>({
name: true,
price: true,
category: false, // 초기에 숨김
});
return (
<>
{/* 토글 버튼 예시 */}
<button onClick={() => setVisibility((v) => ({ ...v, category: !v.category }))}>
카테고리 {visibility.category ? "숨기기" : "보이기"}
</button>
<Grid
data={data}
getRowId={(row) => row.id}
columnVisibility={visibility}
onColumnVisibilityChange={setVisibility}
>
<TableColumn field="name" title="이름" />
<TableColumn field="price" title="가격" />
<TableColumn field="category" title="카테고리" />
</Grid>
</>
);
}개인화 (Personalization)
personalization prop으로 컬럼 순서, 크기, 표시 상태를 저장/복원합니다. 비동기 저장소(IndexedDB 등)도 지원합니다.
import { Grid, TableColumn } from "@vortex/ui-cals"
// 동기 저장소 (localStorage)
const syncPersonalization = {
getItem: () => {
const saved = localStorage.getItem("grid-state")
return saved ? JSON.parse(saved) : null
},
setItem: (state) => localStorage.setItem("grid-state", JSON.stringify(state)),
}
// 비동기 저장소 (IndexedDB 등)
const asyncPersonalization = {
getItem: async () => {
const db = await openMyDB()
return await db.get("grid-state", "main-grid")
},
setItem: async (state) => {
const db = await openMyDB()
await db.put("grid-state", state, "main-grid")
},
}
<Grid
data={data}
getRowId={(row) => row.id}
personalization={asyncPersonalization} // Promise 반환도 OK
>
<TableColumn field="name" title="이름" />
<TableColumn field="price" title="가격" />
</Grid>getItem()이 Promise를 반환하면 내부적으로 .then()으로 처리합니다.
접근성
권장 사항
- ✅
<table>시맨틱 요소를 사용하여 스크린 리더 호환 - ✅ 로딩 시
aria-busy="true"자동 적용 - ✅ 체크박스/라디오는 키보드(Space)로 토글 가능
- ✅ 페이지 탐색 버튼은 키보드로 접근 가능
관련 컴포넌트
- Foundation Table: 기본 테이블 시맨틱 컴포넌트
- Foundation Pagination: 페이지네이션 프리미티브