Не закрывайте диалоговое окно (модальное) при нажатии снаружи в React & HeadlessUI

Моя проблема проста ... Я использую компонент HeadlessUI Dialog для React в своем приложении, и когда я выхожу из модального окна, я хочу, чтобы он не закрывался. В документации есть диалог. Параметр наложения, который имеет дело с этим взаимодействием, но нет настроек для его отключения.

Какие-нибудь решения? Это ссылка на документы HeadlessUI компонента, который я использую: https://headlessui.dev/react/dialog

Может быть, вы знали, что такое "модальное окно блокировки предупреждений" для React ??

12 ответов

Вы можете удалить onClose={closeModal} из Dialog и вместо этого передайте closeModal в обработчик onClick любой кнопки в примере из предоставленной вами ссылки:

      <Dialog
  as="div"
  className="fixed inset-0 z-10 overflow-y-auto" // removed onClose
>

Вместо этого передайте его кнопке закрытия внутри диалогового окна.

      <button onClick={handleClose}>Close</button>

Добавьте возвращаемый пустой объект в функцию onClose диалогового интерфейса. вот мой код:

       <Dialog
    initialFocus={completeButtonRef}
    as='div'
    className='fixed inset-0 z-10 overflow-y-auto'
    onClose={() => {}}
    open={false}
  >

добавлять pointer-events: noneв Диалог.Оверлей.

Это можно сделать, добавив pointer-events-noneучебный класс

https://github.com/tailwindlabs/headlessui/issues/621#issuecomment-867161749

Если вы также хотите отключить закрытие модального окна наEscapeнажатие клавиши:

Передайте функцию "noop" вonCloseсвойство, как было предложено другими в этой теме.

      <Dialog
  ...
  onClose={() => {}}
  // or onClose={noop} if you have lodash installed or a noop function helper

Если вы хотите, чтобы клавиша escape была отключена, выполните следующие действия :

Просто не используйDialog.Panelвнутри вашего модального/диалогового компонента.

      <Dialog
  ...
  onClose={onClose}>
  <div> // <-- use a regular div as your PANEL
      onClose={() => null }>

вы также можете вернуть null, это предотвращает внешний щелчок!

Мне нужно было только установить для onClose значение null. Добавление статического свойства предотвратило работу пользовательских событий закрытия.

      <Dialog
  static
  onClose={() => null}>
</Dialog>

У меня была такая же проблема с https://headlessui.dev/react/dialog ReactJS и Typescript

Я исправил передачу нового свойства с именем onClose(value: boolean): void; где я могу обрабатывать состояние вне компонента onClose = {() => {setIsOpen(false)}} и в диалоговом окне вызывая опору onClose = {onClose}

Часть 1

         <Transition appear show={showModal} as={Fragment}>
        <Dialog
          as="div"
          className={clsx("fixed inset-0 z-50 overflow-y-auto", 
          className)}
          onClose={onClose}
        > 

....

Часть 2 enter code here

         <Modal
        {...args}
        showModal={isOpen}
        onClose={() => {setIsOpen(false) }}
      />

Вам придется пройти.

Искатьstaticна этой странице

Затем вам нужно будет обрабатывать открытие и закрытие.

Я хотел сохранить логику доступности, которую добавляет HeadlessUI (например, Esc для закрытия), а также добавить кнопки в том же переходе, но за пределами диалоговой области. Для этого я добавилhandleCloseлогику как к прозрачному фону, так и к новой оболочке моего фактического содержимого диалога, а затем использовалevent.stopPropagation()при любых щелчках внутри диалогового окна. В Vue с дополнительной логикой TransitionRoot мой код примерно выглядит так:

      <TransitionRoot :show="isOpen">
  <Dialog @close="handleClose">
    <TransitionChild>
      <div aria-hidden="true" class="fixed inset-0 bg-black/70"
           @click="handleClose" />
      <!-- ... a few <button @click="..." class="fixed z-20 ..."> items ... -->
    </TransitionChild>
    <div class="fixed inset-0 h-screen ..."
         @click="handleClose">
      <div class="flex items-center justify-center ...">
        <TransitionChild as="div" class="rounded-lg bg-white ..."
                         @click.stop>
          <!-- @click.stop is equivalent to onClick(e) => e.stopPropagation() -->
          <!-- ... add dialog body in here ... -->
        </TransitionChild>
      </div>
    </div>
  </Dialog>
</TransitionRoot>

У меня сработало следующее, я буду использовать пример кода из предоставленной вами ссылки.

Пример кода:

      import { Dialog, Transition } from '@headlessui/react'
import { Fragment, useState } from 'react'

