feat(ui): modal template for confirming dangerous actions
This commit is contained in:
parent
66239b00a3
commit
0695d7e913
|
@ -18,8 +18,8 @@ const { t } = useI18n()
|
|||
|
||||
const emit = defineEmits<Events>()
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
action: () => undefined,
|
||||
disabled: false,
|
||||
action: () => undefined, // -> Button.onClick prop
|
||||
disabled: false, // -> Pure html/css. Just add attribute `disabled` and the button is inert.
|
||||
confirmColor: 'destructive'
|
||||
})
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, computed, useSlots } from 'vue'
|
||||
import { ref, computed, useSlots, onMounted } from 'vue'
|
||||
import { useColor, type ColorProps } from '~/composables/colors'
|
||||
|
||||
import Loader from '~/components/ui/Loader.vue'
|
||||
|
@ -17,6 +17,8 @@ interface Props {
|
|||
icon?: string
|
||||
|
||||
onClick?: (...args: any[]) => void | Promise<void>
|
||||
|
||||
autofocus? : boolean
|
||||
}
|
||||
|
||||
const props = defineProps<Props & ColorProps>()
|
||||
|
@ -29,6 +31,10 @@ const iconOnly = computed(() => !!props.icon && !slots.default)
|
|||
const internalLoader = ref(false)
|
||||
const isLoading = computed(() => props.isLoading || internalLoader.value)
|
||||
|
||||
const button = ref()
|
||||
|
||||
console.log("props.autofocus", props.autofocus)
|
||||
|
||||
const click = async (...args: any[]) => {
|
||||
internalLoader.value = true
|
||||
|
||||
|
@ -38,10 +44,13 @@ const click = async (...args: any[]) => {
|
|||
internalLoader.value = false
|
||||
}
|
||||
}
|
||||
onMounted(() => {
|
||||
if (props.autofocus) button.value.focus();
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button class="funkwhale is-colored button" :class="[
|
||||
<button ref="button" class="funkwhale is-colored button" :class="[
|
||||
color,
|
||||
'is-' + (variant ?? 'solid'),
|
||||
'is-' + (width ?? 'standard'),
|
||||
|
|
|
@ -3,6 +3,11 @@ import Button from '~/components/ui/Button.vue'
|
|||
|
||||
const { title } = defineProps<{ title:string }>()
|
||||
const isOpen = defineModel<boolean>({ required: true })
|
||||
|
||||
// TODO:
|
||||
// - [ ] Trap focus while open
|
||||
// - [ ] Close on ESC (Implement a global stack of closable/focusable elements)
|
||||
// - [ ] Add aria role to inform screenreaders
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -18,6 +18,10 @@ const { Layout } = DefaultTheme
|
|||
font-size: 14px;
|
||||
}
|
||||
|
||||
.language-template:has(~.preview){
|
||||
flex-grow:1000;
|
||||
}
|
||||
|
||||
.preview,
|
||||
.vp-doc .preview {
|
||||
padding: 16px 0;
|
||||
|
|
|
@ -4,11 +4,12 @@ import { ref, watchEffect } from 'vue'
|
|||
import Alert from '~/components/ui/Alert.vue'
|
||||
import Button from '~/components/ui/Button.vue'
|
||||
import Modal from '~/components/ui/Modal.vue'
|
||||
import Layout from '~/components/ui/Layout.vue'
|
||||
|
||||
const isOpen = ref(false)
|
||||
const isOpen2 = ref(false)
|
||||
|
||||
const isOpen3 = ref(false)
|
||||
|
||||
const alertOpen = ref(true)
|
||||
watchEffect(() => {
|
||||
if (isOpen3.value === false) {
|
||||
|
@ -18,6 +19,7 @@ watchEffect(() => {
|
|||
|
||||
const isOpen4 = ref(false)
|
||||
const isOpen5 = ref(false)
|
||||
const isOpen6 = ref(false)
|
||||
|
||||
</script>
|
||||
|
||||
|
@ -28,7 +30,7 @@ const isOpen5 = ref(false)
|
|||
| `title` | `string` | Yes | | The modal title |
|
||||
| `v-model` | `true` \| `false` | Yes | | Whether the modal is isOpen or not |
|
||||
|
||||
## Modal isOpen
|
||||
<Layout flex>
|
||||
|
||||
```vue-html
|
||||
<Modal v-model="isOpen" title="My modal">
|
||||
|
@ -40,17 +42,25 @@ const isOpen5 = ref(false)
|
|||
</Button>
|
||||
```
|
||||
|
||||
<div class="preview">
|
||||
<Modal v-model="isOpen" title="My modal">
|
||||
Modal content
|
||||
</Modal>
|
||||
<Button @click="isOpen = true">
|
||||
Open modal
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
</Layout>
|
||||
|
||||
## Modal actions
|
||||
|
||||
Use the `#actions` slot to add actions to a modal. Actions typically take the form of [buttons](./button).
|
||||
|
||||
Make sure to add `autofocus` to the preferred button.
|
||||
|
||||
<Layout flex>
|
||||
|
||||
```vue-html
|
||||
<Modal v-model="isOpen" title="My modal">
|
||||
Modal content
|
||||
|
@ -60,7 +70,7 @@ Use the `#actions` slot to add actions to a modal. Actions typically take the fo
|
|||
Cancel
|
||||
</Button>
|
||||
|
||||
<Button @click="isOpen = false">
|
||||
<Button autofocus @click="isOpen = false">
|
||||
Ok
|
||||
</Button>
|
||||
</template>
|
||||
|
@ -71,13 +81,14 @@ Use the `#actions` slot to add actions to a modal. Actions typically take the fo
|
|||
</Button>
|
||||
```
|
||||
|
||||
<div class="preview">
|
||||
<Modal v-model="isOpen2" title="My modal">
|
||||
Modal content
|
||||
<template #actions>
|
||||
<Button @click="isOpen2 = false" color="secondary">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button @click="isOpen2 = false">
|
||||
<Button autofocus @click="isOpen2 = false">
|
||||
Ok
|
||||
</Button>
|
||||
</template>
|
||||
|
@ -85,18 +96,89 @@ Use the `#actions` slot to add actions to a modal. Actions typically take the fo
|
|||
<Button @click="isOpen2 = true">
|
||||
Open modal
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
</Layout>
|
||||
|
||||
### Confirm a dangerous action
|
||||
|
||||
Note that confirmation dialogs interrupt the user's workflow. Consider adding a recovery functionality such as "undo" instead.
|
||||
|
||||
::: tip Read more about designing user experiences around dangerous actions:
|
||||
|
||||
- [How to use visual signals and spacing to differentiate between benign and dangerous options](https://www.nngroup.com/articles/proximity-consequential-options/)
|
||||
> If you need to implement dangerous actions, make sure to place them apart from other actions to prevent accidental clicks. Add contextual hints and information so that the user understands the consequences of the action.
|
||||
|
||||
- [How to design a confirmation dialog](https://www.nngroup.com/articles/confirmation-dialog/)
|
||||
> 1. Let the user confirm potentially destructive actions
|
||||
> 2. Do not use confirmation dialogs for routine tasks
|
||||
> 3. Be specific about the action and its potential consequences
|
||||
> 4. Label the response buttons with their result: "Delete my account" instead of "Yes"
|
||||
> 5. Make sure to give the user all information they need to decide
|
||||
|
||||
:::
|
||||
|
||||
```vue-html
|
||||
<Modal v-model="isOpen" title="Delete account?">
|
||||
<template #alert>
|
||||
<Alert color="red">
|
||||
1 082 music files that you uploaded will be deleted.<br />
|
||||
7 879 items in your collections will be unlinked.
|
||||
</Alert>
|
||||
</template>
|
||||
Do you want to delete your account forever?
|
||||
|
||||
You will not be able to restore your account.
|
||||
<template #actions>
|
||||
<Button autofocus @click="isOpen = false" color="secondary">
|
||||
Keep my account
|
||||
</Button>
|
||||
<Button color="destructive" @click="isOpen = false">
|
||||
I understand. Delete my account now!
|
||||
</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
<Button @click="isOpen = true" color="destructive">
|
||||
Delete my account ...
|
||||
</Button>
|
||||
```
|
||||
|
||||
<Modal v-model="isOpen6" title="Delete account?">
|
||||
<template #alert>
|
||||
<Alert color="red">
|
||||
1 082 music files that you uploaded will be deleted.<br />
|
||||
7 879 items in your collections will be unlinked.
|
||||
</Alert>
|
||||
</template>
|
||||
Do you want to delete your account forever?
|
||||
|
||||
You will not be able to restore your account.
|
||||
<template #actions>
|
||||
<Button autofocus @click="isOpen6 = false" color="secondary">
|
||||
Keep my account
|
||||
</Button>
|
||||
<Button color="destructive" @click="isOpen6 = false">
|
||||
I understand. Delete my account now!
|
||||
</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
<Button @click="isOpen6 = true" color="destructive">
|
||||
Delete my account ...
|
||||
</Button>
|
||||
|
||||
## Nested modals
|
||||
|
||||
You can nest modals to allow users to isOpen a modal from inside another modal. This can be useful when creating a multi-step workflow.
|
||||
|
||||
<Layout flex>
|
||||
|
||||
```vue-html
|
||||
<Modal v-model="isOpen" title="My modal">
|
||||
<Modal v-model="isOpenNested" title="My modal">
|
||||
Nested modal content
|
||||
</Modal>
|
||||
|
||||
<Button @click="isOpenNested = true">
|
||||
<Button autofocus @click="isOpenNested = true">
|
||||
Open nested modal
|
||||
</Button>
|
||||
</Modal>
|
||||
|
@ -106,22 +188,28 @@ You can nest modals to allow users to isOpen a modal from inside another modal.
|
|||
</Button>
|
||||
```
|
||||
|
||||
<div class="preview">
|
||||
<Modal v-model="isOpen4" title="My modal">
|
||||
<Modal v-model="isOpen5" title="My modal">
|
||||
Nested modal content
|
||||
</Modal>
|
||||
<Button @click="isOpen5 = true">
|
||||
<Button autofocus @click="isOpen5 = true">
|
||||
Open nested modal
|
||||
</Button>
|
||||
</Modal>
|
||||
<Button @click="isOpen4 = true">
|
||||
Open modal
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
</Layout>
|
||||
|
||||
## Alert inside modal
|
||||
|
||||
You can nest [Funkwhale alerts](./alert) to visually highlight content within the modal.
|
||||
|
||||
<Layout flex>
|
||||
|
||||
```vue-html
|
||||
<Modal v-model="isOpen" title="My modal">
|
||||
Modal content
|
||||
|
@ -131,7 +219,7 @@ You can nest [Funkwhale alerts](./alert) to visually highlight content within th
|
|||
Alert content
|
||||
|
||||
<template #actions>
|
||||
<Button @click="alertOpen = false">Close alert</Button>
|
||||
<Button autofocus @click="alertOpen = false">Close alert</Button>
|
||||
</template>
|
||||
</fw-alert>
|
||||
</template>
|
||||
|
@ -142,13 +230,14 @@ You can nest [Funkwhale alerts](./alert) to visually highlight content within th
|
|||
</Button>
|
||||
```
|
||||
|
||||
<div class="preview">
|
||||
<Modal v-model="isOpen3" title="My modal">
|
||||
Modal content
|
||||
<template #alert v-if="alertOpen">
|
||||
<Alert>
|
||||
Alert content
|
||||
<template #actions>
|
||||
<Button @click="alertOpen = false">Close alert</Button>
|
||||
<Button autofocus @click="alertOpen = false">Close alert</Button>
|
||||
</template>
|
||||
</Alert>
|
||||
</template>
|
||||
|
@ -164,3 +253,6 @@ You can nest [Funkwhale alerts](./alert) to visually highlight content within th
|
|||
<Button @click="isOpen3 = true">
|
||||
Open modal
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
</Layout>
|
||||
|
|
Loading…
Reference in New Issue