В библиотеке Radix UI управление открытием и закрытием компонентов строится на контролируемом и неконтролируемом подходах. Основная цель — обеспечить полный контроль над состоянием UI, сохраняя при этом доступность и предсказуемое поведение компонентов.
Radix UI предоставляет два типа управления состоянием:
open и
событие onOpenChangeДля контролируемых компонентов ключевыми являются пропсы:
<Dialog open={isOpen} onOpenCha nge={setIsOpen}>
<DialogTrigger>Открыть диалог</DialogTrigger>
<DialogContent>Контент модального окна</DialogContent>
</Dialog>
open — булевое значение, определяющее,
открыт ли компонент.onOpenChange — функция обратного
вызова, вызываемая при попытке открыть или закрыть компонент. Она
принимает новое состояние (true для открытия,
false для закрытия).Таким образом, внешнее состояние isOpen полностью
управляет поведением компонента, а библиотека обеспечивает синхронизацию
внутреннего состояния и UI.
Неконтролируемое состояние удобно использовать для простых случаев, когда нет необходимости в синхронизации с внешними данными:
<DropdownMenu>
<DropdownMenuTrigger>Меню</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>Элемент 1</DropdownMenuItem>
<DropdownMenuItem>Элемент 2</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
В этом примере библиотека самостоятельно управляет открытием и закрытием меню при клике на триггер. Внутренняя логика учитывает фокусировку, клики вне элемента и клавиатурные события.
Radix UI автоматически обрабатывает стандартные сценарии закрытия компонентов:
onSelect.Пример контроля закрытия в DropdownMenu:
<DropdownMenuContent onOpenAutoFo cus={(event) => event.preventDefault()}>
<DropdownMenuItem onSel ect={() => console.log('Выбран элемент')}>
Элемент
</DropdownMenuItem>
</DropdownMenuContent>
Проп onOpenAutoFocus позволяет
предотвратить автоматическую фокусировку при открытии, что важно для
кастомных сценариев UX.
Триггеры (DialogTrigger,
DropdownMenuTrigger, PopoverTrigger) всегда
должны быть логически связаны с контентом, который открывается. В
контролируемом режиме связь осуществляется через open и
onOpenChange. В неконтролируемом — через контекст, который
Radix UI предоставляет автоматически.
<Popover open={isPopoverOpen} onOpenCha nge={setIsPopoverOpen}>
<PopoverTrigger>Открыть поповер</PopoverTrigger>
<PopoverContent>
Содержимое поповера
</PopoverContent>
</Popover>
Контролируемый подход гарантирует:
Radix UI не накладывает жестких ограничений на анимации. Компоненты
предоставляют состояния open и
closed, которые можно использовать в
комбинации с CSS или библиотеками анимации (например,
framer-motion):
<DialogContent
className={cn("dialog-content", isOpen ? "animate-in" : "animate-out")}
>
Контент
</DialogContent>
animate-in — класс для анимации
появления.animate-out — класс для анимации
закрытия.Это позволяет точно управлять поведением визуальных эффектов при изменении состояния компонента.
Для нескольких взаимосвязанных компонентов можно использовать глобальное состояние или контекст. Пример с открытием только одного меню из группы:
const [openMenu, setOpenMenu] = useState(null);
<DropdownMenu open={openMenu === 1} onOpenCha nge={(val) => setOpenMenu(val ? 1 : null)}>
<DropdownMenuTrigger>Меню 1</DropdownMenuTrigger>
<DropdownMenuContent>Элементы 1</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu open={openMenu === 2} onOpenCha nge={(val) => setOpenMenu(val ? 2 : null)}>
<DropdownMenuTrigger>Меню 2</DropdownMenuTrigger>
<DropdownMenuContent>Элементы 2</DropdownMenuContent>
</DropdownMenu>
Такой подход предотвращает одновременное открытие нескольких элементов и упрощает управление состоянием группы компонентов.
open,
onOpenChange) обеспечивает предсказуемость и интеграцию с
внешними логиками.