Table

Table

테마 스타일로 표(table)를 렌더링하는 컴포넌트예요.

출처: 문서

본문

사용법 (Usage)

모든 예시에 사용되는 표 데이터:

const elements = [
  { position: 6, mass: 12.011, symbol: 'C', name: 'Carbon' },
  { position: 7, mass: 14.007, symbol: 'N', name: 'Nitrogen' },
  { position: 39, mass: 88.906, symbol: 'Y', name: 'Yttrium' },
  { position: 56, mass: 137.33, symbol: 'Ba', name: 'Barium' },
  { position: 58, mass: 140.12, symbol: 'Ce', name: 'Cerium' },
];
import { Table } from '@mantine/core';

function Demo() {
  const rows = elements.map((element) => (
    <Table.Tr key={element.name}>
      <Table.Td>{element.position}</Table.Td>
      <Table.Td>{element.name}</Table.Td>
      <Table.Td>{element.symbol}</Table.Td>
      <Table.Td>{element.mass}</Table.Td>
    </Table.Tr>
  ));

  return (
    <Table>
      <Table.Thead>
        <Table.Tr>
          <Table.Th>Element position</Table.Th>
          <Table.Th>Element name</Table.Th>
          <Table.Th>Symbol</Table.Th>
          <Table.Th>Atomic mass</Table.Th>
        </Table.Tr>
      </Table.Thead>
      <Table.Tbody>{rows}</Table.Tbody>
    </Table>
  );
}

data prop

data prop을 사용해 React 노드 배열에서 표 행을 자동으로 생성할 수 있어요. data prop은 다음 속성을 가진 객체를 받아요.

  • head – Table.Th를 Table.Thead에 렌더링하는 React 노드 배열(React.ReactNode[])
  • foot – Table.Th를 Table.Tfoot에 렌더링하는 React 노드 배열(React.ReactNode[])
  • body – Table.Td를 Table.Tbody에 렌더링하는 React 노드 배열의 배열(React.ReactNode[][])
  • caption – Table.Caption을 렌더링하는 React 노드
import { Table, TableData } from '@mantine/core';

const tableData: TableData = {
  caption: 'Some elements from periodic table',
  head: ['Element position', 'Atomic mass', 'Symbol', 'Element name'],
  body: [
    [6, 12.011, 'C', 'Carbon'],
    [7, 14.007, 'N', 'Nitrogen'],
    [39, 88.906, 'Y', 'Yttrium'],
    [56, 137.33, 'Ba', 'Barium'],
    [58, 140.12, 'Ce', 'Cerium'],
  ],
};

function Demo() {
  return <Table data={tableData} />;
}

고정 헤더 (Sticky header)

stickyHeader를 설정하면 표 헤더를 고정(sticky)할 수 있어요. 헤더의 상단 위치를 커스터마이즈하려면 stickyHeaderOffset prop을 사용해요. 앱에 고정 헤더가 있을 때 유용해요. 예를 들어 Mantine 문서 웹사이트에는 60px 높이의 고정 헤더가 있어요.

import { Table } from '@mantine/core';

function Demo() {
  const rows = elements.map((element) => (
    <Table.Tr key={element.name}>
      <Table.Td>{element.position}</Table.Td>
      <Table.Td>{element.name}</Table.Td>
      <Table.Td>{element.symbol}</Table.Td>
      <Table.Td>{element.mass}</Table.Td>
    </Table.Tr>
  ));

  return (
    <Table stickyHeader stickyHeaderOffset={60}>
      <Table.Thead>
        <Table.Tr>
          <Table.Th>Element position</Table.Th>
          <Table.Th>Element name</Table.Th>
          <Table.Th>Symbol</Table.Th>
          <Table.Th>Atomic mass</Table.Th>
        </Table.Tr>
      </Table.Thead>
      <Table.Tbody>{rows}</Table.Tbody>
    </Table>
  );
}

간격 (Spacing)

간격을 제어하려면 horizontalSpacing과 verticalSpacing prop을 사용해요. 두 prop 모두 theme.spacing의 간격과 유효한 CSS 값을 지원하며, 셀 패딩을 설정해요.

