Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
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="Sticky columns and 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}>
<Th modifier="nowrap" dataLabel={columnNames.name}>
{fact.name}
</Th>
<Th modifier="nowrap" dataLabel={columnNames.state}>
<BlueprintIcon />
{` ${fact.state}`}
</Th>
<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