Skip to content

Commit

Permalink
Merge pull request #1905 from alphagov/add-reorderable-component
Browse files Browse the repository at this point in the history
Adds Reorderable lists component
  • Loading branch information
DilwoarH authored Mar 9, 2021
2 parents c8e5670 + cf44d25 commit 4200c9d
Show file tree
Hide file tree
Showing 11 changed files with 637 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* Remove lists from summary action links ([PR #1956](https://github.com/alphagov/govuk_publishing_components/pull/1956))
* Fix GOV.UK Frontend deprecation warning for component-guide print stylesheet ([PR #1961](https://github.com/alphagov/govuk_publishing_components/pull/1961))
* Update search box button ([PR #1957](https://github.com/alphagov/govuk_publishing_components/pull/1957))
* Adds Reorderable lists component ([PR #1905](https://github.com/alphagov/govuk_publishing_components/pull/1905))

## 24.4.1

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
//= require sortablejs/Sortable.js
window.GOVUK = window.GOVUK || {}
window.GOVUK.Modules = window.GOVUK.Modules || {};

(function (Modules) {
function ReorderableList () { }

ReorderableList.prototype.start = function ($module) {
this.$module = $module[0]
this.$upButtons = this.$module.querySelectorAll('.js-reorderable-list-up')
this.$downButtons = this.$module.querySelectorAll('.js-reorderable-list-down')

this.sortable = window.Sortable.create(this.$module, { // eslint-disable-line new-cap
chosenClass: 'gem-c-reorderable-list__item--chosen',
dragClass: 'gem-c-reorderable-list__item--drag',
onSort: function () {
this.updateOrderIndexes()
this.triggerEvent(this.$module, 'reorder-drag')
}.bind(this)
})

if (typeof window.matchMedia === 'function') {
this.setupResponsiveChecks()
} else {
this.sortable.option('disabled', true)
}

var boundOnClickUpButton = this.onClickUpButton.bind(this)
this.$upButtons.forEach(function (button) {
button.addEventListener('click', boundOnClickUpButton)
})

var boundOnClickDownButton = this.onClickDownButton.bind(this)
this.$downButtons.forEach(function (button) {
button.addEventListener('click', boundOnClickDownButton)
})
}

ReorderableList.prototype.setupResponsiveChecks = function () {
var tabletBreakpoint = '40.0625em' // ~640px
this.mediaQueryList = window.matchMedia('(min-width: ' + tabletBreakpoint + ')')
this.mediaQueryList.addListener(this.checkMode.bind(this))
this.checkMode()
}

ReorderableList.prototype.checkMode = function () {
this.sortable.option('disabled', !this.mediaQueryList.matches)
}

ReorderableList.prototype.onClickUpButton = function (e) {
var item = e.target.closest('.gem-c-reorderable-list__item')
var previousItem = item.previousElementSibling
if (item && previousItem) {
item.parentNode.insertBefore(item, previousItem)
this.updateOrderIndexes()
}
// if triggered by keyboard preserve focus on button
if (e.detail === 0) {
if (item !== item.parentNode.firstElementChild) {
e.target.focus()
} else {
e.target.nextElementSibling.focus()
}
}
this.triggerEvent(e.target, 'reorder-move-up')
}

ReorderableList.prototype.onClickDownButton = function (e) {
var item = e.target.closest('.gem-c-reorderable-list__item')
var nextItem = item.nextElementSibling
if (item && nextItem) {
item.parentNode.insertBefore(item, nextItem.nextElementSibling)
this.updateOrderIndexes()
}
// if triggered by keyboard preserve focus on button
if (e.detail === 0) {
if (item !== item.parentNode.lastElementChild) {
e.target.focus()
} else {
e.target.previousElementSibling.focus()
}
}
this.triggerEvent(e.target, 'reorder-move-down')
}

ReorderableList.prototype.updateOrderIndexes = function () {
var $orderInputs = this.$module.querySelectorAll('.gem-c-reorderable-list__actions input')
$orderInputs.forEach(function (input, index) {
input.setAttribute('value', index + 1)
})
}

ReorderableList.prototype.triggerEvent = function (element, eventName) {
var params = { bubbles: true, cancelable: true }
var event

if (typeof window.CustomEvent === 'function') {
event = new window.CustomEvent(eventName, params)
} else {
event = document.createEvent('CustomEvent')
event.initCustomEvent(eventName, params.bubbles, params.cancelable)
}

element.dispatchEvent(event)
}

Modules.ReorderableList = ReorderableList
})(window.GOVUK.Modules)
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
@import "components/print-link";
@import "components/radio";
@import "components/related-navigation";
@import "components/reorderable-list";
@import "components/search";
@import "components/select";
@import "components/share-links";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
.gem-c-reorderable-list {
@include govuk-font(19, bold);

list-style-type: none;
margin-bottom: govuk-spacing(6);
margin-top: 0;
padding-left: 0;
position: relative;

.govuk-form-group {
margin-bottom: 0;
}
}

.gem-c-reorderable-list__item {
margin-bottom: govuk-spacing(3);
border: 1px solid $govuk-border-colour;
padding: govuk-spacing(3);
}

.gem-c-reorderable-list__item--chosen {
background-color: govuk-colour('light-grey');
outline: 2px dotted $govuk-border-colour;
}

.gem-c-reorderable-list__item--drag {
background-color: govuk-colour('white');
list-style-type: none;

.gem-c-reorderable-list__actions {
visibility: hidden;
}
}

.gem-c-reorderable-list__wrapper {
display: block;

@include govuk-media-query($from: desktop) {
display: inline-flex;
width: 100%;
}
}

.gem-c-reorderable-list__content {
margin-bottom: govuk-spacing(2);
@include govuk-media-query($from: desktop) {
margin-bottom: 0;
flex: 0 1 auto;
min-width: 65%;
}
}

.gem-c-reorderable-list__title {
margin: 0;
}

.gem-c-reorderable-list__description {
@include govuk-font(16, regular);
margin: 0;
}

.gem-c-reorderable-list__actions {
display: block;

@include govuk-media-query($from: desktop) {
flex: 1 0 auto;
text-align: right;
}

.gem-c-button {
display: none;
}
}

.js-enabled {
.gem-c-reorderable-list__item {
@include govuk-media-query($from: desktop) {
cursor: move;
}
}

.gem-c-reorderable-list__actions .govuk-form-group {
display: none;
}

.gem-c-reorderable-list__actions .gem-c-button {
display: inline-block;
margin-left: govuk-spacing(3);
width: 80px;
}

.gem-c-reorderable-list__actions .gem-c-button:first-of-type {
margin-left: 0;

@include govuk-media-query($from: desktop) {
margin-left: govuk-spacing(3);
}
}

.gem-c-reorderable-list__item:first-child .gem-c-button:first-of-type,
.gem-c-reorderable-list__item:last-child .gem-c-button:last-of-type {
display: none;

@include govuk-media-query($from: desktop) {
display: inline-block;
visibility: hidden;
}
}

.gem-c-reorderable-list__item:first-child .gem-c-button:last-of-type {
margin-left: 0;

@include govuk-media-query($from: desktop) {
margin-left: govuk-spacing(3);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<%
items ||= []
input_name ||= "ordering"
data_attributes ||= {}
data_attributes[:module] = "reorderable-list"
%>

<%= tag.ol class: "gem-c-reorderable-list", data: data_attributes do %>
<% items.each_with_index do |item, index| %>
<%= tag.li class: "gem-c-reorderable-list__item" do %>
<%= tag.div class: "gem-c-reorderable-list__wrapper" do %>
<%= tag.div class: "gem-c-reorderable-list__content" do %>
<%= tag.p item[:title], class: "gem-c-reorderable-list__title" %>
<%= tag.p(item[:description], class: "gem-c-reorderable-list__description") if item[:description].present? %>
<% end %>
<%= tag.div class: "gem-c-reorderable-list__actions" do %>
<% label_text = capture do %>
Position<span class='govuk-visually-hidden'> for <%= item[:title] %></span>
<% end %>
<%= render "govuk_publishing_components/components/input", {
label: { text: label_text },
name: "#{input_name}[#{item[:id]}]",
type: "number",
value: index + 1,
width: 2
} %>
<%= render "govuk_publishing_components/components/button", {
text: "Up",
type: "button",
aria_label: "Move \"#{item[:title]}\" up",
classes: "js-reorderable-list-up",
secondary_solid: true
} %>
<%= render "govuk_publishing_components/components/button", {
text: "Down",
type: "button",
aria_label: "Move \"#{item[:title]}\" down",
classes: "js-reorderable-list-down",
secondary_solid: true
} %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
name: Reorderable list
description: A list of items that can be reordered
body: |
List items can be reordered by drag and drop or by using the up/down buttons.
On small viewports the drag and drop feature is disabled to prevent being triggered
when scrolling on touch devices.
This component uses SortableJS - a JavaScript library for drag and drop interactions.
When JavaScript is disabled a set of inputs will be shown allowing users to provide
an order index for each item.
When this component is embedded into a form and that form is submit you will receive a
parameter of `ordering` (which can be customised with the `input_name` option).
This will contain item ids and ordering positions in a hash.
For example, for two items with id "a" and "b" that are ordered accordingly,
you'd receive a submission of `ordering[a]=1&ordering[b]=2`, which Rails can
translate to `"ordering" => { "a" => "1", "b" => "2" }`.
accessibility_criteria: |
Buttons in this component must:
* be keyboard focusable
* inform the user about which item they operate on
* preserve focus after interacting with them
examples:
default:
data:
items:
- id: "ce99dd60-67dc-11eb-ae93-0242ac130002"
title: "Budget 2018"
- id: "d321cb86-67dc-11eb-ae93-0242ac130002"
title: "Budget 2018 (web)"
- id: "63a6d29e-6b6d-4157-9067-84c1a390e352"
title: "Impact on households: distributional analysis to accompany Budget 2018"
- id: "0a4d377d-68f4-472f-b2e3-ef71dc750f85"
title: "Table 2.1: Budget 2018 policy decisions"
- id: "5ebd75d7-6c37-4b93-b444-1b7c49757fb9"
title: "Table 2.2: Measures announced at Autumn Budget 2017 or earlier that will take effect from November 2018 or later (£ million)"
with_description:
data:
items:
- id: "ce99dd60-67dc-11eb-ae93-0242ac130002"
title: "Budget 2018"
description: "PDF, 2.56MB, 48 pages"
- id: "d321cb86-67dc-11eb-ae93-0242ac130002"
title: "Budget 2018 (web)"
description: "HTML attachment"
- id: "63a6d29e-6b6d-4157-9067-84c1a390e352"
title: "Impact on households: distributional analysis to accompany Budget 2018"
description: "PDF, 592KB, 48 pages"
- id: "0a4d377d-68f4-472f-b2e3-ef71dc750f85"
title: "Table 2.1: Budget 2018 policy decisions"
description: "MS Excel Spreadsheet, 248KB"
- id: "5ebd75d7-6c37-4b93-b444-1b7c49757fb9"
title: "Table 2.2: Measures announced at Autumn Budget 2017 or earlier that will take effect from November 2018 or later (£ million)"
description: "MS Excel Spreadsheet, 248KB"
within_form:
embed: |
<form>
<%= component %>
<button class="govuk-button" type="submit">Save order</button>
</form>
data:
items:
- id: "ce99dd60-67dc-11eb-ae93-0242ac130002"
title: "Budget 2018"
- id: "d321cb86-67dc-11eb-ae93-0242ac130002"
title: "Budget 2018 (web)"
- id: "63a6d29e-6b6d-4157-9067-84c1a390e352"
title: "Impact on households: distributional analysis to accompany Budget 2018"
- id: "0a4d377d-68f4-472f-b2e3-ef71dc750f85"
title: "Table 2.1: Budget 2018 policy decisions"
- id: "5ebd75d7-6c37-4b93-b444-1b7c49757fb9"
title: "Table 2.2: Measures announced at Autumn Budget 2017 or earlier that will take effect from November 2018 or later (£ million)"
with_custom_input_name:
data:
input_name: "attachments[ordering]"
items:
- id: "5ebd75d7-6c37-4b93-b444-1b7c49757fb9"
title: "Budget 2018"
description: "PDF, 2.56MB, 48 pages"
- id: "0a4d377d-68f4-472f-b2e3-ef71dc750f85"
title: "Budget 2020"
description: "PDF, 2.56MB, 48 pages"
2 changes: 1 addition & 1 deletion govuk_publishing_components.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Gem::Specification.new do |s|
s.license = "MIT"
s.required_ruby_version = ">= 2.6"

s.files = Dir["{node_modules/govuk-frontend,node_modules/axe-core,node_modules/jquery,app,config,db,lib}/**/*", "LICENCE.md", "README.md"]
s.files = Dir["{node_modules/govuk-frontend,node_modules/axe-core,node_modules/jquery,node_modules/sortablejs,app,config,db,lib}/**/*", "LICENCE.md", "README.md"]

s.add_dependency "govuk_app_config"
s.add_dependency "kramdown"
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
"dependencies": {
"axe-core": "^3.5.4",
"govuk-frontend": "^3.11.0",
"jquery": "1.12.4"
"jquery": "1.12.4",
"sortablejs": "^1.13.0"
},
"devDependencies": {
"standardx": "^7.0.0",
Expand Down
Loading

0 comments on commit 4200c9d

Please sign in to comment.