Dismiss паттерны

Dismiss-паттерны в Floating UI представляют собой механизмы, позволяющие управлять поведением всплывающих элементов (tooltip, dropdown, popover) при взаимодействии пользователя с интерфейсом. Основная цель этих паттернов — корректно закрывать или скрывать плавающие элементы при кликах вне их области, нажатии клавиш или других событиях, не нарушая UX.


События закрытия

1. Клик вне элемента Самый распространённый способ скрытия — реагирование на клик вне целевого контейнера. Floating UI предоставляет для этого модуль useDismiss или соответствующий middleware, который регистрирует глобальный слушатель click и проверяет, находится ли событие внутри элемента:

import { useFloating, useDismiss } from '@floating-ui/react';

function Dropdown() {
  const floating = useFloating({
    open: true,
    onOpenChange: (open) => console.log(open),
    middleware: [useDismiss()]
  });

  return (
    <div ref={floating.refs.setReference}>Открыть меню</div>
    <div ref={floating.refs.setFloating}>Содержимое меню</div>
  );
}

При клике вне floating функция автоматически вызовет onOpenChange(false), закрывая меню.

Особенность: useDismiss корректно работает с вложенными floating-элементами, предотвращая закрытие родителя, если клик произошёл на дочернем popover.


2. Escape и другие клавиши Dismiss-паттерн позволяет закрывать всплывающие элементы по клавишам клавиатуры. Обычно используется клавиша Escape, но конфигурация допускает добавление кастомных событий:

useDismiss({
  escapeKey: true,
  outsidePointerDown: true,
  referencePress: false
});
  • escapeKey: true — закрывает при нажатии Escape.
  • outsidePointerDown: true — закрывает при клике вне floating.
  • referencePress: false — не закрывает при клике по элементу-триггеру, что важно для управления меню с кнопкой.

Настройка поведения

1. Игнорирование кликов по определённым элементам Может потребоваться, чтобы клики по конкретным кнопкам внутри floating не закрывали меню. Для этого используется опция enabled или проверка в callback:

useDismiss({
  outsidePointerDown(event) {
    return !event.target.closest('.ignore-dismiss');
  }
});

Элемент с классом .ignore-dismiss теперь не вызывает закрытие floating.


2. Вложенные floating-элементы При создании popover с дочерними меню важно не закрывать родителя при взаимодействии с дочерним элементом. Floating UI учитывает это через передачу refs:

const parentDismiss = useDismiss({ outsidePointerDown: true });
const childDismiss = useDismiss({ outsidePointerDown: true, referencePress: false });

// Передача childRefs в родительский middleware
parentDismiss.refs.setFloating = childDismiss.refs.setFloating;

Таким образом, клики внутри дочернего floating не вызывают закрытие родителя.


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

При использовании CSS-анимаций важно задерживать фактическое удаление элемента после запуска закрывающей анимации. Dismiss-паттерн можно сочетать с state управления видимостью:

const [open, setOpen] = useState(false);
const { refs } = useFloating({ open, onOpenChange: setOpen, middleware: [useDismiss()] });

function handleClose() {
  // Запуск анимации закрытия
  element.classList.add('fade-out');
  setTimeout(() => setOpen(false), 200); // время совпадает с CSS-анимацией
}

Контроль и расширение

  • Пользовательские события: можно добавлять закрытие по другим триггерам, например, scroll, resize или blur.
  • Сочетание с useInteractions: Floating UI позволяет комбинировать useDismiss с другими hooks — useClick, useHover — для гибкого управления поведением floating.
  • Поддержка мобильных устройств: паттерн корректно работает с touch-событиями, предотвращая случайное закрытие при касании элементов.

Практические советы

  1. Всегда указывать refs для trigger и floating-элементов, чтобы middleware мог правильно определить область клика.
  2. Для сложных интерфейсов с несколькими уровнями меню использовать комбинацию useDismiss с referencePress: false и outsidePointerDown с кастомными фильтрами.
  3. Анимации закрытия лучше синхронизировать с delay перед изменением состояния open, чтобы избежать внезапного исчезновения элементов.

Dismiss-паттерны в Floating UI создают надежный, гибкий механизм управления скрытием всплывающих элементов, минимизируя ошибки UX и позволяя строить комплексные интерактивные интерфейсы.