fix(frontend): wrap notifications in Teleport to appear above modals for #2744

This commit is contained in:
bradmartin333 2026-05-21 17:00:32 -06:00 committed by kolaente
parent 4fc4125546
commit 6076102d21
1 changed files with 85 additions and 55 deletions

View File

@ -1,66 +1,96 @@
<template>
<Notifications
position="bottom left"
:max="2"
:ignore-duplicates="true"
class="global-notification"
role="status"
aria-live="polite"
>
<template #body="{ item, close }">
<!-- FIXME: overlay whole notification with button and add event listener on that button instead -->
<div
class="vue-notification-template vue-notification"
:class="[
item.type,
]"
@click="close()"
>
<Teleport :to="teleportTarget">
<Notifications
position="bottom left"
:max="2"
:ignore-duplicates="true"
class="global-notification"
role="status"
aria-live="polite"
>
<template #body="{ item, close }">
<!-- FIXME: overlay whole notification with button and add event listener on that button instead -->
<div
v-if="item.title"
class="notification-title"
class="vue-notification-template vue-notification"
:class="[
item.type,
]"
@click="close()"
>
{{ item.title }}
</div>
<div class="notification-content">
<template v-if="Array.isArray(item.text)">
<template
v-for="(t, k) in item.text"
:key="k"
>
{{ t }}<br>
<div
v-if="item.title"
class="notification-title"
>
{{ item.title }}
</div>
<div class="notification-content">
<template v-if="Array.isArray(item.text)">
<template
v-for="(t, k) in item.text"
:key="k"
>
{{ t }}<br>
</template>
</template>
</template>
<template v-else>
{{ item.text }}
</template>
<span
v-if="item.duplicates > 0"
class="tw:text-xs tw:font-bold tw:ml-1"
<template v-else>
{{ item.text }}
</template>
<span
v-if="item.duplicates > 0"
class="tw:text-xs tw:font-bold tw:ml-1"
>
×{{ item.duplicates + 1 }}
</span>
</div>
<div
v-if="item.data?.actions?.length > 0"
class="mbs-2 tw:flex tw:justify-end tw:gap-2"
>
×{{ item.duplicates + 1 }}
</span>
<XButton
v-for="(action, i) in item.data.actions"
:key="'action_' + i"
:shadow="false"
class="is-small"
variant="secondary"
@click="action.callback"
>
{{ action.title }}
</XButton>
</div>
</div>
<div
v-if="item.data?.actions?.length > 0"
class="mbs-2 tw:flex tw:justify-end tw:gap-2"
>
<XButton
v-for="(action, i) in item.data.actions"
:key="'action_' + i"
:shadow="false"
class="is-small"
variant="secondary"
@click="action.callback"
>
{{ action.title }}
</XButton>
</div>
</div>
</template>
</Notifications>
</template>
</Notifications>
</Teleport>
</template>
<script lang="ts" setup>
import {onBeforeUnmount, onMounted, ref} from 'vue'
const teleportTarget = ref<string | HTMLElement>('body')
let observer: MutationObserver | null = null
function syncTeleportTarget() {
const dialogs = document.querySelectorAll<HTMLDialogElement>('dialog.modal-dialog[open]')
teleportTarget.value = dialogs.item(dialogs.length - 1) ?? 'body'
}
onMounted(() => {
syncTeleportTarget()
observer = new MutationObserver(syncTeleportTarget)
observer.observe(document.body, {
attributes: true,
attributeFilter: ['open'],
childList: true,
subtree: true,
})
})
onBeforeUnmount(() => {
observer?.disconnect()
observer = null
})
</script>
<style scoped>
.vue-notification {
z-index: 9999;