Skip to content

Commit d8acfc4

Browse files
authored
Add tabbed widget JS and CSS to build (#2180)
1 parent df49da8 commit d8acfc4

File tree

7 files changed

+255
-10
lines changed

7 files changed

+255
-10
lines changed

README.asciidoc

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -656,7 +656,7 @@ endif::[]
656656
== Shared attributes
657657

658658
To facilitate consistency across the documentation, there are shared attributes
659-
for common terms and URLs: https://github.com/elastic/docs/blob/master/shared/attributes.asciidoc.
659+
for common terms and URLs: https://github.com/elastic/docs/blob/master/shared/attributes.asciidoc.
660660
There are also attributes related to the versions and branches that are used to
661661
build our books
662662
(for example: https://github.com/elastic/docs/blob/master/shared/versions/stack/master.asciidoc).
@@ -1893,3 +1893,82 @@ named for their IDs:
18931893

18941894
To link to "Section two" from an external
18951895
document, you would use the URL: `section-one.html#section-two`
1896+
1897+
[[tabbed-widgets]]
1898+
== Tabbed widgets
1899+
1900+
Improve the usability of your docs by adding tabbed widgets.
1901+
These handy widgets let you conditionally display content based on the selected tab.
1902+
Best of all, tabbed widgets listen to each other – all widgets on the same page and with the same `data-tab-group` will change content when any tab on the page is selected.
1903+
1904+
How do they work? I'm glad you asked.
1905+
Tabbed widgets use https://docs.asciidoctor.org/asciidoc/latest/pass/pass-block/[HTML passthrough blocks] to pass raw HTML into the build.
1906+
Because of this hack, you must use `include::` statements to render content within a tabbed widget.
1907+
1908+
Here's an example:
1909+
1910+
**`widget.asciidoc`**
1911+
1912+
[source,asciidoc]
1913+
----
1914+
++++
1915+
<div class="tabs" data-tab-group="custom-tab-group-name"> <1>
1916+
<div role="tablist" aria-label="Human readable name of tab group">
1917+
<button role="tab"
1918+
aria-selected="true" <2>
1919+
aria-controls="cloud-tab-config-agent" <3>
1920+
id="cloud-config-agent"> <4>
1921+
Tab #1 title
1922+
</button>
1923+
<button role="tab"
1924+
aria-selected="false"
1925+
aria-controls="self-managed-tab-config-agent"
1926+
id="self-managed-config-agent"
1927+
tabindex="-1"> <5>
1928+
Tab #2 title
1929+
</button>
1930+
</div>
1931+
<div tabindex="0"
1932+
role="tabpanel"
1933+
id="cloud-tab-config-agent"
1934+
aria-labelledby="cloud-config-agent">
1935+
++++
1936+
1937+
// You must use a tagged region for Asciidoc content to render. For example:
1938+
// include::content.asciidoc[tag=central-config]
1939+
1940+
++++
1941+
</div>
1942+
<div tabindex="0"
1943+
role="tabpanel"
1944+
id="self-managed-tab-config-agent"
1945+
aria-labelledby="self-managed-config-agent"
1946+
hidden="">
1947+
++++
1948+
1949+
// You must use a tagged region for Asciidoc content to render. For example:
1950+
// include::content.asciidoc[tag=reg-config]
1951+
1952+
++++
1953+
</div>
1954+
</div>
1955+
++++
1956+
----
1957+
<1> Any tabbed widgets on the same page and with the same name will sync tabs when switched
1958+
<2> Only one tab should have aria-selected set to true. This tab will be selected by default
1959+
<3> The `button.aria-controls` value must match the `div.id` value of its corresponding content bucket
1960+
<4> The `button.id` value must match the `div.aria-labelledby` value of its corresponding content bucket
1961+
<5> All unselected tabs must have a `tabindex` of `-1`
1962+
1963+
**`content.asciidoc`**
1964+
1965+
[source,asciidoc]
1966+
----
1967+
// tag::central-config[]
1968+
This is where the content for tab #1 goes.
1969+
// end::central-config[]
1970+
1971+
// tag::reg-config[]
1972+
This is the content for tab #2 goes.
1973+
// end::reg-config[]
1974+
----

resources/web/docs.js.licenses

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,19 +53,19 @@
5353
* limitations under the License. */
5454
/* details-polyfill
5555
* The MIT License (MIT)
56-
*
56+
*
5757
* Copyright (c) 2018 Rico Sta. Cruz
58-
*
58+
*
5959
* Permission is hereby granted, free of charge, to any person obtaining a copy
6060
* of this software and associated documentation files (the "Software"), to deal
6161
* in the Software without restriction, including without limitation the rights
6262
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
6363
* copies of the Software, and to permit persons to whom the Software is
6464
* furnished to do so, subject to the following conditions:
65-
*
65+
*
6666
* The above copyright notice and this permission notice shall be included in all
6767
* copies or substantial portions of the Software.
68-
*
68+
*
6969
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
7070
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
7171
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -86,7 +86,7 @@
8686
* distribute, sublicense, and/or sell copies of the Software, and to
8787
* permit persons to whom the Software is furnished to do so, subject to
8888
* the following conditions:
89-
*
89+
*
9090
* The above copyright notice and this permission notice shall be
9191
* included in all copies or substantial portions of the Software.
9292
*
@@ -130,10 +130,10 @@
130130
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
131131
* copies of the Software, and to permit persons to whom the Software is
132132
* furnished to do so, subject to the following conditions:
133-
*
133+
*
134134
* The above copyright notice and this permission notice shall be included in
135135
* all copies or substantial portions of the Software.
136-
*
136+
*
137137
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
138138
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
139139
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -289,4 +289,4 @@
289289
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
290290
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
291291
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
292-
* THE SOFTWARE. */
292+
* THE SOFTWARE. */
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
export const switchTabs = () => {
2+
window.addEventListener("DOMContentLoaded", () => {
3+
const tabs = document.querySelectorAll('[role="tab"]');
4+
const tabList = document.querySelector('[role="tablist"]');
5+
// Add a click event handler to each tab
6+
tabs.forEach(tab => {
7+
tab.addEventListener("click", changeTabs);
8+
});
9+
// Enable arrow navigation between tabs in the tab list
10+
let tabFocus = 0;
11+
tabList.addEventListener("keydown", e => {
12+
// Move right
13+
if (e.keyCode === 39 || e.keyCode === 37) {
14+
tabs[tabFocus].setAttribute("tabindex", -1);
15+
if (e.keyCode === 39) {
16+
tabFocus++;
17+
// If we're at the end, go to the start
18+
if (tabFocus >= tabs.length) {
19+
tabFocus = 0;
20+
}
21+
// Move left
22+
} else if (e.keyCode === 37) {
23+
tabFocus--;
24+
// If we're at the start, move to the end
25+
if (tabFocus < 0) {
26+
tabFocus = tabs.length - 1;
27+
}
28+
}
29+
tabs[tabFocus].setAttribute("tabindex", 0);
30+
tabs[tabFocus].focus();
31+
}
32+
});
33+
});
34+
}
35+
36+
const setActiveTab = (target) => {
37+
const parent = target.parentNode;
38+
const grandparent = parent.parentNode;
39+
// Remove all current selected tabs
40+
parent
41+
.querySelectorAll('[aria-selected="true"]')
42+
.forEach(t => t.setAttribute("aria-selected", false));
43+
// Set this tab as selected
44+
target.setAttribute("aria-selected", true);
45+
// Hide all tab panels
46+
grandparent
47+
.querySelectorAll('[role="tabpanel"]')
48+
.forEach(p => p.setAttribute("hidden", true));
49+
// Show the selected panel
50+
grandparent.parentNode
51+
.querySelector(`#${target.getAttribute("aria-controls")}`)
52+
.removeAttribute("hidden");
53+
}
54+
55+
const changeTabs = (e) => {
56+
// get the containing list of the tab that was just clicked
57+
const tabList = e.target.parentNode;
58+
59+
// get all of the sibling tabs
60+
const buttons = Array.apply(null, tabList.querySelectorAll('button'));
61+
62+
// loop over the siblings to discover which index thje clicked one was
63+
const { index } = buttons.reduce(({ found, index }, button) => {
64+
if (!found && buttons[index] === e.target) {
65+
return { found: true, index };
66+
} else if (!found) {
67+
return { found, index: index + 1 };
68+
} else {
69+
return { found, index };
70+
}
71+
}, { found: false, index: 0 });
72+
73+
// get the tab container
74+
const container = tabList.parentNode;
75+
// read the data-tab-group value from the container, e.g. "os"
76+
const { tabGroup } = container.dataset;
77+
// get a list of all the tab groups that match this value on the page
78+
const groups = document.querySelectorAll('[data-tab-group=' + tabGroup + ']');
79+
80+
// for each of the found tab groups, find the tab button at the previously discovered index and select it for each group
81+
groups.forEach((group) => {
82+
const target = group.querySelectorAll('button')[index];
83+
setActiveTab(target);
84+
});
85+
}

resources/web/docs_js/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import AlternativeSwitcher from "./components/alternative_switcher";
22
import ConsoleWidget from "./components/console_widget";
33
import Modal from "./components/modal";
44
import mount from "./components/mount";
5+
import {switchTabs} from "./components/tabbed_widget";
56
import {Cookies, $} from "./deps";
67
import {lang_strings} from "./localization";
78
import store from "./store";
@@ -281,3 +282,6 @@ $(function() {
281282

282283
// Test comment used to detect unminifed JS in tests
283284
});
285+
286+
// Tabbed widgets
287+
switchTabs();
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#guide {
2+
.tabs {
3+
width: 100%;
4+
}
5+
[role=tablist] {
6+
margin: 0 0 -0.1em;
7+
overflow: visible;
8+
}
9+
[role=tab] {
10+
position: relative;
11+
padding: 0.3em 0.5em 0.4em;
12+
border: 1px solid hsl(219, 1%, 72%);
13+
border-radius: 0.2em 0.2em 0 0;
14+
overflow: visible;
15+
font-family: inherit;
16+
font-size: inherit;
17+
background: hsl(220, 20%, 94%);
18+
}
19+
[role=tab]:hover::before,
20+
[role=tab]:focus::before,
21+
[role=tab][aria-selected="true"]::before {
22+
position: absolute;
23+
bottom: 100%;
24+
right: -1px;
25+
left: -1px;
26+
border-radius: 0.2em 0.2em 0 0;
27+
border-top: 3px solid hsl(219, 1%, 72%);
28+
content: '';
29+
}
30+
[role=tab][aria-selected="true"] {
31+
border-radius: 0;
32+
background: hsl(220, 43%, 99%);
33+
outline: 0;
34+
}
35+
[role=tab][aria-selected="true"]:not(:focus):not(:hover)::before {
36+
border-top: 5px solid hsl(218, 96%, 48%);
37+
}
38+
[role=tab][aria-selected="true"]::after {
39+
position: absolute;
40+
z-index: 3;
41+
bottom: -1px;
42+
right: 0;
43+
left: 0;
44+
height: 0.3em;
45+
background: hsl(220, 43%, 99%);
46+
box-shadow: none;
47+
content: '';
48+
}
49+
[role="tab"]:hover,
50+
[role="tab"]:focus,
51+
[role="tab"]:active {
52+
outline: 0;
53+
border-radius: 0;
54+
color: inherit;
55+
}
56+
[role="tab"]:hover::before,
57+
[role="tab"]:focus::before {
58+
border-color: hsl(218, 96%, 48%);
59+
}
60+
[role="tabpanel"] {
61+
position: relative;
62+
z-index: 2;
63+
padding: 1em;
64+
border: 1px solid hsl(219, 1%, 72%);
65+
border-radius: 0 0.2em 0.2em 0.2em;
66+
box-shadow: 0 0 0.2em hsl(219, 1%, 72%);
67+
background: hsl(220, 43%, 99%);
68+
margin-bottom: 1em;
69+
}
70+
[role="tabpanel"] p {
71+
margin: 0;
72+
}
73+
[role="tabpanel"] * + p {
74+
margin-top: 1em;
75+
}
76+
}

resources/web/styles.pcss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
@import './style/settings_modal.pcss';
2525
@import './style/sidebar.pcss';
2626
@import './style/table.pcss';
27+
@import './style/tabbed_widget.pcss';
2728
@import './style/this_page.pcss';
2829
@import './style/toc.pcss';
2930
@import './style/util.pcss';

resources/web/template.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
<script>dataLayer = [];</script><noscript><iframe src="//www.googletagmanager.com/ns.html?id=GTM-58RLH5" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
5151
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= '//www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); })(window,document,'script','dataLayer','GTM-58RLH5');</script>
5252
<!-- End Google Tag Manager -->
53-
53+
5454
<!-- Global site tag (gtag.js) - Google Analytics -->
5555
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-12395217-16"></script>
5656
<script>

0 commit comments

Comments
 (0)