Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] feat: refactor accordion to details and summary #220

Draft
wants to merge 8 commits into
base: 2.x
Choose a base branch
from
Draft
34 changes: 11 additions & 23 deletions modules/localgov_subsites_paragraphs/css/localgov-accordion.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,17 @@
* Basic styling for Accordion paragraph.
*/

.accordion-pane__title button {
width: 100%;
text-align: left;
}

.accordion-pane__title button[hidden] {
display: none;
}

.accordion--initialised .accordion-pane__content {
display: none;
}

.accordion--initialised .accordion-pane__content--open {
display: block;
}

/* initial hide of icon within the button, retain pointer events setting if used */
.accordion-icon {
display: none;
pointer-events: none;
}

.accordion-toggle-all {
margin-bottom: var(--vertical-rhythm-spacing);
}

/* Styling tweaks for layout builder only */
.js-lpb-component .accordion-pane__title h2,
.js-lpb-component .accordion-pane__title h3,
.js-lpb-component .accordion-pane__title h4,
.js-lpb-component .accordion-pane__title h5,
.js-lpb-component .accordion-pane__title h6 {
display: inline;
margin-block-end: 0;
font-size: 1rem;
}
233 changes: 24 additions & 209 deletions modules/localgov_subsites_paragraphs/js/localgov-accordion.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Localgov Accordion behaviour.
*/

