# Table: Руководства

## Паттерны

### Таблица цен или сравнения

Когда использовать:

- Небольшой набор строк с понятными столбцами должен оставаться семантически табличным.

Заметки о композиции:

- Явно задавайте подписи столбцов в `TableHead`.
- Предпочитайте `fullWidth`, когда таблица должна восприниматься как основной блок содержимого.

Компромиссы:

- Высокая семантическая ясность.
- Требует стратегии переполнения на маленьких экранах на стороне потребителя.

```tsx
import { Check, Minus } from '@vega-ui/icons';
import {
  Icon,
  Table,
  TableBody,
  TableData,
  TableHead,
  TableHeading,
  TableRow,
  VisuallyHidden,
} from '@vega-ui/react';

const plans = ['Starter', 'Team', 'Enterprise'];

const features: { name: string, values: (string | boolean)[] }[] = [
  { name: 'Price per seat', values: ['$9', '$29', 'Custom'] },
  { name: 'Projects', values: ['5', 'Unlimited', 'Unlimited'] },
  { name: 'Storage', values: ['10 GB', '1 TB', '5 TB'] },
  { name: 'Single sign-on', values: [false, true, true] },
  { name: 'Audit log', values: [false, false, true] },
];

export const layout = 'wide';

export default function PlanComparisonTable() {
  return (
    <div role='region' aria-label='Compare plans' tabIndex={0} style={{ width: '100%', overflowX: 'auto', border: '1px solid var(--separator-opaque)', borderRadius: 'var(--radius-4)', background: 'var(--surface-primary)' }}>
      <Table fullWidth edgePadded dataAlign='center' style={{ borderStyle: 'hidden' }}>
        <caption style={{ captionSide: 'top', textAlign: 'start', padding: 'var(--spacing-6)', paddingBlockEnd: 0, fontWeight: 600 }}>
          Compare plans
        </caption>
        <TableHead>
          <TableRow>
            <TableHeading scope='col' dataAlign='start'>Feature</TableHeading>
            {plans.map((plan) => <TableHeading key={plan} scope='col'>{plan}</TableHeading>)}
          </TableRow>
        </TableHead>
        <TableBody>
          {features.map((feature) => (
            <TableRow key={feature.name}>
              <TableHeading scope='row' dataAlign='start' style={{ fontWeight: 400, whiteSpace: 'nowrap' }}>{feature.name}</TableHeading>
              {feature.values.map((value, index) => {
                if (typeof value === 'string') {
                  return <TableData key={plans[index]}>{value}</TableData>;
                }

                return (
                  <TableData key={plans[index]}>
                    <Icon size='sm' aria-hidden='true'>{value ? <Check /> : <Minus />}</Icon>
                    <VisuallyHidden>{value ? 'Included' : 'Not included'}</VisuallyHidden>
                  </TableData>
                );
              })}
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </div>
  );
}
```

### Сводная таблица с итогами в подвале

Когда использовать:

- Таблице нужны агрегированные итоги или сводные значения.

Заметки о композиции:

- Размещайте сводки в `TableFoot`, а не придумывайте самодельные строки подвала.
- Выравнивайте числовые столбцы одинаково в теле и подвале.

Компромиссы:

- Итоги семантически связаны с таблицей.
- Может выглядеть тяжеловесно, если в таблице всего одна-две строки.

