From c26edeaf21fffad9b6f0fd2f1da7d2f57000a5c7 Mon Sep 17 00:00:00 2001 From: Chris Yoong Date: Tue, 15 Feb 2022 10:57:36 +0000 Subject: [PATCH] Update Accordion design - Update GOVUK Frontend 4.0 version of Accordion - Maintain nodeListForEach, still required - Remove custom CSS for condensed layout, this is not in use on GOVK and goes against Design system Guidance. Additionally the accessibility impact has not been reviewed in depth. - Layering on GOVUK features on-top of Design System Accordion - Maintain existing GemAccoridon features of manuals-frontend that allow a user to navigation through this section. [1] - Temporarily update to Tabs to resolve testing errors. Part of 4.0 update. [1]https://github.com/alphagov/govuk_publishing_components/pull/1937 --- .../components/accordion.js | 312 ++--------------- .../components/_accordion.scss | 330 +----------------- .../components/print/_accordion.scss | 23 +- .../components/_accordion.html.erb | 47 ++- .../components/docs/accordion.yml | 95 ----- spec/components/accordion_spec.rb | 52 +-- spec/javascripts/components/accordion-spec.js | 131 ++++--- 7 files changed, 160 insertions(+), 830 deletions(-) diff --git a/app/assets/javascripts/govuk_publishing_components/components/accordion.js b/app/assets/javascripts/govuk_publishing_components/components/accordion.js index fdd9427492..36816c3fe0 100644 --- a/app/assets/javascripts/govuk_publishing_components/components/accordion.js +++ b/app/assets/javascripts/govuk_publishing_components/components/accordion.js @@ -1,32 +1,18 @@ /* global nodeListForEach */ -// = require ../vendor/polyfills/closest.js -// = require ../vendor/polyfills/indexOf.js // = require ../vendor/polyfills/common.js - +// This component relies on JavaScript from GOV.UK Frontend +// = require govuk/components/accordion/accordion.js window.GOVUK = window.GOVUK || {} -window.GOVUK.Modules = window.GOVUK.Modules || {}; +window.GOVUK.Modules = window.GOVUK.Modules || {} +window.GOVUK.Modules.GovukAccordion = window.GOVUKFrontend.Accordion; (function (Modules) { function GemAccordion ($module) { this.$module = $module - this.sectionClass = 'gem-c-accordion__section' - this.moduleId = this.$module.getAttribute('id') - this.sections = this.$module.querySelectorAll('.' + this.sectionClass) - this.openAllButton = '' - this.controlsClass = 'gem-c-accordion__controls' - this.openAllClass = 'gem-c-accordion__open-all' - this.openAllTextClass = 'gem-c-accordion__open-all-text' - this.sectionHeaderClass = 'gem-c-accordion__section-header' - this.sectionHeadingClass = 'gem-c-accordion__section-heading' - this.sectionSummaryClass = 'gem-c-accordion__section-summary' - this.sectionButtonClass = 'gem-c-accordion__section-button' - this.sectionExpandedClass = 'gem-c-accordion__section--expanded' - this.sectionInnerContent = 'gem-c-accordion__section-content' - this.toggleLinkClass = 'js-toggle-link' - this.sectionShowHideIconClass = 'gem-c-accordion__toggle-link' - this.sectionShowHideTextClass = 'gem-c-accordion__toggle-text' - this.upChevonIconClass = 'gem-c-accordion-nav__chevron' - this.downChevonIconClass = 'gem-c-accordion-nav__chevron--down' + this.sectionClass = 'govuk-accordion__section' + this.sectionClassExpanded = 'govuk-accordion__section--expanded' + this.sectionHeaderClass = 'govuk-accordion__section-header' + this.sectionInnerContent = 'govuk-accordion__section-content' // Translated component content and language attribute pulled from data attributes this.$module.actions = {} @@ -39,279 +25,14 @@ window.GOVUK.Modules = window.GOVUK.Modules || {}; } GemAccordion.prototype.init = function () { - this.browserSupportsSessionStorage = helper.checkForSessionStorage() - // Indicate that JavaScript has worked this.$module.classList.add('gem-c-accordion--active') - this.initControls() - this.initSectionHeaders() - // Feature flag for anchor tag navigation used on manuals if (this.$module.getAttribute('data-anchor-navigation') === 'true') { this.openByAnchorOnLoad() this.addEventListenersForAnchors() } - - // See if "Show all sections" button text should be updated - var areAllSectionsOpen = this.checkIfAllSectionsOpen() - this.updateOpenAllButton(areAllSectionsOpen) - } - - // Initialise controls and set attributes - GemAccordion.prototype.initControls = function () { - // Create "Show all" button and set attributes - this.openAllButton = document.createElement('button') - this.openAllButton.setAttribute('class', this.openAllClass) - this.openAllButton.setAttribute('aria-expanded', 'false') - - // Create icon, add to element - var icon = document.createElement('span') - icon.classList.add(this.upChevonIconClass) - this.openAllButton.appendChild(icon) - - // Create control wrapper and add controls to it - var accordionControls = document.createElement('div') - accordionControls.setAttribute('class', this.controlsClass) - accordionControls.appendChild(this.openAllButton) - this.$module.insertBefore(accordionControls, this.$module.firstChild) - - // Build additional wrapper for open all toggle text, place icon after wrapped text. - var wrapperOpenAllText = document.createElement('span') - wrapperOpenAllText.classList.add(this.openAllTextClass) - this.openAllButton.insertBefore(wrapperOpenAllText, this.openAllButton.childNodes[0] || null) - - // Handle events for the controls - this.openAllButton.addEventListener('click', this.onOpenOrCloseAllToggle.bind(this)) - } - - // Initialise section headers - GemAccordion.prototype.initSectionHeaders = function () { - // Loop through section headers - nodeListForEach(this.sections, function (section, i) { - // Set header attributes - var header = section.querySelector('.' + this.sectionHeaderClass) - - this.initHeaderAttributes(header, i) - this.setExpanded(this.isExpanded(section), section) - - // Handle events - header.addEventListener('click', this.onSectionToggle.bind(this, section)) - - // See if there is any state stored in sessionStorage and set the sections to - // open or closed. - this.setInitialState(section) - }.bind(this)) - } - - // Set individual header attributes - GemAccordion.prototype.initHeaderAttributes = function (headerWrapper, index) { - var span = headerWrapper.querySelector('.' + this.sectionButtonClass) - var heading = headerWrapper.querySelector('.' + this.sectionHeadingClass) - var summary = headerWrapper.querySelector('.' + this.sectionSummaryClass) - - // Copy existing span element to an actual button element, for improved accessibility. - var button = document.createElement('button') - button.setAttribute('id', this.moduleId + '-heading-' + (index + 1)) - button.setAttribute('aria-controls', this.moduleId + '-content-' + (index + 1)) - - // Create show / hide arrow icons with text. - var showIcons = document.createElement('span') - showIcons.classList.add(this.sectionShowHideIconClass, this.toggleLinkClass) - - // Add pause after heading for assistive technology. - var srPause = document.createElement('span') - srPause.classList.add('govuk-visually-hidden') - srPause.innerHTML = ', ' - - // Build additional copy for assistive technology - var srAdditionalCopy = document.createElement('span') - srAdditionalCopy.classList.add('govuk-visually-hidden') - srAdditionalCopy.innerHTML = this.$module.actions.thisSectionVisuallyHidden - - if (this.$module.actions.locale) { - srAdditionalCopy.lang = this.filterLocale('this_section_visually_hidden') - } - - // Build additional wrapper for toggle text, place icon after wrapped text. - var wrapperShowHideIcon = document.createElement('span') - var icon = document.createElement('span') - icon.classList.add(this.upChevonIconClass) - showIcons.appendChild(icon) - wrapperShowHideIcon.classList.add(this.sectionShowHideTextClass) - showIcons.insertBefore(wrapperShowHideIcon, showIcons.childNodes[0] || null) - - // Copy all attributes (https://developer.mozilla.org/en-US/docs/Web/API/Element/attributes) from span to button - for (var i = 0; i < span.attributes.length; i++) { - var attr = span.attributes.item(i) - button.setAttribute(attr.nodeName, attr.nodeValue) - } - - // span could contain HTML elements (see https://www.w3.org/TR/2011/WD-html5-20110525/content-models.html#phrasing-content) - button.innerHTML = span.innerHTML - heading.removeChild(span) - heading.appendChild(button) - button.appendChild(srPause) - - // If summary content exists add to DOM in correct order - if (typeof (summary) !== 'undefined' && summary !== null) { - button.setAttribute('aria-describedby', this.moduleId + '-summary-' + (index + 1)) - button.appendChild(summary) - } - - button.appendChild(showIcons) - button.appendChild(srAdditionalCopy) - } - - // When section toggled, set and store state - GemAccordion.prototype.onSectionToggle = function (section) { - var expanded = this.isExpanded(section) - this.setExpanded(!expanded, section) - - // Store the state in sessionStorage when a change is triggered - this.storeState(section) - } - - // When Open/Close All toggled, set and store state - GemAccordion.prototype.onOpenOrCloseAllToggle = function () { - var module = this - var sections = this.sections - var nowExpanded = !this.checkIfAllSectionsOpen() - - nodeListForEach(sections, function (section) { - module.setExpanded(nowExpanded, section) - // Store the state in sessionStorage when a change is triggered - module.storeState(section) - }) - - module.updateOpenAllButton(nowExpanded) - } - - // Set section attributes when opened/closed - GemAccordion.prototype.setExpanded = function (expanded, section) { - var icon = section.querySelector('.' + this.upChevonIconClass) - var showHideText = section.querySelector('.' + this.sectionShowHideTextClass) - var button = section.querySelector('.' + this.sectionButtonClass) - var newButtonText = expanded ? this.$module.actions.hideText : this.$module.actions.showText - - showHideText.innerHTML = newButtonText - button.setAttribute('aria-expanded', expanded) - button.classList.add(this.toggleLinkClass) - - if (this.$module.actions.locale) { - showHideText.lang = this.filterLocale(expanded ? 'hide_text' : 'show_text') - } - - // Swap icon, change class - if (expanded) { - section.classList.add(this.sectionExpandedClass) - icon.classList.remove(this.downChevonIconClass) - } else { - section.classList.remove(this.sectionExpandedClass) - icon.classList.add(this.downChevonIconClass) - } - - // See if "Show all sections" button text should be updated - var areAllSectionsOpen = this.checkIfAllSectionsOpen() - this.updateOpenAllButton(areAllSectionsOpen) - } - - // Get state of section - GemAccordion.prototype.isExpanded = function (section) { - return section.classList.contains(this.sectionExpandedClass) - } - - // Check if all sections are open - GemAccordion.prototype.checkIfAllSectionsOpen = function () { - // Get a count of all the Accordion sections - var sectionsCount = this.sections.length - // Get a count of all Accordion sections that are expanded - var expandedSectionCount = this.$module.querySelectorAll('.' + this.sectionExpandedClass).length - var areAllSectionsOpen = sectionsCount === expandedSectionCount - - return areAllSectionsOpen - } - - // Update "Show all sections" button - GemAccordion.prototype.updateOpenAllButton = function (expanded) { - var icon = this.openAllButton.querySelector('.' + this.upChevonIconClass) - var openAllCopy = this.openAllButton.querySelector('.' + this.openAllTextClass) - var newButtonText = expanded ? this.$module.actions.hideAllText : this.$module.actions.showAllText - - this.openAllButton.setAttribute('aria-expanded', expanded) - openAllCopy.innerHTML = newButtonText - - if (this.$module.actions.locale) { - openAllCopy.lang = this.filterLocale(expanded ? 'hide_all_text' : 'show_all_text') - } - - // Swap icon, toggle class - if (expanded) { - icon.classList.remove(this.downChevonIconClass) - } else { - icon.classList.add(this.downChevonIconClass) - } - } - - var helper = { - checkForSessionStorage: function () { - var testString = 'this is the test string' - var result - try { - window.sessionStorage.setItem(testString, testString) - result = window.sessionStorage.getItem(testString) === testString.toString() - window.sessionStorage.removeItem(testString) - return result - } catch (exception) { - if ((typeof console === 'undefined' || typeof console.log === 'undefined')) { - console.log('Notice: sessionStorage not available.') - } - } - } - } - - // Set the state of the accordions in sessionStorage - GemAccordion.prototype.storeState = function (section) { - if (this.browserSupportsSessionStorage) { - // We need a unique way of identifying each content in the GemAccordion. Since - // an `#id` should be unique and an `id` is required for `aria-` attributes - // `id` can be safely used. - var button = section.querySelector('.' + this.sectionButtonClass) - - if (button) { - var contentId = button.getAttribute('aria-controls') - var contentState = button.getAttribute('aria-expanded') - - if (typeof contentId === 'undefined' && (typeof console === 'undefined' || typeof console.log === 'undefined')) { - console.error(new Error('No aria controls present in accordion section heading.')) - } - - if (typeof contentState === 'undefined' && (typeof console === 'undefined' || typeof console.log === 'undefined')) { - console.error(new Error('No aria expanded present in accordion section heading.')) - } - - // Only set the state when both `contentId` and `contentState` are taken from the DOM. - if (contentId && contentState) { - window.sessionStorage.setItem(contentId, contentState) - } - } - } - } - - // Read the state of the accordions from sessionStorage - GemAccordion.prototype.setInitialState = function (section) { - if (this.browserSupportsSessionStorage) { - var button = section.querySelector('.' + this.sectionButtonClass) - - if (button) { - var contentId = button.getAttribute('aria-controls') - var contentState = contentId ? window.sessionStorage.getItem(contentId) : null - - if (contentState !== null) { - this.setExpanded(contentState === 'true', section) - } - } - } } // Navigate to and open accordions with anchored content on page load if a hash is present @@ -327,6 +48,7 @@ window.GOVUK.Modules = window.GOVUK.Modules || {}; // Adding an event listener to all anchor link a tags in an accordion is risky but we circumvent this risk partially by only being a layer of accordion behaviour instead of any sort of change to link behaviour GemAccordion.prototype.addEventListenersForAnchors = function () { var links = this.$module.querySelectorAll('.' + this.sectionInnerContent + ' a[href*="#"]') + nodeListForEach(links, function (link) { if (link.pathname === window.location.pathname) { link.addEventListener('click', this.openForAnchor.bind(this, link.hash.split('#')[1])) @@ -337,9 +59,18 @@ window.GOVUK.Modules = window.GOVUK.Modules || {}; // Find the parent accordion section for the given id and open it GemAccordion.prototype.openForAnchor = function (hash) { var target = document.getElementById(hash) - var section = this.getContainingSection(target) - - this.setExpanded(true, section) + var $section = this.getContainingSection(target) + var $header = $section.querySelector('.' + this.sectionHeaderClass) + var $expanded = this.getContainingSection($section) + var $parent = $header.parentElement + + // manuals-frontend features: + // Should the target anchor link be within the same page, open section - navigate normally + // Should the target anchor link be within a different, closed section, open this section + // Should the target anchor link be within a different page and different, closed section open this section + if ($expanded && (!$parent.classList.contains(this.sectionClassExpanded))) { + $header.click() + } } // Loop through the given id's ancestors until the parent section class is found @@ -347,7 +78,6 @@ window.GOVUK.Modules = window.GOVUK.Modules || {}; while (!target.classList.contains(this.sectionClass)) { target = target.parentElement } - return target } diff --git a/app/assets/stylesheets/govuk_publishing_components/components/_accordion.scss b/app/assets/stylesheets/govuk_publishing_components/components/_accordion.scss index 4a68f5c007..69e8166583 100644 --- a/app/assets/stylesheets/govuk_publishing_components/components/_accordion.scss +++ b/app/assets/stylesheets/govuk_publishing_components/components/_accordion.scss @@ -1,330 +1,2 @@ @import "mixins/prefixed-transform"; - -$gem-c-accordion-border-width: 3px; -$gem-c-accordion-bottom-border-width: 1px; - -// Buttons within the sections don’t need default styling -.gem-c-accordion__section-button { - display: inline-block; - margin-bottom: 0; - padding-top: govuk-spacing(3); - font-weight: bold; - @include govuk-font($size: 24, $weight: bold); -} - -.gem-c-accordion__section-header { - padding-top: govuk-spacing(2) 0; -} - -.gem-c-accordion__section-heading { - margin: govuk-spacing(1) 0; -} - -.js-enabled { - .gem-c-accordion { - border-bottom: $gem-c-accordion-bottom-border-width solid $govuk-border-colour; - } - - .gem-c-accordion__controls { - text-align: left; - } - - .gem-c-accordion__open-all { - position: relative; - z-index: 1; - border-width: 0; - color: $govuk-link-colour; - background: none; - -webkit-appearance: none; - cursor: pointer; - margin-bottom: govuk-spacing(4); - padding: 0 govuk-spacing(1) govuk-spacing(1) 0; - @include govuk-font($size: 16); - @include govuk-link-common; - @include govuk-link-style-default; - - // Remove default button focus outline in Firefox - &::-moz-focus-inner { - padding: 0; - border: 0; - } - } - - // Create Chervon icon align with text - .gem-c-accordion-nav__chevron { - vertical-align: text-top; - display: inline-block; - box-sizing: border-box; - position: relative; - width: govuk-em(20, 14); - height: govuk-em(20, 14); - margin-left: govuk-em(5, 14); - border: govuk-em(1, 14) solid; - border-radius: govuk-em(100, 14); - - // Main icon size across views, yet keep responsive for zoom - @include govuk-media-query($from: tablet) { - width: govuk-em(20, 16); - height: govuk-em(20, 16); - margin-left: govuk-em(5, 16); - border: govuk-em(1, 16) solid; - } - - &:after { - @include prefixed-transform($rotate: -45deg); - content: ""; - display: block; - box-sizing: border-box; - position: absolute; - overflow: visible; - width: govuk-em(6, 14); - height: govuk-em(6, 14); - border-top: govuk-em(2, 14) solid; - border-right: govuk-em(2, 14) solid; - left: govuk-em(6, 14); - bottom: govuk-em(5, 14); - - @include govuk-media-query($from: tablet) { - width: govuk-em(6, 16); - height: govuk-em(6, 16); - border-top: govuk-em(2, 16) solid; - border-right: govuk-em(2, 16) solid; - left: govuk-em(6, 16); - bottom: govuk-em(5, 16); - } - } - } - - .gem-c-accordion__open-all:hover, - .gem-c-accordion__section-button:hover { - .gem-c-accordion-nav__chevron { - color: $govuk-link-hover-colour; - text-decoration: none; - } - } - - // Focus state, also to change chervon icon to black - .gem-c-accordion__open-all:focus { - .gem-c-accordion-nav__chevron { - color: $govuk-focus-text-colour; - text-decoration: none; - } - } - - // Rotate icon to create "Down" version - .gem-c-accordion-nav__chevron--down { - @include prefixed-transform($rotate: 180deg); - } - - .gem-c-accordion__section-heading { - // Override browser defaults to ensure consistent element height - margin-top: 0; // Override browser default - margin-bottom: 0; // Override browser default - @include govuk-font(24); - } - - // Section headers have a pointer cursor as an additional affordance - .gem-c-accordion__section-header { - position: relative; - } - - // For devices that can't hover such as touch devices, - // remove hover state as it can be stuck in that state (iOS). - @media (hover: none) { - .gem-c-accordion__section-header:hover { - border-top-color: $govuk-link-colour; - box-shadow: inset 0 $gem-c-accordion-border-width 0 0 $govuk-link-colour; - - .gem-c-accordion__section-button { - border-top-color: $govuk-link-colour; - } - } - } - - // Buttons within the headers don’t need default styling - .gem-c-accordion__section-button { - padding: govuk-spacing(2) 0 govuk-spacing(5); - position: relative; - margin: 0; - border-width: $gem-c-accordion-bottom-border-width 0 0 0; - border-top: $gem-c-accordion-bottom-border-width solid $govuk-border-colour; - color: $govuk-text-colour; - background: none; - text-align: left; - cursor: pointer; - -webkit-appearance: none; - @include govuk-typography-common; - width: 100%; - - &:hover { - color: $govuk-link-hover-colour; - } - - &:active { - z-index: 1; - color: $govuk-link-active-colour; - background: none; - } - - // Remove default button focus outline in Firefox - &::-moz-focus-inner { - padding: 0; - border: 0; - } - } - - .gem-c-accordion__section-button:focus { - @include govuk-focused-text; - // Overwrite focus border to top - box-shadow: 0 0, 0 -4px; - border-top: 1px solid transparent; - - // Focus state to change chervon icon colour within individual sections - .gem-c-accordion-nav__chevron { - color: $govuk-text-colour; - } - } - - // Extend the touch area of the button to span the section header - .gem-c-accordion__section-button:after { - content: ""; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - } - - .gem-c-accordion__section-button:hover:not(:focus) { - text-decoration: none; - } - - // For devices that can't hover such as touch devices, - // remove hover state as it can be stuck in that state (iOS). - @media (hover: none) { - .gem-c-accordion__section-button:hover { - text-decoration: none; - } - } - - // Add toggle link with Chevron icon on right. - .gem-c-accordion__toggle-link { - display: block; - color: $govuk-link-colour; - text-transform: capitalize; - margin-top: govuk-spacing(1); - margin-bottom: govuk-spacing(1); - @include govuk-font($size: 16, $line-height: 1); - - &:active { - background: $govuk-link-active-colour; - } - } - - .gem-c-accordion__toggle-text { - display: inline-block; - // Setting width of the text so the icon doesn't shift left or right when - // toggled: - min-width: govuk-em(40, 16); - } - - // On hover add underline to toggle link only: - .gem-c-accordion__section-button:hover .gem-c-accordion__toggle-text { - color: $govuk-link-hover-colour; - @include govuk-link-decoration; - @include govuk-link-hover-decoration; - } - - // Ensure the correct focus sstate text colour and no underline: - .gem-c-accordion__section-button:focus .gem-c-accordion__toggle-text { - color: $govuk-focus-text-colour; - text-decoration: none; - } - - .gem-c-accordion__open-all-text { - min-width: govuk-em(120, 16); - display: inline-block; - text-align: left; - } - - .gem-c-accordion__open-all:hover { - .gem-c-accordion__open-all-text { - @include govuk-link-decoration; - @include govuk-link-hover-decoration; - } - } - - .gem-c-accordion__open-all:focus .gem-c-accordion__open-all-text { - text-decoration: none; - } - - // Change the summary subheading size. - .gem-c-accordion__section-summary { - @include govuk-responsive-margin(1, "top"); - @include govuk-responsive-margin(2, "bottom"); - @include govuk-typography-common; - @include govuk-typography-responsive($size: 19); - } - - // Hide body of expanded sections - .gem-c-accordion__section-content { - display: none; - @include govuk-responsive-padding(0, "top"); - @include govuk-responsive-padding(8, "bottom"); - } - - // Show the body of expanded sections - .gem-c-accordion__section--expanded .gem-c-accordion__section-content { - display: block; - } - - // Remove the bottom margin from the last item inside the content - .gem-c-accordion__section-content > :last-child { - margin-bottom: 0; - } - - // Condensed layout - .gem-c-accordion--condensed { - .gem-c-accordion__open-all { - margin-bottom: govuk-spacing(5); - @include govuk-font($size: 14, $line-height: 1); - } - - .gem-c-accordion__section-button { - @include govuk-typography-responsive($size: 19, $important: true); - padding-top: govuk-spacing(1); - padding-bottom: govuk-spacing(5); - } - - // Reduce Chevron size - .gem-c-accordion-nav__chevron { - @include prefixed-transform($scale: .875); - width: govuk-em(20, 14); - height: govuk-em(20, 14); - margin-left: govuk-em(5, 14); - border: govuk-em(1, 14) solid; - border-radius: govuk-em(100, 14); - - &:after { - width: govuk-em(6, 14); - height: govuk-em(6, 14); - border-top: govuk-em(2, 14) solid; - border-right: govuk-em(2, 14) solid; - left: govuk-em(6, 14); - bottom: govuk-em(5, 14); - } - } - - .gem-c-accordion-nav__chevron--down { - @include prefixed-transform($rotate: 180deg, $scale: .875); - } - - .gem-c-accordion__section-summary { - @include govuk-typography-responsive($size: 16, $important: true); - } - - .gem-c-accordion__toggle-link { - @include govuk-font($size: 14, $line-height: 1); - } - } -} +@import "govuk/components/accordion/accordion"; diff --git a/app/assets/stylesheets/govuk_publishing_components/components/print/_accordion.scss b/app/assets/stylesheets/govuk_publishing_components/components/print/_accordion.scss index 87c0a76f3f..4be4380c28 100644 --- a/app/assets/stylesheets/govuk_publishing_components/components/print/_accordion.scss +++ b/app/assets/stylesheets/govuk_publishing_components/components/print/_accordion.scss @@ -6,34 +6,23 @@ @import "../accordion"; // Open all of the accordion sections. -.gem-c-accordion__section-content { +.govuk-accordion__section-content { display: block !important; // stylelint-disable-line declaration-no-important } // Change the colour from the blue link colour to black. -.gem-c-accordion__section-button { +.govuk-accordion__section-button { color: govuk-colour("black") !important; // stylelint-disable-line declaration-no-important } -// Removing spacing -.gem-c-accordion__section-header { - padding-bottom: govuk-spacing(1); -} - // Change the summary subheading size. -.gem-c-accordion__section-summary { +.govuk-accordion__section-summary { @include govuk-typography-common; @include govuk-typography-responsive($size: 16, $important: true); } -// Hide the unusable "Show all sections" button and the "Chevron" icons. -.gem-c-accordion__open-all, -.gem-c-accordion__icon, -.gem-c-accordion__toggle-link { - display: none !important; // stylelint-disable-line declaration-no-important -} - -// Hide all hidden content -.gem-c-accordion .govuk-visually-hidden { +// Hide the unusable "Open all" button and the "+" icons. +.govuk-accordion__open-all, +.govuk-accordion__icon { display: none !important; // stylelint-disable-line declaration-no-important } diff --git a/app/views/govuk_publishing_components/components/_accordion.html.erb b/app/views/govuk_publishing_components/components/_accordion.html.erb index 5ea00c9371..0e613d1e3e 100644 --- a/app/views/govuk_publishing_components/components/_accordion.html.erb +++ b/app/views/govuk_publishing_components/components/_accordion.html.erb @@ -4,12 +4,10 @@ id ||= "default-id-#{SecureRandom.hex(4)}" items ||= [] - condensed ||= false anchor_navigation ||= false - accordion_classes = %w(gem-c-accordion) - accordion_classes << 'gem-c-accordion--condensed' if condensed - accordion_classes << shared_helper.get_margin_bottom + accordion_classes = %w(gem-c-accordion govuk-accordion) + accordion_classes << (shared_helper.get_margin_bottom) translations = { show_text: "components.accordion.show", @@ -22,7 +20,7 @@ locales = {} data_attributes ||= {} - data_attributes[:module] = 'gem-accordion' + data_attributes[:module] = 'govuk-accordion gem-accordion' data_attributes[:anchor_navigation] = anchor_navigation translations.each do |key, translation| @@ -39,27 +37,44 @@ data_attributes[:locale] = unique_locales[0] end end + %> <% if items.any? %> <%= tag.div(class: accordion_classes, id: id, data: data_attributes) do %> <% items.each_with_index do |item, i| %> <% + # The GOVUK Frontend JavaScript for this component + # uses a loop starting at 1 for accessing heading ID values, + # hence the increment below index = i + 1 - section_classes = %w(gem-c-accordion__section) - section_classes << 'gem-c-accordion__section--expanded' if item[:expanded] + item[:data_attributes] ||= nil - summary_classes = %w(gem-c-accordion__section-summary govuk-body) - %> + section_classes = %w(govuk-accordion__section) + section_classes << 'govuk-accordion__section--expanded' if item[:expanded] - <%= tag.section(class: section_classes) do %> -
- <%= content_tag(shared_helper.get_heading_level, class: 'gem-c-accordion__section-heading', id: item[:heading][:id]) do %> - <%= tag.span(item[:heading][:text], id: "#{id}-heading-#{index}", data: item[:data_attributes], class: 'gem-c-accordion__section-button') %> - <% end %> + summary_classes = %w(govuk-accordion__section-summary govuk-body) + %> + <%= tag.div(class: section_classes) do %> + <%= tag.div(class: "govuk-accordion__section-header") do %> + <%= + content_tag( + shared_helper.get_heading_level, + content_tag( + 'span', + item[:heading][:text], + class: "govuk-accordion__section-button", + id: "#{id}-heading-#{index}" + + ), + class: "govuk-accordion__section-heading", + id: item[:heading][:id], + data: item[:data_attributes] + ) + %> <%= tag.div(item[:summary][:text], id: "#{id}-summary-#{index}", class: summary_classes) if item[:summary].present? %> -
- <%= tag.div(item[:content][:html], id: "#{id}-content-#{index}", class: "gem-c-accordion__section-content", 'aria-label': "#{item[:heading][:text]}") %> + <% end %> + <%= tag.div(item[:content][:html], id: "#{id}-content-#{index}", class: "govuk-accordion__section-content", 'aria-labelledby': "#{id}-heading-#{index}") %> <% end %> <% end %> <% end %> diff --git a/app/views/govuk_publishing_components/components/docs/accordion.yml b/app/views/govuk_publishing_components/components/docs/accordion.yml index bb44607e55..3f506faa90 100644 --- a/app/views/govuk_publishing_components/components/docs/accordion.yml +++ b/app/views/govuk_publishing_components/components/docs/accordion.yml @@ -309,98 +309,3 @@ examples: text: How people read content: html:

