Skip to content

feat(Toolbar): dynamic sticky#12375

Merged
thatblindgeye merged 4 commits intopatternfly:mainfrom
kmcfaul:12332-toolbar-dynamic-sticky
Apr 28, 2026
Merged

feat(Toolbar): dynamic sticky#12375
thatblindgeye merged 4 commits intopatternfly:mainfrom
kmcfaul:12332-toolbar-dynamic-sticky

Conversation

@kmcfaul
Copy link
Copy Markdown
Contributor

@kmcfaul kmcfaul commented Apr 23, 2026

What: Closes #12332

  • Adds isStickyBase and isStickyStuck
  • Adds example using scroll event to dynamically apply isStickyStuck
  • Adds unit tests for new props
  • Bumps to core v.77 for styles

Waiting on patternfly/patternfly#8321

Summary by CodeRabbit

  • New Features

    • Introduced isStickyBase and isStickyStuck beta properties for finer control over sticky toolbar positioning and visual feedback when the toolbar is "stuck" during scrolling.
  • Documentation

    • Added dynamic sticky toolbar example demonstrating scroll-based sticky behavior integration.
  • Tests

    • Added comprehensive test coverage for new sticky modifier behaviors.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 23, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Extends the Toolbar component with two new beta properties—isStickyBase and isStickyStuck—for enhanced sticky positioning control. Adds corresponding CSS modifier classes, test coverage for the new props, documentation updates, and a new example component with a custom hook demonstrating dynamic sticky behavior based on scroll position.

Changes

Cohort / File(s) Summary
Core Component
packages/react-core/src/components/Toolbar/Toolbar.tsx
Added isStickyBase and isStickyStuck beta props to ToolbarProps; wired both into renderToolbar to apply corresponding CSS modifier classes. Refined isSticky prop documentation.
Component Tests
packages/react-core/src/components/Toolbar/__tests__/Toolbar.test.tsx
Added three new test cases verifying stickyBase and stickyStuck CSS modifier classes are applied only when their respective props are true.
Documentation & Examples
packages/react-core/src/components/Toolbar/examples/Toolbar.md, packages/react-core/src/components/Toolbar/examples/ToolbarDynamicSticky.tsx
Added "Dynamic sticky toolbar" documentation section referencing new example file. Introduced ToolbarDynamicSticky example component and custom useIsStuckFromScrollParent hook that synchronizes sticky "stuck" state with scroll parent scroll position.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

Needs design review

Suggested reviewers

  • wise-king-sullyman
  • dlabaj
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(Toolbar): dynamic sticky' accurately reflects the main change—adding dynamic sticky functionality to the Toolbar component with new props and an example.
Linked Issues check ✅ Passed All primary coding objectives from issue #12332 are met: new sticky props (isStickyBase, isStickyStuck) added to Toolbar, dynamic example with hook implemented, unit tests added, and documentation updated.
Out of Scope Changes check ✅ Passed All changes are directly related to the linked issue objectives. Modifications to Toolbar.tsx add the required props, tests validate the new functionality, and the new example demonstrates dynamic sticky behavior as specified.
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.

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: 3

