JSFiddle - React, Tailwind, and code Playground
by Arnold Daniels
HTML
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/css/bulma.min.css">
<aside class="menu">
<p class="menu-label">
General
</p>
<ul class="menu-list">
<li><a>Dashboard</a></li>
<li><a>Customers</a></li>
</ul>
<p class="menu-label">
Administration
</p>
<ul class="menu-list collapse">
<li><a>Team Settings</a></li>
<li>
<details>
<summary><a class="is-active">Manage Your Team</a></summary>
<ul>
<li><a>Members</a></li>
<li><a>Plugins</a></li>
<li><a>Add a member</a></li>
</ul>
</details>
</li>
<li><a>Invitations</a></li>
<li><a>Cloud Storage Environment Settings</a></li>
<li><a>Authentication</a></li>
</ul>
<p class="menu-label">
Transactions
</p>
<ul class="menu-list">
<li><a>Payments</a></li>
<li><a>Transfers</a></li>
<li><a>Balance</a></li>
</ul>
</aside>
SCSS
body {
padding: 20px;
}
ul.collapse {
ul {
overflow: hidden;
height: 100%;
}
li:not(.in) > ul {
height: 0;
}
details {
transition: height 0.4s;
overflow: hidden;
}
summary {
list-style: none;
}
summary::-webkit-details-marker {
display: none;
}
summary:focus {
outline: none;
}
}
JavaScript
function delegateEvent(type, selector, handler) {
document.addEventListener(type, function(event) {
for (var target = event.target; target && target != this; target = target.parentNode) {
if (target.matches(selector)) {
handler.call(target, event);
break;
}
}
}, false);
}
function smoothAnimateDetails(details, summary, event) {
if (details.style.height !== '') {
event.preventDefault();
return; // Still in animation
}
transitionTime = durationToMs(getComputedStyle(details).transitionDuration);
if (isNaN(transitionTime) || transitionTime <= 0) {
return;
}
const isOpen = details.open;
details.style.height = details.offsetHeight + "px";
setTimeout(function() {
details.style.height = (isOpen ? summary.offsetHeight : details.scrollHeight) + "px";
}, 0);
if (isOpen) event.preventDefault(); // Don't hide the content during transition
setTimeout(function() {
details.open = !isOpen;
details.style.height = '';
}, transitionTime);
}
function durationToMs(duration) {
const groups = /^(?<amount>[\d\.]*)(?<unit>.*)$/.exec(duration).groups
const amount = Number.parseFloat(groups.amount);
return groups.unit === 'ms' ? amount : groups.unit === 's' ? amount * 1000 : NaN;
}
delegateEvent('click', 'summary', function(event) {
smoothAnimateDetails(this.parentNode, this, event);
event.stopPropagation();
});