```tsx
import {
  Table,
  TableBody,
  TableData,
  TableFoot,
  TableHead,
  TableHeading,
  TableRow,
} from '@vega-ui/react';

const lines = [
  { item: 'Team plan · 12 seats', quantity: 12, unitPrice: 29 },
  { item: 'Extra storage · 500 GB', quantity: 1, unitPrice: 40 },
  { item: 'Priority support', quantity: 1, unitPrice: 99 },
];

const currency = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' });

export default function SummaryTableWithFooterTotals() {
  const subtotal = lines.reduce((sum, line) => sum + line.quantity * line.unitPrice, 0);
  const tax = subtotal * 0.2;

  return (
    <div style={{ width: '100%', border: '1px solid var(--separator-opaque)', borderRadius: 'var(--radius-4)', background: 'var(--surface-primary)' }}>
      <Table fullWidth edgePadded style={{ borderStyle: 'hidden' }}>
        <TableHead>
          <TableRow>
            <TableHeading scope='col'>Item</TableHeading>
            <TableHeading scope='col'>Qty</TableHeading>
            <TableHeading scope='col'>Amount</TableHeading>
          </TableRow>
        </TableHead>
        <TableBody>
          {lines.map((line) => (
            <TableRow key={line.item}>
              <TableData>{line.item}</TableData>
              <TableData>{line.quantity}</TableData>
              <TableData style={{ fontVariantNumeric: 'tabular-nums' }}>{currency.format(line.quantity * line.unitPrice)}</TableData>
            </TableRow>
          ))}
        </TableBody>
        <TableFoot>
          <TableRow>
            <TableHeading scope='row' colSpan={2} dataAlign='start' style={{ fontWeight: 400 }}>Subtotal</TableHeading>
            <TableData style={{ fontVariantNumeric: 'tabular-nums' }}>{currency.format(subtotal)}</TableData>
          </TableRow>
          <TableRow>
            <TableHeading scope='row' colSpan={2} dataAlign='start' style={{ fontWeight: 400 }}>VAT 20%</TableHeading>
            <TableData style={{ fontVariantNumeric: 'tabular-nums' }}>{currency.format(tax)}</TableData>
          </TableRow>
          <TableRow>
            <TableHeading scope='row' colSpan={2} dataAlign='start'>Total due</TableHeading>
            <TableData style={{ fontWeight: 600, fontVariantNumeric: 'tabular-nums' }}>{currency.format(subtotal + tax)}</TableData>
          </TableRow>
        </TableFoot>
      </Table>
    </div>
  );
}
```

### Таблица в оверлеях проверки

Когда использовать:

- Диалогу или выдвижной панели (Drawer) нужна компактная табличная сводка.

Заметки о композиции:

- Делайте таблицу узкой и удобной для быстрого просмотра.
- Не допускайте, чтобы оверлей зависел от горизонтальной прокрутки, если раскладка не рассчитана на это.

Компромиссы:

- Хорошо подходит для сводок и детализации счетов.
- Менее надёжно для широких наборов данных с множеством столбцов.

```tsx
import {
  Button,
  Dialog,
  DialogClose,
  DialogContent,
  DialogTrigger,
  Heading,
  Icon,
  IconButton,
  Table,
  TableBody,
  TableData,
  TableHead,
  TableHeading,
  TableRow,
} from '@vega-ui/react';
import { XIcon } from '@vega-ui/icons';
import { CSSProperties, useId } from 'react';

const charges = Array.from({ length: 18 }, (_, index) => ({
  date: `Oct ${index + 1}`,
  description: index % 3 === 0 ? 'Build minutes overage' : 'API requests',
  amount: 4 + ((index * 7) % 23),
}));

export default function BillingTableInsideADialog() {
  const titleId = useId();

  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button variant='secondary' appearance='ghost'>View October charges</Button>
      </DialogTrigger>
      <DialogContent aria-labelledby={titleId} style={{ display: 'grid', gap: 'var(--spacing-8)' }}>
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 'var(--spacing-8)' }}>
          <Heading id={titleId} as='h2' size={4} fontWeight={600}>October usage charges</Heading>
          <DialogClose asChild>
            <IconButton
              aria-label='Close'
              variant='secondary'
              appearance='ghost'
              style={{ flexShrink: 0, '--icon-button-size': '24px', '--icon-button-br-ratio': 0.5 } as CSSProperties}
            >
              <Icon size='xs'><XIcon strokeWidth={2.5} /></Icon>
            </IconButton>
          </DialogClose>
        </div>
        <div role='region' aria-labelledby={titleId} tabIndex={0} style={{ maxHeight: 280, overflowY: 'auto' }}>
          <Table fullWidth>
            <TableHead>
              <TableRow>
                <TableHeading scope='col' style={{ position: 'sticky', insetBlockStart: 0, background: 'var(--surface-primary)' }}>Date</TableHeading>
                <TableHeading scope='col' style={{ position: 'sticky', insetBlockStart: 0, background: 'var(--surface-primary)' }}>Description</TableHeading>
                <TableHeading scope='col' style={{ position: 'sticky', insetBlockStart: 0, background: 'var(--surface-primary)' }}>Amount</TableHeading>
              </TableRow>
            </TableHead>
            <TableBody>
              {charges.map((charge) => (
                <TableRow key={charge.date}>
                  <TableData>{charge.date}</TableData>
                  <TableData>{charge.description}</TableData>
                  <TableData style={{ fontVariantNumeric: 'tabular-nums' }}>${charge.amount}.00</TableData>
                </TableRow>
              ))}
            </TableBody>
          </Table>
        </div>
      </DialogContent>
    </Dialog>
  );
}
```

