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.refs для trigger и floating-элементов,
чтобы middleware мог правильно определить область клика.useDismiss с referencePress: false
и outsidePointerDown с кастомными фильтрами.open, чтобы избежать внезапного исчезновения
элементов.Dismiss-паттерны в Floating UI создают надежный, гибкий механизм управления скрытием всплывающих элементов, минимизируя ошибки UX и позволяя строить комплексные интерактивные интерфейсы.