This is the content for How people read.

- condensed_layout: - description: | - This layout is for when a smaller accordion is required. Since smaller screens trigger a single column layout, this modifier only makes the accordion smaller when viewed on large screens. - data: - condensed: true - items: - - heading: - text: Understanding agile project management - content: - html: - '' - - heading: - text: Working with agile methods - summary: - text: Workspaces, tools and techniques, user stories, planning. - content: - html: - '' - - heading: - text: Governing agile services - content: - html: - '' - - heading: - text: Phases of an agile project - content: - html: - '' diff --git a/spec/components/accordion_spec.rb b/spec/components/accordion_spec.rb index 19e21217b4..739cee41e9 100644 --- a/spec/components/accordion_spec.rb +++ b/spec/components/accordion_spec.rb @@ -31,14 +31,14 @@ def component_name render_component(test_data) - assert_select ".gem-c-accordion__section-button", text: "Heading 1", count: 1 - assert_select ".gem-c-accordion__section-content", text: /Content 1./, count: 1 + assert_select ".govuk-accordion__section-button", text: "Heading 1", count: 1 + assert_select ".govuk-accordion__section-content", text: /Content 1./, count: 1 - assert_select ".gem-c-accordion__section-button", text: "Heading 2", count: 1 - assert_select ".gem-c-accordion__section-content", text: /Content 2./, count: 1 + assert_select ".govuk-accordion__section-button", text: "Heading 2", count: 1 + assert_select ".govuk-accordion__section-content", text: /Content 2./, count: 1 - assert_select ".gem-c-accordion__section-button", text: "Heading 3", count: 1 - assert_select ".gem-c-accordion__section-content", text: /Content 3./, count: 1 + assert_select ".govuk-accordion__section-button", text: "Heading 3", count: 1 + assert_select ".govuk-accordion__section-content", text: /Content 3./, count: 1 end it "uses the correct id, and interpolates it correctly" do @@ -174,7 +174,7 @@ def component_name assert_select "[data-gtm='google-tag-manager']", count: 2 end - it '`data-module="gem-accordion"` attribute is present when no custom data attributes given' do + it '`data-module="govuk-accordion"` attribute is present when no custom data attributes given' do test_data = { id: "test-for-module-data-attributes", items: [ @@ -189,10 +189,10 @@ def component_name ], } render_component(test_data) - assert_select "[data-module='gem-accordion']", count: 1 + assert_select "[data-module='govuk-accordion gem-accordion']", count: 1 end - it '`data-module="gem-accordion"` attribute is present when custom data attributes given' do + it '`data-module="govuk-accordion"` attribute is present when custom data attributes given' do test_data = { id: "test-for-module-data-attributes", data_attributes: { @@ -216,7 +216,7 @@ def component_name ], } render_component(test_data) - assert_select "[data-module='gem-accordion']", count: 1 + assert_select "[data-module='govuk-accordion gem-accordion']", count: 1 assert_select "[data-gtm]", count: 2 assert_select "[data-gtm='this-is-gtm']", count: 1 assert_select "[data-gtm='this-is-a-second-gtm']", count: 1 @@ -225,8 +225,7 @@ def component_name it "section has class added when expanded flag is present" do test_data = { - id: "condensed-layout", - condensed: true, + id: "test-for-expanded-layout", items: [ { heading: { text: "Heading 1" }, @@ -242,14 +241,13 @@ def component_name ], } render_component(test_data) - assert_select ".gem-c-accordion__section.gem-c-accordion__section--expanded", count: 1 - assert_select ".gem-c-accordion__section", count: 2 + assert_select ".govuk-accordion__section.govuk-accordion__section--expanded", count: 1 + assert_select ".govuk-accordion__section", count: 2 end it "adds id to heading when attribute passed" do test_data = { - id: "condensed-layout", - condensed: true, + anchor_navigation: true, items: [ { heading: { text: "Heading 1", id: "heading-with-id" }, @@ -264,25 +262,9 @@ def component_name ], } render_component(test_data) - assert_select ".gem-c-accordion__section-heading", count: 2 - assert_select ".gem-c-accordion__section-heading#heading-with-id", count: 1 - assert_select ".gem-c-accordion__section-heading:not([id])", count: 1 - end - - it "condensed class added correctly" do - test_data = { - id: "condensed-layout", - condensed: true, - items: [ - { - heading: { text: "Heading 1" }, - summary: { text: "Summary 1." }, - content: { html: "

Content 1.

" }, - }, - ], - } - render_component(test_data) - assert_select ".gem-c-accordion.gem-c-accordion--condensed", count: 1 + assert_select ".govuk-accordion__section-heading", count: 2 + assert_select ".govuk-accordion__section-heading#heading-with-id", count: 1 + assert_select ".govuk-accordion__section-heading:not([id])", count: 1 end it "loop index starts at one, not zero (thanks Nunjucks.)" do diff --git a/spec/javascripts/components/accordion-spec.js b/spec/javascripts/components/accordion-spec.js index dd7884eec5..2423041cd0 100644 --- a/spec/javascripts/components/accordion-spec.js +++ b/spec/javascripts/components/accordion-spec.js @@ -8,29 +8,66 @@ describe('An accordion component', function () { var container = document.createElement('div') var localeData = {} var html = - '
' + - '
' + - '
' + - '

' + - 'Accordion 1' + - '

' + - '
' + - '
' + - '

This is content for accordion 1 of 2

' + - '

This content contains a link

' + - '
' + - '
' + - '
' + - '
' + - '

' + - 'Accordion 2' + - '

' + - '
' + - '
' + - '

This is content for accordion 2 of 2

' + - '
' + - '
' + - '
' + '