Add quicklinks to hardware status page

- Renames SetFocusMixin to JumpLinkMixin to better describe what the
mixin is for: jump links like quick links and skip to main content
- Adds scrollToOffset method to JumpLinkMixin methods to scroll to
selected page elements
- Scroll offset is required to show table header below the nav header
- Setting focus is required for accessibility

Signed-off-by: Dixsie Wolmers <dixsie@ibm.com>
Change-Id: I500a2d70727c5a78aeae4a6193ba22a38e4f0b6f
diff --git a/src/components/Global/PageContainer.vue b/src/components/Global/PageContainer.vue
index c979759..ab4adb6 100644
--- a/src/components/Global/PageContainer.vue
+++ b/src/components/Global/PageContainer.vue
@@ -5,10 +5,10 @@
 </template>
 
 <script>
-import SetFocusMixin from '@/components/Mixins/SetFocusMixin';
+import JumpLinkMixin from '@/components/Mixins/JumpLinkMixin';
 export default {
   name: 'PageContainer',
-  mixins: [SetFocusMixin],
+  mixins: [JumpLinkMixin],
   created() {
     this.$root.$on('skip-navigation', () => {
       this.setFocus(this.$el);
diff --git a/src/components/Mixins/JumpLinkMixin.js b/src/components/Mixins/JumpLinkMixin.js
new file mode 100644
index 0000000..b038527
--- /dev/null
+++ b/src/components/Mixins/JumpLinkMixin.js
@@ -0,0 +1,27 @@
+const JumpLinkMixin = {
+  methods: {
+    setFocus(element) {
+      element.setAttribute('tabindex', '-1');
+      element.focus();
+      // Reason: https://axesslab.com/skip-links/#update-3-a-comment-from-gov-uk
+      element.removeAttribute('tabindex');
+    },
+    scrollToOffset(event) {
+      // Select element to scroll to
+      const ref = event.target.getAttribute('data-ref');
+      const element = this.$refs[ref].$el;
+
+      // Set focus and tabindex on selected element
+      this.setFocus(element);
+
+      // Set scroll offset below header
+      const offset = element.offsetTop - 50;
+      window.scroll({
+        top: offset,
+        behavior: 'smooth',
+      });
+    },
+  },
+};
+
+export default JumpLinkMixin;
diff --git a/src/components/Mixins/SetFocusMixin.js b/src/components/Mixins/SetFocusMixin.js
deleted file mode 100644
index ae3e8e0..0000000
--- a/src/components/Mixins/SetFocusMixin.js
+++ /dev/null
@@ -1,12 +0,0 @@
-const setFocusMixin = {
-  methods: {
-    setFocus(element) {
-      element.setAttribute('tabindex', '-1');
-      element.focus();
-      // Reason: https://axesslab.com/skip-links/#update-3-a-comment-from-gov-uk
-      element.removeAttribute('tabindex');
-    },
-  },
-};
-
-export default setFocusMixin;
diff --git a/src/layouts/AppLayout.vue b/src/layouts/AppLayout.vue
index d5b4c3d..41b2e44 100644
--- a/src/layouts/AppLayout.vue
+++ b/src/layouts/AppLayout.vue
@@ -15,7 +15,7 @@
 import AppNavigation from '@/components/AppNavigation';
 import PageContainer from '@/components/Global/PageContainer';
 import ButtonBackToTop from '@/components/Global/ButtonBackToTop';
-import SetFocusMixin from '@/components/Mixins/SetFocusMixin';
+import JumpLinkMixin from '@/components/Mixins/JumpLinkMixin';
 
 export default {
   name: 'App',
@@ -25,7 +25,7 @@
     PageContainer,
     ButtonBackToTop,
   },
-  mixins: [SetFocusMixin],
+  mixins: [JumpLinkMixin],
   data() {
     return {
       routerKey: 0,
diff --git a/src/locales/en-US.json b/src/locales/en-US.json
index 34efa62..ef4a417 100644
--- a/src/locales/en-US.json
+++ b/src/locales/en-US.json
@@ -372,6 +372,7 @@
     "bmcManager": "BMC manager",
     "chassis": "Chassis",
     "processors": "Processors",
+    "quicklinkTitle": "Quick links to hardware components",
     "system": "System",
     "table": {
       "assetTag": "Asset tag",
diff --git a/src/views/Health/HardwareStatus/HardwareStatus.vue b/src/views/Health/HardwareStatus/HardwareStatus.vue
index 0869e28..0e37f6e 100644
--- a/src/views/Health/HardwareStatus/HardwareStatus.vue
+++ b/src/views/Health/HardwareStatus/HardwareStatus.vue
@@ -2,26 +2,43 @@
   <b-container fluid="xl">
     <page-title />
 
+    <!-- Quicklinks section -->
+    <page-section :section-title="$t('pageHardwareStatus.quicklinkTitle')">
+      <b-row class="w-75">
+        <b-col v-for="column in quicklinkColumns" :key="column.id" xl="4">
+          <div v-for="item in column" :key="item.id">
+            <b-link
+              :href="item.href"
+              :data-ref="item.dataRef"
+              @click.prevent="scrollToOffset"
+            >
+              <jump-link /> {{ item.linkText }}
+            </b-link>
+          </div>
+        </b-col>
+      </b-row>
+    </page-section>
+
     <!-- System table -->
-    <table-system />
+    <table-system ref="system" />
 
     <!-- BMC manager table -->
-    <table-bmc-manager />
+    <table-bmc-manager ref="bmc" />
 
     <!-- Chassis table -->
-    <table-chassis />
+    <table-chassis ref="chassis" />
 
     <!-- DIMM slot table -->
-    <table-dimm-slot />
+    <table-dimm-slot ref="dimms" />
 
     <!-- Fans table -->
-    <table-fans />
+    <table-fans ref="fans" />
 
     <!-- Power supplies table -->
-    <table-power-supplies />
+    <table-power-supplies ref="powerSupply" />
 
     <!-- Processors table -->
-    <table-processors />
+    <table-processors ref="processors" />
   </b-container>
 </template>
 
@@ -35,6 +52,12 @@
 import TableChassis from './HardwareStatusTableChassis';
 import TableProcessors from './HardwareStatusTableProcessors';
 import LoadingBarMixin from '@/components/Mixins/LoadingBarMixin';
+import PageSection from '@/components/Global/PageSection';
+import JumpLink16 from '@carbon/icons-vue/es/jump-link/16';
+
+import JumpLinkMixin from '@/components/Mixins/JumpLinkMixin';
+
+import { chunk } from 'lodash';
 
 export default {
   components: {
@@ -46,14 +69,70 @@
     TableBmcManager,
     TableChassis,
     TableProcessors,
+    PageSection,
+    JumpLink: JumpLink16,
   },
-  mixins: [LoadingBarMixin],
+  mixins: [LoadingBarMixin, JumpLinkMixin],
   beforeRouteLeave(to, from, next) {
     // Hide loader if user navigates away from page
     // before requests complete
     this.hideLoader();
     next();
   },
+  data() {
+    return {
+      links: [
+        {
+          id: 'bmc',
+          dataRef: 'bmc',
+          href: '#bmc',
+          linkText: this.$t('pageHardwareStatus.bmcManager'),
+        },
+        {
+          id: 'chassis',
+          dataRef: 'chassis',
+          href: '#chassis',
+          linkText: this.$t('pageHardwareStatus.chassis'),
+        },
+        {
+          id: 'dimms',
+          dataRef: 'dimms',
+          href: '#dimms',
+          linkText: this.$t('pageHardwareStatus.dimmSlot'),
+        },
+        {
+          id: 'fans',
+          dataRef: 'fans',
+          href: '#fans',
+          linkText: this.$t('pageHardwareStatus.fans'),
+        },
+        {
+          id: 'powerSupply',
+          dataRef: 'powerSupply',
+          href: '#powerSupply',
+          linkText: this.$t('pageHardwareStatus.powerSupplies'),
+        },
+        {
+          id: 'processors',
+          dataRef: 'processors',
+          href: '#processors',
+          linkText: this.$t('pageHardwareStatus.processors'),
+        },
+        {
+          id: 'system',
+          dataRef: 'system',
+          href: '#system',
+          linkText: this.$t('pageHardwareStatus.system'),
+        },
+      ],
+    };
+  },
+  computed: {
+    quicklinkColumns() {
+      // Chunk links array to 3 array's to display 3 items per column
+      return chunk(this.links, 3);
+    },
+  },
   created() {
     this.startLoader();
     const systemTablePromise = new Promise((resolve) => {