Навигация стрелками

Библиотека React Aria предоставляет мощный набор хуков и утилит для создания доступных пользовательских интерфейсов в React. Навигация стрелками — один из ключевых аспектов, обеспечивающий удобство работы с компонентами списка, таблицы, меню или табов с клавиатуры. Она реализуется с помощью хуков useKeyboard, useFocusRing, useListState и компонентов из react-aria и react-stately.


Управление фокусом

Для реализации навигации стрелками необходимо контролировать фокус на элементе списка. В React Aria это делается через:

  • useFocusRing — определяет, находится ли элемент в фокусе, и позволяет корректно отображать индикатор фокуса.
  • useKeyboard — отслеживает нажатия клавиш и позволяет реагировать на стрелки вверх/вниз, влево/вправо.

Пример базового использования:

import {useFocusRing} from '@react-aria/focus';
import {useKeyboard} from '@react-aria/interactions';
import {useRef} from 'react';

function ListItem({item, onActivate}) {
  let ref = useRef();
  let {isFocusVisible, focusProps} = useFocusRing();

  let {keyboardProps} = useKeyboard({
    onKeyDown: (e) => {
      if (e.key === 'Enter' || e.key === ' ') {
        onActivate(item);
      }
    }
  });

  return (
    <div
      {...focusProps}
      {...keyboardProps}
      ref={ref}
      style={{outline: isFocusVisible ? '2px solid blue' : 'none'}}
    >
      {item.name}
    </div>
  );
}

Здесь useFocusRing управляет видимостью фокуса, а useKeyboard реагирует на нажатия клавиш Enter и Space для активации элемента.


Использование useListState для управления коллекцией

useListState из react-stately позволяет хранить состояние элементов коллекции и управлять выбором и фокусом.

import {useListState} from '@react-stately/list';

let state = useListState({items: data});

Состояние state содержит:

  • selectedKeys — выбранные элементы
  • focusedKey — текущий элемент в фокусе
  • Методы setSelectedKeys и setFocusedKey для управления состоянием

Используя эти методы, можно реализовать навигацию стрелками следующим образом:

function handleKeyDown(e) {
  if (e.key === 'ArrowDown') {
    let nextKey = state.collection.getKeyAfter(state.focusedKey);
    if (nextKey) state.setFocusedKey(nextKey);
  }
  if (e.key === 'ArrowUp') {
    let prevKey = state.collection.getKeyBefore(state.focusedKey);
    if (prevKey) state.setFocusedKey(prevKey);
  }
}

Функции getKeyAfter и getKeyBefore предоставляют безопасный способ перемещения фокуса по коллекции, учитывая вложенные группы и скрытые элементы.


Реализация меню с навигацией стрелками

Для меню часто используется комбинация хуков useMenu, useMenuItem и состояния useListState. Основная логика:

  1. Инициализация состояния меню:
let state = useListState({items: menuItems});
  1. Обработка клавиатуры:
function onKeyDown(e) {
  if (e.key === 'ArrowDown') {
    let nextKey = state.collection.getKeyAfter(state.focusedKey);
    if (nextKey) state.setFocusedKey(nextKey);
    e.preventDefault();
  }
  if (e.key === 'ArrowUp') {
    let prevKey = state.collection.getKeyBefore(state.focusedKey);
    if (prevKey) state.setFocusedKey(prevKey);
    e.preventDefault();
  }
  if (e.key === 'Enter') {
    state.selectionManager.select(state.focusedKey);
  }
}
  1. Привязка фокуса к элементу меню:
<MenuItem
  key={item.key}
  item={item}
  isFocused={state.focusedKey === item.key}
/>

MenuItem использует useFocusRing для отображения индикатора и useKeyboard для обработки действий по клавишам.


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

Для табов и горизонтальных списков стрелки влево и вправо применяются аналогично:

function onKeyDown(e) {
  if (e.key === 'ArrowRight') {
    let nextKey = state.collection.getKeyAfter(state.focusedKey);
    if (nextKey) state.setFocusedKey(nextKey);
    e.preventDefault();
  }
  if (e.key === 'ArrowLeft') {
    let prevKey = state.collection.getKeyBefore(state.focusedKey);
    if (prevKey) state.setFocusedKey(prevKey);
    e.preventDefault();
  }
}

Важно, что библиотека корректно обрабатывает циклический фокус при использовании collection.getKeyAfter и collection.getKeyBefore, что позволяет реализовать “кольцевую” навигацию без дополнительных проверок.


Управление доступностью

React Aria автоматически применяет необходимые ARIA-атрибуты для элементов с фокусом:

  • aria-selected для выбранного элемента
  • aria-activedescendant для контейнеров с управляемым фокусом
  • role="listbox" и role="option" для списков и элементов

Это гарантирует корректное взаимодействие с экранными читалками при навигации стрелками.


Комбинирование с другими хуками

Навигация стрелками часто комбинируется с:

  • useTypeSelect — позволяет выбирать элементы по вводу текста
  • useSelectableCollection — для управления множественным выбором
  • useOverlay — для меню, тултипов и всплывающих списков

Пример интеграции с useTypeSelect:

import {useTypeSelect} from '@react-aria/selection';

let {typeSelectProps} = useTypeSelect({keyboardDelegate: state.keyboardDelegate, selectionManager: state.selectionManager});

Это добавляет возможность быстро прыгать к элементам списка по первым буквам при сохранении полноценной навигации стрелками.


Рекомендации по производительности

  • Использовать key для элементов списка для корректного восстановления состояния при ререндере.
  • Ограничивать перерендер элементов списка с помощью React.memo.
  • Для больших коллекций использовать виртуализацию вместе с хуками React Aria, чтобы навигация стрелками оставалась плавной и отзывчивой.

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