346 lines
11 KiB
HTML
346 lines
11 KiB
HTML
|
|
<html>
|
|
<head>
|
|
<title>Pattern</title>
|
|
<script>
|
|
let u = document.location.href.substring(0, document.location.href.search(/patterns/i));
|
|
let p = document.location.pathname.substring(document.location.pathname.search(/patterns/i));
|
|
p = p.replace(/\/$|\/index\.html/i, "").substring(9);
|
|
window.location = u + "?p=" + p;
|
|
|
|
</script>
|
|
</head>
|
|
<body data-prismjs-copy-timeout="1500">
|
|
<h2>What is it</h2>
|
|
<p>A tabs component that provides different sections of content that are displayed one at a time when the user selects that information. </p>
|
|
<h2>When to use it</h2>
|
|
<p>The tabbed user interface enables users to jump to their target section quickly. Tabs present like logically group information on the same page. Information should </p>
|
|
<ul>
|
|
<li>be logically chunked and ordered</li>
|
|
<li>be arallel in nature</li>
|
|
<li>show user's context</li>
|
|
<li>obvious where they begin and end </li>
|
|
</ul>
|
|
<p>Users should not need to see content of multiple tabs simultaneously and the user should be able to easily recognise where they are within the content. </p>
|
|
<h2>How to use it</h2>
|
|
<p>The structure of the tab set is defined in html. There are two forms supported. Adding a class of <code class="inline">.tab-group</code> to the container element will work in place of the <code class="inline">tabset</code> tag, and the tab panels can be defined using either <code class="inline">tab=""</code> or <code class="inline">data-tab=""</code>. Passing an optional element to the init function will initialise tabs within that element. Adding a <code class="inline">order=""</code> or <code class="inline">data-order=""</code> element to the tabset you can have the tabs sorted in a consistent order across tabsets.</p>
|
|
<h2>Example</h2>
|
|
<pre class="language-pug" tab="pug">tabset(order="tab2, tab1")
|
|
div(tab="tab1")
|
|
div(tab="tab2")
|
|
</pre>
|
|
<tabset id="tabs">
|
|
<pre class="language-html" tab="html">
|
|
<tabset id="uniqueID" order="tab title 2,tab title 1">
|
|
<div tab="[tab title 1]"></div>
|
|
<div tab="[tab title 2]"></div>
|
|
</tabset></pre>
|
|
<pre class="language-pug" tab="pug">tabset#uniqueID(order="tab title 2,tab title 1")
|
|
div(tab="[tab title 1]")
|
|
div(tab="[tab title 2]")
|
|
</pre>
|
|
<pre class="language-css" tab="css">tabset, .tab-group {
|
|
margin: 2rem 0 1rem 0;
|
|
}
|
|
tabset [role=tablist], .tab-group [role=tablist] {
|
|
display: -webkit-box;
|
|
display: -ms-flexbox;
|
|
display: flex;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
tabset [role=tablist] li.separator, .tab-group [role=tablist] li.separator {
|
|
border-bottom: 1px solid #7f7f7f;
|
|
display: inline-block;
|
|
margin: 0.45rem 0 0 0;
|
|
width: 100%;
|
|
}
|
|
tabset [role=tablist] li[role=tab], .tab-group [role=tablist] li[role=tab] {
|
|
background-color: #FFF;
|
|
border-left: 1px solid #7f7f7f;
|
|
border-right: 1px solid #7f7f7f;
|
|
border-radius: 0.5rem 0.5rem 0 0;
|
|
border-top: 1px solid #7f7f7f;
|
|
cursor: pointer;
|
|
display: inline;
|
|
margin: 0;
|
|
padding: 1rem 1.5rem 0.14rem 1.5rem;
|
|
z-index: 2;
|
|
}
|
|
tabset [role=tablist] li[role=tab]:last-of-type, .tab-group [role=tablist] li[role=tab]:last-of-type {
|
|
border-right: 1px solid #7f7f7f;
|
|
}
|
|
tabset [role=tablist] li[role=tab]:not(.selected), .tab-group [role=tablist] li[role=tab]:not(.selected) {
|
|
background-color: #f0f0f0;
|
|
border-bottom: 1px solid #7f7f7f;
|
|
}
|
|
tabset [role=tablist] li[role=tab] span, .tab-group [role=tablist] li[role=tab] span {
|
|
display: block;
|
|
margin: 0 0 0.5rem 0;
|
|
}
|
|
tabset .tab-hidden, .tab-group .tab-hidden {
|
|
display: none;
|
|
}
|
|
tabset [role=tabpanel], .tab-group [role=tabpanel] {
|
|
background-color: #FFF;
|
|
border: 1px solid #7f7f7f;
|
|
border-top: none;
|
|
padding: 1rem;
|
|
z-index: 1;
|
|
}
|
|
tabset [role=tabpanel]:not(.open), .tab-group [role=tabpanel]:not(.open) {
|
|
display: none;
|
|
}</pre>
|
|
<div tab="scss">
|
|
<h2>Example</h2>
|
|
<pre class="language-sass">@use "scss/core/tabs/_tabs";
|
|
@include tabs{
|
|
// optional content block
|
|
};
|
|
</pre>
|
|
<pre class="language-sass">// DS2 core (c) 2024 Alexander McIlwraith
|
|
// Licensed under CC BY-SA 4.0
|
|
|
|
$tab-border: #7f7f7f !default;
|
|
$tab-panel-background-color: #FFF !default;
|
|
$tab-panel-top-border: #7f7f7f !default;
|
|
$tab-panel-top-border-width: 1px !default;
|
|
$tab-selected: #FFF !default;
|
|
$tab-selected-text: #000 !default;
|
|
$tab-notselected: #f0f0f0 !default;
|
|
$tab-notselected-text: #000 !default;
|
|
|
|
@mixin tabs {
|
|
tabset, .tab-group {
|
|
margin: 2rem 0 1rem 0;
|
|
|
|
[role="tablist"] {
|
|
display: flex;
|
|
margin: 0;
|
|
padding: 0;
|
|
|
|
|
|
li {
|
|
&.separator {
|
|
border-bottom: 1px solid $tab-border;
|
|
display: inline-block;
|
|
margin: .45rem 0 0 0;
|
|
width: 100%;
|
|
}
|
|
|
|
&[role="tab"] {
|
|
background-color: $tab-selected;
|
|
border-left: 1px solid $tab-border;
|
|
border-right: 1px solid $tab-border;
|
|
border-radius: .5rem .5rem 0 0;
|
|
border-top: 1px solid $tab-border;
|
|
cursor:pointer;
|
|
display: inline;
|
|
margin: 0;
|
|
padding: 1rem 1.5rem .14rem 1.5rem;
|
|
z-index: 2;
|
|
|
|
&:last-of-type {
|
|
border-right: 1px solid $tab-border;
|
|
}
|
|
|
|
&:not(.selected) {
|
|
background-color: $tab-notselected;
|
|
border-bottom: 1px solid $tab-border;
|
|
/// color
|
|
}
|
|
|
|
span {
|
|
display: block;
|
|
margin: 0 0 .5rem 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.tab-hidden {
|
|
display: none;
|
|
}
|
|
|
|
[role="tabpanel"] {
|
|
background-color: $tab-selected;
|
|
border: 1px solid $tab-border;
|
|
border-top: none;
|
|
padding: 1rem;
|
|
z-index: 1;
|
|
|
|
&:not(.open) {
|
|
display: none;
|
|
}
|
|
}
|
|
@content;
|
|
}
|
|
}</pre>
|
|
</div>
|
|
<div tab="js">
|
|
<h2>Example</h2>
|
|
<pre class="language-js">import * as tabs from "./js/core/tabs/_tabs.js";
|
|
tabs.init();</pre>
|
|
<pre class="language-js">/* DS2 core (c) 2024 Alexander McIlwraith
|
|
Released under Creative Commons Attribution-ShareAlike 4.0 International
|
|
*/
|
|
|
|
// create a pure JS mouse click event
|
|
const click = new MouseEvent('click', {
|
|
view: window,
|
|
bubbles: false,
|
|
cancelable: true
|
|
});
|
|
|
|
const waitForElement = (selector) => {
|
|
return new Promise(resolve => {
|
|
if (document.querySelector(selector)) {
|
|
return resolve(document.querySelector(selector));
|
|
}
|
|
|
|
const observer = new MutationObserver(mutations => {
|
|
if (document.querySelector(selector)) {
|
|
observer.disconnect();
|
|
resolve(document.querySelector(selector));
|
|
}
|
|
});
|
|
|
|
// If you get "parameter 1 is not of type 'Node'" error, see https://stackoverflow.com/a/77855838/492336
|
|
observer.observe(document.body, {
|
|
childList: true,
|
|
subtree: true
|
|
});
|
|
});
|
|
}
|
|
|
|
const chooseTab = (tab) => {
|
|
const siblings = Array.from(tab.parentNode.children);
|
|
siblings.forEach(sibling => sibling.classList.remove("selected"));
|
|
tab.classList.add("selected");
|
|
|
|
const tabPanels = Array.from(tab.parentNode.parentNode.children)
|
|
tabPanels.forEach(panel => panel.classList.remove("open"));
|
|
|
|
const tabPanelId = tab.getAttribute("id").replace("tab", "tab-panel");
|
|
document.getElementById(tabPanelId).classList.add("open");
|
|
}
|
|
|
|
let pushState = 0;
|
|
let tabsetCount = 0;
|
|
|
|
export function init(container = document, spacer = true, args = {}) {
|
|
container.querySelectorAll(".tab-group, tabset").forEach(tabGroup => {
|
|
if (tabGroup.querySelector("[role=tablist]") === null) {
|
|
if (tabGroup.getAttribute("id") == null) {
|
|
tabGroup.setAttribute("id", "tab-group-" + tabsetCount);
|
|
tabsetCount++;
|
|
}
|
|
|
|
const tabgroup = tabGroup.getAttribute("id");
|
|
let tablist = "";
|
|
|
|
Array.from(tabGroup.children).forEach(child => {
|
|
|
|
// is details?
|
|
let dtls = child.nodeName == "DETAILS" ? true : false;
|
|
|
|
// get the tab text
|
|
let tab = dtls ? child.querySelector("summary").innerHTML : child.getAttribute("tab") || child.getAttribute("data-tab");
|
|
|
|
// if the tab text is not blank
|
|
if (tab !== null) {
|
|
const tabID = tab.replace(/\W+/g, "-").toLowerCase();
|
|
|
|
|
|
// define the tab panel content
|
|
let tabPanel = null;
|
|
if (dtls) {
|
|
tabPanel = child;
|
|
tabPanel.setAttribute("open", "");
|
|
} else {
|
|
tabPanel = document.createElement('div');
|
|
tabPanel.appendChild(child.cloneNode(true));
|
|
}
|
|
|
|
tabPanel.id = `tab-panel-${tabgroup}-${tabID}`;
|
|
tabPanel.className = tablist === "" ? "open" : "";
|
|
tabPanel.setAttribute("role", "tabpanel");
|
|
tabPanel.setAttribute("tabindex", "0");
|
|
tabPanel.setAttribute("aria-labelledby", `tab-${tabgroup}-${tabID}`);
|
|
child.parentNode.replaceChild(tabPanel, child);
|
|
tablist += `<li tabindex="0" role="tab" id="tab-${tabgroup}-${tabID}"><span>${tab}</span></li>`;
|
|
} else {
|
|
child.classList.add("tab-hidden");
|
|
}
|
|
});
|
|
|
|
const ul = document.createElement('ul');
|
|
ul.setAttribute("role", "tablist");
|
|
tabGroup.insertBefore(ul, tabGroup.firstChild);
|
|
ul.innerHTML = spacer != true ? `${tablist}` : `${tablist}<li role="separator" class="separator"></li>`;
|
|
|
|
if ( tabGroup.hasAttribute("order") || tabGroup.hasAttribute("data-order") ) {
|
|
let order = (tabGroup.getAttribute("order") || tabGroup.getAttribute("data-order")).split(",");
|
|
|
|
const items = Array.from(ul.getElementsByTagName("li"));
|
|
items.sort((a, b) => {
|
|
console.log("here")
|
|
const aa = order.indexOf(a.textContent.trim());
|
|
const bb = order.indexOf(b.textContent.trim());
|
|
|
|
// Check if both items exist in orderArray
|
|
if (aa === -1) return 1; // Move to the end if not found
|
|
if (bb === -1) return -1; // Move to the end if not found
|
|
|
|
return aa - bb; // Sort based on the defined order
|
|
});
|
|
|
|
ul.innerHTML = '';
|
|
items.forEach(item => ul.appendChild(item));
|
|
}
|
|
|
|
tabGroup.querySelectorAll('[role="tab"]').forEach(tab => {
|
|
tab.addEventListener("click", (evt) => {
|
|
if (pushState == 0) {
|
|
window.history.pushState({rand: Math.random(), pushState: pushState, tab: tab.parentNode.firstChild.getAttribute("id")}, "", `#${tab.parentNode.firstChild.getAttribute("id")}`);
|
|
pushState++;
|
|
}
|
|
|
|
chooseTab(evt.currentTarget);
|
|
window.history.pushState({rand: Math.random(), pushState: pushState, tab: tab.getAttribute("id")}, "", `#${tab.getAttribute("id")}`);
|
|
pushState++;
|
|
});
|
|
|
|
tab.addEventListener("keypress", (e) => {
|
|
e.preventDefault();
|
|
if( e.which == 32 || e.which == 13 ) {
|
|
e.currentTarget.dispatchEvent(click);
|
|
}
|
|
})
|
|
});
|
|
ul.querySelector("li").classList.add("selected");
|
|
}
|
|
|
|
if (document.location.hash != "" && document.location.hash.substring(0,5) == "#tab-") {
|
|
waitForElement(document.location.hash).then((el) => {
|
|
//el.scrollIntoView();
|
|
el.focus();
|
|
el.dispatchEvent(click);
|
|
});
|
|
}
|
|
});
|
|
|
|
window.addEventListener("popstate", function (e) {
|
|
e.preventDefault();
|
|
if (e.state != null) {
|
|
chooseTab(document.querySelector(`#${e.state.tab}`));
|
|
} else {
|
|
history.go(-1);
|
|
}
|
|
});
|
|
}
|
|
</pre>
|
|
</div>
|
|
</tabset>
|
|
</body>
|
|
</html> |