Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A progressive disclosure component with wickedElements
<!doctype html>
<html lang="eng">
<!--
"A progressive disclosure component"
https://hankchizljaw.com/wrote/a-progressive-disclosure-component/
with wickedElements
https://github.com/WebReflection/wicked-elements
instead of a web component
-->
<head>
<meta charset="utf-8"/>
<title>Disclosure toggle with wickedElements</title>
<style>
/* General presentation styles */
html {
--color-dark: #212d40;
--color-light: #f3f3f3;
--color-light-glare: #ffffff;
--color-light-glare-tl: rgba(255, 255, 255, 0.4);
--color-primary: #98b06f;
background: var(--color-dark);
}
body,
.button,
.link-button {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
}
body {
padding: 1.5rem;
line-height: 1.4;
color: var(--color-light);
}
p {
margin: 0;
}
a:not([class]) {
color: currentcolor;
}
a:not([class]):hover,
a:not([class]):focus {
text-decoration: none;
}
/* Utilities */
/* Flow utility: https://24ways.org/2018/managing-flow-and-rhythm-with-css-custom-properties/ */
.flow {
--flow-space: 1em;
}
.flow > * + * {
margin-top: var(--flow-space);
}
/* Objects (structure) */
.container {
max-width: 30rem;
margin: 0 auto;
}
.terms {
--flow-space: 2rem;
}
/* Button component */
.button {
display: inline-block;
border: none;
padding: 0.6rem 1.2rem 0.8rem 1.2rem;
text-decoration: none;
background: var(--color-primary);
color: var(--color-dark);
font-size: 1.2rem;
font-weight: 700;
cursor: pointer;
text-align: center;
transition: background 250ms ease-in-out, transform 150ms ease;
-webkit-appearance: none;
-moz-appearance: none;
}
.button:hover,
.button:focus {
background: var(--color-light-glare);
}
.button:focus {
outline: 1px solid var(--color-dark);
outline-offset: -4px;
}
.button:active {
transform: scale(0.99);
}
/* Parts of disclosureToggle component:
link-button, panel, panel__inner
*/
.link-button {
display: inline-flex;
align-items: center;
text-decoration: underline;
text-decoration-skip-ink: auto;
background: transparent;
padding: 0;
margin: 0;
border: 0;
color: var(--color-primary);
font-size: 1rem;
cursor: pointer;
-webkit-appearance: none;
-moz-appearance: none;
}
.link-button svg {
opacity: 0.8;
margin-left: 0.5rem;
transition: all 250ms ease-in-out;
font-size: 0.8rem;
}
.link-button[aria-expanded="true"] svg {
transform: rotate(-180deg);
}
.link-button:hover,
.link-button:focus {
text-decoration: none;
}
.link-button:focus {
text-decoration: none;
outline: 1px solid var(--color-light-glare-tl);
outline-offset: 0.6rem;
}
.link-button:active {
transform: scale(0.99);
}
.panel {
--panel-max-height: 500px;
transition: all 200ms ease;
position: relative;
overflow-y: auto;
overflow-x: hidden;
visibility: hidden;
max-height: 0;
-webkit-overflow-scrolling: touch;
}
.panel__inner {
transition: all 500ms ease;
transition-delay: 50ms;
opacity: 0;
transform: translateY(1rem);
padding-top: 1.5rem;
}
[aria-expanded="true"] + .panel {
max-height: var(--panel-max-height);
visibility: visible;
}
[aria-expanded="true"] + .panel .panel__inner {
opacity: 1;
transform: translateY(0);
}
</style>
</head>
<body>
<main class="[ container ] [ flow ]">
<h1>Buy this thing!</h1>
<p>It is very good and there's absolutely no hidden exceptions!</p>
<a href="http://example.com" class="button">Buy it now!!</a>
<div class="terms">
<div class="flow" data-wicked="disclosure-toggle" data-label="See the terms and conditions">
<div>
<article>
<p>
Here's some secret terms and conditions that we didn't want you to see
because they explain how the product isn't actually very good.
</p>
<p>
Lorem ipsum dolor sit amet, appareat pertinax et ius, ne pro nibh consulatu consetetur, nulla virtute definitiones nec in.
Ad consul feugait eligendi mea, mutat tamquam ei mei.
No hinc graecis phaedrum pro, cu erat ipsum sed, ut novum dissentiunt ullamcorper pro.
<a href="#">Dicat aliquid dissentias</a> in per, meis alterum quaestio mei eu, vero praesent ex eam.
Ei nam homero noluisse dissentiunt, ut vim quot putent.
Vis elitr accusam accommodare id, cu usu quaestio conceptam, habeo tibique placerat eos cu.
An nullam corpora consulatu qui, graeci euripidis est et.
</p>
<p>
In tantas scripta nominati quo, ne essent maluisset voluptaria nam.
Dicat putent feugiat ei sed, te vis delicata gubergren honestatis, ius liber blandit delicata ut.
Vix et dictas detracto voluptua, vis an inani dicunt.
Qui inciderint intellegebat ea, cetero verear cu duo.
</p>
<p>
Vix id etiam sapientem.
Vix case velit feugait eu.
Id diceret delenit perpetua vis, has sale utamur aeterno te.
Cum et consetetur mediocritatem.
</p>
</article>
</div>
</div>
</div>
</main>
<script>
;(function() {
// A key for setting the CSS Custom Property
const KEY = '--panel-max-height';
const SELECTOR = '[data-wicked="disclosure-toggle"]';
const NAME = 'aria-expanded';
/* helper functions */
function getPanels(root) {
const inner = root.querySelector('article');
const panel = inner.parentNode;
return [inner, panel];
}
function getLabel(root) {
return root.dataset.label || 'Toggle content';
}
function decorateTrigger(button, label) {
button.classList.add('link-button');
button.innerHTML = `${label}`
+ '<svg viewBox="0 0 512 512" aria-hidden="true" fill="currentColor" width="1em" height="1em">'
+ '<path d="M60 99.333l196 196 196-196 60 60-256 256-256-256z"></path>'
+ '</svg>';
return button;
}
function setMaxHeight(key, inner, panel) {
function adjustPanelHeight(resolve, reject) {
if (!panel || !inner) {
reject();
}
const height = inner.getBoundingClientRect().height;
panel.style.setProperty(key, `${height}px`);
resolve();
}
return new Promise(adjustPanelHeight);
}
// toggleTriggerState - toggle relevant aria role
function toggleTriggerState(trigger) {
let current = trigger.getAttribute(NAME);
const value = current === null || current === 'true' ? 'false' : 'true';
trigger.setAttribute(NAME, value);
return value;
}
/* disclosureToggle definition */
function init() {
let [inner, panel] = getPanels(this.element);
this.panelInner = inner;
this.panel = panel;
// Create some component-level state
this.triggerExpanded = 'false'
}
function connected() {
function clickListener(_event) {
toggle();
}
const label = getLabel(this.element);
let trigger = document.createElement('button');
this.element.prepend(decorateTrigger(trigger, label));
const toggleTrigger = () => {
this.triggerExpanded = toggleTriggerState(trigger);
};
const toggle = () => {
setMaxHeight(KEY, this.panelInner, this.panel)
.then(toggleTrigger);
};
trigger.addEventListener('click', clickListener);
toggle();
}
/* --- script --- */
// stow definition for later
// use with wickedElements
window.disclosureToggle = {
selector: SELECTOR,
init,
connected,
}
let list = document.querySelectorAll(SELECTOR);
for (node of list) {
let [inner, panel] = getPanels(node);
// setting the 'panel' class
// will set the 'max-height' to 0
panel.classList.add('panel');
inner.classList.add('panel__inner');
}
}());
</script>
<script src="https://unpkg.com/wicked-elements"></script>
<script>
;(function() {
// Now activate the full
// functionality of the component
wickedElements.define(
window.disclosureToggle.selector,
window.disclosureToggle,
);
}());
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.