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

Programmatic Confirmations / Modals #337

Closed
acidjazz opened this issue Jun 23, 2023 · 11 comments
Closed

Programmatic Confirmations / Modals #337

acidjazz opened this issue Jun 23, 2023 · 11 comments
Labels
enhancement New feature or request

Comments

@acidjazz
Copy link
Contributor

acidjazz commented Jun 23, 2023

Not sure if my strategy is ideal, but maybe sharing what I have so far might be useful in having something like this in the UI

Usage:

const confirm = (sshkey: models.SSHKey) =>
  useConfirm().confirm(
    'Are you sure you want to delete this SSH Key?',
    'This action cannot be undone.  This will permanently delete this SSH key.',
    'Delete SSH Key', () => revoke(sshkey))
image

Component (this would go in app.vue similar to notifications):

<script lang="ts" setup>
import type { NotificationAction } from '@nuxthq/ui/dist/runtime/types'
const { confirming, params } = useConfirm()
const actions:NotificationAction[] = [
  {
    label: params.value.label,
    color: 'red',
    click: async () => {
      confirming.value = false
      await useUtils().sleep(200)
      params.value.action()
    },
  },
  {
    label: 'Cancel',
    color: 'white',
    click: () => confirming.value = false,
  },
]
</script>

<template>
  <u-modal v-model="confirming">
    <u-notification
      v-if="confirming"
      id="confirm"
      color="red"
      icon="i-mdi-alert-circle-outline"
      :title="params.title"
      :description="params.message"
      :timeout="0"
      :actions="actions"
    />
  </u-modal>
</template>

Composable:

const confirming = ref<boolean>(false)

interface ConfirmParams {
  title: string
  message: string
  label: string
  action: Function
}

const params = ref<ConfirmParams>({
  title: 'Title',
  message: 'Description',
  label: 'Confirm',
  action: () => {},
})

export const useConfirm = () => {
  function confirm (title: string, message: string, label: string, action: Function) {
    params.value = { title, message, label, action }
    confirming.value = true
  }

  return {
    confirm,
    confirming,
    params,
  }
}

related to #285

@acidjazz acidjazz added the enhancement New feature or request label Jun 23, 2023
@acidjazz
Copy link
Contributor Author

really diggin this look, similar to stripe:

image image

usage

const confirm = (token: models.Token) =>
  useConfirm().confirm(
    'Delete Token',
    'This action cannot be undone.  This will permanently delete this token and invalidate any environments using it.',
    'Delete Token', () => revoke(token))

composable

interface ConfirmParams {
  title: string
  message: string
  label: string
  action: Function
}

const confirming = ref<boolean>(false)

const params = ref<ConfirmParams>({
  title: 'Title',
  message: 'Description',
  label: 'Confirm',
  action: () => {},
})

export const useConfirm = () => {
  function confirm (title: string, message: string, label: string, action: Function) {
    params.value = { title, message, label, action }
    confirming.value = true
  }

  return { confirm, confirming, params }
}

component for app.vue

<script lang="ts" setup>
const { confirming, params } = useConfirm()
</script>

<template>
  <u-modal v-model="confirming" :ui="{ width: 'sm:max-w-sm' }">
    <u-card>
      <div class="font-semibold mb-2">
        {{ params.title }}
      </div>
      <div>
        {{ params.message }}
      </div>
      <template #footer>
        <div class="flex justify-end space-x-2">
          <u-button color="white" @click="confirming = false"> Cancel </u-button>
          <u-button color="red" variant="solid" @click="params.action"> {{ params.label }}</u-button>
        </div>
      </template>
    </u-card>
  </u-modal>
</template>

@clopezpro
Copy link
Contributor

"headlessui" does not have multimodal support, when siblings it requires it to be parent and child, if placed global, closing the Confirm modal closes the others

Copy link
Member

You can already achieve this with VueUse useConfirmDialog, here is an example:

