botoxparty/XP.css

Tablist not switching tabpanels (or tabs)

Opened this issue ยท 2 comments

    <center>
      <div class="window" style="width: 600px">
        <div class="title-bar">
          <div class="title-bar-text">Explorer</div>
          <div class="title-bar-controls">
            <button aria-label="Minimize"></button>
            <button aria-label="Maximize"></button>
            <button aria-label="Close"></button>
          </div>
        </div>
        <div class="window-body">
          <menu role="tablist">
            <button aria-selected="true" aria-controls="about">About</button>
            <button aria-controls="favorites">Favorites</button>
          </menu>
          <article role="tabpanel" id="about">
            <h2>Hi ๐Ÿ‘‹</h2>
            <p>I'm AcaiBerii. I program mostly in c# but I do JavaScript and Java too.</p>
          </article>
          <article role="tabpanel" hidden id="favorites">
            <p><strong>Music genres</strong> EDM, Lo-fi</p>
            <p><strong>Languages</strong> C#, Java, JavaScript, Nemerle, Python</p>
            <p><strong>Editors</strong> Visual Studio, VSCode, Intellij Idea, PyCharm</p>
          </article>
        </div>
      </div>
    </center>

When viewing this, it won't switch tabs or tabpanels.
This also won't let you interact with the tabs at all.

I came across this problem today. Looking at the documentation page, need some javascript to make tabs work.

In the HTML, before the closing body tag, add

<script src="script.js"></script>

Then in script.js, add

document.addEventListener("DOMContentLoaded", function() {
const tabs = document.querySelectorAll("menu[role=tablist]");

for (let i = 0; i < tabs.length; i++) {
  const tab = tabs[i];

  const tabButtons = tab.querySelectorAll("menu[role=tablist] > button");

  tabButtons.forEach((btn) =>
    btn.addEventListener("click", (e) => {
      e.preventDefault();

      tabButtons.forEach((button) => {
        if (
          button.getAttribute("aria-controls") ===
          e.target.getAttribute("aria-controls")
        ) {
          button.setAttribute("aria-selected", true);
          openTab(e, tab);
        } else {
          button.setAttribute("aria-selected", false);
        }
      });
    })
  );
}
  
});


function openTab(event, tab) {
  const articles = tab.parentNode.querySelectorAll('[role="tabpanel"]');
  articles.forEach((p) => {
    p.setAttribute("hidden", true);
  });
  const article = tab.parentNode.querySelector(
    `[role="tabpanel"]#${event.target.getAttribute("aria-controls")}`
  );
  article.removeAttribute("hidden");
}

For anyone using Angular, I'm using the following solution:

  • On the component class, create an array using these fields, each entry representing a tab and a component which will be rendered in the <article> section:
windowTabs: WindowTab[] = [{
    id: 'tab-banana',
    title: 'Banana',
    selected: true,
    component: BananaComponent
  }, {
    id: 'tab-apple',
    title: 'Apple',
    selected: false,
    component: AppleComponent
  }, 
// ...
];

Here's the interface WindowTab used for typing:

export interface WindowTab {
  id: string,
  title: string,
  selected: boolean,
  component: any
}
  • Create this function to switch tabs:
changeSelectedTab(selectedTab: WindowTab): void {
    if (selectedTab.selected)
      return;

    this.windowTabs.forEach(tab => {
      tab.selected = selectedTab.id === tab.id
    })
  }
  • On your component HTML, inside the window, we can iterate on the WindowTab array:
<div class="window-body">
    <section class="tabs">
      <menu role="tablist" aria-label="Sample tabs">
        <ng-container *ngFor="let tab of windowTabs">
          <button
            role="tab"
            [attr.aria-controls]="tab.id"
            [attr.aria-selected]="tab.selected"
            (click)="this.changeSelectedTab(tab)"
          >
            {{tab.title}}
          </button>
        </ng-container>
      </menu>

      <ng-container *ngFor="let tab of windowTabs">
        <article
          role="tabpanel"
          [id]="tab.id"
          [hidden]="!tab.selected"
        >
          <ng-template [ngComponentOutlet]="tab.component"></ng-template>
        </article>
      </ng-container>
    </section>
  </div>

This will dynamically create the tabs, assign the function to switch to a tab, and also render its component when it gets clicked. Hope it helps anyone.