Skip to content
Draft
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
121 changes: 121 additions & 0 deletions blocks/table/table.css
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,124 @@
.table.striped tbody tr:nth-child(odd) {
background-color: var(--color-gray-200);
}

/* Comparison variant: heading row + heading column, 2x first column width */
.table.comparison .table-comparison-scroll {
width: 100%;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}

.table.comparison .table-comparison-scroll table {
width: 100%;
min-width: 600px;
table-layout: fixed;
border-collapse: collapse;
border: 1px solid #e0e0e0;
}

.table.comparison .table-comparison-scroll th,
.table.comparison .table-comparison-scroll td {
padding: 16px 12px;
border: 1px solid #e0e0e0;
vertical-align: top;
}

.table.comparison .table-comparison-scroll thead th {
background-color: #ececec;
font-weight: 700;
text-align: center;
color: #333;
}

.table.comparison .table-comparison-scroll tbody th {
background-color: #fff;
font-weight: 600;
text-align: left;
color: #333;
vertical-align: middle;
}

.table.comparison .table-comparison-scroll thead th:first-child {
text-align: left;
color: #6c757d;
font-weight: 600;
}

/* Heading column: flex on <p>, image left, text right, vertically centered */
.table.comparison .table-comparison-scroll tbody th .table-comparison-cell-content p {
display: flex;
align-items: center;
gap: 12px;
margin: 0;
}

.table.comparison .table-comparison-scroll tbody th .table-comparison-cell-content p picture,
.table.comparison .table-comparison-scroll tbody th .table-comparison-cell-content p img {
flex-shrink: 0;
width: 48px;
height: 48px;
margin: 0;
}

.table.comparison .table-comparison-scroll tbody th .table-comparison-cell-content p img {
width: 48px;
height: 48px;
object-fit: contain;
}

.table.comparison .table-comparison-scroll tbody th .table-comparison-cell-content p a {
color: #06c;
text-decoration: none;
flex: 1;
min-width: 0;
}

.table.comparison .table-comparison-scroll tbody th .table-comparison-cell-content p a:hover {
text-decoration: underline;
}

.table.comparison .table-comparison-scroll tbody td {
background-color: #fff;
text-align: center;
color: #333;
}

.table.comparison .table-comparison-scroll thead tr.table-comparison-row-header-empty th,
.table.comparison .table-comparison-scroll tbody tr.table-comparison-row-header-empty th,
.table.comparison .table-comparison-scroll tbody tr.table-comparison-row-header-empty td {
background-color: transparent;
border-color: transparent;
}

/* Comparison table: colors row – round swatches from pdp/color-swatches.css */
.table.comparison .table-comparison-scroll .table-comparison-color-swatches {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
gap: 6px;
}

.table.comparison .table-comparison-scroll .table-comparison-color-swatch {
width: 24px;
height: 24px;
border: 1px solid #333f48;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
}

.table.comparison .table-comparison-scroll .table-comparison-color-inner {
width: 16px;
height: 16px;
border: 1px solid #333f48;
border-radius: 50%;
}

.table.comparison .table-comparison-scroll .table-comparison-color-text {
font-size: 0.9em;
color: #666;
}
150 changes: 147 additions & 3 deletions blocks/table/table.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,40 @@
import { toClassName } from '../../scripts/aem.js';

const COLOR_SWATCHES_CSS_PATH = '/blocks/pdp/color-swatches.css';

/**
* Fetch color-swatches.css, parse --color-* names and the rule body, inject a scoped
* <style> so .table.comparison .table-comparison-color-swatch gets the same variables,
* and return the set of color names for matching.
* @returns {Promise<Set<string>>}
*/
async function loadColorSwatchesForTable() {
const base = typeof window.hlx?.codeBasePath === 'string' ? window.hlx.codeBasePath : '';
const url = `${base}${COLOR_SWATCHES_CSS_PATH}`;
const res = await fetch(url);
if (!res.ok) return new Set();
const css = await res.text();

const names = new Set([...css.matchAll(/--color-([a-z0-9-]+):/g)].map((m) => m[1]));

const open = css.indexOf('{');
if (open === -1) return names;
let depth = 1;
let pos = open + 1;
while (depth && pos < css.length) {
if (css[pos] === '{') depth += 1;
else if (css[pos] === '}') depth -= 1;
pos += 1;
}
const body = css.slice(open + 1, pos - 1);

const style = document.createElement('style');
style.textContent = `.table.comparison .table-comparison-color-swatch { ${body} }`;
document.head.appendChild(style);

return names;
}

