0
\$\begingroup\$

I haven't had a great deal of time to build components using some of the more fun items that have come since ES6+ started hitting the scene, but figured I'd share a dropdown snippet I created and get some feedback on the javascript approach. Still not a huge fan of the "one-liner" look but went for it this round to see if I saw any benefit.

If you have any suggestions or constructive criticism dive in.

const toBoolean = string => !(string === ("false" || "0" || ""));
const eventArgs = {type:'click', id:'.summary', base: window, capture: false};
const emitEvent = (params, fn) => params.base.addEventListener(params.type, e => e.target.matches(params.id) && fn(e), params.capture);
const switchBoolean = (node, prop) => node.setAttribute(prop, toBoolean(node.getAttribute(prop)) ? "false" : "true");
const shiftAttribute = (node, prop) => node.hasAttribute(prop) ? node.removeAttribute(prop) : node.setAttribute(prop, "");
const setAttributes = (node, obj) => Object.keys(obj).forEach(key => node.setAttribute(key, obj[key]));

const typeProperty = btn => ({type: !btn.type ? "button" : btn.type});
const setBtnDefaults = btn => setAttributes(btn, Object.assign({role: "button"}, typeProperty(btn)));
const toggleDefaults = (toggle, defaults) => setAttributes(toggle, defaults);
const panelDefaults = panel => panel.setAttribute("hidden", "");
const toggleProperties = {"aria-expanded": false, "aria-haspopup": true};
const setToggleDefaults = toggle => toggleDefaults(toggle.firstElementChild, toggleProperties);
const setPanelDefaults = panel => panelDefaults(panel.lastElementChild);
const setDefaults = dropdown => (setToggleDefaults(dropdown), setPanelDefaults(dropdown));
const mapNodesToFn = dropdowns => [...dropdowns].map(setDefaults);

const toggleState = toggle => switchBoolean(toggle, "aria-expanded");
const panelState = panel => shiftAttribute(panel, "hidden");
const setToggleState = event => panelState(event.target.nextElementSibling);
const setPanelState = event => toggleState(event.target);
const updateState = event => (setToggleState(event), setPanelState(event));
const listener = dropdowns => (mapNodesToFn(dropdowns), emitEvent(eventArgs, updateState));

const dropdowns = document.querySelectorAll('.details');
const initDD = (nodes => listener(nodes))(dropdowns);
const buttons = document.querySelectorAll("button");
[...buttons].map(setBtnDefaults);
.details {
  position: relative;
  font: 300 0.85rem/1 sans-serif;
}

.details,
.details .summary,
.details .complete {
  box-sizing: border-box;
}

.details .summary {
  display: block;
  padding: 0.5rem 1rem;
  border: 0;
  background-color: #e74c3c;
  color: #fff;
  border-bottom: 0;
  position: relative;
  border-radius: 0.063rem;
  margin-bottom: 0.5rem;
  z-index: 2;
  cursor: pointer;
  font: 500 0.75rem/1 sans-serif;
  letter-spacing: 0.063rem;
  transition: all 0.2s;
}

/** if complete isn't present, won't have attribute, disable pointer-events */

.details .summary:not([aria-haspopup]) {
  pointer-events: none;
}

/** override base styles */

.details .summary:focus {
  outline: none;
  box-shadow:
    0 0.063rem 0.125rem hsla(0, 0%, 0%, 0.25),
    0 0.125rem 0.5rem hsla(0, 0%, 0%, 0.05);
}

.details .summary:active {
  transform: scale(0.975);
}

.details .complete {
  background-color: #efefef;
  position: relative;
  padding: 1rem 2rem;
  line-height: 1.8;
  font-weight: 400;
  transform: translateY(-0.5rem);
  margin-bottom: 1rem;
}

.details .summary[aria-expanded='true'] ~ .complete {
  border-radius: 0.125rem;
  position: relative;
  box-shadow:
    0 0.125rem 0.25rem hsla(0, 0%, 0%, 0.25),
    0 0.5rem 1rem hsla(0, 0%, 0%, 0.125);
  max-width: 30rem;
  background: #fff;
  color: #1d1d1d;
  background: -webkit-linear-gradient(to bottom left, #fff, #ddd);
  background: -moz-linear-gradient(to bottom left, #fff, #ddd);
  background: -o-linear-gradient(to bottom left, #fff, #ddd);
  background: linear-gradient(to bottom left, #fff, #ddd);
}
.details .summary[aria-expanded="true"] ~ .complete::after{
  content: '';
  height: 0.063rem;
  position: absolute;
  top: 0;
  left: 0;
  width: 33.333%;
  background: #e74c3c;
  background: -webkit-linear-gradient(to right, #e74c3c, #ecf0f1);
  background: -moz-linear-gradient(to right, #e74c3c, #ecf0f1);
  background: -o-linear-gradient(to right, #e74c3c, #ecf0f1);
  background: linear-gradient(to right, #e74c3c, #ecf0f1);
}
.details .summary[aria-expanded='true'] ~ .complete::before {
  content: '';
  width: 0.063rem;
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  background: #e74c3c;
  background: -webkit-linear-gradient(to bottom, #e74c3c, #ecf0f1);
  background: -moz-linear-gradient(to bottom, #e74c3c, #ecf0f1);
  background: -o-linear-gradient(to bottom, #e74c3c, #ecf0f1);
  background: linear-gradient(to bottom, #e74c3c, #ecf0f1);
}

.details .summary::after{
  content: '';
  border: 6px solid transparent;
  border-top-color: #e74c3c;
  position: absolute;
  top: 80%;
  left: 50%;
  z-index: -1;
  transition: top 0.2s;
  transform: translateX(calc(-50% - 3px));
}

.details .summary[aria-expanded='true']::after{
  top: 100%;
}

:focus,
:link:focus,
:visited:focus {
    outline-width: 2px;
    outline-style: solid;
    outline-color: #2c3e50;
    outline-offset: -0.063rem;
}

@media (-webkit-min-device-pixel-ratio: 0) {

    :focus,
    :link:focus,
    :visited:focus {
        outline-color: #2c3e50;
        outline-style: auto;
        outline-offset: -0.063rem;
    }
}

.details .summary[aria-expanded='true']::after:focus{
  outline-width: 0;
  outline-color: #fff;
}
<div class="details">
  <button class="summary">Lorem Ipsum</button>
  <div class="complete">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer dui lorem, venenatis vitae gravida ac, condimentum ut eros.</div>
</div>

<div class="details">
  <button class="summary">Lorem Ipsum</button>
  <div class="complete">Ducs accums nulla eros, quis gravida enim euismod at. Ut eu urna dictum, ornare justo vel, vestibulum risus.</div>
</div>

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.