@smkit/ui
Version:
UI Kit of SberMarketing
442 lines (362 loc) • 14.3 kB
Markdown
# 📋 Table
Универсальный компонент таблицы, используется в списках сущностей и дашбордах.
Содержит следующую иерархию компонент
- Table
- Exports
- CSV
- Excel
- Root
- Head
- Row
- Cell
## Компонент Table
Обертка всей таблицы, может применятся как есть, без слотов. Создает контекст, инициализирует контроллер и отвечает за обработку данных
### Props:
- **data** - входные данные в формате строк списка. Если columns не задан, вычисляет из keys
```typescript
type RowData = (Record<Head['name'], Cell> & { class?: string })[];
// Пример
const data: RowData = [
{ col1: 'cell1', col2: 'cell2' },
{ col1: 'cell3', col2: 'cell4' }
];
```
- **columns** - (опционально) отвечает за свойства колонок и их отображение
```typescript
export enum Types {
Number = 'number',
String = 'string',
Date = 'date',
Boolean = 'boolean',
List = 'list'
}
export interface Head {
title?: string; // Отображаемый заголовок
name: string; // Ключ колонки, связывает значения в строках
width?: number | string; // Ширина колонки
type?: Types | string; // Тип 'number' | 'string' | 'date' | 'boolean' | 'list'
unit?: string;
class?: string; // Класс для стилей колонки
skipRender?: boolean;
sortable?: boolean; // Можно ли отсортировать колонку
resizable?: boolean; // Можно ли менять размер
pinnable?: boolean; // Можно ли закрепить колонку
pinned?: boolean; // Закреплена ли колонка
offsetLeft?: number; // Смещение закрепленной колонки (техническое поле, перерасчитывается само)
element?: HTMLElement; // Элемент ресайза (техническое поле)
index?: number; // Индекс позиции колонки при инициализации (нужно для возврата на место при изменении pinned)
cast?: (cell: Cell) => string; // Кастомный каст колонки в строку
onSort?: SortCallback; // Callback сортировки для данной колонки
}
// Пример
const columns: Head[] = [
{ name: 'col1', title: 'Первая колонка' },
{ name: 'col2', title: 'Вторая колонка' }
];
```
- **config** - (опционально) отвечает за конфигурацию таблицы и свойств по умолчанию
```typescript
export type Config = {
virtualizer?: {
overscan?: number;
// scrollElement: HTMLDivElement
rowHeight: number; // Размер строк в таблице. Нужно как для отображения так и расчета виртуализации
};
enumerate?: boolean;
head?: {
// Глобальный конфиг колонок по-умолчанию
precision?: Record<string, number>;
pinnable?: boolean;
resizable?: boolean;
sortable?: boolean;
};
// Некоторые фичи могут еще не работать
onSort?: SortCallback;
onPagination?: undefined;
onFilter?: undefined;
replaceNull?: string;
};
```
- controller - (опционально) используется, если мы хотим создать контроллер самостоятельно выше по уровню иерархии компонент
Свойства протекают вглубь компонентов и имеют приоритет по специфичности. То есть глобальная конфигурация может быть перезаписана частным случаем
<p align='center'>
default < config.head < columns: Head[] <p Head props
</p>
`Head.svelte`
```typescript
export let head: Head;
export let sortable = head?.sortable ?? $config?.head?.sortable ?? true;
export let resizable = head?.sortable ?? $config?.head?.resizable ?? true;
export let pinnable = head?.pinnable ?? $config?.head?.pinnable ?? true;
```
### Slots
Table имеет два слота, с отображением по-умолчанию
- _default_ - Для компонента Root
- _top_ - Для Экспорта данных и других кнопок управления над таблицей
```html
<script>
import { Table, Exports, Root } from '@smkit/ui/table';
import { data } from './data';
</script>
<table data="{data.body}" columns="{data.header}">
<Exports slot="top" />
<Root />
</table>
```
Пробрасывает наружу пропсы:
- controller - экземпляр класса контроллера
- header - обработанные заголовки для отображения
- body - обработанное тело таблицы в виде массива объектов
- config - обработанный конфиг
```html
<table let:controller let:header let:body let:config>
<Root />
</table>
```
## Компонент Root
Отвечает за структуру таблицы и ее виртуализацию
### Slots
- _header_ - слот заголовка таблицы, используемый через svelte:fragment
```html
...
<svelte:fragment slot="header">
{#each header as head}
<head {head}>
{head.title} {head.width}
</head>
{/each}
</svelte:fragment>
```
- _row_ - слот для подстановки кастомной строки Row и кастомных клеток. Пробрасывает наружу пропсы:
- row - объект строки
- vRow - описание строки от [виртуализации](https://tanstack.com/virtual/latest/docs/api/virtual-item)
- vIndex - виртуальный индекс
```html
...
<Row slot="row" let:row let:vRow let:vIndex>
{#each header as head}
<Cell {head} />
{/each}
</Row>
```
## Компонент Row
Компонент строки, получает информацию о текущем индексе, позиции и данные из контекста. Используется в цикле рендера строк
### Props
- row - (опционально) достается из контекста строки
- vRow - (опционально) достается из контекста строки
- vIndex - (опционально) достается из контекста строки
- style - (опционально) стили всей строки
- class - (опционально) классы стилей всей строки
### Slots
- default - отображение кастомных клеток, через цикл. Порядок клеток регулируется _header_ из **Table**
```html
<table {columns} {data} {controller} let:header>
<Root>
<svelte:fragment slot="header">
{#each header as head}
<head {head} class="p-1 px-2" />
{/each}
</svelte:fragment>
<Row slot="row">
{#each header as head}
<Cell {head} />
{/each}
</Row>
</Root>
</table>
```
## Компонент Cell
Компонент клетки, получает информацию о текущем head из пропсов и о текущем индексе из контекста строки. Имеет два режима, при закрепленном столбце и при обычном
### Props
- value - (на выбор: value/head) - значение клетки напрямую
- head - (на выбор: value/head) - заголовок колонки клетки, значение вычислится само
- class - (опционально) классы стилей клетки
### Slots
- default - Отображение клетки
`Platina360 - team/[teamId]/settings/roles/+page.svelte`
```html
...
<Row
slot="row"
class="hover:bg-[#7997FF33]"
>
<Cell head={{ name: '', width: '5%' }} class="opacity-0 group-hover:opacity-100">
<Checkbox checked={row.isChecked} class="opacity-0 group-hover:opacity-100" />
</Cell>
<Cell head={header[0]}>
<Account name={row.user.name} email={row.user.email} avatar={row.user.avatar} />
</Cell>
<Cell head={header[1]}>
<span class="text-base-content pr-2">
{row.roles}
</span>
<Popover class="opacity-0 group-hover:opacity-100">
<Trigger
slot="trigger"
class="btn-ghost hover:bg-inherit opacity-0 group-hover:opacity-100 border border-neutral/20 max-h-6 w-6 relative"
><span class="hidden"></span></Trigger
>
<button class="btn btn-sm bg-base-100 border-none w-full"> Личные </button>
<button class="btn btn-sm bg-base-100 border-none"> Командные </button>
</Popover>
</Cell>
<Cell head={header[2]}>
<License license={row.licenses} />
</Cell>
<Cell head={header[3]}>
{row.status}
</Cell>
</Row>
```
## Компонент Head
Аналогично компоненту клетки, имеет два варианта с закреплением и без. Имеет пропсы, обозначенные в структуре Head выше, с наследованием и приоритетами по специфике
### Props
- head: Head - объект описания заголовка
- sortable - можно ли сортировать по колонке
- resizable - можно ли менять размер колонки
- pinnable - можно ли закреплять колонку
### Slots
- default - отображение клетки заголовка
`Platina - reports/components/table/Table.svelte`
```html
...
<svelte:fragment slot="header">
{#each header as head}
<head {head} on:sort="{onSort}">
<div class="flex flex-col items-start">
{head.title}
<div class="flex gap-1">
{#if head.type === 'number'}
<button
title="Фильтр по метрике"
class="flex h-5 w-5 items-center justify-center transition-colors"
on:click|stopPropagation|preventDefault="{()"
=""
>
openFilter(head.name)} >
<FunnelIcon
bgColor="{globalFilters.includes(head.name)"
?
Colors.selectedBg
:
Colors.normalBg}
/>
</button>
<button
title="Отобразить метрику на графике"
class="flex h-5 w-5 items-center justify-center transition-colors"
on:click|stopPropagation|preventDefault="{()"
=""
>
{ $chartForm.metric = head.name }} >
<PieIcon
bgColor="{$chartForm.metric"
=""
=""
="head.name"
?
Colors.selectedBg
:
Colors.normalBg}
/>
</button>
{#if columns[head.name].format !== 'percent' && columns[head.name].format !== 'date'}
<button
title="Пересчитать в проценты"
class="flex h-5 w-5 items-center justify-center transition-colors"
on:click|stopPropagation|preventDefault="{()"
=""
>
togglePercentView(head.name)} >
<PercentsIcon
bgColor="{$viewForm.columns?.[head.name]?.isPercent"
?
Colors.selectedBg
:
Colors.normalBg}
/>
</button>
{/if} {#if $viewForm.columns?.[head.name]?.isRounded !== undefined &&
!$viewForm.columns[head.name]?.isPercent}
<button
title="Не округлять бюджеты до целого"
class="flex h-5 w-5 items-center justify-center transition-colors"
on:click|stopPropagation|preventDefault="{()"
=""
>
toggleRoundView(head.name)} >
<RoundedIcon
bgColor="{$viewForm.columns?.[head.name]?.isRounded"
?
Colors.normalBg
:
Colors.selectedBg}
/>
</button>
{/if} {:else if head.type === 'string'}
<button
title="Отобразить группу цветом на графике"
class="flex h-5 w-5 items-center justify-center transition-colors"
on:click|stopPropagation|preventDefault="{()"
=""
>
{ $chartForm.group = head.name }} >
<PieIcon
bgColor="{$chartForm.group"
=""
=""
="head.name"
?
Colors.selectedBg
:
Colors.normalBg}
/>
</button>
{/if}
</div>
{#key showFilterDropdown[head.name]}
<DropdownCard bind:show="{showFilterDropdown[head.name]}" x="-40px" y="0px">
<div class="flex w-[300px] flex-col gap-4 px-4 pb-3 pt-4 shadow-lg">
<div class="flex max-h-10 items-center gap-4" on:click|stopPropagation|preventDefault>
<select selected="{operator}" options="{operators}" label="Фильтр" />
<InputFL bind:value="{filterQuery}" label="Значение" />
</div>
<button
class="btn btn-primary btn-sm mt-2 w-full py-2"
disabled="{!filterQuery"
||
Number.isNaN(Number(filterQuery))}
on:click="{saveFilter}"
>
Сохранить
</button>
</div>
</DropdownCard>
{/key}
</div>
</head>
{/each}
</svelte:fragment>
```
## Компонент Exports
Кнопка экспорта данных, содержащая Excel и CSV по-умолчанию. Должна находиться в контексте таблицы. Можно использовать вне таблицы, если использована ручная инициализация контроллера
```html
<script>
import { Table, Exports } from '@smkit/ui/table';
import { data } from './data';
</script>
<table data="{data.body}" columns="{data.header}">
<Exports slot="top" />
</table>
```
```html
<script>
import { Table, Exports, Controller } from '$lib/table';
import { columns, data } from './tableConfig';
const controller = new Controller();
</script>
<Exports />
<div class="h-[600px]">
<table {columns} {data} {controller} />
</div>
```