Skip to content

Commit

Permalink
Merge pull request #111 from CityOfDetroit/fix.table
Browse files Browse the repository at this point in the history
Use table HTML elements for a11y
  • Loading branch information
maxatdetroit authored Nov 14, 2023
2 parents 862ac60 + b07297c commit dbcdccf
Show file tree
Hide file tree
Showing 21 changed files with 646 additions and 103 deletions.
3 changes: 3 additions & 0 deletions src/components/atoms/TableBody/TableBody.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
tbody {
display: block;
}
45 changes: 39 additions & 6 deletions src/components/atoms/TableBody/TableBody.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import styles from '!!raw-loader!./TableBody.css';
import varStyles from '!!raw-loader!../../../shared/variables.css';
import bootstrapStyles from '!!raw-loader!../../../shared/themed-bootstrap.css';
import {
cellHeaderBlockClass,
stackedTableClass,
} from '../../../shared/js/utilities';

const template = document.createElement('template');

Expand All @@ -15,15 +19,19 @@ export default class TableBody extends HTMLElement {
// Create a shadow root
const shadow = this.attachShadow({ mode: 'open' });
shadow.appendChild(template.content.cloneNode(true));
this.tableBody = document.createElement('div');
this.tableBody.role = 'rowgroup';
this.tableBody = document.createElement('tbody');
// TODO: See CityOfDetroit/detroitmi#1099
// eslint-disable-next-line no-unused-vars
shadow.addEventListener('slotchange', (ev) => {
// TODO: See CityOfDetroit/detroitmi#1099
// eslint-disable-next-line prefer-const
let tempElements = Array.from(this.children);
tempElements.forEach((node, index) => {
if (index === 0) {
node.setIsFirst();
} else if (index % 2 !== 0) {
node.setIsOdd();
}
// TODO: See CityOfDetroit/detroitmi#1099
// eslint-disable-next-line eqeqeq
this.getAttribute('data-striped-row') == 'true' && index % 2 == 0
Expand All @@ -44,11 +52,14 @@ export default class TableBody extends HTMLElement {
this.getAttribute('data-vertical-align') == 'true'
? node.setAttribute('data-vertical-align', 'true')
: 0;
// TODO: See CityOfDetroit/detroitmi#1099
// eslint-disable-next-line eqeqeq
this.getAttribute('data-legacy-responsive') == 'true'
? node.setAttribute('data-legacy-responsive', 'true')
this.getAttribute('data-scrollable') === 'true'
? node.setAttribute('data-scrollable', 'true')
: 0;

if (this.isStacked()) {
node.setIsStacked(true /* isStacked */, this.isCellHeaderBlock());
}

this.tableBody.append(node);
});
});
Expand All @@ -66,4 +77,26 @@ export default class TableBody extends HTMLElement {

shadow.appendChild(this.tableBody);
}

setIsStacked(isStacked, isCellHeaderBlock) {
if (isStacked) {
this.tableBody.classList.add(stackedTableClass);
} else {
this.tableBody.classList.remove(stackedTableClass);
}

if (isCellHeaderBlock) {
this.tableBody.classList.add(cellHeaderBlockClass);
} else {
this.tableBody.classList.remove(cellHeaderBlockClass);
}
}

isStacked() {
return this.tableBody.classList.contains(stackedTableClass);
}

isCellHeaderBlock() {
return this.tableBody.classList.contains(cellHeaderBlockClass);
}
}
9 changes: 5 additions & 4 deletions src/components/atoms/TableCell/TableCell.css
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
.table-cell {
td {
display: block;
padding: 0.5rem 0.5rem;
background-color: var(--bs-table-bg);
border-bottom: solid var(--bs-border-width) var(--bs-secondary);
box-shadow: inset 0 0 0 9999px var(--bs-table-accent-bg);
height: 100%;
}

.table-cell.table-striped,
.table-cell.table-striped-columns {
td.table-striped,
td.table-striped-columns {
--bs-table-accent-bg: var(--bs-table-striped-bg);
}

.table-cell.table-legacy-responsive {
td.table-scrollable {
width: 5em;
}
68 changes: 47 additions & 21 deletions src/components/atoms/TableCell/TableCell.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import styles from '!!raw-loader!./TableCell.css';
import varStyles from '!!raw-loader!../../../shared/variables.css';
import bootstrapStyles from '!!raw-loader!../../../shared/themed-bootstrap.css';
import {
cellHeaderBlockClass,
stackedTableClass,
} from '../../../shared/js/utilities';

const template = document.createElement('template');

Expand All @@ -15,16 +19,22 @@ export default class TableCell extends HTMLElement {
// Create a shadow root
const shadow = this.attachShadow({ mode: 'open' });
shadow.appendChild(template.content.cloneNode(true));
this.tableCell = document.createElement('div');
this.tableCell.role = 'cell';
this.tableCell = document.createElement('td');
// TODO: See CityOfDetroit/detroitmi#1099
// eslint-disable-next-line no-unused-vars
shadow.addEventListener('slotchange', (ev) => {
// TODO: See CityOfDetroit/detroitmi#1099
// eslint-disable-next-line prefer-const
let tempElements = Array.from(this.childNodes);
const tempElements = ev.target.assignedNodes();
tempElements.forEach((node) => {
this.tableCell.appendChild(node);
// Only accept HTMLElements or non-empty text nodes.
if (
node.nodeType !== Node.TEXT_NODE ||
!/^\s*$/.test(node.textContent)
) {
const contentDiv = document.createElement('div');
contentDiv.classList.add('content');
contentDiv.appendChild(node);
this.tableCell.appendChild(contentDiv);
}
});
});

Expand Down Expand Up @@ -52,37 +62,53 @@ export default class TableCell extends HTMLElement {
let stripedCol = this.getAttribute('data-striped-col');
// TODO: See CityOfDetroit/detroitmi#1099
// eslint-disable-next-line prefer-const
let legacyResponsive = this.getAttribute('data-legacy-responsive');
// TODO: See CityOfDetroit/detroitmi#1099
// eslint-disable-next-line prefer-const
let verticalAlign = this.getAttribute('data-vertical-align');
// TODO: See CityOfDetroit/detroitmi#1099
// eslint-disable-next-line prefer-const
let extraClasses = this.getAttribute('data-extra-classes');
// TODO: See CityOfDetroit/detroitmi#1099
// eslint-disable-next-line prefer-const
let tableCellClasses = ['table-cell'];
// TODO: See CityOfDetroit/detroitmi#1099
// eslint-disable-next-line eqeqeq
verticalAlign != undefined && verticalAlign != null
? tableCellClasses.push(verticalAlign)
? this.tableCell.classList.add(verticalAlign)
: 0;
// TODO: See CityOfDetroit/detroitmi#1099
// eslint-disable-next-line eqeqeq
legacyResponsive == 'true'
? tableCellClasses.push('table-legacy-responsive')
this.getAttribute('data-scrollable') === 'true'
? this.tableCell.classList.add('table-scrollable')
: 0;
// TODO: See CityOfDetroit/detroitmi#1099
// eslint-disable-next-line eqeqeq
stripedRow == 'true' ? tableCellClasses.push('table-striped') : 0;
stripedRow == 'true' ? this.tableCell.classList.add('table-striped') : 0;
// TODO: See CityOfDetroit/detroitmi#1099
// eslint-disable-next-line eqeqeq
stripedCol == 'true' ? tableCellClasses.push('table-striped-columns') : 0;
stripedCol == 'true'
? this.tableCell.classList.add('table-striped-columns')
: 0;
// TODO: See CityOfDetroit/detroitmi#1099
// eslint-disable-next-line eqeqeq
extraClasses != undefined && extraClasses != null
? tableCellClasses.push(extraClasses)
? this.tableCell.classList.add(extraClasses)
: 0;
this.tableCell.className = tableCellClasses.join(' ');

const dataLabel = this.getAttribute('data-label');
if (dataLabel) {
this.tableCell.setAttribute('data-label', dataLabel);
}
}

setIsStacked(isStacked, isCellHeaderBlock) {
if (isStacked) {
this.tableCell.classList.add(stackedTableClass);
} else {
this.tableCell.classList.remove(stackedTableClass);
}

if (isCellHeaderBlock) {
this.tableCell.classList.add(cellHeaderBlockClass);
} else {
this.tableCell.classList.remove(cellHeaderBlockClass);
}
}

isStacked() {
return this.tableCell.classList.contains(stackedTableClass);
}
}
27 changes: 27 additions & 0 deletions src/components/atoms/TableCell/_table-cell.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
@use '../../../scss/mixins/table';
@import '../../../../node_modules/bootstrap/scss/mixins/breakpoints';
@import '../../../../node_modules/bootstrap/scss/variables';

@include media-breakpoint-down(lg) {
td.table-stacked.cell-header-block {
@include table.th-td-stacked-block-styles;
}

td.table-stacked:not(.cell-header-block) {
@include table.th-td-stacked-inline-styles;

& div.content {
@include table.cell-content-stacked-inline-styles;
}
}

td[data-label].table-stacked {
&.cell-header-block {
@include table.th-td-labeled-stacked-block-styles;
}

&:not(.cell-header-block) {
@include table.th-td-labeled-stacked-inline-styles;
}
}
}
7 changes: 4 additions & 3 deletions src/components/atoms/TableCellHeader/TableCellHeader.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.table-cell-header {
th {
display: block;
padding: 0.5rem 0.5rem;
background-color: var(--bs-table-bg);
border-bottom: solid var(--bs-border-width) var(--bs-secondary);
Expand All @@ -7,10 +8,10 @@
height: 100%;
}

.table-cell-header.table-striped-columns {
th.table-striped-columns {
--bs-table-accent-bg: var(--bs-table-striped-bg);
}

.table-cell-header.table-legacy-responsive {
th.table-scrollable {
width: 5em;
}
48 changes: 31 additions & 17 deletions src/components/atoms/TableCellHeader/TableCellHeader.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import styles from '!!raw-loader!./TableCellHeader.css';
import varStyles from '!!raw-loader!../../../shared/variables.css';
import bootstrapStyles from '!!raw-loader!../../../shared/themed-bootstrap.css';
import {
cellHeaderBlockClass,
stackedTableClass,
} from '../../../shared/js/utilities';

const template = document.createElement('template');

Expand All @@ -15,8 +19,7 @@ export default class TableCellHeader extends HTMLElement {
// Create a shadow root
const shadow = this.attachShadow({ mode: 'open' });
shadow.appendChild(template.content.cloneNode(true));
this.tableCellHeader = document.createElement('div');
this.tableCellHeader.role = 'columnheader';
this.tableCellHeader = document.createElement('th');
// TODO: See CityOfDetroit/detroitmi#1099
// eslint-disable-next-line no-unused-vars
shadow.addEventListener('slotchange', (ev) => {
Expand Down Expand Up @@ -52,39 +55,50 @@ export default class TableCellHeader extends HTMLElement {
let stripedCol = this.getAttribute('data-striped-col');
// TODO: See CityOfDetroit/detroitmi#1099
// eslint-disable-next-line prefer-const
let legacyResponsive = this.getAttribute('data-legacy-responsive');
// TODO: See CityOfDetroit/detroitmi#1099
// eslint-disable-next-line prefer-const
let verticalAlign = this.getAttribute('data-vertical-align');
// TODO: See CityOfDetroit/detroitmi#1099
// eslint-disable-next-line prefer-const
let extraClasses = this.getAttribute('data-extra-classes');
// TODO: See CityOfDetroit/detroitmi#1099
// eslint-disable-next-line prefer-const
let tableCellHeaderClasses = ['table-cell-header'];
// TODO: See CityOfDetroit/detroitmi#1099
// eslint-disable-next-line eqeqeq
verticalAlign != undefined && verticalAlign != null
? tableCellHeaderClasses.push(verticalAlign)
? this.tableCellHeader.classList.add(verticalAlign)
: 0;
// TODO: See CityOfDetroit/detroitmi#1099
// eslint-disable-next-line eqeqeq
legacyResponsive == 'true'
? tableCellHeaderClasses.push('table-legacy-responsive')
this.getAttribute('data-scrollable') === 'true'
? this.tableCellHeader.classList.add('table-scrollable')
: 0;
// TODO: See CityOfDetroit/detroitmi#1099
// eslint-disable-next-line eqeqeq
stripedRow == 'true' ? tableCellHeaderClasses.push('table-striped') : 0;
stripedRow == 'true'
? this.tableCellHeader.classList.add('table-striped')
: 0;
// TODO: See CityOfDetroit/detroitmi#1099
// eslint-disable-next-line eqeqeq
stripedCol == 'true'
? tableCellHeaderClasses.push('table-striped-columns')
? this.tableCellHeader.classList.add('table-striped-columns')
: 0;
// TODO: See CityOfDetroit/detroitmi#1099
// eslint-disable-next-line eqeqeq
extraClasses != undefined && extraClasses != null
? tableCellHeaderClasses.push(extraClasses)
? this.tableCellHeader.classList.add(extraClasses)
: 0;
this.tableCellHeader.className = tableCellHeaderClasses.join(' ');
}

setIsStacked(isStacked, isCellHeaderBlock) {
if (isStacked) {
this.tableCellHeader.classList.add(stackedTableClass);
} else {
this.tableCellHeader.classList.remove(stackedTableClass);
}

if (isCellHeaderBlock) {
this.tableCellHeader.classList.add(cellHeaderBlockClass);
} else {
this.tableCellHeader.classList.remove(cellHeaderBlockClass);
}
}

isStacked() {
return this.tableCellHeader.classList.contains(stackedTableClass);
}
}
27 changes: 27 additions & 0 deletions src/components/atoms/TableCellHeader/_table-cell-header.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
@use '../../../scss/mixins/table';
@import '../../../../node_modules/bootstrap/scss/mixins/breakpoints';
@import '../../../../node_modules/bootstrap/scss/variables';

@include media-breakpoint-down(lg) {
th.table-stacked.cell-header-block {
@include table.th-td-stacked-block-styles;
}

th.table-stacked:not(.cell-header-block) {
@include table.th-td-stacked-inline-styles;

& div.content {
@include table.cell-content-stacked-inline-styles;
}
}

th[data-label].table-stacked {
&.cell-header-block {
@include table.th-td-labeled-stacked-block-styles;
}

&:not(.cell-header-block) {
@include table.th-td-labeled-stacked-inline-styles;
}
}
}
3 changes: 3 additions & 0 deletions src/components/atoms/TableHeader/TableHeader.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
thead {
display: block;
}
Loading

0 comments on commit dbcdccf

Please sign in to comment.