Skip to content

feat(Table): support dynamic sticky styling#12348

Open
kmcfaul wants to merge 3 commits intopatternfly:mainfrom
kmcfaul:sticky-table-updates
Open

feat(Table): support dynamic sticky styling#12348
kmcfaul wants to merge 3 commits intopatternfly:mainfrom
kmcfaul:sticky-table-updates

Conversation

@kmcfaul
Copy link
Copy Markdown
Contributor

@kmcfaul kmcfaul commented Apr 10, 2026

What: Closes #12329

Needs patternfly/patternfly#8223 to be merged and pulled in for styling.

  • Bumps core to v.66
  • Passes ref through to InnerScrollContainer
  • Adds isStickyHeaderBase and isStickyHeaderStuck properties to Table
  • Adds unit tests for new modifiers
  • Adds "dynamic sticky header" simplified example

Notes -
New styling is most noticeable in glass theme, but still works in default.
If sticky column support goes in before this merges will update that here.

Summary by CodeRabbit

  • New Features

    • Added ref forwarding support to the scroll container for enhanced accessibility and integration
    • Introduced new sticky header configuration options for granular control over header positioning and styling
    • Added example demonstrating dynamic sticky header behavior with scroll-based state tracking
  • Documentation

    • New guidance and example component for implementing dynamic sticky headers

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 10, 2026

Walkthrough

The PR adds ref forwarding to InnerScrollContainer, introduces two new optional boolean props (isStickyHeaderBase and isStickyHeaderStuck) to the Table component for controlling sticky header styling states, provides documentation and a new example component demonstrating dynamic sticky header behavior with scroll event tracking, and adds corresponding test coverage for the new props.

Changes

Cohort / File(s) Summary
Ref Forwarding
packages/react-table/src/components/Table/InnerScrollContainer.tsx
Added innerRef prop to InnerScrollContainerProps interface and implemented ref forwarding using forwardRef to expose the underlying <div> reference.
Sticky Header Props
packages/react-table/src/components/Table/Table.tsx, packages/react-table/src/components/Table/__tests__/Table.test.tsx
Added isStickyHeaderBase and isStickyHeaderStuck optional boolean props to TableProps interface. Updated TableBase to conditionally apply corresponding CSS modifier classes. Added six new test cases verifying the modifiers are applied only when props are truthy.
Documentation & Example
packages/react-table/src/components/Table/examples/Table.md, packages/react-table/src/components/Table/examples/TableStickyHeaderDynamic.tsx
Added documentation section explaining new sticky header props and their relationship to isStickyHeader. Introduced new TableStickyHeaderDynamic example component that demonstrates dynamic sticky header behavior using a custom useIsStuckFromScrollParent hook to track scroll position and conditionally apply the stuck modifier.

Sequence Diagram

sequenceDiagram
    participant ScrollParent as Scroll Parent Container
    participant UseHook as useIsStuckFromScrollParent Hook
    participant TableComponent as TableStickyHeaderDynamic Component
    participant Table as Table Component
    participant DOM as DOM (styled output)

    Note over ScrollParent,DOM: Initialization & Scroll Event Handling

    TableComponent->>UseHook: Provide scrollParent ref & shouldTrack=true
    UseHook->>ScrollParent: Read initial scrollTop value
    UseHook->>UseHook: Initialize isStuck state (scrollTop > 0)
    UseHook->>ScrollParent: Attach passive scroll event listener

    ScrollParent->>UseHook: Dispatch scroll event (scrollTop updated)
    UseHook->>UseHook: Update isStuck state based on scrollTop
    UseHook->>TableComponent: Return updated isStuck value

    TableComponent->>Table: Pass isStickyHeaderBase=true & isStickyHeaderStuck=isStuck
    Table->>DOM: Conditionally apply stickyHeaderBase & stickyHeaderStuck modifiers
    DOM-->>ScrollParent: Render with updated sticky styling

    Note over UseHook,DOM: Cleanup on unmount
    UseHook->>ScrollParent: Remove scroll event listener
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • thatblindgeye
  • mcoker
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Out of Scope Changes check ❓ Inconclusive The PR implements sticky headers fully but only partially addresses sticky columns (prop additions present but example/documentation scope unclear for columns). Clarify whether sticky columns implementation is deferred to a future PR or included; update documentation to reflect actual scope of this PR.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main feature: adding support for dynamic sticky styling to the Table component through new props.
Linked Issues check ✅ Passed All coding requirements from issue #12329 are met: sticky-unstuck and sticky-stuck props added, example with generic hook provided, and documentation included.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@patternfly-build
Copy link
Copy Markdown
Collaborator

patternfly-build commented Apr 10, 2026

