htmlcssvue.jsbootstrap-5

Notifications "action" button position


I try to create a vue/bootstrap notifications component with action buttons.

Thats my current result, no matter what i do, the buttons are part of the notification body itself:
enter image description here

Instead of that, i want the buttons at the bottom:
enter image description here

The button(s) should take up the whole width.

Current version of the vue component:

<template>
  <ul class="toast-container position-fixed top-0 end-0 p-3" style="z-index: 1050;">
    <li v-for="(notification, index) in notifications" :key="index"
      class="toast show align-items-center text-white border-0 mb-2 auto-dissmis" @click="removeNotification(index)">
      <div class="toast-indicator" :class="'bg-' + notification.type"></div>
      <div class="toast-body" v-html="notification.message"></div>
      <div class="toast-footer">
        <button class="btn btn-outline-primary d-block" @click="action.handler(action, $event)"
          v-for="action in notification.actions">
          {{ action.title }}
        </button>
      </div>
    </li>
  </ul>
</template>

<script>
export default {
  name: "Notification",
  data() {
    return {
      notifications: [],
    };
  },
  methods: {
    addNotification(message, opts) {

      opts = Object.assign({
        message,
        type: "primary",
        actions: [{
          title: "Foo",
          handler: (self, event) => {

            if (event) {
              event.stopPropagation();
              event.preventDefault();
            }

            alert("Clicked", self, event);

          }
        }]
      }, opts);

      this.notifications.push(opts);

      setTimeout(() => {
        if (this.notifications.length > 0) {
          //this.notifications.shift();
        }
      }, 2400);

    },
    removeNotification(index) {
      this.notifications.splice(index, 1);
    },
  },
  mounted() {

    this.addNotification(`Sample notification #1`);

    /*
    ["info", "primary", "success", "warning", "danger"].forEach((color, i) => {
      setTimeout(() => {
        this.addNotification(`Sample notification #${i}`, color);
      }, i * 1000);
    });
    */

  },
};
</script>

<style scoped>
.btn-group button:nth-child(1) {
  border-top-left-radius: 0;
}

.btn-group button:last-child {
  border-top-right-radius: 0;
}

.toast-container {
  width: 380px;
  list-style: none;
}

.toast {
  position: relative;
  overflow: hidden;
  width: 100%;
  display: flex;
  align-items: center;
  cursor: pointer;
  background-color: #343a40 !important
}

.toast-footer {
  padding: 0.5rem;
  border-top: 1px solid #dee2e6;
}


.toast-indicator {
  position: absolute;
  bottom: 0;
  left: 0;
  width: 6px;
  height: 100%;

}

.toast-actions {
  margin: 0;
}

li.auto-dissmis>.toast-indicator {
  animation: progress 2.4s linear forwards;
}

@keyframes progress {
  0% {
    height: 100%;
  }

  100% {
    height: 0;
  }
}
</style>

</style>

Solution

  • Found a solution. Create two div's inside the li element, one for the toast itself, one for the buttons:

    <template>
    
      <!--
      <ul class="toast-container position-fixed top-0 end-0 p-3" style="z-index: 1050;">
    
        <li class="toast show align-items-center text-white border-0 mb-2 auto-dissmis">
          <div class="toast-indicator"></div>
          <div class="toast-body">
            Hello Wrodl Message
    
          </div>
          <div class="btn-group d-flex" role="group" aria-label="Basic example">
            <button type="button" class="btn btn-outline-primary flex-fill">Left</button>
            <button type="button" class="btn btn-outline-primary flex-fill">Middle</button>
            <button type="button" class="btn btn-outline-primary flex-fill">Right</button>
          </div>
        </li>
    
      </ul>
      -->
    
    
      <ul class="toast-container position-fixed top-0 end-0 p-3" style="z-index: 1050;">
    
        <li v-for="(notification, index) in notifications" :key="index" @click="removeNotification(index)" class="mb-2 ">
          <div class="toast align-items-center text-white border-0"
            :class="{ 'toast-no-bottom-border-radius': notification.actions.length > 0 }">
            <div class="toast-indicator" :class="'bg-' + notification.type"></div>
            <div class="toast-body" v-html="notification.message"></div>
          </div>
          <div class="btn-group d-flex">
            <button type="button" class="btn btn-outline-primary flex-fill" v-for="(action, index) in notification.actions"
              :key="index" @click="action.handler(action, $event)">
              {{ action.title }}
            </button>
          </div>
        </li>
    
      </ul>
    </template>
    
    <script>
    export default {
      name: "Notification",
      data() {
        return {
          notifications: [],
        };
      },
      methods: {
        addNotification(message, opts) {
    
          opts = Object.assign({
            message,
            type: "primary",
            actions: []
          }, opts);
    
          this.notifications.push(opts);
    
          setTimeout(() => {
            if (this.notifications.length > 0) {
              //this.notifications.shift();
            }
          }, 2400);
    
        },
        removeNotification(index) {
          this.notifications.splice(index, 1);
        },
      },
      mounted() {
    
        let handler = (self, event) => {
    
          if (event) {
            event.stopPropagation();
            event.preventDefault();
          }
    
          alert("Clicked", self, event);
    
        };
    
        this.addNotification(`Sample notification #1`, {
          actions: [{
            title: "Foo",
            handler,
          }, {
            title: "Bar",
            handler,
          }]
        });
    
        this.addNotification(`Sample notification #2`, {
          type: "success"
        });
    
        /*
        ["info", "primary", "success", "warning", "danger"].forEach((color, i) => {
          setTimeout(() => {
            this.addNotification(`Sample notification #${i}`, color);
          }, i * 1000);
        });
        */
    
      },
    };
    </script>
    
    <style scoped>
    .btn-group button:nth-child(1) {
      border-top-left-radius: 0;
    }
    
    .btn-group button:last-child {
      border-top-right-radius: 0;
    }
    
    .toast-container {
      width: 380px;
      list-style: none;
    }
    
    .toast {
      position: relative;
      overflow: hidden;
      width: 100%;
      min-height: 60px;
      display: flex;
      align-items: center;
      cursor: pointer;
      background-color: #343a40 !important
    }
    
    .toast-no-bottom-border-radius {
      border-bottom-left-radius: 0;
      border-bottom-right-radius: 0;
    }
    
    .toast-indicator {
      position: absolute;
      bottom: 0;
      left: 0;
      width: 6px;
      height: 100%;
    }
    
    .toast-actions {
      margin: 0;
    }
    
    .btn-group button {
      pointer-events: auto;
    }
    
    li.auto-dissmis>div.toast>.toast-indicator {
      animation: progress 2.4s linear forwards;
    }
    
    @keyframes progress {
      0% {
        height: 100%;
      }
    
      100% {
        height: 0;
      }
    }
    </style>
    

    enter image description here