Skip to content

Instantly share code, notes, and snippets.

@cferdinandi
Last active May 13, 2025 04:47
Show Gist options
  • Save cferdinandi/7efb6e7f3fa56f51e8c69757952a3e18 to your computer and use it in GitHub Desktop.
Save cferdinandi/7efb6e7f3fa56f51e8c69757952a3e18 to your computer and use it in GitHub Desktop.

Revisions

  1. cferdinandi revised this gist Apr 23, 2025. 6 changed files with 417 additions and 1 deletion.
    39 changes: 39 additions & 0 deletions index-bs.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,39 @@
    <!DOCTYPE html>
    <html>
    <head>
    <title>Bootstrap</title>

    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-SgOJa3DmI69IUzQ2PVdRZhwQ+dy64/BUtbMJw1MZ8t5HZApcHrRKUc4W0kG879m7" crossorigin="anonymous">

    <style type="text/css">
    body {
    margin: 1em auto;
    max-width: 30em;
    width: 88%;
    }
    </style>
    </head>
    <body>

    <h1>Bootstrap</h1>

    <ul class="nav nav-tabs" id="myTab" role="tablist">
    <li class="nav-item" role="presentation">
    <button class="nav-link active" id="home-tab" data-bs-toggle="tab" data-bs-target="#home-tab-pane" type="button" role="tab" aria-controls="home-tab-pane" aria-selected="true">Merlin</button>
    </li>
    <li class="nav-item" role="presentation">
    <button class="nav-link" id="profile-tab" data-bs-toggle="tab" data-bs-target="#profile-tab-pane" type="button" role="tab" aria-controls="profile-tab-pane" aria-selected="false">Gandalf</button>
    </li>
    <li class="nav-item" role="presentation">
    <button class="nav-link" id="contact-tab" data-bs-toggle="tab" data-bs-target="#contact-tab-pane" type="button" role="tab" aria-controls="contact-tab-pane" aria-selected="false">Radagast</button>
    </li>
    </ul>
    <div class="tab-content" id="myTabContent">
    <div class="tab-pane fade show active" id="home-tab-pane" role="tabpanel" aria-labelledby="home-tab" tabindex="0">🪄</div>
    <div class="tab-pane fade" id="profile-tab-pane" role="tabpanel" aria-labelledby="profile-tab" tabindex="0">🧙‍♀️</div>
    <div class="tab-pane fade" id="contact-tab-pane" role="tabpanel" aria-labelledby="contact-tab" tabindex="0">🐿️</div>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-k6d4wzSIapyDyv1kpU366/PK5hCdSbCRGRCMv+eplOQJWyd1fbcAu9OCUj5zNLiq" crossorigin="anonymous"></script>
    </body>
    </html>
    227 changes: 227 additions & 0 deletions index-kraken.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,227 @@
    <!DOCTYPE html>
    <html>
    <head>
    <title>Kraken</title>

    <style type="text/css">
    body {
    margin: 1em auto;
    max-width: 30em;
    width: 88%;
    }

    toggle-tabs [role="tablist"] {
    list-style: none;
    margin: 0 0 2em;
    padding: 0;
    }

    toggle-tabs [role="tablist"] li {
    display: inline-block;
    }

    toggle-tabs [role="tab"] {
    color: currentColor;
    margin: 0 0 0.25em;
    padding: 0.5em 1em;
    text-decoration: none;
    }

    toggle-tabs [role="tab"]:active,
    toggle-tabs [role="tab"]:hover {
    background-color: #f7f7f7;
    }

    toggle-tabs [role="tab"][aria-selected="true"] {
    background-color: #e5e5e5;
    }
    </style>
    </head>
    <body>

    <h1>Kraken</h1>

    <toggle-tabs>
    <ul tabs>
    <li><a href="#merlin">Merlin</a></li>
    <li><a href="#gandalf">Gandalf</a></li>
    <li><a href="#radagast">Radagast</a></li>
    </ul>

    <div id="merlin">🪄</div>
    <div id="gandalf">🧙‍♀️</div>
    <div id="radagast">🐿️</div>
    </toggle-tabs>

    <script>
    customElements.define('toggle-tabs', class extends HTMLElement {

    /**
    * Instantiate the Web Component
    */
    constructor () {

    // Get parent class properties
    super();

    // Define properties
    this.tabList = this.querySelector('[tabs]');

    // Setup UI
    this.setup();

    }

    /**
    * Handle events on the Web Component
    * @param {Event} event The event object
    */
    handleEvent (event) {
    this[`on${event.type}`](event);
    }

    /**
    * Handle click events
    * @param {Event} event The event object
    */
    onclick (event) {

    // Only run on tab links
    if (!event.target.matches('[role="tab"]')) return;

    // Prevent the link from updating the URL
    event.preventDefault();

    // Ignore the currently active tab
    if (event.target.matches('[aria-selected="true"]')) return;

    // Toggle tab visibility
    this.toggle(event.target);

    }

    /**
    * Handle keydown events
    * @param {Event} event The event object
    */
    onkeydown (event) {

    // Only run for left and right arrow keys
    if (!['ArrowLeft', 'ArrowRight'].includes(event.code)) return;

    // Only run if element in focus is on a tab
    let tab = document.activeElement.closest('[role="tab"]');
    if (!tab) return;

    // Only run if focused tab is in this component
    if (!this.tabList.contains(tab)) return;

    // Get the currently active tab
    let currentTab = this.tabList.querySelector('[role="tab"][aria-selected="true"]');

    // Get the parent list item
    let listItem = currentTab.closest('li');

    // If right arrow, get the next sibling
    // Otherwise, get the previous
    let nextListItem = event.code === 'ArrowRight' ? listItem.nextElementSibling : listItem.previousElementSibling;
    if (!nextListItem) return;
    let nextTab = nextListItem.querySelector('a');

    // Toggle tab visibility
    this.toggle(nextTab);
    nextTab.focus();

    }

    /**
    * Toggle tab visibility
    * @param {Node} tab The tab to show
    */
    toggle (tab) {

    // Get the target tab pane
    let tabPane = this.querySelector(tab.hash);
    if (!tabPane) return;

    // Get the current tab and content
    let currentTab = tab.closest('[role="tablist"]').querySelector('[aria-selected="true"]');
    let currentPane = document.querySelector(currentTab.hash);

    // Update the selected tab
    tab.setAttribute('aria-selected', true);
    currentTab.setAttribute('aria-selected', false);

    // Update the visible tabPane
    tabPane.removeAttribute('hidden');
    currentPane.setAttribute('hidden', '');

    // Make sure current tab can be focused and other tabs cannot
    tab.removeAttribute('tabindex');
    currentTab.setAttribute('tabindex', -1);

    }

    /**
    * Add buttons and hide content on page load
    */
    setup () {

    // Only run if there are tabs
    if (!this.tabList) return;

    // Get the list items and links
    let listItems = this.tabList.querySelectorAll('li');
    let links = this.tabList.querySelectorAll('a');

    // Add ARIA to list
    this.tabList.setAttribute('role', 'tablist');

    // Add ARIA to the list items
    for (let item of listItems) {
    item.setAttribute('role', 'presentation');
    }

    // Add ARIA to the links and content
    let instance = this;
    links.forEach(function (link, index) {

    // Get the the target element
    let tabPane = instance.querySelector(link.hash);
    if (!tabPane) return;

    // Add [role] and [aria-selected] attributes
    link.setAttribute('role', 'tab');
    link.setAttribute('aria-selected', index === 0 ? true : false);

    // If it's not the active (first) tab, remove focus
    if (index > 0) {
    link.setAttribute('tabindex', -1);
    }

    // If there's no ID, add one
    if (!link.id) {
    link.id = `tab_${tabPane.id}`;
    }

    // Add ARIA to tab pane
    tabPane.setAttribute('role', 'tabpanel');
    tabPane.setAttribute('aria-labelledby', link.id);

    // If not the active pane, hide it
    if (index > 0) {
    tabPane.setAttribute('hidden', '');
    }

    });

    // Listen for events
    this.tabList.addEventListener('click', this);
    document.addEventListener('keydown', this);

    }

    });
    </script>
    </body>
    </html>
    33 changes: 33 additions & 0 deletions index-wa.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,33 @@
    <!DOCTYPE html>
    <html>
    <head>
    <title>Web Awesome</title>

    <link rel="stylesheet" href="https://early.webawesome.com/[email protected]/dist/styles/themes/default.css" />
    <link rel="stylesheet" href="https://early.webawesome.com/[email protected]/dist/styles/webawesome.css" />

    <style type="text/css">
    body {
    margin: 1em auto;
    max-width: 30em;
    width: 88%;
    }
    </style>
    </head>
    <body>

    <h1>Web Awesome</h1>

    <wa-tab-group>
    <wa-tab panel="merlin">Merlin</wa-tab>
    <wa-tab panel="gandalf">Gandalf</wa-tab>
    <wa-tab panel="radagast">Radagast</wa-tab>

    <wa-tab-panel name="merlin">🪄</wa-tab-panel>
    <wa-tab-panel name="gandalf">🧙‍♀️</wa-tab-panel>
    <wa-tab-panel name="radagast">🐿️</wa-tab-panel>
    </wa-tab-group>

    <script type="module" src="https://early.webawesome.com/[email protected]/dist/webawesome.loader.js"></script>
    </body>
    </html>
    1 change: 0 additions & 1 deletion readme
    Original file line number Diff line number Diff line change
    @@ -1 +0,0 @@
    1
    93 changes: 93 additions & 0 deletions relative-time-kraken.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,93 @@
    <!DOCTYPE html>
    <html>
    <head>
    <title>Relative Time - Kraken</title>

    <style type="text/css">
    body {
    margin: 1em auto;
    max-width: 30em;
    width: 88%;
    }
    </style>
    </head>
    <body>

    <h1>Relative Time - Kraken</h1>

    <relative-time>2025-07-04T09:17:00-04:00</relative-time>

    <script>
    customElements.define('relative-time', class extends HTMLElement {

    /**
    * Instantiate the Web Component
    */
    constructor () {

    // Get parent class properties
    super();

    // Get attributes
    let dateStr = this.textContent;
    let date = Date.parse(dateStr);
    this.date = Number.isNaN(date) ? Date.now() - 1 : date;
    this.locale = this.getAttribute('lang') ?? 'en-US';
    let sync = this.hasAttribute('sync');

    // Create <time> element
    this.time = document.createElement('time');
    this.time.setAttribute('datetime', dateStr);
    this.innerHTML = '';
    this.append(this.time);

    // Format relative time
    this.time.textContent = this.format();

    if (sync) {
    setInterval(() => {
    this.time.textContent = this.format();
    }, 1000 * 60);
    }

    }

    format () {
    let options = this.getOptions();
    let formatter = new Intl.RelativeTimeFormat(this.locale, {
    style: this.getAttribute('type') ?? undefined,
    numeric: this.getAttribute('numeric') ?? undefined,
    });
    return formatter.format(...options);
    }

    getOptions () {

    // Date and elapsed time
    let now = Date.now();
    let elapsed = this.date - now;

    // Units in miliseconds
    let units = [
    ['year', 24 * 60 * 60 * 1000 * 365],
    ['month', 24 * 60 * 60 * 1000 * 365/12],
    ['day', 24 * 60 * 60 * 1000],
    ['hour', 60 * 60 * 1000],
    ['minute', 60 * 1000],
    ['second', 1000]
    ];

    // "Math.abs" accounts for both "past" & "future" scenarios
    for (let [unit, ms] of units) {
    if (Math.abs(elapsed) > ms || unit === 'second') {
    return [Math.round(elapsed / ms), unit];
    }
    }

    }


    });
    </script>
    </body>
    </html>
    25 changes: 25 additions & 0 deletions relative-time-wa.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,25 @@
    <!DOCTYPE html>
    <html>
    <head>
    <title>Relative Time - Web Awesome</title>

    <link rel="stylesheet" href="https://early.webawesome.com/[email protected]/dist/styles/themes/default.css" />
    <link rel="stylesheet" href="https://early.webawesome.com/[email protected]/dist/styles/webawesome.css" />

    <style type="text/css">
    body {
    margin: 1em auto;
    max-width: 30em;
    width: 88%;
    }
    </style>
    </head>
    <body>

    <h1>Relative Time - Web Awesome</h1>

    <wa-relative-time date="2025-07-04T09:17:00-04:00"></wa-relative-time>

    <script type="module" src="https://early.webawesome.com/[email protected]/dist/webawesome.loader.js"></script>
    </body>
    </html>
  2. cferdinandi created this gist Apr 23, 2025.
    1 change: 1 addition & 0 deletions readme
    Original file line number Diff line number Diff line change
    @@ -0,0 +1 @@
    1