Skip to Content

DataTable

정렬, 필터링, 페이지네이션을 지원하는 고급 데이터 테이블


개요

DataTable은 TanStack Table과 @dnd-kit 기반의 고급 데이터 테이블 컴포넌트입니다. 정렬, 필터링, 페이지네이션, 행 선택, 인라인 편집 등 다양한 기능을 통합 제공합니다.

주요 특징

  • 정렬/필터링: 컬럼별 정렬 및 필터 (텍스트, 셀렉트, 범위, 날짜 등)
  • 페이지네이션: 클라이언트/서버 사이드
  • 행 선택: 단일/다중 선택
  • 인라인 편집: 셀 단위 편집
  • 컬럼 관리: 리사이즈, 리오더, 피닝, 가시성 토글
  • 행 피닝: 특정 행 상단/하단 고정
  • 서버 사이드: 수동 페이지네이션/정렬/필터링
  • 접근성: 키보드 네비게이션
  • 디자인 토큰: 테마 커스터마이징 지원

기본 테이블

이름
이메일
역할
홍길동
hong@example.com
관리자
김철수
kim@example.com
분석가
이영희
lee@example.com
뷰어
전체 건수 3

Features

행 선택

이름
이메일
역할
홍길동
hong@example.com
관리자
김철수
kim@example.com
분석가
이영희
lee@example.com
뷰어
전체 건수 3

최대 높이 (스크롤)

지표
현재 값
이전 값
변화율
페이지뷰
125,430
118,200
+6.1%
세션
45,200
42,800
+5.6%
순 방문자
32,100
30,500
+5.2%
이탈률
42.3%
44.1%
-4.1%
평균 체류시간
3m 24s
3m 10s
+7.4%
전체 건수 5

필터링 + 정렬 + 편집

컬럼 meta에 필터 옵션을 설정하고, editable로 인라인 편집을 활성화합니다.

const columns: DataTableColumnDef<Product>[] = [ { id: "name", header: "Name", accessorKey: "name", size: 180, meta: { filterVariant: "text", filterPlaceholder: "이름 검색...", filterAlwaysShow: true, }, }, { id: "category", header: "Category", accessorKey: "category", size: 130, meta: { filterVariant: "select", filterPlaceholder: "카테고리", filterAlwaysShow: true, filterOptions: [ { label: "Electronics", value: "Electronics" }, { label: "Clothing", value: "Clothing" }, ], }, }, { id: "price", header: "Price", accessorKey: "price", size: 100, cell: ({ getValue }) => `$${(getValue() as number).toFixed(2)}`, meta: { editable: false }, }, ] <DataTable columns={columns} data={data} getRowId={(row) => row.id} editable columnFilters={columnFilters} onColumnFiltersChange={setColumnFilters} onCellUpdate={(rowIndex, columnId, value) => console.log({ rowIndex, columnId, value }) } />

필터 순서 제어 + 컬럼 선택기 숨김

meta.filterOrder로 컬럼 정의 순서와 별개로 필터 UI 표시 순서를 지정할 수 있습니다. filterOrder 값이 있는 필터가 우선 배치되고, 없는 필터는 추가된 순서를 유지합니다. showColumnSelector={false}로 컬럼 선택기를 숨길 수 있으며, Reset 버튼은 활성 필터가 있을 때만 표시됩니다.

