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>
);
}