@kmcfaul kmcfaul force-pushed the sticky-table-updates branch from 0668f05 to 4316b40 Compare April 10, 2026 17:54
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@packages/react-table/src/components/Table/examples/TableStickyHeaderDynamic.tsx`:
- Around line 108-111: In the Tbody of the TableStickyHeaderDynamic component
replace the non-header cell currently rendered with <Th ...> (the state cell
where `{` ${fact.state}`}` is rendered) with a data cell component <Td ...> so
body rows use Td not Th; keep the same props (modifier="nowrap",
dataLabel={columnNames.state}) and children (BlueprintIcon and the state text)
to preserve styling and accessibility semantics.
- Line 84: The aria-label on the example component TableStickyHeaderDynamic.tsx
currently reads "Sticky columns and header table" but the demo only shows
dynamic sticky header behavior; update the aria-label prop (the JSX attribute
aria-label on the TableStickyHeaderDynamic example component) to accurately
describe the example (e.g., something indicating "Dynamic sticky header table"
or similar) so it no longer references sticky columns.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b1443169-49cc-4b17-a403-8960c219ab96

📥 Commits

Reviewing files that changed from the base of the PR and between 911223a and 4316b40.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (9)
  • packages/react-core/package.json
  • packages/react-docs/package.json
  • packages/react-icons/package.json
  • packages/react-styles/package.json
  • packages/react-table/src/components/Table/InnerScrollContainer.tsx
  • packages/react-table/src/components/Table/Table.tsx
  • packages/react-table/src/components/Table/examples/Table.md
  • packages/react-table/src/components/Table/examples/TableStickyHeaderDynamic.tsx
  • packages/react-tokens/package.json

Copy link
Copy Markdown
Contributor

@mcoker mcoker left a comment

Choose a reason for hiding this comment

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

NICE

@kmcfaul kmcfaul closed this Apr 10, 2026
@kmcfaul kmcfaul reopened this Apr 10, 2026
@kmcfaul
Copy link
Copy Markdown
Contributor Author

kmcfaul commented Apr 10, 2026

Reopening to rerun Snyk

Copy link
Copy Markdown
Contributor

@thatblindgeye thatblindgeye left a comment

Choose a reason for hiding this comment

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

Nothing blocking for me below

Comment on lines +423 to +425
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.
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.

@edonehoo could you give this a quick look?

@kmcfaul kmcfaul force-pushed the sticky-table-updates branch from f5177ac to 133e4ec Compare April 13, 2026 15:37
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/react-table/src/components/Table/examples/TableStickyHeaderDynamic.tsx (1)

28-46: Use useEffect instead of useLayoutEffect for scroll-listener wiring.

This effect subscribes to scroll events and updates state—it doesn't perform DOM measurements or mutations. useEffect is the appropriate choice and avoids potential SSR-related warnings.

Proposed diff
-import { useLayoutEffect, useRef, useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
...
-  useLayoutEffect(() => {
+  useEffect(() => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/react-table/src/components/Table/examples/TableStickyHeaderDynamic.tsx`
around lines 28 - 46, The effect that wires the scroll listener uses
useLayoutEffect but only updates state (setIsStuck) and does not perform DOM
measurements; replace useLayoutEffect with useEffect in the component so the
scroll listener (syncFromScroll) attached to scrollParentRef.current runs in
useEffect, keeping the same cleanup and dependency array ([shouldTrack,
scrollParentRef]) and preserving logic around shouldTrack, scrollElement and
setIsStuck.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@packages/react-table/src/components/Table/examples/TableStickyHeaderDynamic.tsx`:
- Around line 17-25: The parameter type for scrollParentRef in
useIsStuckFromScrollParent is currently React.RefObject<any>, which loses DOM
type safety; change it to React.RefObject<HTMLDivElement> (or an appropriate
HTMLElement subtype) in the function signature and any related usages so callers
and TypeScript know the ref points to a div and you can safely call DOM methods
like addEventListener and scrollTop on scrollParentRef.current.

---

Nitpick comments:
In
`@packages/react-table/src/components/Table/examples/TableStickyHeaderDynamic.tsx`:
- Around line 28-46: The effect that wires the scroll listener uses
useLayoutEffect but only updates state (setIsStuck) and does not perform DOM
measurements; replace useLayoutEffect with useEffect in the component so the
scroll listener (syncFromScroll) attached to scrollParentRef.current runs in
useEffect, keeping the same cleanup and dependency array ([shouldTrack,
scrollParentRef]) and preserving logic around shouldTrack, scrollElement and
setIsStuck.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0f2cb079-959b-49c1-a113-58254b3eb6c9

📥 Commits

Reviewing files that changed from the base of the PR and between f5177ac and 133e4ec.

📒 Files selected for processing (5)
  • packages/react-table/src/components/Table/InnerScrollContainer.tsx
  • packages/react-table/src/components/Table/Table.tsx
  • packages/react-table/src/components/Table/__tests__/Table.test.tsx
  • packages/react-table/src/components/Table/examples/Table.md
  • packages/react-table/src/components/Table/examples/TableStickyHeaderDynamic.tsx
✅ Files skipped from review due to trivial changes (1)
  • packages/react-table/src/components/Table/tests/Table.test.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/react-table/src/components/Table/examples/Table.md
  • packages/react-table/src/components/Table/Table.tsx
  • packages/react-table/src/components/Table/InnerScrollContainer.tsx

Comment on lines +17 to +25
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 => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the ref type exposed/expected by InnerScrollContainer and align this example type.
fd -i "InnerScrollContainer.tsx"
rg -n -C3 "forwardRef|InnerScrollContainer|RefObject|HTMLDivElement|HTMLElement" packages/react-table/src/components/Table/InnerScrollContainer.tsx
rg -n -C2 "useIsStuckFromScrollParent|scrollParentRef" packages/react-table/src/components/Table/examples/TableStickyHeaderDynamic.tsx

Repository: patternfly/patternfly-react

Length of output: 2319


Replace any on scrollParentRef with concrete DOM type for type safety.

Line 24 uses React.RefObject<any>, losing compile-time checks for DOM APIs like addEventListener. The ref is only used with HTMLDivElement instances.

Proposed diff
-  scrollParentRef: React.RefObject<any>;
+  scrollParentRef: React.RefObject<HTMLDivElement>;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/react-table/src/components/Table/examples/TableStickyHeaderDynamic.tsx`
around lines 17 - 25, The parameter type for scrollParentRef in
useIsStuckFromScrollParent is currently React.RefObject<any>, which loses DOM
type safety; change it to React.RefObject<HTMLDivElement> (or an appropriate
HTMLElement subtype) in the function signature and any related usages so callers
and TypeScript know the ref points to a div and you can safely call DOM methods
like addEventListener and scrollTop on scrollParentRef.current.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Table - add updated sticky props & example

4 participants