(Drupal => {
((Drupal) => {
Drupal.behaviors.localgovAccordion = {
/**
* Attach accordion behaviour.
Expand All @@ -27,230 +27,45 @@
* @param {number} index
* Accordion element index.
*/
init: function init(accordion, index) {
/**
* Expands one accordion pane, setting aria-expanded on button.
*
* @param {HTMLElement} button
* The button associated with the pane to expand.
* @param {HTMLElement} pane
* The pane to expand.
*/
function expandPane(button, pane) {
button.setAttribute('aria-expanded', 'true');
pane.classList.add(openClass);
}

/**
* Collapses one accordion pane, setting aria-expanded on button.
*
* @param {HTMLElement} button
* The button associated with the pane to collapse.
* @param {HTMLElement} pane
* The pane to collapse.
*/
function collapsePane(button, pane) {
button.setAttribute('aria-expanded', 'false');
pane.classList.remove(openClass);
}
init: function init(accordion) {
const accordionPanes = accordion.querySelectorAll('.accordion-pane');
const numberOfPanes = accordionPanes.length;
const displayShowHide = accordion.hasAttribute(
'data-accordion-display-show-hide',
);
let showHideButton;
let showHideButtonLabel;

/**
* Toggles all accordion panes open or closed.
*
* Used both as an event listener callback, and called directly.
*/
function toggleAll() {
const labelEl = showHideButton.querySelector('.accordion-text');
const nextState = showHideButton.getAttribute('aria-expanded') !== 'true';
const nextState =
showHideButton.getAttribute('aria-expanded') !== 'true';

showHideButtonLabel.textContent = showHideButton.dataset[nextState ? 'hideAll' : 'showAll'];
showHideButtonLabel.textContent =
showHideButton.dataset[nextState ? 'hideAll' : 'showAll'];
showHideButton.setAttribute('aria-expanded', nextState);

for (let i = 0; i < numberOfPanes; i++) {
const currentButton = accordionPanes[i].querySelector('[aria-controls]');
const currentPane = accordionPanes[i].querySelector('.accordion-pane__content');
const tabopen = nextState
? 'details:not([open]) summary'
: 'details[open] summary';

if (nextState) {
expandPane(currentButton, currentPane);

} else {
collapsePane(currentButton, currentPane);
}
}
}

/**
* Gets the state of the accordion: all expanded, all collapsed, or mixed.
*
* @return {number}
* Returns a numeric value according to state:
*
* - all expanded: 1
* - all collapsed: 0
* - mixed: -1
*/
function getAccordionState() {
const expandedPanes = accordion.querySelectorAll(`.${openClass}`).length;
return expandedPanes
? (expandedPanes === numberOfPanes ? 1 : -1)
: 0;
}

const accordionPanes = accordion.querySelectorAll('.accordion-pane');
const numberOfPanes = accordionPanes.length;
const initClass = 'accordion--initialised';
const openClass = 'accordion-pane__content--open';
const breakpoint = accordion.dataset.accordionTabsSwitch || null;
const mq = window.matchMedia(`(max-width: '${breakpoint}')`);
const displayShowHide = accordion.hasAttribute('data-accordion-display-show-hide');
const allowMultiple = displayShowHide || accordion.hasAttribute('data-accordion-allow-multiple');
let showHideButton;
let showHideButtonLabel;

const create = function create() {
// Only initialise accordion if it hasn't already been done.
if (accordion.classList.contains(initClass)) {
return;
}

for (let i = 0; i < numberOfPanes; i++) {
const pane = accordionPanes[i];
const content = pane.querySelectorAll('.accordion-pane__content');
const title = pane.querySelectorAll('.accordion-pane__title');
const button = title[0].querySelector('button');
const heading = title[0].querySelector('.accordion-pane__heading');
const id = `accordion-content-${index}-${i}`;

// Add id attribute to all pane content elements.
content[0].setAttribute('id', id);

// Hide default Heading text
if (heading) {
heading.hidden = true;
};

if (button) {
// Add aria-controls id to button and un-hide
button.setAttribute('aria-controls', id);
button.hidden = false;

// Add click event listener to the show/hide button.
button.addEventListener('click', e => {
const targetPaneId = e.target.getAttribute('aria-controls');
const targetPane = accordion.querySelectorAll(`#${targetPaneId}`);
const openPane = accordion.querySelectorAll(`.${openClass}`);

// Check the current state of the button and the content it controls.
if (e.target.getAttribute('aria-expanded') === 'false') {
// Close currently open pane.
if (openPane.length && !allowMultiple) {
const openPaneId = openPane[0].getAttribute('id');
const openPaneButton = accordion.querySelectorAll(
`[aria-controls="${openPaneId}"]`,
);

collapsePane(openPaneButton[0], openPane[0]);
}

// Show new pane.
expandPane(e.target, targetPane[0]);
} else {
// If target pane is currently open, close it.
collapsePane(e.target, targetPane[0]);
}

if (showHideButton) {
const accordionState = getAccordionState();
const toggleState = showHideButton.getAttribute('aria-expanded') === 'true';

if (
(accordionState === 1 && !toggleState) ||
(!accordionState && toggleState)
) {
toggleAll();
}
}
});
};

if (button) {
if (displayShowHide) {
showHideButton = accordion.querySelector('.accordion-toggle-all');
showHideButton.hidden = false;
showHideButton.addEventListener('click', toggleAll);
showHideButtonLabel = showHideButton.querySelector('.accordion-text');
}

// Add init class.
accordion.classList.add(initClass);
};

}
};

const destroy = () => {
for (let i = 0; i < numberOfPanes; i++) {
// Remove id attributes from buttons in accordion pane titles.
const button = accordion
.querySelectorAll('.accordion-pane__title')[i]
.querySelector('button')
.removeAttribute('id');

// Hide buttons in accordion pane titles.
if (button) {
button.hidden = true;
}

// Un-hide default heading text
const heading = accordion
.querySelectorAll('.accordion-pane__title')[i]
.querySelector('.accordion-pane__heading');

if (heading) {
heading.hidden = false;
}

// Remove id attributes from pane content elements.
accordionPanes[i]
.querySelectorAll('.accordion-pane__content')[0]
.removeAttribute('id');

// Remove open class from accordion pane's content elements.
if (
accordionPanes[i]
.querySelectorAll('.accordion-pane__content')[0]
.classList.contains(openClass)
) {
accordionPanes[i]
.querySelectorAll('.accordion-pane__content')[0]
.classList.remove(openClass);
}
const accordionSummary = accordionPanes[i].querySelector(tabopen);
accordionSummary?.click();
}
}

for (let i = 0; i < numberOfPanes; i++) {
if (displayShowHide) {
showHideButton.hidden = true;
showHideButton.removeEventListener('click', toggleAll);
}

// Remove accordion init class.
accordion.classList.remove(initClass);
};

const breakpointCheck = function breakpointCheck() {
if (mq.matches || breakpoint === null) {
create();
} else {
destroy();
showHideButton = accordion.querySelector('.accordion-toggle-all');
showHideButton.hidden = false;
showHideButton.addEventListener('click', toggleAll);
showHideButtonLabel = showHideButton.querySelector('.accordion-text');
}
};

// Trigger create/destroy functions at different screen widths
// based on the value of data-accordion-tabs-switch attribute.
if (window.matchMedia) {
mq.addEventListener("change", () => {
breakpointCheck();
});
breakpointCheck();
}
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,21 +48,23 @@
'accordion-pane'
]
%}

{% set accordion = paragraph.getParentEntity() %}
{% set group_accordion = accordion.localgov_allow_multiple_open.value == 1 and accordion.localgov_display_show_hide_all.value != 1 %}

{% block paragraph %}
<div{{ attributes.addClass(classes) }}>
{% block content %}
<div class="accordion-pane__title">
<{{ heading_level }}>
<span class="accordion-pane__heading">{{ heading_text }}</span>
<button aria-expanded="false" hidden>
{{ heading_text }}
<span class='accordion-icon' aria-hidden='true'></span>
</button>
</{{ heading_level }}>
</div>
<div class="accordion-pane__content">
{{ content.localgov_body_text }}
</div>
<details {% if group_accordion %}name="accordion_{{ accordion.id() }}"{% endif %}>
<summary class="accordion-pane__title">
<{{ heading_level }}>
<span class="accordion-pane__heading">{{ heading_text }}</span>
</{{ heading_level }}>
</summary>
<div class="accordion-pane__content">
{{ content.localgov_body_text }}
</div>
</details>
{% endblock %}
</div>
{% endblock paragraph %}
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
{% if paragraph.localgov_display_show_hide_all.value == 1 %}
{% set show_all = 'Show all sections'|t %}
{% set hide_all = 'Hide all sections'|t %}
<button type="button" class="accordion-toggle-all" aria-expanded="false" data-show-all="{{ show_all }}" data-hide-all="{{ hide_all }}" hidden>
<button type="button" class="accordion-toggle-all" aria-expanded="false" data-show-all="{{ show_all }}" data-hide-all="{{ hide_all }}">
<span class="accordion-icon" aria-hidden="true"></span>
<span class="accordion-text">
{{ show_all }}
Expand Down
Loading