Add batch actions to local user table

- Create TableToolbar component for table batch actions
- Added Toast warning type and toast title message translations
- Update vue-i18n package to latest v8.15.3 to use improved
  pluarlization features

Signed-off-by: Yoshie Muranaka <yoshiemuranaka@gmail.com>
Change-Id: I455beba4f56b8209b1201bbc5ff3f616e960d189
diff --git a/src/components/Global/TableToolbar.vue b/src/components/Global/TableToolbar.vue
new file mode 100644
index 0000000..fc3736d
--- /dev/null
+++ b/src/components/Global/TableToolbar.vue
@@ -0,0 +1,119 @@
+<template>
+  <transition name="slide">
+    <div v-if="isToolbarActive" class="toolbar-container">
+      <div class="toolbar-content">
+        <p class="toolbar-selected">
+          {{ selectedItemsCount }} {{ $t('global.actions.selected') }}
+        </p>
+        <div class="toolbar-actions d-flex">
+          <b-button
+            v-for="(action, index) in actions"
+            :key="index"
+            variant="primary"
+            class="d-block"
+            @click="$emit('batchAction', action.value)"
+          >
+            {{ $t(action.labelKey) }}
+          </b-button>
+          <b-button
+            variant="primary"
+            class="d-block"
+            @click="$emit('clearSelected')"
+          >
+            {{ $t('global.actions.cancel') }}
+          </b-button>
+        </div>
+      </div>
+    </div>
+  </transition>
+</template>
+
+<script>
+export default {
+  name: 'TableToolbar',
+  props: {
+    selectedItemsCount: {
+      type: Number,
+      required: true
+    },
+    actions: {
+      type: Array,
+      required: true,
+      validator: prop => {
+        return prop.every(action => {
+          return (
+            action.hasOwnProperty('value') && action.hasOwnProperty('labelKey')
+          );
+        });
+      }
+    }
+  },
+  data() {
+    return {
+      isToolbarActive: false
+    };
+  },
+  watch: {
+    selectedItemsCount: function(selectedItemsCount) {
+      if (selectedItemsCount > 0) {
+        this.isToolbarActive = true;
+      } else {
+        this.isToolbarActive = false;
+      }
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+$toolbar-height: 46px;
+
+.toolbar-container {
+  width: 100%;
+  position: relative;
+}
+
+.toolbar-content {
+  height: $toolbar-height;
+  background-color: $primary;
+  color: $white;
+  position: absolute;
+  left: 0;
+  right: 0;
+  top: -$toolbar-height;
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+}
+
+.toolbar-actions {
+  > :last-child {
+    position: relative;
+    &::before {
+      content: '';
+      position: absolute;
+      height: $toolbar-height / 2;
+      border-left: 2px solid $white;
+      left: -2px;
+      top: $toolbar-height / 4;
+    }
+  }
+}
+
+.toolbar-selected {
+  line-height: $toolbar-height;
+  margin: 0;
+  padding: 0 $spacer;
+}
+
+.slide-enter-active {
+  transition: transform $duration--moderate-02 $entrance-easing--productive;
+}
+.slide-leave-active {
+  transition: transform $duration--moderate-02 $exit-easing--productive;
+}
+.slide-enter,
+.slide-leave-to {
+  transform: translateY($toolbar-height);
+}
+</style>
diff --git a/src/components/Mixins/BVTableSelectableMixin.js b/src/components/Mixins/BVTableSelectableMixin.js
new file mode 100644
index 0000000..fba2f2b
--- /dev/null
+++ b/src/components/Mixins/BVTableSelectableMixin.js
@@ -0,0 +1,44 @@
+const BVTableSelectableMixin = {
+  data() {
+    return {
+      tableHeaderCheckboxModel: false,
+      tableHeaderCheckboxIndeterminate: false,
+      selectedRows: []
+    };
+  },
+  methods: {
+    clearSelectedRows(tableRef) {
+      if (tableRef) tableRef.clearSelected();
+    },
+    toggleSelectRow(tableRef, rowIndex) {
+      if (tableRef && rowIndex !== undefined) {
+        tableRef.isRowSelected(rowIndex)
+          ? tableRef.unselectRow(rowIndex)
+          : tableRef.selectRow(rowIndex);
+      }
+    },
+    onRowSelected(selectedRows, totalRowsCount) {
+      if (selectedRows && totalRowsCount !== undefined) {
+        this.selectedRows = selectedRows;
+        if (selectedRows.length === 0) {
+          this.tableHeaderCheckboxIndeterminate = false;
+          this.tableHeaderCheckboxModel = false;
+        } else if (selectedRows.length === totalRowsCount) {
+          this.tableHeaderCheckboxIndeterminate = false;
+          this.tableHeaderCheckboxModel = true;
+        } else {
+          this.tableHeaderCheckboxIndeterminate = true;
+          this.tableHeaderCheckboxModel = false;
+        }
+      }
+    },
+    onChangeHeaderCheckbox(tableRef) {
+      if (tableRef) {
+        if (this.tableHeaderCheckboxModel) tableRef.clearSelected();
+        else tableRef.selectAllRows();
+      }
+    }
+  }
+};
+
+export default BVTableSelectableMixin;
diff --git a/src/components/Mixins/BVToastMixin.js b/src/components/Mixins/BVToastMixin.js
index 489173c..a46f5e5 100644
--- a/src/components/Mixins/BVToastMixin.js
+++ b/src/components/Mixins/BVToastMixin.js
@@ -1,22 +1,33 @@
+import i18n from '../../i18n';
+
 const BVToastMixin = {
   methods: {
-    successToast(message) {
+    successToast(message, title = i18n.t('global.response.success')) {
       this.$root.$bvToast.toast(message, {
-        title: 'Success',
+        title,
         variant: 'success',
         autoHideDelay: 10000, //auto hide in milliseconds
         isStatus: true,
         solid: true
       });
     },
-    errorToast(message) {
+    errorToast(message, title = i18n.t('global.response.error')) {
       this.$root.$bvToast.toast(message, {
-        title: 'Error',
+        title,
         variant: 'danger',
         noAutoHide: true,
         isStatus: true,
         solid: true
       });
+    },
+    warningToast(message, title = i18n.t('global.response.warning')) {
+      this.$root.$bvToast.toast(message, {
+        title,
+        variant: 'warning',
+        noAutoHide: true,
+        isStatus: true,
+        solid: true
+      });
     }
   }
 };