🤖 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-core/src/components/Toolbar/examples/Toolbar.md`:
- Around line 47-53: Update the Dynamic sticky toolbar docs to note
scroll-parent guidance: explain that isStickyBase controls the CSS sticky
positioning but isStickyStuck must be driven by scroll state from the same
scrollable ancestor that creates the sticky context, and add a short sentence in
the Toolbar.md example (and mention ToolbarDynamicSticky.tsx) instructing
consumers to attach their scroll listener or intersection observer to the
scrollable container (not window) so isStickyStuck reflects that container's
scroll position.

In
`@packages/react-core/src/components/Toolbar/examples/ToolbarDynamicSticky.tsx`:
- Around line 43-44: The demo array currently produces 0-29 so the UI shows
"item 0"; change the array generation used by the constants array and numbers so
it yields 1-30 instead of 0-29 (adjust the mapping in the Array.from call used
to create array), keeping the existing showEvenOnly filter logic (numbers =
showEvenOnly ? array.filter(number => number % 2 === 0) : array) unchanged so
even-only behavior still works with the new 1-30 range.
- Line 47: The scroll container div using scrollParentRef is not
keyboard-focusable or labeled; update the element referenced by scrollParentRef
to be reachable and announced by assistive tech by adding a focusable attribute
(e.g., tabIndex={0}) and an accessible name (e.g., role="region" with aria-label
or aria-labelledby) so keyboard users can focus and scroll the example content;
ensure these attributes are applied to the same div that has style={{ overflowY:
'scroll', height: '200px' }} and preserve existing refs and styles.
🪄 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: b742df8a-4376-410d-9592-1f4f41533973

📥 Commits

Reviewing files that changed from the base of the PR and between a426464 and 08eb1ce.

📒 Files selected for processing (4)
  • packages/react-core/src/components/Toolbar/Toolbar.tsx
  • packages/react-core/src/components/Toolbar/__tests__/Toolbar.test.tsx
  • packages/react-core/src/components/Toolbar/examples/Toolbar.md
  • packages/react-core/src/components/Toolbar/examples/ToolbarDynamicSticky.tsx

Comment thread packages/react-core/src/components/Toolbar/examples/Toolbar.md
const numbers = showEvenOnly ? array.filter((number) => number % 2 === 0) : array;

return (
<div ref={scrollParentRef} style={{ overflowY: 'scroll', height: '200px' }}>
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 | 🟠 Major

Make the scroll container keyboard accessible.

This fixed-height overflow container is the primary interaction target for demonstrating sticky behavior, but it is not focusable or named. Keyboard users may not be able to scroll the example content.

♿ Proposed fix
-    <div ref={scrollParentRef} style={{ overflowY: 'scroll', height: '200px' }}>
+    <div
+      ref={scrollParentRef}
+      role="region"
+      aria-label="Scrollable dynamic sticky toolbar example"
+      tabIndex={0}
+      style={{ overflowY: 'scroll', height: '200px' }}
+    >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div ref={scrollParentRef} style={{ overflowY: 'scroll', height: '200px' }}>
<div
ref={scrollParentRef}
role="region"
aria-label="Scrollable dynamic sticky toolbar example"
tabIndex={0}
style={{ overflowY: 'scroll', height: '200px' }}
>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react-core/src/components/Toolbar/examples/ToolbarDynamicSticky.tsx`
at line 47, The scroll container div using scrollParentRef is not
keyboard-focusable or labeled; update the element referenced by scrollParentRef
to be reachable and announced by assistive tech by adding a focusable attribute
(e.g., tabIndex={0}) and an accessible name (e.g., role="region" with aria-label
or aria-labelledby) so keyboard users can focus and scroll the example content;
ensure these attributes are applied to the same div that has style={{ overflowY:
'scroll', height: '200px' }} and preserve existing refs and styles.

@patternfly-build
Copy link
Copy Markdown
Collaborator

patternfly-build commented Apr 23, 2026

@kmcfaul kmcfaul force-pushed the 12332-toolbar-dynamic-sticky branch from 08eb1ce to b38a419 Compare April 24, 2026 16:51
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-core/src/components/Toolbar/examples/ToolbarDynamicSticky.tsx (1)

11-11: Tighten the scrollParentRef type.

React.RefObject<any> forgoes type safety. Since the consumer passes an HTMLDivElement ref and the hook only reads scrollTop/addEventListener, typing it as HTMLElement (or making the hook generic) would better document intent and catch misuse.

