Skip to content
Merged
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
8 changes: 8 additions & 0 deletions packages/react-table/src/components/Table/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ export interface TableProps extends React.HTMLProps<HTMLTableElement>, OUIAProps
isStickyHeaderStuck?: boolean;
/** Flag indicating the table footer should stick to the bottom of its scroll container. */
isStickyFooter?: boolean;
/** @beta Flag indicating the table footer should have sticky positioning to the bottom of its scroll container. */
isStickyFooterBase?: boolean;
/** @beta Flag indicating the table footer should have stuck styling, when the footer is not at the bottom of the scroll container. */
isStickyFooterStuck?: boolean;
/** @hide Forwarded ref */
innerRef?: React.RefObject<any>;
/** Flag indicating table is a tree table */
Expand Down Expand Up @@ -107,6 +111,8 @@ const TableBase: React.FunctionComponent<TableProps> = ({
isStickyHeaderBase = false,
isStickyHeaderStuck = false,
isStickyFooter = false,
isStickyFooterBase = false,
isStickyFooterStuck = false,
isPlain = false,
isNoPlainOnGlass = false,
gridBreakPoint = TableGridBreakpoint.gridMd,
Expand Down Expand Up @@ -237,6 +243,8 @@ const TableBase: React.FunctionComponent<TableProps> = ({
isStickyHeaderBase && styles.modifiers.stickyHeaderBase,
isStickyHeaderStuck && styles.modifiers.stickyHeaderStuck,
isStickyFooter && styles.modifiers.stickyFooter,
isStickyFooterBase && styles.modifiers.stickyFooterBase,
isStickyFooterStuck && styles.modifiers.stickyFooterStuck,
isTreeTable && stylesTreeView.modifiers.treeView,
isStriped && styles.modifiers.striped,
isExpandable && styles.modifiers.expandable,
Expand Down
36 changes: 36 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 @@ -234,3 +234,39 @@ test(`Does not render with class ${styles.modifiers.stickyFooter} when isStickyF

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

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

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

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

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

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

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

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

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

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

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

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

expect(screen.getByRole('grid', { name: 'Test table' })).not.toHaveClass(styles.modifiers.stickyFooterStuck);
});
10 changes: 10 additions & 0 deletions packages/react-table/src/components/Table/examples/Table.md
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,16 @@ Use `Tfoot` for semantic table footer rows. Set `isStickyFooter` on `Table` to k

```

### Dynamic sticky footer

A sticky footer may alternatively be implemented with two properties: `isStickyFooterBase` and `isStickyFooterStuck` - which allows separate control of the sticky position and sticky styling. `isStickyFooterBase` should always be applied to make the footer position sticky, and `isStickyFooterStuck` may be applied dynamically to enable the sticky styling, such as when the sticky footer is not at the bottom of the scroll parent as shown in the example.

`isStickyFooter` acts as if both properties are present and true when applied, and is useful when dynamic sticky styling is not necessary.

```ts file="TableStickyFooterDynamic.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,61 @@
import { useLayoutEffect, useRef, useState } from 'react';
import { getResizeObserver } from '@patternfly/react-core';
import { Table, Thead, Tfoot, Tr, Th, Tbody, Td, InnerScrollContainer } from '@patternfly/react-table';

const useIsStuckFromScrollParent = (scrollParentRef: React.RefObject<HTMLDivElement>): boolean => {
const [isStuck, setIsStuck] = useState(false);

useLayoutEffect(() => {
const scrollElement = scrollParentRef.current;
if (!scrollElement) {
return;
}

const syncFromScroll = () => {
setIsStuck(scrollElement.scrollTop + scrollElement.clientHeight < scrollElement.scrollHeight);
};
syncFromScroll();
scrollElement.addEventListener('scroll', syncFromScroll, { passive: true });
const observer = getResizeObserver(scrollElement, syncFromScroll, true);
return () => {
scrollElement.removeEventListener('scroll', syncFromScroll);
observer();
};
}, [scrollParentRef]);

return isStuck;
};

export const TableStickyFooterDynamic: React.FunctionComponent = () => {
const scrollContainerRef = useRef<HTMLDivElement>(null);
const isStuck = useIsStuckFromScrollParent(scrollContainerRef);
const rows = Array.from({ length: 12 }, (_, index) => index + 1);

return (
<div style={{ height: '400px' }}>
<InnerScrollContainer ref={scrollContainerRef}>
<Table aria-label="Dynamic sticky footer table" isStickyFooterBase isStickyFooterStuck={isStuck}>
<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>
);
};
Loading