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

feat(Modal): open programmatically #1319

Merged
merged 19 commits into from
Feb 7, 2024
Merged

feat(Modal): open programmatically #1319

merged 19 commits into from
Feb 7, 2024

Conversation

noook
Copy link
Collaborator

@noook noook commented Feb 5, 2024

πŸ”— Linked issue

Resolves #285

❓ Type of change

  • πŸ“– Documentation (updates to the documentation or readme)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • πŸ‘Œ Enhancement (improving an existing functionality)
  • ✨ New feature (a non-breaking change that adds functionality)
  • 🧹 Chore (updates to the build process or auxiliary tools and libraries)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

πŸ“š Description

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

  • Create a useModal composable to control the displaying of a modal from a component
  • Inject the modal state within the app with an injection key (stores the component to display, and a reference to its props)
  • Create a ModalsContainer to include at the root-level of the app

πŸ“ Checklist

  • I have linked an issue or discussion.
  • I have updated the documentation accordingly.

Copy link

vercel bot commented Feb 5, 2024

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
Copy link
Member

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

@noook
Copy link
Collaborator Author

noook commented Feb 6, 2024

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

ui/playground/app.vue

Lines 27 to 42 in 68e7de3

function reveal () {
// question: do we want the props to be reactive here ?
modal.reveal(Test, {
// UModal props
fullscreen: true,
// Test component props
// @ts-ignore
// count // Warning, this is an object and not a number. Reactive.
count: count.value // Ok, but not reactive.
})
setTimeout(() => {
modal.patch({
fullscreen: false
})
}, 3000)
}

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

@benjamincanac benjamincanac changed the title feat: programmatically open a modal feat(Modal): open programmatically Feb 6, 2024
@benjamincanac
Copy link
Member

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
Copy link
Collaborator Author

noook commented Feb 6, 2024

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)
}

Copy link
Member

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
Copy link
Collaborator Author

noook commented Feb 7, 2024

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

@noook noook marked this pull request as ready for review February 7, 2024 14:00
@noook noook marked this pull request as draft February 7, 2024 14:00
playground/app.vue Outdated Show resolved Hide resolved
@benjamincanac benjamincanac merged commit 6f29c62 into nuxt:dev Feb 7, 2024
1 of 2 checks passed
@benjamincanac
Copy link
Member

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

@benjamincanac
Copy link
Member

@noook I had to rename the Modals component to Modals.client.vue: ade99a8 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
Copy link
Collaborator Author

noook commented Feb 7, 2024

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
Copy link

yulafezmesi commented Feb 9, 2024

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
Copy link
Collaborator Author

noook commented Feb 9, 2024

@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
Copy link

@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
Copy link
Collaborator Author

noook commented Feb 9, 2024

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
Copy link

what i want to achieve by nested modals basically this:

Screen.Recording.2024-02-09.at.23.11.18.mov

@noook
Copy link
Collaborator Author

noook commented Feb 9, 2024

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

@yulafezmesi
Copy link

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

@anthonyfranc
Copy link

anthonyfranc commented Feb 22, 2024

@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
Copy link
Collaborator Author

noook commented Feb 22, 2024

@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
Copy link

avinean commented Feb 23, 2024

@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 #1319 (comment)

@noook
Copy link
Collaborator Author

noook commented Feb 23, 2024

@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
Copy link

@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
Copy link

@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.

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

Successfully merging this pull request may close these issues.

Programmatic modal
6 participants