본문으로 건너뛰기

Table

열 정의와 행 데이터를 받아 데이터 격자를 그립니다. <tr>이나 <td>를 직접 쓰지 않습니다.

tsx
import { Table, type TableColumn } from 'neba';

const headers: TableColumn<Deploy>[] = [
  { key: 'environment', label: '환경', width: 180 },
  { key: 'duration', label: '소요', align: 'end', render: (row) => `${row.duration}분` }
];

<Table headers={headers} items={deploys} getRowKey={(row) => row.id} />;

Props

Prop타입기본값설명
variant공통'solid' | 'outline' | 'text''outline'표면의 무게. Box에 그대로 전달됩니다. text로 두면 시트 없이 행만 남습니다
size공통'xs' | 'sm' | 'md' | 'lg' | 'xl''md'셀의 타입 스케일과 여백, 시트의 모서리 반경
color공통'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info''primary'의미론적 색 역할. 시트는 흰색이므로 하이라인과 행 사이 선에만 나타납니다
density공통'default' | 'compact''default'여백만 바꿉니다. 높이와 글자 크기는 그대로
elevation공통0 | 1 | 2 | 30그림자 깊이. 0은 그림자 없음
headers * readonly TableColumn[]열 정의. key·label·width·align·render를 가집니다
items * readonly Row[]행 데이터
getRowKey(row, index) => Keyindex행의 안정적인 key. 정렬이나 필터가 있으면 반드시 넘기세요
captionReactNode표 위의 설명. 접근성 이름으로도 읽힙니다
emptyReactNodeitems가 비었을 때 대신 보여 줄 내용
stripedbooleanfalse한 행 걸러 배경을 깝니다. 넓은 표에서 눈이 가로로 따라가야 할 때
hoverablebooleanfalse포인터가 올라간 행을 밝힙니다
stickyHeaderbooleanfalse본문이 스크롤될 때 머리행을 고정합니다. 표의 높이가 제한되어 있어야 의미가 있습니다
onRowClick(row, index) => void행을 누를 수 있게 만듭니다
localestring이 컴포넌트가 스스로 쓰는 문자열의 BCP 47 태그. 기본 정렬이 문자열을 비교할 때 쓰는 locale이기도 합니다
classNamesNebaSlots<'table' | 'caption' | 'head' | 'headCell' | 'body' | 'row' | 'cell' | 'empty'>루트 뒤에 있는 각 파트의 class. 루트 자체는 className이 맡으므로 root 키는 없습니다

바깥 sheet은 Box입니다. variant · size · color · density · elevation이 그대로 통과합니다.

TableColumn

ts
interface TableColumn<Row> {
  key: string; // 열 식별자이자, 행에서 읽을 속성 이름
  label?: React.ReactNode; // 머리글. 생략하면 key
  width?: number | string; // 숫자는 px, 문자열은 CSS 길이
  align?: 'start' | 'center' | 'end';
  render?: (row: Row, index: number) => React.ReactNode;
}

render를 주면 셀 내용을 직접 그리고, 없으면 row[key]를 그대로 출력합니다.

예시

width와 align

width기준 너비입니다. 표는 남는 공간에 맞춰 열을 늘리므로 고정값이 아니라 출발 비율로 동작합니다. 값은 <col>에 적용되므로 모든 행에 일관되게 적용됩니다.

숫자 열에는 자릿수를 맞추기 위해 보통 align: 'end'를 씁니다.

striped · hoverable · onRowClick

striped는 행마다 배경을 번갈아 칠합니다. 눈이 가로로 길게 이동해야 하는 넓은 표에 유용합니다. onRowClick은 행을 누를 수 있게 만들고 hover 처리도 함께 켭니다.

getRowKey의 기본값은 행 인덱스입니다. 정렬이나 필터로 행 순서가 바뀔 수 있다면 직접 넘기세요.

empty

items가 비었을 때 표시할 내용입니다. 모든 열을 가로지르는 하나의 셀로 렌더링됩니다.

머리행을 스크롤 중에 고정합니다. 표를 감싼 요소가 높이를 제한하고 있을 때만 효과가 있습니다.

classNames

className은 sheet(table이 가로로 스크롤되는 Box)이고, <table>과 그 안의 모든 것은 classNames로 갑니다.

tsx
<Table
  headers={headers}
  items={items}
  classNames={{ table: 'tabular-nums', headCell: 'text-(--neba-fg)', row: 'align-top' }}
/>

slot은 table, caption, head, headCell, body, row, cell, empty입니다.

cell을 쓰기 전에 알아 둘 것이 하나 있습니다. cell의 padding과 정렬, 배경은 utility가 아니라 inline style로 쓰여 있습니다. host stylesheet의 td 규칙이 한 개짜리 utility를 이기기 때문입니다. headCell·cell·empty에 넘긴 class는 컴포넌트가 inline으로 정하지 않은 것(색, 폰트, 테두리)은 무엇이든 더할 수 있지만, 저 셋을 바꾸려면 important utility(p-4!)여야 합니다. prop 규약을 보세요.

접근성

  • 실제 <table><th scope="col">로 렌더링됩니다.
  • caption은 표의 accessible name으로 읽힙니다.
  • 빈 상태 셀은 colSpan으로 모든 열을 덮으므로, 첫 열에만 걸린 짧은 텍스트로 읽히지 않습니다.
  • onRowClick을 넘기면 행이 tab 순서에 들어가고 Enter와 Space에 반응하며, focus-visible ring을 그립니다. 행의 role은 그대로 두므로 열 머리글과 행 위치는 계속 읽힙니다.
  • 셀 안에 링크나 버튼을 두면 그 컨트롤의 키 입력은 컨트롤이 처리합니다. 다만 컨트롤이 발생시킨 click은 행까지 버블링되므로, 행이 함께 열리지 않아야 한다면 핸들러에서 event.stopPropagation()을 호출하세요.
  • 행이 없을 때의 문구는 locale이 정합니다. empty로 직접 쓸 수도 있습니다.

Released under the MIT License