content/static: create accordion controller for sidebar
Creates accordion controller for use in the left sidebar.
Collapses and expands top level sidenav sections.
Change-Id: I11122b96a10241d55e125515b5b8ccb41f27c461
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/259629
Trust: Jamal Carvalho <jamal@golang.org>
Run-TryBot: Jamal Carvalho <jamal@golang.org>
Reviewed-by: Julie Qiu <julie@golang.org>
diff --git a/content/static/css/unit_outline.css b/content/static/css/unit_outline.css
index 6c10f7d..8708d23 100644
--- a/content/static/css/unit_outline.css
+++ b/content/static/css/unit_outline.css
@@ -32,7 +32,7 @@
position: sticky;
top: 4.5rem;
}
-a.UnitOutline-accordian {
+a.UnitOutline-accordion {
align-items: center;
color: var(--gray-2);
display: flex;
@@ -41,7 +41,7 @@
height: 2.5rem;
padding: 1rem;
}
-a.UnitOutline-accordian--active {
+a.UnitOutline-accordion[aria-expanded='true'] {
background-color: var(--gray-9);
}
.UnitOutline-panel {
@@ -50,6 +50,9 @@
display: block;
overflow-y: auto;
}
+.UnitOutline-panel[aria-hidden='true'] {
+ display: none;
+}
.UnitOutline-jumpTo {
display: flex;
margin-bottom: 0.5625rem;
diff --git a/content/static/html/helpers/_unit_outline.tmpl b/content/static/html/helpers/_unit_outline.tmpl
index 1079981..dfb2116 100644
--- a/content/static/html/helpers/_unit_outline.tmpl
+++ b/content/static/html/helpers/_unit_outline.tmpl
@@ -5,28 +5,47 @@
-->
{{define "unit_outline"}}
- <div class="UnitOutline">
+ <div class="UnitOutline js-accordion">
<div class="UnitOutline-jumpTo">
<button class="UnitOutline-jumpToInput js-jumpToInput"{{if (not .DocOutline.String)}} disabled{{end}}>
Jump to
</button>
</div>
{{if .Readme.String}}
- <a class="UnitOutline-accordian js-accordian js-readmeExpand" href="?readme=expanded#readme-top">README</a>
+ <a href="?readme=expanded#readme-top" class="UnitOutline-accordion js-accordionTrigger js-readmeExpand"
+ aria-expanded="true" aria-controls="readme-panel" id="readme-accordion">
+ README
+ </a>
+ <div class="UnitOutline-panel js-accordionPanel"
+ id="readme-panel" role="region" aria-labelledby="readme-accordion" aria-hidden="false"></div>
{{end}}
{{if (or .DocOutline.String .Unit.IsPackage)}}
- <a class="UnitOutline-accordian js-accordian" href="#doc-top">Documentation</a>
- <div class="UnitOutline-panel">
+ <a class="UnitOutline-accordion js-accordionTrigger" href="#doc-top"
+ aria-expanded="false" aria-controls="outline-panel" id="outline-accordion">
+ Documentation
+ </a>
+ <div class="UnitOutline-panel js-accordionPanel"
+ id="outline-panel" role="region" aria-labelledby="ouline-accordion" aria-hidden="true">
<div class="Documentation">
{{.DocOutline}}
</div>
</div>
{{end}}
{{if .SourceFiles}}
- <a class="UnitOutline-accordian js-accordian" href="#files-top">Source Files</a>
+ <a class="UnitOutline-accordion js-accordionTrigger" href="#files-top"
+ aria-expanded="false" aria-controls="files-panel" id="files-accordion">
+ Source Files
+ </a>
+ <div class="UnitOutline-panel js-accordionPanel"
+ id="files-panel" role="region" aria-labelledby="files-accordion" aria-hidden="true"></div>
{{end}}
{{if (or .Packages .NestedModules)}}
- <a class="UnitOutline-accordian js-accordian" href="#directories-top">Directories</a>
+ <a class="UnitOutline-accordion js-accordionTrigger" href="#directories-top"
+ aria-expanded="false" aria-controls="directories-panel" id="directories-accordion">
+ Directories
+ </a>
+ <div class="UnitOutline-panel js-accordionPanel"
+ id="directories-panel" role="region" aria-labelledby="directories-accordion" aria-hidden="true"></div>
{{end}}
</div>
{{end}}
diff --git a/content/static/js/accordion.js b/content/static/js/accordion.js
new file mode 100644
index 0000000..62b640c
--- /dev/null
+++ b/content/static/js/accordion.js
@@ -0,0 +1,81 @@
+/**
+ * @license
+ * Copyright 2020 The Go Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+export class AccordionController {
+ constructor(accordion) {
+ this.accordion = accordion;
+ this.triggers = [...this.accordion.querySelectorAll('.js-accordionTrigger')];
+ this.activeTrigger = this.triggers[0];
+ this.init();
+ }
+
+ init() {
+ this.accordion.addEventListener('click', e => {
+ if (e.target.classList.contains('js-accordionTrigger')) {
+ this.select(e.target);
+ }
+ });
+
+ this.accordion.addEventListener('keydown', e => {
+ if (e.target.classList.contains('js-accordionTrigger')) {
+ this.handleKeyPress(e);
+ }
+ });
+ }
+
+ select(target) {
+ const isExpanded = target.getAttribute('aria-expanded') === 'true';
+ if (!isExpanded) {
+ target.setAttribute('aria-expanded', 'true');
+ document
+ .getElementById(target.getAttribute('aria-controls'))
+ .setAttribute('aria-hidden', 'false');
+ }
+ if (this.activeTrigger !== target) {
+ this.activeTrigger.setAttribute('aria-expanded', 'false');
+ document
+ .getElementById(this.activeTrigger.getAttribute('aria-controls'))
+ .setAttribute('aria-hidden', 'true');
+ }
+ this.activeTrigger = target;
+ }
+
+ handleKeyPress(e) {
+ const target = e.target;
+ const key = e.which;
+ const PAGE_UP = 33;
+ const PAGE_DOWN = 34;
+ const END = 35;
+ const HOME = 36;
+ const ARROW_UP = 38;
+ const ARROW_DOWN = 40;
+
+ switch (key) {
+ case PAGE_UP:
+ case PAGE_DOWN:
+ case ARROW_UP:
+ case ARROW_DOWN:
+ const index = this.triggers.indexOf(target);
+ const direction = [PAGE_UP, ARROW_UP].includes(key) ? -1 : 1;
+ const newIndex = (index + this.triggers.length + direction) % this.triggers.length;
+ this.triggers[newIndex].focus();
+ e.preventDefault();
+ break;
+ case END:
+ this.triggers[this.triggers.length - 1].focus();
+ e.preventDefault();
+ break;
+ case HOME:
+ this.triggers[0].focus();
+ e.preventDefault();
+ break;
+
+ default:
+ break;
+ }
+ }
+}
diff --git a/content/static/js/unit.js b/content/static/js/unit.js
index c34d8ae..9ef592a 100644
--- a/content/static/js/unit.js
+++ b/content/static/js/unit.js
@@ -5,20 +5,22 @@
* license that can be found in the LICENSE file.
*/
-// Highlights the active accordian on page load and adds event
-// listeners to update on interaction from user.
-document.querySelectorAll('a.js-accordian').forEach((anchor, index) => {
- const activeClass = 'UnitOutline-accordian--active';
- if (index === 0) {
- anchor.classList.add(activeClass);
+import { AccordionController } from './accordion.js';
+
+/**
+ * Instantiates accordian controller for the left sidebar and sets
+ * the panel for the current location hash as active.
+ */
+const accordion = document.querySelector('.js-accordion');
+if (accordion) {
+ const accordionCtlr = new AccordionController(accordion);
+ const activePanel =
+ window.location.hash &&
+ document.querySelector(`a[href=${JSON.stringify(window.location.hash)}]`);
+ if (activePanel) {
+ accordionCtlr.select(activePanel);
}
- anchor.addEventListener('click', () => {
- document.querySelectorAll('a.js-accordian').forEach(el => {
- el.classList.remove(activeClass);
- });
- anchor.classList.add(activeClass);
- });
-});
+}
/**
* Event handlers for expanding and collapsing the readme section.