Работа со списками: Table, Badge, Avatar
Когда в приложении появляются десятки или сотни однотипных записей — пользователей, заказов, задач — простого текста уже недостаточно. Нужны компоненты, которые умеют компактно и наглядно показывать списки данных. В этом уроке разберём три кита Mantine для работы со списками: Table для табличных данных, Badge для статусов и меток, и Avatar для визуального представления пользователей.
Компонент Table: строим таблицу с нуля
Анатомия Table
Как и многие другие сложные компоненты Mantine, Table построен по принципу составных компонентов: сама обёртка Table отвечает только за общий вид, а разметка строк и ячеек описывается через вложенные части — Table.Thead, Table.Tbody, Table.Tr, Table.Th и Table.Td. Такой подход даёт полный контроль над структурой: вы сами решаете, сколько строк и колонок нужно, и можете вставлять в ячейки любой JSX, а не только текст.
import { Table } from '@mantine/core';
function SimpleTable() {
return (
<Table>
<Table.Thead>
<Table.Tr>
<Table.Th>Имя</Table.Th>
<Table.Th>Email</Table.Th>
<Table.Th>Роль</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
<Table.Tr>
<Table.Td>Анна Иванова</Table.Td>
<Table.Td>anna@example.com</Table.Td>
<Table.Td>Администратор</Table.Td>
</Table.Tr>
</Table.Tbody>
</Table>
);
}
Обратите внимание: заголовки колонок находятся внутри Table.Thead, а строки с данными — внутри Table.Tbody. Такое разделение не только семантически правильно (это стандартная структура HTML-таблиц), но и упрощает стилизацию: например, зафиксировать шапку таблицы при прокрутке можно только если она находится в отдельном thead.
Полезные пропсы Table
Голая таблица редко выглядит презентабельно, поэтому Mantine добавляет набор пропсов, которые меняют внешний вид без единой строчки собственного CSS.
| Проп | Что делает |
|---|---|
striped |
Чередует цвет фона строк для лучшей читаемости |
highlightOnHover |
Подсвечивает строку при наведении курсора |
withTableBorder |
Добавляет внешнюю рамку вокруг таблицы |
withColumnBorders |
Рисует вертикальные разделители между колонками |
stickyHeader |
Закрепляет заголовок при прокрутке длинной таблицы |
Все эти пропсы можно комбинировать. Например, для длинного списка заказов часто включают сразу striped, highlightOnHover и withTableBorder — это делает таблицу удобной для сканирования взглядом даже без единого ряда собственных стилей.
<Table
striped
highlightOnHover
withTableBorder
withColumnBorders
verticalSpacing="sm"
>
{/* ... */}
</Table>
Рендеринг данных в таблице
На практике данные почти никогда не пишутся в JSX вручную — они приходят из API, стора или пропсов родительского компонента. Чтобы превратить массив объектов в строки таблицы, достаточно вызвать map и вернуть Table.Tr для каждого элемента.
interface User {
id: string;
name: string;
email: string;
}
const users: User[] = [
{ id: '1', name: 'Анна Иванова', email: 'anna@example.com' },
{ id: '2', name: 'Пётр Сидоров', email: 'petr@example.com' },
];
function UsersTable() {
const rows = users.map((user) => (
<Table.Tr key={user.id}>
<Table.Td>{user.name}</Table.Td>
<Table.Td>{user.email}</Table.Td>
</Table.Tr>
));
return (
<Table>
<Table.Tbody>{rows}</Table.Tbody>
</Table>
);
}
Всегда передавайте в key стабильный уникальный идентификатор из данных (например, id), а не индекс массива. Если список можно отсортировать, отфильтровать или удалить из него элемент, индексы у оставшихся строк сдвинутся, и React перепутает, какой DOM-узел к какому элементу относится — это приводит к странным багам с потерянным состоянием инпутов внутри строк.
Такой же подход работает для любых списков — не только таблиц. Массив данных превращается в массив JSX-элементов через map, а React сам эффективно обновляет только изменившиеся строки при повторном рендере.
Компонент Badge: статусы и метки
Когда в таблице есть колонка вроде «статус заказа» или «роль пользователя», выводить её обычным текстом — плохая идея: глазу не за что зацепиться. Для таких случаев в Mantine есть компонент Badge — компактная цветная метка с текстом внутри.
Главные пропсы Badge — это color (любой цвет из темы) и variant, который определяет стиль заливки.
filled— сплошная заливка цветом, текст белыйlight— приглушённый полупрозрачный фон, подходит для спокойных статусовoutline— только контур без заливкиdot— маленькая цветная точка перед текстом вместо заливки всего бейджа
Часто цвет бейджа зависит от значения статуса, поэтому удобно завести объект-словарь «статус → цвет» и подставлять нужный цвет динамически.
import { Badge } from '@mantine/core';
const statusColors: Record<string, string> = {
active: 'teal',
pending: 'yellow',
blocked: 'red',
};
function StatusBadge({ status }: { status: string }) {
return (
<Badge color={statusColors[status]} variant="light">
{status}
</Badge>
);
}
Компонент Avatar: аватары пользователей
Avatar нужен там, где нужно показать фотографию или инициалы пользователя — в шапке сайта, в списке комментариев или, как в нашем случае, в таблице. Если передать src, компонент покажет картинку; если картинки нет или она не загрузилась, Avatar автоматически покажет инициалы, извлечённые из пропса name.
import { Avatar } from '@mantine/core';
<Avatar src="https://example.com/avatar.jpg" alt="Анна Иванова" radius="xl" />
<Avatar name="Пётр Сидоров" color="initials" radius="xl" />
Проп radius="xl" делает аватар круглым — это самый частый вариант оформления. А значение color="initials" заставляет Mantine генерировать цвет фона на основе самого имени, поэтому у разных пользователей автоматически получаются разные, но стабильные цвета плашек с инициалами.
Группировка через Avatar.Group
Если нужно показать сразу нескольких участников — например, кто состоит в проекте или кто оставил реакцию — пригодится Avatar.Group. Он накладывает аватары друг на друга внахлёст и умеет схлопывать «лишних» участников в кружок с числом.
import { Avatar } from '@mantine/core';
<Avatar.Group spacing="sm">
<Avatar src="/user1.jpg" radius="xl" />
<Avatar src="/user2.jpg" radius="xl" />
<Avatar src="/user3.jpg" radius="xl" />
<Avatar radius="xl">+5</Avatar>
</Avatar.Group>
Собери свой код. Запусти сайт!
От наброска на салфетке до первого работающего лендинга. Наш онлайн-курс «Веб-верстка с нуля и до профессионала» — это интенсивный трек, где ты не будешь зубрить теорию, а с первого дня начнешь превращать идеи в чистый HTML и CSS.
Собери свой первый проект под руководством практикующих разработчиков.
Собираем всё вместе: таблица пользователей
Если таблица большая (сотни строк) и данные обновляются часто — например, приходят по WebSocket — стоит подумать о производительности рендера. Каждый повторный рендер родителя по умолчанию заново рендерит все строки, даже если изменилась только одна из них.
Вынесите строку таблицы в отдельный компонент и оберните его в memo из react. Пока пропсы конкретной строки не поменялись, React пропустит её повторный рендер, даже если обновился родительский компонент или изменились данные соседних строк. Это особенно заметно на больших списках с десятками и сотнями элементов.
import { Table, Badge, Avatar, Group, Text } from '@mantine/core';
import { memo } from 'react';
interface User {
id: string;
name: string;
avatarUrl?: string;
status: 'active' | 'pending' | 'blocked';
}
const statusColors: Record<User['status'], string> = {
active: 'teal',
pending: 'yellow',
blocked: 'red',
};
const UserRow = memo(function UserRow({ user }: { user: User }) {
return (
<Table.Tr>
<Table.Td>
<Group gap="sm">
<Avatar src={user.avatarUrl} name={user.name} radius="xl" size="sm" />
<Text size="sm">{user.name}</Text>
</Group>
</Table.Td>
<Table.Td>
<Badge color={statusColors[user.status]} variant="light">
{user.status}
</Badge>
</Table.Td>
</Table.Tr>
);
});
function UsersTable({ users }: { users: User[] }) {
return (
<Table striped highlightOnHover withTableBorder>
<Table.Thead>
<Table.Tr>
<Table.Th>Пользователь</Table.Th>
<Table.Th>Статус</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{users.map((user) => (
<UserRow key={user.id} user={user} />
))}
</Table.Tbody>
</Table>
);
}
Получившийся компонент уже полностью готов к использованию в реальном проекте: он принимает произвольный массив пользователей, аккуратно отображает аватар и имя, а статус подсвечивает бейджем нужного цвета.
Итоги
В этом уроке мы разобрали три компонента Mantine, без которых не обходится почти ни один список данных в интерфейсе.
- Изучили анатомию
Tableи составные частиTable.Thead,Table.Tbody,Table.Tr,Table.Th,Table.Td - Научились превращать массив данных в строки таблицы через
mapи правильно указыватьkey - Использовали
Badgeдля наглядного отображения статусов с помощью цвета и варианта заливки - Показали пользователей через
Avatarс инициалами и объединили несколько аватаров вAvatar.Group - Собрали готовую таблицу пользователей и оптимизировали её рендер с помощью
memo
В следующем уроке переключимся с компонентов на хуки: разберём useDisclosure, useToggle и useCounter — небольшие, но очень удобные инструменты, которые упрощают управление состоянием интерфейса.