Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent dialog from closing #5

Open
alexambrinos opened this issue Apr 9, 2024 · 8 comments
Open

Prevent dialog from closing #5

alexambrinos opened this issue Apr 9, 2024 · 8 comments
Labels
enhancement New feature or request

Comments

@alexambrinos
Copy link

Hi, thanks for this amazing library! I was trying to convert my app to using this library for handling the modal state but inevitable I have hit a barrier.

I was trying to stack a dialog with an alert dialog from radix ui. I wanted to prevent closing the dialog when trying to close it with a dirty form inside it and show the alert dialog instead. But I can't do that since there's no access to the controlled state of the modal (specifically the onOpenChange of the Root)

If someone can find a way to implement this kind of flow without modifications to the library I would love to know. The only thing that comes to my mind would be to manage the "closable" state in the StateItem like it's being done with the closedAt property. And than pass a "setClosable" it in the Component so it can be changed within the modal.

@lindesvard
Copy link
Owner

Hey! Thanks for trying it out 🥳 Hmm this should be possible with todays solution.

Just use onPointerDownOutside inside your modal and preventDefault + push a confirm modal when ever the form is dirty.

const ModalExample: () => {
const [isDirty, setDirty] = useState(false);
return (
  <DialogContent
    onPointerDownOutside={(e) => {
      if (isDirty) {
        e.preventDefault();
        pushModal('Dynamic');
      } else {
        // Do something else?
      }
    }}
  >
    <button className="bg-black text-white p-2" onClick={() => setDirty((p) => !p)}>
      {isDirty ? 'Dirty' : 'Not Dirty'}
    </button>
  </DialogContent>
);
Skarminspelning.2024-04-10.kl.06.27.43.mov

@alexambrinos
Copy link
Author

alexambrinos commented Apr 10, 2024

Oh great, thanks for your input! So implementing a prevent close function in onPointerDownOutside, along with onEscapeKeyDown and passing it to the DialogPrimitive.Close button should cover all the possible ways to exit of the dialog/sheet, so it's being managed outside of the library.
I guess I'll implement it this way for now. The only bad thing I can see is that developers need to pay attention to covering all the possible ways of exiting the dialog.

Edit: Also I was trying to popAllModals() within the "exit" button in alert dialog so it closes every dialog opened, but the library doesn't notify the emitter with 'change' like in the popModal(). I think the emitter should notify all the dialog closed so the callback can be called for each. So then the useOnPushModal that I have defined in the dialog is not being called.

emitter.emit('change', {
              name: match.name,
              open: false,
              props: match.props,
});

@lindesvard
Copy link
Owner

I will fix the event thingy.

Regarding disable close. There's a lot of different ways to do this. I will play around and see what the best solution would be.

@lindesvard lindesvard added the enhancement New feature or request label Apr 11, 2024
@digitdustin
Copy link

Also could use this 🙌

@digitdustin
Copy link

@alexambrinos I managed to get around this by using the AlertDialog as a wrapping component.

import { AlertDialog } from '@/components/ui/alert-dialog';

export const {
  pushModal,
  popModal,
  ...
} = createPushModal({
  modals: {
    ...
    AlertModal: {
      //@ts-ignore
      Wrapper: AlertDialog,
      Component: AlertModal,
    },
  },
});

The modal doesn't close on interaction and you can still remove it by using popModal. TS isn't happy but oh well

@alexambrinos
Copy link
Author

@digitdustin Great that you found something that works for you! If you always want to have the dialog not closable it's a great ideea.
The setup with the AlertDialog as a wrapper is literally the opposite of the Dialog Wrapper, meaning if I wanted to close the modal with "esc" key for example, I would need to write specific code to pop the modal. I needed something to manage the closable state based on something in the dialog (my specific need was a dirty form) and switch between closable and not closable.
I have a add customer form which is defined inside a Sheet (chose the sheet because I want it to be accessible in multiple parts of the app without interrupting the main activity). I wanted to alert the user if it closes the sheet by mistake.
Maybe I have a very specific edge case so I hope more people would benefit from this change than just me. 😅

@lindesvard
Copy link
Owner

Hey, have not had time to look into this. Will try to find time this week!

@JJozef
Copy link

JJozef commented May 9, 2024

Important

  • I'm using Next.js and shadcn
  • This when using the dynamic Dialog and Drawer.

  • I think it is necessary for pushmodal, to handle this internally either with a new prop, to handle Dialog and Drawer opening/closing events, e.g:
export default function CreateDialog() {
  const [isLoading, startTransition] = useTransition()
  
  const onSubmit = (values) => {
    startTransition(async () => {
     // server action
    })
  }

  return (
    <Dynamic.Content
      // to avoid this
      onPointerDownOutside={(e) => {}}
      onEscapeKeyDown={(e) => {}} 
      
      // new prop e.g:
      closseable={isLoading} // and this disable the option to close the dialog and drawer, until the server action is finished.
    >
      {/* form */}
   </Dynamic.Content>
  )
}
// per default

<Dialog open={} onOpenChange={}>
      {/* content */}
</Dialog>

  • To hide the close/hide buttons Dialog and Drawer, just add a new prop to each, for example: “hiddenClose”.

Warning

You need to add this prop in both, otherwise you will get an error.

// dialog.{jsx,tsx,js}

const DialogContent = forwardRef(
  ({ className, children, hiddenClose = false, ...props }, ref) => (
    // ...
        {!hiddenClose && (
          <DialogPrimitive.Close className='absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity data-[state=open]:bg-accent data-[state=open]:text-muted-foreground hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none'>
            <Cross2Icon className='size-4' />
            <span className='sr-only'>Close</span>
          </DialogPrimitive.Close>
        )}
    // ...
  )
)
// drawer.{jsx,tsx,js}

const DrawerContent = forwardRef(
  ({ className, children, hiddenClose = false, ...props }, ref) => (
    // ...
        {!hiddenClose && (
          <div className='mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted' />
        )}
    // ...
  )
)
// component.{jsx,tsx,js}

export default function CreateDialog() {
  const [isLoading, startTransition] = useTransition()
  
  const onSubmit = (values) => {
    startTransition(async () => {
     // server action
    })
  }

  return (
    <Dynamic.Content
      hiddenClose={isLoading}  // automatically hides when the form is submitted
    >
      {/* form */}
    </Dynamic.Content>
  )
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

4 participants