Управление открытием и закрытием

В библиотеке Radix UI управление открытием и закрытием компонентов строится на контролируемом и неконтролируемом подходах. Основная цель — обеспечить полный контроль над состоянием UI, сохраняя при этом доступность и предсказуемое поведение компонентов.

Radix UI предоставляет два типа управления состоянием:

  1. Неконтролируемое состояние — компонент самостоятельно управляет своим открытием и закрытием.
  2. Контролируемое состояние — состояние передается извне через пропсы, что позволяет интегрировать компонент с глобальными состояниями или логикой приложения.

Проп 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 автоматически обрабатывает стандартные сценарии закрытия компонентов:

  • Клик вне компонента — срабатывает для диалогов, меню, поповеров.
  • Нажатие клавиши Escape — закрывает активный компонент.
  • Выбор элемента — в меню или списках, при необходимости можно предотвратить закрытие через проп 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) обеспечивает предсказуемость и интеграцию с внешними логиками.
  • Неконтролируемое состояние удобно для простых сценариев, позволяя библиотеке управлять открытием/закрытием.
  • Radix UI обрабатывает клики вне компонента, клавишу Escape и выбор элементов, что улучшает UX.
  • Использование триггеров и контента в связке гарантирует правильную работу фокуса и доступности.
  • Поддержка кастомных анимаций позволяет создавать плавные переходы при открытии и закрытии.
  • Для группы взаимосвязанных компонентов рекомендуется использовать внешнее состояние или контекст для согласованного поведения.