♻️ Proposed refactor
-  scrollParentRef: React.RefObject<any>;
+  scrollParentRef: React.RefObject<HTMLElement | null>;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react-core/src/components/Toolbar/examples/ToolbarDynamicSticky.tsx`
at line 11, The scrollParentRef is currently typed as React.RefObject<any>;
tighten it to a more specific DOM type to improve safety — change
scrollParentRef: React.RefObject<any> to React.RefObject<HTMLElement> (or
React.RefObject<HTMLDivElement> if the consumer always passes a div), since the
code only reads scrollTop and uses addEventListener; alternatively make the hook
generic (e.g., <T extends HTMLElement>) and use React.RefObject<T> so callers
can supply the precise element type.
🤖 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-core/src/components/Toolbar/examples/ToolbarDynamicSticky.tsx`:
- Around line 42-44: The SearchInput state (searchValue / setSearchValue) is not
used to filter the rendered list: numbers is computed only from array and
showEvenOnly so typing does nothing; fix by applying searchValue when computing
numbers (e.g., update the numbers definition in this file to include a filter
like .filter(n => n.toString().includes(searchValue)) after the showEvenOnly
filter) or remove the SearchInput state and control if you prefer; ensure you
update the symbols searchValue, setSearchValue and numbers (and the initial
array) so the rendered list reflects the search input.

---

Nitpick comments:
In
`@packages/react-core/src/components/Toolbar/examples/ToolbarDynamicSticky.tsx`:
- Line 11: The scrollParentRef is currently typed as React.RefObject<any>;
tighten it to a more specific DOM type to improve safety — change
scrollParentRef: React.RefObject<any> to React.RefObject<HTMLElement> (or
React.RefObject<HTMLDivElement> if the consumer always passes a div), since the
code only reads scrollTop and uses addEventListener; alternatively make the hook
generic (e.g., <T extends HTMLElement>) and use React.RefObject<T> so callers
can supply the precise element type.
🪄 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: 7887a63e-110c-415d-88c5-d2c3da0c31d8

📥 Commits

Reviewing files that changed from the base of the PR and between b38a419 and 57df9be.

📒 Files selected for processing (2)
  • packages/react-core/src/components/Toolbar/examples/Toolbar.md
  • packages/react-core/src/components/Toolbar/examples/ToolbarDynamicSticky.tsx
✅ Files skipped from review due to trivial changes (1)
  • packages/react-core/src/components/Toolbar/examples/Toolbar.md

Comment on lines +42 to +44
const [searchValue, setSearchValue] = useState('');
const array = Array.from(Array(30), (_, x) => x); // create array of numbers from 1-30 for demo purposes
const numbers = showEvenOnly ? array.filter((number) => number % 2 === 0) : array;
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

searchValue has no effect on the rendered list.

The SearchInput is wired to searchValue/setSearchValue, but numbers is filtered only by showEvenOnly — typing in the search input does nothing visible. For a docs example this is likely to confuse readers about how the toolbar controls relate to the content. Either drop the SearchInput state plumbing or apply it to the rendered list.

🐛 Proposed fix
-  const numbers = showEvenOnly ? array.filter((number) => number % 2 === 0) : array;
+  const filteredByEven = showEvenOnly ? array.filter((number) => number % 2 === 0) : array;
+  const numbers = searchValue
+    ? filteredByEven.filter((number) => `item ${number}`.includes(searchValue))
+    : filteredByEven;