https://stackblitz.com/edit/github-tcbigg-wntk1z?file=app.vue

@moshetanzer
Copy link
Collaborator

Hi,

Any update on this? Is this planned? The creation of UModal programmatically?

Thanks

@benjamincanac
Copy link
Member

This has been implemented in #1319.

@some-user123
Copy link

I don't fully get, how useModal/UModal would help me here. How would I return a value from the modal?

I would like to have a programmatic method to show a yes/no dialog, e.g.

const confirmed = await YesNoDialog.show('Are you sure?', { /* more props */ })
if (!confirmed) {
  return
}
// ...

If this is possible with useModal, maybe it would be a good idea to add an example to the docs? Seems like a common use case...

@clopezpro
Copy link
Contributor

I don't fully get, how useModal/UModal would help me here. How would I return a value from the modal?

I would like to have a programmatic method to show a yes/no dialog, e.g.

const confirmed = await YesNoDialog.show('Are you sure?', { /* more props */ })
if (!confirmed) {
  return
}
// ...

If this is possible with useModal, maybe it would be a good idea to add an example to the docs? Seems like a common use case...

an example
Image

Image

@some-user123
Copy link

Many thanks!

Would you mind showing what your ModalConfirm component looks like?

@clopezpro
Copy link
Contributor

Many thanks!

Would you mind showing what your ModalConfirm component looks like?

<script lang="ts" setup>
import type { InputSize } from '#ui/types'

const props = defineProps({
  title: {
    type: String,
    default: '',
  },
  message: {
    type: String,
    default: '',
  },
  inputResult: {
    type: Object as PropType< {
      type: string
      placeholder: string
      size: InputSize
      value: string
    } >,
    required: false,
  },
  onConfirm: {
    type: Function,
    default: () => {},
  },
  onCancel: {
    type: Function,
    default: () => {},
  },
  loading: {
    type: Boolean,
    default: false,
  },
  icon: {
    type: String,
    default: 'i-carbon-question-answering',

  },
})

function closeModal() {
  props.onCancel()
}
const pending = ref(false)
const msj = ref('')
async function confirmOK() {
  props.onConfirm(msj.value)
}
</script>

<template>
  <UModal
    id="modalConfirm"
    :ui="{
      wrapper: 'relative z-50',
      inner: 'fixed inset-0 overflow-y-auto z-60',
    }"
  >
    <div class="p-1">
      <div class="border-b dark:border-gray-800">
        {{ title }}
      </div>
      <div class="font-sans text-lg">
        {{ message }}
      </div>

      <UInput
        v-show="inputResult"
        id="refInput" v-model="msj" :ui="{
          wrapper: 'relative z-[80]',
        }"
        size="xl"
        autofocus
        :type="inputResult?.type"
        :placeholder="inputResult?.placeholder"
      />

      <div>
        <div class="flex justify-center p-2 gap-x-2 items-center">
          <UButton :loading="pending" icon="i-carbon-checkmark" color="primary" variant="solid" @click="confirmOK" @touchstart="confirmOK">
            SI
          </UButton>
          Ò
          <UButton icon="i-carbon-close" color="red" variant="solid" @click="closeModal" @touchstart="closeModal">
            NO
          </UButton>
        </div>
      </div>
    </div>
  </UModal>
</template>
`

@some-user123
Copy link

some-user123 commented Oct 30, 2024

Got it, many thanks!

Is there a way to handle the case if the modal is closed (without pressing yes or no) but e.g., pressing ESC?

PS: Or disable closing on ESC and handle the keystroke manually...

@noook
Copy link
Collaborator

noook commented Oct 30, 2024

@some-user123 The modal emits 2 events related to the closing: 'close' and 'close-prevented'. Close is emitted when the modal is closed, regardless of how it was closed. If you want to disable closing it with escape, you can use the prevent-close prop, this will make the modal emit the 'close-prevented' whenever a user tries to close the modal (it doesn't close it though).

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

6 participants