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
2 changes: 1 addition & 1 deletion packages/react-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"tslib": "^2.8.1"
},
"devDependencies": {
"@patternfly/patternfly": "6.5.0-prerelease.65",
"@patternfly/patternfly": "6.5.0-prerelease.66",
"case-anything": "^3.1.2",
"css": "^3.0.0",
"fs-extra": "^11.3.3"
Expand Down
2 changes: 1 addition & 1 deletion packages/react-docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"test:a11y": "patternfly-a11y --config patternfly-a11y.config"
},
"dependencies": {
"@patternfly/patternfly": "6.5.0-prerelease.65",
"@patternfly/patternfly": "6.5.0-prerelease.66",
"@patternfly/react-charts": "workspace:^",
"@patternfly/react-code-editor": "workspace:^",
"@patternfly/react-core": "workspace:^",
Expand Down
2 changes: 1 addition & 1 deletion packages/react-icons/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"@fortawesome/free-brands-svg-icons": "^5.15.4",
"@fortawesome/free-regular-svg-icons": "^5.15.4",
"@fortawesome/free-solid-svg-icons": "^5.15.4",
"@patternfly/patternfly": "6.5.0-prerelease.65",
"@patternfly/patternfly": "6.5.0-prerelease.66",
"@rhds/icons": "^2.1.0",
"fs-extra": "^11.3.3",
"tslib": "^2.8.1"
Expand Down
2 changes: 1 addition & 1 deletion packages/react-styles/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"clean": "rimraf dist css"
},
"devDependencies": {
"@patternfly/patternfly": "6.5.0-prerelease.65",
"@patternfly/patternfly": "6.5.0-prerelease.66",
"change-case": "^5.4.4",
"fs-extra": "^11.3.3"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { forwardRef } from 'react';
import { css } from '@patternfly/react-styles';
import styles from '@patternfly/react-styles/css/components/Table/table-scrollable';

Expand All @@ -6,16 +7,22 @@ export interface InnerScrollContainerProps extends React.HTMLProps<HTMLDivElemen
children?: React.ReactNode;
/** Additional classes added to the container */
className?: string;
/** @hide Forwarded ref */
innerRef?: React.Ref<HTMLDivElement>;
}

