Skip to content

Commit

Permalink
Merge pull request #2341 from alphagov/responsive-accessible-table
Browse files Browse the repository at this point in the history
Responsive accessible table for API Users#index
  • Loading branch information
mike29736 authored Sep 5, 2023
2 parents cc4da25 + f0a8f53 commit 35621cd
Show file tree
Hide file tree
Showing 9 changed files with 629 additions and 4 deletions.
3 changes: 3 additions & 0 deletions app/assets/javascripts/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@
//= require govuk_publishing_components/all_components
//= require_tree ./modules
//= require rails-ujs

// Components from this application
//= require_tree ./components
52 changes: 52 additions & 0 deletions app/assets/javascripts/components/table.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
window.GOVUK = window.GOVUK || {}
window.GOVUK.Modules = window.GOVUK.Modules || {};

(function (Modules) {
function Table ($module) {
this.$module = $module
this.searchInput = $module.querySelector('input[name="filter"]')
this.tableRows = $module.querySelectorAll('.js-govuk-table__row')
this.filter = $module.querySelector('.js-app-c-table__filter')
this.filterCount = this.filter.querySelector('.js-filter-count')
this.message = $module.querySelector('.js-app-c-table__message')
this.hiddenClass = 'govuk-!-display-none'
this.filterCountText = this.filterCount.getAttribute('data-count-text')
this.tableRowsContent = []

for (var i = 0; i < this.tableRows.length; i++) {
this.tableRowsContent.push(this.tableRows[i].textContent.toUpperCase())
}
}

Table.prototype.init = function () {
this.$module.updateRows = this.updateRows.bind(this)
this.filter.classList.remove(this.hiddenClass)
this.searchInput.addEventListener('input', this.$module.updateRows)
}

// Reads value of input and filters content
Table.prototype.updateRows = function () {
var value = this.searchInput.value
var hiddenRows = 0
var length = this.tableRows.length

for (var i = 0; i < length; i++) {
if (this.tableRowsContent[i].includes(value.toUpperCase())) {
this.tableRows[i].classList.remove(this.hiddenClass)
} else {
this.tableRows[i].classList.add(this.hiddenClass)
hiddenRows++
}
}

this.filterCount.textContent = (length - hiddenRows) + ' ' + this.filterCountText

if (length === hiddenRows) {
this.message.classList.remove(this.hiddenClass)
} else {
this.message.classList.add(this.hiddenClass)
}
}

Modules.Table = Table
})(window.GOVUK.Modules)
206 changes: 206 additions & 0 deletions app/assets/stylesheets/components/_table.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
@import "govuk_publishing_components/individual_component_support";
@import "govuk/components/table/table";

$table-border-width: 1px;
$table-border-colour: govuk-colour("mid-grey", $legacy: "grey-2");
$table-header-border-width: 2px;
$table-header-background-colour: govuk-colour("light-grey", $legacy: "grey-3");
$vertical-row-bottom-border-width: 3px;
$sort-link-active-colour: govuk-colour("white");
$sort-link-arrow-size: 14px;
$sort-link-arrow-size-small: 8px;
$sort-link-arrow-spacing: $sort-link-arrow-size / 2;
$table-row-hover-background-colour: rgba(43, 140, 196, .2);
$table-row-even-background-colour: govuk-colour("light-grey", $legacy: "grey-4");

/* stylelint-disable */
.govuk-table__cell:empty,
.govuk-table__cell--empty {
color: $govuk-secondary-text-colour;
}

.govuk-table--sortable {
outline: $table-border-width solid $table-border-colour;
outline-offset: 0;

.govuk-table__header {
padding: govuk-spacing(2);
border-right: $table-header-border-width solid govuk-colour("white");
border-bottom: $table-header-border-width solid govuk-colour("white");
background: $table-header-background-colour;
font-weight: normal;

&:last-child {
border-right: 0;
}

.app-table__sort-link {
@include govuk-link-style-no-visited-state;
position: relative;
padding-right: $sort-link-arrow-size;
color: $govuk-link-colour;
text-decoration: none;
}

.app-table__sort-link:focus {
@include govuk-focused-text;
}

.app-table__sort-link:after {
content: "";
position: absolute;
top: 5px;
right: 0;
@include govuk-shape-arrow($direction: up, $base: $sort-link-arrow-size-small, $display: block);
}

.app-table__sort-link:before {
content: "";
position: absolute;
top: 13px;
right: 0;
@include govuk-shape-arrow($direction: down, $base: $sort-link-arrow-size-small, $display: block);
}
}

.govuk-table__header--active {
color: $sort-link-active-colour;
background: $govuk-link-colour;

.app-table__sort-link {
padding-right: govuk-spacing(4);

&:link,
&:visited,
&:hover,
&:active {
color: $sort-link-active-colour;
}

&:focus {
color: $govuk-focus-text-colour;
}
}

.app-table__sort-link--ascending:before,
.app-table__sort-link--descending:before {
content: none;
}

.app-table__sort-link--ascending:after {
content: "";
position: absolute;
top: $sort-link-arrow-spacing;
right: 0;
margin-left: govuk-spacing(1);

@include govuk-shape-arrow($direction: up, $base: $sort-link-arrow-size, $display: inline-block);
}

.app-table__sort-link--descending:after {
content: "";
position: absolute;
top: $sort-link-arrow-spacing;
right: 0;
margin-left: govuk-spacing(1);

@include govuk-shape-arrow($direction: down, $base: $sort-link-arrow-size, $display: inline-block);
}
}

.govuk-table__row {
&:hover {
background-color: $table-row-hover-background-colour;
}

&:nth-child(even) {
background-color: $table-row-even-background-colour;

&:hover {
background-color: $table-row-hover-background-colour;
}
}
}

.govuk-table__cell {
padding: govuk-spacing(2);
border: 0;
}
}
/* stylelint-enable */

