diff --git a/docs/app/views/examples/components/table/_preview.html.erb b/docs/app/views/examples/components/table/_preview.html.erb index 6f29aecbd3..4375030c6b 100644 --- a/docs/app/views/examples/components/table/_preview.html.erb +++ b/docs/app/views/examples/components/table/_preview.html.erb @@ -123,6 +123,7 @@ sample_table = { "Revenue", "Status" ], + # responsive_stack: true, rows: [ [ %( @@ -200,6 +201,7 @@ sample_table = { "Last activity", "" ], + responsive_stack: true, rows: [ [ %( diff --git a/docs/lib/sage_rails/app/helpers/sage_table_helper.rb b/docs/lib/sage_rails/app/helpers/sage_table_helper.rb index 81b26dfff4..ffd4e72a0b 100644 --- a/docs/lib/sage_rails/app/helpers/sage_table_helper.rb +++ b/docs/lib/sage_rails/app/helpers/sage_table_helper.rb @@ -222,6 +222,7 @@ def sage_table_classes(table) table_classlist << " sage-table--condensed" if table.condensed table_classlist << " sage-table--has-leading-input" if table.has_leading_input table_classlist << " sage-table--has-menu-options" if table.has_menu_options + table_classlist << " sage-table--stack" if table.responsive_stack return table_classlist end diff --git a/docs/lib/sage_rails/app/sage_components/sage_table.rb b/docs/lib/sage_rails/app/sage_components/sage_table.rb index ab80f3e32a..7bf998f601 100644 --- a/docs/lib/sage_rails/app/sage_components/sage_table.rb +++ b/docs/lib/sage_rails/app/sage_components/sage_table.rb @@ -10,6 +10,7 @@ class SageTable < SageComponent reset_above: [:optional, NilClass, TrueClass], reset_below: [:optional, NilClass, TrueClass], responsive: [:optional, NilClass, TrueClass], + responsive_stack: [:optional, NilClass, TrueClass], rows: [:optional, NilClass, Array], selectable: [:optional, NilClass, TrueClass], }) diff --git a/packages/sage-assets/lib/stylesheets/components/_table.scss b/packages/sage-assets/lib/stylesheets/components/_table.scss index 2ea7dad0d5..3986538218 100644 --- a/packages/sage-assets/lib/stylesheets/components/_table.scss +++ b/packages/sage-assets/lib/stylesheets/components/_table.scss @@ -273,6 +273,10 @@ $-table-avatar-width: rem(32px); max-width: $-table-cell-truncate-width; } +.sage-table-cell__heading--responsive { + display: none; +} + .sage-table-cell--checkbox { width: $-table-checkbox-width; } @@ -350,11 +354,11 @@ $-table-avatar-width: rem(32px); } @media screen and (max-width: sage-breakpoint(sm-max)) { - .sage-table { + .sage-table--stack { thead { tr, &::after { - display: none; + @include visually-hidden; } } @@ -362,6 +366,11 @@ $-table-avatar-width: rem(32px); position: relative; } + .sage-table-cell__heading--responsive { + display: inline-block; + align-items: center; + } + &.sage-table--has-menu-options { td:last-of-type { position: absolute; @@ -373,121 +382,88 @@ $-table-avatar-width: rem(32px); td { padding: rem(6px) 0; } - } - table, - thead, - tbody, - th, - td, - tr, - caption { - display: block; - } - - tr { - padding: rem(16px) rem(18px); - border: sage-border(default); - border-radius: sage-border(radius); - - & + & { - margin-top: sage-spacing(sm); + // table, + thead, + tbody, + th, + td, + tr, + caption { + display: block; } - } - - td { - display: grid; - grid-template-columns: auto auto; - grid-gap: 1em 0.5em; - justify-content: space-between; - } - - td:nth-of-type(4)::before, - td:nth-of-type(5)::before { - text-align: left; - } - - // TODO UXD - QUINTON - find scalable solution - td::before { - color: sage-color(charcoal, 200); - } - td:nth-child(1)::before { - content: "Name "; - display: none; - } - - td:nth-child(2)::before { - content: "Email "; - } - - td:nth-child(3)::before { - content: "Labels "; - } - - td:nth-child(4)::before { - content: "Status "; - } - - td:nth-child(5)::before { - content: "Status "; - } - - td:nth-child(6)::before { - content: "Status "; - } - - td:nth-child(7)::before { - content: "Status "; - } - .sage-table--has-menu-options td:last-child::before { - display: none; - } + tr { + padding: rem(16px) rem(18px); + border: sage-border(default); + border-radius: sage-border(radius); - .sage-table--has-leading-input { - td:nth-child(2) { - margin-left: sage-spacing(sm); + & + & { + margin-top: sage-spacing(sm); + } } - td:nth-child(-n+2) { - display: inline-flex; + td { + display: grid; + grid-template-columns: auto auto; + grid-gap: 1em 0.5em; + justify-content: space-between; } - td:nth-child(n+3) { - margin-left: rem(40px); - } + // td:nth-of-type(4)::before, + // td:nth-of-type(5)::before { + // text-align: left; + // } - td:nth-child(2)::before { - content: "Name "; + .sage-table--has-menu-options td:last-child::before { display: none; } - td:nth-child(3)::before { - content: "Email "; - } + .sage-table--has-leading-input { + td:nth-child(2) { + margin-left: sage-spacing(sm); + } - td:nth-child(4)::before { - content: "Labels "; - } + td:nth-child(-n+2) { + display: inline-flex; + } - td:nth-child(5)::before { - content: "Status "; - } + td:nth-child(n+3) { + margin-left: rem(40px); + } - td:nth-child(6)::before { - content: "Status "; - } + td:nth-child(2)::before { + content: "Name "; + display: none; + } - td:nth-child(7)::before { - content: "Status "; - } + td:nth-child(3)::before { + content: "Email "; + } - td:nth-child(8)::before { - content: "Status "; - } + td:nth-child(4)::before { + content: "Labels "; + } - td:last-child::before { - display: none; + td:nth-child(5)::before { + content: "Status "; + } + + td:nth-child(6)::before { + content: "Status "; + } + + td:nth-child(7)::before { + content: "Status "; + } + + td:nth-child(8)::before { + content: "Status "; + } + + td:last-child::before { + display: none; + } } } } diff --git a/packages/sage-system/lib/table.js b/packages/sage-system/lib/table.js index cac7ed975b..a941168d4b 100644 --- a/packages/sage-system/lib/table.js +++ b/packages/sage-system/lib/table.js @@ -3,8 +3,7 @@ Sage.table = (function() { // Variables // ================================================== - // const SELECTOR_TABLE = "[data-js-table]"; - const SELECTOR_TABLE = ".sage-table"; + const RESPONSIVE_TABLE = ".sage-table--stack"; const MOBILE_TABLE_MAX_WIDTH = "767"; // SM_MAX // ================================================== @@ -31,31 +30,55 @@ Sage.table = (function() { }); } - // TODO: Q: do this for each table + // create alternative table headings for responsive "stacked" tables function ResponsiveCellHeaders(elem) { - var THarray = []; - var tables = document.querySelectorAll(SELECTOR_TABLE); + const tables = document.querySelectorAll(RESPONSIVE_TABLE); + + const cellHeaderTemplate = (textLabel) => { + const cellHeader = document.createElement('span'); + + cellHeader.classList.add('sage-table-cell__heading--responsive'); + cellHeader.innerText = textLabel.trim(); // trim whitespace just in case + + // content must be hidden from screen readers since it duplicates table headers + cellHeader.setAttribute('aria-hidden', true); + + return cellHeader; + }; + tables.forEach(table => { - // var table = document.querySelector(SELECTOR_TABLE); - var ths = table.getElementsByTagName("th"); - for (var i = 0; i < ths.length; i++) { - var headingText = ths[i].innerHTML.trim(); - THarray.push(headingText); - } - var styleElm = document.createElement("style"), - styleSheet; - document.head.appendChild(styleElm); - styleSheet = styleElm.sheet; - for (var i = 0; i < THarray.length; i++) { - styleSheet.insertRule( - "" + - elem + - " td:nth-child(" + - (i + 1) + - ')::before { content:"' + THarray[i] + ': ";}', - styleSheet.cssRules.length - ); - } + const headers = table.querySelectorAll('thead th'); + const rows = table.querySelectorAll('tbody tr'); + const tableHeadings = []; + + // populate an array with each table's headers + headers.forEach(header => { + const label = header.textContent.trim(); + tableHeadings.push(label); + }) + + rows.forEach(row => { + if (!row.hasChildNodes()) return; // skip empty rows + + const cells = Array.from(row.children); + + // add header text to each cell + const updatedCells = cells.map((cell, i) => { + if (!tableHeadings[i].length) return; // skip empty headers + + const newHeader = cellHeaderTemplate(tableHeadings[i]); + cell.prepend(newHeader); + }); + + // look for cells with positioned content + cells.forEach((cell) => { + const checkboxes = cell.querySelectorAll(".sage-checkbox"); + + if (checkboxes.length) { + cell.classList.add('sage-table-cell--checkbox'); + } + }) + }) }) } @@ -68,7 +91,7 @@ Sage.table = (function() { group.forEach(el => { el.setAttribute('role', args.role); - }) + }); } function addTableAria() { @@ -96,9 +119,9 @@ Sage.table = (function() { if (document.querySelector('.sage-table--sortable') !== null) { sortEvents(); } - if (document.querySelector(SELECTOR_TABLE) !== null && window.innerWidth <= MOBILE_TABLE_MAX_WIDTH) { + if (document.querySelector(RESPONSIVE_TABLE) !== null && window.innerWidth <= MOBILE_TABLE_MAX_WIDTH) { addTableAria(); - ResponsiveCellHeaders(SELECTOR_TABLE); + ResponsiveCellHeaders(RESPONSIVE_TABLE); } }