import { Table } from '@mantine/core';

function Demo() {
  return <Table horizontalSpacing="md" verticalSpacing="sm">{/* {...rows} */}</Table>;
}

캡션과 tfoot (Caption and tfoot)

Table은 tfoot과 caption 요소를 지원해요. captionSide prop(top 또는 bottom)으로 캡션 위치를 바꿀 수 있어요.

import { Table } from '@mantine/core';

function Demo() {
  const rows = elements.map((element) => (
    <Table.Tr key={element.name}>
      <Table.Td>{element.position}</Table.Td>
      <Table.Td>{element.name}</Table.Td>
      <Table.Td>{element.symbol}</Table.Td>
      <Table.Td>{element.mass}</Table.Td>
    </Table.Tr>
  ));

  const ths = (
    <Table.Tr>
      <Table.Th>Element position</Table.Th>
      <Table.Th>Element name</Table.Th>
      <Table.Th>Symbol</Table.Th>
      <Table.Th>Atomic mass</Table.Th>
    </Table.Tr>
  );

  return (
    <Table captionSide="bottom">
      <Table.Caption>Some elements from periodic table</Table.Caption>
      <Table.Thead>{ths}</Table.Thead>
      <Table.Tbody>{rows}</Table.Tbody>
      <Table.Tfoot>{ths}</Table.Tfoot>
    </Table>
  );
}

줄무늬와 행 호버 (Striped and rows hover)

import { Table } from '@mantine/core';

function Demo() {
  return (
    <Table striped highlightOnHover withTableBorder withColumnBorders withRowBorders>
      {/* {...rows} */}
    </Table>
  );
}

스크롤 컨테이너 (Scroll container)

뷰포트 넘침(overflow)을 방지하려면 Table을 Table.ScrollContainer로 감싸요. 이 컴포넌트는 minWidth prop을 받는데, 이 값 이하가 되면 표가 스크롤 가능해져요.

import { Table } from '@mantine/core';

function Demo() {
  return (
    <Table.ScrollContainer minWidth={500}>
      <Table>{/* ... */}</Table>
    </Table.ScrollContainer>
  );
}

기본적으로 Table.ScrollContainer는 ScrollArea를 사용해요. type="native"로 설정하면 네이티브 스크롤바로 바꿀 수 있어요.

Table.ScrollContainer에 maxHeight prop을 설정해 표 높이를 제한할 수도 있어요.

세로 variant (Vertical variant)

variant="vertical"을 설정하면 표를 세로 레이아웃으로 렌더링해요.

import { Table } from '@mantine/core';

export function Demo() {
  return (
    <Table variant="vertical" layout="fixed" withTableBorder>
      <Table.Tbody>
        <Table.Tr>
          <Table.Th w={160}>Epic name</Table.Th>
          <Table.Td>7.x migration</Table.Td>
        </Table.Tr>
        <Table.Tr>
          <Table.Th>Status</Table.Th>
          <Table.Td>Open</Table.Td>
        </Table.Tr>
        <Table.Tr>
          <Table.Th>Total issues</Table.Th>
          <Table.Td>135</Table.Td>
        </Table.Tr>
      </Table.Tbody>
    </Table>
  );
}

탭 숫자 (Tabular numbers)

tabularNums prop을 설정하면 숫자를 탭 스타일로 렌더링해요. font-variant-numeric: tabular-nums를 설정해 숫자의 폭을 동일하게 만들어요. 숫자가 있는 열을 정렬하고 싶을 때 유용해요.

import { NumberFormatter, Table } from '@mantine/core';

const data = [
  { product: 'Apples', unitsSold: 2214411234 },
  { product: 'Oranges', unitsSold: 9983812411 },
  { product: 'Bananas', unitsSold: 1234567890 },
  { product: 'Pineapples', unitsSold: 9948810000 },
  { product: 'Pears', unitsSold: 9933771111 },
];

