Skip to content
Open
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
4 changes: 4 additions & 0 deletions packages/react-table/src/components/Table/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ export interface TableProps extends React.HTMLProps<HTMLTableElement>, OUIAProps
isStickyHeaderBase?: boolean;
/** @beta Flag indicating the table header should have stuck styling, when the header is not at the top of the scroll container. */
isStickyHeaderStuck?: boolean;
/** Flag indicating the table footer should stick to the bottom of its scroll container. */
isStickyFooter?: boolean;
/** @hide Forwarded ref */
innerRef?: React.RefObject<any>;
/** Flag indicating table is a tree table */
Expand Down Expand Up @@ -104,6 +106,7 @@ const TableBase: React.FunctionComponent<TableProps> = ({
isStickyHeader = false,
isStickyHeaderBase = false,
isStickyHeaderStuck = false,
isStickyFooter = false,
isPlain = false,
isNoPlainOnGlass = false,
gridBreakPoint = TableGridBreakpoint.gridMd,
Expand Down Expand Up @@ -233,6 +236,7 @@ const TableBase: React.FunctionComponent<TableProps> = ({
isStickyHeader && styles.modifiers.stickyHeader,
isStickyHeaderBase && styles.modifiers.stickyHeaderBase,
isStickyHeaderStuck && styles.modifiers.stickyHeaderStuck,
isStickyFooter && styles.modifiers.stickyFooter,
isTreeTable && stylesTreeView.modifiers.treeView,
isStriped && styles.modifiers.striped,
isExpandable && styles.modifiers.expandable,
Expand Down
23 changes: 23 additions & 0 deletions packages/react-table/src/components/Table/Tfoot.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { forwardRef } from 'react';
import { css } from '@patternfly/react-styles';
import styles from '@patternfly/react-styles/css/components/Table/table';

export interface TfootProps extends React.HTMLProps<HTMLTableSectionElement> {
/** Content rendered inside the <tfoot> row group */
children?: React.ReactNode;
/** Additional classes added to the <tfoot> element */
className?: string;
/** @hide Forwarded ref */
innerRef?: React.Ref<any>;
}

const TfootBase: React.FunctionComponent<TfootProps> = ({ children, className, innerRef, ...props }: TfootProps) => (
<tfoot className={css(styles.tableTfoot, className)} ref={innerRef} {...props}>
{children}
</tfoot>
);

export const Tfoot = forwardRef((props: TfootProps, ref: React.Ref<HTMLTableSectionElement>) => (
<TfootBase {...props} innerRef={ref} />
));
Tfoot.displayName = 'Tfoot';
12 changes: 12 additions & 0 deletions packages/react-table/src/components/Table/__tests__/Table.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -222,3 +222,15 @@ test(`Does not render with class ${styles.modifiers.stickyHeaderStuck} when isSt

expect(screen.getByRole('grid', { name: 'Test table' })).not.toHaveClass(styles.modifiers.stickyHeaderStuck);
});

test(`Renders with class ${styles.modifiers.stickyFooter} when isStickyFooter is true`, () => {
render(<Table isStickyFooter aria-label="Test table" />);

expect(screen.getByRole('grid', { name: 'Test table' })).toHaveClass(styles.modifiers.stickyFooter);
});

test(`Does not render with class ${styles.modifiers.stickyFooter} when isStickyFooter is false`, () => {
render(<Table isStickyFooter={false} aria-label="Test table" />);

expect(screen.getByRole('grid', { name: 'Test table' })).not.toHaveClass(styles.modifiers.stickyFooter);
});
32 changes: 32 additions & 0 deletions packages/react-table/src/components/Table/__tests__/Tfoot.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { render, screen } from '@testing-library/react';
import { Tfoot } from '../Tfoot';
import styles from '@patternfly/react-styles/css/components/Table/table';

test('Renders a tfoot element with the table footer class', () => {
render(
<table>
<Tfoot>
<tr>
<td>Footer</td>
</tr>
</Tfoot>
</table>
);

expect(screen.getByText('Footer').closest('tfoot')).toHaveClass(styles.tableTfoot);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
expect(screen.getByText('Footer').closest('tfoot')).toHaveClass(styles.tableTfoot);
expect(screen.getByText('Footer').closest('tfoot')).toHaveClass(styles.tableTfoot, { exact: true });

Comment on lines +5 to +16

@thatblindgeye thatblindgeye Sep 10, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition to Rebeccas's comment above:

Suggested change
test('Renders a tfoot element with the table footer class', () => {
render(
<table>
<Tfoot>
<tr>
<td>Footer</td>
</tr>
</Tfoot>
</table>
);
expect(screen.getByText('Footer').closest('tfoot')).toHaveClass(styles.tableTfoot);
test(`Renders with class ${styles.tableTfoot} only by default`, () => {
render(
<table>
<Tfoot />
</table>
);
expect(screen.getByRole('rowgroup')).toHaveClass(styles.tableTfoot, { exact: true });
});

We want to try and use matchers that can more precisely get the element we want. Since we don't need to worry about any other rowgroup (thead or tbody) we should be okay to use that matcher here. Also just a minor tweak to the test name to match similar tests in other test files.

});

test('Forwards props, class names, and refs to the tfoot element', () => {
const ref = { current: null } as React.RefObject<HTMLTableSectionElement>;

render(
<table>
<Tfoot ref={ref} className="custom-footer" data-testid="footer">
<tr />
</Tfoot>
</table>
);

expect(screen.getByTestId('footer')).toHaveClass(styles.tableTfoot, 'custom-footer');
expect(ref.current).toBe(screen.getByTestId('footer'));
});
Comment on lines +19 to +32

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we separate these out into 3 different tests? One for the ref, one for the custom class, and one for the props spreading? Also just using the getByRole matcher from above if we can.

9 changes: 9 additions & 0 deletions packages/react-table/src/components/Table/examples/Table.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ propComponents:
'Tr',
'Th',
'Td',
'Tfoot',
'Caption',
'TableText',
'TdActionsType',
Expand Down Expand Up @@ -480,6 +481,14 @@ The second `Tr` represents the second level of sub columns. The `Th` in this row

```

### Sticky footer

Use `Tfoot` for semantic table footer rows. Set `isStickyFooter` on `Table` to keep the footer visible while its scroll container is scrolling.

```ts file="TableStickyFooter.tsx"

```

### Striped

To apply striping to a basic table, add the `isStriped` property to `Table`.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Table, Thead, Tfoot, Tr, Th, Tbody, Td, InnerScrollContainer } from '@patternfly/react-table';

export const TableStickyFooter: React.FunctionComponent = () => {
const rows = Array.from({ length: 12 }, (_, index) => index + 1);

return (
<div style={{ height: '400px' }}>
<InnerScrollContainer>
<Table aria-label="Sticky footer table" gridBreakPoint="" isStickyFooter>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<Table aria-label="Sticky footer table" gridBreakPoint="" isStickyFooter>
<Table aria-label="Sticky footer table" isStickyFooter>

<Thead>
<Tr>
<Th>Item</Th>
<Th>Value</Th>
</Tr>
</Thead>
<Tbody>
{rows.map((row) => (
<Tr key={row}>
<Td dataLabel="Item">Item {row}</Td>
<Td dataLabel="Value">Value {row}</Td>
</Tr>
))}
</Tbody>
<Tfoot>
<Tr>
<Td colSpan={2}>Total: {rows.length} items</Td>
</Tr>
</Tfoot>
</Table>
</InnerScrollContainer>
</div>
);
};
1 change: 1 addition & 0 deletions packages/react-table/src/components/Table/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export * from './TreeRowWrapper';
export * from './Table';
export * from './Thead';
export * from './Tbody';
export * from './Tfoot';
export * from './Tr';
export * from './Th';
export * from './Td';
Expand Down
Loading