В библиотеке Radix UI многие интерактивные
компоненты, такие как Tooltip, DropdownMenu,
Popover или HoverCard, поддерживают концепцию
автозакрытия. Это механизм, позволяющий компоненту
автоматически закрываться после определённого события или по истечении
заданного времени. Автозакрытие повышает удобство взаимодействия с
интерфейсом и предотвращает зависание открытых элементов на
странице.
Ключевым элементом реализации автозакрытия является таймер, управляемый через состояние компонента и соответствующие пропсы.
Radix UI предоставляет пропсы, через которые можно контролировать задержку закрытия компонента:
delayDuration – определяет время в
миллисекундах перед закрытием.disableOutsidePointerEvents –
блокирует события клика вне компонента до завершения задержки.Пример для Tooltip:
import * as Tooltip from '@radix-ui/react-tooltip';
<Tooltip.Root delayDuration={500}>
<Tooltip.Trigger>Наведи курсор</Tooltip.Trigger>
<Tooltip.Content>
Всплывающая подсказка закроется через 500 мс после ухода курсора
</Tooltip.Content>
</Tooltip.Root>
В этом примере таймер срабатывает после ухода указателя с триггера, отсчитывая 500 миллисекунд, прежде чем скрыть содержимое.
Для более точного контроля автозакрытия компоненты поддерживают функции обратного вызова:
onOpenChange – вызывается при
изменении состояния компонента.onCloseAuto – специфично для случаев
автозакрытия (в HoverCard или Popover).Это позволяет, например, запускать дополнительную логику при закрытии компонента через таймер:
<Popover.Root
onOpenCha nge={(isOpen) => {
if (!isOpen) {
console.log('Popover закрылся автоматически');
}
}}
>
<Popover.Trigger>Открыть Popover</Popover.Trigger>
<Popover.Content>
Контент Popover
</Popover.Content>
</Popover.Root>
Часто требуется менять время автозакрытия в зависимости от состояния приложения. Это можно реализовать с помощью состояния React:
import React, { useState } from 'react';
import * as Tooltip from '@radix-ui/react-tooltip';
function DynamicTooltip() {
const [delay, setDelay] = useState(300);
return (
<div>
<input
type="number"
value={delay}
onCha nge={(e) => setDelay(Number(e.target.value))}
placeholder="Установите задержку в мс"
/>
<Tooltip.Root delayDuration={delay}>
<Tooltip.Trigger>Наведи курсор</Tooltip.Trigger>
<Tooltip.Content>Таймер автозакрытия изменяемый</Tooltip.Content>
</Tooltip.Root>
</div>
);
}
Такой подход позволяет создавать адаптивные интерфейсы с разной динамикой закрытия всплывающих элементов.
Если на странице присутствуют несколько элементов с автозакрытием,
необходимо избегать конфликтов таймеров. Radix UI управляет этим через
внутренние хук-таймеры и подписки на события, но при кастомизации можно
использовать useEffect для управления несколькими
состояниями одновременно:
import { useEffect, useState } from 'react';
function MultiTooltipManager() {
const [openTooltip, setOpenTooltip] = useState(null);
useEffect(() => {
if (openTooltip !== null) {
const timer = setTimeout(() => setOpenTooltip(null), 400);
return () => clearTimeout(timer);
}
}, [openTooltip]);
return (
<>
<button onMouseEn ter={() => setOpenTooltip(1)}>Tooltip 1</button>
<button onMouseEn ter={() => setOpenTooltip(2)}>Tooltip 2</button>
</>
);
}
Таким образом, каждый таймер отслеживается отдельно, предотвращая случайное одновременное закрытие нескольких всплывающих элементов.
onPointerEnter) или
при повторном открытии.Пример интеграции с анимацией framer-motion:
import { motion } from 'framer-motion';
import * as Tooltip from '@radix-ui/react-tooltip';
<Tooltip.Root delayDuration={300}>
<Tooltip.Trigger>Hover me</Tooltip.Trigger>
<Tooltip.Content asChild>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
>
Контент с плавным автозакрытием
</motion.div>
</Tooltip.Content>
</Tooltip.Root>
Здесь таймер автозакрытия запускается одновременно с анимацией исчезновения, обеспечивая согласованное поведение интерфейса.
useState или кастомный хук.Эффективное применение таймеров автозакрытия повышает отзывчивость интерфейса и обеспечивает согласованное поведение интерактивных элементов Radix UI.