Also applies to: 51-56, 69-71

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react-core/src/components/Toolbar/examples/ToolbarDynamicSticky.tsx`
around lines 42 - 44, The SearchInput state (searchValue / setSearchValue) is
not used to filter the rendered list: numbers is computed only from array and
showEvenOnly so typing does nothing; fix by applying searchValue when computing
numbers (e.g., update the numbers definition in this file to include a filter
like .filter(n => n.toString().includes(searchValue)) after the showEvenOnly
filter) or remove the SearchInput state and control if you prefer; ensure you
update the symbols searchValue, setSearchValue and numbers (and the initial
array) so the rendered list reflects the search input.

@kmcfaul kmcfaul force-pushed the 12332-toolbar-dynamic-sticky branch from 57df9be to 7df7cb2 Compare April 27, 2026 17:56
@kmcfaul kmcfaul force-pushed the 12332-toolbar-dynamic-sticky branch from 7df7cb2 to 514cf53 Compare April 28, 2026 14:51
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.

🧹 Nitpick comments (1)
packages/react-core/src/components/Toolbar/Toolbar.tsx (1)

179-181: Define precedence when legacy and beta sticky props are combined.

If consumers pass isSticky, isStickyBase, and isStickyStuck together, all three modifier classes are applied. Consider enforcing precedence (or documenting it explicitly) to avoid ambiguous styling combinations.

♻️ Suggested precedence guard
           <div
+            // Legacy `isSticky` takes precedence over beta split modifiers.
+            // This prevents mixed sticky class combinations.
             className={css(
               styles.toolbar,
               hasNoPadding && styles.modifiers.noPadding,
               isFullHeight && styles.modifiers.fullHeight,
               isStatic && styles.modifiers.static,
               isSticky && styles.modifiers.sticky,
-              isStickyBase && styles.modifiers.stickyBase,
-              isStickyStuck && styles.modifiers.stickyStuck,
+              !isSticky && isStickyBase && styles.modifiers.stickyBase,
+              !isSticky && isStickyStuck && styles.modifiers.stickyStuck,
               isVertical && styles.modifiers.vertical,
               formatBreakpointMods(inset, styles, '', getBreakpoint(width)),
               colorVariant === 'primary' && styles.modifiers.primary,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react-core/src/components/Toolbar/Toolbar.tsx` around lines 179 -
181, Toolbar currently applies all three sticky modifier classes when isSticky,
isStickyBase, and isStickyStuck are passed; change the class selection to
enforce a clear precedence (e.g., isStickyStuck > isStickyBase > isSticky) so
only one modifier from styles.modifiers.{sticky, stickyBase, stickyStuck} is
applied. Locate the className/composition logic in the Toolbar component
(variables isSticky, isStickyBase, isStickyStuck) and replace the boolean OR
application with a precedence check (if isStickyStuck use
styles.modifiers.stickyStuck else if isStickyBase use
styles.modifiers.stickyBase else if isSticky use styles.modifiers.sticky).
Update any relevant tests or documentation strings to reflect the enforced
precedence.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/react-core/src/components/Toolbar/Toolbar.tsx`:
- Around line 179-181: Toolbar currently applies all three sticky modifier
classes when isSticky, isStickyBase, and isStickyStuck are passed; change the
class selection to enforce a clear precedence (e.g., isStickyStuck >
isStickyBase > isSticky) so only one modifier from styles.modifiers.{sticky,
stickyBase, stickyStuck} is applied. Locate the className/composition logic in
the Toolbar component (variables isSticky, isStickyBase, isStickyStuck) and
replace the boolean OR application with a precedence check (if isStickyStuck use
styles.modifiers.stickyStuck else if isStickyBase use
styles.modifiers.stickyBase else if isSticky use styles.modifiers.sticky).
Update any relevant tests or documentation strings to reflect the enforced
precedence.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 629299f0-d07e-4af9-8bb8-8806a0793bf0

📥 Commits

Reviewing files that changed from the base of the PR and between 7df7cb2 and 514cf53.

📒 Files selected for processing (4)
  • packages/react-core/src/components/Toolbar/Toolbar.tsx
  • packages/react-core/src/components/Toolbar/__tests__/Toolbar.test.tsx
  • packages/react-core/src/components/Toolbar/examples/Toolbar.md
  • packages/react-core/src/components/Toolbar/examples/ToolbarDynamicSticky.tsx
✅ Files skipped from review due to trivial changes (1)
  • packages/react-core/src/components/Toolbar/examples/Toolbar.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/react-core/src/components/Toolbar/tests/Toolbar.test.tsx
  • packages/react-core/src/components/Toolbar/examples/ToolbarDynamicSticky.tsx

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.

Just one small nit, otherwise LGTM

Comment thread packages/react-core/src/components/Toolbar/examples/ToolbarDynamicSticky.tsx Outdated
@thatblindgeye thatblindgeye merged commit cb3856a into patternfly:main Apr 28, 2026
13 of 14 checks passed
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.

Toolbar - add updated sticky props & example

4 participants