Динамические табы

Динамические табы — это элементы интерфейса, которые позволяют пользователю переключаться между различными панелями контента, при этом их количество и структура могут изменяться во время работы приложения. В библиотеке React Aria реализована мощная система управления табами, которая обеспечивает корректную работу с доступностью, фокусом и управлением состоянием.


Управление состоянием табов

В React Aria состояние табов отделено от их визуального отображения. Для динамических табов ключевым является useTabListState. Этот хук принимает объект с параметрами и возвращает состояние таб-листа:

import { useTabListState } from "@react-stately/tabs";

const state = useTabListState({
  selectedKey: "tab1",
  defaultSelectedKey: "tab1",
  items: [
    { key: "tab1", name: "Первый" },
    { key: "tab2", name: "Второй" }
  ]
});
  • selectedKey — текущий активный таб.
  • defaultSelectedKey — таб, который будет активен при первой отрисовке.
  • items — массив объектов, представляющих табы, каждый объект должен иметь уникальный ключ key и отображаемое имя name.

Для динамических табов массив items может меняться во время выполнения, что позволит добавлять и удалять вкладки.


Создание компонента TabList

Компонент TabList управляет фокусом и взаимодействием с клавиатурой. React Aria предоставляет хук useTabList:

import { useTabList } from "@react-aria/tabs";
import { useRef } from "react";

function TabListComponent({ state }) {
  const ref = useRef();
  const { tabListProps } = useTabList({ orientation: "horizontal" }, state, ref);

  return (
    <div {...tabListProps} ref={ref}>
      {state.collection.map(item => (
        <Tab key={item.key} item={item} state={state} />
      ))}
    </div>
  );
}
  • orientation задаёт направление табов: horizontal или vertical.
  • tabListProps содержит свойства для корректной работы с клавиатурой и фокусом.
  • state.collection автоматически обновляется при изменении списка табов.

Компонент Tab и управление фокусом

Каждая вкладка рендерится через компонент Tab с использованием хука useTab:

import { useTab } from "@react-aria/tabs";

function Tab({ item, state }) {
  const ref = useRef();
  const { tabProps } = useTab({ key: item.key }, state, ref);

  return (
    <button {...tabProps} ref={ref}>
      {item.name}
    </button>
  );
}
  • tabProps включает обработку событий клавиатуры, роль tab и атрибуты aria-selected.
  • ref используется для управления фокусом при навигации с клавиатуры.

React Aria автоматически обеспечивает правильную работу клавиш ArrowLeft, ArrowRight, Home и End, перемещая фокус между вкладками.


Панели контента

Для отображения содержимого вкладок используется компонент TabPanel с хуком useTabPanel:

import { useTabPanel } from "@react-aria/tabs";

function TabPanel({ children, item, state }) {
  const ref = useRef();
  const { tabPanelProps } = useTabPanel({ key: item.key }, state, ref);

  return (
    <div {...tabPanelProps} ref={ref}>
      {children}
    </div>
  );
}
  • tabPanelProps задаёт атрибуты role="tabpanel" и aria-labelledby, связывая панель с соответствующим табом.
  • React Aria скрывает неактивные панели с помощью hidden и поддерживает доступность для скринридеров.

Динамическое добавление и удаление табов

Для динамических интерфейсов список табов можно изменять через состояние React:

const [tabs, setTabs] = useState([
  { key: "tab1", name: "Первый" },
  { key: "tab2", name: "Второй" }
]);

const addTab = () => {
  const newKey = `tab${tabs.length + 1}`;
  setTabs([...tabs, { key: newKey, name: `Вкладка ${tabs.length + 1}` }]);
};

const removeTab = key => {
  setTabs(tabs.filter(tab => tab.key !== key));
};

После обновления tabs объект состояния useTabListState автоматически обновит коллекцию и отобразит новые табы.


Обеспечение доступности

React Aria гарантирует корректные ARIA-атрибуты:

  • role="tablist" для контейнера.
  • role="tab" для каждой вкладки.
  • aria-selected="true/false" у активного/неактивного таба.
  • aria-controls связывает таб с панелью.
  • role="tabpanel" у панели контента, связанной с табом через aria-labelledby.

Для динамических табов важно сохранять уникальные ключи key, чтобы состояние фокуса и активной вкладки корректно обновлялось при изменении списка.


Горизонтальная и вертикальная ориентация

React Aria поддерживает обе ориентации:

  • Горизонтальная (orientation: "horizontal") — стандартная раскладка табов сверху.
  • Вертикальная (orientation: "vertical") — вкладки располагаются слева или справа.

Компоненты автоматически адаптируют клавиши навигации под ориентацию (ArrowUp/ArrowDown для вертикальной ориентации).


Состояние фокуса и взаимодействие с клавиатурой

Библиотека обеспечивает управление фокусом, даже если табы добавляются динамически:

  • При удалении активного таба фокус перемещается на соседнюю вкладку.
  • Навигация стрелками обновляет фокус без изменения состояния выбранного таба, если это не задано явно.
  • Поддержка клавиш Home и End для быстрого перехода к первой и последней вкладке.

Интеграция с анимациями и стилями

React Aria не накладывает ограничения на визуальные эффекты:

  • Можно использовать framer-motion или CSS для плавной анимации смены панелей.
  • Для активной вкладки часто применяют стили border-bottom или background-color, связывая их с состоянием state.selectedKey.

Итоговая структура компонента

Полный динамический таб-лист выглядит следующим образом:

function DynamicTabs() {
  const [tabs, setTabs] = useState([{ key: "tab1", name: "Первый" }]);
  const state = useTabListState({ items: tabs });

  return (
    <div>
      <TabListComponent state={state} />
      {tabs.map(tab => (
        <TabPanel key={tab.key} item={tab} state={state}>
          Контент {tab.name}
        </TabPanel>
      ))}
      <button onCl ick={() => addTab()}>Добавить вкладку</button>
    </div>
  );
}

Такой подход обеспечивает:

  • Динамическое управление вкладками.
  • Полную доступность.
  • Правильное управление фокусом и клавиатурной навигацией.
  • Лёгкую интеграцию с анимациями и кастомными стилями.