-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1905 from alphagov/add-reorderable-component
Adds Reorderable lists component
- Loading branch information
Showing
11 changed files
with
637 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
108 changes: 108 additions & 0 deletions
108
app/assets/javascripts/govuk_publishing_components/components/reorderable-list.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
117 changes: 117 additions & 0 deletions
117
app/assets/stylesheets/govuk_publishing_components/components/_reorderable-list.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |
45 changes: 45 additions & 0 deletions
45
app/views/govuk_publishing_components/components/_reorderable_list.html.erb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 %> |
85 changes: 85 additions & 0 deletions
85
app/views/govuk_publishing_components/components/docs/reorderable_list.yml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.