function Demo() {
  const rows = data.map((item) => (
    <Table.Tr key={item.product}>
      <Table.Td>{item.product}</Table.Td>
      <Table.Td><NumberFormatter value={item.unitsSold} /></Table.Td>
    </Table.Tr>
  ));

  return (
    <Table tabularNums>
      <Table.Thead>
        <Table.Tr>
          <Table.Th>Product</Table.Th>
          <Table.Th>Units sold</Table.Th>
        </Table.Tr>
      </Table.Thead>
      <Table.Tbody>{rows}</Table.Tbody>
    </Table>
  );
}

예시: 테이블 가상화 (Example: Table virtualization)

이 예시는 Table.ScrollContainer를 @tanstack/react-virtual과 함께 사용해 대규모 데이터셋을 효율적으로 렌더링하는 방법을 보여줘요. 보이는 행만 DOM에서 렌더링되므로 수천 행의 표에서 성능이 크게 개선돼요.

import { useState } from 'react';
import { useVirtualizer } from '@tanstack/react-virtual';
import { Table } from '@mantine/core';
import { generateData } from './data';

const data = generateData(5000);
const ROW_HEIGHT = 36;

function Demo() {
  const [scrollParent, setScrollParent] = useState<HTMLDivElement | null>(null);

  const virtualizer = useVirtualizer({
    count: data.length,
    getScrollElement: () => scrollParent,
    estimateSize: () => ROW_HEIGHT,
    overscan: 20,
  });

  const virtualItems = virtualizer.getVirtualItems();

  return (
    <Table.ScrollContainer minWidth={800} ref={setScrollParent}>
      <Table>
        <Table.Thead>
          <Table.Tr>
            <Table.Th>#</Table.Th>
            <Table.Th>Name</Table.Th>
            <Table.Th>Email</Table.Th>
            <Table.Th>Company</Table.Th>
            <Table.Th>City</Table.Th>
          </Table.Tr>
        </Table.Thead>
        <Table.Tbody>
          {virtualItems.map((virtualItem) => {
            const row = data[virtualItem.index];
            return (
              <Table.Tr key={row.id}>
                <Table.Td>{row.id}</Table.Td>
                <Table.Td>{row.name}</Table.Td>
                <Table.Td>{row.email}</Table.Td>
                <Table.Td>{row.company}</Table.Td>
                <Table.Td>{row.city}</Table.Td>
              </Table.Tr>
            );
          })}
        </Table.Tbody>
      </Table>
    </Table.ScrollContainer>
  );
}

예시: 행 선택이 있는 표 (Example: Table with row selection)

import { useState } from 'react';
import { Table, Checkbox } from '@mantine/core';

const elements = [
  { position: 6, mass: 12.011, symbol: 'C', name: 'Carbon' },
  { position: 7, mass: 14.007, symbol: 'N', name: 'Nitrogen' },
  { position: 39, mass: 88.906, symbol: 'Y', name: 'Yttrium' },
  { position: 56, mass: 137.33, symbol: 'Ba', name: 'Barium' },
  { position: 58, mass: 140.12, symbol: 'Ce', name: 'Cerium' },
];

function Demo() {
  const [selectedRows, setSelectedRows] = useState<number[]>([]);

  const rows = elements.map((element) => (
    <Table.Tr key={element.name} data-selected={selectedRows.includes(element.position)}>
      <Table.Td>
        <Checkbox
          checked={selectedRows.includes(element.position)}
          onChange={(event) =>
            setSelectedRows(
              event.currentTarget.checked
                ? [...selectedRows, element.position]
                : selectedRows.filter((position) => position !== element.position)
            )
          }
        />
      </Table.Td>
      <Table.Td>{element.position}</Table.Td>
      <Table.Td>{element.name}</Table.Td>
      <Table.Td>{element.symbol}</Table.Td>
      <Table.Td>{element.mass}</Table.Td>
    </Table.Tr>
  ));

  return (
    <Table>
      <Table.Thead>
        <Table.Tr>
          <Table.Th />
          <Table.Th>Element position</Table.Th>
          <Table.Th>Element name</Table.Th>
          <Table.Th>Symbol</Table.Th>
          <Table.Th>Atomic mass</Table.Th>
        </Table.Tr>
      </Table.Thead>
      <Table.Tbody>{rows}</Table.Tbody>
    </Table>
  );
}

더 알아보기 (Learn more)