diff --git a/packages/pxweb2-ui/src/lib/components/Table/Table.module.scss b/packages/pxweb2-ui/src/lib/components/Table/Table.module.scss index 0a6caeb66..0287a5d46 100644 --- a/packages/pxweb2-ui/src/lib/components/Table/Table.module.scss +++ b/packages/pxweb2-ui/src/lib/components/Table/Table.module.scss @@ -17,11 +17,7 @@ tbody tr { &:hover { - background: var(--px-color-surface-subtle) !important; - - th { - background: var(--px-color-surface-subtle) !important; - } + background: var(--px-color-surface-subtle); } } @@ -124,6 +120,7 @@ } th.stub-9 { border-left: none; + @media screen and (min-width: fixed.$breakpoints-small-min-width) { padding-left: 156px; } @@ -140,7 +137,6 @@ text-align: right; border-top: 1px solid var(--px-color-border-default); border-left: 1px solid var(--px-color-border-default); - @media screen and (min-width: fixed.$breakpoints-small-min-width) { padding: 8px 12px; } @@ -254,3 +250,6 @@ border: none; } } +.table td.colHover { + background: var(--px-color-surface-subtle); +} diff --git a/packages/pxweb2-ui/src/lib/components/Table/Table.spec.tsx b/packages/pxweb2-ui/src/lib/components/Table/Table.spec.tsx index 9aa13c68c..277f45588 100644 --- a/packages/pxweb2-ui/src/lib/components/Table/Table.spec.tsx +++ b/packages/pxweb2-ui/src/lib/components/Table/Table.spec.tsx @@ -1,6 +1,5 @@ -import { render } from '@testing-library/react'; +import { render, fireEvent } from '@testing-library/react'; import { describe, it, expect } from 'vitest'; - import Table from './Table'; import { pxTable } from './testData'; @@ -45,4 +44,25 @@ describe('Table', () => { }); expect(found).toBe(false); }); + + it('highlights column on header hover', () => { + const { container } = render(); + console.log(container.innerHTML); // Debug: check for data-col and stub classes + + // Find a leaf header cell + const leafHeader = container.querySelector( + 'thead tr:last-child th[data-col]', + ); + expect(leafHeader).toBeTruthy(); + + // Simulate mouseover + fireEvent.mouseOver(leafHeader!); + + // Find highlighted data cells + const col = leafHeader!.getAttribute('data-col'); + const highlightedCells = container.querySelectorAll( + `td[data-col="${col}"][class*="colHover"]`, + ); + expect(highlightedCells.length).toBeGreaterThan(0); + }); }); diff --git a/packages/pxweb2-ui/src/lib/components/Table/Table.tsx b/packages/pxweb2-ui/src/lib/components/Table/Table.tsx index 37a77140c..af2335840 100644 --- a/packages/pxweb2-ui/src/lib/components/Table/Table.tsx +++ b/packages/pxweb2-ui/src/lib/components/Table/Table.tsx @@ -1,4 +1,4 @@ -import { memo, useMemo } from 'react'; +import { memo, useMemo, useRef, useEffect } from 'react'; import cl from 'clsx'; import classes from './Table.module.scss'; @@ -115,8 +115,67 @@ export const Table = memo(function Table({ headingDataCellCodes[i] = dataCellCodes; } + const tableRef = useRef(null); + + useEffect(() => { + const table = tableRef.current; + if (!table) { + return; + } + + let currentCol: string | null = null; + + function clear() { + for (const cell of Array.from( + table!.querySelectorAll('.' + classes.colHover), + )) { + cell.classList.remove(classes.colHover); + } + currentCol = null; + } + + function handleOver(e: MouseEvent) { + // Only highlight if hovering a data cell (td[data-col]) or a leaf header cell (th[data-col] in last header row) + const td = (e.target as HTMLElement).closest('td[data-col]'); + const th = (e.target as HTMLElement).closest( + 'thead tr:last-child th[data-col]', + ); + // If hovering stub or emptyTableData, clear highlight + if ( + (e.target as HTMLElement).closest('.stub') || + (e.target as HTMLElement).closest('.emptyTableData') || + (!td && !th) + ) { + clear(); + return; + } + // Determine column + const cell = td || th; + const col = (cell as HTMLElement).dataset.col; + if (!col || col === currentCol) { + return; + } + clear(); + // Highlight only data cells in the same column + for (const cell of Array.from( + table!.querySelectorAll(`td[data-col="${col}"]`), + )) { + cell.classList.add(classes.colHover); + } + currentCol = col; + } + + table.addEventListener('mouseover', handleOver); + table.addEventListener('mouseleave', clear); + return () => { + table.removeEventListener('mouseover', handleOver); + table.removeEventListener('mouseleave', clear); + }; + }, []); + return (
@@ -224,6 +283,12 @@ export function createHeading( idxRepetitionCurrentHeadingLevel === 1 && table.stub.length === 0, })} + // Only add data-col for leaf header row + data-col={ + idxHeadingLevel === table.heading.length - 1 + ? String(columnIndex + tableMeta.columnOffset) + : undefined + } > {variable.values[i].label} , @@ -608,7 +673,11 @@ function fillEmpty( // Loop through all data columns in the table for (let i = 0; i < maxCols; i++) { - tableRow.push(); + tableRow.push( + , + ); } } @@ -632,7 +701,6 @@ function fillData( const maxCols = tableMeta.columns - tableMeta.columnOffset; // Loop through all data columns in the table - for (let i = 0; i < maxCols; i++) { // Merge the metadata structure for the dimensions of the stub and header cells const dataCellCodes = stubDataCellCodes.concat(headingDataCellCodes[i]); @@ -654,7 +722,11 @@ function fillData( const dataValue = getPxTableData(table.data.cube, dimensions); tableRow.push( - , );
{emptyText} + {emptyText} + + {dataValue?.formattedValue}