import { memo, useMemo } from 'react';
import { __ } from '@wordpress/i18n';
import clsx from 'clsx';
import type { BarDataTableProps } from './types';
/**
* Derives a CSS grid track size for a column.
*
* Left-aligned columns grow to fill available space (`1fr`).
* Right- and center-aligned columns use their explicit width or a sensible
* pixel default derived from `minWidth`.
*
* @param {Object} col - The column definition.
* @return {string} A CSS grid track value.
*/
function colTrack( col: { align?: string; width?: string; minWidth?: number }): string {
if ( col.width ) {
return col.width;
}
if ( ! col.align || 'left' === col.align ) {
return '1fr';
}
return `${ col.minWidth ?? 80 }px`;
}
/**
* Generic, minimalist data table with a full-row proportional background bar.
*
* The bar spans the entire row width and is drawn behind all cell content,
* giving readers an instant visual sense of scale without obscuring any text.
* Because CSS `position: absolute` cannot reliably escape a `
`, this
* component uses a div-grid layout with ARIA table roles for accessibility.
*
* Header and body rows share a single parent CSS grid via `subgrid`, so
* columns are always perfectly aligned regardless of content width.
*
* @template T - The row data type.
* @param {BarDataTableProps} props - Component props.
* @return {JSX.Element} The rendered table.
*/
function BarDataTableInner>({
columns,
data,
rowKey,
barColumnKey,
isLoading = false,
emptyState,
className
}: BarDataTableProps ) {
const gridTemplate = useMemo(
() => columns.map( colTrack ).join( ' ' ),
[ columns ]
);
const barMax = useMemo( () => {
if ( ! barColumnKey || 0 === data.length ) {
return 0;
}
return Math.max(
...data.map( ( row ) => {
const v = row[ barColumnKey ];
return 'number' === typeof v ? v : 0;
})
);
}, [ data, barColumnKey ]);
const showSkeleton = isLoading && 0 === data.length;
return (
{/* Header row. */}
{ columns.map( ( col ) => (
{ col.label }
) ) }
{/* Body rows. */}
{ showSkeleton &&
Array.from({ length: 5 }).map( ( _, i ) => (
{ columns.map( ( col ) => (
) ) }
) ) }
{ ! showSkeleton && 0 === data.length && (
{ emptyState ?? __( 'No data available.', 'burst-statistics' ) }
) }
{ ! showSkeleton &&
data.map( ( row, index ) => {
const barValue = barColumnKey ?
( row[ barColumnKey ] as number ) :
0;
const barPct =
0 < barMax && barColumnKey ?
( barValue / barMax ) * 100 :
0;
return (
{/* Full-row background bar — rendered behind all cells. */}
{ barColumnKey && 0 < barPct && (
) }
{ columns.map( ( col ) => {
const rawValue = row[ col.key as keyof T ];
return (
{ col.cell ? (
col.cell( row, index )
) : (
String( rawValue ?? '' )
) }
);
}) }
);
}) }
);
}
/**
* Memoized export of the generic BarDataTable.
* Cast is required because memo loses the generic type parameter.
*/
export const BarDataTable = memo( BarDataTableInner ) as typeof BarDataTableInner;
export default BarDataTable;
|