Skip to content

Commit

Permalink
feat: introduce tabs in the sidebar
Browse files Browse the repository at this point in the history
  • Loading branch information
isqua committed May 8, 2024
1 parent a9bb2fc commit 35dd00e
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 10 deletions.
9 changes: 8 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,15 @@ <h1 class="hero-title">Showcase your journey</h1>
</div>
</button>
<aside id="sidebar" class="sidebar hidden">
<div role="tablist" class="sidebar-tablist">
<button class="sidebar-tab" id="countries-tab" type="button" role="tab" aria-selected="true" aria-controls="countries">
<svg class="sidebar-icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 21a9.004 9.004 0 0 0 8.716-6.747M12 21a9.004 9.004 0 0 1-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 0 1 7.843 4.582M12 3a8.997 8.997 0 0 0-7.843 4.582m15.686 0A11.953 11.953 0 0 1 12 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0 1 21 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0 1 12 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 0 1 3 12c0-1.605.42-3.113 1.157-4.418" />
</svg>
</button>
</div>
<form action="#" class="sidebar-content form">
<div>
<div id="countries" role="tabpanel" tabindex="0" aria-labelledby="countries-tab" class="sidebar-tabpanel">
<ul class="countries-list">
</ul>
</div>
Expand Down
42 changes: 34 additions & 8 deletions src/sidebar/Sidebar.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,50 @@
bottom: 0;
background-color: #334155;
box-shadow: 0 0 10px rgba(0,0,0,0.3);
padding: 0 0 0 32px;
padding: 0;
z-index: 1;
overflow: hidden;
width: 450px;
max-width: 70vw;
display: flex;
transition: opacity 0.2s ease, transform 0.2s ease;
}

.sidebar-content {
max-height: 100%;
overflow: auto;
padding: 12px;
box-sizing: border-box;
}

.sidebar.hidden {
opacity: 0.5;
transform: translateX(-100%);
user-select: none;
pointer-events: none;
}

.sidebar-tablist {
flex-basis: 44px;
flex-shrink: 0;
display: flex;
padding-top: 48px;
flex-direction: column;
}

.sidebar-tab {
border: 0;
background-color: transparent;
color: rgba(255,255,255,0.5);
padding: 4px 10px 5px;
cursor: pointer;
}

.sidebar-tab[aria-selected="true"] {
color: #fff;
}

.sidebar-content {
max-height: 100%;
overflow: auto;
padding: 12px 12px 12px 0;
box-sizing: border-box;
flex-grow: 1;
}

.sidebar-control {
z-index: 10;
position: fixed;
Expand Down Expand Up @@ -85,3 +107,7 @@
top: 6px;
transform: rotate(-135deg);
}

.sidebar-tabpanel-hidden {
display: none;
}
6 changes: 5 additions & 1 deletion src/sidebar/Sidebar.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { TabsAutomatic } from './Tabs';

import './Sidebar.css';

const CLOSED_MENU_CLASS = 'hidden';
Expand All @@ -7,7 +9,9 @@ export class Sidebar {
constructor(
private sidebar: HTMLElement,
private control: HTMLButtonElement,
) { }
) {
new TabsAutomatic(this.sidebar);
}

initialize() {
this.control.addEventListener("click", () => {
Expand Down
127 changes: 127 additions & 0 deletions src/sidebar/Tabs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
const CLOSED_TAB_CLASS = "sidebar-tabpanel-hidden";

/* @see https://www.w3.org/WAI/ARIA/apg/patterns/tabs/examples/tabs-automatic/ */
export class TabsAutomatic {
private tablistNode: HTMLElement;
private tabpanels: HTMLElement[];
private tabs: HTMLElement[];
private firstTab: null | HTMLElement;
private lastTab: null | HTMLElement;

constructor(groupNode: HTMLElement) {
this.tablistNode = groupNode;

this.tabs = [];

this.firstTab = null;
this.lastTab = null;

this.tabs = Array.from(this.tablistNode.querySelectorAll("[role=tab]"));
this.tabpanels = [];

for (let i = 0; i < this.tabs.length; i += 1) {
const tab = this.tabs[i];
const tabpanel = document.getElementById(tab.getAttribute("aria-controls") ?? "");

if (!tabpanel) {
continue;
}

tab.tabIndex = -1;
tab.setAttribute("aria-selected", "false");
this.tabpanels.push(tabpanel);

tab.addEventListener("keydown", this.onKeydown.bind(this));
tab.addEventListener("click", this.onClick.bind(this));

if (!this.firstTab) {
this.firstTab = tab;
}

this.lastTab = tab;
}

this.setSelectedTab(this.firstTab, false);
}

setSelectedTab(currentTab: HTMLElement | null, setFocus: boolean = true) {
for (let i = 0; i < this.tabs.length; i += 1) {
var tab = this.tabs[i];
if (currentTab === tab) {
tab.setAttribute("aria-selected", "true");
tab.removeAttribute("tabindex");
this.tabpanels[i].classList.remove(CLOSED_TAB_CLASS);
if (setFocus) {
tab.focus();
}
} else {
tab.setAttribute("aria-selected", "false");
tab.tabIndex = -1;
this.tabpanels[i].classList.add(CLOSED_TAB_CLASS);
}
}
}

setSelectedToPreviousTab(currentTab: HTMLElement) {
if (currentTab === this.firstTab) {
this.setSelectedTab(this.lastTab);
} else {
const index = this.tabs.indexOf(currentTab);
this.setSelectedTab(this.tabs[index - 1]);
}
}

setSelectedToNextTab(currentTab: HTMLElement) {
var index;

if (currentTab === this.lastTab) {
this.setSelectedTab(this.firstTab);
} else {
index = this.tabs.indexOf(currentTab);
this.setSelectedTab(this.tabs[index + 1]);
}
}

onKeydown(event: KeyboardEvent) {
let tgt = event.currentTarget;
let flag = false;

if (tgt instanceof HTMLElement) {
switch (event.key) {
case "ArrowLeft":
this.setSelectedToPreviousTab(tgt);
flag = true;
break;

case "ArrowRight":
this.setSelectedToNextTab(tgt);
flag = true;
break;

case "Home":
this.setSelectedTab(this.firstTab);
flag = true;
break;

case "End":
this.setSelectedTab(this.lastTab);
flag = true;
break;

default:
break;
}
}

if (flag) {
event.stopPropagation();
event.preventDefault();
}
}

onClick(event: MouseEvent) {
if (event.currentTarget instanceof HTMLElement) {
this.setSelectedTab(event.currentTarget);
}
}
}

0 comments on commit 35dd00e

Please sign in to comment.