function buildRow(row, cellType = 'td') {
const tr = document.createElement('tr');
[...row.children].forEach((col) => {
Expand All @@ -20,18 +57,126 @@ function buildRowWithRowHeaders(row) {
return tr;
}

function buildComparisonTable(rows) {
const table = document.createElement('table');
const colCount = rows[0]?.children?.length || 0;

// colgroup: first column 2x width of others (2/(n+1) vs 1/(n+1))
if (colCount > 0) {
const colgroup = document.createElement('colgroup');
const firstPct = (200 / (1 + colCount)).toFixed(2);
const otherPct = (100 / (1 + colCount)).toFixed(2);
const firstCol = document.createElement('col');
firstCol.style.width = `${firstPct}%`;
colgroup.appendChild(firstCol);
for (let i = 1; i < colCount; i += 1) {
const col = document.createElement('col');
col.style.width = `${otherPct}%`;
colgroup.appendChild(col);
}
table.appendChild(colgroup);
}

const thead = document.createElement('thead');
if (rows.length > 0) {
const headerRow = buildRow(rows[0], 'th');
const firstHeaderCell = headerRow.querySelector('th');
if (firstHeaderCell && !firstHeaderCell.querySelector('img') && !firstHeaderCell.textContent.trim()) {
headerRow.classList.add('table-comparison-row-header-empty');
}
thead.appendChild(headerRow);
}

const tbody = document.createElement('tbody');
rows.slice(1).forEach((row) => {
const tr = document.createElement('tr');
let rowHeaderEmpty = false;
[...row.children].forEach((col, index) => {
const cell = document.createElement(index === 0 ? 'th' : 'td');
if (index === 0) {
cell.setAttribute('scope', 'row');
const wrap = document.createElement('div');
wrap.className = 'table-comparison-cell-content';
wrap.innerHTML = col.innerHTML;
cell.appendChild(wrap);
rowHeaderEmpty = !wrap.querySelector('img') && !wrap.textContent.trim();
} else {
cell.innerHTML = col.innerHTML;
}
tr.appendChild(cell);
});
if (rowHeaderEmpty) tr.classList.add('table-comparison-row-header-empty');
tbody.appendChild(tr);
});

table.append(thead, tbody);
return table;
}

function createColorSwatch(slug, label) {
const swatch = document.createElement('div');
swatch.className = 'table-comparison-color-swatch';
swatch.title = label || slug;
const inner = document.createElement('div');
inner.className = 'table-comparison-color-inner';
inner.style.backgroundColor = `var(--color-${slug})`;
swatch.appendChild(inner);
return swatch;
}

function replaceColorsRowWithSwatches(table, colorNames) {
if (!colorNames?.size) return;
const tbody = table.querySelector('tbody');
if (!tbody) return;
tbody.querySelectorAll('tr').forEach((tr) => {
tr.querySelectorAll('td').forEach((td) => {
const text = td.textContent.trim();
const tokens = text.split(',').map((t) => t.trim()).filter(Boolean);
if (tokens.length === 0) return;
const hasMatchingColor = tokens.some((t) => colorNames.has(toClassName(t)));
if (!hasMatchingColor) return;
const swatchContainer = document.createElement('div');
swatchContainer.className = 'table-comparison-color-swatches';
tokens.forEach((token) => {
const slug = toClassName(token);
if (colorNames.has(slug)) {
swatchContainer.appendChild(createColorSwatch(slug, token));
} else {
const span = document.createElement('span');
span.className = 'table-comparison-color-text';
span.textContent = token;
swatchContainer.appendChild(span);
}
});
td.textContent = '';
td.appendChild(swatchContainer);
});
});
}

export default function decorate(block) {
const table = document.createElement('table');
const rows = [...block.children];
const hasRowHeaders = block.classList.contains('row-headers');
const isComparison = block.classList.contains('comparison');

if (hasRowHeaders) {
if (isComparison) {
const comparisonTable = buildComparisonTable(rows);
loadColorSwatchesForTable().then((colorNames) => {
replaceColorsRowWithSwatches(comparisonTable, colorNames);
});
const scrollWrapper = document.createElement('div');
scrollWrapper.className = 'table-comparison-scroll';
scrollWrapper.appendChild(comparisonTable);
block.replaceChildren(scrollWrapper);
} else if (hasRowHeaders) {
// build table with row headers (first column is header)
const tbody = document.createElement('tbody');
rows.forEach((row) => {
tbody.appendChild(buildRowWithRowHeaders(row));
});
table.appendChild(tbody);
block.replaceChildren(table);
} else {
// build table head
const thead = document.createElement('thead');
Expand All @@ -46,7 +191,6 @@ export default function decorate(block) {
});

table.append(thead, tbody);
block.replaceChildren(table);
}

block.replaceChildren(table);
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"description": "Starter project for Adobe Helix",
"scripts": {
"lint:js": "eslint .",
"lint:css": "stylelint blocks/**/*.css styles/*.css",
"lint:css": "stylelint blocks/**/*.css styles/*.css widgets/**/*.css",
"lint": "npm run lint:js && npm run lint:css",
"build:json": "npm run build:json:models && npm run build:json:definitions && npm run build:json:filters",
"build:json:models": "merge-json-cli -i \"ue/models/component-models.json\" -o \"component-models.json\"",
Expand Down
1 change: 1 addition & 0 deletions scripts/scripts.js
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,7 @@ function decorateFullWidthBlocks(main) {
*/
function decorateButtons(main) {
main.querySelectorAll('p a[href]').forEach((a) => {
if (a.closest('[data-button-decoration="disabled"]')) return;
a.title = a.title || a.textContent;
const p = a.closest('p');
const text = a.textContent.trim();
Expand Down
Loading