nuxt / ui

A UI Library for Modern Web Apps, powered by Vue & Tailwind CSS.
https://ui.nuxt.com
MIT License
3.45k stars 383 forks source link

feat(Modal): open programmatically #1319

Closed noook closed 3 months ago

noook commented 3 months ago

๐Ÿ”— Linked issue

Resolves #285

โ“ Type of change

๐Ÿ“š Description

Following the discussion in #285, we'd like to display a modal programmatically.

๐Ÿ“ Checklist

vercel[bot] commented 3 months ago

The latest updates on your projects. Learn more about Vercel for Git โ†—๏ธŽ

Name Status Preview Updated (UTC)
ui โœ… Ready (Inspect) Visit Preview Feb 7, 2024 3:17pm
benjamincanac commented 3 months ago

Thanks for the initiative! ๐Ÿ˜Š The initial implementation looks good to me, I'd say this is the way to go.

noook commented 3 months ago

When calling reveal and passing props, should those props be reactive ? Meaning, if I pass a count ref in the props options, and that I keep updating it from the parent component, should the updated value be updated in the modal as well ? In the current implementation, the second option in reveal is the UModal props, combined with the component modal custom props https://github.com/nuxt/ui/blob/68e7de39f314aa420eeb911db8228d3d9fac31a5/playground/app.vue#L27-L42

If yes, how would you proceed to make the values reactive, and what would the API look like user-facing ?

benjamincanac commented 3 months ago

Just an idea but wouldn't it be easier to make those values reactive to pass the component and the props directly to useModal? The composable could then expose an open and close methods.

noook commented 3 months ago

I'm not sure, a very basic usecase would be to open a modal A or B given a branch in your code. And as a composable should be called during the setup function, you can't know at this moment what modal you would like to display.

Options could be passed to useModal, but maybe not the ModalComponent and its props

const modal = useModal()

if (success) {
  modal.open(ModalSuccess, data)
} else {
  modal.open(ModalError, error)
}
benjamincanac commented 3 months ago

Yeah my solution would require to instantiate useModal multiple times, it was just an idea. Maybe we can start like this and improve with reactivity later on?

noook commented 3 months ago

Demo of nested modals using useModal https://github.com/nuxt/ui/assets/19751938/57188536-bbde-4747-a5cf-73ee062b0519

benjamincanac commented 3 months ago

Amazing work, thanks a lot!! ๐ŸŽ‰๐Ÿ˜Š

benjamincanac commented 3 months ago

@noook I had to rename the Modals component to Modals.client.vue: https://github.com/nuxt/ui/commit/ade99a8f059bafa717e4c781d0abc3092c3099db to make it client-only otherwise I was having errors about modalState.component with modalState being null when refreshing the page. Let me know if this is the right fix, not sure about this!

noook commented 3 months ago

I made sure to have div as the default component for this reason. However it makes total sense to have it client only anyway.

yulafezmesi commented 3 months ago

hey @noook! look forward this for a long time, great job!

one more suggestion, can you add async/await support? like useConfirmDialog

also would be awesome if we open nested modals. currently it supports only one modal, right?

please let me know your thoughts, if look forward to contribute if agreed with me!

https://vueuse.org/core/useConfirmDialog/

noook commented 3 months ago

@yulafezmesi It does support nested modal. You have two ways of achieving it:

// app.vue
const modal = useModal()
modal.open(YourModal)

// YourModal.vue
modal.open(AnotherModal) // This will swap the component displayed, and will briefly close then reopen the modal

You can check the demo here: https://github.com/nuxt/ui/assets/19751938/57188536-bbde-4747-a5cf-73ee062b0519

The second way of achieving it is with the events listener:

// app.vue
const modal = useModal()
modal.open(YourModal, {
  // prop and emit types inferred from your component :)
  onConfirm() {
    modal.open(AnotherModal)
  }
})

// YourModal.vue
const props = defineProps<{
  confirm: []
}>()

const onConfirm = () => emit('confirm')

The second example demonstrates perfectly how you could handle a callback when clicking on a confirm button within your component. This mimics the behaviour of https://vueuse.org/core/useConfirmDialog/, except it does not use async/await.

If you really need to await something from the modal, I think you could wrap it like this:

const modal = useModal()

function openConfirmModal() {
  return new Promise((resolve, reject) => {
    modal.open(YourConfirmModal, {
      onConfirm: (eventArg) => resolve(eventArg),
      onClose: () => reject()
    })
  })
}

async function start() {
  try {
    await openConfirmModal()
    console.log('user did confirm')
  } catch {
    console.log('user did not confirm')
  }
}
yulafezmesi commented 3 months ago

@noook that you for the work around, totally make sense to async usage like this. however, really wondering swapping components causes losing state for previous component? I mean i can easly back to old component where the trigger second modal?

noook commented 3 months ago

The component should get destroyed. Also, the current implementation limits us to open a modal with unreactive props.

If saving the state is part of your feature, maybe the usage of a store should be relevant here

yulafezmesi commented 3 months ago

what i want to achieve by nested modals basically this:

https://github.com/nuxt/ui/assets/45617686/3752a294-66bd-4917-a63d-e58254e72d9e

noook commented 3 months ago

Oh this is not possible at all with the current implementation sorry. Earlier I meant chained instead of nested

yulafezmesi commented 3 months ago

wouldn't be nice if can get support this? i'm thinking we can use array of modalState. wondering your thoughts.

anthonyfranc commented 3 months ago

@noook -- Is there a way to close the modal from another component? modal.close(component) seems to forcefully close rather than fade like normal modal behavior.

noook commented 3 months ago

@anthonyfranc i need to check. I think the reason why this is happening is because I don't let elapse enough time before reseting the modal state's component

avinean commented 3 months ago

@noook nice feature. But the fact that nesting is impossible makes me sad. Do you plan to make nesting possible? I mean to make this possible https://github.com/nuxt/ui/pull/1319#issuecomment-1936506297

noook commented 3 months ago

@avinean Feel free to open an issue so we can discuss it there instead. As of now I don't have anything in mind regarding the implementation of this feature

anthonyfranc commented 2 months ago

@anthonyfranc i need to check. I think the reason why this is happening is because I don't let elapse enough time before reseting the modal state's component

Did you happen to figure out the issue with this?

professorhaseeb commented 2 weeks ago

@yulafezmesi It does support nested modal. You have two ways of achieving it:

// app.vue
const modal = useModal()
modal.open(YourModal)

// YourModal.vue
modal.open(AnotherModal) // This will swap the component displayed, and will briefly close then reopen the modal

You can check the demo here: https://github.com/nuxt/ui/assets/19751938/57188536-bbde-4747-a5cf-73ee062b0519

this example closes the parent modal, and opens then nested modal, and breaks the app.