content/static: unit directories tree layout
Updates unit directories section to nest subdirectories
in a two level tree structure.
For golang/go#43694
Change-Id: I374c042234aeb426b24ae44c861bfb61e6511db1
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/283715
Trust: Jamal Carvalho <jamal@golang.org>
Trust: Julie Qiu <julie@golang.org>
Run-TryBot: Jamal Carvalho <jamal@golang.org>
TryBot-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Alexander Rakoczy <alex@golang.org>
Reviewed-by: Julie Qiu <julie@golang.org>
diff --git a/content/static/css/unit_directories.css b/content/static/css/unit_directories.css
index 9ddfb96..88da146 100644
--- a/content/static/css/unit_directories.css
+++ b/content/static/css/unit_directories.css
@@ -19,22 +19,30 @@
width: auto;
}
.UnitDirectories-table {
- margin-top: 1.5rem;
+ border-collapse: collapse;
+ height: 0;
+ table-layout: auto;
width: 100%;
}
+.UnitDirectories-table--tree {
+ margin-top: -2rem;
+}
.UnitDirectories-tableHeader {
background-color: var(--gray-9);
}
+.UnitDirectories-tableHeader--tree {
+ visibility: hidden;
+}
.UnitDirectories td {
border-bottom: 0.0625rem solid var(--gray-8);
max-width: 32rem;
+ min-width: 12rem;
padding: 0.5rem 1rem;
word-break: break-word;
}
.UnitDirectories th {
- text-align: left;
- border-bottom: 0.0625rem solid var(--gray-8);
padding: 0.5rem 1rem;
+ text-align: left;
}
.UnitDirectories-moduleTag {
background-color: var(--blue);
@@ -43,3 +51,83 @@
font-size: 0.74rem;
padding: 0.2rem 0.4rem;
}
+.UnitDirectories tr.hidden {
+ display: none;
+}
+.UnitDirectories tr[aria-controls] {
+ cursor: pointer;
+}
+.UnitDirectories tr[aria-controls]:hover {
+ background-color: var(--gray-10);
+}
+.UnitDirectories th.UnitDirectories-toggleHead {
+ font-size: 0;
+ max-width: 0.625rem;
+ padding: 0;
+ width: 0.625rem;
+}
+.UnitDirectories td.UnitDirectories-toggleCell,
+th.UnitDirectories-toggleCell {
+ background-color: var(--white);
+ border: var(--white);
+ max-width: 0.625rem;
+ padding: 0;
+ width: 0.625rem;
+}
+.UnitDirectories-toggleButton {
+ background-color: transparent;
+ border: none;
+ margin: 0 -0.5rem -1rem -1rem;
+ vertical-align: top;
+}
+.UnitDirectories-subSpacer {
+ border-right: 0.0625rem solid var(--gray-8);
+ display: inline;
+ margin-right: 0.875rem;
+ width: 0.0625rem;
+}
+.UnitDirectories-toggleButton[aria-expanded='true'] img {
+ transform: rotate(90deg);
+}
+.UnitDirectories-pathCell {
+ align-items: flex-start;
+ display: flex;
+ flex-direction: column;
+ line-height: 1.75rem;
+ word-break: break-all;
+}
+.UnitDirectories-subdirectory {
+ border-left: 0.0625rem solid var(--gray-8);
+ display: flex;
+ flex-direction: column;
+ padding: 0.5rem 1rem;
+}
+.UnitDirectories-mobileSynopsis {
+ display: none;
+ line-height: 1.25rem;
+ margin-top: 0.25rem;
+ word-break: keep-all;
+}
+@media only screen and (max-width: 52rem) {
+ .UnitDirectories-mobileSynopsis {
+ display: initial;
+ }
+ .UnitDirectories-table th.UnitDirectories-desktopSynopsis,
+ .UnitDirectories-table td.UnitDirectories-desktopSynopsis {
+ display: none;
+ }
+}
+.UnitDirectories-expandButton {
+ position: relative;
+}
+.UnitDirectories-expandButton button {
+ background-color: transparent;
+ border: none;
+ bottom: 1rem;
+ color: var(--turq-dark);
+ cursor: pointer;
+ font-size: 0.875rem;
+ position: absolute;
+ right: 0;
+ text-decoration: none;
+}
diff --git a/content/static/html/helpers/_unit_directories.tmpl b/content/static/html/helpers/_unit_directories.tmpl
index 336fa30..72274c4 100644
--- a/content/static/html/helpers/_unit_directories.tmpl
+++ b/content/static/html/helpers/_unit_directories.tmpl
@@ -9,6 +9,68 @@
<h2 class="UnitDirectories-title">
<img height="25px" width="20px" src="/static/img/pkg-icon-folder_20x16.svg" alt="">Directories
</h2>
+ <div class="UnitDirectories-expandButton js-expandAllDirectories">
+ <button>Expand all</button>
+ </div>
+ {{- if .Directories -}}
+ <table class="UnitDirectories-table UnitDirectories-table--tree js-expandableTable" data-test-id="UnitDirectories-table">
+ <tr class="UnitDirectories-tableHeader UnitDirectories-tableHeader--tree">
+ <th>Path</th>
+ <th class="UnitDirectories-desktopSynopsis">Synopsis</th>
+ </tr>
+ {{- range .Directories -}}
+ {{- $prefix := .Prefix -}}
+ <tr{{if .Subdirectories}} data-aria-controls="{{range .Subdirectories}}{{$prefix}}-{{.Suffix}} {{end}}"{{end}}>
+ <td data-id="{{$prefix}}" data-aria-owns="{{range .Subdirectories}}{{$prefix}}-{{.Suffix}} {{end}}">
+ <div class="UnitDirectories-pathCell">
+ <div>
+ {{- if .Subdirectories -}}
+ <button type="button" class="UnitDirectories-toggleButton"
+ aria-expanded="false"
+ aria-label="{{len .Subdirectories}} more from"
+ data-aria-controls="{{range .Subdirectories}}{{$prefix}}-{{.Suffix}} {{end}}"
+ data-aria-labelledby="{{$prefix}}-button {{$prefix}}"
+ data-id="{{$prefix}}-button">
+ <img alt="" src="/static/img/pkg-icon-arrowRight_24x24.svg" height="24" width="24">
+ </button>
+ {{- end -}}
+ {{- if .Root -}}
+ <a href="{{.Root.URL}}">{{.Root.Suffix}}</a>
+ {{if .Root.IsModule}}<span class="UnitHeader-badge">Module</span>{{end}}
+ </div>
+ <div class="UnitDirectories-mobileSynopsis">{{.Root.Synopsis}}</div>
+ </div>
+ </td>
+ <td class="UnitDirectories-desktopSynopsis">{{.Root.Synopsis}}</td>
+ {{- else -}}
+ <span>{{.Prefix}}</span>
+ </td>
+ <td class="UnitDirectories-desktopSynopsis"></td>
+ {{- end -}}
+ </tr>
+ {{- range .Subdirectories -}}
+ <tr data-id="{{$prefix}}-{{.Suffix}}">
+ <td>
+ <div class="UnitDirectories-subdirectory">
+ <a href="{{.URL}}">{{.Suffix}}</a>
+ {{if .IsModule}}<span class="UnitHeader-badge">Module</span>{{end}}
+ <div class="UnitDirectories-mobileSynopsis">{{.Synopsis}}</div>
+ </div>
+ </td>
+ <td class="UnitDirectories-desktopSynopsis">{{.Synopsis}}</td>
+ {{- end -}}
+ </tr>
+ {{- end -}}
+ </table>
+ {{- end -}}
+ </div>
+{{end}}
+
+{{define "legacy_unit_directories"}}
+ <div class="UnitDirectories js-unitDirectories" id="section-directories">
+ <h2 class="UnitDirectories-title">
+ <img height="25px" width="20px" src="/static/img/pkg-icon-folder_20x16.svg" alt="">Directories
+ </h2>
{{if (or .Subdirectories .NestedModules) }}
<table class="UnitDirectories-table" data-test-id="UnitDirectories-table">
<tr class="UnitDirectories-tableHeader">
diff --git a/content/static/html/pages/unit_details.tmpl b/content/static/html/pages/unit_details.tmpl
index 6dd91d0..805bb51 100644
--- a/content/static/html/pages/unit_details.tmpl
+++ b/content/static/html/pages/unit_details.tmpl
@@ -36,8 +36,14 @@
{{if .Details.SourceFiles}}
{{block "unit_files" .Details}}{{end}}
{{end}}
- {{if (or .Details.Subdirectories .Details.NestedModules)}}
- {{block "unit_directories" .Details}}{{end}}
+ {{if (.Experiments.IsActive "directory-tree")}}
+ {{if .Details.Directories}}
+ {{block "unit_directories" .Details}}{{end}}
+ {{end}}
+ {{else}}
+ {{if (or .Details.Subdirectories .Details.NestedModules)}}
+ {{block "legacy_unit_directories" .Details}}{{end}}
+ {{end}}
{{end}}
</div>
<div class="UnitDetails-meta" role="complementary" aria-label="links">
diff --git a/content/static/img/pkg-icon-arrowRight_24x24.svg b/content/static/img/pkg-icon-arrowRight_24x24.svg
new file mode 100644
index 0000000..5938446
--- /dev/null
+++ b/content/static/img/pkg-icon-arrowRight_24x24.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="black" width="18px" height="18px"><path d="M10 17l5-5-5-5v10z"/><path d="M0 24V0h24v24H0z" fill="none"/></svg>
\ No newline at end of file
diff --git a/content/static/js/table.js b/content/static/js/table.js
new file mode 100644
index 0000000..60f4f13
--- /dev/null
+++ b/content/static/js/table.js
@@ -0,0 +1,68 @@
+/*!
+ * @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 ExpandableRowsTableController {
+ constructor(table, expandAll) {
+ this.table = table;
+ this.expandAll = expandAll;
+ this.toggles = table.querySelectorAll('[data-aria-controls]');
+ this.setAttributes();
+ this.attachEventListeners();
+ this.updateVisibleItems();
+ }
+ setAttributes() {
+ for (const a of ['data-aria-controls', 'data-aria-labelledby', 'data-id']) {
+ this.table.querySelectorAll(`[${a}]`).forEach(t => {
+ t.setAttribute(a.replace('data-', ''), t.getAttribute(a) ?? '');
+ t.removeAttribute(a);
+ });
+ }
+ }
+ attachEventListeners() {
+ this.toggles.forEach(t => {
+ t.addEventListener('click', e => {
+ this.handleToggleClick(e);
+ this.updateVisibleItems();
+ });
+ });
+ this.expandAll?.addEventListener('click', () => {
+ this.handleExpandAllClick();
+ this.updateVisibleItems();
+ });
+ }
+ handleToggleClick(e) {
+ let target = e.currentTarget;
+ if (!target?.hasAttribute('aria-expanded')) {
+ target = this.table.querySelector(`button[aria-controls="${target?.getAttribute('aria-controls')}"]`);
+ }
+ const isExpanded = target?.getAttribute('aria-expanded') === 'true';
+ target?.setAttribute('aria-expanded', isExpanded ? 'false' : 'true');
+ e.stopPropagation();
+ }
+ handleExpandAllClick() {
+ this.table
+ .querySelectorAll('[aria-expanded=false]')
+ .forEach(t => t.setAttribute('aria-expanded', 'true'));
+ }
+ updateVisibleItems() {
+ this.toggles.forEach(t => {
+ const isExpanded = t?.getAttribute('aria-expanded') === 'true';
+ const rowIds = t?.getAttribute('aria-controls')?.trimEnd().split(' ');
+ rowIds?.forEach(id => {
+ const target = document.getElementById(`${id}`);
+ if (isExpanded) {
+ target?.classList.add('visible');
+ target?.classList.remove('hidden');
+ }
+ else {
+ target?.classList.add('hidden');
+ target?.classList.remove('visible');
+ }
+ });
+ });
+ }
+}
+//# sourceMappingURL=table.js.map
\ No newline at end of file
diff --git a/content/static/js/table.js.map b/content/static/js/table.js.map
new file mode 100644
index 0000000..1bfd1cc
--- /dev/null
+++ b/content/static/js/table.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"table.js","sourceRoot":"","sources":["table.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
diff --git a/content/static/js/table.test.ts b/content/static/js/table.test.ts
new file mode 100644
index 0000000..78b715b
--- /dev/null
+++ b/content/static/js/table.test.ts
@@ -0,0 +1,90 @@
+/*!
+ * @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.
+ */
+
+import { ExpandableRowsTableController } from './table';
+
+describe('ExpandableRowsTableController', () => {
+ let table: HTMLTableElement;
+ let toggle: HTMLButtonElement;
+
+ beforeEach(() => {
+ document.body.innerHTML = `
+ <table class="js-table">
+ <tbody>
+ <tr>
+ <th>Toggle</th>
+ <th>Foo</th>
+ <th>Bar</th>
+ </tr>
+ <tr>
+ <td></td>
+ <td data-id="label-id-1">Hello World</td>
+ <td>Simple row with no toggle or hidden elements</td>
+ </tr>
+ <tr data-aria-controls="hidden-row-id-1 hidden-row-id-2">
+ <td>
+ <button
+ type="button"
+ aria-expanded="false"
+ aria-label="2 more from"
+ data-aria-controls="hidden-row-id-1 hidden-row-id-2"
+ data-aria-labelledby="toggle-id label-id-2"
+ data-id="toggle-id"
+ >
+ +
+ </button>
+ </td>
+ <td data-id="label-id-2">
+ <span>Baz</span>
+ </td>
+ <td></td>
+ </tr>
+ <tr data-id="hidden-row-id-1">
+ <td></td>
+ <td>First hidden row</td>
+ <td></td>
+ </tr>
+ <tr data-id="hidden-row-id-2">
+ <td></td>
+ <td>Second hidden row</td>
+ <td></td>
+ </tr>
+ </tbody>
+ </table>
+ `;
+ table = document.querySelector<HTMLTableElement>('.js-table');
+ new ExpandableRowsTableController(table);
+ toggle = document.querySelector<HTMLButtonElement>('#toggle-id');
+ });
+
+ afterEach(() => {
+ document.body.innerHTML = '';
+ });
+
+ it('sets data-aria-* and data-id attributes to regular html attributes', () => {
+ expect(document.querySelector('#label-id-1')).toBeTruthy();
+ expect(
+ document.querySelector('[aria-controls="hidden-row-id-1 hidden-row-id-2"]')
+ ).toBeTruthy();
+ expect(document.querySelector('[aria-labelledby="toggle-id label-id-2"]')).toBeTruthy();
+ expect(document.querySelector('#toggle-id')).toBeTruthy();
+ expect(document.querySelector('#label-id-2')).toBeTruthy();
+ expect(document.querySelector('#hidden-row-id-1')).toBeTruthy();
+ expect(document.querySelector('#hidden-row-id-2')).toBeTruthy();
+ });
+
+ it('hides rows with unexpanded toggles', () => {
+ expect(document.querySelector('#hidden-row-id-1').classList).toContain('hidden');
+ expect(document.querySelector('#hidden-row-id-2').classList).toContain('hidden');
+ });
+
+ it('shows rows with expanded toggles', () => {
+ toggle.click();
+ expect(document.querySelector('#hidden-row-id-1').classList).toContain('visible');
+ expect(document.querySelector('#hidden-row-id-2').classList).toContain('visible');
+ });
+});
diff --git a/content/static/js/table.ts b/content/static/js/table.ts
new file mode 100644
index 0000000..3845a9d
--- /dev/null
+++ b/content/static/js/table.ts
@@ -0,0 +1,99 @@
+/*!
+ * @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.
+ */
+
+/**
+ * Controller for a table element with expandable rows. Adds event listeners to
+ * a toggle within a table row that controls visiblity of additional related
+ * rows in the table.
+ *
+ * @example
+ * ```typescript
+ * import {ExpandableRowsTableController} from '/static/js/table';
+ *
+ * const el = document .querySelector<HTMLTableElement>('.js-myTableElement')
+ * new ExpandableRowsTableController(el));
+ * ```
+ */
+export class ExpandableRowsTableController {
+ private table: HTMLTableElement;
+ private expandAll?: HTMLButtonElement;
+ private toggles: NodeListOf<HTMLTableRowElement>;
+
+ /**
+ * Create a table controller.
+ * @param table - The table element to which the controller binds.
+ */
+ constructor(table: HTMLTableElement, expandAll?: HTMLButtonElement) {
+ this.table = table;
+ this.expandAll = expandAll;
+ this.toggles = table.querySelectorAll<HTMLTableRowElement>('[data-aria-controls]');
+ this.setAttributes();
+ this.attachEventListeners();
+ this.updateVisibleItems();
+ }
+
+ /**
+ * setAttributes sets data-aria-* and data-id attributes to regular
+ * html attributes as a workaround for limitations from safehtml.
+ */
+ private setAttributes() {
+ for (const a of ['data-aria-controls', 'data-aria-labelledby', 'data-id']) {
+ this.table.querySelectorAll(`[${a}]`).forEach(t => {
+ t.setAttribute(a.replace('data-', ''), t.getAttribute(a) ?? '');
+ t.removeAttribute(a);
+ });
+ }
+ }
+
+ private attachEventListeners() {
+ this.toggles.forEach(t => {
+ t.addEventListener('click', e => {
+ this.handleToggleClick(e);
+ this.updateVisibleItems();
+ });
+ });
+ this.expandAll?.addEventListener('click', () => {
+ this.handleExpandAllClick();
+ this.updateVisibleItems();
+ });
+ }
+
+ private handleToggleClick(e: MouseEvent) {
+ let target = e.currentTarget as HTMLTableRowElement | null;
+ if (!target?.hasAttribute('aria-expanded')) {
+ target = this.table.querySelector(
+ `button[aria-controls="${target?.getAttribute('aria-controls')}"]`
+ );
+ }
+ const isExpanded = target?.getAttribute('aria-expanded') === 'true';
+ target?.setAttribute('aria-expanded', isExpanded ? 'false' : 'true');
+ e.stopPropagation();
+ }
+
+ private handleExpandAllClick() {
+ this.table
+ .querySelectorAll('[aria-expanded=false]')
+ .forEach(t => t.setAttribute('aria-expanded', 'true'));
+ }
+
+ private updateVisibleItems() {
+ this.toggles.forEach(t => {
+ const isExpanded = t?.getAttribute('aria-expanded') === 'true';
+ const rowIds = t?.getAttribute('aria-controls')?.trimEnd().split(' ');
+ rowIds?.forEach(id => {
+ const target = document.getElementById(`${id}`);
+ if (isExpanded) {
+ target?.classList.add('visible');
+ target?.classList.remove('hidden');
+ } else {
+ target?.classList.add('hidden');
+ target?.classList.remove('visible');
+ }
+ });
+ });
+ }
+}
diff --git a/content/static/js/unit.js b/content/static/js/unit.js
index d9c5981..b606ab1 100644
--- a/content/static/js/unit.js
+++ b/content/static/js/unit.js
@@ -7,6 +7,13 @@
import { CopyToClipboardController } from './clipboard.js';
import './toggle-tip.js';
+import { ExpandableRowsTableController } from '/static/js/table.js';
+
+document
+ .querySelectorAll('.js-expandableTable')
+ .forEach(
+ el => new ExpandableRowsTableController(el, document.querySelector('.js-expandAllDirectories'))
+ );
/**
* Instantiates CopyToClipboardController controller copy buttons