From af8af84ab7042258267b021cbb731e1ab9ea1c03 Mon Sep 17 00:00:00 2001 From: shats Date: Mon, 16 Feb 2026 15:56:28 +0700 Subject: [PATCH] refactor(DataTable): enhance table styling and functionality with fixed action column, improved striped rows, and new props for UX customization --- frontend/docs/TABLE_UX.md | 43 +++++++++++++++++++++++++++ frontend/src/components/DataTable.jsx | 38 +++++++++++++++-------- frontend/src/index.css | 16 +++++++++- 3 files changed, 84 insertions(+), 13 deletions(-) create mode 100644 frontend/docs/TABLE_UX.md diff --git a/frontend/docs/TABLE_UX.md b/frontend/docs/TABLE_UX.md new file mode 100644 index 0000000..ca5aec0 --- /dev/null +++ b/frontend/docs/TABLE_UX.md @@ -0,0 +1,43 @@ +# Таблицы: UX/UI по Tabler и проекту + +Рекомендации по формированию таблиц в приложении — [Tabler Tables](https://docs.tabler.io/ui/components/tables/), [Preview](https://preview.tabler.io/tables.html). + +## Классы (Tabler) + +- **Базовые:** `table card-table table-vcenter table-nowrap mb-0` +- **Чередование строк:** `table-striped` — улучшает читаемость +- **Компактный режим:** `table-sm` — меньше отступы в ячейках +- **Обёртка:** `table-responsive` вокруг `` +- **Обрезка длинного текста:** для ячейки использовать `td-truncate` (Tabler) или `text-truncate` + `max-width` + +## Компонент DataTable + +Используйте `DataTable` там, где подходит декларативное описание колонок и общие действия (сортировка, выбор, пагинация): + +- `columns`: `{ key, title, sortable?, icon?, render?, tooltip? }` — для подсказки по заголовку задайте `tooltip` +- `striped={true}` по умолчанию +- `compact={true}` — компактные ячейки +- Колонка «Действия» получает класс `td-actions` (фиксированная минимальная ширина в проекте) + +## Информативность и лаконичность + +1. **Заголовки колонок** — короткие, при необходимости уточнение через `tooltip` (например, значение иконки замка). +2. **Действия в строке** — иконки с `Tooltip` и `aria-label`; колонка «Действия» справа, без лишнего текста. +3. **Копирование** — кнопка/иконка копирования рядом с ячейкой (IP, домен и т.д.) вместо дублирования в «Действия», если это основной сценарий. +4. **Пустое состояние** — через `emptyState` в DataTable или свой блок с призывом к действию (импорт, добавление). + +## Доступность + +- У заголовков: `scope="col"`. +- У таблицы: `role="grid"` при семантической сетке данных. +- Кнопки действий: `aria-label` и/или Tooltip. + +## Где таблица своя, а не DataTable + +В IPRangesManager, DomainsNewManager, ServerManager и др. таблицы рендерятся вручную. Для единообразия: + +- Те же классы: `table card-table table-vcenter table-nowrap mb-0`, при желании `table-striped`. +- Заголовки: `th` с `scope="col"`, короткий текст, при необходимости иконка и тултип. +- Колонка действий: класс `td-actions` у `th` и `td`, кнопки с тултипами. + +При рефакторинге страниц можно постепенно переводить такие таблицы на DataTable с описанием `columns` и общих обработчиков. diff --git a/frontend/src/components/DataTable.jsx b/frontend/src/components/DataTable.jsx index 8da21f6..a4ad188 100644 --- a/frontend/src/components/DataTable.jsx +++ b/frontend/src/components/DataTable.jsx @@ -34,6 +34,8 @@ import Tooltip from './Tooltip.jsx'; * - [{ icon: Component, label: string, onClick: (item) => void, variant?: string }] * @param {boolean} props.selectable - Включить чекбоксы выбора (default: true) * @param {number} props.skeletonRows - Количество строк скелетона (default: 10) + * @param {boolean} props.striped - Чередование фона строк (Tabler UX, default: true) + * @param {boolean} props.compact - Уменьшенные отступы ячеек (default: false) */ function DataTable({ columns = [], @@ -56,11 +58,13 @@ function DataTable({ actions = [], selectable = true, skeletonRows = 10, + striped = true, + compact = false, }) { const hasActions = onEdit || onDelete || onCopy || actions.length > 0; const isEditing = (item) => inlineEdit && item[itemKey] === inlineEdit.editingKey; - // Рендер заголовка колонки + // Рендер заголовка колонки (col.title — подпись, col.tooltip — подсказка для UX) const renderColumnHeader = (col) => { const isSortable = col.sortable !== false && onSort; const isActive = sortField === col.key; @@ -77,22 +81,27 @@ function DataTable({ ); + const thProps = { + key: col.key, + scope: 'col', + style: col.width ? { width: col.width } : undefined, + }; + if (isSortable) { return ( - ); } return ( - ); }; @@ -210,15 +219,20 @@ function DataTable({ } const allSelected = items.length > 0 && items.every(i => selectedItems.has(i[itemKey])); - + const tableClass = [ + 'table card-table table-vcenter table-nowrap mb-0', + striped && 'table-striped', + compact && 'table-sm', + ].filter(Boolean).join(' '); + return ( <>
-
onSort(col.key)} - style={col.width ? { width: col.width } : undefined} > - {content} + {col.tooltip ? {content} : content} - {content} + + {col.tooltip ? {content} : content}
+
{selectable && ( - } + {hasActions && } @@ -261,7 +275,7 @@ function DataTable({ ))} {hasActions && ( - )} diff --git a/frontend/src/index.css b/frontend/src/index.css index 3eef7bd..c356da9 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -296,7 +296,7 @@ button:focus-visible, z-index: 2; } -/* ===== Table Improvements ===== */ +/* ===== Table Improvements (Tabler + UX) ===== */ .table thead th { font-weight: 600; text-transform: uppercase; @@ -318,6 +318,20 @@ button:focus-visible, background-color: rgba(32, 107, 196, 0.1); } +/* Колонка «Действия» — фиксированная ширина, не сжимается (лаконичный UX) */ +.table .td-actions { + min-width: 140px; + white-space: nowrap; +} + +/* Выбранная строка поверх zebra (table-striped) */ +.table-striped tbody tr.table-selected > * { + --tblr-table-bg-type: rgba(32, 107, 196, 0.08); +} +.table-striped tbody tr.table-selected:hover > * { + --tblr-table-bg-type: rgba(32, 107, 196, 0.12); +} + .user-select-none { user-select: none; -webkit-user-select: none;
+ )} {columns.map(renderColumnHeader)} - {hasActions && ДействияДействия
+ {renderActions(item)}