I have a Vue 3 app using Vuetify. Inside a component I'm listening to streamed events. For each new event I want to display a notification fading out after x seconds. I think the Snackbar component is the correct one to pick but unfortunately a snackbar list isn't support.
I started with a NotificationList component expecting notifications as a prop and tries to display them in a stacked list
<template>
<v-snackbar
v-for="[notificationId, notificationMessage] in notifications"
:ref="snackbarElement => notificationReferences.set(notificationId, snackbarElement)"
:key="notificationId"
:model-value="true"
location="top right"
@update:model-value="emit('removeNotification', notificationId)"
>
</v-snackbar>
</template>
<script setup lang="ts">
import { ref, watch, watchEffect } from 'vue'
const props = defineProps<{ notifications: Map<string, string> }>();
const emit = defineEmits<{
(e: 'removeNotification', id: string): void
}>()
const notificationReferences = ref(new Map<string, unknown>());
watch(props.notifications, () => {
// clear all references to avoid dead ones
// notificationReferences.value.clear();
}, { immediate: true });
watchEffect(() => {
let notificationIndex = 0;
for (const [notificationId, snackbarElement] of notificationReferences.value) {
// const snackbarElementHeight = ??? // get height from Vuetify component
const marginTop = notificationIndex * 60; // snackbarElementHeight;
// snackbarElement.style.marginTop = marginTop + "px";
// snackbarElement.style.setProperty('margin-top', marginTop + "px");
notificationIndex++;
}
});
</script>
So other components, e.g. the App component can consume it like so
<template>
<v-app>
<v-main>
<v-btn @click="addNotification">New notification</v-btn>
<notification-list :notifications="notifications" @removeNotification="removeNotification" />
</v-main>
</v-app>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import NotificationList from './NotificationList.vue'
const notifications = ref<Map<string, string>>(new Map())
function addNotification() {
const notificationId = self.crypto.randomUUID();
const notificationMessage = (new Date()).toString();
notifications.value.set(notificationId, notificationMessage);
}
function removeNotification(notificationId) {
notifications.value.delete(notificationId);
}
</script>
I think the code is almost fine, the only thing missing is the vertical offset, currently all snackbars have the same position.
The problem is that when I try to apply a margin to snackbarElement I get the error
TypeError: Cannot set properties of undefined (setting 'marginTop')
but when I try to debug it, e.g. logging the variable, I get the error
TypeError: Cannot convert object to primitive value
Do you have any ideas how to fix this? Maybe my approach can be simplified?
Sidenote:
I found https://vuetifyjs.com/en/api/v-snackbar/#props-offset so maybe I don't even have to deal with notificationReferences at all but the offset prop had no impact, sample
from How to display multiple notifications with Vuetify?
No comments:
Post a Comment