## Сравнение

### Быстрое правило выбора

Используйте `Table`, когда достаточно нативной семантической структуры таблицы, а взаимодействие в стиле grid не нужно.

### `Table` или `DataGrid`

Используйте `Table`, когда:

- данные в основном только для чтения
- главное требование — нативная семантика таблицы
- навигация с клавиатуры по ячейкам не нужна

Используйте `DataGrid`, когда:

- функции нужны более богатое взаимодействие с клавиатуры, выбор или поведение грида

Главный компромисс: `Table` проще и семантичнее, а `DataGrid` справляется с более тяжёлыми моделями взаимодействия.

### `Table` или `DataGridSelectable`

Используйте `Table`, когда:

- выбор строк не входит в основную модель взаимодействия

Используйте `DataGridSelectable`, когда:

- пользователям нужен явный выбор строк

Главный компромисс: `Table` избегает сложности грида, а `DataGridSelectable` добавляет семантику и контролы выбора.

### Выбирайте этот компонент, когда

- Данные табличные и в основном только для чтения.
- Нативная семантическая структура важнее продвинутого взаимодействия.
- Таблица должна оставаться простой и предсказуемой.

### Не выбирайте этот компонент, когда

- Функции нужно поведение клавиатуры в стиле grid.
- Центральную роль играют выбор, встроенное редактирование или более богатое взаимодействие со строками.
- Интерфейс по сути — грид данных, а не семантическая таблица.

## Решение проблем

### Таблице, вероятно, стоит быть DataGrid

#### Симптом

У функции постоянно добавляются требования к выбору, встроенному редактированию или взаимодействию на уровне ячеек.

#### Вероятная причина

`Table` используется для более богатой модели взаимодействия, чем та, на которую он рассчитан.

#### Как проверить

- Перечислите необходимые взаимодействия для строк и ячеек.
- Сравните их с поведением семейства `DataGrid`.

#### Исправление

- Переходите на `DataGrid`, когда взаимодействие в стиле grid становится центральным.
- Оставляйте `Table` для семантических раскладок, в основном только для чтения.

### Столбцы обрезаются на маленьких экранах

#### Симптом

На узких раскладках таблицу трудно читать или столбцы пропадают.

#### Вероятная причина

Раскладка потребителя не предусматривает явной стратегии переполнения или адаптивности.

#### Как проверить

- Проверьте реальный продакшен-контейнер на меньшей ширине.
- Проверьте, есть ли у таблицы обёртка с прокруткой или альтернативная мобильная раскладка.

#### Исправление

- Добавьте обёртку переполнения или адаптивный запасной вариант на стороне потребителя.
- Сократите лишние столбцы в узких контекстах.

### Выравнивание в ячейках выглядит неправильно

#### Симптом

Ячейки заголовков и данных не выровнены, или числовые значения выглядят визуально несогласованно.

#### Вероятная причина

Выбор `dataAlign` и краевых отступов не соответствует задуманной модели содержимого.

#### Как проверить

- Сравните выравнивание заголовка и данных в одном столбце.
- Проверьте отступы первой и последней ячейки с включённым и выключенным `edgePadded`.

#### Исправление

- Используйте одну согласованную стратегию `dataAlign` для всей таблицы.
- Меняйте краевые отступы, только если окружающая раскладка уже обеспечивает нужные промежутки.