export const InnerScrollContainer: React.FunctionComponent<InnerScrollContainerProps> = ({
const InnerScrollContainerBase: React.FunctionComponent<InnerScrollContainerProps> = ({
children,
className,
innerRef,
...props
}: InnerScrollContainerProps) => (
<div className={css(className, styles.scrollInnerWrapper)} {...props}>
<div ref={innerRef} className={css(className, styles.scrollInnerWrapper)} {...props}>
{children}
</div>
);

export const InnerScrollContainer = forwardRef((props: InnerScrollContainerProps, ref: React.Ref<HTMLDivElement>) => (
<InnerScrollContainerBase innerRef={ref} {...props} />
));
InnerScrollContainer.displayName = 'InnerScrollContainer';
10 changes: 9 additions & 1 deletion packages/react-table/src/components/Table/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,12 @@ export interface TableProps extends React.HTMLProps<HTMLTableElement>, OUIAProps
isPlain?: boolean;
/** @beta Flag indicating if the table should not have plain styling when in the glass theme */
isNoPlainOnGlass?: boolean;
/** If set to true, the table header sticks to the top of its container */
/** If set to true, the table header sticks to the top of its container. This property applies both the sticky position and styling. */
isStickyHeader?: boolean;
/** @beta Flag indicating the table header should have sticky positioning to the top of the parentInnerScrollContainer. */
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;
/** @hide Forwarded ref */
innerRef?: React.RefObject<any>;
/** Flag indicating table is a tree table */
Expand Down Expand Up @@ -98,6 +102,8 @@ const TableBase: React.FunctionComponent<TableProps> = ({
variant,
borders = true,
isStickyHeader = false,
isStickyHeaderBase = false,
isStickyHeaderStuck = false,
isPlain = false,
isNoPlainOnGlass = false,
gridBreakPoint = TableGridBreakpoint.gridMd,
Expand Down Expand Up @@ -225,6 +231,8 @@ const TableBase: React.FunctionComponent<TableProps> = ({
styles.modifiers[variant],
!borders && styles.modifiers.noBorderRows,
isStickyHeader && styles.modifiers.stickyHeader,
isStickyHeaderBase && styles.modifiers.stickyHeaderBase,
isStickyHeaderStuck && styles.modifiers.stickyHeaderStuck,
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 @@ -186,3 +186,39 @@ test(`Does not render with class ${styles.modifiers.noPlainOnGlass} when isNoPla

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

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

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

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

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

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

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

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

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

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

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

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

expect(screen.getByRole('grid', { name: 'Test table' })).not.toHaveClass(styles.modifiers.stickyHeaderStuck);
});
13 changes: 11 additions & 2 deletions packages/react-table/src/components/Table/examples/Table.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ The `Table` component takes an explicit and declarative approach, and its implem

The documentation for the deprecated table implementation can be found under the [React deprecated](/components/table/react-deprecated) tab. It is configuration based and takes a less declarative and more implicit approach to laying out the table structure, such as the rows and cells within it.

import { Fragment, isValidElement, useCallback, useEffect, useRef, useState } from 'react';
import { Fragment, isValidElement, useCallback, useEffect, useRef, useState, useLayoutEffect } from 'react';
import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';
import CodeBranchIcon from '@patternfly/react-icons/dist/esm/icons/code-branch-icon';
import CodeIcon from '@patternfly/react-icons/dist/esm/icons/code-icon';
Expand Down Expand Up @@ -327,7 +327,6 @@ To enable a tree table:
- `checkAriaLabel` - (optional) accessible label for the checkbox
- `showDetailsAriaLabel` - (optional) accessible label for the show row details button in the responsive view
4. The first `Td` in each row will pass the following to the `treeRow` prop:

- `onCollapse` - Callback when user expands/collapses a row to reveal/hide the row's children.
- `onCheckChange` - (optional) Callback when user changes the checkbox on a row.
- `onToggleRowDetails` - (optional) Callback when user shows/hides the row details in responsive view.
Expand Down Expand Up @@ -419,6 +418,16 @@ To prevent the default text wrapping behavior and allow horizontal scrolling, al

```

### Dynamic sticky header

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

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

```ts file="TableStickyHeaderDynamic.tsx"

```

### Sticky columns and header

To maintain proper sticky behavior across sticky columns and header, `Table` must be wrapped with `OuterScrollContainer` and `InnerScrollContainer`.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { useLayoutEffect, useRef, useState } from 'react';
import { Table, Thead, Tr, Th, Tbody, Td, InnerScrollContainer } from '@patternfly/react-table';
import BlueprintIcon from '@patternfly/react-icons/dist/esm/icons/blueprint-icon';

interface Fact {
name: string;
state: string;
detail1: string;
detail2: string;
detail3: string;
detail4: string;
detail5: string;
detail6: string;
detail7: string;
}

const useIsStuckFromScrollParent = ({
shouldTrack,
scrollParentRef
}: {
/** Indicates whether to track the scroll top position of the scroll parent element */
shouldTrack: boolean;
/** Reference to the scroll parent element */
scrollParentRef: React.RefObject<any>;
}): boolean => {
const [isStuck, setIsStuck] = useState(false);

useLayoutEffect(() => {
if (!shouldTrack) {
setIsStuck(false);
return;
}

const scrollElement = scrollParentRef.current;
if (!scrollElement) {
setIsStuck(false);
return;
}

const syncFromScroll = () => {
setIsStuck(scrollElement.scrollTop > 0);
};
syncFromScroll();
scrollElement.addEventListener('scroll', syncFromScroll, { passive: true });
return () => scrollElement.removeEventListener('scroll', syncFromScroll);
}, [shouldTrack, scrollParentRef]);

return isStuck;
};

export const TableStickyHeaderDynamic: React.FunctionComponent = () => {
const scrollContainerRef = useRef<HTMLDivElement>(null);
const isStuck = useIsStuckFromScrollParent({ shouldTrack: true, scrollParentRef: scrollContainerRef });

// In real usage, this data would come from some external source like an API via props.
const facts: Fact[] = Array.from({ length: 9 }, (_, index) => ({
name: `Fact ${index + 1}`,
state: `State ${index + 1}`,
detail1: `Test cell ${index + 1}-3`,
detail2: `Test cell ${index + 1}-4`,
detail3: `Test cell ${index + 1}-5`,
detail4: `Test cell ${index + 1}-6`,
detail5: `Test cell ${index + 1}-7`,
detail6: `Test cell ${index + 1}-8`,
detail7: `Test cell ${index + 1}-9`
}));

const columnNames = {
name: 'Fact',
state: 'State',
header3: 'Header 3',
header4: 'Header 4',
header5: 'Header 5',
header6: 'Header 6',
header7: 'Header 7',
header8: 'Header 8',
header9: 'Header 9'
};

return (
<div style={{ height: '400px' }}>
<InnerScrollContainer ref={scrollContainerRef}>
<Table
aria-label="Dynamic sticky header table"
gridBreakPoint=""
isStickyHeaderBase
isStickyHeaderStuck={isStuck}
>
<Thead>
<Tr>
<Th modifier="truncate">{columnNames.name}</Th>
<Th modifier="truncate">{columnNames.state}</Th>
<Th modifier="truncate">{columnNames.header3}</Th>
<Th modifier="truncate">{columnNames.header4}</Th>
<Th modifier="truncate">{columnNames.header5}</Th>
<Th modifier="truncate">{columnNames.header6}</Th>
<Th modifier="truncate">{columnNames.header7}</Th>
<Th modifier="truncate">{columnNames.header8}</Th>
<Th modifier="truncate">{columnNames.header9}</Th>
</Tr>
</Thead>
<Tbody>
{facts.map((fact) => (
<Tr key={fact.name}>
<Td modifier="nowrap" dataLabel={columnNames.name}>
{fact.name}
</Td>
<Td modifier="nowrap" dataLabel={columnNames.state}>
<BlueprintIcon />
{` ${fact.state}`}
</Td>
<Td modifier="nowrap" dataLabel={columnNames.header3}>
{fact.detail1}
</Td>
<Td modifier="nowrap" dataLabel={columnNames.header4}>
{fact.detail2}
</Td>
<Td modifier="nowrap" dataLabel={columnNames.header5}>
{fact.detail3}
</Td>
<Td modifier="nowrap" dataLabel={columnNames.header6}>
{fact.detail4}
</Td>
<Td modifier="nowrap" dataLabel={columnNames.header7}>
{fact.detail5}
</Td>
<Td modifier="nowrap" dataLabel={columnNames.header8}>
{fact.detail6}
</Td>
<Td modifier="nowrap" dataLabel={columnNames.header9}>
{fact.detail7}
</Td>
</Tr>
))}
</Tbody>
</Table>
</InnerScrollContainer>
</div>
);
};
2 changes: 1 addition & 1 deletion packages/react-tokens/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
},
"devDependencies": {
"@adobe/css-tools": "^4.4.4",
"@patternfly/patternfly": "6.5.0-prerelease.65",
"@patternfly/patternfly": "6.5.0-prerelease.66",
"fs-extra": "^11.3.3"
}
}
18 changes: 9 additions & 9 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5070,10 +5070,10 @@ __metadata:
languageName: node
linkType: hard

"@patternfly/patternfly@npm:6.5.0-prerelease.65":
version: 6.5.0-prerelease.65
resolution: "@patternfly/patternfly@npm:6.5.0-prerelease.65"
checksum: 10c0/5d6042bb3b3a562b3b1421395edbbd5899f84a3349d45be29f9d3d9a9e18968b8e86275f75a86b9a3720db905679b52a4227377b640c0096f498d584e5f4c2fb
"@patternfly/patternfly@npm:6.5.0-prerelease.66":
version: 6.5.0-prerelease.66
resolution: "@patternfly/patternfly@npm:6.5.0-prerelease.66"
checksum: 10c0/bd5f5809334a5256c5924bfacf1e07f52f52ef7c86955e23d2cf32b142403fa41dc8f40b14dd2026468db628b482fa9175280548ac91a9798882bf053bc1bea4
languageName: node
linkType: hard

Expand Down Expand Up @@ -5171,7 +5171,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@patternfly/react-core@workspace:packages/react-core"
dependencies:
"@patternfly/patternfly": "npm:6.5.0-prerelease.65"
"@patternfly/patternfly": "npm:6.5.0-prerelease.66"
"@patternfly/react-icons": "workspace:^"
"@patternfly/react-styles": "workspace:^"
"@patternfly/react-tokens": "workspace:^"
Expand All @@ -5192,7 +5192,7 @@ __metadata:
resolution: "@patternfly/react-docs@workspace:packages/react-docs"
dependencies:
"@patternfly/documentation-framework": "npm:^6.36.8"
"@patternfly/patternfly": "npm:6.5.0-prerelease.65"
"@patternfly/patternfly": "npm:6.5.0-prerelease.66"
"@patternfly/patternfly-a11y": "npm:5.1.0"
"@patternfly/react-charts": "workspace:^"
"@patternfly/react-code-editor": "workspace:^"
Expand Down Expand Up @@ -5232,7 +5232,7 @@ __metadata:
"@fortawesome/free-brands-svg-icons": "npm:^5.15.4"
"@fortawesome/free-regular-svg-icons": "npm:^5.15.4"
"@fortawesome/free-solid-svg-icons": "npm:^5.15.4"
"@patternfly/patternfly": "npm:6.5.0-prerelease.65"
"@patternfly/patternfly": "npm:6.5.0-prerelease.66"
"@rhds/icons": "npm:^2.1.0"
fs-extra: "npm:^11.3.3"
tslib: "npm:^2.8.1"
Expand Down Expand Up @@ -5319,7 +5319,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@patternfly/react-styles@workspace:packages/react-styles"
dependencies:
"@patternfly/patternfly": "npm:6.5.0-prerelease.65"
"@patternfly/patternfly": "npm:6.5.0-prerelease.66"
change-case: "npm:^5.4.4"
fs-extra: "npm:^11.3.3"
languageName: unknown
Expand Down Expand Up @@ -5361,7 +5361,7 @@ __metadata:
resolution: "@patternfly/react-tokens@workspace:packages/react-tokens"
dependencies:
"@adobe/css-tools": "npm:^4.4.4"
"@patternfly/patternfly": "npm:6.5.0-prerelease.65"
"@patternfly/patternfly": "npm:6.5.0-prerelease.66"
fs-extra: "npm:^11.3.3"
languageName: unknown
linkType: soft
Expand Down
Loading