Fix: Gradient overlay on TableCard does not disappear on resize (#60208)

Fixes a bug where the linear gradient overlay on tables would not disappear when resizing the viewport back to a larger size. The issue was affecting tables in the WooPayments plugin.
This commit is contained in:
Miguel Gasca 2025-08-14 18:05:57 +02:00 committed by GitHub
parent e74cf32cf5
commit 6c7fad08d5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 184 additions and 17 deletions

View file

@ -0,0 +1,4 @@
Significance: patch
Type: fix
Fix linear gradient overlay not disappearing on table resize

View file

@ -10,6 +10,60 @@ import { Button, Notice } from '@wordpress/components';
*/
import { headers, rows, summary } from './index';
// Create headers with many columns to trigger horizontal scrolling
const wideHeaders = [
{ key: 'month', label: 'Month' },
{ key: 'orders', label: 'Orders' },
{ key: 'revenue', label: 'Revenue' },
{ key: 'profit', label: 'Profit' },
{ key: 'taxes', label: 'Taxes' },
{ key: 'shipping', label: 'Shipping' },
{ key: 'discounts', label: 'Discounts' },
{ key: 'refunds', label: 'Refunds' },
{ key: 'fees', label: 'Fees' },
{ key: 'net', label: 'Net Revenue' },
];
// Create rows with many columns
const wideRows = [
[
{ display: 'January', value: 1 },
{ display: 10, value: 10 },
{ display: '$530.00', value: 530 },
{ display: '$450.00', value: 450 },
{ display: '$80.00', value: 80 },
{ display: '$25.00', value: 25 },
{ display: '$15.00', value: 15 },
{ display: '$0.00', value: 0 },
{ display: '$5.00', value: 5 },
{ display: '$405.00', value: 405 },
],
[
{ display: 'February', value: 2 },
{ display: 13, value: 13 },
{ display: '$675.00', value: 675 },
{ display: '$580.00', value: 580 },
{ display: '$95.00', value: 95 },
{ display: '$30.00', value: 30 },
{ display: '$20.00', value: 20 },
{ display: '$0.00', value: 0 },
{ display: '$8.00', value: 8 },
{ display: '$517.00', value: 517 },
],
[
{ display: 'March', value: 3 },
{ display: 9, value: 9 },
{ display: '$460.00', value: 460 },
{ display: '$390.00', value: 390 },
{ display: '$70.00', value: 70 },
{ display: '$22.00', value: 22 },
{ display: '$18.00', value: 18 },
{ display: '$0.00', value: 0 },
{ display: '$6.00', value: 6 },
{ display: '$344.00', value: 344 },
],
];
const TableCardExample = () => {
const [ { query }, setState ] = useState( {
query: {
@ -124,9 +178,36 @@ const TableCardWithTablePrefaceExample = () => {
);
};
const TableCardWideExample = () => {
const [ { query }, setState ] = useState( {
query: {
paged: 1,
},
} );
return (
<TableCard
title="Revenue with many columns (test horizontal scroll)"
rows={ wideRows }
headers={ wideHeaders }
onQueryChange={ ( param ) => ( value ) =>
setState( {
// @ts-expect-error: ignore for storybook
query: {
[ param ]: value,
},
} ) }
query={ query }
rowsPerPage={ 7 }
totalRows={ 10 }
summary={ summary }
/>
);
};
export const Basic = () => <TableCardExample />;
export const Actions = () => <TableCardWithActionsExample />;
export const TablePreface = () => <TableCardWithTablePrefaceExample />;
export const WideTable = () => <TableCardWideExample />;
export default {
title: 'Components/TableCard',

View file

@ -129,22 +129,36 @@ const Table: React.VFC< TableProps > = ( {
const updateTableShadow = () => {
const table = container.current;
if ( table?.scrollWidth && table?.scrollHeight && table?.offsetWidth ) {
const scrolledToEnd =
table.scrollWidth - table.scrollLeft <= table.offsetWidth;
if ( scrolledToEnd && isScrollableRight ) {
setIsScrollableRight( false );
} else if ( ! scrolledToEnd && ! isScrollableRight ) {
setIsScrollableRight( true );
}
const scrolledToStart = table.scrollLeft === 0;
if ( scrolledToStart && isScrollableLeft ) {
setIsScrollableLeft( false );
} else if ( ! scrolledToStart && ! isScrollableLeft ) {
setIsScrollableLeft( true );
}
if ( ! table ) {
return;
}
// Get current dimensions
const scrollWidth = table.scrollWidth;
const offsetWidth = table.offsetWidth;
const scrollLeft = table.scrollLeft;
// Check if the table is actually scrollable
const isTableScrollable = scrollWidth > offsetWidth;
// If table is not scrollable, remove all scroll indicators
if ( ! isTableScrollable ) {
setIsScrollableRight( false );
setIsScrollableLeft( false );
// Reset scroll position when table is no longer scrollable
if ( scrollLeft !== 0 ) {
table.scrollLeft = 0;
}
return;
}
// Calculate scroll states
const scrolledToEnd = scrollWidth - scrollLeft <= offsetWidth;
const scrolledToStart = scrollLeft === 0;
// Update scroll indicators based on current state
setIsScrollableRight( ! scrolledToEnd );
setIsScrollableLeft( ! scrolledToStart );
};
const sortedBy =
@ -166,10 +180,18 @@ const Table: React.VFC< TableProps > = ( {
const scrollable = scrollWidth > clientWidth;
setTabIndex( scrollable ? 0 : undefined );
updateTableShadow();
window.addEventListener( 'resize', updateTableShadow );
const handleResize = () => {
// Use requestAnimationFrame to ensure DOM has updated
requestAnimationFrame( () => {
updateTableShadow();
} );
};
window.addEventListener( 'resize', handleResize );
return () => {
window.removeEventListener( 'resize', updateTableShadow );
window.removeEventListener( 'resize', handleResize );
};
}, [] );

View file

@ -258,4 +258,64 @@ describe( 'Table', () => {
expect( el ).toHaveClass( 'class-222' );
} );
it( 'should remove scrollable classes when table is no longer scrollable', () => {
// Mock a table that is initially scrollable
const mockScrollableTable = {
scrollWidth: 1000,
offsetWidth: 500,
scrollLeft: 0,
};
// Mock a table that is no longer scrollable
const mockNonScrollableTable = {
scrollWidth: 400,
offsetWidth: 500,
scrollLeft: 0,
};
const {} = render(
<Table
caption="Test table"
headers={ mockHeaders }
rows={ mockData }
/>
);
const tableElement = screen.getByLabelText( 'Test table' );
// Mock the container ref to simulate scrollable state
Object.defineProperty( tableElement, 'scrollWidth', {
value: mockScrollableTable.scrollWidth,
writable: true,
} );
Object.defineProperty( tableElement, 'offsetWidth', {
value: mockScrollableTable.offsetWidth,
writable: true,
} );
Object.defineProperty( tableElement, 'scrollLeft', {
value: mockScrollableTable.scrollLeft,
writable: true,
} );
// Trigger a resize event to simulate window resize
window.dispatchEvent( new Event( 'resize' ) );
// Now simulate the table becoming non-scrollable
Object.defineProperty( tableElement, 'scrollWidth', {
value: mockNonScrollableTable.scrollWidth,
writable: true,
} );
Object.defineProperty( tableElement, 'offsetWidth', {
value: mockNonScrollableTable.offsetWidth,
writable: true,
} );
// Trigger another resize event
window.dispatchEvent( new Event( 'resize' ) );
// The table should not have scrollable classes when it's not scrollable
expect( tableElement ).not.toHaveClass( 'is-scrollable-right' );
expect( tableElement ).not.toHaveClass( 'is-scrollable-left' );
} );
} );