Last active
April 23, 2025 02:55
-
-
Save cferdinandi/7efb6e7f3fa56f51e8c69757952a3e18 to your computer and use it in GitHub Desktop.
Watch the tutorial at https://youtu.be/_gZjjg0xu9A
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment