diff --git a/exampleSite/content/shortcodes/tabs.en.md b/exampleSite/content/shortcodes/tabs.en.md new file mode 100644 index 0000000000..d664eeacaf --- /dev/null +++ b/exampleSite/content/shortcodes/tabs.en.md @@ -0,0 +1,119 @@ +--- +title: Tabbed views +description : "Synchronize selection of content in different tabbed views" +--- + +Choose which content to see across the page. Very handy for providing code +snippets for multiple languages or providing configuration in different formats. + +## Code example + + {{}} + {{%/* tab name="python" */%}} + ```python + print("Hello World!") + ``` + {{%/* /tab */%}} + {{%/* tab name="R" */%}} + ```R + > print("Hello World!") + ``` + {{%/* /tab */%}} + {{%/* tab name="Bash" */%}} + ```Bash + echo "Hello World!" + ``` + {{%/* /tab */%}} + {{}} + +Renders as: + +{{< tabs >}} +{{% tab name="python" %}} +```python +print("Hello World!") +``` +{{% /tab %}} +{{% tab name="R" %}} +```R +> print("Hello World!") +``` +{{% /tab %}} +{{% tab name="Bash" %}} +```Bash +echo "Hello World!" +``` +{{% /tab %}} +{{< /tabs >}} + +Tab views with the same tabs that belong to the same group sychronize their selection: + +{{< tabs >}} +{{% tab name="python" %}} +```python +print("Hello World!") +``` +{{% /tab %}} +{{% tab name="R" %}} +```R +> print("Hello World!") +``` +{{% /tab %}} +{{% tab name="Bash" %}} +```Bash +echo "Hello World!" +``` +{{% /tab %}} +{{< /tabs >}} + +## Config example + + {{}} + {{%/* tab name="json" */%}} + ```json + { + "Hello": "World" + } + ``` + {{%/* /tab */%}} + {{%/* tab name="XML" */%}} + ```xml + World + ``` + {{%/* /tab */%}} + {{%/* tab name="properties" */%}} + ```properties + Hello = World + ``` + {{%/* /tab */%}} + {{}} + +Renders as: + +{{< tabs groupId="config" >}} +{{% tab name="json" %}} +```json +{ + "Hello": "World" +} +``` +{{% /tab %}} +{{% tab name="XML" %}} +```xml +World +``` +{{% /tab %}} +{{% tab name="properties" %}} +```properties +Hello = World +``` +{{% /tab %}} +{{< /tabs >}} + +{{% notice warning %}} +When using tab views with different content sets, make sure to use a common `groupId` for equal sets but distinct +`groupId` for different sets. The `groupId` defaults to `'default'`. +**Take this into account across the whole site!** +The tab selection is restored automatically based on the `groupId` and if it cannot find a tab item because it came + from the `'default'` group on a different page then all tabs will be empty at first. +{{% /notice %}} diff --git a/layouts/partials/header.html b/layouts/partials/header.html index 2f97f76d83..8ee0334bc0 100644 --- a/layouts/partials/header.html +++ b/layouts/partials/header.html @@ -17,6 +17,7 @@ + {{with .Site.Params.themeVariant}} diff --git a/layouts/shortcodes/tab.html b/layouts/shortcodes/tab.html new file mode 100644 index 0000000000..d258e00ac4 --- /dev/null +++ b/layouts/shortcodes/tab.html @@ -0,0 +1,12 @@ +{{ if .Parent }} + {{ $name := trim (.Get "name") " " }} + {{ if not (.Parent.Scratch.Get "tabs") }} + {{ .Parent.Scratch.Set "tabs" slice }} + {{ end }} + {{ with .Inner }} + {{ $.Parent.Scratch.Add "tabs" (dict "name" $name "content" . ) }} + {{ end }} +{{ else }} + {{- errorf "[%s] %q: tab shortcode missing its parent" site.Language.Lang .Page.Path -}} +{{ end}} + diff --git a/layouts/shortcodes/tabs.html b/layouts/shortcodes/tabs.html new file mode 100644 index 0000000000..417a808783 --- /dev/null +++ b/layouts/shortcodes/tabs.html @@ -0,0 +1,21 @@ +{{ with .Inner }}{{/* don't do anything, just call it */}}{{ end }} +{{ $groupId := default "default" (.Get "groupId") }} +
+
+ {{ range $idx, $tab := .Scratch.Get "tabs" }} + + {{ end }} +
+
+ {{ range $idx, $tab := .Scratch.Get "tabs" }} +
+ {{ .content }} +
+ {{ end }} +
+
diff --git a/static/css/tabs.css b/static/css/tabs.css new file mode 100644 index 0000000000..2ad2728772 --- /dev/null +++ b/static/css/tabs.css @@ -0,0 +1,43 @@ +#body .tab-nav-button { + border-width: 1px 1px 1px 1px !important; + border-color: #ccc !important; + border-radius: 4px 4px 0 0 !important; + background-color: #ddd !important; + float: left; + display: block; + position: relative; + margin-left: 4px; + bottom: -1px; +} +#body .tab-nav-button:first-child { + margin-left: 0px; +} +#body .tab-nav-button.active { + background-color: #fff !important; + border-bottom-color: #fff !important; +} + +#body .tab-panel { + margin-top: 32px; + margin-bottom: 32px; +} +#body .tab-content { + display: block; + clear: both; + padding: 8px; + border-width: 1px; + border-style: solid; + border-color: #ccc; +} +#body .tab-content .tab-item{ + display: none; +} + +#body .tab-content .tab-item.active{ + display: block; +} + +#body .tab-item pre{ + margin-bottom: 0; + margin-top: 0; +} diff --git a/static/js/learn.js b/static/js/learn.js index b0d199da86..85e09d55b7 100644 --- a/static/js/learn.js +++ b/static/js/learn.js @@ -47,6 +47,57 @@ function fallbackMessage(action) { return actionMsg; } +function switchTab(tabGroup, tabId) { + allTabItems = jQuery("[data-tab-group='"+tabGroup+"']"); + targetTabItems = jQuery("[data-tab-group='"+tabGroup+"'][data-tab-item='"+tabId+"']"); + + // if event is undefined then switchTab was called from restoreTabSelection + // so it's not a button event and we don't need to safe the selction or + // prevent page jump + var isButtonEvent = event != undefined; + + if(isButtonEvent){ + // save button position relative to viewport + var yposButton = event.target.getBoundingClientRect().top; + } + + allTabItems.removeClass("active"); + targetTabItems.addClass("active"); + + if(isButtonEvent){ + // reset screen to the same position relative to clicked button to prevent page jump + var yposButtonDiff = event.target.getBoundingClientRect().top - yposButton; + window.scrollTo(window.scrollX, window.scrollY+yposButtonDiff); + + // Store the selection to make it persistent + if(window.localStorage){ + var selectionsJSON = window.localStorage.getItem("tabSelections"); + if(selectionsJSON){ + var tabSelections = JSON.parse(selectionsJSON); + }else{ + var tabSelections = {}; + } + tabSelections[tabGroup] = tabId; + window.localStorage.setItem("tabSelections", JSON.stringify(tabSelections)); + } + } +} + +function restoreTabSelections() { + if(window.localStorage){ + var selectionsJSON = window.localStorage.getItem("tabSelections"); + if(selectionsJSON){ + var tabSelections = JSON.parse(selectionsJSON); + }else{ + var tabSelections = {}; + } + Object.keys(tabSelections).forEach(function(tabGroup) { + var tabItem = tabSelections[tabGroup]; + switchTab(tabGroup, tabItem); + }); + } +} + // for the window resize $(window).resize(function() { setMenuHeight(); @@ -83,6 +134,8 @@ $(window).resize(function() { jQuery(document).ready(function() { + restoreTabSelections(); + jQuery('#sidebar .category-icon').on('click', function() { $( this ).toggleClass("fa-angle-down fa-angle-right") ; $( this ).parent().parent().children('ul').toggle() ; @@ -229,7 +282,7 @@ jQuery(document).ready(function() { e.stopPropagation(); } }); - + jQuery(document).keydown(function(e) { // prev links - left arrow key if(e.which == '37') { @@ -264,7 +317,7 @@ jQuery(document).ready(function() { }); } - /** + /** * Fix anchor scrolling that hides behind top nav bar * Courtesy of https://stackoverflow.com/a/13067009/28106 * @@ -346,7 +399,7 @@ jQuery(document).ready(function() { $(document).ready($.proxy(anchorScrolls, 'init')); })(window.document, window.history, window.location); - + }); jQuery(window).on('load', function() {