Skip to content

Commit

Permalink
PP-13398 make the multi-select dropdown enhancement easier to use
Browse files Browse the repository at this point in the history
  • Loading branch information
nlsteers committed Dec 4, 2024
1 parent fbba0ad commit c8163fa
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 65 deletions.
41 changes: 14 additions & 27 deletions app/assets/sass/components/multi-select.scss
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@
-webkit-appearance: none;
appearance: none;
max-width: 100%;

display: block;
cursor: pointer;

div {
Expand All @@ -47,48 +45,37 @@
}
}

.multi-select-dropdown{
.multi-select-dropdown {
@extend .govuk-clearfix;

top: 0;
left: 0;
background-color: govuk-colour("white");
z-index: 10;
position: absolute;
border: 2px solid $govuk-text-colour;
box-sizing: border-box;
width: 100%;
font-size: 16px;

.multi-select-close-button {
min-height: 34px;
width: 100%;
margin-bottom: 0;
}

.multi-select-dropdown-inner-container {
padding: 10px 5px;
overflow-y: scroll;
overflow-x: hidden;

.govuk-checkboxes__item {
label {
width: 100%;
}
}
}

&:focus {
outline: 3px solid $govuk-focus-colour;
outline-offset: 0;
}

.multi-select-dropdown-close-area {
width: 15%;
top: 0;
right: 13px;
position: absolute;
z-index: 14;
height: 100%;
background-image: url("/public/images/dd-arrow.svg");
background-repeat: no-repeat;
background-position: right -4px top 6px;
}

.govuk-checkboxes__item {
display: flex;
align-items: start;
text-align: left;
cursor: pointer;
input {
z-index: 12;
}
}
}
59 changes: 35 additions & 24 deletions app/browsered/multi-select.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
'use strict'
// TODO: we should probably do some browser testing in this project to prove this all works as intended

const multiSelect = require('../views/includes/multi-select.njk')

// Polyfills introduced as a temporary fix to make Smoketests pass. See PP-3489
require('./polyfills')

// Variables
const MAXIMUM_VISIBLE_ITEMS = 8.5 // Maximum amount of items to show in dropdown
const MINIMUM_VISIBLE_ITEMS = 3.5 // Minimum amount of items to show in dropdown (assuming total is larger than this value)

// Selectors
const ENHANCEMENT_SELECTOR = [...document.querySelectorAll('select[data-enhance-multiple]')]
const TOP_LEVEL_SELECTOR = '.multi-select'
const OPEN_BUTTON_SELECTOR = '.multi-select-title'
const CLOSE_BUTTON_SELECTOR = '.multi-select-dropdown-close-area'
const CLOSE_BUTTON_SELECTOR = '.multi-select-close-button'
const DROPDOWN_SELECTOR = '.multi-select-dropdown'
const SCROLL_CONTAINER_SELECTOR = '.multi-select-dropdown-inner-container'
const ITEM_SELECTOR = '.govuk-checkboxes__input'
Expand Down Expand Up @@ -56,11 +53,24 @@ function progressivelyEnhanceSelects () {
const dropdown = [...newMultiSelect.querySelectorAll(DROPDOWN_SELECTOR)][0]
const scrollContainer = [...newMultiSelect.querySelectorAll(SCROLL_CONTAINER_SELECTOR)][0]

// close if user clicks outside the dropdown
document.addEventListener('click', (event) => {
const dropdowns = document.querySelectorAll(DROPDOWN_SELECTOR)
dropdowns.forEach(dropdown => {
if (!dropdown.contains(event.target) &&
!event.target.closest(OPEN_BUTTON_SELECTOR)) {
dropdown.style.visibility = 'hidden'
dropdown.closest(TOP_LEVEL_SELECTOR)
.querySelector(OPEN_BUTTON_SELECTOR)
.setAttribute('aria-expanded', false)
}
})
}, false)

openButton.addEventListener('click', onOpenButtonClick, false)
closeButton.addEventListener('click', onCloseAreaClick, false)
closeButton.addEventListener('click', onCloseButtonClick, false)
items.forEach(item => {
item.addEventListener('change', onItemChange, false)
item.addEventListener('blur', onItemBlur, false)
})
const itemHeight = ([...items].map(item => item.parentNode.offsetHeight).reduce((sum, value) => sum + value) / items.length)
const maxVisibleItems = Math.min(Math.floor(((window.innerHeight - openButton.getBoundingClientRect().top) / itemHeight) - 0.5) + 0.5, MAXIMUM_VISIBLE_ITEMS)
Expand Down Expand Up @@ -92,29 +102,30 @@ const closeMultiSelectOnEscapeKeypress = function () {
}
}

const onItemBlur = event => {
const dropdown = event.target.closest(DROPDOWN_SELECTOR)
setTimeout(() => {
if ([...dropdown.querySelectorAll(`${ITEM_SELECTOR}:focus`)].length <= 0) {
dropdown.style.visibility = 'hidden'
dropdown.closest(TOP_LEVEL_SELECTOR).querySelector(OPEN_BUTTON_SELECTOR).setAttribute('aria-expanded', false)
}
}, 100)
const onCloseButtonClick = event => {
const { target } = event
event.stopPropagation()
const dropdown = target.closest(TOP_LEVEL_SELECTOR).querySelector(DROPDOWN_SELECTOR)
dropdown.style.visibility = 'hidden'
target.setAttribute('aria-expanded', false)
}

const onOpenButtonClick = event => {
const { target } = event
target.blur();
[...target.closest(TOP_LEVEL_SELECTOR).querySelectorAll(DROPDOWN_SELECTOR)][0].style.visibility = 'visible';
[...target.closest(TOP_LEVEL_SELECTOR).querySelectorAll(ITEM_SELECTOR)][0].focus()
target.closest(OPEN_BUTTON_SELECTOR).setAttribute('aria-expanded', true)
}
event.stopPropagation()

// close all other dropdowns first
document.querySelectorAll(DROPDOWN_SELECTOR).forEach(dropdown => {
dropdown.style.visibility = 'hidden'
dropdown.closest(TOP_LEVEL_SELECTOR)
.querySelector(OPEN_BUTTON_SELECTOR)
.setAttribute('aria-expanded', false)
})

const onCloseAreaClick = event => {
const { target } = event;
[...target.closest(TOP_LEVEL_SELECTOR).querySelectorAll(OPEN_BUTTON_SELECTOR)][0].focus();
[...target.closest(TOP_LEVEL_SELECTOR).querySelectorAll(DROPDOWN_SELECTOR)][0].style.visibility = 'hidden'
target.closest(OPEN_BUTTON_SELECTOR).setAttribute('aria-expanded', false)
const dropdown = target.closest(TOP_LEVEL_SELECTOR).querySelector(DROPDOWN_SELECTOR)
dropdown.style.visibility = 'visible'
dropdown.querySelector(ITEM_SELECTOR).focus()
target.setAttribute('aria-expanded', true)
}

const onItemChange = event => {
Expand Down
30 changes: 16 additions & 14 deletions app/views/includes/multi-select.njk
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
<div id="{{id}}__container" class="multi-select">
<div id="{{ id }}__container" class="multi-select">
<button type="button"
class="multi-select-title"
id="{{id}}" aria-expanded="false">
<div><span class="govuk-visually-hidden">Currently selected: </span><span class="multi-select-current-selections"></span></div>
class="multi-select-title"
id="{{ id }}" aria-expanded="false">
<div><span class="govuk-visually-hidden">Currently selected: </span><span
class="multi-select-current-selections"></span></div>
</button>
<div role="group" aria-labelledby="option-select-title-{{name}}"
class="multi-select-dropdown"
id="list-of-sectors-{{ name }}"
style="visibility:hidden;">
<div class="multi-select-dropdown-inner-container govuk-checkboxes">
<div role="group" aria-labelledby="option-select-title-{{ name }}"
class="multi-select-dropdown"
id="list-of-sectors-{{ name }}"
style="visibility:hidden;">
<button type="button" class="govuk-button govuk-button--secondary multi-select-close-button">Close</button>
<div class="multi-select-dropdown-inner-container govuk-checkboxes" data-module="govuk-checkboxes">
{% for item in items %}
<div class="govuk-checkboxes__item">
<input class="govuk-checkboxes__input govuk-!-font-size-16" name="{{name}}" value="{{ item.value }}" id="{{ item.id }}" type="checkbox" {% if item.checked %}checked{% endif %}>
<label for="{{ item.id }}" class="govuk-label govuk-checkboxes__label govuk-!-font-size-16 multi-select-label-{{ name }}">{{ item.text }}</label>
</div>
<div class="govuk-checkboxes__item">
<input class="govuk-checkboxes__input" name="{{ name }}" value="{{ item.value }}" id="{{ item.id }}"
type="checkbox" {% if item.checked %}checked{% endif %}>
<label for="{{ item.id }}" class="govuk-label govuk-checkboxes__label">{{ item.text }}</label>
</div>
{% endfor %}
</div>
<div class="multi-select-dropdown-close-area"></div>
</div>
</div>

0 comments on commit c8163fa

Please sign in to comment.