DataTable
정렬, 필터링, 페이지네이션을 지원하는 고급 데이터 테이블
개요
DataTable은 TanStack Table과 @dnd-kit 기반의 고급 데이터 테이블 컴포넌트입니다. 정렬, 필터링, 페이지네이션, 행 선택, 인라인 편집 등 다양한 기능을 통합 제공합니다.
주요 특징
- ✅ 정렬/필터링: 컬럼별 정렬 및 필터 (텍스트, 셀렉트, 범위, 날짜 등)
- ✅ 페이지네이션: 클라이언트/서버 사이드
- ✅ 행 선택: 단일/다중 선택
- ✅ 인라인 편집: 셀 단위 편집
- ✅ 컬럼 관리: 리사이즈, 리오더, 피닝, 가시성 토글
- ✅ 행 피닝: 특정 행 상단/하단 고정
- ✅ 서버 사이드: 수동 페이지네이션/정렬/필터링
- ✅ 접근성: 키보드 네비게이션
- ✅ 푸터 합계 행: 컨럼 정의의
footer로 하단 합계 행 자동 렌더링 - ✅ 디자인 토큰: 테마 커스터마이징 지원
기본 테이블
Preview
Features
행 선택
Preview
최대 높이 (스크롤)
Preview
필터링 + 정렬 + 편집
컨럼 meta에 필터 옵션을 설정하고, editable로 인라인 편집을 활성화합니다.
editType: "lookup"을 지정하면 검색 가능한 Combobox로 편집합니다.
tableRef를 사용하면 외부에서 newRow(), cancelNewRow(), saveNewRow()를 제어할 수 있습니다.
Preview
인라인 편집 타입
meta.editType으로 셀 편집 UI 타입을 지정합니다. lookup은 검색 가능한 Combobox로 렌더링됩니다.
Preview
필터 순서 제어 + 컬럼 선택기 숨김
meta.filterOrder로 컬럼 정의 순서와 별개로 필터 UI 표시 순서를 지정할 수 있습니다.
filterOrder 값이 있는 필터가 우선 배치되고, 없는 필터는 추가된 순서를 유지합니다.
showColumnSelector={false}로 컬럼 선택기를 숨길 수 있으며, Reset 버튼은 활성 필터가 있을 때만 표시됩니다.
Preview
컬럼/행 피닝
Preview
설정 저장 (Storage)
storage prop에 ConfigStorage 구현체를 전달하고 autoSave를 켜면, 컬럼 순서(order), 보이기/숨기기(visibility), 너비(sizing) 설정이 자동 저장됩니다. 새로고침 후에도 설정이 유지됩니다.
Preview
고정 높이
height로 테이블 영역의 고정 높이를 지정합니다. 데이터가 적어도 높이가 줄어들지 않고, 데이터가 많으면 스크롤됩니다.
maxHeight는 최대 높이만 제한하므로 데이터가 적으면 줄어드는 반면, height는 항상 고정됩니다.
Preview
컬럼 정렬 (Column Align)
meta.align으로 헤더와 셀의 텍스트 정렬을 지정합니다. "left" (기본), "center", "right"를 지원합니다.
헤더와 셀을 개별 지정하려면 headerAlign, cellAlign을 사용합니다. 개별 값이 align보다 우선합니다.
selectable 사용 시 selection 컬럼은 자동으로 center 정렬됩니다.
Preview
확장 가능 행 (Detail Panel)
expandable과 renderExpandedRow로 행을 펼쳐 상세 콘텐츠를 표시합니다. chevron 아이콘으로 토글합니다.
Preview
푸터 합계 행 (Footer Total Row)
컬럼 정의의 footer 속성으로 테이블 하단에 합계 행을 표시합니다. footer가 하나라도 정의되면 자동으로 <tfoot>이 렌더링됩니다.
Preview
enableSearch / enableSorting (전체 기본값)
enableSearch와 enableSorting props로 전체 컬럼의 검색/정렬 기본값을 설정합니다.
컬럼에 개별 선언된 값이 항상 우선합니다.
Preview
날짜 범위 필터 (date-range)
meta.filterVariant를 "date-range"로 설정하면 DatePicker의 range 모드로 날짜 범위 필터링을 할 수 있습니다.
from~to 범위 내의 날짜 데이터만 표시됩니다.
필터 태그의 날짜 포맷은 기본 yyyy-MM-dd이며, meta.filterDateFormat으로 커스터마이징 가능합니다. (지원 토큰: yyyy, yy, MM, dd)
Preview
Controlled Row Selection
rowSelection prop으로 외부에서 선택 상태를 제어할 수 있습니다. onRowSelectionChange 콜백과 함께 사용하면 양방향 바인딩이 가능합니다.
rowSelection을 전달하지 않으면 기존처럼 내부 상태로 동작합니다 (uncontrolled).
Preview
Border Variant
variant="border"로 셀마다 좌우 border를 추가합니다.
Preview
서버 사이드 페이지네이션
Code
<DataTable
columns={columns}
data={data}
getRowId={(row) => row.id}
manualPagination
pageCount={totalPages}
pageSizeOptions={[10, 20, 50]}
onPaginationChange={({ pageIndex, pageSize }) => {
fetchData(pageIndex, pageSize)
}}
/>사용 예시
예시 1: 분석 지표 테이블
Preview
예시 2: 커스텀 셀 렌더링
Preview
API Reference
Props
| Prop | Type | Default | Description |
|---|---|---|---|
columns | DataTableColumnDef<T>[] | [] | 컬럼 정의 |
data | T[] | [] | 데이터 배열 |
getRowId | (row: T) => string | - | 행 고유 ID 함수 (필수) |
title | React.ReactNode | - | 테이블 제목 |
actions | React.ReactNode | - | 액션 영역 |
selectable | boolean | false | 행 선택 활성화 |
enableMultiRowSelection | boolean | false | 다중 선택 허용 |
enableRowSelection | (row: Row) => boolean | () => true | 행별 선택 가능 여부 |
editable | boolean | false | 인라인 편집 활성화 |
manualPagination | boolean | false | 서버 사이드 페이지네이션 |
manualSorting | boolean | false | 서버 사이드 정렬 |
manualFiltering | boolean | false | 서버 사이드 필터링 |
height | string | number | - | 고정 높이 (데이터 적어도 유지) |
maxHeight | string | number | - | 최대 높이 (스크롤) |
loading | boolean | false | 로딩 상태 |
loadingDim | string | - | 로딩 시 dim 배경색 |
loadingContent | React.ReactNode | - | 로딩 시 표시할 커스텀 콘텐츠 |
pageCount | number | - | 전체 페이지 수 (서버) |
rowCount | number | - | 전체 행 수 (서버) |
defaultPageSize | number | 10 | 페이지 크기 초기값 |
pageSizeOptions | number[] | [10,20,50,100] | 페이지 크기 옵션 |
hidePagination | boolean | false | 페이지네이션 숨김 |
tableLayout | "auto" | "fixed" | - | 테이블 레이아웃 |
showColumnSelector | boolean | true | 컬럼 선택기 표시 여부 |
storage | ConfigStorage<TableConfig> | - | 설정 저장소 구현체 |
autoSave | boolean | false | 설정 자동 저장 활성화 |
enableRowPinning | boolean | false | 행 피닝 활성화 |
enableSearch | boolean | - | 전체 컬럼 검색 기본값 |
enableSorting | boolean | - | 전체 컬럼 정렬 기본값 |
variant | "default" | "border" | - | 스타일 변형 (border: 셀 좌우 border) |
tableRef | React.RefObject<DataTableRef> | - | 임퍼러티브 핸들 ref (새 행 제어) |
expandable | boolean | false | 확장 가능 행 활성화 |
getSubRows | (row: T) => T[] | undefined | - | 하위 행 접근 함수 (트리 구조) |
getRowCanExpand | (row: Row) => boolean | - | 행 확장 가능 여부 판별 함수 |
renderExpandedRow | (row: Row) => React.ReactNode | - | 확장 시 렌더링할 콘텐츠 |
rowSelection | Record<string, boolean> | - | 행 선택 상태 (controlled) |
columnFilters | ColumnFiltersState | - | 컬럼 필터 상태 (controlled) |
columnOrder | string[] | - | 컬럼 순서 (controlled) |
columnVisibility | VisibilityState | - | 컬럼 표시 상태 (controlled) |
columnPinning | ColumnPinningState | - | 컬럼 피닝 상태 (controlled) |
rowPinning | RowPinningState | - | 행 피닝 상태 (controlled) |
expanded | ExpandedState | - | 확장 상태 (controlled) |
className | string | - | 추가 CSS 클래스 |
Events
| Event | Type | Description |
|---|---|---|
onRowSelectionChange | (rows: T[]) => void | 행 선택 변경 |
onPaginationChange | (state: { pageIndex, pageSize }) => void | 페이지 변경 |
onSortingChange | (sorting: SortingState) => void | 정렬 변경 |
onCellUpdate | (rowIndex, columnId, value) => void | 셀 값 업데이트 (onBlur) |
onCellEditStart | (rowIndex, columnId, value) => void | 셀 편집 시작 (onFocus) |
onCellEditEnd | (rowIndex, columnId, value) => void | 셀 편집 종료 (onBlur) |
onRowClick | (row: T) => void | 행 클릭 |
onRowDoubleClick | (row: T) => void | 행 더블클릭 |
onSaveNewRow | (newRowData: Partial<T>) => void | 새 행 저장 |
onColumnFiltersChange | (filters: ColumnFiltersState) => void | 컬럼 필터 변경 |
onColumnOrderChange | (order: string[]) => void | 컬럼 순서 변경 |
onColumnVisibilityChange | (visibility: VisibilityState) => void | 컬럼 표시 변경 |
onColumnPinningChange | (pinning: ColumnPinningState) => void | 컬럼 피닝 변경 |
onRowPinningChange | (pinning: RowPinningState) => void | 행 피닝 변경 |
onExpandedChange | (expanded: ExpandedState) => void | 확장 상태 변경 |
DataTableColumnDef
TanStack Table의 ColumnDef를 확장합니다.
| Prop | Type | Default | Description |
|---|---|---|---|
id | string | - | 컬럼 고유 ID (필수) |
header | string | Function | - | 헤더 텍스트/렌더 함수 |
accessorKey | string | - | 데이터 접근 키 |
size | number | - | 컬럼 너비 (px) |
maxSize | number | - | 컬럼 최대 너비 (px), 리사이즈 상한 제한 |
cell | Function | - | 셀 커스텀 렌더 함수 |
footer | string | Function | - | 푸터 렌더 함수 (합계 등) |
enableSearch | boolean | - | 검색 활성화 (props 기본값 오버라이드) |
enableSorting | boolean | - | 정렬 활성화 (props 기본값 오버라이드) |
meta | object | - | 필터/편집/피닝 설정 |
DataTableRef
tableRef prop을 통해 외부에서 새 행 추가/취소/저장을 제어할 수 있습니다.
| Method | Type | Description |
|---|---|---|
newRow() | () => void | 새 행 추가 모드 활성화 |
cancelNewRow() | () => void | 새 행 추가 취소 |
saveNewRow() | () => void | 새 행 저장 (onSaveNewRow 콜백 호출) |
import { useRef } from "react"
import { DataTable, type DataTableRef } from "@vortex/ui-icignal"
const tableRef = useRef<DataTableRef>(null)
<DataTable
tableRef={tableRef}
editable
onSaveNewRow={(newRow) => console.log(newRow)}
// ...
/>
// 외부에서 호출
tableRef.current?.newRow()
tableRef.current?.cancelNewRow()
tableRef.current?.saveNewRow()ConfigStorage<T>
| Method | Type | Description |
|---|---|---|
getItem() | () => T | null | 저장된 설정 가져오기 |
setItem() | (value: T) => void | 설정 저장하기 |
removeItem() | () => void | 저장된 설정 삭제하기 |
TableConfig
| Property | Type | Description |
|---|---|---|
columnVisibility | Record<string, boolean> | 컬럼 보이기/숨기기 상태 |
columnOrder | string[] | 컬럼 순서 |
columnSizing | Record<string, number> | 컬럼 너비 (px) |
Column Meta
| Prop | Type | Description |
|---|---|---|
filterVariant | "text" | "select" | "range" | "switch" | "checkbox" | "date" | "date-range" | 필터 유형 |
filterPlaceholder | string | 필터 플레이스홀더 |
filterAlwaysShow | boolean | 필터 항상 표시 |
filterOptions | { label, value }[] | select 필터 옵션 |
filterOrder | number | 필터 표시 순서 (값이 있는 필터가 우선, 오름차순) |
filterFormat | (value: FilterValue) => string | React.ReactNode | 필터 값 포맷 함수 |
filterDateFormat | string | 날짜 필터 포맷 (기본: yyyy-MM-dd) |
editable | boolean | 셀 편집 가능 여부 |
editType | "text" | "select" | "lookup" | "switch" | 셀 편집 UI 타입 |
editOptions | { label, value }[] | select/lookup 편집 옵션 |
editPlaceholder | string | 편집 UI 플레이스홀더 |
align | "left" | "center" | "right" | 헤더/셀 텍스트 정렬 (기본: left) |
headerAlign | "left" | "center" | "right" | 헤더만 정렬 (align보다 우선) |
cellAlign | "left" | "center" | "right" | 셀만 정렬 (align보다 우선) |
pinned | "left" | "right" | 컬럼 고정 위치 |
기본 사용법
import { DataTable, type DataTableColumnDef } from "@vortex/ui-icignal"
const columns: DataTableColumnDef<User>[] = [
{ id: "name", header: "이름", accessorKey: "name", size: 150 },
{ id: "email", header: "이메일", accessorKey: "email", size: 200 },
]
const data = [
{ id: "1", name: "홍길동", email: "hong@example.com" },
]
<DataTable columns={columns} data={data} getRowId={(row) => row.id} />필터/정렬/편집 통합 예시
import { DataTable, type DataTableColumnDef } from "@vortex/ui-icignal"
import { useState } from "react"
interface Metric {
id: string
name: string
category: string
value: number
status: "active" | "inactive"
}
const [data, setData] = useState<Metric[]>([
{ id: "1", name: "페이지뷰", category: "트래픽", value: 12500, status: "active" },
{ id: "2", name: "세션", category: "트래픽", value: 3200, status: "active" },
{ id: "3", name: "이탈률", category: "트래픽", value: 42, status: "inactive" },
{ id: "4", name: "매출", category: "매출", value: 98000, status: "active" },
{ id: "5", name: "ARPU", category: "매출", value: 1250, status: "active" },
])
const columns: DataTableColumnDef<Metric>[] = [
{
id: "name",
header: "지표명",
accessorKey: "name",
size: 150,
enableSearch: true,
},
{
id: "category",
header: "카테고리",
accessorKey: "category",
size: 120,
meta: {
filterVariant: "select",
filterOptions: [
{ label: "트래픽", value: "트래픽" },
{ label: "매출", value: "매출" },
],
},
},
{
id: "value",
header: "값",
accessorKey: "value",
size: 100,
meta: { editable: true },
},
{
id: "status",
header: "상태",
accessorKey: "status",
size: 100,
meta: {
filterVariant: "select",
filterOptions: [
{ label: "활성", value: "active" },
{ label: "비활성", value: "inactive" },
],
},
},
]
<DataTable
columns={columns}
data={data}
getRowId={(row) => row.id}
title="분석 지표"
selectable
editable
onCellUpdate={(rowIndex, columnId, value) => {
setData((prev) =>
prev.map((row, i) => (i === rowIndex ? { ...row, [columnId]: value } : row))
)
}}
onRowSelectionChange={(rows) => console.log("선택:", rows)}
/>접근성
ARIA 속성
<DataTable columns={columns} data={data} getRowId={(row) => row.id} />
// 자동: role="table", role="row", role="cell" 등권장 사항
- ✅ 각 컬럼에 명확한
header텍스트 제공 - ✅ 컬럼에 항상
id와size명시 - ✅ 키보드: Tab으로 셀 이동, 정렬/선택 조작
- ✅ 로딩 상태에서
loadingprop 활용 - ❌ 과도한 컬럼 수로 가독성 저하 지양