.app-c-table {
.govuk-table__cell {
word-break: break-word;
}
}

.app-c-table--vertical {
.govuk-table__head {
clip: rect(0 0 0 0);
-webkit-clip-path: inset(50%);
clip-path: inset(50%);
height: 1px;
overflow: hidden;
position: absolute;
width: 1px;
}

.govuk-table__body .govuk-table__row {
display: block;
}

.govuk-table__cell {
display: flex;
justify-content: space-between;
min-width: 1px;
text-align: right;
}

@include govuk-media-query($until: tablet) {
.govuk-table__cell {
padding-right: 0;
}

.govuk-table__cell:last-child {
border-bottom: 0
}

.govuk-table__body .govuk-table__row {
border-bottom: $vertical-row-bottom-border-width solid $table-border-colour;
}
}

.app-c-table__duplicate-heading {
font-weight: 700;
padding-right: 1em;
text-align: left;
word-break: initial;
}

@include govuk-media-query($from: tablet) {
.govuk-table__head {
clip: auto;
-webkit-clip-path: none;
clip-path: none;
display: table-header-group;
height: auto;
overflow: auto;
position: relative;
width: auto;
}

.govuk-table__body .govuk-table__row {
display: table-row;
}

.govuk-table__cell {
display: table-cell;
text-align: left;
}

.app-c-table__duplicate-heading {
display: none;
}
}
}
75 changes: 75 additions & 0 deletions app/helpers/components/table_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
module Components
module TableHelper
def self.helper(context, caption = nil, opt = {})
builder = TableBuilder.new(context.tag)

classes = %w[app-c-table govuk-table]
classes << "govuk-table--sortable" if opt[:sortable]
classes << opt[:classes] if opt[:classes]

caption_classes = %w[govuk-table__caption]
caption_classes << opt[:caption_classes] if opt[:caption_classes]

context.tag.table class: classes, id: opt[:table_id] do
context.concat context.tag.caption caption, class: caption_classes
yield(builder)
end
end

class TableBuilder
include ActionView::Helpers::UrlHelper
include ActionView::Helpers::TagHelper

attr_reader :tag

def initialize(tag)
# rubocop:disable Rails/HelperInstanceVariable
@tag = tag
# rubocop:enable Rails/HelperInstanceVariable
end

def head
tag.thead class: "govuk-table__head" do
tag.tr class: "govuk-table__row", role: "row" do
yield(self)
end
end
end

def body
tag.tbody class: "govuk-table__body" do
yield(self)
end
end

def row
tag.tr class: "govuk-table__row js-govuk-table__row", role: "row" do
yield(self)
end
end

def header(str, opt = {})
classes = %w[govuk-table__header]
classes << "govuk-table__header--#{opt[:format]}" if opt[:format]
classes << "govuk-table__header--active" if opt[:sort_direction]
link_classes = %w[app-table__sort-link]
link_classes << "app-table__sort-link--#{opt[:sort_direction]}" if opt[:sort_direction]
str = link_to str, opt[:href], class: link_classes, data: opt[:data_attributes] if opt[:href]
tag.th str, class: classes, scope: opt[:scope] || "col", role: "columnheader"
end

def cell(str, opt = {}, &block)
classes = %w[govuk-table__cell]
classes << "govuk-table__cell--#{opt[:format]}" if opt[:format]
classes << "govuk-table__cell--empty" unless str || block_given?
str ||= "Not set"

if block_given?
tag.td class: classes, role: "cell", &block
else
tag.td str, class: classes, role: "cell"
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module ComponentHelper
module NavigationItemsHelper
def navigation_items
return [] unless current_user

Expand Down
3 changes: 2 additions & 1 deletion app/views/api_users/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
} %>
</div>

<%= render "govuk_publishing_components/components/table", {
<%= render "components/table", {
caption: "API users",
caption_classes: "govuk-visually-hidden",
filterable: true,
vertical_on_small_screen: true,
label: "Filter API users",
head: [
{
Expand Down
Loading

0 comments on commit 35621cd

Please sign in to comment.