diff --git a/src/components/Table/__stand__/Table.dev.stand.mdx b/src/components/Table/__stand__/Table.dev.stand.mdx
index 8ad09b2..3d624ee 100644
--- a/src/components/Table/__stand__/Table.dev.stand.mdx
+++ b/src/components/Table/__stand__/Table.dev.stand.mdx
@@ -22,6 +22,7 @@ import { TableExampleLoadingDataWithSkeleton } from './examples/TableExampleLoad
import { TableExampleLoadingRowWithLoader } from './examples/TableExampleLoadingRowWithLoader/TableExampleLoadingRowWithLoader';
import { TableExampleLoadingRowWithSkeleton } from './examples/TableExampleLoadingRowWithSkeleton/TableExampleLoadingRowWithSkeleton';
import { TableExampleLoadingNestedRowWithLoader } from './examples/TableExampleLoadingNestedRowWithLoader/TableExampleLoadingNestedRowWithLoader';
+import { TableExampleFilter } from './examples/TableExampleFilter/TableExampleFilter';
import {
TableExampleResizableInside,
TableExampleResizableOutside,
@@ -31,9 +32,13 @@ import { MdxTabs, MdxMenu, MdxInformer } from '@consta/stand';
+- [Обзор](#обзор)
+- [Импорт](#импорт)
- [Свойства](#свойства)
-- [Как формируется таблица](#как-формируется-таблица)
-- [Колонка](#колонка)
+- [Содержимое](#содержимое)
+ - [Строки и колонки](#строки-и-колонки)
+ - [Колонка](#колонка)
+- [Внешний вид](#внешний-вид)
- [Ширина колонки](#ширина-колонки)
- [Разделитель колонок](#разделитель-колонок)
- [Закрепление колонок](#закрепление-колонок)
@@ -41,74 +46,54 @@ import { MdxTabs, MdxMenu, MdxInformer } from '@consta/stand';
- [Представление данных в ячейке](#представление-данных-в-ячейке)
- [Представление данных в ячейке в шапке](#представление-данных-в-ячейке-в-шапке)
- [Объединение ячеек](#объединение-ячеек)
-- [Закрепление шапки](#закрепление-шапки)
-- [Виртуальный скролл](#виртуальный-скролл)
-- [onScrollToBottom](#onScrollToBottom)
-- [Таблица в полоску](#таблица-в-полоску)
-- [Выделение строк](#выделение-строк)
- - [Наведение на строку](#наведение-на-строку)
- - [Выбор строки](#выбор-строки)
-- [Управление шириной столбцов](#управление-шириной-столбцов)
-- [headerZIndex](#headerzindex)
-- [Вложенные строки](#вложенные-строки)
-- [Рендер строки](#рендер-строки)
-- [Индикатор ячейки и подсказки](#индикатор-ячейки-и-подсказки)
-- [Ключ строки](#ключ-строки)
-- [Адаптивная ширина колонок](#адаптивная-ширина-колонок)
+ - [Закрепление шапки](#закрепление-шапки)
+ - [Таблица в полоску](#таблица-в-полоску)
+ - [Управление шириной столбцов](#управление-шириной-столбцов)
+ - [headerZIndex](#headerzindex)
+ - [Вложенные строки](#вложенные-строки)
+ - [Рендер строки](#рендер-строки)
+ - [Индикатор ячейки и подсказки](#индикатор-ячейки-и-подсказки)
+ - [Адаптивная ширина колонок](#адаптивная-ширина-колонок)
+- [Поведение](#поведение)
+ - [Виртуальный скролл](#виртуальный-скролл)
+ - [onScrollToBottom](#onscrolltobottom)
+ - [Выделение строк](#выделение-строк)
+ - [Состояние загрузки](#состояние-загрузки)
+ - [Ключ строки](#ключ-строки)
+ - [Фильтрация](#фильтрация)
+- [Пример использования](#пример-использования)
-## Как формируется таблица
+## Обзор
-Для вывода самой простой таблицы, используйте 2 свойства:
-
-- `rows` - строки. Тип строки может быть любым массивом объекта `Record`.
-- `columns`- колонки. Представляют массив объектов, у которого `accessor` это свойство объекта ваших данных.
+Компонент Table предназначен для отображения табличных данных. Он поддерживает настройку колонок, строк, виртуальную прокрутку, закрепление элементов и другие функции для работы с большими наборами данных.
-
-
-
+## Импорт
```tsx
import { Table, TableColumn } from '@consta/table/Table';
-
-type Row = { name: string; profession: string; status: string };
-
-const rows: Row[] = [
- {
- name: 'Антон',
- profession: 'Строитель, который построил дом',
- status: 'недоступен',
- },
- {
- name: 'Василий',
- profession: 'Отвечает на вопросы, хотя его не спросили',
- status: 'на связи',
- },
-];
-
-const columns: TableColumn[] = [
- {
- title: 'Имя',
- accessor: 'name',
- },
- {
- title: 'Профессия',
- accessor: 'profession',
- },
- {
- title: 'Статус',
- accessor: 'status',
- },
-];
-
-export const TableExampleSimple = () => ;
```
-
-
## Свойства
+| Свойство | Тип | По умолчанию | Описание |
+| -------------------------------------------- | ----------------------------- | ----------------- | ------------------------------------------------------------ |
+| [`columns?`](#колонка) | `TableColumn[]` | - | Колонки |
+| [`rows?`](#строки-и-колонки) | `ROW[]` | - | Строки |
+| [`getRowKey?`](#ключ-строки) | `GetRowKey` | `(row) => row.id` | Функция получения ключа, если ключ не найден берется `index` |
+| [`onRowMouseEnter?`](#выделение-строк) | `TableRowMouseEvent` | - | Событие `onMouseEnter` на строке |
+| [`onRowMouseLeave?`](#выделение-строк) | `TableRowMouseEvent` | - | Событие `onMouseLeave` на строке |
+| [`onRowClick?`](#выделение-строк) | `TableRowMouseEvent` | - | Событие `onClick` на строке |
+| [`virtualScroll?`](#виртуальный-скролл) | `boolean` | - | Включение виртуальной прокрутки |
+| [`stickyHeader?`](#закрепление-шапки) | `boolean` | - | Зафиксировать шапку сверху |
+| [`resizable?`](#управление-шириной-столбцов) | `'inside'` | `'outside'` | - | Включение возможности изменять ширину колонок |
+| [`zebraStriped?`](#таблица-в-полоску) | `boolean` | - | Окрашивание строк через одну |
+| [`headerZIndex?`](#headerzindex) | `number` | `1` | `zIndex` шапки |
+| [`rowHoverEffect?`](#выделение-строк) | `boolean` | - | Включает эффект наведения на строку |
+| `className?` | `string` | - | Дополнительный CSS-класс |
+| `ref?` | `React.Ref` | - | Ссылка на корневой DOM-элемент |
+
```ts
export type TableRenderHeaderCell = (props: {
title?: string;
@@ -155,24 +140,58 @@ type TableRowMouseEvent = (
type GetRowKey = (row: ROW) => string | number;
```
-| Свойство | Тип | По умолчанию | Описание |
-| ------------------ | ----------------------------- | ----------------- | ------------------------------------------------------------ |
-| `columns?` | `TableColumn[]` | - | Колонки |
-| `rows?` | `ROW[]` | - | Строки |
-| `getRowKey?` | `GetRowKey` | `(row) => row.id` | Функция получения ключа, если ключ не найден берется `index` |
-| `onRowMouseEnter?` | `TableRowMouseEvent` | - | Событие `onMouseEnter` на строке |
-| `onRowMouseLeave?` | `TableRowMouseEvent` | - | Событие `onMouseLeave` на строке |
-| `onRowClick?` | `TableRowMouseEvent` | - | Событие `onClick` на строке |
-| `virtualScroll?` | `boolean` | - | Включение виртуальной прокрутки |
-| `stickyHeader?` | `boolean` | - | Зафиксировать шапку сверху |
-| `resizable?` | `'inside'` | `'outside'` | - | Включение возможности изменять ширину колонок |
-| `zebraStriped?` | `boolean` | - | Окрашивание строк через одну |
-| `headerZIndex?` | `number` | `1` | `zIndex` шапки |
-| `rowHoverEffect?` | `boolean` | - | Включает эффект наведения на строку |
-| `className?` | `string` | - | Дополнительный CSS-класс |
-| `ref?` | `React.Ref` | - | Ссылка на корневой DOM-элемент |
-
-## Колонка
+## Содержимое
+
+### Строки и колонки
+
+Для вывода самой простой таблицы, используйте 2 свойства:
+
+- `rows` - строки. Тип строки может быть любым массивом объекта `Record`.
+- `columns`- колонки. Представляют массив объектов, у которого `accessor` это свойство объекта ваших данных.
+
+
+
+
+
+```tsx
+import { Table, TableColumn } from '@consta/table/Table';
+
+type Row = { name: string; profession: string; status: string };
+
+const rows: Row[] = [
+ {
+ name: 'Антон',
+ profession: 'Строитель, который построил дом',
+ status: 'недоступен',
+ },
+ {
+ name: 'Василий',
+ profession: 'Отвечает на вопросы, хотя его не спросили',
+ status: 'на связи',
+ },
+];
+
+const columns: TableColumn[] = [
+ {
+ title: 'Имя',
+ accessor: 'name',
+ },
+ {
+ title: 'Профессия',
+ accessor: 'profession',
+ },
+ {
+ title: 'Статус',
+ accessor: 'status',
+ },
+];
+
+export const TableExampleSimple = () => ;
+```
+
+
+
+### Колонка
Колонки формируются с помощью CSS-свойств `display: grid` и `grid-template-columns`.
@@ -218,7 +237,9 @@ export type TableColumn = {
| `columns` | Вложенные колонки |
| [`colSpan`](#объединение-ячеек) | Функция для вывода количества занятых колонок ячейкой |
-## Ширина колонки
+## Внешний вид
+
+### Ширина колонки
Для настройки ширины колонок используйте `width`, `maxWidth`, `minWidth`, где `width` — это желаемая ширина колонки. Если все колонки будут помещаться в таблицу без скролла, она останется неизменной. Чтобы таблица не уменьшала или не увеличивала колонку вне желаемых размеров, ограничьте ее, используя `maxWidth` и `minWidth`.
@@ -318,7 +339,9 @@ const columns: TableColumn[] = [
},
];
-export const TableExampleWidth = () => ;
+export const TableExampleSeparator = () => (
+
+);
```
@@ -507,7 +530,7 @@ export const TableExampleGroupColumns = () => (
### Представление данных в ячейке
-По умолчанию компонент таблицы пытается привести данные к строке и выводит их через компонент [`DataCell`](##LIBS.LIB.STAND/lib:table/stand:components-datacell-stable). Вы можете в колонке использовать свойство `renderCell` и выводить данные, как вам потребуется.
+По умолчанию компонент таблицы пытается привести данные к строке и выводит их через компонент `DataCell`. Вы можете в колонке использовать свойство `renderCell` и выводить данные, как вам потребуется.
@@ -598,9 +621,7 @@ const columns: TableColumn[] = [
];
export const TableExampleRenderCell = () => (
-
-
-
+
);
```
@@ -608,7 +629,7 @@ export const TableExampleRenderCell = () => (
### Представление данных в ячейке в шапке
-По умолчанию компонент таблицы пытается привести данные к строке и выводит их через компонент [`HeaderDataCell`](##LIBS.LIB.STAND/lib:table/stand:components-headerdatacell-stable).
+По умолчанию компонент таблицы пытается привести данные к строке и выводит их через компонент `HeaderDataCell`.
Вы можете в колонке использовать свойство `renderHeaderCell` и выводить данные, как вам потребуется.
@@ -710,7 +731,6 @@ export const TableExampleRenderHeaderCell = () => (
import { AnimateIconSwitcherProvider } from '@consta/icons/AnimateIconSwitcherProvider';
import { IconArrowRight } from '@consta/icons/IconArrowRight';
import { withAnimateSwitcherHOC } from '@consta/icons/withAnimateSwitcherHOC';
-import { Example } from '@consta/stand';
import { Button } from '@consta/uikit/Button';
import { useMutableRef } from '@consta/uikit/useMutableRef';
import React, { useCallback, useMemo, useState } from 'react';
@@ -914,35 +934,28 @@ export const TableExampleColSpan = () => {
-## Закрепление шапки
+### Закрепление шапки
Чтобы закрепить шапку, используйте свойство `stickyHeader` и ограничьте высоту таблицы в свойстве `maxHeight`.
+
+
```tsx
```
-
-
-## Виртуальный скролл
-
-Если в таблице много данных, включите свойство `virtualScroll` и ограничьте высоту таблицы для лучшей производительности. Может принимать два значения:
-
-- `boolean` – будет включен виртуальный скролл по горизонтали и вертикали,
-- `[boolean, boolean]` – первый элемент определяет, включается ли виртуальный скролл по горизонтали, второй – по вертикали.
-
-
-
-Чтобы избежать скачков строки при включении виртуального скролла, задайте одинаковую высоту ячеек в строке.
+### Таблица в полоску
-
+Для отображения таблицы в полоску используйте `zebraStriped`.
+
+
```tsx
{
columns={columns}
stickyHeader
virtualScroll
+ zebraStriped
/>
```
-
-
-## onScrollToBottom
-
-Свойство `onScrollToBottom` позволяет отслеживать, когда пользователь достиг конца таблицы. Полезно для дозагрузки данных в момент достижения конца таблицы.
+### Управление шириной столбцов
-
+Чтобы дать пользователю возможность управлять шириной столбцов, используйте свойство `resizable`. Уменьшение и увеличение будет в пределах `minWidth` и `maxWidth` у колонки.
-```tsx
-const TableExampleOnScrollToBottom = () => {
- const [isScrollToBottom, setIsScrollToBottom] = useState(false);
+
- return (
- <>
-
- {isScrollToBottom ? 'Вы проскроллилили до конца' : 'Скролльте вниз'}
-
- setIsScrollToBottom(true)}
- />
- >
- );
-};
-```
+Чтобы запретить изменение ширины у определенной колонки, установите у `minWidth` и `maxWidth` равное значение.
-
+
-
+Закрепленные колонки (`pinned`) не могут изменять размер.
-## Таблица в полоску
+Есть 2 режима работы:
-Для отображения таблицы в полоску используйте `zebraStriped`.
+- `inside`. При `resizable = 'inside'` таблица будет стараться уместить весь контент в ширину контейнера таблицы. То есть при увеличении ширины одного столбца будет уменьшаться ширина другого, и наоборот. Рекомендуется использовать этот режим, когда столбцов мало и они все помещаются в таблицу.
-```tsx
-
-```
-
-
-
-
-
-## Выделение строк
+
-Строки могут иметь состояние:
+```tsx
+import { Table, TableColumn } from '@consta/table/Table';
+import rows from '@consta/table/Table/__mocks__/olympic-winners.json';
-- Наведение на строку – [`hover`](#наведение-на-строку),
-- Выбор строки - [`active`](#выбор-строки)
+type ROW = {
+ athlete: string;
+ age: number | null;
+ country: string;
+};
-
+const columns: TableColumn[] = [
+ {
+ title: 'Имя',
+ width: 'auto',
+ accessor: 'athlete',
+ // Запретили менять ширину у колонки
+ minWidth: 200,
+ maxWidth: 200,
+ },
+ {
+ title: 'Страна',
+ accessor: 'country',
+ width: 'auto',
+ minWidth: 140,
+ },
+ {
+ title: 'Возраст',
+ accessor: 'age',
+ width: 'auto',
+ minWidth: 100,
+ },
+];
-Чтобы не рендерить всю таблицу при каждом изменении данных в ячейке, используйте [стейт-менеджер](https://www.reatom.dev/blog/what-is-state-manager/) или [контекст](https://react.dev/reference/react/createContext).
+export const TableExampleResizableInside = () => (
+
+);
+```
-
+
-Пример с наведением и выбором строки:
+- `outside`. При `resizable = 'outside'` таблица будет расширятся при увеличении колонки. Рекомендуется использовать этот режим, когда столбцов много и они не помещаются в таблицу.
-
+
```tsx
-import { Example } from '@consta/stand';
-import { action, atom, AtomMut } from '@reatom/core';
-import { useAction, useAtom } from '@reatom/npm-react';
-import React from 'react';
-
-import { DataCell } from '@consta/table/DataCell';
-import { DataNumberingCell } from '@consta/table/DataNumberingCell';
-import { Table, TableColumn, TableRenderCell } from '@consta/table/Table';
-
-// Types
+import { Table, TableColumn } from '@consta/table/Table';
+import rows from '@consta/table/Table/__mocks__/olympic-winners.json';
type ROW = {
- id: number;
- name: string;
- profession: string;
- status: string;
- hover: AtomMut;
- active: AtomMut;
-};
-
-// Atoms
-const rowsAtom = atom[]>([
- atom({
- id: 1,
- name: 'Антон Григорьев',
- profession: 'Строитель, который построил дом',
- status: 'недоступен',
- hover: atom(false),
- active: atom(false),
- }),
- atom({
- id: 2,
- name: 'Василий Пупкин',
- profession: 'Отвечает на вопросы, хотя его не спросили',
- status: 'на связи',
- hover: atom(false),
- active: atom(false),
- }),
-]);
-
-// Actions
-
-const onRowClickAction = action<[AtomMut]>((ctx, rowAtom) => {
- const row = ctx.get(rowAtom);
- row.active(ctx, !ctx.get(row.active));
-});
-
-const onRowMouseEnterAction = action<[AtomMut]>((ctx, rowAtom) => {
- ctx.get(rowAtom).hover(ctx, true);
-});
-
-const onRowMouseLeaveAction = action<[AtomMut]>((ctx, rowAtom) => {
- ctx.get(rowAtom).hover(ctx, false);
-});
-
-const DataCellName: TableRenderCell> = (props) => {
- const [row] = useAtom(props.row);
- const [active] = useAtom(row.active);
- const [hover] = useAtom(row.hover);
-
- return (
-
- {row.id}
-
- );
-};
-
-const createDataCellOther = (
- accessor: Exclude,
-) => {
- const Component: TableRenderCell> = (props) => {
- const [row] = useAtom(props.row);
-
- return {row[accessor]} ;
- };
-
- return Component;
-};
-
-const columns: TableColumn>[] = [
- {
- title: '',
- accessor: 'id',
- width: 48,
- maxWidth: 48,
- minWidth: 48,
- renderCell: DataCellName,
- },
- {
- title: 'Имя',
- accessor: 'name',
- width: 240,
- renderCell: createDataCellOther('name'),
- },
- {
- title: 'Профессия',
- accessor: 'profession',
- width: '1fr',
- renderCell: createDataCellOther('profession'),
- },
- {
- title: 'Статус',
- accessor: 'status',
- width: '1fr',
- minWidth: 150,
- renderCell: createDataCellOther('status'),
- },
-];
-
-export const TableExampleActiveRowWithNumbering = () => {
- const onRowClick = useAction(onRowClickAction);
- const onRowMouseEnter = useAction(onRowMouseEnterAction);
- const onRowMouseLeave = useAction(onRowMouseLeaveAction);
- const [rows] = useAtom(rowsAtom);
-
- return (
-
-
-
- );
-};
-```
-
-
-
-### Наведение на строку
-
-Есть 2 способа задать состояние `hover` строкам:
-
-- Автоматически, с помощью Свойства `rowHoverEffect`. В этом случае все строки будут подсвечиваться при наведении мыши.
-
-Пример автоматического способа:
-
-
-
-
-
-```tsx
-import React from 'react';
-
-import { Table, TableColumn } from '@consta/table/Table';
-import rows from '@consta/table/Table/__mocks__/olympic-winners.json';
-
-type ROW = {
- athlete: string;
- age: number | null;
- country: string;
- year: number;
- date: string;
- sport: string;
- gold: number;
- silver: number;
- bronze: number;
- total: number;
+ athlete: string;
+ age: number | null;
+ country: string;
};
const columns: TableColumn[] = [
@@ -1198,140 +1057,244 @@ const columns: TableColumn[] = [
title: 'Имя',
width: 'auto',
accessor: 'athlete',
+ minWidth: 200,
},
{
title: 'Страна',
accessor: 'country',
width: 'auto',
+ minWidth: 140,
+ pinned: 'left',
},
{
title: 'Возраст',
accessor: 'age',
+ width: 'auto',
minWidth: 100,
},
+ {
+ title: 'Медали',
+ columns: [
+ {
+ title: 'Бронза',
+ accessor: 'bronze',
+ width: 'auto',
+ minWidth: 100,
+ },
+ {
+ title: 'Серебро',
+ accessor: 'silver',
+ width: 'auto',
+ minWidth: 100,
+ },
+ {
+ title: 'Золото',
+ accessor: 'gold',
+ width: 'auto',
+ minWidth: 100,
+ },
+ {
+ title: 'Всего',
+ accessor: 'total',
+ width: 'auto',
+ minWidth: 100,
+ },
+ ],
+ },
+ {
+ title: 'Год',
+ accessor: 'year',
+ width: 'auto',
+ minWidth: 140,
+ pinned: 'right',
+ },
];
-export const TableExampleRowHoverEffect = () => (
+export const TableExampleResizableOutside = () => (
);
```
-- Подконтрольный. Для этого нужно у любой ячейки в строке указать атрибут `data-row-hover='true'`. В этом режиме вы можете выбирать, у каких строк включить этот эффект а у каких нет.
+### headerZIndex
-Пример подконтрольного способа:
+Свойство `headerZIndex` влияет на `z-index` следующих элементов:
-
+- Шапка таблицы.
+- Ячейки в закрепленных колонках `z-index: calc(var(--table-header-z-index) - 1)`.
+- Ячейки в закрепленных колонках в шапке `z-index: calc(var(--table-header-z-index) + 1)`.
+- Ресайзеры колонок `z-index: calc(var(--table-header-z-index) + 10)`.
-
+
-```tsx
-import { action, atom } from '@reatom/core';
-import { useAction, useAtom } from '@reatom/npm-react';
-import React from 'react';
+Если вы используете всплывающие окна (`Popover`, `Tooltip`), привязывайте их `z-index` к переменной `--table-header-z-index`.
+Например: `z-index: calc(var(--table-header-z-index) + 1)`. Таким образом плавающий элемент будет находится под шапкой таблицы.
-import { DataNumberingCell } from '@consta/table/DataNumberingCell';
-import { Table, TableColumn, TableRenderCell } from '@consta/table/Table';
+
-// Types
+### Вложенные строки
-type ROW = {
- id: number;
- name: string;
- profession: string;
- status: string;
-};
+Реализовать вложенные строки возможно через `renderCell` у колонки, взяв компонент `DataCell` и назначив ему свойство `level`.
-const rows: ROW[] = [
- {
- id: 1,
- name: 'Антон Григорьев',
- profession: 'Строитель, который построил дом',
- status: 'недоступен',
- },
- {
- id: 2,
- name: 'Василий Пупкин',
- profession: 'Отвечает на вопросы, хотя его не спросили',
- status: 'на связи',
- },
-];
+
-// Atoms
+
-const hoverIdAtom = atom(undefined);
+```tsx
+import { AnimateIconSwitcherProvider } from '@consta/icons/AnimateIconSwitcherProvider';
+import { IconArrowRight } from '@consta/icons/IconArrowRight';
+import { withAnimateSwitcherHOC } from '@consta/icons/withAnimateSwitcherHOC';
+import { Button } from '@consta/uikit/Button';
+import { useMutableRef } from '@consta/uikit/useMutableRef';
+import React, { useCallback, useMemo, useState } from 'react';
-// Actions
+import { DataCell } from '@consta/table/DataCell';
+import { Table, TableColumn, TableRenderCell } from '@consta/table/Table';
+import { range } from '##/utils/array';
-const onRowMouseEnterAction = action<[ROW]>((ctx, row) =>
- hoverIdAtom(ctx, row.id),
-);
+type ROW = {
+ idx: number;
+ col1: string;
+ col2: string;
+ col3: string;
+ parent: number | undefined;
+ level: number;
+};
-const onRowMouseLeaveAction = action<[ROW]>((ctx, row) => {
- const hoverId = ctx.get(hoverIdAtom);
- if (hoverId === row.id) {
- hoverIdAtom(ctx, undefined);
- }
+const IconArrow = withAnimateSwitcherHOC({
+ startIcon: IconArrowRight,
+ startDirection: 0,
+ endDirection: 90,
});
-const DataCellName: TableRenderCell = (props) => {
- const [hover] = useAtom((ctx) => {
- const hoverId = ctx.spy(hoverIdAtom);
- return hoverId === props.row.id;
- });
+const getDataCell = (idx: number): ROW => {
+ const parent = Math.floor(idx / 10) * 10;
- return (
- {props.row.id}
- );
+ return {
+ idx,
+ col1: `Данные 1 - ${idx}`,
+ col2: `Данные 2 - ${idx}`,
+ col3: `Данные 3 - ${idx}`,
+ parent: parent === idx ? undefined : parent,
+ level: parent === idx ? 0 : 1,
+ };
};
-const columns: TableColumn[] = [
- {
- title: '',
- accessor: 'id',
- width: 48,
- maxWidth: 48,
- minWidth: 48,
- renderCell: DataCellName,
- },
- {
- title: 'Имя',
- accessor: 'name',
- width: 240,
- },
- {
- title: 'Профессия',
- accessor: 'profession',
- width: '1fr',
- },
- {
- title: 'Статус',
- accessor: 'status',
- width: '1fr',
- minWidth: 150,
- },
-];
+const data = range(100000).map(getDataCell);
-export const TableExampleHoveredControlled = () => {
- const onRowMouseEnter = useAction(onRowMouseEnterAction);
- const onRowMouseLeave = useAction(onRowMouseLeaveAction);
+const DataCellCol1 = (props: {
+ row: {
+ col1: string;
+ parent: number | undefined;
+ idx: number;
+ level: number;
+ };
+ opened: boolean | undefined;
+ toggle: (idx: number) => void;
+}) => {
+ const {
+ row: { col1, parent, idx, level },
+ opened,
+ toggle,
+ } = props;
+
+ return (
+
+ toggle(idx)}
+ />
+ ) : undefined
+ }
+ >
+ {col1}
+
+
+ );
+};
+
+export const TableExampleNestedRows = () => {
+ const [openedList, setOpenedList] = useState([]);
+
+ const openedListRef = useMutableRef(openedList);
+
+ const rows = useMemo(() => {
+ return data.filter(
+ (dataItem) =>
+ dataItem.parent === undefined ||
+ openedList.findIndex(
+ (openedListItem) => openedListItem === dataItem.parent,
+ ) !== -1,
+ );
+ }, [openedList]);
+
+ const toggle = useCallback((idx: number) => {
+ setOpenedList((state) => {
+ const open = state.findIndex((value) => value === idx) !== -1;
+ if (open) {
+ return state.filter((value) => value !== idx);
+ }
+ return [...state, idx];
+ });
+ }, []);
+
+ const renderCellCol1: TableRenderCell = useCallback(
+ (props) => (
+ item === props.row.idx) !==
+ -1
+ }
+ />
+ ),
+ [],
+ );
+
+ const columns: TableColumn[] = useMemo(
+ () => [
+ {
+ title: 'Колонка - 1',
+ accessor: 'col1',
+ renderCell: renderCellCol1,
+ },
+ {
+ title: 'Колонка - 2',
+ accessor: 'col2',
+ },
+ {
+ title: 'Колонка - 3',
+ accessor: 'col3',
+ },
+ ],
+ [],
+ );
return (
row.idx}
/>
);
};
@@ -1339,438 +1302,482 @@ export const TableExampleHoveredControlled = () => {
-### Выбор строки
-
-Для выбора строки укажите в любой ячейке строки атрибут `data-row-active='true'`.
+### Рендер строки
-Пример с использованием стейт-менеджера:
+Чтобы вывести всю строку с собственным представлением, укажите у первого столбца `colSpan = () => 'end'`, и `renderCell`.
-
+
```tsx
-import { Example } from '@consta/stand';
-import { Checkbox } from '@consta/uikit/Checkbox';
-import { atom, AtomMut } from '@reatom/core';
-import { useAction, useAtom } from '@reatom/npm-react';
-import React from 'react';
+import { AnimateIconSwitcherProvider } from '@consta/icons/AnimateIconSwitcherProvider';
+import { IconArrowRight } from '@consta/icons/IconArrowRight';
+import { withAnimateSwitcherHOC } from '@consta/icons/withAnimateSwitcherHOC';
+import { Button } from '@consta/uikit/Button';
+import { Grid, GridItem } from '@consta/uikit/Grid';
+import { cnMixSpace } from '@consta/uikit/MixSpace';
+import { Text } from '@consta/uikit/Text';
+import { useMutableRef } from '@consta/uikit/useMutableRef';
+import React, { useCallback, useMemo, useState } from 'react';
import { DataCell } from '@consta/table/DataCell';
import { Table, TableColumn, TableRenderCell } from '@consta/table/Table';
-type ROW = {
- id: number;
- name: string;
- profession: string;
- status: string;
-};
-
-const activeIdsAtom = atom>>({});
+const IconArrow = withAnimateSwitcherHOC({
+ startIcon: IconArrowRight,
+ startDirection: 0,
+ endDirection: 90,
+});
-const DataCellName: TableRenderCell = (props) => {
- const [active] = useAtom((ctx) => {
- const activeAtom = ctx.spy(activeIdsAtom)[props.row.id];
- return activeAtom ? ctx.spy(activeAtom) : false;
- });
+type Option = { label: string; value: string };
+type Options = { label: string; value: Option[] };
- const onChange = useAction((ctx) => {
- const activeIds = ctx.get(activeIdsAtom);
- const activeAtom = ctx.get(activeIdsAtom)[props.row.id];
+type Item = {
+ id: number;
+ label: string;
+ formula: string;
+ type: string;
+};
- if (activeAtom) {
- activeAtom(ctx, !ctx.get(activeAtom));
- } else {
- activeIdsAtom(ctx, { ...activeIds, [props.row.id]: atom(true) });
- }
- });
+type ItemInfo = {
+ isInfo: number;
+ options: Options[];
+};
- return (
- }
- >
- {props.row.name}
-
- );
+type Row = {
+ id?: number;
+ label?: string;
+ formula?: string;
+ type?: string;
+ status?: 'work' | 'problem' | 'wait' | 'success';
+ isInfo?: number;
+ options?: Options[];
};
-const columns: TableColumn[] = [
- {
- title: 'Имя',
- accessor: 'name',
- width: 240,
- renderCell: DataCellName,
- },
- {
- title: 'Профессия',
- accessor: 'profession',
- width: '1fr',
- },
+const data: Row[] = [
{
- title: 'Статус',
- accessor: 'status',
- width: '1fr',
- minWidth: 150,
+ id: 1,
+ label: 'Запись инклинометрии',
+ formula: 'Время замера * Количество',
+ type: 'Кондуктор',
},
-];
-
-const rows: ROW[] = [
{
- id: 1,
- name: 'Антон Григорьев',
- profession: 'Строитель, который построил дом',
- status: 'недоступен',
+ isInfo: 1,
+ options: [
+ {
+ label: 'Порты',
+ value: [
+ { label: 'Входящий', value: 'A2-папа' },
+ { label: 'Исходящий', value: 'A2-папа' },
+ ],
+ },
+ {
+ label: 'Размеры',
+ value: [
+ { label: 'ширина(мм)', value: '60' },
+ { label: 'длинна(мм)', value: '80' },
+ ],
+ },
+ ],
},
{
id: 2,
- name: 'Василий Пупкин',
- profession: 'Отвечает на вопросы, хотя его не спросили',
- status: 'на связи',
+ label: 'Шаблонирование при бурении',
+ formula: 'Интервал/Скорость СПО',
+ type: 'Труба бурильная',
},
-];
-
-export const TableExampleActiveRow = () => (
-
-
-
-);
-```
-
-
-
-## Управление шириной столбцов
-
-Чтобы дать пользователю возможность управлять шириной столбцов, используйте свойство `resizable`. Уменьшение и увеличение будет в пределах `minWidth` и `maxWidth` у колонки.
-
-
+ {
+ isInfo: 2,
+ options: [
+ {
+ label: 'Диаметры',
+ value: [
+ { label: 'Внешний', value: '14.7' },
+ { label: 'Внутренний', value: '12.7' },
+ ],
+ },
+ {
+ label: 'Нагрузка и моменты',
+ value: [
+ { label: 'Растягивающая нагрузка тела трубы, кН', value: '30' },
+ { label: 'Допустимый момент на кручение ЗС,кН*м', value: '10' },
+ {
+ label: 'Допустимый момент на кручение тела трубы, кН*м',
+ value: '10',
+ },
+ {
+ label: 'Рекомендуемый момент свинчивания, кН*м',
+ value: '10',
+ },
+ ],
+ },
+ ],
+ },
+];
-Чтобы запретить изменение ширины у определенной колонки, установите у `minWidth` и `maxWidth` равное значение.
+const isItemInfo = (arg: Row): arg is ItemInfo =>
+ Object.prototype.hasOwnProperty.call(arg, 'isInfo');
-
+const isItem = (arg: Row): arg is Item =>
+ Object.prototype.hasOwnProperty.call(arg, 'id');
-Закрепленные колонки (`pinned`) не могут изменять размер.
+const LabelCell = (props: {
+ id: number;
+ label: string;
+ opened: boolean | undefined;
+ toggle: (idx: number) => void;
+}) => {
+ const { id, opened, toggle, label } = props;
-Есть 2 режима работы:
+ return (
+
+ toggle(id)}
+ />
+ }
+ >
+ {label}
+
+
+ );
+};
-- `inside`. При `resizable = 'inside'` таблица будет стараться уместить весь контент в ширину контейнера таблицы. То есть при увеличении ширины одного столбца будет уменьшаться ширина другого, и наоборот. Рекомендуется использовать этот режим, когда столбцов мало и они все помещаются в таблицу.
+const InfoCell = (props: { options: Options[] }) => {
+ const { options } = props;
+ return (
+
+ {options.map((opt) => {
+ return (
+ <>
+
+ {opt.label}
+
+ {opt.value.map((val) => (
+
+ {val.value}
+
+ {val.label}
+
+
+ ))}
+ >
+ );
+ })}
+
+ );
+};
-
+export const TableExampleRenderRow = () => {
+ const [openedList, setOpenedList] = useState([]);
-
+ const openedListRef = useMutableRef(openedList);
-```tsx
-import { Example } from '@consta/stand';
-import React from 'react';
+ const rows = useMemo(() => {
+ return data.filter(
+ (dataItem) =>
+ Object.prototype.hasOwnProperty.call(dataItem, 'id') ||
+ (isItemInfo(dataItem) &&
+ openedList.findIndex(
+ (openedListItem) => openedListItem === dataItem.isInfo,
+ ) !== -1),
+ );
+ }, [openedList]);
-import { Table, TableColumn } from '@consta/table/Table';
-import rows from '@consta/table/Table/__mocks__/olympic-winners.json';
+ const toggle = useCallback((idx: number) => {
+ setOpenedList((state) => {
+ const open = state.findIndex((value) => value === idx) !== -1;
+ if (open) {
+ return state.filter((value) => value !== idx);
+ }
+ return [...state, idx];
+ });
+ }, []);
-type ROW = {
- athlete: string;
- age: number | null;
- country: string;
-};
+ const renderLabelCell: TableRenderCell = useCallback(({ row }) => {
+ if (isItem(row)) {
+ return (
+ id === row.id) !== -1}
+ toggle={toggle}
+ />
+ );
+ }
+ if (isItemInfo(row)) {
+ return ;
+ }
+ return null;
+ }, []);
-const columns: TableColumn[] = [
- {
- title: 'Имя',
- width: 'auto',
- accessor: 'athlete',
- // Запретили менять ширину у колонки
- minWidth: 200,
- maxWidth: 200,
- },
- {
- title: 'Страна',
- accessor: 'country',
- width: 'auto',
- minWidth: 140,
- },
- {
- title: 'Возраст',
- accessor: 'age',
- width: 'auto',
- minWidth: 100,
- },
-];
+ const columns: TableColumn[] = useMemo(
+ () => [
+ {
+ title: 'Название',
+ accessor: 'label',
+ renderCell: renderLabelCell,
+ colSpan: ({ row }) => (isItemInfo(row) ? 'end' : 1),
+ minWidth: 300,
+ },
+ {
+ title: 'Формула',
+ accessor: 'formula',
+ minWidth: 200,
+ },
+ {
+ title: 'Тип',
+ accessor: 'type',
+ minWidth: 180,
+ },
+ ],
+ [],
+ );
-export const TableExampleResizable = () => (
-
-);
+ return (
+ {
+ if (isItemInfo(row)) {
+ return `${row.isInfo}-info`;
+ }
+ if (isItem(row)) {
+ return `${row.id}`;
+ }
+ return '';
+ }}
+ />
+ );
+};
```
-- `outside`. При `resizable = 'outside'` таблица будет расширятся при увеличении колонки. Рекомендуется использовать этот режим, когда столбцов много и они не помещаются в таблицу.
+## Состояние загрузки
+
+Загрузку можно отобразить двумя видами с помощью `Loader` или `Skeleton`.
+
+**Пример загрузки данных всей таблицы с помощью `Loader`:**
-
+
```tsx
-import { Example } from '@consta/stand';
+import { Loader } from '@consta/uikit/Loader';
import React from 'react';
-import { Table, TableColumn } from '@consta/table/Table';
-import rows from '@consta/table/Table/__mocks__/olympic-winners.json';
+import { DataCell } from '@consta/table/DataCell';
+import { Table, TableColumn, TableRenderCell } from '@consta/table/Table';
-type ROW = {
- athlete: string;
- age: number | null;
- country: string;
+type Item = {
+ id: number;
+ label: string;
};
-const columns: TableColumn[] = [
+type Loader = {
+ isLoader: true;
+};
+
+type Row = Item | Loader;
+
+const data: Row[] = [{ isLoader: true }];
+
+const isLoader = (arg: Row): arg is Loader =>
+ Object.prototype.hasOwnProperty.call(arg, 'isLoader');
+
+const renderIdCell: TableRenderCell = ({ row }) => {
+ if (isLoader(row)) {
+ return (
+
+
+
+ );
+ }
+ return {row.id} ;
+};
+
+const columns: TableColumn[] = [
{
- title: 'Имя',
- width: 'auto',
- accessor: 'athlete',
- minWidth: 200,
- },
- {
- title: 'Страна',
- accessor: 'country',
- width: 'auto',
- minWidth: 140,
- pinned: 'left',
- },
- {
- title: 'Возраст',
- accessor: 'age',
- width: 'auto',
- minWidth: 100,
- },
- {
- title: 'Медали',
- columns: [
- {
- title: 'Бронза',
- accessor: 'bronze',
- width: 'auto',
- minWidth: 100,
- },
- {
- title: 'Серебро',
- accessor: 'silver',
- width: 'auto',
- minWidth: 100,
- },
- {
- title: 'Золото',
- accessor: 'gold',
- width: 'auto',
- minWidth: 100,
- },
- {
- title: 'Всего',
- accessor: 'total',
- width: 'auto',
- minWidth: 100,
- },
- ],
+ title: 'Номер',
+ accessor: 'id',
+ renderCell: renderIdCell,
+ colSpan: ({ row }) => (isLoader(row) ? 'end' : 1),
+ minWidth: 300,
},
{
- title: 'Год',
- accessor: 'year',
- width: 'auto',
- minWidth: 140,
- pinned: 'right',
+ title: 'Наименование',
+ accessor: 'label',
+ minWidth: 300,
},
];
-export const TableExampleResizableOutside = () => (
-
-);
+export const TableExampleLoadingDataWithLoader = () => {
+ return (
+
+ );
+};
```
-## headerZIndex
-
-Свойство `headerZIndex` влияет на [`z-index`](https://developer.mozilla.org/ru/docs/Web/CSS/z-index) следующих элементов:
-
-- Шапка таблицы.
-- Ячейки в закрепленных колонках `z-index: calc(var(--table-header-z-index) - 1)`.
-- Ячейки в закрепленных колонках в шапке `z-index: calc(var(--table-header-z-index) + 1)`.
-- Ресайзеры колонок `z-index: calc(var(--table-header-z-index) + 10)`.
-
-
-
-Если вы используете всплывающие окна (`Popover`, `Tooltip`), привязывайте их `z-index` к переменной `--table-header-z-index`.
-Например: `z-index: calc(var(--table-header-z-index) + 1)`. Таким образом плавающий элемент будет находится под шапкой таблицы.
-
-
-
-## Вложенные строки
-
-Реализовать вложенные строки возможно через `renderCell` у колонки, взяв компонент [`DataCell`](##LIBS.LIB.STAND/lib:table/stand:components-datacell-stable) и назначив ему свойство `level`.
+**Пример загрузки данных всей таблицы с помощью `Skeleton`:**
-
+
```tsx
-import { AnimateIconSwitcherProvider } from '@consta/icons/AnimateIconSwitcherProvider';
-import { IconArrowRight } from '@consta/icons/IconArrowRight';
-import { withAnimateSwitcherHOC } from '@consta/icons/withAnimateSwitcherHOC';
-import { Button } from '@consta/uikit/Button';
-import { useMutableRef } from '@consta/uikit/useMutableRef';
-import React, { useCallback, useMemo, useState } from 'react';
+import { Loader } from '@consta/uikit/Loader';
+import { cnMixSpace } from '@consta/uikit/MixSpace';
+import { SkeletonBrick } from '@consta/uikit/Skeleton';
+import React from 'react';
import { DataCell } from '@consta/table/DataCell';
import { Table, TableColumn, TableRenderCell } from '@consta/table/Table';
-import { range } from '##/utils/array';
-type ROW = {
- idx: number;
- col1: string;
- col2: string;
- col3: string;
- parent: number | undefined;
- level: number;
+type Item = {
+ id: number;
+ label: string;
};
-const IconArrow = withAnimateSwitcherHOC({
- startIcon: IconArrowRight,
- startDirection: 0,
- endDirection: 90,
-});
+type Loader = {
+ isLoader: true;
+};
-const getDataCell = (idx: number): ROW => {
- const parent = Math.floor(idx / 10) * 10;
+type Row = Item | Loader;
- return {
- idx,
- col1: `Данные 1 - ${idx}`,
- col2: `Данные 2 - ${idx}`,
- col3: `Данные 3 - ${idx}`,
- parent: parent === idx ? undefined : parent,
- level: parent === idx ? 0 : 1,
- };
-};
+const data: Row[] = [
+ { isLoader: true },
+ { isLoader: true },
+ { isLoader: true },
+];
-const data = range(100000).map(getDataCell);
+const isLoader = (arg: Row): arg is Loader =>
+ Object.prototype.hasOwnProperty.call(arg, 'isLoader');
-const DataCellCol1 = (props: {
- row: {
- col1: string;
- parent: number | undefined;
- idx: number;
- level: number;
- };
- opened: boolean | undefined;
- toggle: (idx: number) => void;
-}) => {
- const {
- row: { col1, parent, idx, level },
- opened,
- toggle,
- } = props;
+const renderIdCell: TableRenderCell = ({ row }) => {
+ if (isLoader(row)) {
+ return (
+
+
+
+ );
+ }
+ return {row.id} ;
+};
+
+const columns: TableColumn[] = [
+ {
+ title: 'Номер',
+ accessor: 'id',
+ renderCell: renderIdCell,
+ colSpan: ({ row }) => (isLoader(row) ? 'end' : 1),
+ minWidth: 300,
+ },
+ {
+ title: 'Наименование',
+ accessor: 'label',
+ minWidth: 300,
+ },
+];
+export const TableExampleLoadingDataWithSkeleton = () => {
return (
-
- toggle(idx)}
- />
- ) : undefined
- }
- >
- {col1}
-
-
+
);
};
+```
-export const TableExampleNestedRows = () => {
- const [openedList, setOpenedList] = useState([]);
+
- const openedListRef = useMutableRef(openedList);
+**Пример подгрузки строки с помощью `Loader`:**
- const rows = useMemo(() => {
- return data.filter(
- (dataItem) =>
- dataItem.parent === undefined ||
- openedList.findIndex(
- (openedListItem) => openedListItem === dataItem.parent,
- ) !== -1,
- );
- }, [openedList]);
+
- const toggle = useCallback((idx: number) => {
- setOpenedList((state) => {
- const open = state.findIndex((value) => value === idx) !== -1;
- if (open) {
- return state.filter((value) => value !== idx);
- }
- return [...state, idx];
- });
- }, []);
+
- const renderCellCol1: TableRenderCell = useCallback(
- (props) => (
- item === props.row.idx) !==
- -1
- }
- />
- ),
- [],
- );
+```tsx
+import { Loader } from '@consta/uikit/Loader';
+import React from 'react';
- const columns: TableColumn[] = useMemo(
- () => [
- {
- title: 'Колонка - 1',
- accessor: 'col1',
- renderCell: renderCellCol1,
- },
- {
- title: 'Колонка - 2',
- accessor: 'col2',
- },
- {
- title: 'Колонка - 3',
- accessor: 'col3',
- },
- ],
- [],
- );
+import { DataCell } from '@consta/table/DataCell';
+import { Table, TableColumn, TableRenderCell } from '@consta/table/Table';
+
+type Item = {
+ id: number;
+ label: string;
+};
+
+type Loader = {
+ isLoader: true;
+};
+
+type Row = Item | Loader;
+
+const data: Row[] = [
+ { id: 1, label: 'Item 1' },
+ { id: 2, label: 'Item 2' },
+ { id: 3, label: 'Item 3' },
+ { isLoader: true },
+];
+const isLoader = (arg: Row): arg is Loader =>
+ Object.prototype.hasOwnProperty.call(arg, 'isLoader');
+
+const renderIdCell: TableRenderCell = ({ row }) => {
+ if (isLoader(row)) {
+ return (
+
+
+
+ );
+ }
+ return {row.id} ;
+};
+
+const columns: TableColumn[] = [
+ {
+ title: 'Номер',
+ accessor: 'id',
+ renderCell: renderIdCell,
+ colSpan: ({ row }) => (isLoader(row) ? 'end' : 1),
+ minWidth: 300,
+ },
+ {
+ title: 'Наименование',
+ accessor: 'label',
+ minWidth: 300,
+ },
+];
+
+export const TableExampleLoadingRowWithLoader = () => {
return (
row.idx}
+ getRowKey={(row) => (isLoader(row) ? `loader` : `${row.id}`)}
/>
);
};
@@ -1778,260 +1785,916 @@ export const TableExampleNestedRows = () => {
-## Рендер строки
-
-Чтобы вывести всю строку с собственным представлением, укажите у первого столбца `colSpan = () => 'end'`, и `renderCell`.
+**Пример подгрузки строки с помощью `Skeleton`:**
-
+
```tsx
-import { AnimateIconSwitcherProvider } from '@consta/icons/AnimateIconSwitcherProvider';
-import { IconArrowRight } from '@consta/icons/IconArrowRight';
-import { withAnimateSwitcherHOC } from '@consta/icons/withAnimateSwitcherHOC';
-import { Example } from '@consta/stand';
-import { Button } from '@consta/uikit/Button';
-import { Grid, GridItem } from '@consta/uikit/Grid';
+import { Loader } from '@consta/uikit/Loader';
import { cnMixSpace } from '@consta/uikit/MixSpace';
-import { Text } from '@consta/uikit/Text';
-import { useMutableRef } from '@consta/uikit/useMutableRef';
-import React, { useCallback, useMemo, useState } from 'react';
+import { SkeletonBrick } from '@consta/uikit/Skeleton';
+import React from 'react';
import { DataCell } from '@consta/table/DataCell';
import { Table, TableColumn, TableRenderCell } from '@consta/table/Table';
-const IconArrow = withAnimateSwitcherHOC({
- startIcon: IconArrowRight,
- startDirection: 0,
- endDirection: 90,
-});
-
-type Option = { label: string; value: string };
-type Options = { label: string; value: Option[] };
-
type Item = {
id: number;
label: string;
- formula: string;
- type: string;
};
-type ItemInfo = {
- isInfo: number;
- options: Options[];
+type Loader = {
+ isLoader: true;
};
-type Row = {
- id?: number;
- label?: string;
- formula?: string;
- type?: string;
- status?: 'work' | 'problem' | 'wait' | 'success';
- isInfo?: number;
- options?: Options[];
-};
+type Row = Item | Loader;
const data: Row[] = [
+ { id: 1, label: 'Item 1' },
+ { id: 2, label: 'Item 2' },
+ { id: 3, label: 'Item 3' },
+ { isLoader: true },
+];
+
+const isLoader = (arg: Row): arg is Loader =>
+ Object.prototype.hasOwnProperty.call(arg, 'isLoader');
+
+const renderIdCell: TableRenderCell = ({ row }) => {
+ if (isLoader(row)) {
+ return (
+
+
+
+ );
+ }
+ return {row.id} ;
+};
+
+const columns: TableColumn[] = [
{
- id: 1,
- label: 'Запись инклинометрии',
- formula: 'Время замера * Количество',
- type: 'Кондуктор',
- },
- {
- isInfo: 1,
- options: [
- {
- label: 'Порты',
- value: [
- { label: 'Входящий', value: 'A2-папа' },
- { label: 'Исходящий', value: 'A2-папа' },
- ],
- },
- {
- label: 'Размеры',
- value: [
- { label: 'ширина(мм)', value: '60' },
- { label: 'длинна(мм)', value: '80' },
- ],
- },
- ],
- },
- {
- id: 2,
- label: 'Шаблонирование при бурении',
- formula: 'Интервал/Скорость СПО',
- type: 'Труба бурильная',
+ title: 'Номер',
+ accessor: 'id',
+ renderCell: renderIdCell,
+ colSpan: ({ row }) => (isLoader(row) ? 'end' : 1),
+ minWidth: 300,
},
{
- isInfo: 2,
- options: [
- {
- label: 'Диаметры',
- value: [
- { label: 'Внешний', value: '14.7' },
- { label: 'Внутренний', value: '12.7' },
- ],
- },
- {
- label: 'Нагрузка и моменты',
- value: [
- { label: 'Растягивающая нагрузка тела трубы, кН', value: '30' },
- { label: 'Допустимый момент на кручение ЗС,кН*м', value: '10' },
- {
- label: 'Допустимый момент на кручение тела трубы, кН*м',
- value: '10',
- },
- {
- label: 'Рекомендуемый момент свинчивания, кН*м',
- value: '10',
- },
- ],
- },
- ],
+ title: 'Наименование',
+ accessor: 'label',
+ minWidth: 300,
},
];
-const isItemInfo = (arg: Row): arg is ItemInfo =>
- Object.prototype.hasOwnProperty.call(arg, 'isInfo');
+export const TableExampleLoadingRowWithSkeleton = () => {
+ return (
+ (isLoader(row) ? `loader` : `${row.id}`)}
+ />
+ );
+};
+```
-const isItem = (arg: Row): arg is Item =>
- Object.prototype.hasOwnProperty.call(arg, 'id');
+
-const LabelCell = (props: {
+В состоянии загрузки вы можете показывать не только единую строку-заглушку на всю ширину таблицы. При необходимости `Skeleton` можно отрисовывать отдельно в каждой ячейке.
+
+**Пример подгрузки вложенной строки с помощью `Loader`:**
+
+
+
+
+
+```tsx
+import { IconArrowRight } from '@consta/icons/IconArrowRight';
+
+import { Button } from '@consta/uikit/Button';
+import React from 'react';
+
+import { DataCell } from '@consta/table/DataCell';
+import { Table, TableColumn, TableRenderCell } from '@consta/table/Table';
+
+type Item = {
id: number;
label: string;
- opened: boolean | undefined;
- toggle: (idx: number) => void;
-}) => {
- const { id, opened, toggle, label } = props;
-
- return (
-
- toggle(id)}
- />
- }
- >
- {label}
-
-
- );
+ loading?: boolean;
};
-const InfoCell = (props: { options: Options[] }) => {
- const { options } = props;
- return (
-
- {options.map((opt) => {
- return (
- <>
-
- {opt.label}
-
- {opt.value.map((val) => (
-
- {val.value}
-
- {val.label}
-
-
- ))}
- >
- );
- })}
-
+type Row = Item;
+
+const data: Row[] = [
+ { id: 1, label: 'Item 1' },
+ { id: 2, label: 'Item 2' },
+ { id: 3, label: 'Item 3' },
+ { id: 4, label: 'Item 4', loading: true },
+ { id: 5, label: 'Item 5' },
+ { id: 6, label: 'Item 6' },
+ { id: 7, label: 'Item 7' },
+];
+
+const renderIdCell: TableRenderCell = ({ row }) => (
+
+ }
+ >
+ {row.id}
+
+);
+
+const columns: TableColumn[] = [
+ {
+ title: 'Номер',
+ accessor: 'id',
+ renderCell: renderIdCell,
+ minWidth: 300,
+ },
+ {
+ title: 'Наименование',
+ accessor: 'label',
+ minWidth: 300,
+ },
+];
+
+export const TableExampleLoadingNestedRowWithLoader = () => {
+ return (
+ row.id}
+ />
+ );
+};
+```
+
+
+
+## Индикатор ячейки и подсказки
+
+Для отображения индикатора используйте свойство `indicator` у компонента `DataCell`. Для вывода подсказок используйте компонент `Popover`. В примере ниже реализована логика показа подсказок по наведению курсора.
+
+
+
+
+
+```tsx
+import { Informer } from '@consta/uikit/Informer';
+import {
+ animateTimeout,
+ cnMixPopoverAnimate,
+} from '@consta/uikit/MixPopoverAnimate';
+import { Popover } from '@consta/uikit/Popover';
+import { useDebounce } from '@consta/uikit/useDebounce';
+import { useFlag } from '@consta/uikit/useFlag';
+import React, { useCallback, useRef } from 'react';
+import { Transition } from 'react-transition-group';
+
+import { DataCell } from '@consta/table/DataCell';
+import { Table, TableColumn } from '@consta/table/Table';
+
+type Cell = {
+ data: T;
+ status?: 'alert' | 'warning';
+ statusMessage?: string;
+};
+
+type Row = {
+ name: Cell;
+ profession: Cell;
+ status: Cell;
+};
+
+const rows: Row[] = [
+ {
+ name: {
+ data: 'Антон',
+ },
+ profession: {
+ data: 'РОЮЛАВТМЯО',
+ status: 'alert',
+ statusMessage: 'Неизвестное название диапазона: РОЮЛАВТМЯО.',
+ },
+ status: {
+ data: 'недоступен',
+ },
+ },
+ // ...
+];
+
+const titleMap: Record<'alert' | 'warning', string> = {
+ alert: 'Ошибка',
+ warning: 'Предупреждение',
+};
+
+const DataCellWithInformer = ({
+ data,
+ tableRef,
+ statusMessage,
+ status,
+}: {
+ data: string;
+ status?: 'alert' | 'warning';
+ statusMessage?: string;
+ tableRef: React.RefObject;
+}) => {
+ const cellRef = useRef(null);
+ const popoverContentRef = useRef(null);
+ const [informerVisible, setInformerVisible] = useFlag();
+
+ const hoverStateRef = useRef<{
+ popover: boolean;
+ anchor: boolean;
+ }>({
+ popover: false,
+ anchor: false,
+ });
+
+ const mouseEnterController = useDebounce(
+ useCallback(() => {
+ (hoverStateRef.current.anchor || hoverStateRef.current.popover) &&
+ setInformerVisible.on();
+ }, []),
+ 200,
+ );
+
+ const mouseLeaveController = useDebounce(
+ useCallback(() => {
+ !hoverStateRef.current.anchor &&
+ !hoverStateRef.current.popover &&
+ setInformerVisible.off();
+ }, []),
+ 200,
+ );
+
+ const anchorOnMouseEnter: React.MouseEventHandler =
+ useCallback((e) => {
+ hoverStateRef.current.anchor = true;
+ mouseEnterController();
+ }, []);
+
+ const anchorOnMouseLeave: React.MouseEventHandler =
+ useCallback((e) => {
+ hoverStateRef.current.anchor = false;
+ mouseLeaveController();
+ }, []);
+
+ const popoverOnMouseEnter: React.MouseEventHandler =
+ useCallback((e) => {
+ hoverStateRef.current.popover = true;
+ mouseEnterController();
+ }, []);
+
+ const popoverOnMouseLeave: React.MouseEventHandler =
+ useCallback((e) => {
+ hoverStateRef.current.popover = false;
+ mouseLeaveController();
+ }, []);
+
+ return (
+ <>
+
+ {status === 'alert' ? '#ТИПДАННЫХ?' : data}
+
+ {status && (
+
+ {(animate) => (
+
+
+
+ )}
+
+ )}
+ >
+ );
+};
+
+const columns: TableColumn[] = [
+ {
+ title: 'Имя',
+ accessor: 'name',
+ renderCell: ({ row, tableRef }) => (
+
+ ),
+ },
+ {
+ title: 'Профессия',
+ accessor: 'profession',
+ renderCell: ({ row, tableRef }) => (
+
+ ),
+ },
+ {
+ title: 'Статус',
+ accessor: 'status',
+ renderCell: ({ row, tableRef }) => (
+
+ ),
+ },
+];
+
+export const TableExampleWithIndicator = () => {
+ return (
+
+ );
+};
+```
+
+
+
+### Адаптивная ширина колонок
+
+Вы можете менять ширину колонок в зависимости от ширины самой таблицы.
+
+
+
+
+
+```tsx
+import { Button } from '@consta/uikit/Button';
+import { getLastPoint, useBreakpoints } from '@consta/uikit/useBreakpoints';
+import React, { useCallback, useMemo, useRef, useState } from 'react';
+
+import { Table, TableColumn } from '@consta/table/Table';
+
+type Row = { name: string; profession: string; status: string };
+
+const rows: Row[] = [
+ {
+ name: 'Антон',
+ profession: 'Строитель, который построил дом',
+ status: 'недоступен',
+ },
+ {
+ name: 'Василий',
+ profession: 'Отвечает на вопросы, хотя его не спросили',
+ status: 'на связи',
+ },
+];
+
+const columnsWidthMap: Record<
+ 's' | 'm' | 'l',
+ Record<'name' | 'profession' | 'status', TableColumn>
+> = {
+ s: {
+ name: {
+ width: 100,
+ minWidth: 100,
+ maxWidth: 100,
+ },
+ profession: {
+ width: '1fr',
+ minWidth: 150,
+ },
+ status: {
+ width: 120,
+ minWidth: 120,
+ maxWidth: 120,
+ },
+ },
+ m: {
+ name: {
+ width: 120,
+ minWidth: 120,
+ maxWidth: 120,
+ },
+ profession: {
+ width: '1fr',
+ minWidth: 250,
+ },
+ status: {
+ width: 120,
+ minWidth: 120,
+ maxWidth: 120,
+ },
+ },
+ l: {
+ name: {
+ width: 150,
+ minWidth: 150,
+ maxWidth: 150,
+ },
+ profession: {
+ width: '1fr',
+ },
+ status: {
+ width: 150,
+ minWidth: 150,
+ maxWidth: 150,
+ },
+ },
+};
+
+const breakpointsMap = { s: 300, m: 500, l: 760 };
+const breakpointsSequence: (keyof typeof breakpointsMap)[] = ['s', 'm', 'l'];
+
+export const TableExampleAdaptiveColumns = () => {
+ const [widthSequence, setWidthSequence] = useState(0);
+
+ const handleWidthChange = useCallback(() => {
+ setWidthSequence((state) => {
+ const newState = state + 1;
+ return newState >= breakpointsSequence.length ? 0 : newState;
+ });
+ }, []);
+
+ const tableRef = useRef(null);
+
+ const point =
+ getLastPoint(
+ useBreakpoints({
+ ref: tableRef,
+ map: breakpointsMap,
+ isActive: true,
+ }),
+ ) || 's';
+
+ const columns: TableColumn[] = useMemo(
+ () => [
+ {
+ title: 'Имя',
+ accessor: 'name',
+ ...columnsWidthMap[point].name,
+ },
+ {
+ title: 'Профессия',
+ accessor: 'profession',
+ ...columnsWidthMap[point].profession,
+ },
+ {
+ title: 'Статус',
+ accessor: 'status',
+ ...columnsWidthMap[point].status,
+ },
+ ],
+ [point],
+ );
+
+ return (
+ <>
+
+
+ >
+ );
+};
+```
+
+
+
+## Поведение
+
+### Виртуальный скролл
+
+Если в таблице много данных, включите свойство `virtualScroll` и ограничьте высоту таблицы для лучшей производительности. Может принимать два значения:
+
+- `boolean` – будет включен виртуальный скролл по горизонтали и вертикали,
+- `[boolean, boolean]` – первый элемент определяет, включается ли виртуальный скролл по горизонтали, второй – по вертикали.
+
+
+
+Чтобы избежать скачков строки при включении виртуального скролла, задайте одинаковую высоту ячеек в строке.
+
+
+
+
+
+
+
+```tsx
+
+```
+
+
+
+### onScrollToBottom
+
+Свойство `onScrollToBottom` позволяет отслеживать, когда пользователь достиг конца таблицы. Полезно для дозагрузки данных в момент достижения конца таблицы.
+
+
+
+
+
+```tsx
+const TableExampleOnScrollToBottom = () => {
+ const [isScrollToBottom, setIsScrollToBottom] = useState(false);
+
+ return (
+ <>
+
+ {isScrollToBottom ? 'Вы проскроллили до конца' : 'Скролльте вниз'}
+
+ setIsScrollToBottom(true)}
+ />
+ >
+ );
+};
+```
+
+
+
+### Выделение строк
+
+Строки могут иметь состояние:
+
+- Наведение на строку – `hover`,
+- Выбор строки - `active`
+
+
+
+Чтобы не рендерить всю таблицу при каждом изменении данных в ячейке, используйте стейт-менеджер или контекст.
+
+
+
+Пример с наведением и выбором строки:
+
+
+
+
+
+```tsx
+import { action, atom, AtomMut } from '@reatom/core';
+import { useAction, useAtom } from '@reatom/npm-react';
+import React from 'react';
+
+import { DataCell } from '@consta/table/DataCell';
+import { DataNumberingCell } from '@consta/table/DataNumberingCell';
+import { Table, TableColumn, TableRenderCell } from '@consta/table/Table';
+
+// Types
+
+type ROW = {
+ id: number;
+ name: string;
+ profession: string;
+ status: string;
+ hover: AtomMut;
+ active: AtomMut;
+};
+
+// Atoms
+const rowsAtom = atom[]>([
+ atom({
+ id: 1,
+ name: 'Антон Григорьев',
+ profession: 'Строитель, который построил дом',
+ status: 'недоступен',
+ hover: atom(false),
+ active: atom(false),
+ }),
+ atom({
+ id: 2,
+ name: 'Василий Пупкин',
+ profession: 'Отвечает на вопросы, хотя его не спросили',
+ status: 'на связи',
+ hover: atom(false),
+ active: atom(false),
+ }),
+]);
+
+// Actions
+
+const onRowClickAction = action<[AtomMut]>((ctx, rowAtom) => {
+ const row = ctx.get(rowAtom);
+ row.active(ctx, !ctx.get(row.active));
+});
+
+const onRowMouseEnterAction = action<[AtomMut]>((ctx, rowAtom) => {
+ ctx.get(rowAtom).hover(ctx, true);
+});
+
+const onRowMouseLeaveAction = action<[AtomMut]>((ctx, rowAtom) => {
+ ctx.get(rowAtom).hover(ctx, false);
+});
+
+const DataCellName: TableRenderCell> = (props) => {
+ const [row] = useAtom(props.row);
+ const [active] = useAtom(row.active);
+ const [hover] = useAtom(row.hover);
+
+ return (
+
+ {row.id}
+
+ );
+};
+
+const createDataCellOther = (
+ accessor: Exclude,
+) => {
+ const Component: TableRenderCell> = (props) => {
+ const [row] = useAtom(props.row);
+
+ return {row[accessor]} ;
+ };
+
+ return Component;
+};
+
+const columns: TableColumn>[] = [
+ {
+ title: '',
+ accessor: 'id',
+ width: 48,
+ maxWidth: 48,
+ minWidth: 48,
+ renderCell: DataCellName,
+ },
+ {
+ title: 'Имя',
+ accessor: 'name',
+ width: 240,
+ renderCell: createDataCellOther('name'),
+ },
+ {
+ title: 'Профессия',
+ accessor: 'profession',
+ width: '1fr',
+ renderCell: createDataCellOther('profession'),
+ },
+ {
+ title: 'Статус',
+ accessor: 'status',
+ width: '1fr',
+ minWidth: 150,
+ renderCell: createDataCellOther('status'),
+ },
+];
+
+export const TableExampleActiveRowWithNumbering = () => {
+ const onRowClick = useAction(onRowClickAction);
+ const onRowMouseEnter = useAction(onRowMouseEnterAction);
+ const onRowMouseLeave = useAction(onRowMouseLeaveAction);
+ const [rows] = useAtom(rowsAtom);
+
+ return (
+
);
};
+```
+
+
+
+Есть 2 способа задать состояние `hover` строкам:
+
+- Автоматически, с помощью свойства `rowHoverEffect`. В этом случае все строки будут подсвечиваться при наведении мыши.
+
+
+
+
+
+```tsx
+import React from 'react';
+
+import { Table, TableColumn } from '@consta/table/Table';
+import rows from '@consta/table/Table/__mocks__/olympic-winners.json';
+
+type ROW = {
+ athlete: string;
+ age: number | null;
+ country: string;
+ year: number;
+ date: string;
+ sport: string;
+ gold: number;
+ silver: number;
+ bronze: number;
+ total: number;
+};
+
+const columns: TableColumn[] = [
+ {
+ title: 'Имя',
+ width: 'auto',
+ accessor: 'athlete',
+ },
+ {
+ title: 'Страна',
+ accessor: 'country',
+ width: 'auto',
+ },
+ {
+ title: 'Возраст',
+ accessor: 'age',
+ minWidth: 100,
+ },
+];
+
+export const TableExampleRowHoverEffect = () => (
+
+);
+```
+
+
+
+- Подконтрольный. Для этого нужно у любой ячейки в строке указать атрибут `data-row-hover='true'`. В этом режиме вы можете выбирать, у каких строк включить этот эффект а у каких нет.
+
+
+
+
+
+```tsx
+import { action, atom } from '@reatom/core';
+import { useAction, useAtom } from '@reatom/npm-react';
+import React from 'react';
+
+import { DataNumberingCell } from '@consta/table/DataNumberingCell';
+import { Table, TableColumn, TableRenderCell } from '@consta/table/Table';
+
+// Types
+
+type ROW = {
+ id: number;
+ name: string;
+ profession: string;
+ status: string;
+};
-export const TableExampleRenderRow = () => {
- const [openedList, setOpenedList] = useState([]);
+const rows: ROW[] = [
+ {
+ id: 1,
+ name: 'Антон Григорьев',
+ profession: 'Строитель, который построил дом',
+ status: 'недоступен',
+ },
+ {
+ id: 2,
+ name: 'Василий Пупкин',
+ profession: 'Отвечает на вопросы, хотя его не спросили',
+ status: 'на связи',
+ },
+];
- const openedListRef = useMutableRef(openedList);
+// Atoms
- const rows = useMemo(() => {
- return data.filter(
- (dataItem) =>
- Object.prototype.hasOwnProperty.call(dataItem, 'id') ||
- (isItemInfo(dataItem) &&
- openedList.findIndex(
- (openedListItem) => openedListItem === dataItem.isInfo,
- ) !== -1),
- );
- }, [openedList]);
+const hoverIdAtom = atom(undefined);
- const toggle = useCallback((idx: number) => {
- setOpenedList((state) => {
- const open = state.findIndex((value) => value === idx) !== -1;
- if (open) {
- return state.filter((value) => value !== idx);
- }
- return [...state, idx];
- });
- }, []);
+// Actions
- const renderLabelCell: TableRenderCell = useCallback(({ row }) => {
- if (isItem(row)) {
- return (
- id === row.id) !== -1}
- toggle={toggle}
- />
- );
- }
- if (isItemInfo(row)) {
- return ;
- }
- return null;
- }, []);
+const onRowMouseEnterAction = action<[ROW]>((ctx, row) =>
+ hoverIdAtom(ctx, row.id),
+);
- const columns: TableColumn[] = useMemo(
- () => [
- {
- title: 'Название',
- accessor: 'label',
- renderCell: renderLabelCell,
- colSpan: ({ row }) => (isItemInfo(row) ? 'end' : 1),
- minWidth: 300,
- },
- {
- title: 'Формула',
- accessor: 'formula',
- minWidth: 200,
- },
- {
- title: 'Тип',
- accessor: 'type',
- minWidth: 180,
- },
- ],
- [],
+const onRowMouseLeaveAction = action<[ROW]>((ctx, row) => {
+ const hoverId = ctx.get(hoverIdAtom);
+ if (hoverId === row.id) {
+ hoverIdAtom(ctx, undefined);
+ }
+});
+
+const DataCellName: TableRenderCell = (props) => {
+ const [hover] = useAtom((ctx) => {
+ const hoverId = ctx.spy(hoverIdAtom);
+ return hoverId === props.row.id;
+ });
+
+ return (
+ {props.row.id}
);
+};
+
+const columns: TableColumn[] = [
+ {
+ title: '',
+ accessor: 'id',
+ width: 48,
+ maxWidth: 48,
+ minWidth: 48,
+ renderCell: DataCellName,
+ },
+ {
+ title: 'Имя',
+ accessor: 'name',
+ width: 240,
+ },
+ {
+ title: 'Профессия',
+ accessor: 'profession',
+ width: '1fr',
+ },
+ {
+ title: 'Статус',
+ accessor: 'status',
+ width: '1fr',
+ minWidth: 150,
+ },
+];
+
+export const TableExampleHoveredControlled = () => {
+ const onRowMouseEnter = useAction(onRowMouseEnterAction);
+ const onRowMouseLeave = useAction(onRowMouseLeaveAction);
return (
{
- if (isItemInfo(row)) {
- return `${row.isInfo}-info`;
- }
- if (isItem(row)) {
- return `${row.id}`;
- }
- return '';
- }}
+ zebraStriped
+ onRowMouseEnter={onRowMouseEnter}
+ onRowMouseLeave={onRowMouseLeave}
+ rowHoverEffect
/>
);
};
@@ -2039,7 +2702,100 @@ export const TableExampleRenderRow = () => {
-## Состояние загрузки
+Для выбора строки укажите в любой ячейке строки атрибут `data-row-active='true'`.
+
+
+
+
+
+```tsx
+import { Checkbox } from '@consta/uikit/Checkbox';
+import { atom, AtomMut } from '@reatom/core';
+import { useAction, useAtom } from '@reatom/npm-react';
+import React from 'react';
+
+import { DataCell } from '@consta/table/DataCell';
+import { Table, TableColumn, TableRenderCell } from '@consta/table/Table';
+
+type ROW = {
+ id: number;
+ name: string;
+ profession: string;
+ status: string;
+};
+
+const activeIdsAtom = atom>>({});
+
+const DataCellName: TableRenderCell = (props) => {
+ const [active] = useAtom((ctx) => {
+ const activeAtom = ctx.spy(activeIdsAtom)[props.row.id];
+ return activeAtom ? ctx.spy(activeAtom) : false;
+ });
+
+ const onChange = useAction((ctx) => {
+ const activeIds = ctx.get(activeIdsAtom);
+ const activeAtom = ctx.get(activeIdsAtom)[props.row.id];
+
+ if (activeAtom) {
+ activeAtom(ctx, !ctx.get(activeAtom));
+ } else {
+ activeIdsAtom(ctx, { ...activeIds, [props.row.id]: atom(true) });
+ }
+ });
+
+ return (
+ }
+ >
+ {props.row.name}
+
+ );
+};
+
+const columns: TableColumn[] = [
+ {
+ title: 'Имя',
+ accessor: 'name',
+ width: 240,
+ renderCell: DataCellName,
+ },
+ {
+ title: 'Профессия',
+ accessor: 'profession',
+ width: '1fr',
+ },
+ {
+ title: 'Статус',
+ accessor: 'status',
+ width: '1fr',
+ minWidth: 150,
+ },
+];
+
+const rows: ROW[] = [
+ {
+ id: 1,
+ name: 'Антон Григорьев',
+ profession: 'Строитель, который построил дом',
+ status: 'недоступен',
+ },
+ {
+ id: 2,
+ name: 'Василий Пупкин',
+ profession: 'Отвечает на вопросы, хотя его не спросили',
+ status: 'на связи',
+ },
+];
+
+export const TableExampleActiveRow = () => (
+
+);
+```
+
+
+
+### Состояние загрузки
Загрузку можно отобразить двумя видами с помощью `Loader` или `Skeleton`.
@@ -2119,7 +2875,6 @@ export const TableExampleLoadingDataWithLoader = () => {
```tsx
-import { Loader } from '@consta/uikit/Loader';
import { cnMixSpace } from '@consta/uikit/MixSpace';
import { SkeletonBrick } from '@consta/uikit/Skeleton';
import React from 'react';
@@ -2269,7 +3024,6 @@ export const TableExampleLoadingRowWithLoader = () => {
```tsx
-import { Loader } from '@consta/uikit/Loader';
import { cnMixSpace } from '@consta/uikit/MixSpace';
import { SkeletonBrick } from '@consta/uikit/Skeleton';
import React from 'react';
@@ -2339,8 +3093,6 @@ export const TableExampleLoadingRowWithSkeleton = () => {
-В состоянии загрузки вы можете показывать не только единую строку-заглушку на всю ширину таблицы. При необходимости `Skeleton` можно отрисовывать отдельно в каждой ячейке.
-
**Пример подгрузки вложенной строки с помощью `Loader`:**
@@ -2396,263 +3148,29 @@ const columns: TableColumn[] = [
renderCell: renderIdCell,
minWidth: 300,
},
- {
- title: 'Наименование',
- accessor: 'label',
- minWidth: 300,
- },
-];
-
-export const TableExampleLoadingNestedRowWithLoader = () => {
- return (
- row.id}
- />
- );
-};
-```
-
-
-
-## Индикатор ячейки и подсказки
-
-Для отображения индикатора используйте свойство `indicator` у компонента `DataCell`. Для вывода подсказок используйте компонент `Popover`. В примере ниже реализована логика показа подсказок по наведению курсора.
-
-Пример со всплывающими подсказками:
-
-
-
-
-
-```tsx
-import { Example } from '@consta/stand';
-import { Informer } from '@consta/uikit/Informer';
-import {
- animateTimeout,
- cnMixPopoverAnimate,
-} from '@consta/uikit/MixPopoverAnimate';
-import { Popover } from '@consta/uikit/Popover';
-import { useDebounce } from '@consta/uikit/useDebounce';
-import { useFlag } from '@consta/uikit/useFlag';
-import React, { useCallback, useRef } from 'react';
-import { Transition } from 'react-transition-group';
-
-import { DataCell } from '@consta/table/DataCell';
-import { Table, TableColumn } from '@consta/table/Table';
-
-type Cell = {
- data: T;
- status?: 'alert' | 'warning';
- statusMessage?: string;
-};
-
-type Row = {
- name: Cell;
- profession: Cell;
- status: Cell;
-};
-
-const rows: Row[] = [
- {
- name: {
- data: 'Антон',
- },
- profession: {
- data: 'РОЮЛАВТМЯО',
- status: 'alert',
- statusMessage: 'Неизвестное название диапазона: РОЮЛАВТМЯО.',
- },
- status: {
- data: 'недоступен',
- },
- },
- ..//..//..
-];
-
-const titleMap: Record<'alert' | 'warning', string> = {
- alert: 'Ошибка',
- warning: 'Предупреждение',
-};
-
-const DataCellWithInformer = ({
- data,
- tableRef,
- statusMessage,
- status,
-}: {
- data: string;
- status?: 'alert' | 'warning';
- statusMessage?: string;
- tableRef: React.RefObject;
-}) => {
- const cellRef = useRef(null);
- const popoverContentRef = useRef(null);
- const [informerVisible, setInformerVisible] = useFlag();
-
- const hoverStateRef = useRef<{
- popover: boolean;
- anchor: boolean;
- }>({
- popover: false,
- anchor: false,
- });
-
- const mouseEnterController = useDebounce(
- useCallback(() => {
- (hoverStateRef.current.anchor || hoverStateRef.current.popover) &&
- setInformerVisible.on();
- }, []),
- 200,
- );
-
- const mouseLeaveController = useDebounce(
- useCallback(() => {
- !hoverStateRef.current.anchor &&
- !hoverStateRef.current.popover &&
- setInformerVisible.off();
- }, []),
- 200,
- );
-
- const anchorOnMouseEnter: React.MouseEventHandler =
- useCallback((e) => {
- hoverStateRef.current.anchor = true;
- mouseEnterController();
- }, []);
-
- const anchorOnMouseLeave: React.MouseEventHandler =
- useCallback((e) => {
- hoverStateRef.current.anchor = false;
- mouseLeaveController();
- }, []);
-
- const popoverOnMouseEnter: React.MouseEventHandler =
- useCallback((e) => {
- hoverStateRef.current.popover = true;
- mouseEnterController();
- }, []);
-
- const popoverOnMouseLeave: React.MouseEventHandler =
- useCallback((e) => {
- hoverStateRef.current.popover = false;
- mouseLeaveController();
- }, []);
-
- return (
- <>
-
- {status === 'alert' ? '#ТИПДАННЫХ?' : data}
-
- {status && (
-
- {(animate) => (
-
-
-
- )}
-
- )}
- >
- );
-};
-
-const columns: TableColumn[] = [
- {
- title: 'Имя',
- accessor: 'name',
- renderCell: ({ row, tableRef }) => (
-
- ),
- },
- {
- title: 'Профессия',
- accessor: 'profession',
- renderCell: ({ row, tableRef }) => (
-
- ),
- },
- {
- title: 'Статус',
- accessor: 'status',
- renderCell: ({ row, tableRef }) => (
-
- ),
+ {
+ title: 'Наименование',
+ accessor: 'label',
+ minWidth: 300,
},
];
-export const TableExampleWithIndicator = () => {
+export const TableExampleLoadingNestedRowWithLoader = () => {
return (
-
+ row.id}
+ />
);
};
```
-## Ключ строки
+### Ключ строки
Чтобы оптимизировать рендеринг, укажите уникальный ключ строки в `getRowKey`.
@@ -2666,215 +3184,200 @@ export const TableExampleWithIndicator = () => {
row.uniqueKey} />
```
-## Адаптивная ширина колонок
+### Фильтрация
-Вы можете менять ширину колонок в зависимости от ширины самой таблицы.
+Для фильтрации используйте в `renderHeaderCell` компонент `FlatSelect`.
-
+
```tsx
-import { Example } from '@consta/stand';
+import { IconFunnel } from '@consta/icons/IconFunnel';
import { Button } from '@consta/uikit/Button';
-import { getLastPoint, useBreakpoints } from '@consta/uikit/useBreakpoints';
-import React, { useCallback, useMemo, useRef, useState } from 'react';
+import { FlatSelect } from '@consta/uikit/FlatSelect';
+import { atom } from '@reatom/core';
+import { useAtom } from '@reatom/npm-react';
+import React, { useRef } from 'react';
-import { Table, TableColumn } from '@consta/table/Table';
-
-type Row = { name: string; profession: string; status: string };
+import { HeaderDataCell } from '@consta/table/HeaderDataCell';
+import { Table, TableColumn, TableRenderHeaderCell } from '@consta/table/Table';
-const rows: Row[] = [
+const dataAtom = atom([
{
- name: 'Антон',
+ name: 'Антон Григорьев',
profession: 'Строитель, который построил дом',
status: 'недоступен',
},
{
- name: 'Василий',
+ name: 'Василий Пупкин',
profession: 'Отвечает на вопросы, хотя его не спросили',
status: 'на связи',
},
-];
-
-const columnsWidthMap: Record<
- 's' | 'm' | 'l',
- Record<'name' | 'profession' | 'status', TableColumn>
-> = {
- s: {
- name: {
- width: 100,
- minWidth: 100,
- maxWidth: 100,
- },
- profession: {
- width: '1fr',
- minWidth: 150,
- },
- status: {
- width: 120,
- minWidth: 120,
- maxWidth: 120,
- },
- },
- m: {
- name: {
- width: 120,
- minWidth: 120,
- maxWidth: 120,
- },
- profession: {
- width: '1fr',
- minWidth: 250,
- },
- status: {
- width: 120,
- minWidth: 120,
- maxWidth: 120,
- },
- },
- l: {
- name: {
- width: 150,
- minWidth: 150,
- maxWidth: 150,
- },
- profession: {
- width: '1fr',
- },
- status: {
- width: 150,
- minWidth: 150,
- maxWidth: 150,
- },
- },
-};
+]);
-const breakpointsMap = { s: 300, m: 500, l: 760 };
-const breakpointsSequence: (keyof typeof breakpointsMap)[] = ['s', 'm', 'l'];
+const statusFilterValueAtom = atom([]);
+const nameFilterValueAtom = atom([]);
-export const TableExampleAdaptiveColumns = () => {
- const [widthSequence, setWidthSequence] = useState(0);
+const rowsAtom = atom((ctx) => {
+ const data = ctx.spy(dataAtom);
+ const statusFilterValue = ctx.spy(statusFilterValueAtom);
+ const nameFilterValue = ctx.spy(nameFilterValueAtom);
- const handleWidthChange = useCallback(() => {
- setWidthSequence((state) => {
- const newState = state + 1;
- return newState >= breakpointsSequence.length ? 0 : newState;
- });
- }, []);
+ return data.filter((row) => {
+ return (
+ (nameFilterValue.length
+ ? nameFilterValue.some((el) =>
+ row.name.toLowerCase().includes(el.toLowerCase()),
+ )
+ : true) &&
+ (statusFilterValue.length
+ ? statusFilterValue.some((el) =>
+ row.status.toLowerCase().includes(el.toLowerCase()),
+ )
+ : true)
+ );
+ });
+});
- const tableRef = useRef(null);
+type ROW = {
+ name: string;
+ profession: string;
+ status: string;
+};
- const point =
- getLastPoint(
- useBreakpoints({
- ref: tableRef,
- map: breakpointsMap,
- isActive: true,
- }),
- ) || 's';
+const nameFilterItems = ['Антон Григорьев', 'Василий Пупкин'];
+const statusFilterItems = ['недоступен', 'на связи'];
- const columns: TableColumn[] = useMemo(
- () => [
- {
- title: 'Имя',
- accessor: 'name',
- ...columnsWidthMap[point].name,
- },
- {
- title: 'Профессия',
- accessor: 'profession',
- ...columnsWidthMap[point].profession,
- },
- {
- title: 'Статус',
- accessor: 'status',
- ...columnsWidthMap[point].status,
- },
- ],
- [point],
+const NameHeaderCell: TableRenderHeaderCell = ({ title }) => {
+ const buttonRef = useRef(null);
+ const [nameFilterValue, setNameFilterValue] = useAtom(nameFilterValueAtom);
+ return (
+ <>
+
+ }
+ >
+ {title}
+
+ item}
+ getItemKey={(item) => item}
+ onChange={(value) => setNameFilterValue(value || [])}
+ items={nameFilterItems}
+ multiple
+ style={{ zIndex: 10 }}
+ />
+ >
);
+};
+const StatusHeaderCell: TableRenderHeaderCell = ({ title }) => {
+ const buttonRef = useRef(null);
+ const [statusFilterValue, setStatusFilterValue] = useAtom(
+ statusFilterValueAtom,
+ );
return (
<>
-
+ }
+ >
+ {title}
+
+ item}
+ getItemKey={(item) => item}
+ onChange={(value) => setStatusFilterValue(value || [])}
+ items={statusFilterItems}
+ multiple
+ style={{ zIndex: 10 }}
/>
-
>
);
};
+
+const columns: TableColumn[] = [
+ {
+ title: 'Имя',
+ accessor: 'name',
+ width: 240,
+ renderHeaderCell: NameHeaderCell,
+ },
+ {
+ title: 'Профессия',
+ accessor: 'profession',
+ width: '1fr',
+ },
+ {
+ title: 'Статус',
+ accessor: 'status',
+ width: '1fr',
+ minWidth: 150,
+ renderHeaderCell: StatusHeaderCell,
+ },
+];
+
+export const TableExampleFilter = () => {
+ const [rows] = useAtom(rowsAtom);
+ return ;
+};
```
-## Свойства
+## Пример использования
-```ts
-export type TableRenderHeaderCell = (props: {
- title?: string;
- index: number;
-}) => React.ReactNode | null;
+```tsx
+import { Table, TableColumn } from '@consta/table/Table';
-export type TableRenderCell = (props: {
- row: T;
- rowIndex: number;
- columnIndex: number;
-}) => React.ReactNode | null;
+type Row = { name: string; profession: string; status: string };
-export type TableColumn = {
- title?: string;
- width?:
- | number
- | 'auto'
- | '1fr'
- | '2fr'
- | '3fr'
- | '4fr'
- | '5fr'
- | '6fr'
- | '7fr'
- | '8fr'
- | '9fr'
- | '10fr';
- maxWidth?: number;
- minWidth?: number;
- renderHeaderCell?: TableRenderHeaderCell;
- isSeparator?: boolean;
- pinned?: TableColumnPropPinned;
- renderCell?: TableRenderCell;
- colSpan?: TabletColSpan;
- accessor?: string;
- columns?: TableColumn[];
-};
+const rows: Row[] = [
+ {
+ name: 'Антон',
+ profession: 'Строитель, который построил дом',
+ status: 'недоступен',
+ },
+ {
+ name: 'Василий',
+ profession: 'Отвечает на вопросы, хотя его не спросили',
+ status: 'на связи',
+ },
+];
-type TableRowMouseEvent = (
- row: ROW,
- props: { e: React.MouseEvent },
-) => void;
+const columns: TableColumn[] = [
+ {
+ title: 'Имя',
+ accessor: 'name',
+ },
+ {
+ title: 'Профессия',
+ accessor: 'profession',
+ },
+ {
+ title: 'Статус',
+ accessor: 'status',
+ },
+];
-type GetRowKey = (row: ROW) => string | number;
+;
```
-
-| Свойство | Тип | По умолчанию | Описание |
-| ------------------ | ----------------------------- | ----------------- | ------------------------------------------------------------ |
-| `columns?` | `TableColumn[]` | - | Колонки |
-| `rows?` | `ROW[]` | - | Строки |
-| `getRowKey?` | `GetRowKey` | `(row) => row.id` | Функция получения ключа, если ключ не найден берется `index` |
-| `onRowMouseEnter?` | `TableRowMouseEvent` | - | Событие `onMouseEnter` на строке |
-| `onRowMouseLeave?` | `TableRowMouseEvent` | - | Событие `onMouseLeave` на строке |
-| `onRowClick?` | `TableRowMouseEvent` | - | Событие `onClick` на строке |
-| `virtualScroll?` | `boolean` | - | Включение виртуальной прокрутки |
-| `stickyHeader?` | `boolean` | - | Зафиксировать шапку сверху |
-| `resizable?` | `'inside'` | `'outside'` | - | Включение возможности изменять ширину колонок |
-| `zebraStriped?` | `boolean` | - | Окрашивание строк через одну |
-| `headerZIndex?` | `number` | `1` | `zIndex` шапки |
-| `rowHoverEffect?` | `boolean` | - | Включает эффект наведения на строку |
-| `className?` | `string` | - | Дополнительный CSS-класс |
-| `ref?` | `React.Ref` | - | Ссылка на корневой DOM-элемент |
diff --git a/src/components/Table/__stand__/examples/TableExampleFilter/TableExampleFilter.tsx b/src/components/Table/__stand__/examples/TableExampleFilter/TableExampleFilter.tsx
new file mode 100644
index 0000000..421d42a
--- /dev/null
+++ b/src/components/Table/__stand__/examples/TableExampleFilter/TableExampleFilter.tsx
@@ -0,0 +1,152 @@
+import { IconFunnel } from '@consta/icons/IconFunnel';
+import { Example } from '@consta/stand';
+import { Button } from '@consta/uikit/Button';
+import { FlatSelect } from '@consta/uikit/FlatSelect';
+import { atom } from '@reatom/core';
+import { useAtom } from '@reatom/npm-react';
+import React, { useRef } from 'react';
+
+import { HeaderDataCell } from '##/components/HeaderDataCell';
+import { Table, TableColumn, TableRenderHeaderCell } from '##/components/Table';
+
+const dataAtom = atom([
+ {
+ name: 'Антон Григорьев',
+ profession: 'Строитель, который построил дом',
+ status: 'недоступен',
+ },
+ {
+ name: 'Василий Пупкин',
+ profession: 'Отвечает на вопросы, хотя его не спросили',
+ status: 'на связи',
+ },
+]);
+
+const statusFilterValueAtom = atom([]);
+const nameFilterValueAtom = atom([]);
+
+const rowsAtom = atom((ctx) => {
+ const data = ctx.spy(dataAtom);
+ const statusFilterValue = ctx.spy(statusFilterValueAtom);
+ const nameFilterValue = ctx.spy(nameFilterValueAtom);
+
+ return data.filter((row) => {
+ return (
+ (nameFilterValue.length
+ ? nameFilterValue.some((el) =>
+ row.name.toLowerCase().includes(el.toLowerCase()),
+ )
+ : true) &&
+ (statusFilterValue.length
+ ? statusFilterValue.some((el) =>
+ row.status.toLowerCase().includes(el.toLowerCase()),
+ )
+ : true)
+ );
+ });
+});
+
+type ROW = {
+ name: string;
+ profession: string;
+ status: string;
+};
+
+const nameFilterItems = ['Антон Григорьев', 'Василий Пупкин'];
+const statusFilterItems = ['недоступен', 'на связи'];
+
+const NameHeaderCell: TableRenderHeaderCell = ({ title }) => {
+ const buttonRef = useRef(null);
+ const [nameFilterValue, setNameFilterValue] = useAtom(nameFilterValueAtom);
+ return (
+ <>
+
+ }
+ >
+ {title}
+
+ item}
+ getItemKey={(item) => item}
+ onChange={(value) => setNameFilterValue(value || [])}
+ items={nameFilterItems}
+ multiple
+ style={{ zIndex: 10 }}
+ />
+ >
+ );
+};
+
+const StatusHeaderCell: TableRenderHeaderCell = ({ title }) => {
+ const buttonRef = useRef(null);
+ const [statusFilterValue, setStatusFilterValue] = useAtom(
+ statusFilterValueAtom,
+ );
+ return (
+ <>
+
+ }
+ >
+ {title}
+
+ item}
+ getItemKey={(item) => item}
+ onChange={(value) => setStatusFilterValue(value || [])}
+ items={statusFilterItems}
+ multiple
+ style={{ zIndex: 10 }}
+ />
+ >
+ );
+};
+
+const columns: TableColumn[] = [
+ {
+ title: 'Имя',
+ accessor: 'name',
+ width: 240,
+ renderHeaderCell: NameHeaderCell,
+ },
+ {
+ title: 'Профессия',
+ accessor: 'profession',
+ width: '1fr',
+ },
+ {
+ title: 'Статус',
+ accessor: 'status',
+ width: '1fr',
+ minWidth: 150,
+ renderHeaderCell: StatusHeaderCell,
+ },
+];
+
+export const TableExampleFilter = () => {
+ const [rows] = useAtom(rowsAtom);
+ return (
+
+
+
+ );
+};
diff --git a/yarn.lock b/yarn.lock
index 80788cf..eca9aef 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1873,9 +1873,9 @@
workbox-webpack-plugin "^6.4.1"
"@consta/uikit@^5.22.0":
- version "5.23.0"
- resolved "https://registry.yarnpkg.com/@consta/uikit/-/uikit-5.23.0.tgz#81fab63df3f3dd05a55e8cf67d7d33991854a289"
- integrity sha512-gH5cIYQbniUhMLtmIYCDg3N7UvEBYdLMS6wmZ6OWr6X1muSoOfICxPI6P9ECPCxcn62DUe5C8F2yRV1WaSsSHA==
+ version "5.26.0"
+ resolved "https://registry.yarnpkg.com/@consta/uikit/-/uikit-5.26.0.tgz#db188813404cd58486ceae7b7a3a44172cc390fb"
+ integrity sha512-rbkrl4PPVsg+Ly8UtC4B8y1VD0cbOHFGTpNrDg6qsUyuSAqT1e80XGgzefACmEmlh4NDt9kexWiUIpdlckvZ2Q==
"@cspotcode/source-map-support@^0.8.0":
version "0.8.1"