const columns: DataTableColumnDef<Product>[] = [ { id: "name", header: "Name", accessorKey: "name", meta: { filterVariant: "text", filterAlwaysShow: true, filterOrder: 2, // 세 번째로 표시 }, }, { id: "category", header: "Category", accessorKey: "category", meta: { filterVariant: "select", filterAlwaysShow: true, filterOrder: 0, // 첫 번째로 표시 filterOptions: [ { label: "Electronics", value: "Electronics" }, { label: "Clothing", value: "Clothing" }, ], }, }, { id: "status", header: "Status", accessorKey: "status", meta: { filterVariant: "select", filterAlwaysShow: true, filterOrder: 1, // 두 번째로 표시 filterOptions: [ { label: "Active", value: "active" }, { label: "Inactive", value: "inactive" }, ], }, }, ] // 필터 순서: Category → Status → Name (컬럼 순서와 무관) // 컬럼 선택기 숨김, Reset 버튼은 필터가 있을 때만 노출 <DataTable columns={columns} data={data} getRowId={(row) => row.id} columnFilters={columnFilters} onColumnFiltersChange={setColumnFilters} showColumnSelector={false} />

컬럼/행 피닝

const columns: DataTableColumnDef<Product>[] = [ { id: "id", header: "ID", accessorKey: "id", size: 80, meta: { pinned: "left" }, }, { id: "name", header: "Name", accessorKey: "name", size: 200 }, { id: "actions", header: "Actions", size: 100, meta: { pinned: "right" }, cell: () => <Button variant="ghost" size="sm">Detail</Button>, }, ] <DataTable columns={columns} data={data} getRowId={(row) => row.id} enableRowPinning rowPinning={{ top: ["1", "2"], bottom: ["50"] }} maxHeight="300px" tableLayout="fixed" />

서버 사이드 페이지네이션

<DataTable columns={columns} data={data} getRowId={(row) => row.id} manualPagination pageCount={totalPages} pageSizeOptions={[10, 20, 50]} onPaginationChange={({ pageIndex, pageSize }) => { fetchData(pageIndex, pageSize) }} />

사용 예시

예시 1: 분석 지표 테이블

지표
현재 값
이전 값
변화율
페이지뷰
125,430
118,200
+6.1%
세션
45,200
42,800
+5.6%
순 방문자
32,100
30,500
+5.2%
이탈률
42.3%
44.1%
-4.1%
평균 체류시간
3m 24s
3m 10s
+7.4%
전체 건수 5

예시 2: 커스텀 셀 렌더링

import { DataTable, Badge, type DataTableColumnDef } from "@vortex/ui-icignal" const columns: DataTableColumnDef<Product>[] = [ { id: "name", header: "Name", accessorKey: "name", size: 150 }, { id: "price", header: "Price", accessorKey: "price", size: 100, cell: ({ getValue }) => `$${(getValue() as number).toFixed(2)}`, }, { id: "status", header: "Status", accessorKey: "status", size: 100, cell: ({ getValue }) => { const status = getValue() as string return ( <Badge variant={status === "active" ? "default" : "secondary"}> {status} </Badge> ) }, }, ]

API Reference

Props

PropTypeDefaultDescription
columnsDataTableColumnDef<T>[][]컬럼 정의
dataT[][]데이터 배열
getRowId(row: T) => string-행 고유 ID 함수 (필수)
titleReact.ReactNode-테이블 제목
actionsReact.ReactNode-액션 영역
selectablebooleanfalse행 선택 활성화
enableMultiRowSelectionbooleanfalse다중 선택 허용
enableRowSelection(row: Row) => boolean() => true행별 선택 가능 여부
editablebooleanfalse인라인 편집 활성화
manualPaginationbooleanfalse서버 사이드 페이지네이션
manualSortingbooleanfalse서버 사이드 정렬
manualFilteringbooleanfalse서버 사이드 필터링
maxHeightstring | number-최대 높이 (스크롤)
loadingbooleanfalse로딩 상태
pageCountnumber-전체 페이지 수 (서버)
rowCountnumber-전체 행 수 (서버)
pageSizeOptionsnumber[][10,20,50,100]페이지 크기 옵션
tableLayout"auto" | "fixed"-테이블 레이아웃
showColumnSelectorbooleantrue컬럼 선택기 표시 여부
enableRowPinningbooleanfalse행 피닝 활성화
onRowSelectionChange(rows: T[]) => void-행 선택 변경 핸들러
onPaginationChange(state) => void-페이지 변경 핸들러
onSortingChange(state) => void-정렬 변경 핸들러
onCellUpdate(rowIdx, colId, val) => void-셀 업데이트 핸들러
onRowClick(row: T) => void-행 클릭 핸들러
onRowDoubleClick(row: T) => void-행 더블클릭 핸들러
classNamestring-추가 CSS 클래스

DataTableColumnDef

TanStack Table의 ColumnDef를 확장합니다.

PropTypeDefaultDescription
idstring-컬럼 고유 ID (필수)
headerstring | Function-헤더 텍스트/렌더 함수
accessorKeystring-데이터 접근 키
sizenumber-컬럼 너비 (px)
cellFunction-셀 커스텀 렌더 함수
enableSearchboolean-검색 활성화
metaobject-필터/편집/피닝 설정

Column Meta

PropTypeDescription
filterVariant"text" | "select" | "range" | "switch" | "checkbox" | "date"필터 유형
filterPlaceholderstring필터 플레이스홀더
filterAlwaysShowboolean필터 항상 표시
filterOptions{ label, value }[]select 필터 옵션
filterOrdernumber필터 표시 순서 (값이 있는 필터가 우선, 오름차순)
filterFormat(value: FilterValue) => string | React.ReactNode필터 값 포맷 함수
editableboolean셀 편집 가능 여부
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} />

접근성

ARIA 속성

<DataTable columns={columns} data={data} getRowId={(row) => row.id} /> // 자동: role="table", role="row", role="cell" 등

권장 사항

  • ✅ 각 컬럼에 명확한 header 텍스트 제공
  • ✅ 컬럼에 항상 idsize 명시
  • ✅ 키보드: Tab으로 셀 이동, 정렬/선택 조작
  • ✅ 로딩 상태에서 loading prop 활용
  • ❌ 과도한 컬럼 수로 가독성 저하 지양

관련 컴포넌트

Last updated on