Data Grid
TanStack Table 위에 올린 헤드리스 데이터 그리드 — 상태와 라우팅만 소유하고 UI는 위임합니다.
개요
@featuring-corp/data-grid는 TanStack Table v8 위에 올린 헤드리스(headless) 데이터 그리드입니다.
스프레드시트급 상호작용(셀 선택·클립보드·인라인 편집·자동 채우기·셀 병합)을 제공하되, 셀의 모양은 소유하지 않습니다.
- 얇은 인프라(thin-infra) — 그리드는 상태와 키/포인터 라우팅만 책임집니다. 셀·헤더·에디터의 렌더는 전적으로 소비자 코드입니다.
- 명시 합성(explicit composition) —
useDataGrid+ 기능 훅(useDataGridSelection/Editing/FillHandle)을 만들어<DataGrid.Root>에 주입합니다. 매직 없는 의존성 주입. - TanStack 그대로 — row model·정렬·필터·페이지네이션은 TanStack의 표준 API를 그대로 씁니다. 그리드는 그 위에 셀 선택·편집·클립보드를 얹을 뿐입니다.
- 디자인 시스템 정합 — 크기(
sm/md/lg)·토큰·@layer ft-*캐스케이드·$css가 컴포넌트 패키지와 동일하게 동작합니다. - 접근성 우선 —
rolegrid/table/treegrid,aria-rowindex/aria-colindex절대 좌표,aria-sort/aria-selected를 사실대로 노출합니다.
이 문서는 코어 모델과 설계 사상을 다룹니다. 실제 사용 예제는 Storybook의
DataGrid/*스토리에서 살아 있는 형태로 확인하세요. 여기서는 "왜 이렇게 설계했는가 → 무엇인가 → 어떻게 엮이는가"를 설명합니다.
설치
pnpm add @featuring-corp/data-grid @tanstack/react-tablepeer dependency로 다음을 요구합니다.
@tanstack/react-table ^8.21.3react ^18 || ^19,react-dom ^18 || ^19
브랜드 토큰·리셋·@layer 순서를 담은 프리셋 CSS를 한 번 import 합니다(컴포넌트 패키지 프리셋을 이미 쓰고 있다면 생략 가능).
import '@featuring-corp/data-grid/preset/featuring';
// 또는
import '@featuring-corp/data-grid/preset/dataEffect';빠른 시작
가장 단순한 그리드입니다. row model 팩토리는 기본으로 깔리지 않으므로 getCoreRowModel()을 직접 넘겨야 합니다(정렬·필터·페이지네이션도 필요한 것만 명시).
import { useDataGrid, DataGrid, getCoreRowModel, type ColumnDef } from '@featuring-corp/data-grid';
type Row = { id: string; name: string; followers: number };
const columns: ColumnDef<Row>[] = [
{
accessorKey: 'name',
header: ({ header }) => <DataGrid.HeaderCell header={header}>채널</DataGrid.HeaderCell>,
cell: (info) => <DataGrid.Cell cell={info.cell}>{info.getValue() as string}</DataGrid.Cell>,
},
{
accessorKey: 'followers',
header: ({ header }) => <DataGrid.HeaderCell header={header}>팔로워</DataGrid.HeaderCell>,
cell: (info) => <DataGrid.Cell cell={info.cell}>{(info.getValue() as number).toLocaleString()}</DataGrid.Cell>,
},
];
function Grid({ data }: { data: Row[] }) {
const table = useDataGrid<Row>({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getRowId: (r) => r.id,
});
return (
<DataGrid.Root table={table}>
<DataGrid.Header />
<DataGrid.Body />
</DataGrid.Root>
);
}아래는 살아 있는 예제입니다 — 코드를 직접 고치면 즉시 반영됩니다. (data·columns는 컴포넌트 밖에서 한 번만 만들어 안정적인 참조를 유지합니다 — 렌더마다 새로 만들면 controlled 상태와 만나 무한 렌더가 됩니다.)
const data = [ { id: '1', name: '김민준', followers: 128000 }, { id: '2', name: '이서연', followers: 84500 }, { id: '3', name: '박도윤', followers: 233000 }, { id: '4', name: '최하은', followers: 45200 }, ]; const columns = [ { accessorKey: 'name', header: ({ header }) => <DataGrid.HeaderCell header={header}>채널</DataGrid.HeaderCell>, cell: (info) => <DataGrid.Cell cell={info.cell}>{info.getValue()}</DataGrid.Cell>, }, { accessorKey: 'followers', header: ({ header }) => <DataGrid.HeaderCell header={header}>팔로워</DataGrid.HeaderCell>, cell: (info) => <DataGrid.Cell cell={info.cell}>{info.getValue().toLocaleString()}</DataGrid.Cell>, }, ]; function HelloGrid() { const table = useDataGrid({ data, columns, getCoreRowModel: getCoreRowModel(), getRowId: (row) => row.id, }); return ( <DataGrid.Root table={table} size="md"> <DataGrid.Header /> <DataGrid.Body /> </DataGrid.Root> ); } render(<HelloGrid />);
<DataGrid.Header />와 <DataGrid.Body />는 children이 없으면 자동 렌더합니다(모든 헤더 그룹·행을 순회). 행/셀을 직접 구성하려면
render-prop 모드로 전환합니다 — Composition & DI를 참고하세요.
멘탈 모델 — 세 조각의 합성
그리드는 세 종류의 객체를 명시적으로 합성합니다. 훅이 인스턴스를 만들고, Root가 그것을 context로 전파합니다.
useDataGrid(options) ─────────────▶ table (TanStack Table + loading/error/virtualization 확장)
useDataGridSelection(table) ──────▶ selection
useDataGridEditing(table, opts) ──▶ editing
useDataGridFillHandle(table, opts)▶ fillHandle
<DataGrid.Root table selection editing fillHandle> ← DI 허브
<DataGrid.Header /> <DataGrid.Body /> ← context에서 위 인스턴스를 읽어 동작
</DataGrid.Root>useDataGrid— TanStackTable을 만들고loading/error/getSkeletonRowCount/virtualization/debug를 얹은DataGridInstance를 돌려줍니다.- 기능 훅 — 셀 선택·편집·자동 채우기는 각각 독립 훅입니다. 필요한 기능만 만들면 됩니다(편집 없는 읽기 전용 그리드는
useDataGridEditing을 호출하지 않습니다). Root는 DI 허브 — 훅 반환값을 prop으로 받아 context에 전파합니다.table에 부착하지 않고 prop으로 주입하므로 훅 호출 순서에 의존하지 않고, 타입이 합성을 강제합니다. 주입하지 않은 기능은 조용히 비활성(개발 모드에서 1회 경고)됩니다.
자세한 구조는 Architecture와 Composition & DI에서 다룹니다.
무엇을 소유하고, 무엇을 위임하나
이 그리드의 핵심 결정은 책임 경계입니다.
| 그리드가 소유 | 소비자가 소유 |
|---|---|
| 셀 선택 상태(anchor/range)·키보드 내비게이션 | 셀·헤더의 렌더(column.cell/header) |
| 편집 라이프사이클(진입/commit/cancel)·undo/redo | 에디터 UI(TextInput·Select·DatePicker…) |
| 클립보드 직렬화(TSV)·붙여넣기 타일링·잘라내기 이동 | 데이터 state 갱신(onCellEdit) |
| 자동 채우기 기하(드래그/더블클릭) | 채우기 값 계산 정책(meta.fill/getFillValue) |
| 좌표 공간·병합(colSpan/rowSpan) dense 시맨틱 | 빈/로딩/에러 레이아웃(minHeight·정렬) |
| ARIA 역할·좌표·정렬/선택 상태 노출 | aria-label 등 로케일 문자열 |
그래서 그리드는 "어떤 에디터를 쓸지", "셀이 어떻게 생겼는지", "에러 화면이 어떤 모양인지"에 어떤 의견도 갖지 않습니다. 이 무의견(unopinionated) 성질이 디자인 시스템 안에서 자유로운 조립을 가능하게 합니다.
다른 그리드와의 차이
- AG Grid / MUI X DataGrid 대비 — 그것들은 셀 렌더러·테마·에디터를 프레임워크가 소유합니다. 이 그리드는 그 반대로, 렌더를 전부 소비자에게 돌려주고 상호작용 인프라만 제공합니다.
- TanStack Table 단독 대비 — TanStack은 row model·정렬·필터의 "두뇌"지만 셀 선택·클립보드·인라인 편집·셀 병합은 없습니다. 이 패키지는 그 위에 그 스프레드시트 레이어를 얹고, 디자인 시스템 토큰·접근성을 묶습니다.
다음 단계
설계 사상·코어 모델 → 기능별 사용법 → 레퍼런스 순으로 이어집니다. 사이드바도 같은 순서입니다.
코어 모델
- Architecture —
DataGridInstance·runtime registry·context 전파·compound 트리. - Composition & DI — 명시 DI 모델, 자동 렌더 vs render-prop,
Cell/HeaderCell유니온.
컬럼
- Columns — accessor·display 컬럼,
meta, 가시성·리사이즈·순서. - Grouped headers — 중첩 columnDef로 만드는 다단계 헤더.
- Sorting —
getSortedRowModel·SortArrow·다중/서버 정렬. - Filtering — 전역·컬럼·패싯 필터·서버 위임.
- Pagination — 클라이언트/서버 페이지네이션·
aria-rowindex절대 좌표.
선택 · 편집
- Row selection — 체크박스 행 선택·전체 선택·controlled.
- Selection model — 셀 선택(anchor/range 다중 사각형)·정책 seam.
- Editing model — 얇은 인프라 에디터·record-on-commit/confirm·undo/redo.
- Validation —
meta.parseValue단일 진실·동기/비동기 검증. - Clipboard & Fill — TSV·잘라내기 이동·채우기 우선순위.
행 그룹 · 계층
- Grouping —
getGroupedRowModel·집계·treegrid. - Tree data —
getSubRows계층·aria-level/posinset/setsize. - Expansion — 행 아래 상세 패널 펼치기.
레이아웃 · 스케일
- Cell merging —
colSpan/rowSpandense 좌표. - Pinning — sticky 좌/우 고정·경계 마커·병합 clamp.
- Reordering — @dnd-kit 레시피·
columnOrder·onCellPointerDownveto. - Virtualization — 행/열 윈도잉·
estimateRowHeight·scroll-to-active. - View state — loading/error/empty·skeleton.
- Server-side data — manual sort/filter/pagination·서버 CRUD.
- Touch & Pointer — 통합 포인터 모델·탭/더블탭·선택 핸들.
레퍼런스
- Accessibility — role·좌표·정직한 상태 ARIA·로케일 중립 정책.
- Extending — 모든 override seam과 veto 패턴.
- API reference — 공개 export 표.