Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Responsive accessible table for API Users#index #2341

Merged
merged 9 commits into from
Sep 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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