export default function MyModal() {
  let [isOpen, setIsOpen] = useState(true)

  function closeModal() {
    setIsOpen(false)
  }

  function openModal() {
    setIsOpen(true)
  }

  return (
    <>
      <div className="fixed inset-0 flex items-center justify-center">
        <button
          type="button"
          onClick={openModal}
          className="rounded-md bg-black bg-opacity-20 px-4 py-2 text-sm font-medium text-white hover:bg-opacity-30 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75"
        >
          Open dialog
        </button>
      </div>

      <Transition appear show={isOpen} as={Fragment}>
        <Dialog as="div" className="relative z-10" onClose={closeModal}>
          <Transition.Child
            as={Fragment}
            enter="ease-out duration-300"
            enterFrom="opacity-0"
            enterTo="opacity-100"
            leave="ease-in duration-200"
            leaveFrom="opacity-100"
            leaveTo="opacity-0"
          >
            <div className="fixed inset-0 bg-black bg-opacity-25" />
          </Transition.Child>

          <div className="fixed inset-0 overflow-y-auto">
            <div className="flex min-h-full items-center justify-center p-4 text-center">
              <Transition.Child
                as={Fragment}
                enter="ease-out duration-300"
                enterFrom="opacity-0 scale-95"
                enterTo="opacity-100 scale-100"
                leave="ease-in duration-200"
                leaveFrom="opacity-100 scale-100"
                leaveTo="opacity-0 scale-95"
              >
                <Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all">
                  <Dialog.Title
                    as="h3"
                    className="text-lg font-medium leading-6 text-gray-900"
                  >
                    Payment successful
                  </Dialog.Title>
                  <div className="mt-2">
                    <p className="text-sm text-gray-500">
                      Your payment has been successfully submitted. We’ve sent
                      you an email with all of the details of your order.
                    </p>
                  </div>

                  <div className="mt-4">
                    <button
                      type="button"
                      className="inline-flex justify-center rounded-md border border-transparent bg-blue-100 px-4 py-2 text-sm font-medium text-blue-900 hover:bg-blue-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
                      onClick={closeModal}
                    >
                      Got it, thanks!
                    </button>
                  </div>
                </Dialog.Panel>
              </Transition.Child>
            </div>
          </div>
        </Dialog>
      </Transition>
    </>
  )
}

Мои изменения:

      import { Dialog, Transition } from '@headlessui/react'
import { Fragment, useState } from 'react'

export default function MyModal({ closeOnClickingOutside }) {
  let [isOpen, setIsOpen] = useState(true)
    
    const Overlay = closeOnClickingOutside ? 'div' : Dialog.Panel;
    const Content = closeOnClickingOutside ? Dialog.Panel : 'div';

  function closeModal() {
    setIsOpen(false)
  }

  function openModal() {
    setIsOpen(true)
  }

  return (
    <>
      <div className="fixed inset-0 flex items-center justify-center">
        <button
          type="button"
          onClick={openModal}
          className="rounded-md bg-black bg-opacity-20 px-4 py-2 text-sm font-medium text-white hover:bg-opacity-30 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75"
        >
          Open dialog
        </button>
      </div>

      <Transition appear show={isOpen} as={Fragment}>
        <Dialog as="div" className="relative z-10" onClose={closeModal}>
          <Transition.Child
            as={Fragment}
            enter="ease-out duration-300"
            enterFrom="opacity-0"
            enterTo="opacity-100"
            leave="ease-in duration-200"
            leaveFrom="opacity-100"
            leaveTo="opacity-0"
          >
            <div className="fixed inset-0 bg-black bg-opacity-25" />
          </Transition.Child>

          <Overlay className="fixed inset-0 overflow-y-auto">
            <div className="flex min-h-full items-center justify-center p-4 text-center">
              <Transition.Child
                as={Fragment}
                enter="ease-out duration-300"
                enterFrom="opacity-0 scale-95"
                enterTo="opacity-100 scale-100"
                leave="ease-in duration-200"
                leaveFrom="opacity-100 scale-100"
                leaveTo="opacity-0 scale-95"
              >
                <Content className="w-full max-w-md transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all">
                  <Dialog.Title
                    as="h3"
                    className="text-lg font-medium leading-6 text-gray-900"
                  >
                    Payment successful
                  </Dialog.Title>
                  <div className="mt-2">
                    <p className="text-sm text-gray-500">
                      Your payment has been successfully submitted. We’ve sent
                      you an email with all of the details of your order.
                    </p>
                  </div>

                  <div className="mt-4">
                    <button
                      type="button"
                      className="inline-flex justify-center rounded-md border border-transparent bg-blue-100 px-4 py-2 text-sm font-medium text-blue-900 hover:bg-blue-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
                      onClick={closeModal}
                    >
                      Got it, thanks!
                    </button>
                  </div>
                </Content>
              </Transition.Child>
            </div>
          </div>
        </Dialog>
      </Transition>
    </>
  )
}
``

измените функцию handleClose на это:

         const handleClose = (event, reason) => {
    if (reason !== "backdropClick") {
      setOpen(false)
    }
  }

В обновлении heandlessui 1.16 компонент Overlay был представлен только в Dialog.Panel, поэтому он выполнялся непосредственно в Dialog для интерпретации вызова onClose по щелчку мыши из Dialog.Panel.

Как оказалось:

      <Dialog
                    as="div"
                    className="relative z-10"
                    onClose={() => {
                        if (event instanceof PointerEvent === false) {
                            closeModal()
                        }
                    }}
                >
Другие вопросы по тегам